# seismo-relay `v0.12.0` A ground-up replacement for **Blastware** — Instantel's aging Windows-only software for managing MiniMate Plus seismographs. Built in Python. Runs on Windows, Linux, or macOS. Connects to instruments over direct RS-232 or cellular modem (Sierra Wireless RV50 / RV55). > **Status:** Active development. Full read + write + erase + monitoring > pipeline working end-to-end over TCP/cellular. ACH Auto Call Home server > handles inbound unit connections, downloads events, and persists everything > to a SQLite database. SFM REST API exposes device control and DB queries. > See [CHANGELOG.md](CHANGELOG.md) for full version history. --- ## What's in here ``` seismo-relay/ ├── seismo_lab.py ← Main GUI (Bridge + Analyzer + Console tabs) │ ├── minimateplus/ ← MiniMate Plus client library │ ├── transport.py ← SerialTransport, TcpTransport, SocketTransport │ ├── protocol.py ← DLE frame layer, SUB command dispatch │ ├── client.py ← High-level client (connect, get_events, push_config, …) │ ├── framing.py ← Frame builders, DLE codec, S3FrameParser │ └── models.py ← DeviceInfo, Event, ComplianceConfig, MonitorLogEntry, … │ ├── sfm/ ← SFM REST API server (FastAPI, port 8200) │ ├── server.py ← All device + DB endpoints │ ├── database.py ← SeismoDb — SQLite persistence layer │ └── sfm_webapp.html ← Embedded web UI (served at /) │ ├── bridges/ │ ├── ach_server.py ← Inbound ACH call-home server (main production server) │ ├── ach_mitm.py ← Transparent MITM proxy for capturing BW sessions │ ├── s3-bridge/ ← RS-232 serial bridge (capture tool) │ ├── tcp_serial_bridge.py ← Local TCP↔serial bridge (bench testing) │ ├── gui_bridge.py ← Standalone bridge GUI │ └── raw_capture.py ← Simple raw capture tool │ ├── parsers/ │ ├── s3_analyzer.py ← Session parser, differ, Claude export │ ├── gui_analyzer.py ← Standalone analyzer GUI │ └── frame_db.py ← SQLite frame database │ └── docs/ └── instantel_protocol_reference.md ← Reverse-engineered protocol spec ``` --- ## Quick start ### ACH inbound server (production) Listens for inbound unit call-homes, downloads all new events and monitor log entries, and writes everything to `bridges/captures/seismo_relay.db`. ```bash python bridges/ach_server.py --port 12345 --output bridges/captures/ ``` Point the unit's ACEmanager **Remote Host** to this machine's IP and **Remote Port** to `12345`. Options: ``` --port N Listen port (default 12345) --output DIR Capture directory (default bridges/captures/) --allow-ip IP Allowlist an IP (repeat for multiple; default: accept all) --max-events N Safety cap for first run (default: unlimited) --clear-after-download Erase device memory after successful download --verbose Debug logging ``` ### SFM REST server Exposes device control and DB queries as a REST API. Proxied by terra-view. ```bash python sfm/server.py # default: 0.0.0.0:8200 python -m uvicorn sfm.server:app --host 0.0.0.0 --port 8200 --reload ``` Open `http://localhost:8200` for the embedded web UI, or `http://localhost:8200/docs` for the interactive API docs. ### Seismo Lab GUI ```bash python seismo_lab.py ``` --- ## SFM REST API ### Live device endpoints Each call dials the device, does its work, and closes the connection. TCP connections are retried once on `ProtocolError` to handle cold-boot timing. **Caching** — frequently-polled endpoints are cached in-process to avoid redundant TCP round-trips: | Method | URL | Cache | |--------|-----|-------| | `GET` | `/device/info` | Indefinite; invalidated by `POST /device/config` | | `GET` | `/device/events` | Count-probe fast path (~2s); full download only when new events detected | | `GET` | `/device/event/{idx}/waveform` | Permanent per event index | | `GET` | `/device/monitor/status` | 30-second TTL | | `POST` | `/device/connect` | — | | `POST` | `/device/config` | Writes compliance config; invalidates cache | | `POST` | `/device/monitor/start` | Sends SUB 0x96 | | `POST` | `/device/monitor/stop` | Sends SUB 0x97 | All cached endpoints accept `?force=true` to bypass the cache. Transport query params (supply one set): ``` Serial: ?port=COM5&baud=38400 TCP: ?host=1.2.3.4&tcp_port=12345 ``` ### DB read endpoints Query the SQLite database written by `ach_server.py`. All read-only except `PATCH /db/events/{id}/false_trigger`. | Method | URL | Description | |--------|-----|-------------| | `GET` | `/db/units` | All known serials with summary stats | | `GET` | `/db/events` | Triggered events (filter by serial, date range, false_trigger) | | `GET` | `/db/monitor_log` | Monitoring intervals | | `GET` | `/db/sessions` | ACH call-home session history | | `PATCH` | `/db/events/{id}/false_trigger?value=true` | Flag / unflag false triggers | --- ## minimateplus library ```python from minimateplus import MiniMateClient from minimateplus.transport import TcpTransport # Serial client = MiniMateClient(port="COM5") # TCP (cellular modem) client = MiniMateClient(transport=TcpTransport("1.2.3.4", 12345), timeout=30.0) with client: # Read info = client.connect() # DeviceInfo — serial, firmware, compliance config count = client.count_events() # Number of stored events keys = client.list_event_keys() # Fast browse walk — event keys only, no download events = client.get_events() # Full download: headers + peaks + metadata monitor = client.get_monitor_status() # Battery, memory, is_monitoring flag log = client.get_monitor_log_entries() # Monitoring intervals (partial 0x2C records) # Write client.apply_config( sample_rate=1024, trigger_level_geo=0.5, project="Bridge Inspection 2026", client_name="City of Portland", operator="B. Harrison", ) # Control client.start_monitoring() # SUB 0x96 client.stop_monitoring() # SUB 0x97 client.delete_all_events() # Erase all (SUB 0xA3 → 0x1C → 0x06 → 0xA2) ``` `get_events()` runs the full per-event sequence: `1E → 0A → 0C → 5A → 1F`. SUB 5A bulk stream provides `client`, `operator`, and `sensor_location` as they existed at record time — not backfilled from the current compliance config. --- ## Database `ach_server.py` writes to `bridges/captures/seismo_relay.db` (SQLite, WAL mode). Three tables, all unit-keyed by serial number: | Table | Key | Contents | |-------|-----|----------| | `ach_sessions` | UUID | Per-call-home audit record: serial, peer IP, events_downloaded, duration | | `events` | UUID, UNIQUE(serial, waveform_key) | Triggered events: timestamp, PPV per channel, project/client/operator strings, false_trigger flag | | `monitor_log` | UUID, UNIQUE(serial, waveform_key) | Monitoring intervals: start/stop time, duration, geo threshold | Deduplication is by `(serial, waveform_key)` — repeat call-homes or re-runs never produce duplicate rows. Post-erase key reuse is handled automatically via the high-water mark in `ach_state.json`. --- ## Connecting over cellular (RV50 / RV55) Field units connect via Sierra Wireless RV50 or RV55 cellular modems. ### Required ACEmanager settings | Setting | Value | Why | |---------|-------|-----| | Configure Serial Port | `38400,8N1` | Must match MiniMate baud rate | | Flow Control | `None` | Hardware FC blocks TX if pins unconnected | | **Quiet Mode** | **Enable** | **Critical** — disabled injects `RING`/`CONNECT` onto serial, corrupting the S3 handshake | | Data Forwarding Timeout | `1` (= 0.1 s) | Lower latency | | TCP Connect Response Delay | `0` | Non-zero silently drops the first POLL frame | | TCP Idle Timeout | `2` (minutes) | Prevents premature disconnect | | DB9 Serial Echo | `Disable` | Echo corrupts the data stream | --- ## Protocol quick-reference | Term | Value | Meaning | |------|-------|---------| | DLE | `0x10` | Data Link Escape | | STX | `0x02` | Start of frame | | ETX | `0x03` | End of frame | | ACK | `0x41` | Frame-start marker sent before every BW frame | | DLE stuffing | `10 10` on wire | Literal `0x10` in payload | **Response SUB rule:** `response_SUB = 0xFF - request_SUB` (no exceptions) Full protocol documentation: [`docs/instantel_protocol_reference.md`](docs/instantel_protocol_reference.md) --- ## Requirements ```bash pip install pyserial fastapi uvicorn ``` Python 3.10+. Tkinter is included with the standard Python installer on Windows (check "tcl/tk and IDLE" during install). --- ## Virtual COM ports (bridge capture) ``` Blastware → COM4 (virtual) ↔ s3_bridge.py ↔ COM5 (physical) → MiniMate Plus ``` Use **com0com** or **VSPD** to create the virtual COM pair on Windows. --- ## Roadmap - [x] Full read pipeline — device info, compliance config, event download with true event-time metadata - [x] Write commands — push compliance config, trigger thresholds, project strings to device - [x] Erase all events — confirmed erase sequence from live MITM capture - [x] Monitor control — start/stop monitoring, read battery/memory/status - [x] Monitor log entries — decode partial 0x2C records (continuous monitoring intervals) - [x] ACH inbound server — accept call-home connections, download events, dedup by key - [x] SQLite persistence — events, monitor log, and session history in `seismo_relay.db` - [x] SFM REST API — device control + DB query endpoints, live device cache - [ ] Terra-view integration — seismo-relay router, unit detail page, VISON-style event listing - [ ] Vibration summary reports — highest legit PPV per project → Word doc (false trigger filtering first) - [ ] Compliance config encoder — build raw write payloads from a `ComplianceConfig` object - [ ] Modem manager — push RV50/RV55 configs via Sierra Wireless API