Files
docs/wiki/sources/teltonika-ingestion-architecture.md
T
julian 22b1b069df Bootstrap LLM-maintained wiki with TRM architecture knowledge
Initialize CLAUDE.md schema, index, and log; ingest three architecture
sources (system overview, Teltonika ingestion design, official Teltonika
data-sending protocols) into 7 entity pages, 8 concept pages, and 3
source pages with wikilink cross-references.
2026-04-30 13:20:17 +02:00

64 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Teltonika Ingestion — Architecture Reference
type: source
created: 2026-04-30
updated: 2026-04-30
sources: [teltonika-ingestion-architecture]
tags: [teltonika, ingestion, protocol, codec]
source_path: raw/teltonika-ingestion-architecture.md
source_kind: note
---
# Teltonika Ingestion — Architecture Reference
## TL;DR
Design for the Teltonika protocol adapter inside the [[tcp-ingestion]] service. Goal: ingest telemetry from any Teltonika device — including unseen models — without parser changes, by leaning on the protocol's self-description (codec ID announces framing; IO bag carries opaque model-specific telemetry). Phase 1 implements data-sending codecs **8, 8E, 16**; Phase 2 will add command codecs **12, 13, 14**. Six design principles govern the adapter; the codec dispatch is a registry built so Phase 2 is additive, not a rewrite.
## Key claims
- **Project lives at `tcp-ingestion/`** — a single Node.js/TypeScript project containing the vendor-agnostic shell (`src/core/`) and per-vendor adapters (`src/adapters/`). Today the only adapter is Teltonika.
- **Layout rules**: `core/` never imports from `adapters/`; adapters never import from each other; the Teltonika folder is self-contained so it can be lifted into its own service via `git mv` later.
- **Phase 1 scope** = data-sending codecs 8, 8E, 16 (covers the deployed Teltonika telemetry fleet).
- **Phase 2 scope** = GPRS command codecs 12, 13, 14 (server → device). Deferred because they are a **distinct feature** with different security implications, not an incremental codec. See [[phase-2-commands]].
- **The parser is model-agnostic** — what matters is the codec ID byte, not the device model. Fixed AVL fields (timestamp, lat/lon/alt, angle, satellites, speed) are codec-defined; only the IO element bag varies, and it's passed through verbatim. New Teltonika models work on day one.
- **`Position` shape** is the boundary contract — `device_id`, `timestamp`, `lat`, `lon`, `alt`, `angle`, `speed`, `satellites`, `priority`, `attributes` (raw IO map keyed by numeric ID as string). See [[position-record]].
- **Six design principles** (priority order):
1. Implement Codec 8, 8E, 16 — the closed set.
2. Defer Codec 12, 13, 14.
3. Pass the IO map through unchanged — naming/interpretation is a Processor concern.
4. Log unknown codec IDs and drop the connection (loud failure > silent corruption).
5. Validate CRC-16/IBM; NACK (no ACK) on mismatch — devices retransmit.
6. Maintain a fixture suite of real packet captures + Teltonika-doc captures + synthetic edge cases.
- **Codec dispatch is a flat registry** keyed on codec ID — not an inheritance hierarchy. Codec parsers are independent because their record shapes diverge. See [[codec-dispatch]].
- **Phase 1 forward-compatibility seams** for Phase 2:
- Codec dispatch is a registry, not a switch (§8.1).
- Session owns the socket; handlers borrow write access via a `respond(bytes)` callback (§8.2).
- Per-device state is local to the socket; no shared registry today. Phase 2 adds the connection registry alongside, not woven through (§8.3).
- **Phase 2 outbound command flow**: `SPA → Directus → Redis Streams → Ingestion → device`. Directus enforces single auth surface; commands are persisted as rows in a `commands` collection before being routed; Redis is transport, not source of truth.
- **Phase 2 connection registry**: Redis hash `connections:registry` mapping `imei → instance_id`. Per-instance heartbeat keys (`SET instance:heartbeat:{instance_id} EX 90`) plus a registry janitor handle crash recovery — Redis hashes don't support per-field TTL.
- **Phase 2 command correlation**: Teltonika's command codecs carry no correlation ID, so the protocol assumes one outstanding command per connection. The Ingestion service enforces this via a per-socket write queue.
## TCP session lifecycle (Phase 1 happy path)
1. Device connects to the Teltonika port.
2. IMEI handshake: device sends 2-byte length + ASCII IMEI; server responds `0x01` to accept.
3. AVL data loop: read preamble (4×0x00) + length + payload + 4-byte CRC; validate CRC; dispatch on codec ID byte; emit `Position` records to Redis; ACK with 4-byte big-endian record count.
4. Session ends on disconnect; no state preserved across sessions.
## Notable quotes
> "Ingest telemetry from any Teltonika device, including models we have never seen, without code changes to the parser."
> "Naming and interpreting IO elements is explicitly a Processor concern, driven by per-model configuration."
> "A loud failure (the device reconnects, fails again, shows up in logs) is strictly better than a quiet corruption."
> "A fixture suite is not optional infrastructure. It is the only place the parser's correctness is actually verified."
## Open questions / follow-ups
- Per-model IO dictionary: where does it live downstream — Directus collection, static config in the Processor, or both?
- Phase 2 timing: no commitment given. Driver will be the first real need to issue commands (configuration, remote actions).
- Pending-command sweeper cadence (30s) and command default TTL (5 min) — operational defaults, may want tuning once command volume is real.