Skip to main content

diaggpsd

Live GNSS from a Qualcomm modem's DIAG stream — served to gpsd clients and written to RINEX. diaggpsd subscribes to a Qualcomm modem's GNSS DIAG log records, decodes them, and makes the fixes and raw measurements available three ways:

  • gpsd clients — a gpsd-compatible JSON server on TCP 2947, so gpspipe, cgps, xgps, gpsmon, JOSM, and anything built on libgps just work.
  • Raw JSON — one packet per line on stdout or a plain TCP port, for logging and custom tooling without the gpsd protocol overhead.
  • RINEX — observation/navigation files for post-processing pipelines.

diaggpsd feeds gpsd clients by speaking the gpsd protocol; it is not the gpsd daemon and does not replace it. It reads the modem directly over DIAG, so it works where no kernel GPS driver or NMEA port is exposed.

Layout

diaggpsd is one Python package, diaggpsd, under src/diaggpsd/. Its console commands (diaggpsd, diaggpsd-diag-setup, diaggpsd-dlf-to-jsonl, diaggpsd-dump-pos-report, diaggpsd-gnss-compare) are the supported way to run it; python -m diaggpsd starts the daemon too. The modules:

Module Role
diaggpsd.py the live daemon — argument parsing, DIAG subscription, main loop, gpsd/raw output
gnss_packets.py thin re-export shim over the diaggrok GNSS parsers
at.py AT-command interface for GNSS engine setup (Quectel-oriented)
gpsd_server.py the gpsd JSON protocol server (TCP 2947)
diag_setup.py DIAG log-mask setup helper
dlf_to_jsonl.py offline: decode a DIAG capture to JSONL
dump_pos_report.py offline: dump position reports from a live/captured stream
gnss_compare.py / gnss_compare_ref_filter.py compare decoded fixes against a reference track
rinex_writer.py RINEX observation/navigation output
modem_profiles.py per-chipset NV/AT setup profiles

Dependencies

diaggpsd is first-party Python. At install time it pulls two sibling libraries that hold the reverse-engineered pieces:

  • diaggrok — the DIAG GNSS log-record parsers (imported through gnss_packets.py).
  • diagmunge — the DIAG transport core (DiagClient) and replay.

The live AT-port path additionally needs pyserial, installed via the optional serial extra. DIAG capture, replay, decode, RINEX, and the gpsd server are all pyserial-free.

From PyPI:

pip install diaggpsd             # daemon + gpsd server + offline tooling
pip install "diaggpsd[serial]"   # + the live AT-port path (pyserial)

diaggrok and diagmunge come from PyPI with it. Each PyPI version is the same code as the GitHub tag of the same number (diaggpsd==2026.11.8 is tag v2026.11.8). From a clone, pip install . works the same way.

Requires Python 3.11+.

Quick start

Two steps: diaggpsd-diag-setup prepares the modem once (NV items, the GNSS session, AT commands), then diaggpsd streams. diaggpsd itself never sends AT commands, so the AT options (--at-port, --no-at, --cold-start) belong to the setup step.

USB-connected modem

# One-time setup: enable GNSS logging and start the engine over AT.
diaggpsd-diag-setup --chipset sdx55 --diag-port /dev/ttyUSB0 --at-port /dev/ttyUSB2

# Stream to gpsd clients.
diaggpsd --diag-port /dev/ttyUSB0 --debug

# In another terminal:
gpspipe -w 127.0.0.1

diaggpsd-diag-setup --list-profiles lists the chipset profiles. Add --cold-start to delete cached assistance, or --no-at when the GNSS engine is already running or the AT path is unavailable. On QuecOpen builds the host-side AT port is bridged to an SMD device node by an AP-side service; if that bridge is stopped, AT commands can fail silently, so --no-at is the robust choice there.

Network-attached modem

A modem that relays its DIAG stream over the network is read without a serial port. In the two passive modes the device sets the log mask and diaggpsd never writes one.

# The device pushes a DGE1-framed UDP stream (diagbarf):
diaggpsd --diag-port udp-listen:0.0.0.0:12399 --debug

# The device runs diag_socket_log and connects to this host:
diaggpsd --diag-port tcp --debug
#   on the modem: diag_socket_log -a <host_ip> -p 2500

Transport modes

