Adds full support for reading device compliance configuration (2090-byte E5
response) containing record time, trigger/alarm levels, and project strings.
protocol.py:
- Implement read_compliance_config() two-step read (SUB 1A → E5)
- Fixed length 0x082A (2090 bytes)
models.py:
- Add ComplianceConfig dataclass with fields: record_time, sample_rate,
trigger_level_geo, alarm_level_geo, max_range_geo, project strings
- Add compliance_config field to DeviceInfo
client.py:
- Implement _decode_compliance_config_into() to extract:
* Record time float at offset +0x28 ✅
* Trigger/alarm levels per-channel (heuristic parsing) 🔶
* Project/setup strings from E5 payload
* Placeholder for sample_rate (location TBD ❓)
- Update connect() to read SUB 1A after SUB 01, cache in device_info
- Add ComplianceConfig to imports
sfm/server.py:
- Add _serialise_compliance_config() JSON encoder
- Include compliance_config in /device/info response
- Updated _serialise_device_info() to output compliance config
Both record_time (at fixed offset 0x28) and project strings are ✅ CONFIRMED
from protocol reference §7.6. Trigger/alarm extraction uses heuristics
pending more detailed field mapping from captured data.
Sample rate remains undiscovered in the E5 payload — likely in the
mystery flags at offset +0x12 or requires a "fast mode" capture.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Confirmed 2026-04-01 against Blastware event report for BE11529 thump
event ("00:28:12 April 1, 2026", PVS 3.906 in/s).
models.py:
- Timestamp.from_waveform_record(): decode 9-byte format from 0C record
bytes[0-8]: [day][sub_code][month][year:2BE][?][hour][min][sec]
- Timestamp: add hour/minute/second optional fields; __str__ includes
time when available
- PeakValues: add peak_vector_sum field (confirmed fixed offset 87)
client.py:
- _decode_waveform_record_into: add timestamp decode from bytes[0:9]
- _extract_record_type: decode byte[1] (sub_code), not ASCII string
search; 0x10 → "Waveform", histogram TBD
- _extract_peak_floats: add PVS from offset 87 (IEEE 754 BE float32)
= √(T²+V²+L²) at max instantaneous vector moment
sfm/server.py:
- _serialise_timestamp: add hour/minute/second/day fields to JSON
- _serialise_peak_values: add peak_vector_sum to JSON
docs: update §7.7.5 and §8 with confirmed 9-byte timestamp layout,
PVS field, and byte[1] record type encoding; update command table;
close resolved open questions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- _decode_serial_number: read from data[11:] not data[:8] — was returning
the LENGTH_ECHO byte (0x0A = '\n') instead of the serial string
- _extract_peak_floats: search for channel label strings ("Tran" etc) and
read float at label+6; old step-4 aligned scan was reading trigger levels
instead of PPV values
- get_events: add debug=False param; stashes raw 210-byte record on
Event._raw_record when True for field-layout inspection
- server /device/events: add ?debug=true query param; includes
raw_record_hex + raw_record_len in response when set
- models: add Event._raw_record optional bytes field