feat(tpms): slice-1 ingest + JSON API for Mazda CX-9 - #11
Merged
Conversation
rtl_433 (register #156, 315 MHz) streams JSON on stdout; a Python ingest daemon
reads its stdin, filters to the Abarth-124Spider decoder (matches Mazda VDO
sensors), normalizes to kPa + °C + battery_ok, and upserts into SQLite. A small
Flask app exposes /api/vehicles, /api/vehicles/mazda-cx9/tpms/{latest,history},
and /api/health for a future home dashboard.
Scope kept to what Tom greenlit on the issue: parked-only, Mazda-only,
any-tire-low (no sensor→corner mapping, positions stay NULL). Systemd units
for plexpi ship under deploy/plexpi/ alongside a manual install README —
plexpi has no itguy so this follows the bluelinky/fuelbot convention.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Six findings from the review, all real: 1. rtl_433 emits system-local time by default — capture unit now passes `-M utc` and the docstring on `_parse_time` documents the assumption. Without this every stored ts would be skewed by the local UTC offset. 2. `User=`/`Group=pi` are rejected in `systemctl --user` units. Removed and moved data + log paths under `%h` so no sudo is needed. 3. `history_for_vehicle` joined sensors on `sensor_id` alone — TPMS IDs aren't globally unique; when a second vehicle is ever seeded a collision would cross-contaminate the results. Join is now on (vehicle_id, sensor_id). 4. Dropped the stale `Wants=tpms-ingest.socket` — copy-paste from an abandoned socket-activation design. 5. API `_connect` re-ran the full schema script + vehicle upsert on every request. Now: init once at app startup, per-request handles are plain sqlite3 connections. 6. `main()` in ingest had a dead-branch `return 0 if run() >= 0 else 1` — `run()` never returns a negative count. Simplified. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Slice-1 of TPMS ingest for the 2013 Mazda CX-9, per Tom's scoping answers on #7:
rtl_433 -M utc -f 315000000 -R 156 -F json→ stdout (Abarth-124Spider decoder covers Mazda VDO sensors at 315 MHz per issue Research Task: Tire Pressure Monitoring #1 research).src/tpms/ingest.py): reads that JSON stream from stdin, filters to the Abarth-124Spider decoder, normalizes pressure to kPa + temperature to °C +battery_ok, upserts into SQLite. Survives malformed lines and non-target decoder events without dropping the pipe.src/tpms/api.py): Flask app on:8090:GET /api/vehicles— vehicle set (mazda-cx9 only in slice-1).GET /api/vehicles/mazda-cx9/tpms/latest— one row per known sensor, plusany_low/all_staleflags for the future dashboard.GET /api/vehicles/mazda-cx9/tpms/history?since=&until=&limit=— time-window slice.GET /api/health—last_event_ts,receiver_ok, so the dashboard can distinguish sensors-quiet from receiver-broken.src/tpms/db.py): vehicles + sensors + readings. Slice-1 seeds one vehicle; positions stay NULL (Tom's Q4 answer — any-tire-low is sufficient).deploy/plexpi/tpms-{capture,api}.service) —systemctl --userunits targeting thepiaccount on plexpi. plexpi has noitguy, sodeploy/plexpi/README.mdhas the manual install ritual (bluelinky/fuelbot pattern).Scope explicitly deferred: sensor→corner mapping, drive-time reception, other vehicles, dashboard UI.
Design notes
-M utc— without it, timestamps are system-local with no tz suffix and the ingest daemon would skew every reading by the local UTC offset. Captured in both the systemd unit and the module docstring.create_app()runs schema init once. Concurrent reader alongside the ingest writer — WAL is on.(ts, sensor_id)PK conflict — rtl_433 sometimes re-emits an identical event.Test plan
stale+any_lowsemantics, unknown-vehicle 404, history bounded bysince/until, health with empty DB.python -m src.tpms.ingest, then hit every API endpoint. See the captured session below.Captured test run (showboat)
TPMS slice-1 test run
2026-07-13T00:23:41Z by Showboat 0.6.1
Closes #7
🤖 Generated with Claude Code