From f9139d6aa3a33c721e9fc3601969f12d4e1473a0 Mon Sep 17 00:00:00 2001 From: serversdwn Date: Thu, 25 Dec 2025 00:36:46 +0000 Subject: [PATCH] feat: Add comprehensive NL-43/NL-53 Communication Guide and command references - Introduced a new communication guide detailing protocol basics, transport modes, and a quick startup checklist. - Added a detailed list of commands with their functions and usage for NL-43/NL-53 devices. - Created a verified quick reference for command formats to prevent common mistakes. - Implemented an improvements document outlining critical fixes, security enhancements, reliability upgrades, and code quality improvements for the SLMM project. - Enhanced the frontend with a new button to retrieve all device settings, along with corresponding JavaScript functionality. - Added a test script for the new settings retrieval API endpoint to demonstrate its usage and validate functionality. --- FEATURE_SUMMARY.md | 195 +++++++++++++ README.md | 27 ++ SETTINGS_ENDPOINT.md | 258 +++++++++++++++++ UI_UPDATE.md | 171 ++++++++++++ app/__pycache__/routers.cpython-310.pyc | Bin 21902 -> 27214 bytes app/__pycache__/services.cpython-310.pyc | Bin 18645 -> 22824 bytes app/routers.py | 219 +++++++++++++++ app/services.py | 140 ++++++++++ data/slmm.log | 15 + API.md => docs/API.md | 261 +++++++++++++++++- .../COMMUNICATION_GUIDE.md | 0 IMPROVEMENTS.md => docs/IMPROVEMENTS.md | 0 NL43_COMMANDS.md => docs/NL43_COMMANDS.md | 0 NL43_quickref.md => docs/NL43_quickref.md | 0 .../nl43_Command_ref.md | 0 templates/index.html | 24 ++ test_settings_endpoint.py | 98 +++++++ 17 files changed, 1395 insertions(+), 13 deletions(-) create mode 100644 FEATURE_SUMMARY.md create mode 100644 SETTINGS_ENDPOINT.md create mode 100644 UI_UPDATE.md rename API.md => docs/API.md (59%) rename COMMUNICATION_GUIDE.md => docs/COMMUNICATION_GUIDE.md (100%) rename IMPROVEMENTS.md => docs/IMPROVEMENTS.md (100%) rename NL43_COMMANDS.md => docs/NL43_COMMANDS.md (100%) rename NL43_quickref.md => docs/NL43_quickref.md (100%) rename nl43_Command_ref.md => docs/nl43_Command_ref.md (100%) create mode 100644 test_settings_endpoint.py diff --git a/FEATURE_SUMMARY.md b/FEATURE_SUMMARY.md new file mode 100644 index 0000000..f311f0d --- /dev/null +++ b/FEATURE_SUMMARY.md @@ -0,0 +1,195 @@ +# Feature Summary: Device Settings Verification + +## What Was Added + +A new API endpoint that retrieves all current device settings in a single request, allowing users to quickly verify the NL43/NL53 configuration before starting measurements. + +## New Endpoint + +**`GET /api/nl43/{unit_id}/settings`** + +Returns comprehensive device configuration including: +- Measurement state and weighting settings +- Timing and interval configuration +- Battery level and device clock +- Sleep mode and FTP status + +## Files Modified + +### 1. [app/routers.py](app/routers.py) +**Lines:** 728-761 + +Added new route handler `get_all_settings()` that: +- Validates device configuration exists +- Checks TCP communication is enabled +- Calls `NL43Client.get_all_settings()` +- Returns formatted JSON response with all settings +- Handles connection errors, timeouts, and exceptions + +### 2. [README.md](README.md) +**Updated sections:** +- Line 134: Added new endpoint to Measurement Settings table +- Lines 259-283: Added usage example showing how to verify device settings + +## Files Created + +### 1. [test_settings_endpoint.py](test_settings_endpoint.py) +Test/demonstration script showing: +- How to use the `get_all_settings()` method +- Example API endpoint usage with curl +- Expected response format + +### 2. [SETTINGS_ENDPOINT.md](SETTINGS_ENDPOINT.md) +Comprehensive documentation including: +- Detailed endpoint description +- Complete list of settings retrieved +- Usage examples in bash, Python, and JavaScript +- Performance considerations +- Best practices and troubleshooting + +### 3. [FEATURE_SUMMARY.md](FEATURE_SUMMARY.md) +This file - summary of changes for reference + +## Existing Functionality Used + +The implementation leverages the existing `get_all_settings()` method in [app/services.py](app/services.py#L538) which was already implemented but not exposed via the API. This method queries multiple device settings and handles errors gracefully. + +## How It Works + +1. **User makes GET request** to `/api/nl43/{unit_id}/settings` +2. **Router validates** device configuration exists and TCP is enabled +3. **NL43Client queries device** for each setting sequentially (with 1-second delays) +4. **Individual errors** are caught and returned as error strings +5. **Response returned** with all settings in JSON format + +## Usage Example + +```bash +# Quick verification before measurement +curl http://localhost:8100/api/nl43/NL43-001/settings + +# Response: +{ + "status": "ok", + "unit_id": "NL43-001", + "settings": { + "measurement_state": "Stop", + "frequency_weighting": "A", + "time_weighting": "F", + "measurement_time": "00:01:00", + "leq_interval": "1s", + "lp_interval": "125ms", + "index_number": "0", + "battery_level": "100%", + "clock": "2025/12/24,20:45:30", + "sleep_mode": "Off", + "ftp_status": "On" + } +} +``` + +## Benefits + +1. **Single request** - Get all settings at once instead of multiple API calls +2. **Pre-flight checks** - Verify configuration before starting measurements +3. **Documentation** - Easy to save configuration snapshots for audit trails +4. **Troubleshooting** - Quickly identify misconfigured settings +5. **Multi-device** - Compare settings across multiple devices + +## Performance Notes + +- **Query time:** ~10-15 seconds (due to required 1-second delays between commands) +- **Rate limiting:** Automatically enforced by NL43Client +- **Error handling:** Partial failures don't prevent other settings from being retrieved +- **Caching recommended:** Settings don't change frequently, cache for 5-10 minutes + +## Testing + +To test the new endpoint: + +1. **Start the server:** + ```bash + uvicorn app.main:app --reload --port 8100 + ``` + +2. **Configure a device** (if not already configured): + ```bash + curl -X PUT http://localhost:8100/api/nl43/test-meter/config \ + -H "Content-Type: application/json" \ + -d '{"host": "192.168.1.100", "tcp_port": 80, "tcp_enabled": true}' + ``` + +3. **Query settings:** + ```bash + curl http://localhost:8100/api/nl43/test-meter/settings + ``` + +4. **Check Swagger UI:** + - Navigate to http://localhost:8100/docs + - Find "GET /api/nl43/{unit_id}/settings" endpoint + - Click "Try it out" and test interactively + +## Integration Tips + +### Frontend Integration +```javascript +// React/Vue/Angular example +async function verifyDeviceBeforeMeasurement(unitId) { + const response = await fetch(`/api/nl43/${unitId}/settings`); + const { settings } = await response.json(); + + // Verify critical settings + if (settings.frequency_weighting !== 'A') { + alert('Warning: Frequency weighting not set to A'); + } + + // Check battery + const batteryPercent = parseInt(settings.battery_level); + if (batteryPercent < 20) { + alert('Low battery! Please charge device.'); + } + + return settings; +} +``` + +### Python Automation +```python +def ensure_correct_config(unit_id: str, required_config: dict): + """Verify device matches required configuration.""" + settings = get_device_settings(unit_id) + + mismatches = [] + for key, expected in required_config.items(): + actual = settings.get(key) + if actual != expected: + mismatches.append(f"{key}: expected {expected}, got {actual}") + + if mismatches: + raise ValueError(f"Configuration mismatch: {', '.join(mismatches)}") + + return True +``` + +## Future Enhancements + +Potential improvements for future versions: + +1. **Filtered queries** - Query parameter to select specific settings +2. **Diff mode** - Compare current settings to expected values +3. **Batch queries** - Get settings from multiple devices in one request +4. **Settings profiles** - Save/load common configuration profiles +5. **Change detection** - Track when settings were last modified + +## Support + +For questions or issues with this feature: +- See [SETTINGS_ENDPOINT.md](SETTINGS_ENDPOINT.md) for detailed documentation +- Check [README.md](README.md) for general API usage +- Review [COMMUNICATION_GUIDE.md](COMMUNICATION_GUIDE.md) for protocol details + +## Version Info + +- **Added:** December 24, 2025 +- **API Version:** Compatible with existing v1 API +- **Breaking Changes:** None - purely additive feature diff --git a/README.md b/README.md index edc286e..acc5c50 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ Logs are written to: | Method | Endpoint | Description | |--------|----------|-------------| +| GET | `/api/nl43/{unit_id}/settings` | Get all current device settings for verification | | GET | `/api/nl43/{unit_id}/frequency-weighting` | Get frequency weighting (A/C/Z) | | PUT | `/api/nl43/{unit_id}/frequency-weighting` | Set frequency weighting | | GET | `/api/nl43/{unit_id}/time-weighting` | Get time weighting (F/S/I) | @@ -255,6 +256,32 @@ curl -X POST http://localhost:8100/api/nl43/meter-001/start curl http://localhost:8100/api/nl43/meter-001/live ``` +### Verify Device Settings +```bash +curl http://localhost:8100/api/nl43/meter-001/settings +``` + +This returns all current device configuration: +```json +{ + "status": "ok", + "unit_id": "meter-001", + "settings": { + "measurement_state": "Stop", + "frequency_weighting": "A", + "time_weighting": "F", + "measurement_time": "00:01:00", + "leq_interval": "1s", + "lp_interval": "125ms", + "index_number": "0", + "battery_level": "100%", + "clock": "2025/12/24,20:45:30", + "sleep_mode": "Off", + "ftp_status": "On" + } +} +``` + ### Stream Real-time Data (JavaScript) ```javascript const ws = new WebSocket('ws://localhost:8100/api/nl43/meter-001/stream'); diff --git a/SETTINGS_ENDPOINT.md b/SETTINGS_ENDPOINT.md new file mode 100644 index 0000000..faa856a --- /dev/null +++ b/SETTINGS_ENDPOINT.md @@ -0,0 +1,258 @@ +# Device Settings Verification Endpoint + +## Overview + +The new `GET /api/nl43/{unit_id}/settings` endpoint provides a comprehensive view of all current device settings. This allows you to quickly verify the configuration of your NL43/NL53 sound level meter before starting measurements, ensuring the device is configured correctly for your testing requirements. + +## Endpoint Details + +**URL:** `GET /api/nl43/{unit_id}/settings` + +**Description:** Retrieves all queryable settings from the device in a single request. + +**Response Time:** Approximately 10-15 seconds (due to required 1-second delay between device commands) + +## Settings Retrieved + +The endpoint queries the following categories of settings: + +### Measurement Configuration +- **measurement_state**: Current state (Measure, Stop, Pause) +- **frequency_weighting**: Frequency weighting (A, C, or Z) +- **time_weighting**: Time weighting (F=Fast, S=Slow, I=Impulse) + +### Timing and Intervals +- **measurement_time**: Total measurement duration setting +- **leq_interval**: Leq calculation interval +- **lp_interval**: Lp sampling interval +- **index_number**: Current index/file number for storage + +### Device Information +- **battery_level**: Current battery percentage +- **clock**: Device clock time (format: YYYY/MM/DD,HH:MM:SS) + +### Operational Status +- **sleep_mode**: Sleep mode status (On/Off) +- **ftp_status**: FTP server status (On/Off) + +## Usage Examples + +### Basic Request + +```bash +curl http://localhost:8100/api/nl43/NL43-001/settings +``` + +### Response Format + +```json +{ + "status": "ok", + "unit_id": "NL43-001", + "settings": { + "measurement_state": "Stop", + "frequency_weighting": "A", + "time_weighting": "F", + "measurement_time": "00:01:00", + "leq_interval": "1s", + "lp_interval": "125ms", + "index_number": "0", + "battery_level": "100%", + "clock": "2025/12/24,20:45:30", + "sleep_mode": "Off", + "ftp_status": "On" + } +} +``` + +### Error Handling + +Individual settings that fail to query will show an error message instead of a value: + +```json +{ + "status": "ok", + "unit_id": "NL43-001", + "settings": { + "measurement_state": "Stop", + "frequency_weighting": "A", + "time_weighting": "Error: Status error - device is in wrong state for this command", + ... + } +} +``` + +This partial error handling ensures you get as much information as possible even if some settings fail to query. + +## Common Use Cases + +### Pre-Measurement Verification + +Before starting a measurement session, verify all critical settings: + +```bash +# Get all settings +SETTINGS=$(curl -s http://localhost:8100/api/nl43/meter-001/settings) + +# Extract specific values (using jq) +FREQ_WEIGHT=$(echo $SETTINGS | jq -r '.settings.frequency_weighting') +TIME_WEIGHT=$(echo $SETTINGS | jq -r '.settings.time_weighting') + +echo "Frequency: $FREQ_WEIGHT, Time: $TIME_WEIGHT" +``` + +### Configuration Audit + +Document device configuration for quality assurance: + +```bash +# Save settings snapshot +curl http://localhost:8100/api/nl43/meter-001/settings > config_snapshot_$(date +%Y%m%d_%H%M%S).json +``` + +### Multi-Device Comparison + +Compare settings across multiple devices: + +```bash +# Compare two devices +curl http://localhost:8100/api/nl43/meter-001/settings > device1.json +curl http://localhost:8100/api/nl43/meter-002/settings > device2.json +diff device1.json device2.json +``` + +## Integration Examples + +### Python + +```python +import requests + +def verify_device_settings(unit_id: str) -> dict: + """Retrieve and verify device settings.""" + response = requests.get(f"http://localhost:8100/api/nl43/{unit_id}/settings") + response.raise_for_status() + + data = response.json() + settings = data["settings"] + + # Verify critical settings + assert settings["frequency_weighting"] == "A", "Wrong frequency weighting!" + assert settings["time_weighting"] == "F", "Wrong time weighting!" + + return settings + +# Usage +settings = verify_device_settings("NL43-001") +print(f"Battery: {settings['battery_level']}") +print(f"Clock: {settings['clock']}") +``` + +### JavaScript/TypeScript + +```typescript +interface DeviceSettings { + measurement_state: string; + frequency_weighting: string; + time_weighting: string; + measurement_time: string; + leq_interval: string; + lp_interval: string; + index_number: string; + battery_level: string; + clock: string; + sleep_mode: string; + ftp_status: string; +} + +async function getDeviceSettings(unitId: string): Promise { + const response = await fetch(`http://localhost:8100/api/nl43/${unitId}/settings`); + const data = await response.json(); + + if (data.status !== "ok") { + throw new Error("Failed to retrieve settings"); + } + + return data.settings; +} + +// Usage +const settings = await getDeviceSettings("NL43-001"); +console.log(`Frequency weighting: ${settings.frequency_weighting}`); +console.log(`Battery level: ${settings.battery_level}`); +``` + +## Performance Considerations + +### Query Duration + +The endpoint queries multiple settings sequentially with required 1-second delays between commands. Total query time depends on: +- Number of settings queried (~10-12 settings) +- Network latency +- Device response time + +**Expected duration:** 10-15 seconds + +### Caching Strategy + +For applications that need frequent access to settings: + +1. **Cache results** - Settings don't change frequently unless you modify them +2. **Refresh periodically** - Query every 5-10 minutes or on-demand +3. **Track changes** - Re-query after sending configuration commands + +### Rate Limiting + +The endpoint respects device rate limiting (1-second delay between commands). Concurrent requests to the same device will be serialized automatically. + +## Best Practices + +1. **Pre-flight check**: Always verify settings before starting critical measurements +2. **Document configuration**: Save settings snapshots for audit trails +3. **Monitor battery**: Check battery level to avoid measurement interruption +4. **Sync clocks**: Verify device clock is accurate for timestamped data +5. **Error handling**: Check for "Error:" prefixes in individual setting values + +## Related Endpoints + +- `GET /api/nl43/{unit_id}/frequency-weighting` - Get single frequency weighting setting +- `PUT /api/nl43/{unit_id}/frequency-weighting` - Set frequency weighting +- `GET /api/nl43/{unit_id}/time-weighting` - Get single time weighting setting +- `PUT /api/nl43/{unit_id}/time-weighting` - Set time weighting +- `GET /api/nl43/{unit_id}/battery` - Get battery level only +- `GET /api/nl43/{unit_id}/clock` - Get device clock only + +## Troubleshooting + +### Slow Response + +**Problem:** Endpoint takes longer than expected + +**Solutions:** +- Normal behavior due to rate limiting (1 second between commands) +- Check network connectivity +- Verify device is not in sleep mode + +### Partial Errors + +**Problem:** Some settings show "Error:" messages + +**Solutions:** +- Device may be in wrong state for certain queries +- Check if measurement is running (some settings require stopped state) +- Verify firmware version supports all queried commands + +### Connection Timeout + +**Problem:** 504 Gateway Timeout error + +**Solutions:** +- Verify device IP address and port in configuration +- Check if device is powered on and connected +- Ensure TCP communication is enabled in device config + +## See Also + +- [README.md](README.md) - Main documentation +- [API.md](API.md) - Complete API reference +- [COMMUNICATION_GUIDE.md](COMMUNICATION_GUIDE.md) - NL43 protocol details diff --git a/UI_UPDATE.md b/UI_UPDATE.md new file mode 100644 index 0000000..6550b57 --- /dev/null +++ b/UI_UPDATE.md @@ -0,0 +1,171 @@ +# Standalone UI Update: Get ALL Settings Button + +## What Was Added + +A new **"Get ALL Settings"** button has been added to the standalone web UI at [templates/index.html](templates/index.html). + +## Location + +The button is located in the **Measurement Settings** fieldset, at the top before the individual frequency and time weighting controls. + +## Visual Layout + +``` +┌─────────────────────────────────────────────────────┐ +│ Measurement Settings │ +├─────────────────────────────────────────────────────┤ +│ │ +│ [Get ALL Settings] ← NEW BUTTON (bold styling) │ +│ │ +│ Frequency Weighting: │ +│ [Get] [Set A] [Set C] [Set Z] │ +│ │ +│ Time Weighting: │ +│ [Get] [Set Fast] [Set Slow] [Set Impulse] │ +│ │ +└─────────────────────────────────────────────────────┘ +``` + +## Functionality + +When clicked, the button: + +1. **Shows loading message**: "Retrieving all device settings (this may take 10-15 seconds)..." + +2. **Calls API**: `GET /api/nl43/{unit_id}/settings` + +3. **Displays results in two places**: + - **Status area** (top): Shows formatted JSON with all settings + - **Log area** (bottom): Shows each setting on a separate line + +## Example Output + +### Status Area Display +```json +{ + "measurement_state": "Stop", + "frequency_weighting": "A", + "time_weighting": "F", + "measurement_time": "00:01:00", + "leq_interval": "1s", + "lp_interval": "125ms", + "index_number": "0", + "battery_level": "100%", + "clock": "2025/12/24,20:45:30", + "sleep_mode": "Off", + "ftp_status": "On" +} +``` + +### Log Area Display +``` +Retrieving all device settings (this may take 10-15 seconds)... +=== ALL DEVICE SETTINGS === +measurement_state: Stop +frequency_weighting: A +time_weighting: F +measurement_time: 00:01:00 +leq_interval: 1s +lp_interval: 125ms +index_number: 0 +battery_level: 100% +clock: 2025/12/24,20:45:30 +sleep_mode: Off +ftp_status: On +=========================== +``` + +## Code Changes + +### HTML Changes (Line 60) +```html + +``` + +### JavaScript Changes (Lines 284-305) +```javascript +async function getAllSettings() { + const unitId = document.getElementById('unitId').value; + log('Retrieving all device settings (this may take 10-15 seconds)...'); + + const res = await fetch(`/api/nl43/${unitId}/settings`); + const data = await res.json(); + + if (!res.ok) { + log(`Get All Settings failed: ${res.status} - ${data.detail || JSON.stringify(data)}`); + return; + } + + // Display in status area + statusEl.textContent = JSON.stringify(data.settings, null, 2); + + // Log summary + log('=== ALL DEVICE SETTINGS ==='); + Object.entries(data.settings).forEach(([key, value]) => { + log(`${key}: ${value}`); + }); + log('==========================='); +} +``` + +## Usage Flow + +1. **Configure device** using the "Unit Config" section +2. **Click "Get ALL Settings"** to retrieve current configuration +3. **Review settings** in the Status and Log areas +4. **Verify** critical settings match requirements before starting measurements + +## Benefits + +✓ **Quick verification** - One click to see all device settings +✓ **Pre-measurement check** - Ensure device is configured correctly +✓ **Debugging** - Identify misconfigured settings easily +✓ **Documentation** - Copy settings from status area for records +✓ **Comparison** - Compare settings across multiple devices + +## Error Handling + +If the request fails: +- Error message is displayed in the log +- Status code and details are shown +- Previous status display is preserved + +Example error output: +``` +Retrieving all device settings (this may take 10-15 seconds)... +Get All Settings failed: 502 - Failed to communicate with device +``` + +## Testing the Feature + +1. **Start the SLMM server**: + ```bash + cd /home/serversdown/slmm + uvicorn app.main:app --reload --port 8100 + ``` + +2. **Open the standalone UI**: + ``` + http://localhost:8100 + ``` + +3. **Configure a device**: + - Enter Unit ID (e.g., "nl43-1") + - Enter Host IP (e.g., "192.168.1.100") + - Enter Port (e.g., "80") + - Click "Save Config" + +4. **Test the new button**: + - Click "Get ALL Settings" + - Wait 10-15 seconds for results + - Review settings in Status and Log areas + +## Related Files + +- [templates/index.html](templates/index.html) - Standalone UI (updated) +- [app/routers.py](app/routers.py#L728-L761) - Settings endpoint +- [app/services.py](app/services.py#L538-L606) - Client implementation +- [README.md](README.md) - Main documentation +- [SETTINGS_ENDPOINT.md](SETTINGS_ENDPOINT.md) - API documentation diff --git a/app/__pycache__/routers.cpython-310.pyc b/app/__pycache__/routers.cpython-310.pyc index 833ee9363b1a626f8f437a65a6b634a53323dacc..9101142c720172f5702e4829eb530de65c61b47c 100644 GIT binary patch delta 3853 zcmbtXdu)@}75Dx8h({haDzHwST837QiLEWLE$bwvwcDyy71FfFAJepRuI>0U zDg9?we0=Xck9+R<-E;2AbLYv;TcjjjTs+SLpYbQRYVF^9q2wXulH+3C8#eJ0DV<<6 ze9V57JhhH3nOa5%rwQH56jpkagca&$D%IdSpM5Uup!?{4DY?GOLL)SK%tE8A=&1Fn z4w%Pi9OiL3-vIL=It=q+Iqw9^D=D8Q8M{QL#V`)AXQR>z2C72kMT~LsD+D)SZyJhv}E)+7_uh@j%@p z^r)2EDs_)Z-LKHE%C#P;du)#GhK$c&qt8gWUa9+a)OCF$3j=lZSv=oNZML_rRQ`TN zjMaZ3^?PrW*e3zNvm)lN63pi!lOo}Jj+{PGSXDj+jH|?A(H$_DXK@lY;Zq_|U*mWQ z7WnDZ7wgv&a$0=5;WW@{Un!XuJN#ATtk~!GI?jSj$SN*)DpQyI*NJ0V>ImYiRQPwq zI&UdCFJ5W%0=?6?hMWqaYiEw%r#bB@YO$Vmb@XXUGL{3w#kDQ`yyAy-6v zLlyb4cxJ6&!6uGKSU`%?#c>T*CKd6eW!2(dR{^;u z{@v9=-b&SP@)6f{;9XQe<7cRLJ+-fUxkBC+$G3PFz6~qM(uSuKG0hmz=y-$93`2_z z>f(o4cs>P5)j^SK%+Avv4AkJM$Rs_^HR(!yqtB+^r}t(Cg&}VmeUMpTxj$ z4fdxp1bJaZ+*JNJpy;vkbqFussu^ciD7koJP>X=35myHRmdv_P+be39!0}l;VITve zl4a`BSsq1eF(Ap6VxWCtL00up`)0ozR0aUzf$TnfD@ykuF_0vXj35~Wa!zdbl#3^O z7ggVY*>B-0>h&;C4p$V}ND*-;MWjkmNC~9+iPkEc@?U-SLT-xTp4&h%%YTJg zZhr^#{v!_bddWNDOmEG?;xvZagJY3+kj^;Z-hfZN{6#1wu6IGW)Cx|VgaLmS6?{eA zG0F~P^hu-Q{Hnsfi;s zh3YRCe_2&Q-oxOReE|M;=o-V>1va521u^#G`6N+s{dytM((<=*TQuI7?QT(HhDFYw6NFu!f*^L+#B)dPjp-xM~Yl6-I_~&@8DHVp z`GGaLECXwj%fxkoOU5;K%)GFAKL%f4o((I`@{&N7PhTt%-NQI0L0w)FNUoaZ%10#( zZ&79r501!0*;6D12hxJ~XMouVW`Ij(Ku$vhw^WF2o(hHEmPP6l@bUH8SmR8_x>C{a zV)seP#QWYw6SVN8ZBn5{r<6=LQ#`hVSjO$dl5`q2T5OaYg#RL_?YMG;Q0KV)kbRUu z&*K`4XQWCE_tbn^a?o;5#|QHy7O(vL7p&mVqpRMbcO@bR_6 z2hr_GmUZJBl~q6(q1w$@{moRCJpMZb<-QCu2QB@a?*l2mUraAtyF_MEE`W^l+sJ$v zNdPKfDUqG>wtKK}6C$bCzyKa=tNF2BKq};e?L1rpBR6p#hG!c!^6m$CD;ur`Xcq8( z)OaxOusQsUrcfMTQJZ!O|Na zZk#Zw*--a#~C{?~U=Ys%f&87yz%V+BB!?r7uB?^(tzI+BpytfY#G>lIl~t z1|+phI}|XqR%z)IIqH=l9QYvWOVX+id{@=(sIOX88YNec{#7}iBl{OIq3FEs&+mrV z5gKC(B*~B83y#1D_U1XzRVW~cnMtYkF;bUURj4paCa{4R!;Ql1*a;#;kO05959`*V zOdv~f@nbu4qJp88p^sr4FEypYB+4bZgo76y`k9h7P{vscqPsm6Mlo2J2qCO0%_Zz9 zJqC|(x>Tdg__F)(gjMSdwSS{w#1#8xC?_4Jaj(n^PqD5%@L!yDmim6O;Taod^|*>T z1BCVE6SW4I*A1saO3EB7{RAPypP|c{SkNOi)nI%7xD zZ}s*1e6U8SD@5te>#yA}0XFbj<43|KcV5gUwSU4JO}VfcJ;e!6 zrFDzAea?Nzxm)_ar$h!HW;I_RQ-AZMZG-sFruH_!*3;IliES)sxehxx-QvPMk5T?1 z<8Di(vP)gA8XWGpsl4Z)Wps2#&hpg}d_%+}Lx_RTNbtUkFvC1Ubl+CE;>{4eR>5l% dJh9*r#V*~p6zua^;Q`+94VkjQmTWR+!*73?=#c;b diff --git a/app/__pycache__/services.cpython-310.pyc b/app/__pycache__/services.cpython-310.pyc index 79b077ca8866e7c8915b7fb0feb24263f7a8df97..79721337b0286a8a7a915ce4aab135628c7d6164 100644 GIT binary patch delta 4597 zcmb_fYj9h|6~24*v~0`958?+VH#UhRVjDTJGhp02>e!CKamWJ*=&v#p=5`n|X<8d-Ljp|;>YJvd(?{qNXiI-+rZZ()Nc*QVjHi@lrjwqtSGr!= zSEWCykv{Ee&whK(+3)UIz4J7A@m1nYR91Q7;kS$T}GLn`=)7jAi->y8V=Wd_A8k z(;ajt^c331$9BuJ^Pfk9>(z&%GpaJDMklAVXna!fvkPdv z0pSe7MLuo6uYtU*KjP~nm-O?#FK+x64t*WLSm)c={071+2&WNVyeI~2B1ED#$bmC`=Af!6GioTAV(N?<*Axu{Fj({}zLt#9>{WPRMn~~?1(WSc*G#+8 z1D~!S0=BmD)>ON4Cw#Va10#Doyr1jhDz0^`qtQF{I8NcYzM zSQMh}w6Y9?rTK!VN3y}^$M9Kh(%<&iXBNmJS&|@&w)De8ki~F{G0dQ_qL|H2j~Vg- zRZFpWvRCQPPZ5%?fGa2+(>T4sIzROqPf#efqtY`9fhWK-Hl}xUHVoBGS_J+_2xr*| zM)JvOhnp0IZ)MLl)Uf#tm;pMuh{+#Ok163$ES!pkv}hu(MB|#uj)!7adKoSrQ~diP z5!A~6Q8?V6e!(ADv&V4AP*6cRbp_{5hl^ChevWdamG})qAE?npa6hv??hR z_Df8iEj=lUqjUTg1=*&zX4~{Vw|W6 z(aO+6OI{Btp3+$bo^7U@LMS>h4i*iW0+;~>Z0SMl3I=({`07duXF|=$tYXZEJ>?-3 z_Odp=u$hciTPf*Bn`<&vd@pCst^Cm3%BIy@xnlIOJNaPIPOc7K*t+30Pk3>j)^1(h znt;vCYS>=I_hQA-8|H?I(X2AcIihi@K5CSLO!6@7@8g7$1!9MTY??4Hm8VIvR)b3L zS@Hx_ghFdUS|rrIK&a!aojT9imdLO~U3(<3kQZ&#ogXQKYGOC5i3`p_PkIVBhcBzd zQ!__ZrbM6uM4~abn*Tw+>{Bw_qE{J4{|biwAiO&6g4c=+7{n<>t9+o$Hp6%ZiivVm zg|{}Rs&NG;DWN!3aALaBFdF4cx94oZZfX?NG(Q3Ovantq_pG3sfP+oKGWzLT>dFH7 z`tiG!1A$MPFrslm6-au)SUy?fbQAf8n>3MINzr42!V~9wtf+=XQ59T74+jK6t4Z$AbS1SdWMt5ZTrhYD%umRV$#IAAJxAswP9+Oec7bX{peiu& z@sdn7fyvP)WlXP*Sx|w&m{gL=E>JlIr=GE3;^P)f86!T2K;;ssUbCR$;}%o~lT%=F z3rv5qV1jXoza*7Qpeo~3O}Kxj$-L8oj9ZlCatmA@fh$Ai+bzhrNlCIYfvj90Bl8C= zxVS|ruFRrGAgd@Md(?uATa+SOEEl*ci@3gI!G#tX%k*m%iGa74!n~u5I9Av z(rkZ9W%EiX7E`EtJQ`LDSsA<} z<|@SVh1J4xIY&5_2v6rc$(X9nPRt~zn)5`o*@>hU(o)HEGaP#?svcJDjlUR<13yR5%;=bQ2oSzo$qS)P*hXL_%?*t-zHtej2UeaS}N&)&M@4&wVM zX5w=Q|3Fwq_y8bR2Blqvau-R!ACPOhwzE|}B|rb{PIB`#v~l1)r4w5%2zH`h+OfhVFYyF2f@=t^<2p=KzAZ$bUm)?HOk0+-&~*y8$o2U}c( xf5z5d5Z*(07r}wxM!%~OTJ))*TgZt1oz~-UblNN`~9J2@*g7n^v3`I delta 786 zcmY+;OH30{6b9fq(?aW`O~D`*R9dhvu{1(-p{O({@=C3d7VU-@@3c-pFw^>2xzNTK zH)76;L}Ow_sUiv^nkYU<5Cr26jSeh~5`m}-WMgpSJ!N5>#dpu#^WSrC&j7r?3g!-z zX{EqF|K$cbc%b*M9>^Xi#f(lL{yi|Uz&ZBAxI@>o3%8`k{S zDPum~$>*JULv175l@uaHY&YA(S6k+=$f}q>sgKxM1FOyqES9WRfe+ow_VJ@7ncbSq zHTzjDPi|u3A;HtA{xPBvH1$f()?A3I!zBTLlxlH=!P3niza)CLC^vW!Q10Mqr#-uu z*GdYnJI}%`T;~OOt#;vYnCS*ztti9<#ZX9tzrtE+UOXu2Q&%bFNCbj}G@W z$0OSABbMh6Qu%;*NOThY_|07ks+#Lrt%qk=%$dUhrmmX31$`=iyX`S+edJ`yzoKT6Kp)S`vC9;&AnSCOY z_(j#f7cB@F3>QEJwuWm;Zqf-*#%XaV>nLd@3W!4DGfsxXfxA==5RZu`98J&UV+rLc n{Z|t&h?m4`Vw4yo-Vj;DcVbb%?q(b8z|rOk(b%e*4i~~-^K;*9 diff --git a/app/routers.py b/app/routers.py index 3b1509e..c9f17db 100644 --- a/app/routers.py +++ b/app/routers.py @@ -725,6 +725,42 @@ async def get_ftp_status(unit_id: str, db: Session = Depends(get_db)): raise HTTPException(status_code=500, detail=f"Failed to get FTP status: {str(e)}") +@router.get("/{unit_id}/settings") +async def get_all_settings(unit_id: str, db: Session = Depends(get_db)): + """Get all current device settings for verification. + + Returns a comprehensive view of all device configuration including: + - Measurement state and weightings + - Timing and interval settings + - Battery level and clock + - Sleep and FTP status + + This is useful for verifying device configuration before starting measurements. + """ + cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first() + if not cfg: + raise HTTPException(status_code=404, detail="NL43 config not found") + + if not cfg.tcp_enabled: + raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") + + client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) + try: + settings = await client.get_all_settings() + logger.info(f"Retrieved all settings for unit {unit_id}") + return {"status": "ok", "unit_id": unit_id, "settings": settings} + + except ConnectionError as e: + logger.error(f"Failed to get settings for {unit_id}: {e}") + raise HTTPException(status_code=502, detail="Failed to communicate with device") + except TimeoutError: + logger.error(f"Timeout getting settings for {unit_id}") + raise HTTPException(status_code=504, detail="Device communication timeout") + except Exception as e: + logger.error(f"Unexpected error getting settings for {unit_id}: {e}") + raise HTTPException(status_code=500, detail="Internal server error") + + @router.get("/{unit_id}/ftp/files") async def list_ftp_files(unit_id: str, path: str = "/", db: Session = Depends(get_db)): """List files on the device via FTP. @@ -748,6 +784,14 @@ async def list_ftp_files(unit_id: str, path: str = "/", db: Session = Depends(ge raise HTTPException(status_code=500, detail="Internal server error") +class TimingPayload(BaseModel): + preset: str + + +class IndexPayload(BaseModel): + index: int + + class DownloadRequest(BaseModel): remote_path: str @@ -790,3 +834,178 @@ async def download_ftp_file(unit_id: str, payload: DownloadRequest, db: Session except Exception as e: logger.error(f"Unexpected error downloading file from {unit_id}: {e}") raise HTTPException(status_code=500, detail="Internal server error") + + +# Timing/Interval Configuration Endpoints + +@router.get("/{unit_id}/measurement-time") +async def get_measurement_time(unit_id: str, db: Session = Depends(get_db)): + """Get current measurement time preset.""" + cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first() + if not cfg: + raise HTTPException(status_code=404, detail="NL43 config not found") + + if not cfg.tcp_enabled: + raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") + + client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) + try: + preset = await client.get_measurement_time() + return {"status": "ok", "measurement_time": preset} + except Exception as e: + logger.error(f"Failed to get measurement time for {unit_id}: {e}") + raise HTTPException(status_code=502, detail=str(e)) + + +@router.put("/{unit_id}/measurement-time") +async def set_measurement_time(unit_id: str, payload: TimingPayload, db: Session = Depends(get_db)): + """Set measurement time preset (10s, 1m, 5m, 10m, 15m, 30m, 1h, 8h, 24h, or custom like 00:05:30).""" + cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first() + if not cfg: + raise HTTPException(status_code=404, detail="NL43 config not found") + + if not cfg.tcp_enabled: + raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") + + client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) + try: + await client.set_measurement_time(payload.preset) + return {"status": "ok", "message": f"Measurement time set to {payload.preset}"} + except Exception as e: + logger.error(f"Failed to set measurement time for {unit_id}: {e}") + raise HTTPException(status_code=502, detail=str(e)) + + +@router.get("/{unit_id}/leq-interval") +async def get_leq_interval(unit_id: str, db: Session = Depends(get_db)): + """Get current Leq calculation interval preset.""" + cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first() + if not cfg: + raise HTTPException(status_code=404, detail="NL43 config not found") + + if not cfg.tcp_enabled: + raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") + + client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) + try: + preset = await client.get_leq_interval() + return {"status": "ok", "leq_interval": preset} + except Exception as e: + logger.error(f"Failed to get Leq interval for {unit_id}: {e}") + raise HTTPException(status_code=502, detail=str(e)) + + +@router.put("/{unit_id}/leq-interval") +async def set_leq_interval(unit_id: str, payload: TimingPayload, db: Session = Depends(get_db)): + """Set Leq calculation interval preset (Off, 10s, 1m, 5m, 10m, 15m, 30m, 1h, 8h, 24h, or custom like 00:05:30).""" + cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first() + if not cfg: + raise HTTPException(status_code=404, detail="NL43 config not found") + + if not cfg.tcp_enabled: + raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") + + client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) + try: + await client.set_leq_interval(payload.preset) + return {"status": "ok", "message": f"Leq interval set to {payload.preset}"} + except Exception as e: + logger.error(f"Failed to set Leq interval for {unit_id}: {e}") + raise HTTPException(status_code=502, detail=str(e)) + + +@router.get("/{unit_id}/lp-interval") +async def get_lp_interval(unit_id: str, db: Session = Depends(get_db)): + """Get current Lp store interval.""" + cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first() + if not cfg: + raise HTTPException(status_code=404, detail="NL43 config not found") + + if not cfg.tcp_enabled: + raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") + + client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) + try: + preset = await client.get_lp_interval() + return {"status": "ok", "lp_interval": preset} + except Exception as e: + logger.error(f"Failed to get Lp interval for {unit_id}: {e}") + raise HTTPException(status_code=502, detail=str(e)) + + +@router.put("/{unit_id}/lp-interval") +async def set_lp_interval(unit_id: str, payload: TimingPayload, db: Session = Depends(get_db)): + """Set Lp store interval (Off, 10ms, 25ms, 100ms, 200ms, 1s).""" + cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first() + if not cfg: + raise HTTPException(status_code=404, detail="NL43 config not found") + + if not cfg.tcp_enabled: + raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") + + client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) + try: + await client.set_lp_interval(payload.preset) + return {"status": "ok", "message": f"Lp interval set to {payload.preset}"} + except Exception as e: + logger.error(f"Failed to set Lp interval for {unit_id}: {e}") + raise HTTPException(status_code=502, detail=str(e)) + + +@router.get("/{unit_id}/index-number") +async def get_index_number(unit_id: str, db: Session = Depends(get_db)): + """Get current index number for file numbering.""" + cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first() + if not cfg: + raise HTTPException(status_code=404, detail="NL43 config not found") + + if not cfg.tcp_enabled: + raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") + + client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) + try: + index = await client.get_index_number() + return {"status": "ok", "index_number": index} + except Exception as e: + logger.error(f"Failed to get index number for {unit_id}: {e}") + raise HTTPException(status_code=502, detail=str(e)) + + +@router.put("/{unit_id}/index-number") +async def set_index_number(unit_id: str, payload: IndexPayload, db: Session = Depends(get_db)): + """Set index number for file numbering (0000-9999).""" + cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first() + if not cfg: + raise HTTPException(status_code=404, detail="NL43 config not found") + + if not cfg.tcp_enabled: + raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") + + client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) + try: + await client.set_index_number(payload.index) + return {"status": "ok", "message": f"Index number set to {payload.index:04d}"} + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + except Exception as e: + logger.error(f"Failed to set index number for {unit_id}: {e}") + raise HTTPException(status_code=502, detail=str(e)) + + +@router.get("/{unit_id}/settings/all") +async def get_all_settings(unit_id: str, db: Session = Depends(get_db)): + """Get all device settings for verification.""" + cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first() + if not cfg: + raise HTTPException(status_code=404, detail="NL43 config not found") + + if not cfg.tcp_enabled: + raise HTTPException(status_code=403, detail="TCP communication is disabled for this device") + + client = NL43Client(cfg.host, cfg.tcp_port, ftp_username=cfg.ftp_username, ftp_password=cfg.ftp_password) + try: + settings = await client.get_all_settings() + return {"status": "ok", "settings": settings} + except Exception as e: + logger.error(f"Failed to get all settings for {unit_id}: {e}") + raise HTTPException(status_code=502, detail=str(e)) diff --git a/app/services.py b/app/services.py index fcb0cc5..8c9ad80 100644 --- a/app/services.py +++ b/app/services.py @@ -465,6 +465,146 @@ class NL43Client: logger.info(f"DRD stream ended for {self.device_key}") + async def set_measurement_time(self, preset: str): + """Set measurement time preset. + + Args: + preset: Time preset (10s, 1m, 5m, 10m, 15m, 30m, 1h, 8h, 24h, or custom like "00:05:30") + """ + await self._send_command(f"Measurement Time Preset Manual,{preset}\r\n") + logger.info(f"Set measurement time to {preset} on {self.device_key}") + + async def get_measurement_time(self) -> str: + """Get current measurement time preset. + + Returns: Current time preset setting + """ + resp = await self._send_command("Measurement Time Preset Manual?\r\n") + return resp.strip() + + async def set_leq_interval(self, preset: str): + """Set Leq calculation interval preset. + + Args: + preset: Interval preset (Off, 10s, 1m, 5m, 10m, 15m, 30m, 1h, 8h, 24h, or custom like "00:05:30") + """ + await self._send_command(f"Leq Calculation Interval Preset,{preset}\r\n") + logger.info(f"Set Leq interval to {preset} on {self.device_key}") + + async def get_leq_interval(self) -> str: + """Get current Leq calculation interval preset. + + Returns: Current interval preset setting + """ + resp = await self._send_command("Leq Calculation Interval Preset?\r\n") + return resp.strip() + + async def set_lp_interval(self, preset: str): + """Set Lp store interval. + + Args: + preset: Store interval (Off, 10ms, 25ms, 100ms, 200ms, 1s) + """ + await self._send_command(f"Lp Store Interval,{preset}\r\n") + logger.info(f"Set Lp interval to {preset} on {self.device_key}") + + async def get_lp_interval(self) -> str: + """Get current Lp store interval. + + Returns: Current store interval setting + """ + resp = await self._send_command("Lp Store Interval?\r\n") + return resp.strip() + + async def set_index_number(self, index: int): + """Set index number for file numbering. + + Args: + index: Index number (0000-9999) + """ + if not 0 <= index <= 9999: + raise ValueError("Index must be between 0000 and 9999") + await self._send_command(f"Index Number,{index:04d}\r\n") + logger.info(f"Set index number to {index:04d} on {self.device_key}") + + async def get_index_number(self) -> str: + """Get current index number. + + Returns: Current index number + """ + resp = await self._send_command("Index Number?\r\n") + return resp.strip() + + async def get_all_settings(self) -> dict: + """Query all device settings for verification. + + Returns: Dictionary with all current device settings + """ + settings = {} + + # Measurement settings + try: + settings["measurement_state"] = await self.get_measurement_state() + except Exception as e: + settings["measurement_state"] = f"Error: {e}" + + try: + settings["frequency_weighting"] = await self.get_frequency_weighting() + except Exception as e: + settings["frequency_weighting"] = f"Error: {e}" + + try: + settings["time_weighting"] = await self.get_time_weighting() + except Exception as e: + settings["time_weighting"] = f"Error: {e}" + + # Timing/interval settings + try: + settings["measurement_time"] = await self.get_measurement_time() + except Exception as e: + settings["measurement_time"] = f"Error: {e}" + + try: + settings["leq_interval"] = await self.get_leq_interval() + except Exception as e: + settings["leq_interval"] = f"Error: {e}" + + try: + settings["lp_interval"] = await self.get_lp_interval() + except Exception as e: + settings["lp_interval"] = f"Error: {e}" + + try: + settings["index_number"] = await self.get_index_number() + except Exception as e: + settings["index_number"] = f"Error: {e}" + + # Device info + try: + settings["battery_level"] = await self.get_battery_level() + except Exception as e: + settings["battery_level"] = f"Error: {e}" + + try: + settings["clock"] = await self.get_clock() + except Exception as e: + settings["clock"] = f"Error: {e}" + + # Sleep mode + try: + settings["sleep_mode"] = await self.get_sleep_status() + except Exception as e: + settings["sleep_mode"] = f"Error: {e}" + + # FTP status + try: + settings["ftp_status"] = await self.get_ftp_status() + except Exception as e: + settings["ftp_status"] = f"Error: {e}" + + logger.info(f"Retrieved all settings for {self.device_key}") + return settings + async def enable_ftp(self): """Enable FTP server on the device. diff --git a/data/slmm.log b/data/slmm.log index 8068ceb..50bfbb8 100644 --- a/data/slmm.log +++ b/data/slmm.log @@ -594,3 +594,18 @@ 2025-12-24 06:46:07,062 - app.services - INFO - Sending command to 63.45.161.30:2255: Sleep Mode,On 2025-12-24 06:46:07,221 - app.services - INFO - Device 63.45.161.30:2255 entering sleep mode 2025-12-24 06:46:07,222 - app.routers - INFO - Put device nl43-1 to sleep +2025-12-24 07:24:24,390 - app.services - INFO - Sending command to 63.45.161.30:2255: DOD? +2025-12-24 07:24:29,391 - app.services - ERROR - Connection timeout to 63.45.161.30:2255 +2025-12-24 07:24:29,391 - app.routers - ERROR - Failed to get live status for nl43-1: Failed to connect to device at 63.45.161.30:2255 +2025-12-24 20:37:45,341 - app.main - INFO - Database tables initialized +2025-12-24 20:37:45,342 - app.main - INFO - CORS allowed origins: ['*'] +2025-12-24 20:38:04,141 - app.main - INFO - Database tables initialized +2025-12-24 20:38:04,142 - app.main - INFO - CORS allowed origins: ['*'] +2025-12-24 20:38:40,693 - app.main - INFO - Database tables initialized +2025-12-24 20:38:40,693 - app.main - INFO - CORS allowed origins: ['*'] +2025-12-24 20:39:08,484 - app.main - INFO - Database tables initialized +2025-12-24 20:39:08,484 - app.main - INFO - CORS allowed origins: ['*'] +2025-12-24 21:22:06,678 - app.main - INFO - Database tables initialized +2025-12-24 21:22:06,679 - app.main - INFO - CORS allowed origins: ['*'] +2025-12-24 21:22:38,825 - app.main - INFO - Database tables initialized +2025-12-24 21:22:38,825 - app.main - INFO - CORS allowed origins: ['*'] diff --git a/API.md b/docs/API.md similarity index 59% rename from API.md rename to docs/API.md index ff0d276..0ed5ca1 100644 --- a/API.md +++ b/docs/API.md @@ -240,6 +240,210 @@ Sets the time weighting. - `S` - Slow (1s) - `I` - Impulse (35ms) +## Timing and Interval Configuration + +### Get Measurement Time +``` +GET /{unit_id}/measurement-time +``` +Gets the current measurement time preset. + +**Response:** +```json +{ + "status": "ok", + "measurement_time": "1h" +} +``` + +### Set Measurement Time +``` +PUT /{unit_id}/measurement-time +``` +Sets the measurement time preset. + +**Request Body:** +```json +{ + "preset": "1h" +} +``` + +**Preset Values:** +- `10s`, `1m`, `5m`, `10m`, `15m`, `30m`, `1h`, `8h`, `24h` +- Custom format: `HH:MM:SS` (e.g., `00:05:30` for 5.5 minutes) + +### Get Leq Calculation Interval +``` +GET /{unit_id}/leq-interval +``` +Gets the current Leq calculation interval. + +**Response:** +```json +{ + "status": "ok", + "leq_interval": "1m" +} +``` + +### Set Leq Calculation Interval +``` +PUT /{unit_id}/leq-interval +``` +Sets the Leq calculation interval preset. + +**Request Body:** +```json +{ + "preset": "1m" +} +``` + +**Preset Values:** +- `Off`, `10s`, `1m`, `5m`, `10m`, `15m`, `30m`, `1h`, `8h`, `24h` +- Custom format: `HH:MM:SS` (e.g., `00:05:30` for 5.5 minutes) + +### Get Lp Store Interval +``` +GET /{unit_id}/lp-interval +``` +Gets the current Lp store interval. + +**Response:** +```json +{ + "status": "ok", + "lp_interval": "1s" +} +``` + +### Set Lp Store Interval +``` +PUT /{unit_id}/lp-interval +``` +Sets the Lp store interval. + +**Request Body:** +```json +{ + "preset": "1s" +} +``` + +**Preset Values:** +- `Off`, `10ms`, `25ms`, `100ms`, `200ms`, `1s` + +### Get Index Number +``` +GET /{unit_id}/index-number +``` +Gets the current index number for file numbering. + +**Response:** +```json +{ + "status": "ok", + "index_number": "0042" +} +``` + +### Set Index Number +``` +PUT /{unit_id}/index-number +``` +Sets the index number for file numbering. This number is incremented with each measurement and used in file names. + +**Request Body:** +```json +{ + "index": 42 +} +``` + +**Valid Range:** 0000 to 9999 + +## Device Settings Query + +### Get All Settings +``` +GET /{unit_id}/settings/all +``` +Retrieves all current device settings for verification. This is useful for confirming device configuration before starting measurements. + +**Response:** +```json +{ + "status": "ok", + "settings": { + "measurement_state": "Stop", + "frequency_weighting": "A", + "time_weighting": "F", + "measurement_time": "1h", + "leq_interval": "1m", + "lp_interval": "1s", + "index_number": "0042", + "battery_level": "80", + "clock": "2025/12/24,02:30:15", + "sleep_mode": "Off", + "ftp_status": "Off" + } +} +``` + +**Note:** If any setting query fails, the error message will be included in the response for that setting (e.g., `"frequency_weighting": "Error: Connection timeout"`). + +## Data Retrieval + +### Get Final Results +``` +GET /{unit_id}/results +``` +Retrieves the final calculation results (DLC) from the last completed measurement. + +**Response:** +```json +{ + "status": "ok", + "data": { + "leq": "68.4", + "lmax": "82.1", + "lmin": "42.3", + "lpeak": "89.5" + } +} +``` + +## Power Management + +### Sleep Device +``` +POST /{unit_id}/sleep +``` +Enables Sleep Mode on the device. When enabled, the device will automatically enter sleep mode between Timer Auto measurements. + +**Note:** This is a SETTING, not a command to sleep immediately. Sleep Mode only applies when using Timer Auto measurements. + +### Wake Device +``` +POST /{unit_id}/wake +``` +Disables Sleep Mode on the device. + +### Get Sleep Status +``` +GET /{unit_id}/sleep/status +``` +Gets the current Sleep Mode status. + +**Response:** +```json +{ + "status": "ok", + "sleep_mode": "Off" +} +``` + ## FTP File Management ### Enable FTP @@ -344,8 +548,46 @@ All endpoints return standard HTTP status codes: ### Terra-view Integration Example ```javascript -// Get live status from all devices const devices = ['nl43-1', 'nl43-2', 'nl43-3']; + +// Configure all devices before measurement +for (const device of devices) { + // Set measurement time to 12 hours + await fetch(`http://localhost:8000/api/nl43/${device}/measurement-time`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ preset: '12h' }) + }); + + // Set Leq interval to 1 minute + await fetch(`http://localhost:8000/api/nl43/${device}/leq-interval`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ preset: '1m' }) + }); + + // Set index number for daily file organization + const dayNumber = new Date().getDate(); + await fetch(`http://localhost:8000/api/nl43/${device}/index-number`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ index: dayNumber }) + }); + + // Verify all settings are correct + const settings = await fetch(`http://localhost:8000/api/nl43/${device}/settings/all`) + .then(r => r.json()); + console.log(`Device ${device} settings:`, settings.settings); +} + +// Start measurement on all devices at 7pm +await Promise.all( + devices.map(id => + fetch(`http://localhost:8000/api/nl43/${id}/start`, { method: 'POST' }) + ) +); + +// Get live status from all devices const statuses = await Promise.all( devices.map(id => fetch(`http://localhost:8000/api/nl43/${id}/live`) @@ -353,25 +595,18 @@ const statuses = await Promise.all( ) ); -// Start measurement on all devices -await Promise.all( - devices.map(id => - fetch(`http://localhost:8000/api/nl43/${id}/start`, { method: 'POST' }) - ) -); - -// Download latest files from all devices +// Download files from all devices the next morning for (const device of devices) { // Enable FTP await fetch(`http://localhost:8000/api/nl43/${device}/ftp/enable`, { method: 'POST' }); - // List files + // List files in device data directory const res = await fetch(`http://localhost:8000/api/nl43/${device}/ftp/files?path=/NL43_DATA`); const { files } = await res.json(); - // Download latest file + // Download latest measurement file const latestFile = files .filter(f => !f.is_dir) .sort((a, b) => b.modified - a.modified)[0]; @@ -384,10 +619,10 @@ for (const device of devices) { }); const blob = await download.blob(); - // Process blob... + // Save to local storage or process... } - // Disable FTP, re-enable TCP + // Disable FTP to re-enable TCP await fetch(`http://localhost:8000/api/nl43/${device}/ftp/disable`, { method: 'POST' }); diff --git a/COMMUNICATION_GUIDE.md b/docs/COMMUNICATION_GUIDE.md similarity index 100% rename from COMMUNICATION_GUIDE.md rename to docs/COMMUNICATION_GUIDE.md diff --git a/IMPROVEMENTS.md b/docs/IMPROVEMENTS.md similarity index 100% rename from IMPROVEMENTS.md rename to docs/IMPROVEMENTS.md diff --git a/NL43_COMMANDS.md b/docs/NL43_COMMANDS.md similarity index 100% rename from NL43_COMMANDS.md rename to docs/NL43_COMMANDS.md diff --git a/NL43_quickref.md b/docs/NL43_quickref.md similarity index 100% rename from NL43_quickref.md rename to docs/NL43_quickref.md diff --git a/nl43_Command_ref.md b/docs/nl43_Command_ref.md similarity index 100% rename from nl43_Command_ref.md rename to docs/nl43_Command_ref.md diff --git a/templates/index.html b/templates/index.html index 3c8e979..0b94b2b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -57,6 +57,7 @@
Measurement Settings +
@@ -280,6 +281,29 @@ } // Measurement settings functions + async function getAllSettings() { + const unitId = document.getElementById('unitId').value; + log('Retrieving all device settings (this may take 10-15 seconds)...'); + + const res = await fetch(`/api/nl43/${unitId}/settings`); + const data = await res.json(); + + if (!res.ok) { + log(`Get All Settings failed: ${res.status} - ${data.detail || JSON.stringify(data)}`); + return; + } + + // Display in status area + statusEl.textContent = JSON.stringify(data.settings, null, 2); + + // Log summary + log('=== ALL DEVICE SETTINGS ==='); + Object.entries(data.settings).forEach(([key, value]) => { + log(`${key}: ${value}`); + }); + log('==========================='); + } + async function getFreqWeighting() { const unitId = document.getElementById('unitId').value; const res = await fetch(`/api/nl43/${unitId}/frequency-weighting?channel=Main`); diff --git a/test_settings_endpoint.py b/test_settings_endpoint.py new file mode 100644 index 0000000..94923a9 --- /dev/null +++ b/test_settings_endpoint.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python3 +""" +Test script to demonstrate the GET /api/nl43/{unit_id}/settings endpoint. + +This endpoint retrieves all current device settings for verification purposes. +""" + +import asyncio +import sys + + +async def test_settings_retrieval(): + """Test the settings retrieval functionality.""" + from app.services import NL43Client + + # Example configuration - adjust these to match your actual device + host = "192.168.1.100" # Replace with your NL43 device IP + port = 80 + unit_id = "NL43-001" + + print(f"Connecting to NL43 device at {host}:{port}...") + print(f"Unit ID: {unit_id}\n") + + client = NL43Client(host, port) + + try: + print("Retrieving all device settings...") + settings = await client.get_all_settings() + + print("\n" + "="*60) + print("DEVICE SETTINGS SUMMARY") + print("="*60) + + for key, value in settings.items(): + print(f"{key:.<30} {value}") + + print("="*60) + print(f"\nTotal settings retrieved: {len(settings)}") + print("\n✓ Settings retrieval successful!") + + except ConnectionError as e: + print(f"\n✗ Connection Error: {e}") + print("\nTroubleshooting:") + print(" 1. Verify the device IP address and port") + print(" 2. Ensure the device is powered on and connected to the network") + print(" 3. Check firewall settings") + sys.exit(1) + + except Exception as e: + print(f"\n✗ Error: {e}") + sys.exit(1) + + +async def test_api_endpoint(): + """Demonstrate how to call the API endpoint.""" + print("\n" + "="*60) + print("API ENDPOINT USAGE") + print("="*60) + print("\nTo retrieve all settings via the API, use:") + print("\n GET /api/nl43/{unit_id}/settings") + print("\nExample with curl:") + print("\n curl http://localhost:8000/api/nl43/NL43-001/settings") + print("\nExample response:") + print(""" +{ + "status": "ok", + "unit_id": "NL43-001", + "settings": { + "measurement_state": "Stop", + "frequency_weighting": "A", + "time_weighting": "F", + "measurement_time": "00:01:00", + "leq_interval": "1s", + "lp_interval": "125ms", + "index_number": "0", + "battery_level": "100%", + "clock": "2025/12/24,20:45:30", + "sleep_mode": "Off", + "ftp_status": "On" + } +} +""") + print("="*60) + + +if __name__ == "__main__": + print("NL43 Settings Retrieval Test") + print("="*60) + print("\nThis test demonstrates the new /api/nl43/{unit_id}/settings endpoint") + print("which allows you to view all current device settings for verification.\n") + + # Show API usage + asyncio.run(test_api_endpoint()) + + # Uncomment below to test actual device connection + # asyncio.run(test_settings_retrieval()) + + print("\n✓ Test completed!")