AdaTrack v1.7: Native Teltonika Support and OBD Dashboard Widgets

  • July 1, 2026
  • AdaTrack Team
AdaTrack v1.7: Native Teltonika Support and OBD Dashboard Widgets

AdaTrack v1.7: Native Teltonika Support and OBD Dashboard Widgets

Published: | Category: Engineering

Teltonika GPS trackers — the FMB920, FMT100, FMB140, and their siblings — are among the most widely deployed vehicle tracking devices in the world. They connect to a server over a persistent TCP session using a proprietary binary framing protocol called Codec8, identify themselves by IMEI rather than a UUID, batch up to 255 AVL records per TCP packet, and demand a per-packet 4-byte acknowledgement before advancing their send buffer. None of those requirements are compatible with AdaTrack's UDP ingestion path. Until now, operators running Teltonika fleets had two choices: run a separate data bridge that forwarded decoded JSON over UDP, or accept that these devices simply could not connect. AdaTrack v1.7 removes both workarounds with a compiled Codec8 plugin that handles the full TCP lifecycle natively, routing every AVL record through the same geofencing, alerting, workflow, and WebSocket pipeline that UDP and MQTT telemetry already use. Alongside it, a new plugin widget framework and the platform's first scripted UI plugin bring OBD-style instrument clusters directly into the dashboard builder and trip replay.

What Codec8 Actually Requires

The gap between the Codec8 wire format and AdaTrack's existing ingestion path is not bridgeable by a decoder script. The differences are structural, not cosmetic.

AdaTrack's UDP format is [DeviceID (16 bytes)][Timestamp (8 bytes)][Payload][HMAC-SHA256 (32 bytes)]. A Teltonika packet looks nothing like that. Each TCP data packet opens with a four-byte zero preamble, a four-byte data field length, a one-byte Codec ID (0x08 for Codec8, 0x8E for Codec8E), a record count, the AVL records themselves, a repeated record count for validation, and finally a four-byte CRC-16/IBM checksum computed over the data field using the reflected polynomial 0xA001. After receiving a packet, the server must respond with a four-byte big-endian count of successfully received records — the device will not advance its internal buffer pointer until that ACK arrives. Miss the ACK and the device retransmits; send the wrong count and the device may replay records already processed.

Authentication is equally incompatible. Teltonika performs an IMEI handshake before any data flows: the device sends a two-byte IMEI length followed by the IMEI ASCII string; the server responds with a single 0x01 byte to accept or 0x00 to reject. There is no shared secret, no HMAC, and no UUID anywhere in that exchange. The platform's HMAC verification layer is simply the wrong tool for this session model.

Multi-record batching adds a third incompatibility. The Goja VM pool that executes JavaScript decoders produces exactly one Telemetry row per invocation. A single Codec8 packet carrying 50 AVL records would require 50 decoder invocations and 50 pipeline executions — each with its own geofence evaluation, alert rule pass, and workflow trigger check. The existing call path has no concept of a batch envelope.

The Codec8 plugin resolves all three gaps within its own process boundary. It owns a standalone TCP listener on port 6868 (configurable), handles the IMEI handshake, parses the binary frame in Go, validates the CRC, and calls host.SubmitPacket() once per AVL record — the same PluginHost method the MQTT broker uses to inject pre-authenticated payloads into PacketProcessor.IngestRawPayload(). From that call site, every Codec8 record is processed by the same processAuthenticated() function as UDP and MQTT traffic. Platform core required zero modifications.

IMEI-to-Device Resolution Without Manual Mapping

Teltonika devices identify themselves by a 15-digit IMEI. AdaTrack identifies devices by a UUID. Bridging those two identity spaces is the plugin's second core responsibility.

The plugin maintains a plugin_codec8_imei_map table, scoped through host.DB() to the plugin's own database namespace. Each row maps an IMEI string (primary key) to an AdaTrack device UUID with a foreign key into the devices table, plus a last_seen_at timestamp updated on every successful data packet. An in-process LRU cache (2,048 entries, five-minute TTL) means the IMEI lookup for a connected device costs zero database round-trips during a normal session — only cache misses and expirations touch PostgreSQL.

