Skip to content

nth-bailey/com-port-nmea-to-timescale-db

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

📡 gpsink

Stream NMEA GPS data from a COM port radio dongle into TimescaleDB with PostGIS.

Built for ≥1 Hz radio GPS data sources transmitting RMC or GGA sentences ($GNRMC, $GPGGA, $GLRMC, …).

Features

  • Serial / COM port reader — configurable baud rate, parity, stop bits, flow control
  • NMEA parser — extracts lat/lon/speed/course/altitude from any $xxRMC or $xxGGA sentence via pynmea2. Missing fields (e.g. speed in GGA) evaluate to NULL.
  • TimescaleDB writer — auto-provisions a hypertable with a PostGIS geometry(Point, 4326) column
  • Optional database — run in serial-only mode (--no-db) to verify COM port connectivity before involving the DB
  • Auto-reconnect — both serial port and database connections automatically retry with exponential backoff on transient failures (e.g. USB hiccup, network outage)
  • CLI — fully configurable via command-line flags
  • GUI — simple Tkinter interface with database on/off toggle for configuring and monitoring the stream

Quick Start

# Install with uv
uv sync --extra dev

# Run the CLI
uv run gpsink run --port COM3 --baud 9600 --db-host localhost --db-name gpsink

# Run serial-only mode (no database — just verify COM port)
uv run gpsink run --port COM3 --no-db

# Launch the GUI
uv run gpsink gui

# Provision the database schema only
uv run gpsink provision --db-host localhost --db-name gpsink

CLI Reference

gpsink run [OPTIONS]      Start reading NMEA data and writing to TimescaleDB
gpsink gui                Launch the graphical interface
gpsink provision          Create extensions, table, and hypertable

gpsink run Options

Flag Default Description
--port / -p COM3 Serial / COM port name
--baud / -b 9600 Baud rate
--bytesize 8 Data bits (5-8)
--parity N Parity (N/E/O/M/S)
--stopbits 1 Stop bits (1/1.5/2)
--db-host localhost TimescaleDB host
--db-port 5432 TimescaleDB port
--db-name gpsink Database name
--db-user postgres Database user
--db-password postgres Database password
--table gps_readings Target table name
--source-label default Label for this GPS source (e.g. truck-1)
--source-uuid Optional UUID for this GPS source
--no-db off Skip database — serial-only / dry-run mode
-v off Enable debug logging

Auto-Reconnect

Both the serial port reader and the database writer will automatically attempt to reconnect when the connection is lost (e.g. USB cable briefly unplugged, network outage). Reconnection uses exponential backoff (1s → 2s → 4s → … up to 30s), with up to 10 retries by default. Reconnection events are logged to the console/GUI live feed.

GUI Reference

alt text

Database Schema

CREATE TABLE gps_readings (
    time         TIMESTAMPTZ      NOT NULL,  -- UTC timestamp of the fix
    source_uuid  UUID,                       -- Actual UUID for the source
    source_label TEXT             NOT NULL,  -- User-defined entity / track label
    geom         GEOMETRY(Point, 4326),      -- WGS 84 point (lon, lat) in decimal degrees
    latitude     DOUBLE PRECISION NOT NULL,  -- Decimal degrees; positive = North, negative = South
    longitude    DOUBLE PRECISION NOT NULL,  -- Decimal degrees; positive = East, negative = West
    altitude     DOUBLE PRECISION,           -- Metres above mean sea level (from GGA)
    speed_knots  DOUBLE PRECISION,           -- Speed over ground in knots (1 kt ≈ 1.852 km/h)
    course       DOUBLE PRECISION,           -- Track angle in degrees true (0–360°)
    status       CHAR(1),                    -- 'A' = active/valid fix, 'V' = void/invalid
    raw_sentence TEXT                         -- Original NMEA sentence verbatim
);
-- Automatically converted to a TimescaleDB hypertable

NMEA → DB conversion: Raw RMC and GGA sentences encode position as DDDMM.MMMM with N/S/E/W indicators. The parser (pynmea2) converts these to signed decimal degrees before insertion (e.g. 48.1173, with South/West as negative). The geom column stores the same coordinates as a PostGIS Point(longitude, latitude) in SRID 4326 (WGS 84).

Multi-entity tracking: Use --source-label (CLI) or the Source Label field (GUI) to label each GPS stream. Run multiple gpsink instances against the same table with different source labels to track separate vehicles, drones, etc. Query by WHERE source_label = 'truck-1'. Additional tracking can use the source_uuid field for hardcoded actual UUIDs.

Development

# Install dev dependencies
uv sync --extra dev

# Run tests
uv run pytest

# Run with coverage
uv run pytest --cov=gpsink --cov-report=term-missing

Project Structure

├── src/gpsink/
│   ├── __init__.py        # Package version
│   ├── config.py          # Dataclass configs for serial & DB
│   ├── nmea_parser.py     # RMC sentence parser → GPSFix
│   ├── serial_reader.py   # Threaded COM port reader
│   ├── db.py              # TimescaleDB/PostGIS writer
│   ├── cli.py             # Click CLI (run / gui / provision)
│   └── gui.py             # Tkinter GUI
├── tests/
│   ├── conftest.py        # Shared fixtures & sample sentences
│   ├── test_config.py
│   ├── test_nmea_parser.py
│   ├── test_serial_reader.py
│   ├── test_db.py
│   ├── test_cli.py
│   └── test_gui.py
└── pyproject.toml         # PEP 621 project config (uv/hatchling)

License

MIT

About

Real-time NMEA GPS data pipeline: reads $GPRMC sentences from a serial COM port (radio dongle) and streams them into TimescaleDB with PostGIS. Features auto-reconnect with exponential backoff, configurable serial params, a Click CLI, and a Tkinter GUI. Supports serial-only dry-run mode. Built with Python & uv.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages