Add Phase 1 and Phase 2 planning documents
ROADMAP plus granular task files per phase. Phase 1 (12 tasks + 1.13 device authority) covers Codec 8/8E/16 telemetry ingestion; Phase 2 (6 tasks) covers Codec 12/14 outbound commands; Phase 3 enumerates deferred items.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
# Task 1.6 — Codec 8 Extended parser
|
||||
|
||||
**Phase:** 1 — Inbound telemetry
|
||||
**Status:** ⬜ Not started
|
||||
**Depends on:** 1.4, 1.5 (shared GPS Element / timestamp helpers), 1.9
|
||||
**Wiki refs:** `docs/wiki/concepts/avl-data-format.md` § Codec 8 Extended, `docs/wiki/sources/teltonika-data-sending-protocols.md` § Codec 8 Extended
|
||||
|
||||
## Goal
|
||||
|
||||
Parse Codec 8 Extended (`0x8E`) AVL data bodies into `Position` records, including the **NX variable-length IO section** that does not exist in Codecs 8 or 16.
|
||||
|
||||
## Deliverables
|
||||
|
||||
- `src/adapters/teltonika/codec/data/codec8e.ts` exporting `codec8eHandler: CodecDataHandler` with `codec_id: 0x8E`.
|
||||
- Test file `test/codec8e.test.ts` with the canonical doc example plus at least two synthetic fixtures: one with NX entries, one with mixed N1/N2/N4/N8/NX.
|
||||
|
||||
## Specification
|
||||
|
||||
### Differences from Codec 8
|
||||
|
||||
| Field | Codec 8 | Codec 8 Extended |
|
||||
|-------|---------|------------------|
|
||||
| Codec ID | `0x08` | `0x8E` |
|
||||
| Event IO ID width | 1B | 2B |
|
||||
| N total / N* counts | 1B | **2B** |
|
||||
| IO ID width | 1B | 2B |
|
||||
| Value widths | 1/2/4/8B | 1/2/4/8B (same) |
|
||||
| Variable-length IO (NX) | — | **Yes** |
|
||||
|
||||
The fixed AVL fields (timestamp, priority, GPS element 15B) are identical to Codec 8.
|
||||
|
||||
### IO Element layout (Codec 8E)
|
||||
|
||||
```
|
||||
[Event IO ID 2B]
|
||||
[N total 2B]
|
||||
[N1 2B] then N1 × ([IO ID 2B][Value 1B])
|
||||
[N2 2B] then N2 × ([IO ID 2B][Value 2B])
|
||||
[N4 2B] then N4 × ([IO ID 2B][Value 4B])
|
||||
[N8 2B] then N8 × ([IO ID 2B][Value 8B])
|
||||
[NX 2B] then NX × ([IO ID 2B][Length 2B][Value <Length> bytes]) ← unique to 8E
|
||||
```
|
||||
|
||||
### NX section — the load-bearing complication
|
||||
|
||||
The NX section is the most error-prone part of Codec 8E. Each entry self-describes:
|
||||
|
||||
- 2 bytes IO ID.
|
||||
- 2 bytes length (unsigned big-endian).
|
||||
- `length` bytes of raw value.
|
||||
|
||||
Store NX values as **`Buffer`** (not number/bigint) — they may be ICCID-class data, BLE sensor payloads, or similar binary content. The Processor decodes them per model.
|
||||
|
||||
```ts
|
||||
attributes[String(ioId)] = buf.subarray(offset, offset + length); // copy via .subarray; treat as Buffer
|
||||
```
|
||||
|
||||
**Common bug:** off-by-one in the length field's endianness or width. Verify with a fixture that has at least one NX entry whose length value spans both bytes (e.g. 256+ bytes).
|
||||
|
||||
**Common bug 2:** mishandling NX length 0. Permitted by the spec; treat as a 0-byte Buffer.
|
||||
|
||||
### Cursor invariant
|
||||
|
||||
Same as Codec 8: after parsing all N records and the trailing N2 byte, the cursor must equal the body's last byte. Mismatch = parser bug; throw with offset details.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] Canonical doc example (one record with N1=1, N2=1, N4=1, N8=2, NX=0) parses correctly. Note: the doc's NX section count is `00 00`, so this fixture covers the "NX present but empty" path.
|
||||
- [ ] At least one synthetic fixture has NX > 0 with mixed lengths (e.g. 1B, 8B, 64B values).
|
||||
- [ ] At least one synthetic fixture has an NX entry with `length = 0`.
|
||||
- [ ] At least one synthetic fixture has an NX entry with `length` requiring full 16 bits (≥ 256B).
|
||||
- [ ] All NX values land in `attributes` as `Buffer` instances; non-NX values land as `number` or `bigint` per width.
|
||||
|
||||
## Risks / open questions
|
||||
|
||||
- Maximum total record size remains 255 bytes per the spec. NX with large values can push this — verify the per-record size guard.
|
||||
- Memory pressure: storing many `Buffer` instances per record could add up. Use `Buffer.subarray` (zero-copy view) rather than `Buffer.from(slice)` (copy). Confirm that downstream consumers (the publisher, task 1.8) handle the view semantics correctly — they should be safe because the underlying frame buffer is held until publish completes.
|
||||
|
||||
## Done
|
||||
|
||||
(Fill in once complete.)
|
||||
Reference in New Issue
Block a user