Tasks 1.1-1.9 marked done with their landing commit SHAs. Tasks 1.10 (observability), 1.12 (production hardening), and 1.13 (device authority) marked paused with explicit resume triggers — pilot deployment on real Teltonika hardware takes priority. Task 1.11 remains as next, in slimmed form for the pilot (no /readyz healthcheck since the metrics endpoint is part of paused 1.10).
3.7 KiB
Task 1.7 — Codec 16 parser
Phase: 1 — Inbound telemetry
Status: 🟩 Done — landed in commit 381287b
Depends on: 1.4, 1.5 (shared helpers), 1.9
Wiki refs: docs/wiki/concepts/avl-data-format.md § Codec 16, docs/wiki/sources/teltonika-data-sending-protocols.md § Codec 16
Goal
Parse Codec 16 (0x10) AVL data bodies into Position records, including the per-record Generation Type byte.
Deliverables
src/adapters/teltonika/codec/data/codec16.tsexportingcodec16Handler: CodecDataHandlerwithcodec_id: 0x10.- Test file
test/codec16.test.tswith the canonical doc example (multi-record) plus at least one synthetic fixture covering each Generation Type value.
Specification
Differences from Codec 8 / Codec 8E
| Field | Codec 8 | Codec 16 | Codec 8E (for contrast) |
|---|---|---|---|
| Codec ID | 0x08 |
0x10 |
0x8E |
| Event IO ID width | 1B | 2B | 2B |
| Generation Type | — | 1B | — |
| N total / N* counts | 1B | 1B | 2B |
| IO ID width | 1B | 2B | 2B |
| Value widths | 1/2/4/8B | 1/2/4/8B | 1/2/4/8B |
| Variable-length IO (NX) | — | — | Yes |
Codec 16 is a "mixed" layout: 2-byte IO IDs (like 8E) but 1-byte counts (like 8), plus the new Generation Type field. This is the trap — implementers who copy from Codec 8E will get the count widths wrong; implementers who copy from Codec 8 will get the IO ID widths wrong. Read the spec carefully and write fixture-driven tests first.
IO Element layout (Codec 16)
[Event IO ID 2B]
[Generation Type 1B] ← unique to Codec 16
[N total 1B]
[N1 1B] then N1 × ([IO ID 2B][Value 1B])
[N2 1B] then N2 × ([IO ID 2B][Value 2B])
[N4 1B] then N4 × ([IO ID 2B][Value 4B])
[N8 1B] then N8 × ([IO ID 2B][Value 8B])
No NX section.
Generation Type
1-byte enum:
| Value | Meaning |
|---|---|
| 0 | On Exit |
| 1 | On Entrance |
| 2 | On Both |
| 3 | Reserved |
| 4 | Hysteresis |
| 5 | On Change |
| 6 | Eventual |
| 7 | Periodical |
Storage decision: store as attributes['__generation_type'] (consistent with the __event convention from task 1.5). Codec 8 and 8E omit this key entirely. Downstream code can pattern-match on its presence.
Open question (carried from task 1.5): if we promote Generation Type to a typed
Positionfield, then__eventshould also become typed. Recommendation: keep them inattributesfor Phase 1; revisit when Processor-side modeling firms up. Flagged in position-record open questions.
AVL ID range
Codec 16 (and 8E) supports IO IDs > 255. The parser treats this transparently — IO IDs are read as 2-byte unsigned values; nothing prevents ioId = 1234. Just confirm no fixture has an off-by-one assumption that breaks for >255.
Acceptance criteria
- Canonical doc example (two records, N1=2, N2=2, codec ID
0x10, generation type0x05) parses correctly with both records' attributes populated. - A synthetic fixture exists for each Generation Type 0–7 (eight fixtures total or one fixture with eight records varying the field).
- At least one synthetic fixture has an IO ID > 255 to verify 2-byte read.
attributes['__generation_type']is set on every Codec 16 position; absent on Codec 8 / 8E positions.
Risks / open questions
- The "mixed widths" trap is real. Pair-review (or have a second LLM agent review) the field-width table before declaring done. Fixture tests catch this if they're built carefully.
- Reserved value
3for Generation Type: spec says reserved. Decision: log adebugif observed; do not reject. We do not police reserved values that don't break parsing.
Done
(Fill in once complete.)