fix: correct DOD field parsing and stop measurement-time resets
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>
This commit is contained in:
+27
-15
@@ -69,10 +69,16 @@ def persist_snapshot(s: NL43Snapshot, db: Session):
|
|||||||
|
|
||||||
logger.info(f"State transition check for {s.unit_id}: '{previous_state}' -> '{new_state}'")
|
logger.info(f"State transition check for {s.unit_id}: '{previous_state}' -> '{new_state}'")
|
||||||
|
|
||||||
# Device returns "Start" when measuring, "Stop" when stopped
|
# The device reports "Start" while measuring; the DOD path uses that string,
|
||||||
# Normalize to previous behavior for backward compatibility
|
# but the DRD stream path tags snapshots "Measure" (and the DOD fallback also
|
||||||
is_measuring = new_state == "Start"
|
# uses "Measure"). Treat ALL of these as "measuring" — otherwise opening and
|
||||||
was_measuring = previous_state == "Start"
|
# closing the live stream flips state "Start"->"Measure"->"Start", which the
|
||||||
|
# old equality check misread as stop-then-start and RESET measurement_start_time
|
||||||
|
# every single time (the "elapsed time keeps resetting / shows wrong value on
|
||||||
|
# another computer" bug — and each extra viewer made it worse).
|
||||||
|
MEASURING_STATES = {"Start", "Measure"}
|
||||||
|
is_measuring = new_state in MEASURING_STATES
|
||||||
|
was_measuring = previous_state in MEASURING_STATES
|
||||||
|
|
||||||
if not was_measuring and is_measuring:
|
if not was_measuring and is_measuring:
|
||||||
# Measurement just started - record the start time
|
# Measurement just started - record the start time
|
||||||
@@ -691,22 +697,28 @@ class NL43Client:
|
|||||||
|
|
||||||
snap = NL43Snapshot(unit_id="", raw_payload=resp, measurement_state=measurement_state)
|
snap = NL43Snapshot(unit_id="", raw_payload=resp, measurement_state=measurement_state)
|
||||||
|
|
||||||
# Parse known positions (based on NL43 communication guide - DRD format)
|
# Parse DOD positional fields. DOD's layout is DIFFERENT from DRD: it has NO
|
||||||
# DRD format: d0=counter, d1=Lp, d2=Leq, d3=Lmax, d4=Lmin, d5=Lpeak, d6=LIeq, ...
|
# leading counter and it includes LE plus LN1–LN5. The device returns 4 channels
|
||||||
|
# of 16 fields each — [Lp, Leq, LE, Lmax, Lmin, LN1, LN2, LN3, LN4, LN5, Lpeak,
|
||||||
|
# LIeq, Leq_mov, Ltm5, over, under] — and channel 1 (parts[0:16]) is the main
|
||||||
|
# display. The previous code reused the DRD map (treating parts[0] as a counter),
|
||||||
|
# which shifted everything: Lp was reported as the counter, Leq as Lp, LE as Leq,
|
||||||
|
# and LN1 as Lpeak (you could spot it because "Lpeak" came out < Lmax).
|
||||||
try:
|
try:
|
||||||
# Capture d0 (counter) for timer synchronization
|
|
||||||
if len(parts) >= 1:
|
if len(parts) >= 1:
|
||||||
snap.counter = parts[0] # d0: Measurement interval counter (1-600)
|
snap.lp = parts[0] # Lp: instantaneous sound pressure level
|
||||||
if len(parts) >= 2:
|
if len(parts) >= 2:
|
||||||
snap.lp = parts[1] # d1: Instantaneous sound pressure level
|
snap.leq = parts[1] # Leq: equivalent continuous level
|
||||||
if len(parts) >= 3:
|
# parts[2] = LE (sound exposure level) — not currently surfaced
|
||||||
snap.leq = parts[2] # d2: Equivalent continuous sound level
|
|
||||||
if len(parts) >= 4:
|
if len(parts) >= 4:
|
||||||
snap.lmax = parts[3] # d3: Maximum level
|
snap.lmax = parts[3] # Lmax
|
||||||
if len(parts) >= 5:
|
if len(parts) >= 5:
|
||||||
snap.lmin = parts[4] # d4: Minimum level
|
snap.lmin = parts[4] # Lmin
|
||||||
if len(parts) >= 6:
|
if len(parts) >= 11:
|
||||||
snap.lpeak = parts[5] # d5: Peak level
|
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).
|
||||||
|
# Surfaced as snap.ln1/snap.ln2 once those fields are added to the snapshot
|
||||||
|
# dataclass + NL43Status model — next step on this branch.
|
||||||
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}")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user