fix: improve monitoring functionality with session-reset signal and payload adjustments
This commit is contained in:
@@ -582,17 +582,33 @@ 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).
|
Standard two-step read (probe at offset 0x00, data at offset 0x2C).
|
||||||
Response SUB = 0xFF − 0x1C = **0xE3** (standard formula — no exception).
|
Response SUB = 0xFF − 0x1C = **0xE3** (standard formula — no exception).
|
||||||
|
|
||||||
Payload length indicates mode:
|
**Payload length is ~46–49 bytes in BOTH idle and monitoring states** — length alone
|
||||||
- **44 bytes (0x2C)** — unit is **idle**: full status block with battery + memory
|
is NOT a reliable mode indicator. Earlier note claiming "12 bytes when monitoring"
|
||||||
- **12 bytes** — unit is **monitoring**: abbreviated block, no battery/memory fields
|
was wrong (confirmed 2026-04-08 from 4-8-26/mid-monitor captures).
|
||||||
|
|
||||||
**Field offsets (idle payload, relative to the 11-byte section header start):**
|
**Monitoring flag (confirmed from 4-8-26/2ndtry byte-by-byte comparison):**
|
||||||
|
- `section[1] == 0x00` → unit is **idle**
|
||||||
|
- `section[1] == 0x10` → unit is **monitoring** (flips exactly at start/stop transitions)
|
||||||
|
|
||||||
|
Battery and memory fields are present in **both** states.
|
||||||
|
|
||||||
|
**Field offsets (relative to `data[11:]` = section, confirmed from 2ndtry IDLE frames):**
|
||||||
|
|
||||||
| Offset | Field | Type | Notes |
|
| Offset | Field | Type | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `[0x2F:0x31]` | battery voltage × 100 | uint16 BE | `0x02A8` = 680 → 6.80 V |
|
| `[36:38]` | battery voltage × 100 | uint16 BE | `0x02A8` = 680 → 6.80 V |
|
||||||
| `[0x31:0x35]` | memory total (bytes) | uint32 BE | e.g. 983040 = 960 KB |
|
| `[38:42]` | memory total (bytes) | uint32 BE | e.g. 983026 ≈ 960 KB |
|
||||||
| `[0x35:0x39]` | memory free (bytes) | uint32 BE | |
|
| `[42:46]` | memory free (bytes) | uint32 BE | decreases as events are stored |
|
||||||
|
|
||||||
|
### SESSION_RESET signal (`41 03`) — required for monitoring units
|
||||||
|
|
||||||
|
Confirmed from 4-8-26 BW TX captures: Blastware sends a 2-byte `41 03` (ACK + ETX,
|
||||||
|
no STX) immediately before the first POLL probe AND between the probe and data frames.
|
||||||
|
This signal is **required to wake units that are actively monitoring** — without it
|
||||||
|
the unit does not respond to POLL over TCP. Harmless for idle units.
|
||||||
|
|
||||||
|
`SESSION_RESET = bytes([0x41, 0x03])` is defined in `framing.py` and sent by
|
||||||
|
`protocol.startup()` before and between POLL frames.
|
||||||
|
|
||||||
### SUB 0x96 — Start monitoring
|
### SUB 0x96 — Start monitoring
|
||||||
|
|
||||||
|
|||||||
+16
-9
@@ -1767,25 +1767,32 @@ def _decode_monitor_status(data: bytes) -> MonitorStatus:
|
|||||||
# The data section starts at offset 11 (after the S3 section header).
|
# The data section starts at offset 11 (after the S3 section header).
|
||||||
section = data[11:] if len(data) > 11 else data
|
section = data[11:] if len(data) > 11 else data
|
||||||
|
|
||||||
# Log the raw payload at WARNING level so we can see it in the server logs
|
log.debug(
|
||||||
# and confirm the field offsets and is_monitoring detection are correct.
|
|
||||||
log.warning(
|
|
||||||
"_decode_monitor_status: total data=%d bytes section=%d bytes hex=%s",
|
"_decode_monitor_status: total data=%d bytes section=%d bytes hex=%s",
|
||||||
len(data), len(section), section.hex(),
|
len(data), len(section), section.hex(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mode: idle payload is 44 bytes; monitoring is shorter (12 bytes observed)
|
# Monitoring flag confirmed from 2ndtry 4-8-26 capture:
|
||||||
is_monitoring = len(section) < 20
|
# section[1] == 0x00 → idle
|
||||||
|
# section[1] == 0x10 → monitoring (flips exactly at start/stop transitions)
|
||||||
|
# Payload length is ~46-49 bytes in BOTH states — length alone is unreliable.
|
||||||
|
is_monitoring = len(section) > 1 and section[1] == 0x10
|
||||||
|
|
||||||
battery_v = None
|
battery_v = None
|
||||||
memory_total = None
|
memory_total = None
|
||||||
memory_free = None
|
memory_free = None
|
||||||
|
|
||||||
if not is_monitoring and len(section) >= 0x39:
|
# Battery and memory fields confirmed from 2ndtry IDLE S3 frames:
|
||||||
batt_raw = struct.unpack(">H", section[0x2F:0x31])[0]
|
# section[36:38] battery × 100 uint16 BE 0x02A8 = 680 → 6.80 V
|
||||||
|
# section[38:42] memory_total uint32 BE bytes = total flash
|
||||||
|
# section[42:46] memory_free uint32 BE bytes = free flash
|
||||||
|
# These fields appear in BOTH idle and monitoring payloads (battery/memory
|
||||||
|
# are available regardless of mode).
|
||||||
|
if len(section) >= 46:
|
||||||
|
batt_raw = struct.unpack(">H", section[36:38])[0]
|
||||||
battery_v = batt_raw / 100.0
|
battery_v = batt_raw / 100.0
|
||||||
memory_total = struct.unpack(">I", section[0x31:0x35])[0]
|
memory_total = struct.unpack(">I", section[38:42])[0]
|
||||||
memory_free = struct.unpack(">I", section[0x35:0x39])[0]
|
memory_free = struct.unpack(">I", section[42:46])[0]
|
||||||
|
|
||||||
return MonitorStatus(
|
return MonitorStatus(
|
||||||
is_monitoring=is_monitoring,
|
is_monitoring=is_monitoring,
|
||||||
|
|||||||
@@ -438,6 +438,14 @@ def bulk_waveform_term_params(key4: bytes, counter: int) -> bytes:
|
|||||||
POLL_PROBE = build_bw_frame(0x5B, 0x00) # length-probe POLL (offset = 0)
|
POLL_PROBE = build_bw_frame(0x5B, 0x00) # length-probe POLL (offset = 0)
|
||||||
POLL_DATA = build_bw_frame(0x5B, 0x30) # data-request POLL (offset = 0x30)
|
POLL_DATA = build_bw_frame(0x5B, 0x30) # data-request POLL (offset = 0x30)
|
||||||
|
|
||||||
|
# Session-reset signal (ACK + ETX, no STX/payload).
|
||||||
|
# Confirmed from 4-8-26 BW TX captures: Blastware sends this 2-byte sequence
|
||||||
|
# immediately before the first POLL probe, and again between the POLL probe
|
||||||
|
# and the POLL data request. Required to wake a unit that is actively
|
||||||
|
# monitoring — without it the unit does not respond to POLL over TCP.
|
||||||
|
# Harmless for idle units (they respond to POLL regardless).
|
||||||
|
SESSION_RESET = bytes([0x41, 0x03])
|
||||||
|
|
||||||
|
|
||||||
# ── S3 response dataclass ─────────────────────────────────────────────────────
|
# ── S3 response dataclass ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ from .framing import (
|
|||||||
bulk_waveform_term_params,
|
bulk_waveform_term_params,
|
||||||
POLL_PROBE,
|
POLL_PROBE,
|
||||||
POLL_DATA,
|
POLL_DATA,
|
||||||
|
SESSION_RESET,
|
||||||
)
|
)
|
||||||
from .transport import BaseTransport
|
from .transport import BaseTransport
|
||||||
|
|
||||||
@@ -190,6 +191,13 @@ class MiniMateProtocol:
|
|||||||
log.debug("startup: draining boot string")
|
log.debug("startup: draining boot string")
|
||||||
self._drain_boot_string()
|
self._drain_boot_string()
|
||||||
|
|
||||||
|
# Send session-reset signal (ACK+ETX) before the first POLL probe.
|
||||||
|
# Confirmed from 4-8-26 BW TX captures: Blastware always sends this
|
||||||
|
# 2-byte signal at session start. Required to wake units that are
|
||||||
|
# actively monitoring — without it they don't respond to POLL over TCP.
|
||||||
|
log.debug("startup: session reset signal")
|
||||||
|
self._send(SESSION_RESET)
|
||||||
|
|
||||||
log.debug("startup: POLL probe")
|
log.debug("startup: POLL probe")
|
||||||
self._send(POLL_PROBE)
|
self._send(POLL_PROBE)
|
||||||
probe_rsp = self._recv_one(
|
probe_rsp = self._recv_one(
|
||||||
@@ -200,6 +208,10 @@ class MiniMateProtocol:
|
|||||||
"startup: POLL probe response page_key=0x%04X", probe_rsp.page_key
|
"startup: POLL probe response page_key=0x%04X", probe_rsp.page_key
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Send another session-reset between probe and data (matches BW behavior).
|
||||||
|
log.debug("startup: session reset signal (inter-frame)")
|
||||||
|
self._send(SESSION_RESET)
|
||||||
|
|
||||||
log.debug("startup: POLL data request")
|
log.debug("startup: POLL data request")
|
||||||
self._send(POLL_DATA)
|
self._send(POLL_DATA)
|
||||||
data_rsp = self._recv_one(
|
data_rsp = self._recv_one(
|
||||||
|
|||||||
Reference in New Issue
Block a user