MAVLink telemetry logs and receive records
Two ground-side records of MAVLink traffic: the ground station's .tlog and the Foxborne receiver's JSONL. Both stamp each message when it reached the ground, so both give receive time, never send time.
On this page10
MAVLink traffic from a vehicle can land in two records on the ground. Ground station software writes a telemetry log, the .tlog, and the Foxborne MAVLink receiver at range control writes receiver JSONL. Both are written on the ground as each message arrives, and both say when the ground heard it.
The two records#
| Property | Ground station telemetry log | Receiver JSONL |
|---|---|---|
| File | .tlog | .jsonl |
| Written by | Ground station software | The Foxborne MAVLink receiver |
| What it holds | Every MAVLink packet the ground station logged, whole | One JSON object per message received: its name, its sender and its arrival |
| Time stamp | 8 bytes before each packet: microseconds since the Unix epoch | recv_utc in UTC to the millisecond, and mono_ns on the receiver's monotonic clock |
| Clock | The ground station computer's clock | The receiver's clock, kept on UTC by NTP |
| How it reaches Foxborne | Listed in the File import catalog | The MAVLink receiver source, a storage source or Import files |
Ground station telemetry logs#
Ground stations record .tlog files of the MAVLink traffic they handle. Mission Planner starts one when it connects, and QGroundControl writes one after each flight when that setting is on.
Structure#
A .tlog has no header of its own. It is a run of records to the end of the file, each an 8-byte timestamp followed by one MAVLink packet as it arrived.
| Bytes in the record | Field | Content |
|---|---|---|
| 0 to 7 | Timestamp | A uint64, big endian: microseconds since the Unix epoch, on the ground station's clock |
| 8 to end | Packet | The MAVLink packet as received, from its start marker to its checksum |
The packet's start marker says which MAVLink version follows, and with it where each field sits:
| Packet field | MAVLink 1 | MAVLink 2 |
|---|---|---|
| Start marker, byte 0 | 0xFE | 0xFD |
| Payload length | Byte 1 | Byte 1 |
| Message ID | Byte 5, one byte | Bytes 7 to 9, three bytes |
| Header before the payload | 6 bytes | 10 bytes |
| After the payload | A 2-byte checksum | A 2-byte checksum, then a signature block when the packet is signed |
The timestamp is receive time#
pymavlink, the MAVLink library for Python, shows where the timestamp comes from. It writes each record right after it parses a packet from the link, with the computer's clock at that moment:
msg = self.mav.parse_char(s)
if msg:
if self.logfile and msg.get_type() != 'BAD_DATA' :
usec = int(time.time() * 1.0e6) & ~3
self.logfile.write(struct.pack('>Q', usec) + msg.get_msgbuf())The stamp is therefore the time the ground station logged the packet, on the ground station's clock. It is a receive time, as good as that computer's time synchronization.
pymavlink clears the timestamp's two lowest bits when it writes, and its reader takes those two bits back as a link number. Its stamps land on multiples of 4 µs.
tbuf = self.f.read(8)
(tusec,) = struct.unpack('>Q', tbuf)
t = tusec * 1.0e-6
...
self._link = tusec & 0x3Importing .tlog files#
The console's File import entry, under Integrations and Ingest, lists .tlog among the formats it takes by upload, drop folder or removable media. Each file arrives under a user login with the sender's SHA-256 manifest, and it is checked against that manifest before it is parsed.
Settings, under Import, lists the formats your deployment reads. In the example deployment, File import reads On, 4 formats, and .tlog is not among them. Its Formats on row names PX4 ULog, journalctl JSON, collector JSONL and receiver JSONL.
The Telemetry gap alert, R2 under Alerts, names its signals as vehicle HEARTBEAT arrivals in the ground-station .tlog and the ground receiver log. In the example deployment it reads the range control receive logs. Sortie closeout triage, automation A1, counts a ground-station .tlog among the sources a test card can list.
Receiver JSONL#
The Foxborne MAVLink receiver listens for telemetry on the ground and writes one JSON object per receipt, one receipt per line. Foxborne recognizes the file by the key "recv_utc" in its first 4,096 bytes, and the example dataset's parser is receive 0.4.1.
| Field | What it holds |
|---|---|
recv_utc | The arrival time in UTC, on the receiver's clock |
mono_ns | The arrival time on the receiver's monotonic clock, in nanoseconds |
link | The link the message arrived on |
sysid, compid | The sender's MAVLink system and component IDs |
msgid, msg | The message ID and name, such as 0 and HEARTBEAT |
seq | The MAVLink packet sequence number, 0 to 255 |
len | The payload length in bytes, 9 for a HEARTBEAT |
In the example dataset, the receiver clock is good to ±15 ms, and the run page gives the bound as ±15 ms + latency. Receiver JSONL records has the full schema and an example record. MAVLink ground receiver sets the receiver up.
Why both are receive time#
A receipt always comes after the transmission, later by the link latency. Both records stamp the arrival, and neither carries the moment the vehicle sent the message.
A HEARTBEAT has no time field. The vehicle's own clock reaches the ground only in messages that carry it, such as SYSTEM_TIME, msgid 2. Its time_unix_usec counts microseconds since the Unix epoch, and time_boot_ms milliseconds since boot.
Foxborne 1.4.1 added receive log latency estimates from SYSTEM_TIME. Foxborne gives each receipt an interval that reaches back by the clock error plus the latency, and forward by the clock error alone:
interval = [ t - error - latency , t + error ]
earliest send = 14:32:03.912 - 15 ms - 412 ms = 14:32:03.485
latest send = 14:32:03.912 + 15 ms = 14:32:03.927The same holds for a .tlog stamp: it marks when the ground station logged the packet, on the ground station's clock.
HEARTBEAT at 1 Hz#
Vehicles send HEARTBEAT, msgid 0, on a steady schedule. MAVLink's heartbeat service describes the rate as typically 1 Hz on radio links, and treats a system as lost after four or five missed heartbeats.
In the example dataset, UAS-04's heartbeats reach range control about once a second as system ID 4, and UGV-02's as system ID 22. A gap in those receipts is the first sign on the ground that something stopped.
How telemetry_gap reads receipts#
The telemetry_gap rule reads HEARTBEAT receipts from the ground receive log. It measures the time between consecutive heartbeats from the same system ID and fires when two are more than 3 s apart.
The finding sits where the next heartbeat was due, one expected interval after the last receipt. It reads Observed: telemetry receive gap, not necessarily radio failure.
| Incident | Last received | Next received | Finding title |
|---|---|---|---|
| INC-0142 | 14:32:03.912 | 14:32:10.874 | Heartbeat due and not received. Nothing for 6.96 s |
| INC-0143 | 15:41:02.713 | 15:41:33.689 | Heartbeat due and not received. Nothing for 31.0 s |
A receive gap proves only that nothing arrived. In INC-0142 the MAVLink router on the companion computer had stopped, and in INC-0143 the radio had dropped off the companion's USB bus.
Related#
- Receiver JSONL records gives every field of a receive record.
- MAVLink ground receiver records receipts at range control.
- telemetry_gap explains what a receive gap does and does not mean.
- Clocks, anchors and error bounds explains why receive time is not send time.