Update to v 0.4.0 #6
@@ -41,6 +41,8 @@ class NL43Status(Base):
|
|||||||
lmax = Column(String, nullable=True) # Maximum level
|
lmax = Column(String, nullable=True) # Maximum level
|
||||||
lmin = Column(String, nullable=True) # Minimum level
|
lmin = Column(String, nullable=True) # Minimum level
|
||||||
lpeak = Column(String, nullable=True) # Peak level
|
lpeak = Column(String, nullable=True) # Peak level
|
||||||
|
ln1 = Column(String, nullable=True) # Percentile slot LN1 (configurable; device default L5, contract L1)
|
||||||
|
ln2 = Column(String, nullable=True) # Percentile slot LN2 (configurable; device default L10)
|
||||||
battery_level = Column(String, nullable=True)
|
battery_level = Column(String, nullable=True)
|
||||||
power_source = Column(String, nullable=True)
|
power_source = Column(String, nullable=True)
|
||||||
sd_remaining_mb = Column(String, nullable=True)
|
sd_remaining_mb = Column(String, nullable=True)
|
||||||
|
|||||||
@@ -450,6 +450,8 @@ def get_status(unit_id: str, db: Session = Depends(get_db)):
|
|||||||
"lmax": status.lmax,
|
"lmax": status.lmax,
|
||||||
"lmin": status.lmin,
|
"lmin": status.lmin,
|
||||||
"lpeak": status.lpeak,
|
"lpeak": status.lpeak,
|
||||||
|
"ln1": status.ln1,
|
||||||
|
"ln2": status.ln2,
|
||||||
"battery_level": status.battery_level,
|
"battery_level": status.battery_level,
|
||||||
"power_source": status.power_source,
|
"power_source": status.power_source,
|
||||||
"sd_remaining_mb": status.sd_remaining_mb,
|
"sd_remaining_mb": status.sd_remaining_mb,
|
||||||
@@ -472,6 +474,8 @@ class StatusPayload(BaseModel):
|
|||||||
lmax: str | None = None
|
lmax: str | None = None
|
||||||
lmin: str | None = None
|
lmin: str | None = None
|
||||||
lpeak: str | None = None
|
lpeak: str | None = None
|
||||||
|
ln1: str | None = None
|
||||||
|
ln2: str | None = None
|
||||||
battery_level: str | None = None
|
battery_level: str | None = None
|
||||||
power_source: str | None = None
|
power_source: str | None = None
|
||||||
sd_remaining_mb: str | None = None
|
sd_remaining_mb: str | None = None
|
||||||
@@ -504,6 +508,8 @@ def upsert_status(unit_id: str, payload: StatusPayload, db: Session = Depends(ge
|
|||||||
"lmax": status.lmax,
|
"lmax": status.lmax,
|
||||||
"lmin": status.lmin,
|
"lmin": status.lmin,
|
||||||
"lpeak": status.lpeak,
|
"lpeak": status.lpeak,
|
||||||
|
"ln1": status.ln1,
|
||||||
|
"ln2": status.ln2,
|
||||||
"battery_level": status.battery_level,
|
"battery_level": status.battery_level,
|
||||||
"power_source": status.power_source,
|
"power_source": status.power_source,
|
||||||
"sd_remaining_mb": status.sd_remaining_mb,
|
"sd_remaining_mb": status.sd_remaining_mb,
|
||||||
@@ -1205,6 +1211,8 @@ async def stream_live(websocket: WebSocket, unit_id: str):
|
|||||||
"lmax": snap.lmax, # Maximum level
|
"lmax": snap.lmax, # Maximum level
|
||||||
"lmin": snap.lmin, # Minimum level
|
"lmin": snap.lmin, # Minimum level
|
||||||
"lpeak": snap.lpeak, # Peak level
|
"lpeak": snap.lpeak, # Peak level
|
||||||
|
"ln1": snap.ln1, # LN1 percentile (L1/L10 contract); null on DRD stream
|
||||||
|
"ln2": snap.ln2, # LN2 percentile; null on DRD stream
|
||||||
"raw_payload": snap.raw_payload,
|
"raw_payload": snap.raw_payload,
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1876,6 +1884,8 @@ async def run_diagnostics(unit_id: str, db: Session = Depends(get_db)):
|
|||||||
"lmax": status.lmax,
|
"lmax": status.lmax,
|
||||||
"lmin": status.lmin,
|
"lmin": status.lmin,
|
||||||
"lpeak": status.lpeak,
|
"lpeak": status.lpeak,
|
||||||
|
"ln1": status.ln1,
|
||||||
|
"ln2": status.ln2,
|
||||||
"battery_level": status.battery_level,
|
"battery_level": status.battery_level,
|
||||||
"power_source": status.power_source,
|
"power_source": status.power_source,
|
||||||
"sd_remaining_mb": status.sd_remaining_mb,
|
"sd_remaining_mb": status.sd_remaining_mb,
|
||||||
|
|||||||
+8
-3
@@ -46,6 +46,8 @@ class NL43Snapshot:
|
|||||||
lmax: Optional[str] = None # Maximum level
|
lmax: Optional[str] = None # Maximum level
|
||||||
lmin: Optional[str] = None # Minimum level
|
lmin: Optional[str] = None # Minimum level
|
||||||
lpeak: Optional[str] = None # Peak level
|
lpeak: Optional[str] = None # Peak level
|
||||||
|
ln1: Optional[str] = None # Percentile slot LN1 (configurable; device default L5, contract L1)
|
||||||
|
ln2: Optional[str] = None # Percentile slot LN2 (configurable; device default L10)
|
||||||
battery_level: Optional[str] = None
|
battery_level: Optional[str] = None
|
||||||
power_source: Optional[str] = None
|
power_source: Optional[str] = None
|
||||||
sd_remaining_mb: Optional[str] = None
|
sd_remaining_mb: Optional[str] = None
|
||||||
@@ -108,6 +110,8 @@ def persist_snapshot(s: NL43Snapshot, db: Session):
|
|||||||
row.lmax = s.lmax
|
row.lmax = s.lmax
|
||||||
row.lmin = s.lmin
|
row.lmin = s.lmin
|
||||||
row.lpeak = s.lpeak
|
row.lpeak = s.lpeak
|
||||||
|
row.ln1 = s.ln1
|
||||||
|
row.ln2 = s.ln2
|
||||||
row.battery_level = s.battery_level
|
row.battery_level = s.battery_level
|
||||||
row.power_source = s.power_source
|
row.power_source = s.power_source
|
||||||
row.sd_remaining_mb = s.sd_remaining_mb
|
row.sd_remaining_mb = s.sd_remaining_mb
|
||||||
@@ -716,9 +720,10 @@ class NL43Client:
|
|||||||
snap.lmin = parts[4] # Lmin
|
snap.lmin = parts[4] # Lmin
|
||||||
if len(parts) >= 11:
|
if len(parts) >= 11:
|
||||||
snap.lpeak = parts[10] # Lpeak (parts[5] is LN1, NOT Lpeak)
|
snap.lpeak = parts[10] # Lpeak (parts[5] is LN1, NOT Lpeak)
|
||||||
# LN1/LN2 percentiles live at parts[5]/parts[6] (the L1/L10 display contract).
|
if len(parts) >= 6:
|
||||||
# Surfaced as snap.ln1/snap.ln2 once those fields are added to the snapshot
|
snap.ln1 = parts[5] # LN1 percentile slot (device default L5; contract L1)
|
||||||
# dataclass + NL43Status model — next step on this branch.
|
if len(parts) >= 7:
|
||||||
|
snap.ln2 = parts[6] # LN2 percentile slot (device default L10)
|
||||||
except (IndexError, ValueError) as e:
|
except (IndexError, ValueError) as e:
|
||||||
logger.warning(f"Error parsing DOD data points: {e}")
|
logger.warning(f"Error parsing DOD data points: {e}")
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Migration script to add ln1 and ln2 percentile columns to the nl43_status table.
|
||||||
|
|
||||||
|
The NL-43 DOD response carries percentile slots LN1-LN5; the live SLM display
|
||||||
|
(Terra-View) shows two of them (default L1/L10). This adds storage for the two
|
||||||
|
surfaced slots. Run once per database to update existing schema.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sqlite3
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
DB_PATH = Path(__file__).parent / "data" / "slmm.db"
|
||||||
|
|
||||||
|
|
||||||
|
def migrate():
|
||||||
|
"""Add ln1 and ln2 columns to the nl43_status table."""
|
||||||
|
|
||||||
|
if not DB_PATH.exists():
|
||||||
|
print(f"Database not found at {DB_PATH}")
|
||||||
|
print("No migration needed - database will be created with new schema")
|
||||||
|
return
|
||||||
|
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor.execute("PRAGMA table_info(nl43_status)")
|
||||||
|
columns = [row[1] for row in cursor.fetchall()]
|
||||||
|
|
||||||
|
if "ln1" in columns and "ln2" in columns:
|
||||||
|
print("✓ ln1/ln2 columns already exist, no migration needed")
|
||||||
|
return
|
||||||
|
|
||||||
|
if "ln1" not in columns:
|
||||||
|
print("Adding ln1 column...")
|
||||||
|
cursor.execute("ALTER TABLE nl43_status ADD COLUMN ln1 TEXT")
|
||||||
|
print("✓ Added ln1 column")
|
||||||
|
|
||||||
|
if "ln2" not in columns:
|
||||||
|
print("Adding ln2 column...")
|
||||||
|
cursor.execute("ALTER TABLE nl43_status ADD COLUMN ln2 TEXT")
|
||||||
|
print("✓ Added ln2 column")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
print("\n✓ Migration completed successfully!")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
conn.rollback()
|
||||||
|
print(f"✗ Migration failed: {e}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
migrate()
|
||||||
Reference in New Issue
Block a user