Files
NmeaClient/CLAUDE.md
T
jens 74b949f864 - refactored
- added claude.md
2026-05-24 09:21:26 +02:00

3.6 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this project is

A Python client for u-blox GNSS receivers (NEO-F9P, ZED-X20P) connected over TCP/IP. It receives and parses both NMEA 0183 and UBX binary protocol messages, and can send UBX poll requests to the receivers.

Hardware reference documentation is in readme.md (u-blox datasheets, integration manuals, interface specs, RTK/RTCM references).

Running

python network_backend.py

The entry point in network_backend.py connects to two GNSS receivers, wires up packetizers and message listeners, then polls UBX messages every second.

Architecture

The design follows a listener/talker pipeline pattern:

NetworkBackend (TCP socket per receiver)
    └─> Transceiver (one per receiver, named to match socket)
            ├─> NmeaPacketizer   ──> NmeaListener subclasses (Gga, Gll, Gsa, Gsv, Rmc)
            └─> UbxPacketizer    ──> UbxListener instances (wrapping UbxMsg subclasses)

Core abstractions (msg/):

  • MsgContainer — wraps raw bytes with a source (ATransceiver) and timestamp
  • MsgListener — ABC with on_recv(msg) and is_msg(msg) (default: False); only called when is_msg returns True
  • MsgTalker — holds a list of MsgListener sinks; call_listener iterates them and dispatches

Transceiver (transceiver.py): inherits both ATransceiver (MsgListener) and MsgTalker. Receives raw bytes from the backend via on_recv, wraps them in MsgContainer, and fans out to downstream listeners. Sends bytes back via backend.send.

NetworkBackend (network_backend.py): manages non-blocking TCP sockets via selectors. Each connection has a name, address, associated Transceiver, and outgoing Queue. Call register_xcvr(xcvr) to bind a transceiver to its socket by name match, then connect() + start() to begin the event loop thread.

NMEA parsing (nmea/):

  • NmeaPacketizer: buffers bytes and emits a MsgContainer per line (CR/LF delimited)
  • NmeaListener / AsciiListener: filters by regex on the raw bytes; subclasses override on_recv to parse CSV fields
  • Message classes (nmea/messages/): Gga, Gll, Gsa, Gsv, Rmc — each takes (name, tid) where tid is the talker ID (e.g. 'GN', 'GP'); use '--' to match any talker

UBX parsing (ubx/):

  • ubx.py: frame_create(class, id, data) builds a framed UBX packet with Fletcher checksum; frame_parse / frame_parse_hdr decode received frames
  • UbxPacketizer: buffers bytes, detects sync word 0xB5 0x62, reassembles complete frames by checking the length field and verifying checksum
  • UbxMsg (base class in ubx/messages/msg.py): holds mclass / mid; subclasses implement from_bytes(data) as a classmethod
  • UbxListener: wraps a UbxMsg, matches by class+id in is_msg, parses and prints in on_recv
  • UbxQuery (ubx/query.py): extends UbxListener with a semaphore-based synchronous poll() — sends a UBX poll frame and blocks until the response arrives

Implemented UBX messages (ubx/messages/):

  • nav.py: Sig (0x01/0x43 NAV-SIG), Sat (0x01/0x35 NAV-SAT)
  • rxm.py: RawX (0x02/0x15 RXM-RAWX)

Adding a new UBX message

  1. Create a subclass of UbxMsg with the correct mclass/mid and a from_bytes classmethod
  2. Instantiate UbxListener(YourMsg()) and register it on the UbxPacketizer

Adding a new NMEA sentence

Subclass NmeaListener with the appropriate msg_type string, override on_recv to split on b',' and populate a Msg dataclass, then register on the NmeaPacketizer.