diff --git a/app/models.py b/app/models.py index df79233..ce454f9 100644 --- a/app/models.py +++ b/app/models.py @@ -29,11 +29,11 @@ class NL43Status(Base): unit_id = Column(String, primary_key=True, index=True) last_seen = Column(DateTime, default=func.now()) measurement_state = Column(String, default="unknown") # Measure/Stop - lp = Column(String, nullable=True) - leq = Column(String, nullable=True) - lmax = Column(String, nullable=True) - lmin = Column(String, nullable=True) - lpeak = Column(String, nullable=True) + lp = Column(String, nullable=True) # Instantaneous sound pressure level + leq = Column(String, nullable=True) # Equivalent continuous sound level + lmax = Column(String, nullable=True) # Maximum level + lmin = Column(String, nullable=True) # Minimum level + lpeak = Column(String, nullable=True) # Peak level battery_level = Column(String, nullable=True) power_source = Column(String, nullable=True) sd_remaining_mb = Column(String, nullable=True) diff --git a/app/routers.py b/app/routers.py index 2e8220f..e9c5006 100644 --- a/app/routers.py +++ b/app/routers.py @@ -652,11 +652,11 @@ async def stream_live(websocket: WebSocket, unit_id: str): "unit_id": unit_id, "timestamp": datetime.utcnow().isoformat(), "measurement_state": snap.measurement_state, - "lp": snap.lp, - "leq": snap.leq, - "lmax": snap.lmax, - "lmin": snap.lmin, - "lpeak": snap.lpeak, + "lp": snap.lp, # Instantaneous sound pressure level + "leq": snap.leq, # Equivalent continuous sound level + "lmax": snap.lmax, # Maximum level + "lmin": snap.lmin, # Minimum level + "lpeak": snap.lpeak, # Peak level "raw_payload": snap.raw_payload, }) except Exception as e: diff --git a/app/services.py b/app/services.py index 24f99c5..b582748 100644 --- a/app/services.py +++ b/app/services.py @@ -25,11 +25,11 @@ logger = logging.getLogger(__name__) class NL43Snapshot: unit_id: str measurement_state: str = "unknown" - lp: Optional[str] = None - leq: Optional[str] = None - lmax: Optional[str] = None - lmin: Optional[str] = None - lpeak: Optional[str] = None + lp: Optional[str] = None # Instantaneous sound pressure level + leq: Optional[str] = None # Equivalent continuous sound level + lmax: Optional[str] = None # Maximum level + lmin: Optional[str] = None # Minimum level + lpeak: Optional[str] = None # Peak level battery_level: Optional[str] = None power_source: Optional[str] = None sd_remaining_mb: Optional[str] = None @@ -188,19 +188,20 @@ class NL43Client: snap = NL43Snapshot(unit_id="", raw_payload=resp, measurement_state="Measure") - # Parse known positions (based on NL43 communication guide) - # DOD format: Main Lp, Main Leq, Main LE, Main Lmax, Main Lmin, LN1-5, Lpeak, LIeq, Leq,mov, Ltm5, flags... + # Parse known positions (based on NL43 communication guide - DRD format) + # DRD format: d0=counter, d1=Lp, d2=Leq, d3=Lmax, d4=Lmin, d5=Lpeak, d6=LIeq, ... try: - if len(parts) >= 1: - snap.lp = parts[0] + # Skip d0 (counter) - start from d1 if len(parts) >= 2: - snap.leq = parts[1] + snap.lp = parts[1] # d1: Instantaneous sound pressure level + if len(parts) >= 3: + snap.leq = parts[2] # d2: Equivalent continuous sound level if len(parts) >= 4: - snap.lmax = parts[3] + snap.lmax = parts[3] # d3: Maximum level if len(parts) >= 5: - snap.lmin = parts[4] - if len(parts) >= 11: - snap.lpeak = parts[10] + snap.lmin = parts[4] # d4: Minimum level + if len(parts) >= 6: + snap.lpeak = parts[5] # d5: Peak level except (IndexError, ValueError) as e: logger.warning(f"Error parsing DOD data points: {e}") @@ -439,18 +440,20 @@ class NL43Client: snap = NL43Snapshot(unit_id="", raw_payload=line, measurement_state="Measure") - # Parse known positions + # Parse known positions (DRD format - same as DOD) + # DRD format: d0=counter, d1=Lp, d2=Leq, d3=Lmax, d4=Lmin, d5=Lpeak, d6=LIeq, ... try: - if len(parts) >= 1: - snap.lp = parts[0] + # Skip d0 (counter) - start from d1 if len(parts) >= 2: - snap.leq = parts[1] + snap.lp = parts[1] # d1: Instantaneous sound pressure level + if len(parts) >= 3: + snap.leq = parts[2] # d2: Equivalent continuous sound level if len(parts) >= 4: - snap.lmax = parts[3] + snap.lmax = parts[3] # d3: Maximum level if len(parts) >= 5: - snap.lmin = parts[4] - if len(parts) >= 11: - snap.lpeak = parts[10] + snap.lmin = parts[4] # d4: Minimum level + if len(parts) >= 6: + snap.lpeak = parts[5] # d5: Peak level except (IndexError, ValueError) as e: logger.warning(f"Error parsing DRD data points: {e}") diff --git a/migrate_revert_field_names.py b/migrate_revert_field_names.py new file mode 100644 index 0000000..17a0124 --- /dev/null +++ b/migrate_revert_field_names.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +""" +Migration script to revert NL43 measurement field names back to correct DRD format. + +The previous migration was incorrect. According to NL43 DRD documentation: +- d0 = counter (1-600) - NOT a measurement! +- d1 = Lp (instantaneous sound pressure level) +- d2 = Leq (equivalent continuous sound level) +- d3 = Lmax (maximum level) +- d4 = Lmin (minimum level) +- d5 = Lpeak (peak level) + +Changes: +- laeq -> lp (was incorrectly mapped to counter field!) +- lae -> leq +- lasmax -> lmax +- lasmin -> lmin +- lapeak -> lpeak +""" + +import sqlite3 +import sys +from pathlib import Path + +def migrate_database(db_path: str): + """Revert database schema to correct DRD field names.""" + + print(f"Reverting database migration: {db_path}") + + # Connect to database + conn = sqlite3.connect(db_path) + cur = conn.cursor() + + try: + # Check if migration is needed + cur.execute("PRAGMA table_info(nl43_status)") + columns = [row[1] for row in cur.fetchall()] + + if 'lp' in columns: + print("✓ Database already has correct field names") + return + + if 'laeq' not in columns: + print("✗ Database schema does not match expected format") + sys.exit(1) + + print("Starting revert migration...") + + # Create new table with correct column names + cur.execute(""" + CREATE TABLE nl43_status_new ( + unit_id VARCHAR PRIMARY KEY, + last_seen DATETIME, + measurement_state VARCHAR, + lp VARCHAR, + leq VARCHAR, + lmax VARCHAR, + lmin VARCHAR, + lpeak VARCHAR, + battery_level VARCHAR, + power_source VARCHAR, + sd_remaining_mb VARCHAR, + sd_free_ratio VARCHAR, + raw_payload TEXT + ) + """) + print("✓ Created new table with correct DRD field names") + + # Copy data from old table to new table + # Note: laeq was incorrectly mapped to d0 (counter), so we discard it + # The actual measurements start from d1 + cur.execute(""" + INSERT INTO nl43_status_new + (unit_id, last_seen, measurement_state, lp, leq, lmax, lmin, lpeak, + battery_level, power_source, sd_remaining_mb, sd_free_ratio, raw_payload) + SELECT + unit_id, last_seen, measurement_state, lae, lasmax, lasmin, lapeak, NULL, + battery_level, power_source, sd_remaining_mb, sd_free_ratio, raw_payload + FROM nl43_status + """) + rows_copied = cur.rowcount + print(f"✓ Copied {rows_copied} rows (note: discarded incorrect 'laeq' counter field)") + + # Drop old table + cur.execute("DROP TABLE nl43_status") + print("✓ Dropped old table") + + # Rename new table + cur.execute("ALTER TABLE nl43_status_new RENAME TO nl43_status") + print("✓ Renamed new table to nl43_status") + + # Commit changes + conn.commit() + print("✓ Revert migration completed successfully") + print("\nNote: The 'lp' field will be populated correctly on next device measurement") + + except Exception as e: + conn.rollback() + print(f"✗ Migration failed: {e}") + sys.exit(1) + finally: + conn.close() + +if __name__ == "__main__": + # Default database path + db_path = Path(__file__).parent / "data" / "slmm.db" + + # Allow custom path as command line argument + if len(sys.argv) > 1: + db_path = Path(sys.argv[1]) + + if not db_path.exists(): + print(f"✗ Database not found: {db_path}") + sys.exit(1) + + migrate_database(str(db_path))