# 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.ts` exporting `codec16Handler: CodecDataHandler` with `codec_id: 0x10`. - Test file `test/codec16.test.ts` with 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 `Position` field, then `__event` should also become typed. Recommendation: keep them in `attributes` for 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 type `0x05`) 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 `3` for Generation Type: spec says reserved. Decision: log a `debug` if observed; do not reject. We do not police reserved values that don't break parsing. ## Done (Fill in once complete.)