Subscribes to an OwnTracks MQTT location stream and writes GTFS-RT Trip Updates to Redis
  • Python 94.3%
  • Dockerfile 4.3%
  • Makefile 1.4%
Find a file
Max Katz-Christy 0c4a15dd13
All checks were successful
Build / build (push) Successful in 23s
feat: support MQTT username and password
2026-03-12 03:51:45 +01:00
.forgejo/workflows ci: add build script 2026-03-10 17:10:38 +01:00
src/trip_updogger feat: support MQTT username and password 2026-03-12 03:51:45 +01:00
tests Initial Commit 2026-03-06 15:19:28 +01:00
.pre-commit-config.yaml build: default pre-commit 2026-03-10 15:02:53 +01:00
CLAUDE.md refactor: rm compose 2026-03-10 01:40:39 +01:00
Dockerfile build: use git for railroad-club 2026-03-10 04:47:28 +01:00
LICENSE.txt docs: add agpl-3 2026-03-10 14:44:44 +01:00
Makefile build: cp origin/main 2026-03-10 17:28:23 +01:00
pyproject.toml refactor: use railroad-club 2026-03-10 03:43:56 +01:00
README.md refactor: rename api to cafe-car 2026-03-10 14:36:55 +01:00
uv.lock build: use git for railroad-club 2026-03-10 04:47:28 +01:00

trip-updogger

Tiny async Python service that bridges OwnTracks location events via MQTT to GTFS-RT Trip Updates in Redis.

Part of a larger stack; see deploy-gtfs-rt for the full deployment.

How it fits together

OwnTracks app (phone)
    └─> MQTT broker
            └─> trip-updogger
                    └─> Redis (trip_update:{trip_id} keys)
                            └─> cafe-car (serves GTFS-RT feeds)

On each location event, the service queries PostgreSQL for the vehicle's stop times (resolving the MQTT device name through a tripalias table), computes the current delay against the schedule, and writes a Trip Update to Redis. If the MQTT connection drops, it reconnects with exponential backoff (1s → 60s max).


Payload transformation

Each OwnTracks location event is decoded as follows:

OwnTracks field Meaning Notes
topic owntracks/{user}/{device} driver, trip_id user maps to a driver row (for feed lookup); device is used as the alias to resolve to a real trip_id
lat, lon vehicle position used to compute delay against scheduled stop times
tst timestamp seconds since epoch; used to determine time-of-day for schedule lookup
cog bearing degrees; not written to Redis
vel speed km/h; not written to Redis

Redis key: trip_update:{device} — written on each location update (key uses the alias/device name).

Redis value:

{"trip_id": "...", "vehicle_id": "...", "timestamp": 1234567890, "delay": 42, "stop_sequence": 5}

delay is in seconds (positive = late, negative = early).


Environment variables

Variable Example Description
MQTT_BROKER tcp://host.docker.internal:1883 MQTT broker URL (tcp scheme)
REDIS_URL redis://host.docker.internal:6379/1 Redis connection URL including DB number
DATABASE_URL postgresql+psycopg2://postgres:postgres@host.docker.internal:5432/postgres PostgreSQL connection URL

All three are required — the service exits with KeyError if any is missing.


Running

# Install dependencies (Python 3.13, uv)
uv sync

# Run locally (requires MQTT broker, Redis, and PostgreSQL with GTFS data)
MQTT_BROKER=tcp://localhost:1883 REDIS_URL=redis://localhost:6379/1 \
  DATABASE_URL=postgresql+psycopg2://postgres:postgres@localhost:5432/postgres \
  uv run python -m trip_updogger.main

# Build and push Docker image (requires clean, pushed branch)
make push

Testing

# Publish a location event (vel in km/h, cog in degrees)
mosquitto_pub -t owntracks/alice/trip123 \
  -m '{"_type":"location","lat":51.5,"lon":-0.1,"tst":1700000000,"vel":36,"cog":90}'

# Verify the Redis key (use the DB set in REDIS_URL)
redis-cli -n 1 GET trip_update:trip123