Operators do not interact with plugin_codec8_imei_map directly. When creating or editing a device, selecting Codec8 TCP as the transport type exposes an IMEI field in the standard device form. Saving the form writes the mapping to the plugin's table automatically. The same form fetches a passthrough decoder template from GET /api/v1/plugins/codec8/decoder-template and either reuses an existing profile with a matching decoder script or creates a new Teltonika Codec8 device profile on the spot — no profile management step required. For administrators, the plugin admin page at /plugins/codec8/ surfaces all IMEI mappings across all users alongside live server health counters: active connections, total packets received, total records ingested, CRC errors, and server uptime.

Ordering Offline-Buffered Batches Before They Enter the Pipeline

Teltonika devices buffer AVL records internally while they have no network connectivity and flush the full backlog as a series of TCP packets when they reconnect. A device that was offline for two hours might send several hundred records covering that entire period across multiple sequential packets, all arriving within seconds of one another.

The travel log's trip detector is a stateful finite state machine that expects records in chronological order. Feeding it out-of-order records — for example, a reconnection packet interleaved with live telemetry from a different device on the same worker goroutine — can create phantom trip boundaries or miss real ones. More subtly, the Codec8 protocol does not guarantee that records within a single session arrive in strict time order if the device's own internal buffer had write-ordering issues during the outage.

The plugin addresses this with a session accumulator. All AVL records received during a single TCP session are held in memory rather than submitted immediately. Per-packet ACKs are sent to the device as each packet arrives, so the device clears its buffer without waiting for the full flush — the ACK protocol and the submission pipeline are decoupled. When the session ends — either by idle timeout (default 30 seconds, configurable via session_idle_timeout_sec) or TCP disconnect — the accumulator sorts all buffered records by timestamp and submits them to processAuthenticated() in order. The maximum records held in a single session is bounded by max_session_records (default 10,000). Out-of-order records that are discarded rather than accumulated are tracked by the codec8_session_out_of_order_dropped_total Prometheus counter, visible in the Travel Log Grafana dashboard provisioned with the observability stack.

A Plugin Widget Framework With No React in the Plugin

Teltonika devices transmit considerably more than a GPS coordinate. Every AVL record carries a variable-length IO element map — integer-keyed fields encoding engine RPM (IO ID 36), coolant temperature (IO ID 32), external voltage in millivolts (IO ID 66), OBD vehicle speed (IO ID 37), odometer in metres (IO ID 16), satellite count (IO ID 21), ignition state (IO ID 239), and a dozen more OBD-II values depending on the vehicle and firmware configuration. Until v1.7, those values landed in the payload JSONB column as opaque numbers — readable in the raw telemetry panel, but not visualised.

The right approach for a platform built on a plugin architecture is not to hardcode Teltonika-specific rendering in core components. Instead, v1.7 introduces two new scripted host APIs that any Goja plugin can call: adatrack.dashboard.registerWidget and adatrack.replay.registerRenderer.

registerWidget takes a JavaScript object declaring a widget ID, display name, icon, and — critically — a display type chosen from a fixed primitive vocabulary: speedometer, odometer, gauge, thermometer, voltage_meter, signal_bars, indicator, and fault_badge. It also declares a source: a dot-path into the decoded payload JSON (io.66 for external voltage, for example) and an optional transform (divide_by_1000 to convert millivolts to volts). A PluginWidgetRegistry singleton — a mutex-protected map keyed by pluginID/widgetID — stores the declaration in memory. The dashboard API endpoint returns both built-in widget types and registered plugin widgets in the same response, so the frontend's Add Widget panel shows plugin widgets alongside native ones without any special casing.

The rendering primitives themselves are React components in the platform's frontend. The plugin does not write JSX. It declares configuration; the platform renders. If the source IO ID is absent in a given reading — because the vehicle firmware did not include it in that record — the widget displays a dash state rather than erroring. This graceful degradation contract is enforced by the primitive components, not by plugin code.

registerRenderer follows the same pattern for trip replay. A plugin declares a renderer ID, a set of IO IDs it handles, and a display configuration. When the trip replay timeline is scrubbed to a point whose payload contains any of those IO IDs, the replay panel replaces the generic raw JSON view with the registered renderer — automatically, with no user toggle required.

