Two device-data bugs surfaced while scoping the live-feed work:
1. DOD parser misalignment. DOD's response has no leading counter and
includes LE + LN1-LN5, but the parser reused the DRD field map
(parts[0]=counter). That shifted everything: Lp was stored as the
counter, Leq as Lp, LE as Leq, and LN1 as Lpeak (visible because
"Lpeak" came out below Lmax, which is impossible). Parse DOD with its
own map: Lp=0, Leq=1, Lmax=3, Lmin=4, Lpeak=10 (channel 1 = main).
2. measurement_start_time reset on every live-stream open/close. The DOD
path tags state "Start"; the DRD stream path tags "Measure". The
transition detector treated only "Start" as measuring, so opening the
stream ("Start"->"Measure") read as a stop (cleared start time) and
closing it ("Measure"->"Start") read as a start (reset to now). Every
viewer reset the elapsed measurement time. Treat {"Start","Measure"}
both as measuring.
LN1/LN2 (L1/L10) parsing + model/serialization is the next step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SLMM - Sound Level Meter Manager
Version 0.3.0
Backend API service for controlling and monitoring Rion NL-43/NL-53 Sound Level Meters via TCP and FTP protocols.
Overview
SLMM is a standalone backend module that provides REST API routing and command translation for NL43/NL53 sound level meters. This service acts as a bridge between the hardware devices and frontend applications, handling all device communication, data persistence, and protocol management.
Note: This is a backend-only service. Actual user interfacing is done via customized front ends or cli.
Features
- Persistent TCP Connections: Cached per-device connections with OS-level keepalive, tuned for cellular modem reliability
- Background Polling: Continuous automatic polling of devices with configurable intervals
- Offline Detection: Automatic device reachability tracking with failure counters
- Device Management: Configure and manage multiple NL43/NL53 devices
- Real-time Monitoring: Stream live measurement data via WebSocket
- Measurement Control: Start, stop, pause, resume, and reset measurements
- Data Retrieval: Access current and historical measurement snapshots
- FTP Integration: Download measurement files directly from devices
- Device Configuration: Manage frequency/time weighting, clock sync, and more
- Rate Limiting: Automatic 1-second delay enforcement between device commands
- Persistent Storage: SQLite database for device configs and measurement cache
- Connection Diagnostics: Live UI and API endpoints for monitoring TCP connection pool status
Architecture
┌─────────────────┐ ┌──────────────────────────────┐ ┌─────────────────┐
│ │◄───────►│ SLMM API │◄───────►│ NL43/NL53 │
│ (Frontend) │ HTTP │ • REST Endpoints │ TCP │ Sound Meters │
└─────────────────┘ │ • WebSocket Streaming │ (kept │ (via cellular │
│ • Background Poller │ alive) │ modem) │
│ • Connection Pool (v0.3) │ └─────────────────┘
└──────────────────────────────┘
│
▼
┌──────────────┐
│ SQLite DB │
│ • Config │
│ • Status │
└──────────────┘
Persistent TCP Connection Pool (v0.3.0)
SLMM maintains persistent TCP connections to devices with OS-level keepalive, designed for reliable operation over cellular modems:
- Connection Reuse: One cached TCP socket per device, reused across all commands (no repeated handshakes)
- TCP Keepalive: Probes keep cellular NAT tables alive and detect dead connections early
- Transparent Retry: Stale cached connections automatically retry with a fresh socket
- Configurable: Idle TTL (300s), max age (1800s), and keepalive timing via environment variables
- Diagnostics: Live UI on the roster page and API endpoints for monitoring pool status
Background Polling (v0.2.0)
Background polling service continuously queries devices and updates the status cache:
- Automatic Updates: Devices are polled at configurable intervals (10-3600 seconds)
- Offline Detection: Devices marked unreachable after 3 consecutive failures
- Per-Device Configuration: Each device can have a custom polling interval
- Resource Efficient: Dynamic sleep intervals and smart scheduling
Status requests return cached data instantly (<100ms) instead of waiting for device queries (1-2 seconds).
Quick Start
Prerequisites
- Python 3.10+
- pip package manager
Installation
- Clone the repository:
git clone <repository-url>
cd slmm
- Install dependencies:
pip install -r requirements.txt
Running the Server
# Development mode with auto-reload
uvicorn app.main:app --reload --port 8100
# Production mode
uvicorn app.main:app --host 0.0.0.0 --port 8100
The API will be available at http://localhost:8100
API Documentation
Once running, visit:
- Swagger UI:
http://localhost:8100/docs - ReDoc:
http://localhost:8100/redoc - Health Check:
http://localhost:8100/health
Configuration
Environment Variables
Server:
PORT: Server port (default: 8100)CORS_ORIGINS: Comma-separated list of allowed origins (default: "*")
TCP Connection Pool:
TCP_PERSISTENT_ENABLED: Enable persistent connections (default: "true")TCP_IDLE_TTL: Close idle connections after N seconds (default: 300)TCP_MAX_AGE: Force reconnect after N seconds (default: 1800)TCP_KEEPALIVE_IDLE: Seconds idle before keepalive probes (default: 15)TCP_KEEPALIVE_INTERVAL: Seconds between keepalive probes (default: 10)TCP_KEEPALIVE_COUNT: Failed probes before declaring dead (default: 3)
Database
The SQLite database is automatically created at data/slmm.db on first run.
Logging
Logs are written to:
- Console output (stdout)
- data/slmm.log file
API Endpoints
Device Configuration
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/nl43/{unit_id}/config |
Get device configuration |
| PUT | /api/nl43/{unit_id}/config |
Update device configuration |
Device Status
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/nl43/{unit_id}/status |
Get cached measurement snapshot (updated by background poller) |
| GET | /api/nl43/{unit_id}/live |
Request fresh DOD data from device (bypasses cache) |
| WS | /api/nl43/{unit_id}/stream |
WebSocket stream for real-time DRD data |
Background Polling
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/nl43/{unit_id}/polling/config |
Get device polling configuration |
| PUT | /api/nl43/{unit_id}/polling/config |
Update polling interval and enable/disable polling |
| GET | /api/nl43/_polling/status |
Get global polling status for all devices |
Connection Pool
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/nl43/_connections/status |
Get pool config, active connections, age/idle times |
| POST | /api/nl43/_connections/flush |
Force-close all cached TCP connections |
Measurement Control
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/nl43/{unit_id}/start |
Start measurement |
| POST | /api/nl43/{unit_id}/stop |
Stop measurement |
| POST | /api/nl43/{unit_id}/pause |
Pause measurement |
| POST | /api/nl43/{unit_id}/resume |
Resume paused measurement |
| POST | /api/nl43/{unit_id}/reset |
Reset measurement data |
| POST | /api/nl43/{unit_id}/store |
Manually store data to SD card |
Device Information
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/nl43/{unit_id}/battery |
Get battery level |
| GET | /api/nl43/{unit_id}/clock |
Get device clock time |
| PUT | /api/nl43/{unit_id}/clock |
Set device clock time |
| GET | /api/nl43/{unit_id}/results |
Get final calculation results (DLC) |
Measurement Settings
| 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) |
| PUT | /api/nl43/{unit_id}/time-weighting |
Set time weighting |
Sleep Mode
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/nl43/{unit_id}/sleep |
Put device into sleep mode |
| POST | /api/nl43/{unit_id}/wake |
Wake device from sleep |
| GET | /api/nl43/{unit_id}/sleep/status |
Get sleep mode status |
FTP File Management
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/nl43/{unit_id}/ftp/enable |
Enable FTP server on device |
| POST | /api/nl43/{unit_id}/ftp/disable |
Disable FTP server on device |
| GET | /api/nl43/{unit_id}/ftp/status |
Get FTP server status |
| GET | /api/nl43/{unit_id}/ftp/files |
List files on device |
| POST | /api/nl43/{unit_id}/ftp/download |
Download file from device |
For detailed API documentation and examples, see API.md.
Project Structure
slmm/
├── app/
│ ├── __init__.py # Package initialization
│ ├── main.py # FastAPI application and startup
│ ├── routers.py # API route definitions
│ ├── models.py # SQLAlchemy database models
│ ├── services.py # NL43Client and business logic
│ ├── background_poller.py # Background polling service ⭐ NEW
│ └── database.py # Database configuration
├── data/
│ ├── slmm.db # SQLite database (auto-created)
│ ├── slmm.log # Application logs
│ └── downloads/ # Downloaded files from devices
├── templates/
│ └── index.html # Simple web interface (optional)
├── manuals/ # Device documentation
├── migrate_add_polling_fields.py # Database migration for v0.2.0 ⭐ NEW
├── test_polling.sh # Polling feature test script ⭐ NEW
├── API.md # Detailed API documentation
├── COMMUNICATION_GUIDE.md # NL43 protocol documentation
├── NL43_COMMANDS.md # Command reference
├── CHANGELOG.md # Version history ⭐ NEW
├── requirements.txt # Python dependencies
└── README.md # This file
Database Schema
NL43Config Table
Stores device connection configuration:
unit_id(PK): Unique device identifierhost: Device IP address or hostnametcp_port: TCP control port (default: 80)tcp_enabled: Enable/disable TCP communicationftp_enabled: Enable/disable FTP functionalityftp_username: FTP authentication usernameftp_password: FTP authentication passwordweb_enabled: Enable/disable web interface accesspoll_interval_seconds: Polling interval in seconds (10-3600, default: 60) ⭐ NEWpoll_enabled: Enable/disable background polling for this device ⭐ NEW
NL43Status Table
Caches latest measurement snapshot:
unit_id(PK): Unique device identifierlast_seen: Timestamp of last updatemeasurement_state: Current state (Measure/Stop)measurement_start_time: When measurement started (UTC)counter: Measurement interval counter (1-600)lp: Instantaneous sound pressure levelleq: Equivalent continuous sound levellmax: Maximum sound levellmin: Minimum sound levellpeak: Peak sound levelbattery_level: Battery percentagepower_source: Current power sourcesd_remaining_mb: Free SD card space (MB)sd_free_ratio: SD card free space ratioraw_payload: Raw device response datais_reachable: Device reachability status (Boolean) ⭐ NEWconsecutive_failures: Count of consecutive poll failures ⭐ NEWlast_poll_attempt: Last time background poller attempted to poll ⭐ NEWlast_success: Last successful poll timestamp ⭐ NEWlast_error: Last error message (truncated to 500 chars) ⭐ NEW
Protocol Details
TCP Communication
- Uses ASCII command protocol over TCP
- Persistent connections with OS-level keepalive (tuned for cellular modems)
- Connections cached per device and reused across commands
- Transparent retry on stale connections
- Enforces ≥1 second delay between commands to same device
- Two-line response format:
- Line 1: Result code (R+0000 for success)
- Line 2: Data payload (for query commands)
FTP Communication
- Uses active mode FTP (requires device to connect back)
- TCP and FTP are mutually exclusive on the device
- Credentials configurable per device
- Default NL43 FTP Credentials: Username:
USER, Password:0000
Data Formats
DOD (Data Output Display): Snapshot of current display values DRD (Data Real-time Display): Continuous streaming data DLC (Data Last Calculation): Final stored measurement results
Example Usage
Configure a Device
curl -X PUT http://localhost:8100/api/nl43/meter-001/config \
-H "Content-Type: application/json" \
-d '{
"host": "192.168.1.100",
"tcp_port": 2255,
"tcp_enabled": true,
"ftp_enabled": true,
"ftp_username": "USER",
"ftp_password": "0000"
}'
Start Measurement
curl -X POST http://localhost:8100/api/nl43/meter-001/start
Get Cached Status (Fast - from background poller)
curl http://localhost:8100/api/nl43/meter-001/status
Get Live Status (Bypasses cache)
curl http://localhost:8100/api/nl43/meter-001/live
Configure Background Polling ⭐ NEW
# Set polling interval to 30 seconds
curl -X PUT http://localhost:8100/api/nl43/meter-001/polling/config \
-H "Content-Type: application/json" \
-d '{
"poll_interval_seconds": 30,
"poll_enabled": true
}'
# Get polling configuration
curl http://localhost:8100/api/nl43/meter-001/polling/config
# Check global polling status
curl http://localhost:8100/api/nl43/_polling/status
Check Connection Pool Status
curl http://localhost:8100/api/nl43/_connections/status | jq '.'
Flush All Cached Connections
curl -X POST http://localhost:8100/api/nl43/_connections/flush
Verify Device Settings
curl http://localhost:8100/api/nl43/meter-001/settings
This returns all current device configuration:
{
"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)
const ws = new WebSocket('ws://localhost:8100/api/nl43/meter-001/stream');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Live measurement:', data);
};
Download Files via FTP
# Enable FTP
curl -X POST http://localhost:8100/api/nl43/meter-001/ftp/enable
# List files
curl http://localhost:8100/api/nl43/meter-001/ftp/files?path=/NL43_DATA
# Download file
curl -X POST http://localhost:8100/api/nl43/meter-001/ftp/download \
-H "Content-Type: application/json" \
-d '{"remote_path": "/NL43_DATA/measurement.wav"}' \
--output measurement.wav
# Disable FTP
curl -X POST http://localhost:8100/api/nl43/meter-001/ftp/disable
Integration with Terra-View
This backend is designed to be consumed by the Terra-View frontend application. The frontend should:
- Use the config endpoints to register and configure devices
- Poll or stream live status for real-time monitoring
- Use control endpoints to manage measurements
- Download files via FTP endpoints for analysis
See API.md for detailed integration examples.
Troubleshooting
Connection Issues
- Check connection pool status:
curl http://localhost:8100/api/nl43/_connections/status - Flush stale connections:
curl -X POST http://localhost:8100/api/nl43/_connections/flush - Verify device IP address and port in configuration
- Ensure device is on the same network
- Check firewall rules allow TCP/FTP connections
- Verify RX55 network adapter is properly configured on device
Cellular Modem Issues
- If modem wedges from too many handshakes, ensure
TCP_PERSISTENT_ENABLED=true(default) - Increase
TCP_IDLE_TTLif connections expire between poll cycles - Keepalive probes (default: every 15s) keep NAT tables alive — adjust
TCP_KEEPALIVE_IDLEif needed - Set
TCP_PERSISTENT_ENABLED=falseto disable pooling for debugging
Rate Limiting
- API automatically enforces 1-second delay between commands
- If experiencing delays, this is normal device behavior
- Multiple devices can be controlled in parallel
FTP Active Mode
- Ensure server can accept incoming connections from device
- FTP uses active mode (device connects back to server)
- May require firewall configuration for data channel
WebSocket Disconnects
- WebSocket streams maintain persistent connection
- Limit concurrent streams to avoid device overload
- Connection will auto-close if device stops responding
Development
Running Tests
# Add test commands when implemented
pytest
Database Migrations
# Migrate to v0.2.0 (add background polling fields)
python3 migrate_add_polling_fields.py
# Legacy: Migrate to add FTP credentials
python migrate_add_ftp_credentials.py
# Set FTP credentials for a device
python set_ftp_credentials.py <unit_id> <username> <password>
Testing Background Polling
# Run comprehensive polling tests
./test_polling.sh [unit_id]
# Test settings endpoint
python3 test_settings_endpoint.py <unit_id>
# Test sleep mode auto-disable
python3 test_sleep_mode_auto_disable.py <unit_id>
Legacy Scripts
Old migration scripts and manual polling tools have been moved to archive/ for reference. See archive/README.md for details.
Contributing
This is a standalone module kept separate from the SFM/Terra-View codebase. When contributing:
- Maintain separation from frontend code
- Follow existing API patterns and error handling
- Update API documentation for new endpoints
- Ensure rate limiting is enforced for device commands
License
[Specify license here]
Related Documentation
- API.md - Complete API reference with examples
- COMMUNICATION_GUIDE.md - NL43 protocol details
- NL43_COMMANDS.md - Device command reference
- manuals/ - Device manufacturer documentation
Support
For issues and questions:
- Backend API issues: This repository
- Frontend/UI issues: Terra-View repository
- Device protocol questions: See COMMUNICATION_GUIDE.md