AP_Follow: discard target state when FOLL_SYSID changes - #33891
Conversation
| // Handles incoming MAVLink messages to update the target's position, velocity, and heading. | ||
| void AP_Follow::handle_msg(const mavlink_message_t &msg) | ||
| { | ||
| if (_sysid != _sysid_used) { |
There was a problem hiding this comment.
Doesn't this mean that if there are two different leaders both publishing their follow-state we would end up constantly invalidating the valid leader's state?
An alternative solution might be to replace _estimate_valid with a uint32_t _estimate_valid_ms and then somehow force a timeout if the target sysid changes.
There was a problem hiding this comment.
_sysid is the current sysid that is set.
_sysid_used is the last one that was consumed.
msg.msgid is the sysid of the message that has just arrived.
So the sysid of the message has no impact on this.
There was a problem hiding this comment.
Pull request overview
This PR updates the AP_Follow library to ensure cached target state is treated as belonging to the currently-selected FOLL_SYSID, and is discarded/invalidated when FOLL_SYSID changes so consumers don’t keep using stale target position/velocity/heading.
Changes:
- Moves sysid-change handling out of
update_estimates()into message handling +have_target()gating, so stale cached data is rejected immediately afterFOLL_SYSIDchanges. - Resets FOLLOW_TARGET-vs-GLOBAL_POSITION_INT selection behavior on sysid mismatch and consolidates sysid adoption (
FOLL_SYSID=0) intohandle_msg(). - Ensures the estimate is reset when the source system changes (or estimate error is too large), preventing shaping across a retarget.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| libraries/AP_Follow/AP_Follow.h | Clarifies _sysid_used meaning as the sysid that supplied the currently-held target data. |
| libraries/AP_Follow/AP_Follow.cpp | Reworks sysid mismatch/retarget behavior to invalidate cached target state and reset FOLLOW_TARGET preference and estimate when appropriate. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
I ran it by Claude. This is what I got:
I asked it for a suggested improvement: The root cause of #3 is that Fix: stop using the mismatch as a gate, and make it a positive discard instead — rmackay9's instinct, just via a different mechanism than his I checked where
So it's loop-rate and message-independent on all three. That's the right home for the discard. Suggested shapevoid AP_Follow::update_estimates()
{
WITH_SEMAPHORE(_follow_sem);
// FOLL_SYSID has changed: everything we hold was supplied by the previous
// system, so discard it. Nothing is reported until the newly named system
// transmits and is accepted.
if (_sysid != _sysid_used) {
_sysid_used = _sysid;
_last_location_update_ms = 0; // we hold no data from this system
_estimate_valid = false;
_using_follow_target = false; // re-learn the new target's message type
_automatic_sysid = false; // a write has superseded any adoption
}
if (!have_target()) {
clear_dist_and_bearing_to_target();
_estimate_valid = false;
return;
}
...and then:
The ordering is the part to get right: the detection must sit before the Why this is better than the gate#3 dissolves. #1 dissolves too. The discard runs at loop rate under It fixes a threading hole. Copilot's point goes away as a side effect. Caveats worth stating in the PR
And on Plane the whole thing lives behind |
Thanks, that was a useful read. Answers in order. Fixed. Every getter now goes through estimate_usable(), which is _estimate_valid && have_target(), so a FOLL_SYSID write is seen on the next call rather than the next tick. get_distance_to_target_m() and get_bearing_to_target_deg() are reporting only and still clear on the next update_estimates(), which is one loop. Agreed, and it is deliberate. It is now in the commit message. If a script rewrites FOLL_SYSID faster than the target transmits, follow reports no target rather than the previous vehicle's position. I think this is the intended behaviour rather than a wedge. If the newly named system never decodes then we genuinely hold no data for it, so reporting no target is correct. It is also not a regression. On master a decode failure after a retarget left have_target() true while serving the previous vehicle's position, which is the bug this PR fixes. The part that is missing is a GCS message explaining why, which I would rather do separately. On moving the discard into update_estimates(), I looked at it and stayed with handle_msg(). It reintroduces the race the current ordering exists to prevent. If _automatic_sysid is only cleared at loop rate, a message arriving between the FOLL_SYSID write and the next update_estimates() still takes the 10 second timeout path and zeroes the sysid the user just wrote. Clearing it in handle_msg() before the timeout check closes that window by construction. There is also the Plane point you raised, which cuts the other way. update_estimates() is inside #if AP_SCRIPTING_ENABLED at ArduPlane/Plane.cpp:209, so a scripting-disabled build would never run the discard, whereas handle_msg() and have_target() are always compiled. On the semaphore, that is fair, but it is not new. handle_msg() already wrote _target_pos_ned_m, _last_location_update_ms and _estimate_valid from the MAVLink thread without the lock on master, so the semaphore has never protected against that writer. Worth fixing, but not in this PR. Reviewing it again did find a real hole in the sysid 0 handling. Refusing to adopt sysid 0 was not enough, because with FOLL_SYSID zero the message still passed the filter and was decoded, so a FOLLOW_TARGET from sysid 0 latched _using_follow_target and permanently blocked auto-adoption via GLOBAL_POSITION_INT. should_handle_message() now rejects sysid 0 outright, matching MAVLink_routing::learn_route(). |
12766e7 to
43bbccf
Compare
|
I have done a lot more work on this and I think I have finally filled the holes. |
Summary
FOLL_SYSID only filters incoming messages; it does not invalidate the target data already held, so after a change the library keeps reporting the previous vehicle's position, velocity and heading as valid. This makes the held target state belong to the system named by FOLL_SYSID.
Classification & Testing (check all that apply and add your own)
Found in a Copter flight log where a Lua script switched FOLL_SYSID between two beacons roughly 5 m apart. The vehicle continued to be positioned relative to the previous beacon, and the mode consuming the follow estimate advanced its state machine against the old beacon's geometry. SITL Copter ModeFollow_with_FOLLOW_TARGET and three ship-landing tests exercising GLOBAL_POSITION_INT follow pass with this change on a downstream branch. No existing autotest changes FOLL_SYSID in flight, so the retarget path itself is not covered by automated testing.
Description
_sysid is used only as an inbound filter in should_handle_message(). The cached target state carries no record of which system supplied it, and have_target() checks only the age of _last_location_update_ms. So for up to FOLL_TIMEOUT after FOLL_SYSID changes, get_target_sysid() reports the new vehicle while every getter returns the previous one. update_estimates() noticed the change but only cleared _estimate_valid, and the re-init branch immediately re-seeded the estimate from the previous target's last position and marked it valid again.
Two other pieces of state also survived a retarget. _using_follow_target latches when a FOLLOW_TARGET message arrives and causes every GLOBAL_POSITION_INT to be rejected, so retargeting from a FOLLOW_TARGET vehicle to one that only sends GLOBAL_POSITION_INT stalled until the 10 second sysid timeout. _automatic_sysid was set on adoption and never cleared, so a later explicit FOLL_SYSID write could be discarded by that same timeout and the parameter reset to 0.
The change gives _sysid_used a single meaning, the system that supplied the data currently held, updated wherever an update is accepted. have_target() rejects a mismatch, so the estimate and every getter report no target until the new system transmits. On a mismatch the FOLLOW_TARGET preference is dropped and _automatic_sysid is cleared, since a parameter write has superseded an adoption. The estimate is reset when an accepted update comes from a different system, so it snaps to the new target rather than shaping across the gap.
Sysid adoption for FOLL_SYSID = 0 was duplicated in both message decoders and is now done once in handle_msg(), along with the _using_follow_target gate on GLOBAL_POSITION_INT. No parameter changes, and no behaviour change while FOLL_SYSID is fixed.