fix: correct monitoring flag and battery/memory offsets in _decode_monitor_status
section[6] is the monitoring flag (was wrongly section[1] — section[1] is always 0x00 in both states). Battery and memory fields use relative-from-end offsets (section[-11:-9], section[-9:-5], section[-5:-1]) instead of absolute positions, which broke when the payload grew by 3 bytes in monitoring mode. Confirmed from full byte diff of 142 0xE3 frames in 4-8-26/2ndtry capture. SFM start_monitoring now polls /device/monitor/status every 5s for up to 60s instead of a fixed 25s delay (unit runs ~40s on-device sensor check before confirming monitoring state). Also corrects stale 1C→6E response anomaly claim in protocol reference — no exceptions to the 0xFF−SUB rule are known.
This commit is contained in:
@@ -586,19 +586,24 @@ Response SUB = 0xFF − 0x1C = **0xE3** (standard formula — no exception).
|
|||||||
is NOT a reliable mode indicator. Earlier note claiming "12 bytes when monitoring"
|
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).
|
was wrong (confirmed 2026-04-08 from 4-8-26/mid-monitor captures).
|
||||||
|
|
||||||
**Monitoring flag (confirmed from 4-8-26/2ndtry byte-by-byte comparison):**
|
**Monitoring flag (CORRECTED 2026-04-08 — full byte diff of 2ndtry capture):**
|
||||||
- `section[1] == 0x00` → unit is **idle**
|
- `section[6] == 0x00` → unit is **idle**
|
||||||
- `section[1] == 0x10` → unit is **monitoring** (flips exactly at start/stop transitions)
|
- `section[6] == 0x10` → unit is **monitoring**
|
||||||
|
|
||||||
Battery and memory fields are present in **both** states.
|
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.
|
||||||
|
|
||||||
**Field offsets (relative to `data[11:]` = section, confirmed from 2ndtry IDLE frames):**
|
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.
|
||||||
|
|
||||||
| Offset | Field | Type | Notes |
|
**Field offsets (relative to `data[11:]` = section):**
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
| Offset (relative to end) | Field | Type | Notes |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `[36:38]` | battery voltage × 100 | uint16 BE | `0x02A8` = 680 → 6.80 V |
|
| `section[-11:-9]` | battery voltage × 100 | uint16 BE | `0x02A8` = 680 → 6.80 V |
|
||||||
| `[38:42]` | memory total (bytes) | uint32 BE | e.g. 983026 ≈ 960 KB |
|
| `section[-9:-5]` | memory total (bytes) | uint32 BE | e.g. 983026 ≈ 960 KB |
|
||||||
| `[42:46]` | memory free (bytes) | uint32 BE | decreases as events are stored |
|
| `section[-5:-1]` | memory free (bytes) | uint32 BE | decreases as events are stored |
|
||||||
|
| `section[-1]` | frame checksum | — | last byte, skip |
|
||||||
|
|
||||||
### SESSION_RESET signal (`41 03`) — required for monitoring units
|
### SESSION_RESET signal (`41 03`) — required for monitoring units
|
||||||
|
|
||||||
@@ -632,10 +637,88 @@ Wire bytes (confirmed frame 305 of 2ndtry BW capture):
|
|||||||
|
|
||||||
Both start and stop acks are standard 17-byte zero-data S3 frames.
|
Both start and stop acks are standard 17-byte zero-data S3 frames.
|
||||||
|
|
||||||
|
### On-device sensor check behavior (confirmed 2026-04-08)
|
||||||
|
|
||||||
|
Confirmed from 4-8-26/sensor-check BW+S3 capture (Blastware "Unit Channel Test" comms
|
||||||
|
check issued while unit was performing its on-device sensor check).
|
||||||
|
|
||||||
|
**Unit IS reachable during on-device sensor check** — POLL (SUB 5B) responded normally
|
||||||
|
throughout. However, the unit partially handled channel-test commands (SUB 0x0E) for
|
||||||
|
channels 0–4 and then went **silent for ~40 seconds** while the sensor check ran, before
|
||||||
|
resuming responses for channels 5–7 and the trigger test (SUB 0x98).
|
||||||
|
|
||||||
|
Key findings:
|
||||||
|
- On-device sensor check duration: approximately **40 seconds** (log gap `18:40:48` → `18:41:28`)
|
||||||
|
- Unit IS reachable for POLL during the check window — SESSION_RESET + POLL works
|
||||||
|
- Partial command responses during check are possible (device may buffer some, drop others)
|
||||||
|
- The Blastware "Unit Channel Test" (remote comms check, SUBs 0x0E + 0x98) is a SEPARATE
|
||||||
|
operation from the on-device check — it is a passive remote read; the unit's screen does
|
||||||
|
not change during a remote comms check
|
||||||
|
|
||||||
|
**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`.
|
||||||
|
|
||||||
|
### SUBs known from sensor-check capture (4-8-26) — NOT YET IMPLEMENTED
|
||||||
|
|
||||||
|
| BW SUB | RSP SUB | Function | Notes |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 0x15 | 0xEA | Serial number / short ID | 2-step read; data offset = 0x0A (10 bytes); confirmed serial `"BE11529"` at `data[11+5:]` |
|
||||||
|
| 0x01 | 0xFE | Device info block | 2-step read; data offset = 0x98 (152 bytes); payload includes serial + firmware + float config fields |
|
||||||
|
| 0x0E | 0xF1 | Channel sensor data | 2-step read; channel selector in `params[6:8]` (`0x0000`–`0x0007`); data length 0x0A per channel; used by Blastware "Unit Channel Test" — see docs/ for details |
|
||||||
|
| 0x98 | 0x67 | Trigger test | Single probe frame (`params[0]=0xFF`); sent twice per test cycle; all-zero data response; used after 0x0E channel scan |
|
||||||
|
|
||||||
|
Blastware's "Unit Channel Test" sequence: `POLL×N → 0x15 → 0x01 → 0x08 → 0x01 → 0x0E×8 → 0x98×2 → 0x0E×8` (repeat pass with live ADC readings).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Compliance config field inventory (from Blastware UI, 2026-04-08)
|
||||||
|
|
||||||
|
Fields visible in the Blastware Compliance Setup dialog — most are NOT YET decoded to byte
|
||||||
|
offsets in the raw 1A/E5 payload. Only fields with `✅` have confirmed offsets in the code.
|
||||||
|
|
||||||
|
**Recording Setup tab:**
|
||||||
|
- Recording Mode: Continuous / Single Shot / Histogram (enum)
|
||||||
|
- Record Stop Mode: Fixed Record Time / Auto / Manual Stop (enum)
|
||||||
|
- Sample Rate: Standard 1024 / Fast 2048 / Faster 4096 sps ✅ (anchor−2)
|
||||||
|
- Record Time: float, seconds ✅ (anchor+10)
|
||||||
|
- Histogram Interval: 5 / 15 / 30 / 60 minutes (enum, mode-gated)
|
||||||
|
- Storage Mode: Save All Data / Save Triggered (enum)
|
||||||
|
- Geophone Type: Standard Triaxial / 4.5 Hz (bool/enum)
|
||||||
|
- Geophone Channels: Enable all geophones (bool), Trigger Source (bool)
|
||||||
|
- Chan 1-3 Trigger Level (float, in/s) ✅ (`trigger_level_geo`)
|
||||||
|
- Chan 1-3 Maximum Range: Normal 10.000 / 1.25 in/s (enum) ✅ (`max_range_geo`)
|
||||||
|
- Microphone Channels: Enable all microphones (bool), Trigger Source (bool)
|
||||||
|
- Chan 4 Trigger Level (dB or psi depending on units)
|
||||||
|
|
||||||
|
**Notes tab:**
|
||||||
|
- Enable User Notes (bool)
|
||||||
|
- Project, Client, User Name, Seis Loc (ASCII strings) ✅ (sourced from A5 frame 7 via 5A)
|
||||||
|
- Enable Extended Notes (bool); Extended Notes text; Extended Notes Title
|
||||||
|
- Enable Job Number (bool); Job Number (int)
|
||||||
|
- Enable Scaled Distance (bool); Distance from Blast (float); Charge Weight (float) — Scaled Distance is derived
|
||||||
|
|
||||||
|
**Special Setups tab:**
|
||||||
|
- Unit Timer: Timer Mode (Off/On), Start Date/Time, Stop Date/Time
|
||||||
|
- Self Check: Mode (Off/On), Time (HH:MM)
|
||||||
|
- Sensor Check: **Before monitoring** / After each event / **Disabled** ❓ (byte offset unknown)
|
||||||
|
- Measurement Units: Imperial / Metric
|
||||||
|
- Show Mic units in dB (bool)
|
||||||
|
- Time Format: 24 Hour / 12 Hour (AM/PM)
|
||||||
|
- Backlight on Time (seconds) ✅ (event index block +75)
|
||||||
|
- Power Saving Timeout (minutes) ✅ (event index block +83)
|
||||||
|
- Monitoring LCD Cycle ✅ (event index block +84:86)
|
||||||
|
- Set unit time with setup (bool)
|
||||||
|
|
||||||
|
The "Sensor Check" dropdown (`Before monitoring` / `After each event` / `Disabled`) has NOT
|
||||||
|
been located in the raw config bytes. The user's unit always runs with `Before monitoring`.
|
||||||
|
Full compliance config encoder is a future task.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## What's next
|
## What's next
|
||||||
|
|
||||||
- Compliance config encoder — build raw write payloads from a `ComplianceConfig` object
|
- Compliance config encoder — build raw write payloads from a `ComplianceConfig` object
|
||||||
|
- Locate "Sensor Check" byte in compliance config (need capture with Disabled vs Before-monitoring)
|
||||||
- ACH inbound server — accept call-home connections from field units
|
- ACH inbound server — accept call-home connections from field units
|
||||||
- Modem manager — push RV50/RV55 configs via Sierra Wireless API
|
- Modem manager — push RV50/RV55 configs via Sierra Wireless API
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
| 2026-03-11 | §14, Appendix B | **CONFIRMED — Aux Trigger read location:** SUB `FE` (FULL_CONFIG_RESPONSE), destuffed payload offset `0x0109`, uint8. `0x00` = disabled, `0x01` = enabled. Confirmed via controlled capture: changed Aux Trigger in Blastware, sent to unit, re-read config. FE diff showed clean isolated flip at `0x0109` with only 3 other bytes changing (likely counters/checksums at `0x0033`, `0x00C0`, `0x04ED`). |
|
| 2026-03-11 | §14, Appendix B | **CONFIRMED — Aux Trigger read location:** SUB `FE` (FULL_CONFIG_RESPONSE), destuffed payload offset `0x0109`, uint8. `0x00` = disabled, `0x01` = enabled. Confirmed via controlled capture: changed Aux Trigger in Blastware, sent to unit, re-read config. FE diff showed clean isolated flip at `0x0109` with only 3 other bytes changing (likely counters/checksums at `0x0033`, `0x00C0`, `0x04ED`). |
|
||||||
| 2026-03-11 | §14, Appendix B | **PARTIAL — Aux Trigger write path:** Write command not yet isolated. The BW→S3 write appears to occur inside the A4 (POLL_RESPONSE) stream via inner frame handshaking — multiple WRITE_CONFIRM_RESPONSE inner frames (SUBs `7C`, `7D`, `8B`, `8C`, `8D`, `8E`, `96`, `97`) appeared in A4 after the write, and the TRIGGER_CONFIG_RESPONSE (SUB `E3`) inner frames were removed. Write command itself not yet captured in a clean session — likely SUB `15` or embedded in the partial session 0. Write path deferred for a future clean capture. |
|
| 2026-03-11 | §14, Appendix B | **PARTIAL — Aux Trigger write path:** Write command not yet isolated. The BW→S3 write appears to occur inside the A4 (POLL_RESPONSE) stream via inner frame handshaking — multiple WRITE_CONFIRM_RESPONSE inner frames (SUBs `7C`, `7D`, `8B`, `8C`, `8D`, `8E`, `96`, `97`) appeared in A4 after the write, and the TRIGGER_CONFIG_RESPONSE (SUB `E3`) inner frames were removed. Write command itself not yet captured in a clean session — likely SUB `15` or embedded in the partial session 0. Write path deferred for a future clean capture. |
|
||||||
| 2026-03-11 | §4, §14 | **NEW — SUB A4 is a composite container frame:** A4 (POLL_RESPONSE) payload contains multiple embedded inner frames using the same DLE framing (10 02 start, 10 03 end, 10 10 stuffing). Phase-shift diffing issue resolved in s3_analyzer.py by adding `_extract_a4_inner_frames()` and `_diff_a4_payloads()` — diff count reduced from 2300 → 17 meaningful entries. |
|
| 2026-03-11 | §4, §14 | **NEW — SUB A4 is a composite container frame:** A4 (POLL_RESPONSE) payload contains multiple embedded inner frames using the same DLE framing (10 02 start, 10 03 end, 10 10 stuffing). Phase-shift diffing issue resolved in s3_analyzer.py by adding `_extract_a4_inner_frames()` and `_diff_a4_payloads()` — diff count reduced from 2300 → 17 meaningful entries. |
|
||||||
| 2026-03-11 | §14 | **NEW — SUB `6E` response anomaly:** BW sends SUB `1C` (TRIGGER_CONFIG_READ) and S3 responds with SUB `6E` — does NOT follow the `0xFF - SUB` rule (`0xFF - 0x1C = 0xE3`). Only known exception to the response pairing rule observed to date. SUB `6E` payload starts with ASCII string `"Long2"`. |
|
| 2026-03-11 | §14 | **NEW — SUB `6E` response anomaly:** BW sends SUB `1C` (TRIGGER_CONFIG_READ) and S3 responds with SUB `6E` — does NOT follow the `0xFF - SUB` rule (`0xFF - 0x1C = 0xE3`). Only known exception to the response pairing rule observed to date. SUB `6E` payload starts with ASCII string `"Long2"`. ~~CORRECTION 2026-04-08: This "exception" was a misidentification. The `1C` in that capture was BW→S3 (a monitor status poll), and the `6E` response was from an inner A4 sub-frame misread as a top-level S3 frame. Confirmed from 4-8-26/2ndtry capture (338 BW TX frames): SUB 0x1C always receives response SUB 0xE3 (= 0xFF − 0x1C). No exceptions to the response pairing rule are known.~~ |
|
||||||
| 2026-03-12 | §11 | **CONFIRMED — BW→S3 large-frame checksum algorithm:** SUBs `68`, `69`, `71`, `82`, and `1A` (with data) use: `chk = (sum(b for b in payload[2:-1] if b != 0x10) + 0x10) % 256` — SUM8 of payload bytes `[2:-1]` skipping all `0x10` bytes, plus `0x10` as a constant, mod 256. Validated across 20 frames from two independent captures with differing string content (checksums differ between sessions, both validate correctly). Small frames (POLL, read commands) continue to use plain SUM8 of `payload[0:-1]`. The two formulas are consistent: small frames have exactly one `0x10` (CMD at `[0]`), which the large-frame formula's `[2:]` start and `+0x10` constant account for. |
|
| 2026-03-12 | §11 | **CONFIRMED — BW→S3 large-frame checksum algorithm:** SUBs `68`, `69`, `71`, `82`, and `1A` (with data) use: `chk = (sum(b for b in payload[2:-1] if b != 0x10) + 0x10) % 256` — SUM8 of payload bytes `[2:-1]` skipping all `0x10` bytes, plus `0x10` as a constant, mod 256. Validated across 20 frames from two independent captures with differing string content (checksums differ between sessions, both validate correctly). Small frames (POLL, read commands) continue to use plain SUM8 of `payload[0:-1]`. The two formulas are consistent: small frames have exactly one `0x10` (CMD at `[0]`), which the large-frame formula's `[2:]` start and `+0x10` constant account for. |
|
||||||
| 2026-03-12 | §11 | **RESOLVED — BAD CHK false positives on BW POLL frames:** Parser bug — BW frame terminator (`03 41`, ETX+ACK) was being included in the de-stuffed payload instead of being stripped as framing. BW frames end with bare `0x03` (not `10 03`). Fix: strip trailing `03 41` from BW payloads before checksum computation. |
|
| 2026-03-12 | §11 | **RESOLVED — BAD CHK false positives on BW POLL frames:** Parser bug — BW frame terminator (`03 41`, ETX+ACK) was being included in the de-stuffed payload instead of being stripped as framing. BW frames end with bare `0x03` (not `10 03`). Fix: strip trailing `03 41` from BW payloads before checksum computation. |
|
||||||
| 2026-03-30 | §3, §5.1 | **CONFIRMED — BW→S3 two-step read offset is at payload[5], NOT payload[3:4].** All BW read-command frames have `payload[3] = 0x00` and `payload[4] = 0x00` unconditionally. The two-step offset byte lives at `payload[5]`: `0x00` for the length-probe step, `DATA_LEN` for the data-fetch step. Validated against all captured frames in `bridges/captures/3-11-26/raw_bw_*.bin` — every frame is an exact bit-for-bit match when built with offset at `[5]`. The `page_hi`/`page_lo` framing in the docstring was a misattribution from the S3-side response layout (where `[3]`/`[4]` ARE page bytes). |
|
| 2026-03-30 | §3, §5.1 | **CONFIRMED — BW→S3 two-step read offset is at payload[5], NOT payload[3:4].** All BW read-command frames have `payload[3] = 0x00` and `payload[4] = 0x00` unconditionally. The two-step offset byte lives at `payload[5]`: `0x00` for the length-probe step, `DATA_LEN` for the data-fetch step. Validated against all captured frames in `bridges/captures/3-11-26/raw_bw_*.bin` — every frame is an exact bit-for-bit match when built with offset at `[5]`. The `page_hi`/`page_lo` framing in the docstring was a misattribution from the S3-side response layout (where `[3]`/`[4]` ARE page bytes). |
|
||||||
@@ -93,6 +93,11 @@
|
|||||||
| 2026-04-06 | §7.8.4 | **NEW — 5A chunk timing and count (empirical, BE11529 at 1024 sps).** Each chunk response arrives within ~1 second over TCP/cellular. A 9,306-sample event (≈9.1 s at 1024 sps) produces **35 chunks** before end-of-stream. Chunks 1–16 have varying data lengths (1036–1123 bytes); chunks 17–35 are uniformly 1036 bytes each (post-event silence, all-zero ADC samples). Safe recv timeout for chunk loop: **10 s** (10× typical response time). Default transport timeout (120 s) results in a ~2-minute stall per event at end-of-stream. |
|
| 2026-04-06 | §7.8.4 | **NEW — 5A chunk timing and count (empirical, BE11529 at 1024 sps).** Each chunk response arrives within ~1 second over TCP/cellular. A 9,306-sample event (≈9.1 s at 1024 sps) produces **35 chunks** before end-of-stream. Chunks 1–16 have varying data lengths (1036–1123 bytes); chunks 17–35 are uniformly 1036 bytes each (post-event silence, all-zero ADC samples). Safe recv timeout for chunk loop: **10 s** (10× typical response time). Default transport timeout (120 s) results in a ~2-minute stall per event at end-of-stream. |
|
||||||
| 2026-04-06 | §7.8.3 | **KNOWN ISSUE — `_decode_a5_waveform` hardcoded fi==9 skip.** The decoder contains `elif fi == 9: continue` which was written for the 9-frame original blast capture where frame 9 was a device terminator. For streams with >9 frames (current device produces 35+), frame index 9 is live waveform data — this skip discards ~1,070 bytes (~133 sample-sets) per event. The terminator is now detected via `page_key == 0x0000`, not by frame index. The fi==9 skip should be removed. |
|
| 2026-04-06 | §7.8.3 | **KNOWN ISSUE — `_decode_a5_waveform` hardcoded fi==9 skip.** The decoder contains `elif fi == 9: continue` which was written for the 9-frame original blast capture where frame 9 was a device terminator. For streams with >9 frames (current device produces 35+), frame index 9 is live waveform data — this skip discards ~1,070 bytes (~133 sample-sets) per event. The terminator is now detected via `page_key == 0x0000`, not by frame index. The fi==9 skip should be removed. |
|
||||||
| 2026-04-06 | §7.8 | **CONFIRMED — ADC count-to-physical-unit conversion.** Raw waveform samples are signed 16-bit integers (counts). Conversion: `value = counts × (range / 32767)`. For geo channels: range = 10.000 in/s (from the device's compliance config geo range field). For the mic channel: range is in psi (device-specific). Near-full-scale counts (≈32,700) on all four channels simultaneously indicate ADC saturation (clipping) from a high-amplitude event. |
|
| 2026-04-06 | §7.8 | **CONFIRMED — ADC count-to-physical-unit conversion.** Raw waveform samples are signed 16-bit integers (counts). Conversion: `value = counts × (range / 32767)`. For geo channels: range = 10.000 in/s (from the device's compliance config geo range field). For the mic channel: range is in psi (device-specific). Near-full-scale counts (≈32,700) on all four channels simultaneously indicate ADC saturation (clipping) from a high-amplitude event. |
|
||||||
|
| 2026-04-08 | §5.1, §7.10, §12 | **NEW — Monitoring commands confirmed.** SUB 0x1C (monitor status), 0x96 (start monitoring), 0x97 (stop monitoring) all confirmed from 4-8-26/2ndtry capture. SESSION_RESET (`41 03`) required before POLL to wake a monitoring unit. `section[6] == 0x10` is the monitoring flag (CORRECTED 2026-04-08 — was wrongly `section[1]`). Battery/memory at relative-from-end offsets: `section[-11:-9]` (battery×100), `section[-9:-5]` (memory_total), `section[-5:-1]` (memory_free) — stable across all payload size variants (52–55 bytes). |
|
||||||
|
| 2026-04-08 | §7.10 | **NEW — SUBs 0x0E (channel sensor data) and 0x98 (trigger test) observed** in 4-8-26/sensor-check capture (Blastware "Unit Channel Test" comms check). SUB 0x0E: 2-step read with channel selector in `params[6:8]`, data length 0x0A per channel, RSP SUB = 0xF1. SUB 0x98: single probe frame with `params[0] = 0xFF`, RSP SUB = 0x67; sent twice per test cycle. Not yet implemented in SFM. |
|
||||||
|
| 2026-04-08 | §7.10 | **NEW — SUBs 0x15 and 0x01 observed in sensor-check capture.** SUB 0x15 (serial number short form, data length 0x0A, RSP 0xEA) and SUB 0x01 (device info block, data length 0x98 = 152 bytes, RSP 0xFE) seen in Blastware's "Unit Channel Test" init sequence. Note: SUB 0x01 response SUB 0xFE collides with the existing SUB 0xFE → RSP 0x01 naming convention — they are inverse commands. |
|
||||||
|
| 2026-04-08 | §12 | **CONFIRMED — Unit partially reachable during on-device sensor check.** 4-8-26/sensor-check capture shows: POLL responds normally throughout; SUB 0x0E channel reads partially served (channels 0–4 responded), then ~40s silent gap while sensor check ran, then channels 5–7 responded. On-device sensor check duration ≈ 40 s. SFM `_pollMonitorConfirm()` polls status every 5 s for up to 60 s after start_monitoring. |
|
||||||
|
| 2026-04-08 | §7.9 (NEW) | **NEW — Compliance config field inventory captured from Blastware UI.** See §7.9 for full field list (Recording Setup, Notes, Special Setups tabs). Most fields NOT yet mapped to raw byte offsets. Confirmed decoded: sample_rate, record_time, trigger_level_geo, alarm_level_geo, max_range_geo, backlight_on_time, power_saving_timeout, monitoring_lcd_cycle, project/client/operator/sensor_location/notes. Sensor Check dropdown (Before monitoring / After each event / Disabled) NOT YET LOCATED in raw config bytes. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -249,6 +254,11 @@ Step 4 — Device sends actual data payload:
|
|||||||
| `09` | **UNKNOWN READ A** | Read command, response (`F6`) returns 0xCA (202) bytes. Purpose unknown. | 🔶 INFERRED |
|
| `09` | **UNKNOWN READ A** | Read command, response (`F6`) returns 0xCA (202) bytes. Purpose unknown. | 🔶 INFERRED |
|
||||||
| `1A` | **COMPLIANCE CONFIG READ** | Multi-step sequence (A+B+C+D frames). Response (E5) carries sample_rate (uint16 BE at anchor−2), record_time (float32 BE at anchor+10), trigger/alarm/max_range floats, and project strings. Anchor: `\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00`, search cfg[0:150]. Total ~2126 cfg bytes. | ✅ CONFIRMED 2026-04-02 |
|
| `1A` | **COMPLIANCE CONFIG READ** | Multi-step sequence (A+B+C+D frames). Response (E5) carries sample_rate (uint16 BE at anchor−2), record_time (float32 BE at anchor+10), trigger/alarm/max_range floats, and project strings. Anchor: `\x01\x2c\x00\x00\xbe\x80\x00\x00\x00\x00`, search cfg[0:150]. Total ~2126 cfg bytes. | ✅ CONFIRMED 2026-04-02 |
|
||||||
| `2E` | **UNKNOWN READ B** | Read command, response (`D1`) returns 0x1A (26) bytes. Purpose unknown. | 🔶 INFERRED |
|
| `2E` | **UNKNOWN READ B** | Read command, response (`D1`) returns 0x1A (26) bytes. Purpose unknown. | 🔶 INFERRED |
|
||||||
|
| `0E` | **CHANNEL SENSOR DATA** | Real-time sensor reading for one channel. Two-step read, data length 0x0A (10 bytes). Channel selector in params[6:8] (0x0000–0x0007 for 8 channels). Response (F1) carries amplitude, frequency, overswing data for that channel. Used by Blastware "Unit Channel Test" comms check. | ✅ CONFIRMED 2026-04-08 |
|
||||||
|
| `98` | **TRIGGER TEST** | Trigger-test command. Single probe frame; `params[0] = 0xFF`. Response (0x67) is all-zero data. Sent twice per Blastware comms-check cycle. Not a full POLL, no monitor state change. | ✅ CONFIRMED 2026-04-08 |
|
||||||
|
| `1C` | **MONITOR STATUS READ** | Two-step read, data offset 0x2C (44 bytes). `section[6] == 0x10` → monitoring; `0x00` → idle (CORRECTED 2026-04-08 — was wrongly documented as section[1]). Payload length varies (52–55 bytes) but battery/memory block is always the last 10 bytes before checksum: `section[-11:-9]` = battery×100 (uint16 BE), `section[-9:-5]` = memory_total (uint32 BE), `section[-5:-1]` = memory_free (uint32 BE). Confirmed from 2ndtry 4-8-26 full byte diff across 3 payload size variants. | ✅ CONFIRMED 2026-04-08 |
|
||||||
|
| `96` | **START MONITORING** | Single write frame, no data payload. Transitions unit from idle to monitoring mode (after optional on-device sensor check ~40 s). | ✅ CONFIRMED 2026-04-08 |
|
||||||
|
| `97` | **STOP MONITORING** | Single write frame, no data payload. Stops monitoring, unit returns to idle. | ✅ CONFIRMED 2026-04-08 |
|
||||||
|
|
||||||
All requests use CMD byte `0x02`. All responses use CMD byte `0x10 0x02` (which, after de-stuffing, is just the DLE+CMD combination — see §3).
|
All requests use CMD byte `0x02`. All responses use CMD byte `0x10 0x02` (which, after de-stuffing, is just the DLE+CMD combination — see §3).
|
||||||
|
|
||||||
@@ -263,7 +273,7 @@ All requests use CMD byte `0x02`. All responses use CMD byte `0x10 0x02` (which,
|
|||||||
| `01` | `FE` | ✅ CONFIRMED |
|
| `01` | `FE` | ✅ CONFIRMED |
|
||||||
| `08` | `F7` | ✅ CONFIRMED |
|
| `08` | `F7` | ✅ CONFIRMED |
|
||||||
| `06` | `F9` | ✅ CONFIRMED |
|
| `06` | `F9` | ✅ CONFIRMED |
|
||||||
| `1C` | `E3` | ✅ CONFIRMED |
|
| `1C` | `E3` | ✅ CONFIRMED 2026-04-08 |
|
||||||
| `1E` | `E1` | ✅ CONFIRMED |
|
| `1E` | `E1` | ✅ CONFIRMED |
|
||||||
| `0A` | `F5` | ✅ CONFIRMED |
|
| `0A` | `F5` | ✅ CONFIRMED |
|
||||||
| `0C` | `F3` | ✅ CONFIRMED |
|
| `0C` | `F3` | ✅ CONFIRMED |
|
||||||
@@ -272,6 +282,10 @@ All requests use CMD byte `0x02`. All responses use CMD byte `0x10 0x02` (which,
|
|||||||
| `09` | `F6` | ✅ CONFIRMED |
|
| `09` | `F6` | ✅ CONFIRMED |
|
||||||
| `1A` | `E5` | ✅ CONFIRMED |
|
| `1A` | `E5` | ✅ CONFIRMED |
|
||||||
| `2E` | `D1` | ✅ CONFIRMED |
|
| `2E` | `D1` | ✅ CONFIRMED |
|
||||||
|
| `0E` | `F1` | ✅ CONFIRMED 2026-04-08 |
|
||||||
|
| `98` | `67` | ✅ CONFIRMED 2026-04-08 |
|
||||||
|
| `96` | `69` | ✅ CONFIRMED 2026-04-08 |
|
||||||
|
| `97` | `68` | ✅ CONFIRMED 2026-04-08 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1226,6 +1240,151 @@ where `geo_range` is from the compliance config (typically 10.000 in/s). Mic cha
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 7.9 Compliance Config Field Inventory (Blastware UI, 2026-04-08) ✅
|
||||||
|
|
||||||
|
Fields visible in the Blastware "Compliance Setup" dialog. ✅ = byte offset confirmed in code. ❓ = not yet located in raw bytes.
|
||||||
|
|
||||||
|
### Recording Setup tab
|
||||||
|
|
||||||
|
| Field | Values / Type | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| Recording Mode | Continuous / Single Shot / Histogram | ❓ |
|
||||||
|
| Record Stop Mode | Fixed Record Time / Auto / Manual Stop | ❓ |
|
||||||
|
| Sample Rate | Standard 1024 / Fast 2048 / Faster 4096 sps | ✅ `sample_rate` (anchor−2) |
|
||||||
|
| Record Time | float, seconds (3, 5, 8, 10, 13…) | ✅ `record_time` (anchor+10) |
|
||||||
|
| Histogram Interval | 5 / 15 / 30 / 60 min (mode-gated behind Histogram mode) | ❓ |
|
||||||
|
| Storage Mode | Save All Data / Save Triggered | ❓ |
|
||||||
|
| Geophone Type | Standard Triaxial / 4.5 Hz Geophone | ❓ |
|
||||||
|
| Geophone — Enable all | bool | ❓ |
|
||||||
|
| Geophone — Trigger Source | bool | ❓ |
|
||||||
|
| Chan 1-3 Trigger Level | float, in/s | ✅ `trigger_level_geo` |
|
||||||
|
| Chan 1-3 Maximum Range | Normal 10.000 / 1.25 in/s | ✅ `max_range_geo` |
|
||||||
|
| Microphone — Enable all | bool | ❓ |
|
||||||
|
| Microphone — Trigger Source | bool | ❓ |
|
||||||
|
| Chan 4 Trigger Level | float, dB or psi | ❓ |
|
||||||
|
|
||||||
|
### Notes tab
|
||||||
|
|
||||||
|
| Field | Values / Type | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| Enable User Notes | bool | ❓ |
|
||||||
|
| Project | ASCII string | ✅ (sourced from A5 frame 7 via SUB 5A) |
|
||||||
|
| Client | ASCII string | ✅ (sourced from A5 frame 7) |
|
||||||
|
| User Name | ASCII string | ✅ (sourced from A5 frame 7) |
|
||||||
|
| Seis Loc | ASCII string | ✅ (sourced from A5 frame 7) |
|
||||||
|
| Enable Extended Notes | bool | ❓ |
|
||||||
|
| Extended Notes | ASCII text | ❓ |
|
||||||
|
| Extended Notes Title | ASCII string | ❓ |
|
||||||
|
| Enable Job Number | bool | ❓ |
|
||||||
|
| Job Number | int | ❓ |
|
||||||
|
| Enable Scaled Distance | bool | ❓ |
|
||||||
|
| Distance from Blast | float | ❓ |
|
||||||
|
| Charge Weight | float | ❓ |
|
||||||
|
|
||||||
|
### Special Setups tab
|
||||||
|
|
||||||
|
| Field | Values / Type | Status |
|
||||||
|
|---|---|---|
|
||||||
|
| Unit Timer Mode | Off / On | ❓ |
|
||||||
|
| Start Date/Time | date+time | ❓ |
|
||||||
|
| Stop Date/Time | date+time | ❓ |
|
||||||
|
| Self Check Mode | Off / On | ❓ |
|
||||||
|
| Self Check Time | HH:MM | ❓ |
|
||||||
|
| **Sensor Check** | **Before monitoring / After each event / Disabled** | ❓ not yet located |
|
||||||
|
| Measurement Units | Imperial / Metric | ❓ |
|
||||||
|
| Show Mic units in dB | bool | ❓ |
|
||||||
|
| Time Format | 24 Hour / 12 Hour (AM/PM) | ❓ |
|
||||||
|
| Backlight on Time | int, seconds (0–255) | ✅ event index block +75 |
|
||||||
|
| Power Saving Timeout | int, minutes | ✅ event index block +83 |
|
||||||
|
| Monitoring LCD Cycle | int | ✅ event index block +84:86 (uint16 BE) |
|
||||||
|
| Set unit time with setup | bool | ❓ |
|
||||||
|
|
||||||
|
**Note on Sensor Check:** The dropdown has three states — "Before monitoring", "After each event", "Disabled". The user's unit always runs "Before monitoring", so no enable/disable diff capture has been done yet. Finding this byte requires a capture with Disabled vs Before-monitoring with all other settings held constant.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7.10 SUB 0x0E — Channel Sensor Data & SUB 0x98 — Trigger Test ✅ 2026-04-08
|
||||||
|
|
||||||
|
Both confirmed from 4-8-26/sensor-check capture (Blastware "Unit Channel Test" comms check).
|
||||||
|
|
||||||
|
### SUB 0x0E — Channel Sensor Data
|
||||||
|
|
||||||
|
Standard two-step read. Data length: **0x0A (10 bytes)** per channel.
|
||||||
|
|
||||||
|
**Request params:** Channel selector in `params[6:8]` (bytes 6 and 7 of the 10-byte params field).
|
||||||
|
|
||||||
|
| `params[6:8]` | Channel |
|
||||||
|
|---|---|
|
||||||
|
| `00 00` | Channel 0 (Transverse) |
|
||||||
|
| `00 01` | Channel 1 (Vertical) |
|
||||||
|
| `10 02` | Channel 2 (Longitudinal) — note `0x10` in params[6] |
|
||||||
|
| `00 03` | Channel 3 |
|
||||||
|
| `10 04` | Channel 4 — note `0x10` in params[6] |
|
||||||
|
| `00 05` | Channel 5 |
|
||||||
|
| `00 06` | Channel 6 |
|
||||||
|
| `00 07` | Channel 7 |
|
||||||
|
|
||||||
|
Response SUB = 0xFF − 0x0E = **0xF1**.
|
||||||
|
|
||||||
|
**Response data (10 bytes at section = data[11:]):**
|
||||||
|
|
||||||
|
The data response payload for each channel is 10 bytes. From the sensor-check S3 capture,
|
||||||
|
channels 0 and 1 have non-zero data; channels 5–7 are all-zero (no sensors connected to those channels). Full byte-level field mapping NOT YET done — data includes overswing ratio, frequency, and amplitude per the Blastware "Unit Channel Test" dialog columns.
|
||||||
|
|
||||||
|
Example (channel 0, first pass):
|
||||||
|
```
|
||||||
|
data[11:] = 00 01 00 00 4A 00 25 10 02 0A (17 bytes including probe + data frames combined)
|
||||||
|
```
|
||||||
|
|
||||||
|
Probe frame response confirms data length in page bytes; data frame carries the 10-byte channel reading.
|
||||||
|
|
||||||
|
**Blastware "Unit Channel Test" sequence (confirmed 4-8-26/sensor-check):**
|
||||||
|
|
||||||
|
```
|
||||||
|
SESSION_RESET + POLL × 3 (startup — SESSION_RESET required for monitoring unit)
|
||||||
|
SUB 0x08 (event index read)
|
||||||
|
POLL × 8 (Blastware polls while waiting; unit may be in sensor check during this window)
|
||||||
|
SUB 0x15 (serial number, data length 0x0A)
|
||||||
|
SUB 0x01 (device info, data length 0x98)
|
||||||
|
SUB 0x08 (event index again)
|
||||||
|
SUB 0x01 (device info again)
|
||||||
|
SUB 0x0E × 8 channels (pass 1 — channels 0–7, probe+data each)
|
||||||
|
[~40 second gap if unit is performing on-device sensor check]
|
||||||
|
SUB 0x0E × 8 channels (pass 2 — same channels, fresh ADC readings)
|
||||||
|
SUB 0x98 × 2 (trigger test)
|
||||||
|
```
|
||||||
|
|
||||||
|
### SUB 0x98 — Trigger Test
|
||||||
|
|
||||||
|
Single probe frame only. No data step.
|
||||||
|
|
||||||
|
**Request format:** `params[0] = 0xFF` (all other param bytes = 0x00). Sent as a single frame (probe step only, no data step).
|
||||||
|
|
||||||
|
Response SUB = 0xFF − 0x98 = **0x67**. Response is a standard 17-byte zero-data S3 frame.
|
||||||
|
|
||||||
|
Wire bytes for request (confirmed frame 52 of 4-8-26 BW capture):
|
||||||
|
```
|
||||||
|
41 02 10 10 00 98 FF 00 00 00 00 00 00 00 00 00 00 00 00 A7 03
|
||||||
|
```
|
||||||
|
|
||||||
|
Blastware sends SUB 0x98 twice per comms-check cycle.
|
||||||
|
|
||||||
|
### SUB 0x01 — Device Info Block ❓
|
||||||
|
|
||||||
|
Observed in Blastware comms-check init sequence (before 0x0E channel reads). Standard two-step read with data length 0x98 (152 bytes).
|
||||||
|
|
||||||
|
**Response SUB = 0xFE** (= 0xFF − 0x01 = 0xFE).
|
||||||
|
|
||||||
|
⚠️ **Naming collision:** The existing SUB 0xFE (Full Config Read, RSP 0x01) and this SUB 0x01 (RSP 0xFE) are inverse commands. They are NOT the same command. SUB 0xFE is a full 166-byte config block used for firmware version, calibration date, etc. SUB 0x01 returns 152 bytes and appears to contain a subset of device identification data.
|
||||||
|
|
||||||
|
Observed payload (data[11:], first ~40 bytes of 161 total):
|
||||||
|
```
|
||||||
|
00 00 00 00 00 42 45 31 31 35 32 39 00 ... "BE11529\0"
|
||||||
|
```
|
||||||
|
Contains serial number, firmware bytes, and floating-point calibration fields. Full field map NOT YET done.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 8. Timestamp Format
|
## 8. Timestamp Format
|
||||||
|
|
||||||
Two timestamp wire formats are used:
|
Two timestamp wire formats are used:
|
||||||
@@ -1675,7 +1834,7 @@ The `.bin` files produced by `s3_bridge` are **not raw wire bytes**. The logger
|
|||||||
| **Auto Window** — "1 to 9 seconds" per manual (§3.13.1b). **Mode-gated:** only transmitted/active when Record Stop Mode = Auto. Capture attempted in Fixed mode (3→9 change) — no wire change observed in any frame. Deferred pending mode switch. | LOW | 2026-03-02 | Updated 2026-03-09 |
|
| **Auto Window** — "1 to 9 seconds" per manual (§3.13.1b). **Mode-gated:** only transmitted/active when Record Stop Mode = Auto. Capture attempted in Fixed mode (3→9 change) — no wire change observed in any frame. Deferred pending mode switch. | LOW | 2026-03-02 | Updated 2026-03-09 |
|
||||||
| **Auxiliary Trigger read location** — **RESOLVED:** SUB `FE` offset `0x0109`, uint8, `0x00`=disabled, `0x01`=enabled. Confirmed 2026-03-11 via controlled toggle capture. | RESOLVED | 2026-03-02 | Resolved 2026-03-11 |
|
| **Auxiliary Trigger read location** — **RESOLVED:** SUB `FE` offset `0x0109`, uint8, `0x00`=disabled, `0x01`=enabled. Confirmed 2026-03-11 via controlled toggle capture. | RESOLVED | 2026-03-02 | Resolved 2026-03-11 |
|
||||||
| **Auxiliary Trigger write path** — Write command not yet captured in a clean session. Inner frame handshake visible in A4 (multiple WRITE_CONFIRM_RESPONSE SUBs appear, TRIGGER_CONFIG_RESPONSE removed), but the BW→S3 write command itself was in a partial session. Likely SUB `15` or similar. Deferred for clean capture. | LOW | 2026-03-11 | NEW |
|
| **Auxiliary Trigger write path** — Write command not yet captured in a clean session. Inner frame handshake visible in A4 (multiple WRITE_CONFIRM_RESPONSE SUBs appear, TRIGGER_CONFIG_RESPONSE removed), but the BW→S3 write command itself was in a partial session. Likely SUB `15` or similar. Deferred for clean capture. | LOW | 2026-03-11 | NEW |
|
||||||
| **SUB `6E` response to SUB `1C`** — S3 responds to TRIGGER_CONFIG_READ (SUB `1C`) with SUB `6E`, NOT `0xE3` as the `0xFF - SUB` rule would predict. Only known exception to the response pairing rule observed to date. Payload starts with ASCII `"Long2"`. Purpose unknown. | LOW | 2026-03-11 | NEW |
|
| ~~**SUB `6E` response to SUB `1C`**~~ — ~~RESOLVED 2026-04-08: This was a misidentification.~~ The `1C → 6E` "exception" was misread — likely an inner A4 sub-frame. Confirmed from 4-8-26 capture (338 frames): SUB 0x1C always → 0xE3. No exceptions to the `0xFF − SUB` rule are known. | RESOLVED | 2026-04-08 | CLOSED |
|
||||||
| **Max Geo Range float 6.2061 in/s** — NOT a user-selectable range (manual only shows 1.25 and 10.0 in/s). Likely internal ADC full-scale constant or hardware range ceiling. Not worth capturing. | LOW | 2026-02-26 | Downgraded 2026-03-02 |
|
| **Max Geo Range float 6.2061 in/s** — NOT a user-selectable range (manual only shows 1.25 and 10.0 in/s). Likely internal ADC full-scale constant or hardware range ceiling. Not worth capturing. | LOW | 2026-02-26 | Downgraded 2026-03-02 |
|
||||||
| MicL channel units — **RESOLVED: psi**, confirmed from `.set` file unit string `"psi\0"` | RESOLVED | 2026-03-01 | |
|
| MicL channel units — **RESOLVED: psi**, confirmed from `.set` file unit string `"psi\0"` | RESOLVED | 2026-03-01 | |
|
||||||
| Backlight offset — **RESOLVED: +4B in event index data**, uint8, seconds | RESOLVED | 2026-03-02 | |
|
| Backlight offset — **RESOLVED: +4B in event index data**, uint8, seconds | RESOLVED | 2026-03-02 | |
|
||||||
|
|||||||
+35
-22
@@ -1755,14 +1755,22 @@ def _decode_monitor_status(data: bytes) -> MonitorStatus:
|
|||||||
data is the raw S3 frame .data attribute (includes the 11-byte section
|
data is the raw S3 frame .data attribute (includes the 11-byte section
|
||||||
header, so field offsets below are relative to data[11]).
|
header, so field offsets below are relative to data[11]).
|
||||||
|
|
||||||
Payload length indicates mode:
|
Monitoring flag (confirmed 4-8-26/2ndtry, full byte diff analysis):
|
||||||
44 bytes (0x2C): idle — full status block with battery + memory fields
|
section[6] == 0x00 → idle
|
||||||
12 bytes : actively monitoring — abbreviated, no battery/memory
|
section[6] == 0x10 → monitoring
|
||||||
|
|
||||||
Field offsets (idle mode, confirmed 4-8-26/2ndtry):
|
The payload size varies (52–55+ bytes) but the battery/memory block is
|
||||||
data[11 + 0x2F : 11 + 0x31] battery × 100 uint16 BE
|
always the last 10 bytes before the trailing checksum byte:
|
||||||
data[11 + 0x31 : 11 + 0x35] memory_total uint32 BE bytes
|
|
||||||
data[11 + 0x35 : 11 + 0x39] memory_free uint32 BE bytes
|
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)
|
||||||
|
|
||||||
|
Values confirmed from 4-8-26/2ndtry capture (BE11529):
|
||||||
|
battery 0x02A8 = 680 → 6.80 V
|
||||||
|
mem_total 0x000EFFF2 = 983026 bytes ≈ 960 KB
|
||||||
|
mem_free 0x000E9E52 = 958034 bytes ≈ 935 KB
|
||||||
"""
|
"""
|
||||||
# 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
|
||||||
@@ -1772,27 +1780,32 @@ def _decode_monitor_status(data: bytes) -> MonitorStatus:
|
|||||||
len(data), len(section), section.hex(),
|
len(data), len(section), section.hex(),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Monitoring flag confirmed from 2ndtry 4-8-26 capture:
|
# Monitoring flag: section[6] (CORRECTED 2026-04-08 — was wrongly section[1]).
|
||||||
# section[1] == 0x00 → idle
|
# Byte diff of 2ndtry BW-S3 captures confirms section[6] flips 0x00↔0x10
|
||||||
# section[1] == 0x10 → monitoring (flips exactly at start/stop transitions)
|
# exactly at the start/stop monitoring transitions (0xE3 frame #36 / #132).
|
||||||
# Payload length is ~46-49 bytes in BOTH states — length alone is unreliable.
|
is_monitoring = len(section) > 6 and section[6] == 0x10
|
||||||
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
|
||||||
|
|
||||||
# Battery and memory fields confirmed from 2ndtry IDLE S3 frames:
|
# Battery and memory offsets are RELATIVE TO THE END of the section.
|
||||||
# section[36:38] battery × 100 uint16 BE 0x02A8 = 680 → 6.80 V
|
# The payload length varies (52–55+ bytes) depending on monitoring state and
|
||||||
# section[38:42] memory_total uint32 BE bytes = total flash
|
# internal counters, but the battery/memory block is always the last 10 bytes
|
||||||
# section[42:46] memory_free uint32 BE bytes = free flash
|
# before the checksum (section[-1]).
|
||||||
# These fields appear in BOTH idle and monitoring payloads (battery/memory
|
#
|
||||||
# are available regardless of mode).
|
# section[-11:-9] battery × 100 uint16 BE 0x02A8 = 6.80 V
|
||||||
if len(section) >= 46:
|
# section[-9 :-5] memory_total uint32 BE ≈ 960 KB on BE11529
|
||||||
batt_raw = struct.unpack(">H", section[36:38])[0]
|
# section[-5 :-1] memory_free uint32 BE decreases as events fill
|
||||||
|
# section[-1] frame checksum (not data)
|
||||||
|
#
|
||||||
|
# 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]
|
||||||
battery_v = batt_raw / 100.0
|
battery_v = batt_raw / 100.0
|
||||||
memory_total = struct.unpack(">I", section[38:42])[0]
|
memory_total = struct.unpack(">I", section[-9:-5])[0]
|
||||||
memory_free = struct.unpack(">I", section[42:46])[0]
|
memory_free = struct.unpack(">I", section[-5:-1])[0]
|
||||||
|
|
||||||
return MonitorStatus(
|
return MonitorStatus(
|
||||||
is_monitoring=is_monitoring,
|
is_monitoring=is_monitoring,
|
||||||
|
|||||||
+46
-12
@@ -813,13 +813,14 @@ function populateDeviceBar() {
|
|||||||
|
|
||||||
// ── Monitoring ─────────────────────────────────────────────────────────────────
|
// ── Monitoring ─────────────────────────────────────────────────────────────────
|
||||||
async function refreshMonitorStatus() {
|
async function refreshMonitorStatus() {
|
||||||
if (!devHost()) return;
|
if (!devHost()) return null;
|
||||||
try {
|
try {
|
||||||
const r = await fetch(`${api()}/device/monitor/status?${deviceParams()}`);
|
const r = await fetch(`${api()}/device/monitor/status?${deviceParams()}`);
|
||||||
if (!r.ok) return;
|
if (!r.ok) return null;
|
||||||
const s = await r.json();
|
const s = await r.json();
|
||||||
updateMonitorPanel(s);
|
updateMonitorPanel(s);
|
||||||
} catch (_) {}
|
return s;
|
||||||
|
} catch (_) { return null; }
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMonitorPanel(s) {
|
function updateMonitorPanel(s) {
|
||||||
@@ -837,19 +838,18 @@ function updateMonitorPanel(s) {
|
|||||||
panel.className = 'monitoring';
|
panel.className = 'monitoring';
|
||||||
startB.disabled = true;
|
startB.disabled = true;
|
||||||
stopB.disabled = false;
|
stopB.disabled = false;
|
||||||
batEl.textContent = '—';
|
|
||||||
memTEl.textContent = '—';
|
|
||||||
memFEl.textContent = '—';
|
|
||||||
} else {
|
} else {
|
||||||
badge.textContent = 'IDLE';
|
badge.textContent = 'IDLE';
|
||||||
badge.className = 'mon-status-badge idle';
|
badge.className = 'mon-status-badge idle';
|
||||||
panel.className = 'idle';
|
panel.className = 'idle';
|
||||||
startB.disabled = false;
|
startB.disabled = false;
|
||||||
stopB.disabled = true;
|
stopB.disabled = true;
|
||||||
batEl.textContent = s.battery_v != null ? `${s.battery_v.toFixed(2)} V` : '—';
|
|
||||||
memTEl.textContent = s.memory_total_kb != null ? `${s.memory_total_kb} KB` : '—';
|
|
||||||
memFEl.textContent = s.memory_free_kb != null ? `${s.memory_free_kb} KB` : '—';
|
|
||||||
}
|
}
|
||||||
|
// Battery and memory are available in both states — update if present,
|
||||||
|
// keep previous value if this was an optimistic update with no real data.
|
||||||
|
if (s.battery_v != null) batEl.textContent = `${s.battery_v.toFixed(2)} V`;
|
||||||
|
if (s.memory_total_kb != null) memTEl.textContent = `${s.memory_total_kb} KB`;
|
||||||
|
if (s.memory_free_kb != null) memFEl.textContent = `${s.memory_free_kb} KB`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startMonitoring() {
|
async function startMonitoring() {
|
||||||
@@ -860,13 +860,47 @@ async function startMonitoring() {
|
|||||||
try {
|
try {
|
||||||
const r = await fetch(`${api()}/device/monitor/start?${deviceParams()}`, { method: 'POST' });
|
const r = await fetch(`${api()}/device/monitor/start?${deviceParams()}`, { method: 'POST' });
|
||||||
if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error(e.detail || r.statusText); }
|
if (!r.ok) { const e = await r.json().catch(() => ({})); throw new Error(e.detail || r.statusText); }
|
||||||
setStatus('Monitoring started.', 'ok');
|
|
||||||
await refreshMonitorStatus();
|
// Optimistically show MONITORING immediately. The unit may run a ~40s on-device
|
||||||
|
// sensor check before fully entering monitor mode. We poll status every 5s for
|
||||||
|
// up to 60s, updating the badge when is_monitoring flips to true.
|
||||||
|
updateMonitorPanel({ is_monitoring: true });
|
||||||
|
setStatus('Monitoring started — sensor check in progress (~40s)…', 'loading');
|
||||||
|
btn.textContent = '▶ Start';
|
||||||
|
|
||||||
|
_pollMonitorConfirm(0);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setStatus(`Start monitoring failed: ${e.message}`, 'error');
|
setStatus(`Start monitoring failed: ${e.message}`, 'error');
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
|
||||||
btn.textContent = '▶ Start';
|
btn.textContent = '▶ Start';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _pollMonitorConfirm(attempt) {
|
||||||
|
// Poll /device/monitor/status every 5s for up to 60s after startMonitoring().
|
||||||
|
// Updates the panel on each successful poll. Resolves once is_monitoring is true
|
||||||
|
// or after 12 attempts (60s), whichever comes first.
|
||||||
|
const MAX_ATTEMPTS = 12;
|
||||||
|
const INTERVAL_MS = 5000;
|
||||||
|
if (attempt >= MAX_ATTEMPTS) {
|
||||||
|
const s = await refreshMonitorStatus();
|
||||||
|
if (!s || !s.is_monitoring) {
|
||||||
|
setStatus('Warning: unit did not confirm monitoring state after 60s. Check device.', 'error');
|
||||||
|
} else {
|
||||||
|
setStatus('Monitoring active.', 'ok');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await new Promise(res => setTimeout(res, INTERVAL_MS));
|
||||||
|
const s = await refreshMonitorStatus();
|
||||||
|
if (s && s.is_monitoring) {
|
||||||
|
setStatus('Monitoring active.', 'ok');
|
||||||
|
} else {
|
||||||
|
const elapsed = (attempt + 1) * 5;
|
||||||
|
setStatus(`Sensor check in progress… (${elapsed}s elapsed)`, 'loading');
|
||||||
|
_pollMonitorConfirm(attempt + 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stopMonitoring() {
|
async function stopMonitoring() {
|
||||||
|
|||||||
Reference in New Issue
Block a user