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:
@@ -582,28 +582,32 @@ All confirmed from 4-8-26/2ndtry BW TX/S3 capture (clean start → 30s monitor
|
||||
Standard two-step read (probe at offset 0x00, data at offset 0x2C).
|
||||
Response SUB = 0xFF − 0x1C = **0xE3** (standard formula — no exception).
|
||||
|
||||
**Payload length is ~46–49 bytes in BOTH idle and monitoring states** — length alone
|
||||
is NOT a reliable mode indicator. Earlier note claiming "12 bytes when monitoring"
|
||||
was wrong (confirmed 2026-04-08 from 4-8-26/mid-monitor captures).
|
||||
**Payload length is 46–47 bytes IDLE, 48–49 bytes MONITORING** — not a reliable sole
|
||||
indicator due to 1-byte jitter overlap at the boundary.
|
||||
|
||||
**Monitoring flag (CORRECTED 2026-04-08 — full byte diff of 2ndtry capture):**
|
||||
- `section[6] == 0x00` → unit is **idle**
|
||||
- `section[6] == 0x10` → unit is **monitoring**
|
||||
**Monitoring flag (CONFIRMED 2026-04-09 — byte diff of all 144 data frames, 2ndtry capture):**
|
||||
- `section[1] == 0x00` → unit is **idle**
|
||||
- `section[1] == 0x10` → unit is **monitoring**
|
||||
|
||||
Earlier note claiming `section[1]` was the flag was WRONG — section[1] is always 0x00 in both states. The correction was found by diffing all 0xE3 data frames across the start/stop transitions: `section[6]` is the only byte that flips cleanly at frame #36 (start) and #132 (stop) within the 2ndtry 0xE3 frame sequence.
|
||||
This is `data[12]` (= `frame.data[12]`). The flag is 0x00 in all 36 IDLE_BEFORE frames,
|
||||
0x10 in all 98 MONITORING frames, and 0x00 in all 10 IDLE_AFTER frames — 100% accurate.
|
||||
|
||||
Battery and memory fields are present in **both** states, but the payload grows by **3 bytes** when monitoring is active (section goes from ~52 to ~55 bytes), shifting subsequent fields by +3.
|
||||
**HISTORY OF THIS FIELD (do not re-derive):** The original implementation used `section[1]`.
|
||||
A re-analysis in the prior session incorrectly concluded `section[1]` is always 0x00 and
|
||||
"corrected" the flag to `section[6]`, which has non-binary values (0xea idle, 0x07 monitoring)
|
||||
and is device-specific. The 2026-04-09 re-analysis confirms `section[1]` was right.
|
||||
|
||||
**Field offsets (relative to `data[11:]` = section):**
|
||||
**IMPORTANT — `frame.data` has checksum already stripped** by `S3FrameParser._finalise()`
|
||||
(`raw_payload = body[:-1]`; `data = raw_payload[5:]`). There is NO trailing checksum byte in
|
||||
`section`. All relative-from-end offsets must account for this.
|
||||
|
||||
Battery and memory are at **relative offsets from the end** — the payload can vary by ±1–3 bytes due to counter jitter and monitoring-mode expansion, but these 10 bytes are always anchored at the end:
|
||||
Battery and memory fields are present in **both** states:
|
||||
|
||||
| Offset (relative to end) | Field | Type | Notes |
|
||||
|---|---|---|---|
|
||||
| `section[-11:-9]` | battery voltage × 100 | uint16 BE | `0x02A8` = 680 → 6.80 V |
|
||||
| `section[-9:-5]` | memory total (bytes) | uint32 BE | e.g. 983026 ≈ 960 KB |
|
||||
| `section[-5:-1]` | memory free (bytes) | uint32 BE | decreases as events are stored |
|
||||
| `section[-1]` | frame checksum | — | last byte, skip |
|
||||
| `section[-10:-8]` | battery voltage × 100 | uint16 BE | `0x02A8` = 680 → 6.80 V |
|
||||
| `section[-8:-4]` | memory total (bytes) | uint32 BE | e.g. 983026 ≈ 960 KB |
|
||||
| `section[-4:]` | memory free (bytes) | uint32 BE | decreases as events are stored |
|
||||
|
||||
### SESSION_RESET signal (`41 03`) — required for monitoring units
|
||||
|
||||
@@ -657,7 +661,7 @@ Key findings:
|
||||
|
||||
**SFM behavior after `POST /device/monitor/start`:** `_pollMonitorConfirm()` polls
|
||||
`/device/monitor/status` every 5 s for up to 60 s, updating the badge on each poll.
|
||||
Status will show MONITORING once `section[6]` flips to `0x10`.
|
||||
Status will show MONITORING once `section[1]` flips to `0x10`.
|
||||
|
||||
### SUBs known from sensor-check capture (4-8-26) — NOT YET IMPLEMENTED
|
||||
|
||||
|
||||
Reference in New Issue
Block a user