Files
docs/wiki/concepts/avl-data-format.md
T
julian 22b1b069df Bootstrap LLM-maintained wiki with TRM architecture knowledge
Initialize CLAUDE.md schema, index, and log; ingest three architecture
sources (system overview, Teltonika ingestion design, official Teltonika
data-sending protocols) into 7 entity pages, 8 concept pages, and 3
source pages with wikilink cross-references.
2026-04-30 13:20:17 +02:00

139 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 0360°, 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 <Length>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.