diff --git a/minimateplus/client.py b/minimateplus/client.py index 3b934a8..7bc95f9 100644 --- a/minimateplus/client.py +++ b/minimateplus/client.py @@ -145,7 +145,7 @@ class MiniMateClient: log.info("connect: %s", device_info) return device_info - def get_events(self, include_waveforms: bool = True) -> list[Event]: + def get_events(self, include_waveforms: bool = True, debug: bool = False) -> list[Event]: """ Download all stored events from the device using the confirmed 1E → 0A → 0C → 1F event-iterator protocol. @@ -218,6 +218,8 @@ class MiniMateClient: # SUB 0C — full waveform record (peak values, project strings) try: record = proto.read_waveform_record(key4) + if debug: + ev._raw_record = record _decode_waveform_record_into(record, ev) except ProtocolError as exc: log.warning( @@ -262,14 +264,19 @@ def _decode_serial_number(data: bytes) -> DeviceInfo: Returns: New DeviceInfo with serial, firmware_minor, serial_trail_0 populated. """ - if len(data) < 9: + # data is data_rsp.data = payload[5:]. The 11-byte section header occupies + # data[0..10]: [LENGTH_ECHO:1][00×4][KEY_ECHO:4][00×2]. + # Actual serial payload starts at data[11]. + actual = data[11:] if len(data) > 11 else data + + if len(actual) < 9: # Short payload — gracefully degrade - serial = data.rstrip(b"\x00").decode("ascii", errors="replace") + serial = actual.rstrip(b"\x00").decode("ascii", errors="replace") return DeviceInfo(serial=serial, firmware_minor=0) - serial = data[:8].rstrip(b"\x00").decode("ascii", errors="replace") - trail_0 = data[8] if len(data) > 8 else None - fw_minor = data[9] if len(data) > 9 else 0 + serial = actual[:8].rstrip(b"\x00").decode("ascii", errors="replace") + trail_0 = actual[8] if len(actual) > 8 else None + fw_minor = actual[9] if len(actual) > 9 else 0 return DeviceInfo( serial=serial, diff --git a/minimateplus/models.py b/minimateplus/models.py index 30faec4..93d3a2c 100644 --- a/minimateplus/models.py +++ b/minimateplus/models.py @@ -197,6 +197,11 @@ class Event: # requested (large data transfer — up to several MB per event). raw_samples: Optional[dict] = None # {"Tran": [...], "Vert": [...], ...} + # ── Debug / introspection ───────────────────────────────────────────────── + # Raw 210-byte waveform record bytes, set when debug mode is active. + # Exposed by the SFM server via ?debug=true so field layouts can be verified. + _raw_record: Optional[bytes] = field(default=None, repr=False) + def __str__(self) -> str: ts = str(self.timestamp) if self.timestamp else "no timestamp" ppv = "" diff --git a/sfm/server.py b/sfm/server.py index 5921975..63d59fb 100644 --- a/sfm/server.py +++ b/sfm/server.py @@ -126,8 +126,8 @@ def _serialise_device_info(info: DeviceInfo) -> dict: } -def _serialise_event(ev: Event) -> dict: - return { +def _serialise_event(ev: Event, debug: bool = False) -> dict: + d: dict = { "index": ev.index, "timestamp": _serialise_timestamp(ev.timestamp), "sample_rate": ev.sample_rate, @@ -135,6 +135,11 @@ def _serialise_event(ev: Event) -> dict: "peak_values": _serialise_peak_values(ev.peak_values), "project_info": _serialise_project_info(ev.project_info), } + if debug: + raw = getattr(ev, "_raw_record", None) + d["raw_record_hex"] = raw.hex() if raw else None + d["raw_record_len"] = len(raw) if raw else 0 + return d # ── Transport factory ───────────────────────────────────────────────────────── @@ -257,6 +262,7 @@ def device_events( baud: int = Query(38400, description="Serial baud rate"), host: Optional[str] = Query(None, description="TCP host — modem IP or ACH relay"), tcp_port: int = Query(DEFAULT_TCP_PORT, description=f"TCP port (default {DEFAULT_TCP_PORT})"), + debug: bool = Query(False, description="Include raw record hex for field-layout inspection"), ) -> dict: """ Connect to the device, read the event index, and download all stored @@ -264,15 +270,18 @@ def device_events( Supply either *port* (serial) or *host* (TCP/modem). + Pass debug=true to include raw_record_hex in each event — useful for + verifying field offsets against the protocol reference. + This does NOT download raw ADC waveform samples — those are large and fetched separately via GET /device/event/{idx}/waveform (future endpoint). """ - log.info("GET /device/events port=%s host=%s", port, host) + log.info("GET /device/events port=%s host=%s debug=%s", port, host, debug) try: def _do(): with _build_client(port, baud, host, tcp_port) as client: - return client.connect(), client.get_events() + return client.connect(), client.get_events(debug=debug) info, events = _run_with_retry(_do, is_tcp=_is_tcp(host)) except HTTPException: raise @@ -286,7 +295,7 @@ def device_events( return { "device": _serialise_device_info(info), "event_count": len(events), - "events": [_serialise_event(ev) for ev in events], + "events": [_serialise_event(ev, debug=debug) for ev in events], }