The Codec8 Telemetry UI Plugin: OBD Instruments in Five Widgets

The platform's first scripted UI plugin — codec8-telemetry — is seeded automatically on all deployments that have the Codec8 TCP plugin enabled. It is written entirely in JavaScript and executes in the Goja sandbox, using the two new host APIs to register its declarations at plugin startup.

Five dashboard widgets are registered:

  • Speed — a speedometer primitive bound to OBD vehicle speed (IO ID 37, km/h), with configurable max scale and speed-zone colouring matching the travel log's route gradient.
  • Odometer — an odometer primitive bound to the cumulative odometer counter (IO ID 16), with the raw metre value divided by 1,000 for display in kilometres.
  • Satellite Count — a signal_bars primitive bound to the GPS satellite count field from the GPS element (not IO, but exposed in the same payload structure), showing signal acquisition quality at a glance.
  • Battery Voltage — a voltage_meter primitive bound to external voltage (IO ID 66, millivolts), divided by 1,000 for display in volts, with threshold colouring to highlight low-voltage or alternator-fault conditions.
  • Engine Temperature — a thermometer primitive bound to coolant temperature (IO ID 32, °C), with configurable warning and critical thresholds.

All five appear in the dashboard Add Widget panel under a Codec8 section and behave identically to built-in widgets: draggable, resizable, and included in the responsive breakpoint layout system. They can be placed on any dashboard — not just a device-specific view — allowing operators to build fleet-level instrument panels that aggregate readings across multiple Teltonika devices.

The trip replay telemetry panel is the plugin's second contribution. When a user scrubs through a trip recorded by a Codec8 device, the replay panel detects the presence of known OBD IO IDs in the payload at the cursor position and switches from the default raw JSON table to a structured instrument view. The panel shows — at the playback cursor's exact timestamp — speed, RPM, coolant temperature, engine load, fuel level, voltage, ignition state, active GSM operator, and any active DTC fault codes, all decoded from the IO element map using the same ID reference table the widgets use. Scrubbing forward or backward updates all values in real time as the replay position changes.

Enabling Codec8 Support

The Codec8 TCP plugin requires Standard tier or above and the plugin:codec8 license feature. Enable it from Admin > Plugins > Codec8 TCP. On first enable, the plugin migration creates the plugin_codec8_imei_map table via the platform's standard migration runner. The TCP listener starts on :6868 by default; this can be changed in the plugin configuration panel without restarting the server.

Once the plugin is enabled, registering a Teltonika device takes three steps in the standard device form: set the transport type to Codec8 TCP, enter the device's 15-digit IMEI, and save. The IMEI mapping is written, the passthrough decoder profile is created or reused, and the device is ready to connect. No firewall rules need to change beyond opening port 6868 inbound. Teltonika devices connect using their factory-default TCP server settings — no custom firmware, no AT command reconfiguration.

The codec8-telemetry scripted plugin activates automatically when the Codec8 TCP plugin is enabled, seeding its widget declarations into the registry at startup. Dashboard widgets become available in the Add Widget panel immediately; the trip replay renderer activates the first time a Teltonika trip is opened. Six new Prometheus metrics — codec8_connections_active, codec8_connections_total, codec8_packets_received_total, codec8_records_ingested_total, codec8_crc_errors_total, and codec8_decode_errors_total — are scraped alongside the platform's existing metric set and visualised in the Travel Log Grafana dashboard.

v1.7 Is Available Now

Native Teltonika support, the plugin widget framework, and the Codec8 Telemetry UI plugin are live in the current release. Self-hosted operators can upgrade with adatrack-ctl upgrade. If you are running Teltonika FMB, FMT, or FMC hardware, point your devices at port 6868, register the IMEI in the device form, and telemetry will appear on the live map within the first connected packet. Trip replay ties directly into AdaTrack's automated driver logbook.

Get Started

Key Highlights

  • Real-time Telemetry Ingestion via UDP
  • High Performance Time Series Storage
  • Advanced WebGL Powered Geospatial Visualization
  • Intelligent Geofencing and Alerting Engine

We are committed to providing cutting-edge solutions that help businesses run robust, scalable, and secure IoT systems.