Files
docs/wiki/concepts/codec-dispatch.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

76 lines
3.6 KiB
Markdown

---
title: Codec Dispatch (Registry)
type: concept
created: 2026-04-30
updated: 2026-04-30
sources: [teltonika-ingestion-architecture, teltonika-data-sending-protocols]
tags: [teltonika, parser, design-seam]
---
# Codec Dispatch
How the [[teltonika]] adapter routes incoming frames to the right parser, and the seam that makes [[phase-2-commands]] additive instead of a rewrite.
## The mechanism
The codec ID (byte 0 of the AVL data payload) indexes into a **flat registry** of handlers. Each handler implements:
```ts
interface CodecHandler {
codec_id: number
handle(
payload: Buffer,
ctx: { imei: string; publish: (p: Position) => Promise<void> }
): Promise<{ ack_count: number }>
}
```
Phase 1 registers handlers for IDs `0x08`, `0x8E`, and `0x10`. Phase 2 will register additional handlers for `0x0C`, `0x0D`, `0x0E` — these will use a different `ctx` shape (they need to write bytes back, not just `publish`), but the registry shape is identical.
Per-codec directionality matters for the Phase 2 handler shape:
| Codec | ID | Direction | Handler needs |
|-------|-----|-----------|---------------|
| 8 / 8E / 16 | `0x08` / `0x8E` / `0x10` | device → server | `publish(Position)` only |
| 12 | `0x0C` | bidirectional | `respond(bytes)` for outbound + parse responses |
| 13 | `0x0D` | device → server only | parse-only (no `respond`) |
| 14 | `0x0E` | bidirectional | `respond(bytes)` + ACK type `0x06` / nACK type `0x11` |
| 15 | `0x0F` | device → server only | out of scope (FMX6 RS232 only) |
Codec 13 and 15, despite living in the GPRS-message family, are *one-way* — handlers for them never write to the socket. This matters for handler-shape design: the Phase 2 outbound family is not "all command codecs," it's specifically `0x0C` and `0x0E`.
## Why a registry, not a switch
- Adding a new codec is a registration, not a code change in dispatch logic.
- Phase 2 command codecs slot in alongside Phase 1 data codecs without modifying Phase 1 paths.
- Codec parsers are **independent** — there is no shared base class. Their record shapes diverge in ways abstraction would obscure rather than help.
## The "session owns the socket; handler borrows it" rule
Phase 1 handlers receive payload + a `publish(Position)` callback and emit records via Redis. They never write to the socket directly — the session loop handles ACKs.
Phase 2 command handlers will need to **write to the socket** (to send commands). They borrow write access through a `respond(bytes: Buffer)` callback added to `ctx` for command codecs. The session retains socket ownership; handlers borrow write access through a narrow interface.
## Unknown codec policy
When the codec ID does not match a registered handler:
- `WARN` log entry with IMEI, offending codec ID, raw header bytes.
- Socket is destroyed.
- No ACK sent.
- No attempt to "skip ahead" or guess record layout.
Reasoning: a Teltonika device sending an unrecognized codec is **misconfigured**, not subtly broken. Silently truncating its data — or worse, mis-parsing — produces records with plausible-looking but wrong coordinates. Loud failure beats quiet corruption.
The `teltonika_unknown_codec_total{codec_id}` counter is the canary for codec coverage drift.
## CRC failure policy
Different from unknown-codec. CRC mismatch = transient transmission issue:
- Frame is **not** ACK'd → device retransmits on next session.
- `WARN` log with IMEI, expected CRC, computed CRC, frame length.
- Connection stays open.
Repeated CRC failures from the same device in a short window indicate a deeper problem (firmware, line quality) — surface via metrics, not just logs.