diff --git a/minimateplus/client.py b/minimateplus/client.py index 554a44a..bb72858 100644 --- a/minimateplus/client.py +++ b/minimateplus/client.py @@ -163,7 +163,7 @@ class MiniMateClient: log.info("connect: %s", device_info) return device_info - def get_events(self, include_waveforms: bool = True, debug: bool = False) -> list[Event]: + def get_events(self, full_waveform: bool = False, debug: bool = False) -> list[Event]: """ Download all stored events from the device using the confirmed 1E → 0A → 0C → 5A → 1F event-iterator protocol. @@ -247,21 +247,39 @@ class MiniMateClient: "get_events: 0C failed for key=%s: %s", key4.hex(), exc ) - # SUB 5A — bulk waveform stream: event-time metadata - # Stops early after "Project:" is found (typically in A5[7] of 9) - # so we fetch only ~8 frames rather than the full multi-MB stream. - # This is the authoritative source for client/operator/seis_loc/notes. + # SUB 5A — bulk waveform stream. + # By default (full_waveform=False): stop early after frame 7 ("Project:") + # is found — fetches only ~8 frames for event-time metadata. + # When full_waveform=True: fetch the complete stream (stop_after_metadata=False, + # max_chunks=128) and decode raw ADC samples into ev.raw_samples. + # The full waveform MUST be fetched here, inside the 1E→0A→0C→5A→1F loop. + # Issuing 5A after 1F has advanced the event context will time out. try: - a5_frames = proto.read_bulk_waveform_stream( - key4, stop_after_metadata=True - ) - if a5_frames: - _decode_a5_metadata_into(a5_frames, ev) - log.debug( - "get_events: 5A metadata client=%r operator=%r", - ev.project_info.client if ev.project_info else None, - ev.project_info.operator if ev.project_info else None, + if full_waveform: + log.info( + "get_events: 5A full waveform download for key=%s", key4.hex() ) + a5_frames = proto.read_bulk_waveform_stream( + key4, stop_after_metadata=False, max_chunks=128 + ) + if a5_frames: + _decode_a5_metadata_into(a5_frames, ev) + _decode_a5_waveform(a5_frames, ev) + log.info( + "get_events: 5A decoded %d sample-sets", + len((ev.raw_samples or {}).get("Tran", [])), + ) + else: + a5_frames = proto.read_bulk_waveform_stream( + key4, stop_after_metadata=True + ) + if a5_frames: + _decode_a5_metadata_into(a5_frames, ev) + log.debug( + "get_events: 5A metadata client=%r operator=%r", + ev.project_info.client if ev.project_info else None, + ev.project_info.operator if ev.project_info else None, + ) except ProtocolError as exc: log.warning( "get_events: 5A failed for key=%s: %s — event-time metadata unavailable", diff --git a/sfm/server.py b/sfm/server.py index 6263d16..c2f7240 100644 --- a/sfm/server.py +++ b/sfm/server.py @@ -427,14 +427,12 @@ def device_event_waveform( def _do(): with _build_client(port, baud, host, tcp_port) as client: info = client.connect() - events = client.get_events() + # full_waveform=True fetches the complete 5A stream inside the + # 1E→0A→0C→5A→1F loop. Issuing a second 5A after 1F times out. + events = client.get_events(full_waveform=True) matching = [ev for ev in events if ev.index == index] - if not matching: - return None, None, info - ev = matching[0] - client.download_waveform(ev) - return ev, events, info - ev, events, info = _run_with_retry(_do, is_tcp=_is_tcp(host)) + return matching[0] if matching else None, info + ev, info = _run_with_retry(_do, is_tcp=_is_tcp(host)) except HTTPException: raise except ProtocolError as exc: diff --git a/sfm/waveform_viewer.html b/sfm/waveform_viewer.html index fc9a181..5ec560c 100644 --- a/sfm/waveform_viewer.html +++ b/sfm/waveform_viewer.html @@ -132,6 +132,49 @@ .ch-vert { color: #3fb950; } .ch-long { color: #d29922; } .ch-mic { color: #bc8cff; } + + #unit-bar { + background: #0d1117; + border-bottom: 1px solid #21262d; + padding: 8px 20px; + display: flex; + align-items: center; + gap: 16px; + flex-wrap: wrap; + font-size: 12px; + } + + .unit-field { display: flex; flex-direction: column; gap: 1px; } + .unit-field .uf-label { color: #484f58; font-size: 10px; text-transform: uppercase; letter-spacing: 0.05em; } + .unit-field .uf-value { color: #c9d1d9; font-family: monospace; font-size: 13px; } + .unit-field .uf-value.highlight { color: #58a6ff; font-weight: 600; } + + .event-chips { + display: flex; + gap: 5px; + flex-wrap: wrap; + margin-left: 8px; + } + + .event-chip { + background: #21262d; + border: 1px solid #30363d; + border-radius: 5px; + color: #8b949e; + cursor: pointer; + font-size: 12px; + padding: 3px 10px; + transition: all 0.12s; + } + .event-chip:hover { background: #1f6feb; border-color: #1f6feb; color: #fff; } + .event-chip.active { background: #1f6feb; border-color: #388bfd; color: #fff; font-weight: 600; } + + #connect-btn { + background: #238636; + margin-left: auto; + } + #connect-btn:hover { background: #2ea043; } + #connect-btn:disabled { background: #21262d; color: #484f58; }
@@ -147,15 +190,35 @@ - - - + + -