12 min read
What protocol decoding actually involves
Why every GPS manufacturer speaks a different binary dialect, what a decoder has to survive, and how to tell whether a device will work before you buy a container of them.
There is no standard, and there is not going to be one
GPS trackers do not speak a common protocol. Each manufacturer defines its own binary framing, its own handshake, its own field layout, and its own idea of what an "event" is. A Queclink frame and a Concox frame have almost nothing in common beyond both containing a latitude somewhere.
This is the single fact that determines the shape of every tracking platform. Everything downstream — the map, the alerts, the reports — can be written once. The layer that talks to hardware has to be written again for every family you support.
What a decoder has to handle
A decoder is not a parser you write once and forget:
- Framing — finding message boundaries in a byte stream that arrives in arbitrary chunks.
- The login handshake — most devices identify themselves once, then send positions with no further identification.
- Acknowledgements — many protocols retransmit until acknowledged, and getting the ack wrong produces duplicate positions rather than an error.
- Undocumented variants — the same model with different firmware sending a field the datasheet does not mention.
- Nonsense — devices report impossible coordinates, timestamps from 1970, and speeds no vehicle achieves. A decoder that trusts its input will corrupt the history.
What that looks like in practice
Nexara maintains seven decoders across five manufacturers. They are not evenly sized: the dedicated implementation for one Queclink device family is over 1,400 lines, while a well-behaved protocol needs closer to 300.
The difference is not code quality. It is how many firmware variants that family has shipped, and how many of them are still in the field reporting.
The size of a decoder is a good proxy for how long that hardware has been in production. Old, popular families are the messiest.
What "normalized" actually means
The point of decoding is not to parse a frame. It is to make every manufacturer produce the same document, so the application above never learns which vendor sent what.
On Nexara a position looks the same whether it came from a Queclink unit over TCP or an AsiaTelco unit over UDP:
- deviceId — the platform’s own identifier, stable across hardware replacement.
- imei — the hardware identity, kept because support conversations happen in IMEIs.
- timestamp — always UTC ISO-8601, whatever the device reported.
- location — GeoJSON Point, longitude first, because that is what spatial queries expect.
- speed, ignition, batteryLevel — typed, with absent rather than zero when the device did not report them.
Absent is not zero
The last point in that list causes more downstream bugs than any framing question. A device that does not report ignition is not a device reporting ignition off, and a decoder that defaults the field to false will produce alert rules that fire on hardware which has no ignition wire.
The same applies to battery, speed and heading. Every field a protocol does not carry must decode to absent, and every consumer must handle absent. This is tedious and it is the difference between a platform that can carry mixed hardware and one that quietly lies about half of it.
Test with captures, not with devices
A decoder tested against a live unit on a desk is tested against one firmware, one configuration and one network. The bugs live elsewhere.
The workable approach is to keep raw packet captures as fixtures — the real bytes a real device sent, including the malformed ones — and run the decoder against them on every change. When a firmware revision breaks something, the capture that exposed it becomes a permanent test rather than a memory.
This is also what makes a support claim checkable. "We decode this family" means "these captures pass", which is a statement someone can verify, unlike a logo on a page.
Keep the packets that broke you. A decoder’s test suite should read like a history of every device that misbehaved.
The configurable alternative
Not every protocol needs bespoke code. Where framing and field offsets are regular, a protocol can be described as data — a configuration rather than a program — and decoded by a generic engine. That is how a platform adds a device family without shipping a release.
It does not work for everything. Protocols with stateful handshakes or conditional field layouts still need real code. But it moves a good proportion of the long tail from "engineering project" to "configuration".
What is actually inside a frame
Without reproducing any manufacturer’s specification, the general shape is consistent enough to describe. A typical position frame carries a start marker, a length, a message type, a device or session identifier, a timestamp, a coordinate pair, speed and heading, a status bitfield, and a checksum.
The difficulty is that almost none of those are in the same place, the same size, or the same units between manufacturers. Coordinates may be signed integers scaled by a constant, or degrees-minutes, or a packed binary format. Timestamps may be UTC, device-local, or seconds since a manufacturer-specific epoch. The status bitfield is where each vendor puts everything they could not fit elsewhere, and it is rarely documented completely.
A normalized platform absorbs that variation once, so the application above it sees one shape regardless of which hardware produced it.
Why both TCP and UDP matter
Trackers use both, sometimes the same model depending on configuration. TCP gives ordered delivery and a connection the server can hold open to send commands back. UDP costs less battery and less cellular data, which matters for asset trackers reporting a few times a day on a small battery.
They are not interchangeable from the platform side. A UDP listener has no connection to associate a packet with, so device identity has to come from the payload on every message, and there is no transport-level acknowledgement to rely on. Supporting both means two ingestion paths that converge on the same decoder.
Decoding is only half of it
Protocols run both ways, and the return path gets scoped last. Sending a command to a device — change the reporting interval, request a position now, move to a new server, trigger an output — is its own protocol surface with its own framing and acknowledgement semantics.
It is harder than reading, for three reasons. The device has to be reachable, which for a TCP device means holding the connection open and for a UDP device means waiting until it speaks first. The acknowledgement may arrive as an ordinary status message minutes later rather than as a reply. And a command that is silently dropped is indistinguishable from one that succeeded and did nothing.
That is why command delivery has to be modelled as a queue with state — sent, acknowledged, expired — rather than as a function call. Anything simpler produces an interface where a button was pressed and nobody can say what happened.
How to evaluate a platform’s protocol claims
Compatibility pages are marketing surfaces. A logo grid tells you which manufacturers a vendor would like to support. Useful questions instead:
- Is there a decoder for this specific family, running in production, today?
- Which transports — TCP, UDP, or both?
- What happens to a message the decoder does not recognise: dropped silently, or captured for inspection?
- Can you send a packet capture and get a definitive answer before buying hardware?
How to check before you commit
Before buying a container of hardware, get a packet capture from a single unit reporting to any endpoint you control. That capture answers the question definitively, and it takes an afternoon.
Verified support for a device family is a stronger signal than a logo on a compatibility page. Ask whether a decoder exists and is in production, not whether the manufacturer is "supported".
