feat: add high-water mark state tracking to ach_server + fix monitoring flag
ach_server.py: - Add ach_state.json per-unit state tracking (keyed by serial number) - count_events() before any download; skip session if no new events since last call-home - Download only events beyond the previous high-water mark (all_events[last_count:]) - --max-events N safety cap for first-run units with many stored events - state_path and max_events wired through AchSession constructor and serve() client.py (_decode_monitor_status): - Revert monitoring flag to section[1] == 0x10 (was incorrectly changed to section[6]) - Fix battery/memory offsets to section[-10:-8], [-8:-4], [-4:] (no trailing checksum byte) - Both confirmed by full byte diff of all 144 0xE3 data frames in 4-8-26/2ndtry capture Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+28
-27
@@ -1755,17 +1755,20 @@ def _decode_monitor_status(data: bytes) -> MonitorStatus:
|
||||
data is the raw S3 frame .data attribute (includes the 11-byte section
|
||||
header, so field offsets below are relative to data[11]).
|
||||
|
||||
Monitoring flag (confirmed 4-8-26/2ndtry, full byte diff analysis):
|
||||
section[6] == 0x00 → idle
|
||||
section[6] == 0x10 → monitoring
|
||||
NOTE: frame.data has the checksum byte already stripped by S3FrameParser
|
||||
(_finalise returns raw_payload[5:] where raw_payload = body[:-1]).
|
||||
There is NO trailing checksum byte in section.
|
||||
|
||||
The payload size varies (52–55+ bytes) but the battery/memory block is
|
||||
always the last 10 bytes before the trailing checksum byte:
|
||||
Monitoring flag (confirmed 4-8-26/2ndtry, byte diff of all 144 data frames):
|
||||
section[1] == 0x00 → idle
|
||||
section[1] == 0x10 → monitoring
|
||||
|
||||
section[-11:-9] battery × 100 uint16 BE (0x02A8 = 6.80 V)
|
||||
section[-9 :-5] memory_total uint32 BE bytes
|
||||
section[-5 :-1] memory_free uint32 BE bytes
|
||||
section[-1] checksum (not data)
|
||||
The payload length varies (46–49 bytes) — IDLE is 46-47, MONITORING is 48-49.
|
||||
The battery/memory block is always the last 10 bytes of section (no checksum):
|
||||
|
||||
section[-10:-8] battery × 100 uint16 BE (0x02A8 = 6.80 V)
|
||||
section[-8 :-4] memory_total uint32 BE bytes
|
||||
section[-4:] memory_free uint32 BE bytes
|
||||
|
||||
Values confirmed from 4-8-26/2ndtry capture (BE11529):
|
||||
battery 0x02A8 = 680 → 6.80 V
|
||||
@@ -1780,32 +1783,30 @@ def _decode_monitor_status(data: bytes) -> MonitorStatus:
|
||||
len(data), len(section), section.hex(),
|
||||
)
|
||||
|
||||
# Monitoring flag: section[6] (CORRECTED 2026-04-08 — was wrongly section[1]).
|
||||
# Byte diff of 2ndtry BW-S3 captures confirms section[6] flips 0x00↔0x10
|
||||
# exactly at the start/stop monitoring transitions (0xE3 frame #36 / #132).
|
||||
is_monitoring = len(section) > 6 and section[6] == 0x10
|
||||
# Monitoring flag: section[1] == 0x10.
|
||||
# Confirmed from byte diff of all 144 0xE3 data frames in 4-8-26/2ndtry capture:
|
||||
# section[1] = 0x00 in all IDLE frames, 0x10 in all MONITORING frames.
|
||||
# (section[6] also changes but has non-binary values 0xea/0x07 — device-specific.)
|
||||
is_monitoring = len(section) > 1 and section[1] == 0x10
|
||||
|
||||
battery_v = None
|
||||
memory_total = None
|
||||
memory_free = None
|
||||
|
||||
# Battery and memory offsets are RELATIVE TO THE END of the section.
|
||||
# The payload length varies (52–55+ bytes) depending on monitoring state and
|
||||
# internal counters, but the battery/memory block is always the last 10 bytes
|
||||
# before the checksum (section[-1]).
|
||||
# Battery and memory at relative-from-end offsets.
|
||||
# Payload length varies (46–49 bytes) but the battery/memory block is always
|
||||
# the last 10 bytes. No checksum byte — it was stripped by S3FrameParser.
|
||||
#
|
||||
# section[-11:-9] battery × 100 uint16 BE 0x02A8 = 6.80 V
|
||||
# section[-9 :-5] memory_total uint32 BE ≈ 960 KB on BE11529
|
||||
# section[-5 :-1] memory_free uint32 BE decreases as events fill
|
||||
# section[-1] frame checksum (not data)
|
||||
# section[-10:-8] battery × 100 uint16 BE 0x02A8 = 6.80 V
|
||||
# section[-8 :-4] memory_total uint32 BE ≈ 960 KB on BE11529
|
||||
# section[-4:] memory_free uint32 BE decreases as events fill
|
||||
#
|
||||
# Confirmed stable across IDLE (52b), MONITORING (55b), and counter-jitter
|
||||
# IDLE variants (53b) from 4-8-26/2ndtry full capture analysis.
|
||||
if len(section) >= 11:
|
||||
batt_raw = struct.unpack(">H", section[-11:-9])[0]
|
||||
# Confirmed stable across IDLE (46b), MONITORING (48-49b) variants.
|
||||
if len(section) >= 10:
|
||||
batt_raw = struct.unpack(">H", section[-10:-8])[0]
|
||||
battery_v = batt_raw / 100.0
|
||||
memory_total = struct.unpack(">I", section[-9:-5])[0]
|
||||
memory_free = struct.unpack(">I", section[-5:-1])[0]
|
||||
memory_total = struct.unpack(">I", section[-8:-4])[0]
|
||||
memory_free = struct.unpack(">I", section[-4:])[0]
|
||||
|
||||
return MonitorStatus(
|
||||
is_monitoring=is_monitoring,
|
||||
|
||||
Reference in New Issue
Block a user