# 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 ```bash 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`.