Files
julian 90d6a73a60 Sync ROADMAP statuses with landed work; mark 1.10/1.12/1.13 as paused
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).
2026-04-30 16:49:07 +02:00

3.6 KiB
Raw Permalink Blame History

Task 1.6 — Codec 8 Extended parser

Phase: 1 — Inbound telemetry Status: 🟩 Done — landed in commit 381287b 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.

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.)