Mode Invocation Who sets the log mask
USB / serial (default) --diag-port /dev/ttyUSB0 diaggpsd
TCP diag_socket_log --diag-port tcp or tcp:0.0.0.0:2500 diaggpsd
UDP, DGE1 framing (diagbarf) --diag-port udp-listen or udp-listen:0.0.0.0:12399 the device
UDP, QMDL2 chunks (diag_udp_fwd) --diag-port udp or udp:0.0.0.0:2500 the device

When diaggpsd sets the log mask, it clears the mask again on exit with one LOG_CONFIG DISABLE, draining queued frames for up to 10 s while it waits for the ack. That also happens when the subscribe itself fails, for example when a slow diag_socket_log tunnel backs up until a mask ack never arrives (exit status 1). If no ack comes, the log says so; power-cycle the modem (AT+CFUN=1,1) and send a zero log mask before the next run.

Output modes

  • stdout JSON (default) — one parsed packet per line (PositionReport, GpsMeasurementReport, GlonassMeasurementReport, OemdreMeasurementReport, OemdreSvPolyReport). Suppress with --no-stdout.
  • Raw TCP — --raw-port 5000 broadcasts the JSON lines to connected TCP clients.
  • gpsd server (TCP 2947) — the standard gpsd JSON protocol. Disable with --no-gpsd, move with --gpsd-port PORT.

Offline decoding: input formats for dlf_to_jsonl

How a DIAG capture was produced determines how to decode it. Two on-disk formats both commonly carry the .dlf extension but are not interchangeable:

Source On-disk format Invocation
QCSuper (--dlf) DLF records (per-record length/type header) dlf_to_jsonl capture.dlf out.jsonl
raw HDLC dump 0x7E-delimited HDLC byte stream (no DLF envelope) dlf_to_jsonl --format bin --verify-crc capture.dlf out.jsonl

Using DLF mode on a raw-HDLC file yields a near-zero parse rate (the 0x7E delimiters do not form a valid DLF record header). DLF mode on QCSuper output is correct and needs no --verify-crc (QCSuper validates CRCs at capture time).

GPS lock notes

After start, the modem needs time to acquire satellites: typically 10–30 s outdoors with fresh XTRA assistance, 1–10 min on a cold start with clear sky. The first PositionReport with pos_source=2 (Kalman fix) marks a usable position; the gpsd server begins pushing TPV at that point.

gpsd protocol subset

VERSION (on connect), WATCH (subscribe/unsubscribe), TPV (from PositionReport), SKY (from GPS/GLONASS measurement reports), and minimal DEVICES / POLL stubs for client compatibility. TPV.mode: 1 = no fix, 2 = 2D, 3 = 3D (Kalman).

Acknowledgements

Reverse-engineering of Qualcomm DIAG GNSS log records was pioneered by projects including openpilot's qcomgpsd, QCSuper, and SCAT. diaggpsd relies on struct definitions re-derived clean-room in the diaggrok library and stands on the documentation those projects contributed to the community.

License

Apache-2.0. See LICENSE and NOTICE.

Metadata

Release files for diaggpsd 2026.11.8

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for diaggpsd 2026.11.8
File Size Uploaded Provenance Actions
diaggpsd-2026.11.8.tar.gz 106.7 kB Without provenance Details for diaggpsd-2026.11.8.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for diaggpsd 2026.11.8
File Interpreter ABI Platform Actions
diaggpsd-2026.11.8-py3-none-any.whl Python 3 none any Details for diaggpsd-2026.11.8-py3-none-any.whl

Total release size: 188.7 kB

Release files / diaggpsd-2026.11.8.tar.gz

Download URL diaggpsd-2026.11.8.tar.gz
Size 106.7 kB
Tags Source
SHA-256 checksum
How to use checksums
a4d48a44d68e07f23e7db9e1915da50a1b4342217d45b8276747c57f369634a3
BLAKE2b-256 checksum
How to use checksums
9d6906d734e3133a8a1338ca9ba0d5de1b9639b3208f6b05e955c2a147439323
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Release files / diaggpsd-2026.11.8-py3-none-any.whl

Download URL diaggpsd-2026.11.8-py3-none-any.whl
Size 82.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f42cd4baa67060e0cc1d7c12c3e7a99126b0db4fb7c1ef65734672a680e40366
BLAKE2b-256 checksum
How to use checksums
98b0c84670c2a3ca96903e2d44a1541169f7cfb280a6e519aa4e5b6ffc86f6ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.10

Security for diaggpsd 2026.11.8

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page