63 lines
3.6 KiB
Markdown
63 lines
3.6 KiB
Markdown
# 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`.
|