> For the complete documentation index, see [llms.txt](https://docs.theseus.us/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.theseus.us/micro-vps/autopilot-integration/developer-api/mavlink-wall-clock-sync.md).

# Mavlink Wall-Clock Sync

This guide walks through configuring the NanoPi to set its system clock from a MAVLink peer's `SYSTEM_TIME` messages. This is useful in systems where there is one device that all of the other device logs should be synced with.

The wall-clock source can be any MAVLink device that sends `SYSTEM_TIME` with a valid epoch timestamp — a flight controller with GPS, a companion computer with NTP, a dedicated time module, etc. The timesync module does not care where the peer gets its time from.

## How It Works

```
Time source (GPS, NTP, etc.) → MAVLink peer → SYSTEM_TIME → SHM → chrony → Pi clock
```

1. Some MAVLink device on the bus has an accurate wall clock (however it obtained it).
2. That device sends `SYSTEM_TIME` messages containing `time_unix_usec`.
3. The timesync module receives those messages and writes valid timestamps into an NTP shared memory segment.
4. Chrony reads that segment as a reference clock and adjusts the Pi's system clock.
5. All other Theseus sensors sync from the Pi via NTP automatically.

## Prerequisites

* The configured MAVLink peer must be sending `SYSTEM_TIME` with a valid epoch `time_unix_usec` (i.e., a real wall-clock time, not zero or boot-relative). Without valid timestamps, chrony falls back to whatever time source it had before.
* If the peer is a flight controller using GPS as its time source, set `BRD_RTC_TYPES=1` on the FCU (reboot required). Do **not** use `2` — it creates a circular time loop where the FCU trusts the Pi's clock, which is trying to trust the FCU's clock.
* `chrony.conf` must include the SHM refclock line. This is included by default on mvps devices, but verify it is present:

```
refclock SHM 0 refid MAV precision 1e-3 delay 0.01 poll 1
```

## Parameters

All parameters live in `mav_timesync_params.yaml` (file ID: `components/timesync/mav_timesync_params`). Four parameters control wall-clock sync:

| Parameter                    | Type   | Default         | Description                                                                                                                                                                                        |
| ---------------------------- | ------ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mav_clock_input`            | bool   | `false`         | Master switch. When `true`, valid `SYSTEM_TIME` messages from the configured source are written into chrony's SHM segment. When `false`, no clock adjustment happens regardless of other settings. |
| `system_clock_source_mode`   | string | `timesync_peer` | Determines which MAVLink peer provides the wall-clock time for chrony. `timesync_peer` reuses the normal TIMESYNC target. `dedicated_peer` uses the explicit sysid/compid below instead.           |
| `system_clock_source_sysid`  | int    | `42`            | MAVLink system ID of the dedicated wall-clock peer. Only used when `system_clock_source_mode` is `dedicated_peer`.                                                                                 |
| `system_clock_source_compid` | int    | `1`             | MAVLink component ID of the dedicated wall-clock peer. Only used when `system_clock_source_mode` is `dedicated_peer`.                                                                              |

### When to use each mode

**`timesync_peer` (default):** The same MAVLink peer used for TIMESYNC round-trip offset estimation also provides `SYSTEM_TIME` for chrony. This is the simpler configuration and works whenever that peer has a valid wall clock.

**`dedicated_peer`:** A different MAVLink device provides the wall-clock time for chrony. Use this when:

* The TIMESYNC peer does not have a reliable wall clock.
* You want to source wall-clock time from a specific device on the MAVLink bus that is not the TIMESYNC target.

In dedicated peer mode, the TIMESYNC peer still handles boot-offset initialization and round-trip time measurement. Only the chrony wall-clock feed is redirected to the dedicated peer.

## Configuration

{% tabs %}
{% tab title="Option A: Helper Script" %}
Start by downloading the below python helper script:

{% file src="/files/AV6mN9MhnjE5bPCXTrT9" %}

The `timesync_clock_source.py` script wraps the Params API so you don't need to remember individual curl commands. It lives at `scripts/device/timesync_clock_source.py` in the repository.

**Check current status:**

```bash
python3 scripts/device/timesync_clock_source.py status
```

Example output:

```
TIMESYNC peer:              1:1
System clock source:        fallback to TIMESYNC peer (1:1)
Configured dedicated peer:  42:1 (ignored in current mode)
clock_source_mode:          timesync_peer
mav_clock_input:            False
Note: restart the consuming service after changing params.
```

**Enable wall-clock sync with the default TIMESYNC peer:**

```bash
python3 scripts/device/timesync_clock_source.py enable-clock-input
```

This sets `mav_clock_input: true` while leaving `system_clock_source_mode: timesync_peer`, so chrony gets its time from the same peer used for TIMESYNC.

**Switch to a dedicated peer:**

```bash
python3 scripts/device/timesync_clock_source.py set --sysid 42 --compid 1
```

This sets `system_clock_source_mode: dedicated_peer` and configures the peer IDs. If `mav_clock_input` is already enabled, the dedicated peer will start feeding chrony on the next service restart.

**Clear the dedicated peer (revert to TIMESYNC peer):**

```bash
python3 scripts/device/timesync_clock_source.py clear
```

**Disable wall-clock sync entirely:**

```bash
python3 scripts/device/timesync_clock_source.py disable-clock-input
```

**Custom API URL** (if the edge API is not at the default `http://192.168.218.100:5050`):

```bash
python3 scripts/device/timesync_clock_source.py --base-url http://192.168.218.100:5050/api/v2/params status
```

{% endtab %}

{% tab title="Option B: curl Commands" %}
All parameters use the Params API at `http://<device>:5050/api/v2/params`. The file ID for timesync is `components/timesync/mav_timesync_params`.

**Read all timesync params:**

```bash
curl http://192.168.218.100:5050/api/v2/params/components/timesync/mav_timesync_params
```

**Enable wall-clock sync:**

```bash
curl -X PUT http://192.168.218.100:5050/api/v2/params/components/timesync/mav_timesync_params/mav_clock_input \
  -H 'Content-Type: application/json' \
  -d '{"value": true}'
```

**Switch to dedicated peer mode with sysid 42, compid 1:**

```bash
# Set the peer IDs first
curl -X PUT http://192.168.218.100:5050/api/v2/params/components/timesync/mav_timesync_params/system_clock_source_sysid \
  -H 'Content-Type: application/json' \
  -d '{"value": 42}'

curl -X PUT http://192.168.218.100:5050/api/v2/params/components/timesync/mav_timesync_params/system_clock_source_compid \
  -H 'Content-Type: application/json' \
  -d '{"value": 1}'

# Then switch the mode
curl -X PUT http://192.168.218.100:5050/api/v2/params/components/timesync/mav_timesync_params/system_clock_source_mode \
  -H 'Content-Type: application/json' \
  -d '{"value": "dedicated_peer"}'
```

**Revert to TIMESYNC peer mode:**

```bash
curl -X PUT http://192.168.218.100:5050/api/v2/params/components/timesync/mav_timesync_params/system_clock_source_mode \
  -H 'Content-Type: application/json' \
  -d '{"value": "timesync_peer"}'
```

**Reset peer IDs to defaults:**

```bash
curl -X DELETE http://192.168.218.100:5050/api/v2/params/components/timesync/mav_timesync_params/system_clock_source_sysid
curl -X DELETE http://192.168.218.100:5050/api/v2/params/components/timesync/mav_timesync_params/system_clock_source_compid
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
Parameter changes require restarting the consuming service (Cyclops or MVPS) for the new values to take effect. The process reads its YAML config once at startup.
{% endhint %}

## Verification

After restarting the service, verify that chrony is receiving time from the MAVLink source:

```bash
chronyc sources
```

Look for a line with `#* MAV` — the `*` means chrony has selected it as the active source:

```
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
#* MAV                           0   1   377     1   +125us[ +130us] +/-  500us
```

Verify the system clock is correct:

```bash
date
```

If chrony shows `MAV` but without the `*`, it means chrony sees the source but has not selected it yet (it may still be evaluating samples). Give it 30-60 seconds after the peer starts sending valid timestamps.

## Troubleshooting

**No `MAV` source in `chronyc sources`:**

* Check that `mav_clock_input: true` is set and the service was restarted.
* Verify `refclock SHM 0 refid MAV ...` exists in `/etc/chrony/chrony.conf`.

**`MAV` source present but not selected (`#?` instead of `#*`):**

* The peer may not have a valid wall clock yet. Check `time_unix_usec` in the `SYSTEM_TIME` messages — values near zero mean the peer does not have a time source.
* Chrony may be preferring another source. Check `chronyc sources -v` for competing sources with better stratum.

**`system_time_invalid` events in logs:**

* The configured source is sending `SYSTEM_TIME` with a `time_unix_usec` that is not a valid epoch timestamp (before Feb 2009). This means the peer does not yet have a valid wall clock. The events will stop once the peer starts sending valid time, and a `system_time_recovered` event will be emitted.

**Dedicated peer configured but no clock feed:**

* Verify `system_clock_source_mode` is `dedicated_peer`, not `timesync_peer`. In `timesync_peer` mode the explicit sysid/compid values are ignored.
* Verify the dedicated peer is on the MAVLink bus and sending `SYSTEM_TIME` at the expected sysid:compid.
