feat: Add monitoring functionality to MiniMate protocol and web interface

- Introduced new SUBs for monitoring status, start, and stop commands in protocol.py.
- Implemented read_monitor_status, start_monitoring, and stop_monitoring methods in MiniMateProtocol class.
- Added new API endpoints for monitoring status retrieval and control in server.py.
- Enhanced the web application with a monitoring panel, including battery and memory status display.
- Created a new Python script to parse SUB 0x1C response frames for monitoring status.
- Documented the monitoring status response format and field locations in markdown and text files.
This commit is contained in:
2026-04-08 14:34:42 -04:00
parent 8545daac04
commit a41e7a9e1a
9 changed files with 1121 additions and 10 deletions
+71 -2
View File
@@ -57,7 +57,7 @@ SUB_SERIAL_NUMBER = 0x15
SUB_FULL_CONFIG = 0x01
SUB_EVENT_INDEX = 0x08
SUB_CHANNEL_CONFIG = 0x06
SUB_TRIGGER_CONFIG = 0x1C
SUB_MONITOR_STATUS = 0x1C # Monitoring status read (battery, memory, mode) ✅
SUB_EVENT_HEADER = 0x1E
SUB_EVENT_ADVANCE = 0x1F
SUB_WAVEFORM_HEADER = 0x0A
@@ -77,6 +77,10 @@ SUB_WRITE_CONFIRM_C = 0x74 # Confirm C — sent after 69 ✅
SUB_TRIGGER_CONFIG_WRITE = 0x82 # Write trigger config (0x22 + 0x60) ✅
SUB_TRIGGER_CONFIRM = 0x83 # Confirm trigger write ✅
# Monitoring control SUBs (confirmed from 4-8-26/2ndtry BW TX capture)
SUB_START_MONITORING = 0x96 # Start monitoring → response 0x69 ✅
SUB_STOP_MONITORING = 0x97 # Stop monitoring → response 0x68 ✅
# Hardcoded data lengths for the two-step read protocol.
#
# The S3 probe response page_key is always 0x0000 — it does NOT carry the
@@ -91,7 +95,7 @@ DATA_LENGTHS: dict[int, int] = {
SUB_SERIAL_NUMBER: 0x0A, # 10-byte serial number block ✅
SUB_FULL_CONFIG: 0x98, # 152-byte full config block ✅
SUB_EVENT_INDEX: 0x58, # 88-byte event index ✅
SUB_TRIGGER_CONFIG: 0x2C, # 44-byte trigger config 🔶
SUB_MONITOR_STATUS: 0x2C, # 44-byte monitor status block (idle) ✅
SUB_EVENT_HEADER: 0x08, # 8-byte event header (waveform key + event data) ✅
SUB_EVENT_ADVANCE: 0x08, # 8-byte next-key response ✅
# SUB_WAVEFORM_HEADER (0x0A) is VARIABLE — length read from probe response
@@ -1056,6 +1060,71 @@ class MiniMateProtocol:
self._send(frame)
return self.recv_write_ack(expected_sub=rsp_sub)
# ── Monitoring ────────────────────────────────────────────────────────────
def read_monitor_status(self) -> S3Frame:
"""
Read monitoring status (SUB 0x1C → response 0xE3).
Two-step read: probe (offset=0x00) then data (offset=0x2C).
Returns:
S3Frame with 44 bytes of status data (idle state).
When unit is actively monitoring the payload is shorter (12 bytes);
callers should check frame data length to determine mode.
Raises:
ProtocolError: on timeout or wrong response SUB.
"""
rsp_sub = _expected_rsp_sub(SUB_MONITOR_STATUS) # 0xFF - 0x1C = 0xE3
log.debug("read_monitor_status: probe step rsp_sub=0x%02X", rsp_sub)
probe_frame = build_bw_frame(SUB_MONITOR_STATUS, offset=0x00)
self._send(probe_frame)
self._recv_one(expected_sub=rsp_sub)
log.debug("read_monitor_status: data step offset=0x%02X", DATA_LENGTHS[SUB_MONITOR_STATUS])
data_frame = build_bw_frame(SUB_MONITOR_STATUS, offset=DATA_LENGTHS[SUB_MONITOR_STATUS])
self._send(data_frame)
return self._recv_one(expected_sub=rsp_sub)
def start_monitoring(self) -> S3Frame:
"""
Send Start Monitoring command (SUB 0x96 → response 0x69).
Single write frame, no data payload. Confirmed from 4-8-26/2ndtry
BW TX capture frame 92.
Returns:
S3Frame ack from device.
Raises:
ProtocolError: on timeout or wrong response SUB.
"""
rsp_sub = _expected_rsp_sub(SUB_START_MONITORING) # 0xFF - 0x96 = 0x69
log.debug("start_monitoring: rsp_sub=0x%02X", rsp_sub)
frame = build_bw_write_frame(SUB_START_MONITORING, b"")
self._send(frame)
return self.recv_write_ack(expected_sub=rsp_sub)
def stop_monitoring(self) -> S3Frame:
"""
Send Stop Monitoring command (SUB 0x97 → response 0x68).
Single write frame, no data payload. Confirmed from 4-8-26/2ndtry
BW TX capture frame 305.
Returns:
S3Frame ack from device.
Raises:
ProtocolError: on timeout or wrong response SUB.
"""
rsp_sub = _expected_rsp_sub(SUB_STOP_MONITORING) # 0xFF - 0x97 = 0x68
log.debug("stop_monitoring: rsp_sub=0x%02X", rsp_sub)
frame = build_bw_write_frame(SUB_STOP_MONITORING, b"")
self._send(frame)
return self.recv_write_ack(expected_sub=rsp_sub)
# ── Internal helpers ──────────────────────────────────────────────────────
def _send(self, frame: bytes) -> None: