Lean replacement for the traccar GPS server that
runs on Pi sarah in the RV. Receives NMEA datagrams (GPRMC + GPGGA) from
the Pepwave MAX BR1 Pro 5G, stores positions in SQLite, and exposes both an
HTTP API and an MCP server for queries.
Replaces ~500 MB of Java + MariaDB with a single self-contained Dart binary (~30 MB) and a SQLite file.
- UDP listener (port 5031) decodes GPRMC + GPGGA sentences, verifies
checksums, assembles them into a single
Positionrecord (keyed on HH:MM:SS), and writes to SQLite. - HTTP API (port 8002) exposes positions, history, and aggregate stats as JSON or GeoJSON.
- MCP JSON-RPC (port 8002
/mcp) wraps the API as three tools for any MCP-aware LLM client:get_current_positionget_position_history(with thinning + geojson)get_travel_stats
- Speak any GPS protocol other than NMEA GPRMC/GPGGA. The Pepwave is being reconfigured from TAIP to NMEA as part of the cutover; if you point a TAIP-only device at this, parses will fail silently.
- Have a web UI. Use kiwix-serve's UI for the library; for the GPS data, the HTTP API and MCP are the interfaces.
- Multi-tenant or auth. Trusted-LAN only.
gps-tracker/
├── bin/server.dart entrypoint: wires UDP + storage + HTTP/MCP
├── lib/
│ ├── nmea/
│ │ ├── parser.dart checksum-verifying GPRMC/GPGGA parser
│ │ ├── assembler.dart pairs RMC+GGA by HH:MM:SS, 2s reaper
│ │ └── sentence.dart sealed sentence classes
│ └── src/
│ ├── api.dart shelf-based HTTP endpoints
│ ├── config.dart env-driven settings
│ ├── listener.dart RawDatagramSocket → assembler glue
│ ├── mcp.dart JSON-RPC MCP server (3 tools)
│ ├── position.dart the merged-record type
│ └── storage.dart SQLite WAL with prepared insert + queries
├── test/nmea_test.dart parser + assembler unit tests
├── tool/ one-shot ops (backfill from MariaDB, ...)
├── Dockerfile multi-stage build → debian-slim runtime
└── docker-compose.yml deploy with `docker compose up -d`
| Var | Default | Notes |
|---|---|---|
GPS_DB_PATH |
/srv/library/gps/positions.db |
SQLite path. WAL is created alongside automatically. |
GPS_UDP_PORT |
5031 |
Must match Pepwave's GPS forwarding destination. |
GPS_HTTP_PORT |
8002 |
HTTP API + /mcp JSON-RPC. |
GPS_DEVICE_ID |
SARA |
Default device id when the sentence stream doesn't carry one. |
A single positions table; see lib/src/storage.dart for the canonical
definition. Indexed on (device_id, fixtime) and (fixtime).
The Pepwave currently sends TAIP to the legacy traccar on the same port. For zero-downtime cutover:
- Build and start gps-tracker on a different UDP port (e.g. 5032).
- Configure the Pepwave with a second GPS forwarding destination pointing at sarah:5032 (NMEA, with GPRMC + GPGGA enabled, others disabled).
- Verify gps-tracker is recording fixes alongside traccar for a few days.
- Backfill historical data:
dart run tool/backfill_from_mariadb.dart. - Switch Pepwave to send only the new destination on port 5031; stop traccar; remove the traccar+mariadb containers.
- Update the laptop's GeoJSON export script to query SQLite instead of MariaDB.
Personal use. No public license.