--- title: AVL Data Format (Teltonika canonical) type: concept created: 2026-04-30 updated: 2026-04-30 sources: [teltonika-data-sending-protocols, teltonika-ingestion-architecture] tags: [teltonika, protocol, parser, data-model] --- # AVL Data Format The canonical Teltonika AVL packet structure across codecs 8, 8E, and 16. This is the byte-level reference the [[teltonika]] adapter implements. See [[teltonika-data-sending-protocols]] for the official source and [[codec-dispatch]] for how the codec ID byte selects between the three formats. ## Outer envelope (TCP) ``` ┌────────────────┬──────────────────┬──────────┬────┬──────────┬────┬───────┐ │ Preamble │ Data Field Length│ Codec ID │ N1 │ AVL Data │ N2 │ CRC-16│ │ 4B = 0x00000000│ 4B │ 1B │ 1B │ X bytes │ 1B │ 4B │ └────────────────┴──────────────────┴──────────┴────┴──────────┴────┴───────┘ ``` - **Data Field Length** is computed from the start of `Codec ID` through `N2` (not the whole packet). - **CRC-16/IBM** is computed over the same range. Lower 2 bytes of the 4-byte CRC field carry the value; upper 2 bytes are zero. - **N1 must equal N2** — record count repeated for integrity. - Server ACK is a **4-byte big-endian integer** equal to the number of records accepted. Mismatched count → device retransmits. ## AVL record (one of N records) ``` [Timestamp 8B][Priority 1B][GPS Element 15B][IO Element X B] ``` - **Timestamp**: UNIX milliseconds since epoch (UTC), big-endian. - **Priority**: enum — `0` Low, `1` High, `2` Panic. ### GPS Element (15B) ``` [Longitude 4B][Latitude 4B][Altitude 2B][Angle 2B][Satellites 1B][Speed 2B] ``` - **Lat/Lon**: signed 32-bit integer = `((d + m/60 + s/3600 + ms/3600000) × 10⁷)`, where d/m/s/ms are degrees/minutes/seconds/milliseconds. **Two's complement** — first bit = sign (0 positive, 1 negative). - **Altitude**: meters above sea level, signed 16-bit. - **Angle**: heading 0–360°, unsigned 16-bit. - **Satellites**: count of satellites in use, unsigned 8-bit. - **Speed**: km/h, unsigned 16-bit. **`0x0000` when GPS data is invalid** — distinct from "stationary." ## IO Element — codec-specific The IO Element layout is where the three codecs diverge. The parser must dispatch on codec ID to read these correctly. ### Codec 8 (`0x08`) — 1-byte everything ``` [Event IO ID 1B] [N total 1B] [N1 1B] [IO ID 1B][Value 1B] × N1 [N2 1B] [IO ID 1B][Value 2B] × N2 [N4 1B] [IO ID 1B][Value 4B] × N4 [N8 1B] [IO ID 1B][Value 8B] × N8 ``` ### Codec 8 Extended (`0x8E`) — 2-byte fields + variable-length ``` [Event IO ID 2B] [N total 2B] [N1 2B] [IO ID 2B][Value 1B] × N1 [N2 2B] [IO ID 2B][Value 2B] × N2 [N4 2B] [IO ID 2B][Value 4B] × N4 [N8 2B] [IO ID 2B][Value 8B] × N8 [NX 2B] [IO ID 2B][Length 2B][Value B] × NX ← variable-length section ``` The **NX section** is unique to 8E. Carries arbitrary-length values (e.g. ICCID-class data, BLE payloads). Each entry self-describes its length. The parser must be length-aware here — getting it wrong silently corrupts subsequent records. ### Codec 16 (`0x10`) — Generation Type, mixed widths ``` [Event IO ID 2B] [Generation Type 1B] ← unique to Codec 16 [N total 1B] [N1 1B] [IO ID 2B][Value 1B] × N1 [N2 1B] [IO ID 2B][Value 2B] × N2 [N4 1B] [IO ID 2B][Value 4B] × N4 [N8 1B] [IO ID 2B][Value 8B] × N8 ``` - **No NX section** — Codec 16 does not include variable-size IO elements. - **Generation Type** values: `0`=On Exit, `1`=On Entrance, `2`=On Both, `3`=Reserved, `4`=Hysteresis, `5`=On Change, `6`=Eventual, `7`=Periodical. - Codec 16 is the channel for AVL IDs > 255 on FMB630/FM63XY. ### Side-by-side | | Codec 8 | Codec 8 Extended | Codec 16 | |---|---------|------------------|----------| | Codec ID | 0x08 | 0x8E | 0x10 | | Event IO ID width | 1B | 2B | 2B | | N total / Nk count widths | 1B / 1B | 2B / 2B | 1B / 1B | | IO ID width | 1B | 2B | 2B | | Generation Type | — | — | 1B | | Variable-length IO (NX) | — | yes | — | | AVL IDs > 255 supported | no | yes | yes | ## Size limits - **Minimum AVL record**: 45 bytes (all IO elements disabled). - **Maximum AVL record**: 255 bytes. - **Maximum AVL packet**: 512 bytes for FMB640/FMB641/FMC640/FMM640; **1280 bytes for other devices**. The parser should treat oversized packets as malformed (drop the connection per the [[codec-dispatch]] unknown-codec policy — though this is a different malformation, the same loud-failure principle applies). ## UDP envelope When the device runs over UDP instead of TCP: ``` [UDP Channel Header 5B] [AVL Packet Header (1+2+15)B] [AVL Data Array — same as TCP] ``` - **UDP Channel Header**: `Length 2B + Packet ID 2B + Not Usable Byte 1B`. - **AVL Packet Header**: `AVL Packet ID 1B + IMEI Length 2B = 0x000F + IMEI 15B`. - Server ACK over UDP is short: `[UDP Channel Header 5B][AVL Packet ID 1B][Number Accepted 1B]`. The AVL Data Array (Codec ID byte + records + N2) is identical to TCP. **The Phase 1 implementation is TCP-only; UDP is documented here for completeness and future evaluation.** ## Mapping to [[position-record]] The parser writes: - `device_id` ← IMEI from the handshake (or from the UDP AVL Packet Header). - `timestamp` ← AVL record `Timestamp` (UNIX ms → `Date`). - `latitude`, `longitude`, `altitude`, `angle`, `speed`, `satellites` ← GPS Element fields, with two's-complement decoding for lat/lon and `Speed === 0x0000 ⇒ "GPS invalid"` retained as-is (the Processor decides how to surface it). - `priority` ← AVL record `Priority` (0/1/2). - `attributes` ← every IO element from N1/N2/N4/N8 (and NX for Codec 8E), keyed by **numeric IO ID as string**, value as `number | bigint | Buffer` per IO width. See [[io-element-bag]] for why naming/units stay out of the parser. > Note: Codec 16's `Generation Type` and 8E's NX `Length` are not currently in the [[position-record]] shape. Generation Type is codec-defined (not model-defined) and may deserve a typed field — flagged as an open question on the source page.