fix: serial decode offset, PPV label scan, debug mode for waveform records
- _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
This commit is contained in:
@@ -145,7 +145,7 @@ class MiniMateClient:
|
|||||||
log.info("connect: %s", device_info)
|
log.info("connect: %s", device_info)
|
||||||
return 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
|
Download all stored events from the device using the confirmed
|
||||||
1E → 0A → 0C → 1F event-iterator protocol.
|
1E → 0A → 0C → 1F event-iterator protocol.
|
||||||
@@ -218,6 +218,8 @@ class MiniMateClient:
|
|||||||
# SUB 0C — full waveform record (peak values, project strings)
|
# SUB 0C — full waveform record (peak values, project strings)
|
||||||
try:
|
try:
|
||||||
record = proto.read_waveform_record(key4)
|
record = proto.read_waveform_record(key4)
|
||||||
|
if debug:
|
||||||
|
ev._raw_record = record
|
||||||
_decode_waveform_record_into(record, ev)
|
_decode_waveform_record_into(record, ev)
|
||||||
except ProtocolError as exc:
|
except ProtocolError as exc:
|
||||||
log.warning(
|
log.warning(
|
||||||
@@ -262,14 +264,19 @@ def _decode_serial_number(data: bytes) -> DeviceInfo:
|
|||||||
Returns:
|
Returns:
|
||||||
New DeviceInfo with serial, firmware_minor, serial_trail_0 populated.
|
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
|
# 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)
|
return DeviceInfo(serial=serial, firmware_minor=0)
|
||||||
|
|
||||||
serial = data[:8].rstrip(b"\x00").decode("ascii", errors="replace")
|
serial = actual[:8].rstrip(b"\x00").decode("ascii", errors="replace")
|
||||||
trail_0 = data[8] if len(data) > 8 else None
|
trail_0 = actual[8] if len(actual) > 8 else None
|
||||||
fw_minor = data[9] if len(data) > 9 else 0
|
fw_minor = actual[9] if len(actual) > 9 else 0
|
||||||
|
|
||||||
return DeviceInfo(
|
return DeviceInfo(
|
||||||
serial=serial,
|
serial=serial,
|
||||||
|
|||||||
@@ -197,6 +197,11 @@ class Event:
|
|||||||
# requested (large data transfer — up to several MB per event).
|
# requested (large data transfer — up to several MB per event).
|
||||||
raw_samples: Optional[dict] = None # {"Tran": [...], "Vert": [...], ...}
|
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:
|
def __str__(self) -> str:
|
||||||
ts = str(self.timestamp) if self.timestamp else "no timestamp"
|
ts = str(self.timestamp) if self.timestamp else "no timestamp"
|
||||||
ppv = ""
|
ppv = ""
|
||||||
|
|||||||
@@ -126,8 +126,8 @@ def _serialise_device_info(info: DeviceInfo) -> dict:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _serialise_event(ev: Event) -> dict:
|
def _serialise_event(ev: Event, debug: bool = False) -> dict:
|
||||||
return {
|
d: dict = {
|
||||||
"index": ev.index,
|
"index": ev.index,
|
||||||
"timestamp": _serialise_timestamp(ev.timestamp),
|
"timestamp": _serialise_timestamp(ev.timestamp),
|
||||||
"sample_rate": ev.sample_rate,
|
"sample_rate": ev.sample_rate,
|
||||||
@@ -135,6 +135,11 @@ def _serialise_event(ev: Event) -> dict:
|
|||||||
"peak_values": _serialise_peak_values(ev.peak_values),
|
"peak_values": _serialise_peak_values(ev.peak_values),
|
||||||
"project_info": _serialise_project_info(ev.project_info),
|
"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 ─────────────────────────────────────────────────────────
|
# ── Transport factory ─────────────────────────────────────────────────────────
|
||||||
@@ -257,6 +262,7 @@ def device_events(
|
|||||||
baud: int = Query(38400, description="Serial baud rate"),
|
baud: int = Query(38400, description="Serial baud rate"),
|
||||||
host: Optional[str] = Query(None, description="TCP host — modem IP or ACH relay"),
|
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})"),
|
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:
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Connect to the device, read the event index, and download all stored
|
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).
|
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
|
This does NOT download raw ADC waveform samples — those are large and
|
||||||
fetched separately via GET /device/event/{idx}/waveform (future endpoint).
|
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:
|
try:
|
||||||
def _do():
|
def _do():
|
||||||
with _build_client(port, baud, host, tcp_port) as client:
|
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))
|
info, events = _run_with_retry(_do, is_tcp=_is_tcp(host))
|
||||||
except HTTPException:
|
except HTTPException:
|
||||||
raise
|
raise
|
||||||
@@ -286,7 +295,7 @@ def device_events(
|
|||||||
return {
|
return {
|
||||||
"device": _serialise_device_info(info),
|
"device": _serialise_device_info(info),
|
||||||
"event_count": len(events),
|
"event_count": len(events),
|
||||||
"events": [_serialise_event(ev) for ev in events],
|
"events": [_serialise_event(ev, debug=debug) for ev in events],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user