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.
3.6 KiB
title, type, created, updated, sources, tags
| title | type | created | updated | sources | tags | |||||
|---|---|---|---|---|---|---|---|---|---|---|
| Codec Dispatch (Registry) | concept | 2026-04-30 | 2026-04-30 |
|
|
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:
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:
WARNlog 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.
WARNlog 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.