closed TG-1; git was inited;
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
# ais_hub
|
||||
|
||||
Central AIS/GPS/radio telemetry aggregation service for embedded Linux.
|
||||
|
||||
`ais_hub` is a Python 3.11 asyncio service. It ingests raw AIS (UDP), GPS
|
||||
(UART) and radio-telemetry (UDP) streams, reassembles multi-fragment AIS
|
||||
sentences, maintains a single authoritative in-memory state keyed by MMSI,
|
||||
writes history to SQLite (WAL + retention), and publishes data to external
|
||||
processes (BLE server, web UI, SPI bridge) via stable interfaces.
|
||||
|
||||
No BLE, web UI or MCU code lives here; those are separate processes that
|
||||
consume the interfaces below.
|
||||
|
||||
## Interfaces
|
||||
|
||||
Полный JSON-контракт с примерами для фронтенда — в **[docs/API.md](docs/API.md)**.
|
||||
Короткая сводка ниже.
|
||||
|
||||
### HTTP (REST, `GET` unless noted)
|
||||
|
||||
| Path | Purpose |
|
||||
| --- | --- |
|
||||
| `/api/v1/health` | Liveness + uptime + version. |
|
||||
| `/api/v1/stats` | Counters, gauges, uptime. |
|
||||
| `/api/v1/ownship` | Latest GPS fix. |
|
||||
| `/api/v1/vessels` | All merged targets. Query: `since`, `limit`. |
|
||||
| `/api/v1/vessels/{mmsi}` | One merged target. |
|
||||
| `/api/v1/base_stations` | AIS base station snapshots. |
|
||||
| `/api/v1/atons` | AtoN snapshots. |
|
||||
| `/api/v1/slots` | TDMA slot occupancy + per-slot detail from AIS-catcher. |
|
||||
| `/api/v1/radio` | Latest RSSI pair (channel A/B) from AIS-catcher. |
|
||||
| `/api/v1/logs` | **Service logs tail**, from in-memory ring. Query: `level`, `limit`, `since`. |
|
||||
| `/api/v1/nmea/tail` | **Raw NMEA tail** (separate from `/logs`). Query: `source=ais\|gps\|radio`, `limit`. |
|
||||
| `/api/v1/history/positions` | AIS dynamic history. Query: `mmsi`, `from`, `to`, `limit`. |
|
||||
| `POST /api/v1/tx` | Inject NMEA sentence(s) into the TX outbox. Body: `{"payload": "!AIVDM,..."}` or `{"nmea": ["...", "..."]}`. |
|
||||
|
||||
### WebSocket
|
||||
|
||||
- `GET /ws` — on connect, clients receive a full `state.snapshot` event
|
||||
(ownship, vessels, base_stations, atons, slots, stats). Then a live
|
||||
stream of `Event` envelopes:
|
||||
- `ownship.update`, `target.update`, `base_station.update`,
|
||||
`aton.update`, `radio.update`, `slots.update`, `slots.detail`,
|
||||
`signal.update`, `stats.update`.
|
||||
- Each client has its own bounded queue; slow consumers drop oldest
|
||||
events and the `ws_dropped` counter increments.
|
||||
|
||||
### Local UDP
|
||||
|
||||
| Endpoint | Direction | Contract |
|
||||
| --- | --- | --- |
|
||||
| `127.0.0.1:7001` | ais_hub → external | JSON `Event`, one event per datagram, dropped if > `max_datagram_bytes`. |
|
||||
| `127.0.0.1:6007` | ais_hub → SPI bridge | Raw NMEA fan-out, one sentence per datagram (`<line>\r\n`). Untouched from ingest. |
|
||||
| `127.0.0.1:6010` | ais_hub → SPI bridge | **TX publish outbox**. One NMEA sentence per datagram. Fed by `POST /api/v1/tx` and internal emitters. |
|
||||
|
||||
ais_hub does **not** listen on `:6010`; it is publish-only.
|
||||
|
||||
## Layout
|
||||
|
||||
- `src/ais_hub/ingest/` — AIS UDP / GPS UART / radio UDP listeners, raw tap.
|
||||
- `src/ais_hub/parser/` — NMEA utils, AIS multi-fragment assembler, GPS,
|
||||
radio telemetry parsers.
|
||||
- `src/ais_hub/core/` — domain models, in-memory state, eager per-MMSI
|
||||
merge, stats counters, internal pub/sub bus.
|
||||
- `src/ais_hub/storage/` — aiosqlite DB (WAL), batched writer with retry,
|
||||
retention cleanup, read queries for REST.
|
||||
- `src/ais_hub/publish/` — aiohttp REST + WS server, UDP publishers,
|
||||
bounded queue helpers with drop-oldest policy.
|
||||
- `src/ais_hub/app.py` — orchestrator (supervisors, graceful shutdown).
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
Runtime dependencies: `aiohttp`, `pyais`, `pyserial-asyncio`, `aiosqlite`,
|
||||
`PyYAML`.
|
||||
|
||||
## Run
|
||||
|
||||
```bash
|
||||
ais_hub --config config/config.example.yaml
|
||||
```
|
||||
|
||||
### Environment variable overrides
|
||||
|
||||
Env vars override any YAML value. Prefix `AIS_HUB_`, level separator
|
||||
`__`, upper-case. Examples:
|
||||
|
||||
```bash
|
||||
AIS_HUB_INGEST__AIS_UDP__PORT=4011
|
||||
AIS_HUB_STORAGE__PATH=/data/ais_hub.db
|
||||
AIS_HUB_STORAGE__STORE_RAW_NMEA=true
|
||||
AIS_HUB_LOGGING__LEVEL=DEBUG
|
||||
```
|
||||
|
||||
## Deploy on embedded Linux (systemd)
|
||||
|
||||
1. Create user and directories:
|
||||
|
||||
```bash
|
||||
useradd -r -s /sbin/nologin ais_hub
|
||||
install -d -o ais_hub -g ais_hub /var/lib/ais_hub /var/log/ais_hub /etc/ais_hub
|
||||
```
|
||||
|
||||
2. Install package (system-wide or in a venv the unit points at):
|
||||
|
||||
```bash
|
||||
pip install .
|
||||
```
|
||||
|
||||
3. Copy config:
|
||||
|
||||
```bash
|
||||
install -m 0644 config/config.example.yaml /etc/ais_hub/config.yaml
|
||||
```
|
||||
|
||||
4. Install unit and enable:
|
||||
|
||||
```bash
|
||||
install -m 0644 systemd/ais_hub.service /etc/systemd/system/
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now ais_hub
|
||||
journalctl -u ais_hub -f
|
||||
```
|
||||
|
||||
For UART access, add the `ais_hub` user to the serial/dialout group or
|
||||
grant access via a udev rule on `/dev/ttyS1`.
|
||||
|
||||
## Resilience notes
|
||||
|
||||
- Each subsystem runs under a supervisor task: on exception it is logged
|
||||
and restarted with exponential backoff; the process itself never dies
|
||||
because of a single subsystem failure.
|
||||
- All inter-subsystem queues are bounded with drop-oldest. Per-channel
|
||||
drop counters are exposed via `/api/v1/stats`:
|
||||
- `parser_in_dropped`, `udp_nmea_dropped`,
|
||||
- `storage_in_dropped`, `tx_outbox_dropped`,
|
||||
- `bus_dropped`, `ws_dropped`.
|
||||
- SQLite transient errors are retried up to 3 times with exponential
|
||||
backoff; on persistent failure the batch is dropped, the writer keeps
|
||||
draining the queue, and REST history may return an error while the
|
||||
rest of the service continues to serve live data.
|
||||
- Multi-fragment AIS is keyed by
|
||||
`(source_tag, talker, channel, seq_id, total_fragments)`. Fragment
|
||||
ordering is strictly validated; any deviation resets the per-key
|
||||
buffer and increments `ais_fragment_errors`. Stale buffers are GC'd
|
||||
after 60 s (`ais_fragment_timeouts`).
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
pip install -e .[dev]
|
||||
pytest
|
||||
```
|
||||
Reference in New Issue
Block a user