Listens for inbound unit connections, connects upstream to a real Blastware
ACH server, and forwards bytes bidirectionally while saving both directions to
raw_bw_<ts>.bin and raw_s3_<ts>.bin in the existing capture format.
Used to capture the 4-11-26 Blastware ACH session that confirmed the erase-all
protocol (SUBs 0xA3/0x1C/0x06/0xA2) and the event deletion wire sequence.
Usage:
python bridges/ach_mitm.py --bw-host 127.0.0.1 --bw-port 9999 --listen-port 9998
Point the unit's call-home destination at this machine:9998.
Point this proxy's --bw-host/port at the upstream Blastware ACH server.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
State format (ach_state.json):
- Replace event_count with downloaded_keys (set of hex strings) + max_downloaded_key
- Key-based tracking correctly handles delete-then-re-record: after device erase the
count drops to 0, but new events have new (or recycled) keys
Browse pre-check:
- list_event_keys() walk before get_events() to bail early when nothing is new
- get_events() called with skip_waveform_for_keys= for already-seen keys, so repeat
call-homes only download waveforms for genuinely new events
--clear-after-download flag:
- After saving new events, calls client.delete_all_events() (0xA3→0x1C→0x06→0xA2)
- On success: resets downloaded_keys=[] and max_downloaded_key="00000000" so the
next session starts fresh (device counter resets to 0x01110000 after erase)
Post-erase key-reuse detection:
- Device counter resets to 0x01110000 after any erase; new events reuse old keys
- If max(device_keys) < max_downloaded_key, the device was wiped externally
(Blastware, manual) — seen_keys is discarded and all device keys treated as new
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Event model uses peak_values (not peaks) and project_info (not direct fields).
PeakValues fields are tran/vert/long/micl/peak_vector_sum (not transverse etc).
ProjectInfo fields accessed via ev.project_info.project etc.
Also fix ev.timestamp serialization: use str() instead of .isoformat() since
Timestamp is a custom dataclass, not datetime.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
calibration_date, aux_trigger, setup_name etc. don't exist directly on
DeviceInfo — they live in DeviceInfo.compliance_config (ComplianceConfig).
_device_info_to_dict now accesses them via cc = d.compliance_config.
Log line updated to show serial/firmware/model/event_count instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
socket.accept() on Windows blocks indefinitely and ignores KeyboardInterrupt.
Setting a 1-second timeout on the server socket causes the accept loop to wake
up every second and re-check, so Ctrl-C is handled within ~1 second.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Session directory and log file are now created ONLY after startup() succeeds.
Internet scanners and dropped connections no longer litter the output folder.
Raw bytes are buffered in memory until startup succeeds, then flushed to disk.
- Add --allow-ip IP flag (repeatable) to allowlist specific source IPs.
Connections from un-listed IPs are rejected immediately (socket closed, no log).
If no --allow-ip flags are given, all IPs are still accepted (original behavior).
Usage: --allow-ip 63.43.212.232 --allow-ip 152.1.2.3
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
_decode_event_count: read uint16 BE at offset 10 (confirmed 2026-04-10 from
live BE11529 event index — data[10:12]=0x0006=6, matches device LCD).
Previous uint32 at offset 3 always returned 1 regardless of event count.
ach_server.py: use device_info.event_count (already fetched during connect())
instead of calling count_events() separately. This saves 2*N round-trips and
avoids the 1F linked-list walk which was overcounting on some devices.
count_events() kept as fallback when connect() is skipped (--events-only).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace all Unicode arrows/checkmarks (-> [OK] [FAIL]) in ach_server.py
and client.py log calls — Windows cp1252 console can't encode them
- Fix DeviceInfo attribute: serial_number -> serial
- Fix _device_info_to_dict key: serial_number -> serial
- Demote count_events 1E/1F per-key log lines from WARNING to DEBUG
(they were flooding the console on devices with many stored events)
- FileHandler now opens with encoding='utf-8' so session log files
can hold any characters without codec errors
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ach_server.py:
- Add ach_state.json per-unit state tracking (keyed by serial number)
- count_events() before any download; skip session if no new events since last call-home
- Download only events beyond the previous high-water mark (all_events[last_count:])
- --max-events N safety cap for first-run units with many stored events
- state_path and max_events wired through AchSession constructor and serve()
client.py (_decode_monitor_status):
- Revert monitoring flag to section[1] == 0x10 (was incorrectly changed to section[6])
- Fix battery/memory offsets to section[-10:-8], [-8:-4], [-4:] (no trailing checksum byte)
- Both confirmed by full byte diff of all 144 0xE3 data frames in 4-8-26/2ndtry capture
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
minimateplus/transport.py:
- Add SocketTransport(TcpTransport) — wraps an already-accepted inbound
socket; connect() is a no-op; everything else inherited from TcpTransport.
Enables the ACH server to reuse all existing protocol/client code without
any changes.
bridges/ach_server.py:
- Minimal inbound ACH server — listens on port 12345, accepts call-home
connections from MiniMate Plus units, runs the full BW protocol:
startup handshake → get_device_info → get_events(full_waveform=True)
- Saves device_info.json + events.json + raw_rx_<ts>.bin + session log
per connection to bridges/captures/ach_inbound_<ts>/
- raw_rx.bin is byte-compatible with existing Analyzer tooling
- Taps transport.read() to capture raw S3 bytes alongside parsed output
- Each connection runs in its own daemon thread
- Clearly distinguishes push vs pull protocol in the startup log
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a production-safe headphone-splitter mode:
- Device bytes tee'd to both --upstream (primary/prod) and --mirror (new server)
- Only primary server responses are returned to the device
- Mirror connect/write failures are non-fatal and logged; prod is unaffected
- New raw_mirror_<ts>.bin capture file alongside raw_client/raw_server
Three modes: standalone (capture only), bridge (one upstream), splitter (two).
Default listen port changed to 12345 to match project ACH setup.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- bridges/ach_bridge.py: transparent TCP bridge that MITMs the MiniMate Plus
call-home connection — forwards to real ACH server while logging all frames
to raw_client/raw_server .bin files compatible with parse_capture.py;
standalone capture mode for lab use without a real server
- bridges/serial_watch.py: RS-232 serial monitor with live S3 frame parsing;
taps the line between MiniMate and modem (RV50/RV55); captures raw bytes,
.log and .jsonl; --ack-ok mode auto-replies to AT commands; fixed fatal
indentation bug in the original that silently prevented any data capture
- seismo_lab.py: new "Serial Watch" fourth tab (SerialWatchPanel) wrapping
serial_watch.py functionality; COM port picker with refresh, baud config,
ack-ok toggle, colour-coded live frame log (teal frames / yellow ctrl /
blue AT), raw .bin capture auto-fed into Analyzer tab on stop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.
- Introduced new SUBs for monitoring status, start, and stop commands in protocol.py.
- Implemented read_monitor_status, start_monitoring, and stop_monitoring methods in MiniMateProtocol class.
- Added new API endpoints for monitoring status retrieval and control in server.py.
- Enhanced the web application with a monitoring panel, including battery and memory status display.
- Created a new Python script to parse SUB 0x1C response frames for monitoring status.
- Documented the monitoring status response format and field locations in markdown and text files.
Frame index 9 was assumed to be the device terminator based on the
9-frame original blast capture. For streams with >9 frames (current
device produces 35), fi==9 is live waveform data — the skip was
dropping ~133 sample-sets per event.
Terminator detection is handled upstream via page_key==0x0000 in
read_bulk_waveform_stream, so no index-based skip is needed here.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds §7.8.4 to protocol reference and corresponding CLAUDE.md sections:
- End-of-stream: device sends exactly 1 raw byte after last chunk; handled
via TimeoutError + bytes_fed>0 check → graceful break to termination
- Chunk timing: ~1s per chunk, 35 chunks for a 9,306-sample event, safe
timeout is 10s (not default 120s)
- fi==9 decoder bug: hardcoded skip drops ~133 sample-sets per event;
noted as known issue pending fix
- ADC conversion: counts × (range/32767) → physical units (in/s for geo)
Changelog entries added for all four items (2026-04-06).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two improvements to eliminate the ~2-min-per-event wait and unnecessary
full-event-list download when only one event is requested:
1. protocol.py: pass timeout=10.0 to _recv_one in the 5A chunk loop.
Device responds within ~1s per chunk; 10s gives a safe 10x buffer.
End-of-stream detection (raw_bytes=1) now fires in 10s instead of 120s,
cutting ~110s of dead wait per event.
2. client.py: add stop_after_index parameter to get_events(). When set,
iteration stops immediately after the target event is collected — no
further 0A/1E/0C/5A/1F cycles for events the caller doesn't need.
3. server.py: pass stop_after_index=index to both /device/event/{idx}
and /device/event/{idx}/waveform endpoints so a single-event request
only downloads that one event.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When the device finishes streaming waveform data, it sends a single
partial byte (raw_bytes=1) rather than a complete A5 frame, then goes
silent for the full 120s timeout.
Observed: 35 chunks succeed, chunk 36 times out with raw_bytes=1 —
identical for both events in the test run.
Fix: on TimeoutError, if bytes_fed > 0 and we already have collected
frames, treat it as natural end-of-stream and break to the termination
step rather than propagating the exception. True transport failures
(raw_bytes=0, no prior frames) still raise.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wraps each recv_one call in a try/except TimeoutError, logging:
- On timeout: chunk_num, counter, raw bytes_fed (distinguishes "device
silent" from "device sent unparseable bytes")
- On success: chunk_num, page_key, data_len, contains_Project flag
Parser is explicitly reset before each chunk recv so bytes_fed is
accurate per-chunk rather than cumulative. Helps identify exactly which
chunk fails and whether the device is responding at all.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Added `_decode_a5_waveform()` to parse SUB 5A frames into per-channel time-series data.
- Introduced `download_waveform(event)` method in `MiniMateClient` to fetch full waveform data.
- Updated `Event` model to include new fields: `total_samples`, `pretrig_samples`, `rectime_seconds`, and `_waveform_key`.
- Enhanced documentation in `CHANGELOG.md` and `instantel_protocol_reference.md` to reflect new features and confirmed protocol details.
Add read_bulk_waveform_stream() to MiniMateProtocol and wire it into
get_events() so each event gets authoritative client/operator/sensor_location
from the A5 frames recorded at event-time, not the current compliance config.
- framing.py: bulk_waveform_params() and bulk_waveform_term_params() helpers
(probe/chunk params and termination params for SUB 5A, confirmed from
1-2-26 BW TX capture)
- protocol.py: read_bulk_waveform_stream(key4, stop_after_metadata=True) —
probe + chunk loop (counter += 0x0400), early stop when b"Project:" found
(A5[7] of 9), then sends termination at offset=0x005A
- client.py: _decode_a5_metadata_into() needle-searches concatenated A5 frame
data for Project/Client/User Name/Seis Loc/Extended Notes; get_events() now
calls SUB 5A after each SUB 0C, overwriting project_info with event-time values
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The 210-byte waveform record only stores "Project:" — client, operator,
sensor_location, and notes are device-level settings in SUB 1A, not
per-event fields. Backfill those into each event's project_info after
download, same pattern as the sample_rate backfill.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The channel block is in frame C data (cfg[44:1071]) not deep in a
hypothetical frame D section. The offset-1000 assumption was wrong —
searching from 44 lets us find it while unit string validation still
prevents false positives.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Temporary: log tran_pos, surrounding bytes, and exact unit string check
results at WARNING level so we can see why trigger/alarm/max_range are
still null even with the full 2126-byte cfg.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Old key was (page_key, chunk_len) which would incorrectly drop a second
config section that has the same length as the first (e.g. current-config
vs event-time-config when settings haven't changed).
New key is the full chunk bytes — only truly byte-identical chunks are
dropped. Different data that happens to share page_key and length now
comes through correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DeviceInfo.event_count: Optional[int] = None (new field in models.py)
- connect() now calls proto.read_event_index() after compliance config and
stores the decoded count in device_info.event_count
- _serialise_device_info() exposes event_count in /device/info and /device/events
JSON responses
event_count is decoded from uint32 BE at offset +3 of the 88-byte F7 payload
(🔶 inferred — needs live device confirmation against a multi-event device).
Any ProtocolError from the index read is caught and logged; event_count stays
None rather than failing the whole connect().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sample_rate is a device-level setting stored in the compliance config,
not per-event in the waveform record. After downloading events, backfill
ev.sample_rate from info.compliance_config.sample_rate for any event
that didn't get it from the waveform record decode path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The channel block is only present in the full ~2126-byte cfg (when frame D
delivers correctly rather than duplicating frame B's page). Layout per §7.6:
[00 00][max_range f32][00 00][trigger f32]["in.\0"][alarm f32]["/s\0\0"][00 01][label]
Relative offsets from the "Tran" label position (label-24/label-18/label-10)
are validated by checking the unit strings "in.\0" at label-14 and "/s\0\0"
at label-6 before reading the floats. Guard against "Tran2" false-match.
When frame D duplicates, cfg is ~1071 bytes and tran_pos search returns a hit
without the unit string sentinels — we log the miss and leave fields None.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two-step probe+fetch for SUB 08 (EVENT_INDEX), returning the raw 88-byte
(0x58) index block. SUB_EVENT_INDEX and DATA_LENGTHS[0x08]=0x58 were
already registered — this just wires the method that calls them.
Docstring notes the partially-decoded layout (event count at +3 as uint32 BE,
timestamps at +7) pending live device confirmation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New/corrected sections:
§7.6.1 Record Time — corrected: anchor+10 supersedes the +0x28 absolute
offset. Added 3 s, 5 s, 8 s confirmations alongside existing 7/10/13.
Warning added: do NOT use fixed offset for BE11529.
§7.6.2 (NEW) SUB 1A Multi-Frame Read Protocol — 4-frame A/B/C/D
sequence documented (reverse-engineered from raw_bw capture). E5
page_key field explained. BE11529 duplicate-page behaviour and
(page_key, chunk_len) dedup strategy documented.
§7.6.3 (NEW) Sample Rate and DLE Jitter — Normal/Fast/Faster = 1024/
2048/4096 Sa/s confirmed. Root cause of ±1 byte cfg jitter explained:
4096 = 0x1000 → `10 10 00` in raw frame → `10 00` after DLE unstuffing
= 1 byte shorter than 04 00/08 00. Anchor search requirement explained.
Changelog — 5 new entries covering the 4-frame sequence, duplicate-page
detection, record_time anchor correction, sample_rate confirmation, and
_pending_frames / reset_parser=False implementation notes.
Quick Reference — Record Time and Sample Rate rows updated with correct
locations, types, and confirmed values.
Open Questions — SUB 1A item updated to "substantially resolved".
Record time item updated. Sample rate added as resolved.
record_time (float32_BE) and sample_rate (uint16_BE) both validated
against live device across normal / fast / faster modes and multiple
record time settings. Diagnostic scaffolding no longer needed.
Fixed a 1-byte offset jitter that produced garbage values when the
device was set to "faster" (4096 Sa/s) mode.
Root cause: 4096 = 0x1000, so the sample_rate bytes in the raw S3
frame are `10 10 00` (DLE-escaped). After DLE unstuffing → `10 00`
(2 bytes vs 3 for 1024/2048), making frame C 1 byte shorter and
shifting all subsequent field offsets by -1.
Fix: locate the stable 10-byte anchor `01 2c 00 00 be 80 00 00 00 00`
(max-record-limit constant + first two alarm-level floats) and read:
sample_rate = uint16_BE at anchor - 2
record_time = float32_BE at anchor + 10
Offline-validated against all five logged hex dumps (1071 and 1070
byte cfg, record times 3/5/8 s, sample rates 1024 and 4096):
all five: correct values with anchor approach.
Both offsets identified from _cfg_diagnostic scan on BE11529:
cfg[64] float32_BE = 3.0 → record_time in seconds
cfg[52] uint16_BE = 1024 → sample_rate in Sa/s
Values are plausible but NOT yet validated by changing device settings
and re-reading. Marked as ✅ candidate in docstring — confirm and
remove the ⚠️ note once a device-side change is observed here.
BE11529 sometimes returns frame D with page_key=0x0000 (44 bytes),
identical to the frame B response, inflating cfg to ~1115 bytes and
mis-aligning all field offsets. Track (page_key, chunk_size) pairs
and drop any repeat before appending to the running config buffer.
- setup_name was broken: _find_string_after(b"Standard Recording Setup")
returned what comes AFTER the string (i.e. "Project:"), not the name
itself. Fixed by searching for the first long (>=8 char) ASCII string
in cfg[40:250] with _find_first_string().
- record_time offset 0x28 was wrong (that location holds "(L)", a unit
label string). Disabled for now to avoid returning garbage; correct
offset will be determined from _cfg_diagnostic() output.
- Added _cfg_diagnostic(): logs all strings and all plausible float32/uint16
values across the full cfg so record_time and sample_rate offsets can be
pinpointed from a single device run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reverse-engineered the full Blastware 4-frame sequence for SUB 1A:
A (probe): offset=0x0000, params[7]=0x64
B (data req 1): offset=0x0400, params[2]=0x00, params[7]=0x64 → bytes 0..1023
C (data req 2): offset=0x0400, params[2]=0x04, params[7]=0x64 → bytes 1024..2047
D (data req 3): offset=0x002A, params[2]=0x08, params[7]=0x64 → bytes 2048..2089
We were only sending A+D and getting 44 bytes (the last chunk).
Now sends B, C, D in sequence; each E5 response has 11-byte echo header
stripped, and chunks are concatenated. Devices that return all data in
one frame (BE18189 style) are handled — timeouts on B/C are skipped
gracefully and data from D still accumulates.
Total expected: 0x0400 + 0x0400 + 0x002A = 0x082A = 2090 bytes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BE11529 sends the compliance config (SUB 1A / E5 response) as a stream
of small frames (~44 bytes each) rather than one large frame like BE18189.
Changes:
- read_compliance_config(): loop calling _recv_one() until 0x082A bytes
accumulated or 2s inter-frame gap; first frame strips 11-byte echo header,
subsequent frames logged in full for structure analysis
- _recv_one(): add reset_parser=False option to preserve parser state and
_pending_frames buffer between consecutive reads from one device response;
also stash any extra frames parsed in a single TCP chunk so they are not lost
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The heuristic offsets for trigger/alarm levels were causing struct unpack
errors. These fields require detailed field mapping from actual E5 captures
to determine exact byte positions relative to channel labels.
For now, skip extraction and leave trigger_level_geo/alarm_level_geo as None.
This prevents the '500 Device error: bytes must be in range(0, 256)' error.
Once we capture an E5 response and map the exact float positions, we can
re-enable this section with correct offsets.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
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
- Establish v0.5.0 as first versioned release
- README rewritten to reflect current scope: Blastware replacement in
progress, not just a reverse-engineering capture tool
- Documents all current components: seismo_lab.py, minimateplus,
sfm/server.py, Console tab, TCP/cellular transport
- Adds ACEmanager required settings table (Quiet Mode etc.)
- Adds roadmap section
- CHANGELOG.md created with entries from v0.1.0 through v0.5.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
seismo_lab.py:
- Add ConsolePanel — third tab for direct device connections over serial
or TCP without the bridge subprocess
- Commands: POLL, Serial #, Full Config, Event Index (open/close per cmd)
- Colour-coded output: TX blue, RX raw teal, parsed green, errors red
- Save Log and Send to Analyzer buttons; auto-saves to bridges/captures/
- Queue/after(100) pattern — no performance impact
- Add SCRIPT_DIR to sys.path so minimateplus imports work from GUI
docs/instantel_protocol_reference.md:
- Confirm calibration year field at SUB FE payload offset 0x56–0x57
(uint16 BE): 0x07E7=2023 (BE18189), 0x07E9=2025 (BE11529)
- Document full Sierra Wireless RV50/RV55 required ACEmanager settings
(Quiet Mode, Data Forwarding Timeout, TCP Connect Response Delay, etc.)
- Correct §14.2: RV50/RV55 sends RING/CONNECT over TCP to caller even
with Quiet Mode on; parser handles by scanning for DLE+STX
- Confirm "Operating System" boot string capture via cold-start Console
- Resolve open question: 0x07E7 field = calibration year
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- bridges/tcp_serial_bridge.py: increase default boot_delay 2s → 8s to
cover MiniMate Plus cold-start time (unit wakes from RS-232 line
assertion but takes 5-10s to be ready for POLL_PROBE).
- sfm/server.py: add _run_with_retry() — on TCP connections only, retries
once on ProtocolError. Serial timeouts are not retried (usually a real
fault). Confirmed behaviour: unit wakes purely from RS-232 line voltage,
no software wake-up frame needed.
- minimateplus/transport.py: add TcpTransport — stdlib socket-based transport
with same interface as SerialTransport. Overrides read_until_idle() with
idle_gap=1.5s to absorb the modem's 1-second serial data forwarding buffer.
- minimateplus/client.py: make `port` param optional (default "") so
MiniMateClient works cleanly when a pre-built transport is injected.
- minimateplus/__init__.py: export SerialTransport and TcpTransport.
- sfm/server.py: add `host` / `tcp_port` query params to all device endpoints.
New _build_client() helper selects TCP or serial transport automatically.
OSError (connection refused, timeout) now returns HTTP 502.
- docs/instantel_protocol_reference.md: add changelog entry and full §14
(TCP/Modem Transport) documenting confirmed transparent passthrough, no ENQ
on connect, modem forwarding delay, call-up vs ACH modes, and hardware note
deprecating Raven X in favour of RV55/RX55.
Usage: GET /device/info?host=<modem_ip>&tcp_port=12345