90d6a73a60
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).
85 lines
3.7 KiB
Markdown
85 lines
3.7 KiB
Markdown
# 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.)
|