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.post1

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.post1
File Size Uploaded Provenance Actions
diaggpsd-2026.11.8.post1.tar.gz 107.0 kB Without provenance Details for diaggpsd-2026.11.8.post1.tar.gz

Built distribution (wheel)

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

Total release size: 189.2 kB

Release files / diaggpsd-2026.11.8.post1.tar.gz

Download URL diaggpsd-2026.11.8.post1.tar.gz
Size 107.0 kB
Tags Source
SHA-256 checksum
How to use checksums
ab35f63ea900cbda42c12c64a75565cb9cbe0b4b79fb5206b630d68545623920
BLAKE2b-256 checksum
How to use checksums
757eb9e20270061a726e0f30475859ad6fbb456b422d3bf37868a2e1bb9457e1
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.post1-py3-none-any.whl

Download URL diaggpsd-2026.11.8.post1-py3-none-any.whl
Size 82.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e5b4692ba9d40cee7a88fabebf89a3bc1cb0886f5df9e47da869b949ef951e51
BLAKE2b-256 checksum
How to use checksums
d0dcd79fb9042310d3869529cdaa0197ee32ff4e689240e8069ea8d9570cbc60
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.post1

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