Skip to content

offboard: give watchdog grace period from when setpoints start#2927

Merged
julianoes merged 1 commit into
mainfrom
fix-offboard-premature-watchdog
Jul 16, 2026
Merged

offboard: give watchdog grace period from when setpoints start#2927
julianoes merged 1 commit into
mainfrom
fix-offboard-premature-watchdog

Conversation

@julianoes

Copy link
Copy Markdown
Collaborator

Problem

OffboardImpl::process_heartbeat() stops setpoint streaming when the vehicle is not in offboard mode more than 3 seconds after _last_started:

if (!offboard_mode_active && _mode != Mode::NotActive &&
    _time.elapsed_since_s(_last_started) > 3.0) {
    stop_sending_setpoints();
}

However, _last_started is only ever written in start(). Before the first start() call it is still default-initialized (epoch 0), so the 3-second guard is always satisfied. Any heartbeat that arrives between the first set_*() and start() therefore resets the setpoint state, and start() fails with NoSetpointSet — even though the caller followed the documented sequence (set a setpoint, then start).

How it was found

In real-time use the window between set_*() and start() is almost never hit by a 1 Hz heartbeat. In lockstep simulation with a speed factor it becomes frequent: at speed factor 30, PX4's heartbeats arrive every ~33 ms of wall time, and the PX4 SITL integration tests ('Offboard attitude control') failed on Offboard::start() returning NoSetpointSet roughly one run in three.

Fix

Stamp _last_started whenever streaming transitions out of NotActive (new note_setpoints_started() helper, called at each mode transition under the existing lock). The watchdog's grace period then covers the gap between the first setpoint and start(), matching the intent of the existing "we make sure we don't stop too eagerly" comment. Watchdog behaviour is otherwise unchanged: streaming without a successful start() still gets stopped, just 3 seconds after it began rather than on the next heartbeat.

process_heartbeat() stops setpoint streaming when the vehicle is not in
offboard mode more than 3 seconds after _last_started. However,
_last_started is only ever written in start(), so before the first
start() it is still default-initialized (epoch 0) and the 3-second
guard is always satisfied: any heartbeat arriving between the first
set_*() and start() resets the setpoint state, and start() then fails
with NoSetpointSet.

In real-time use the window between set_*() and start() is rarely hit
at 1 Hz heartbeats. In lockstep simulation with a speed factor this
becomes frequent: at 30x, PX4's heartbeats arrive every ~33 ms of wall
time, and the PX4 SITL integration tests failed on
Offboard::start() roughly one run in three.

Stamp _last_started whenever streaming transitions out of NotActive, so
the watchdog's grace period covers the gap between the first setpoint
and start(), as intended by the existing 'don't stop too eagerly'
comment.

We also rename _last_started because the name isn't quite right.

Signed-off-by: Julian Oes <julian@oes.ch>
@julianoes
julianoes force-pushed the fix-offboard-premature-watchdog branch from 7453dcc to dfc69b5 Compare July 15, 2026 21:41
@julianoes
julianoes merged commit a0823b1 into main Jul 16, 2026
52 checks passed
@julianoes
julianoes deleted the fix-offboard-premature-watchdog branch July 16, 2026 02:01
julianoes added a commit that referenced this pull request Jul 16, 2026
Backport of dfc69b5 (#2927).

process_heartbeat() stops setpoint streaming when the vehicle is not in
offboard mode more than 3 seconds after the grace start. That timestamp
was only written in start(), so before the first start() it was still
epoch 0 and the guard always passed: any heartbeat between the first
set_*() and start() reset the setpoint state, and start() then failed
with NoSetpointSet. Frequent under lockstep SITL with a speed factor.

Stamp the grace start whenever streaming transitions out of NotActive so
the watchdog covers the gap between the first setpoint and start().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant