Skip to content

turtlebot3: stop the debounce profile from hiding the goal-status faults - #74

Open
bburda wants to merge 2 commits into
mainfrom
fix/turtlebot-debounce-thresholds
Open

turtlebot3: stop the debounce profile from hiding the goal-status faults#74
bburda wants to merge 2 commits into
mainfrom
fix/turtlebot-debounce-thresholds

Conversation

@bburda

@bburda bburda commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #73.

The debounce profile's confirmation_threshold: -3 suits the no-progress check, which repeats
every five seconds while the robot is stuck. It does not suit the other two faults:
NAVIGATION_GOAL_ABORTED and NAVIGATION_GOAL_CANCELED come from a goal status change, fire once,
and are cleared by one PASSED on a later success. Under -3 the counter reached -1 and stopped, so
both stayed PREFAILED and the default CONFIRMED-only fault list never showed them.

They now report under their own source_id and take their thresholds from a per-source file: -1 to
confirm on the single event, 0 to heal on the single clear. The no-progress fault keeps the global
-3, so the storm-versus-filtered contrast is unchanged.

Two smaller fixes on the same files. healing_threshold was 3 under a comment saying three PASSED
events heal; the cost is healing_threshold minus the counter at recovery, so from -3 it was six
and the detector sends one. And the storm profile set confirmation_threshold: 0, which the fault
manager rejects and replaces with -1.

Testing

The demo needs Gazebo and a TurtleBot, so this was measured against a running fault_manager with
the profiles' own values, replaying the event pattern each reporter produces.

Before, global -3 only:

one FAILED            PREFAILED, counter -1
after 3 s of silence  PREFAILED, counter -1
one PASSED            PREFAILED, counter  0

After, with the per-source file:

goal_status  one FAILED   CONFIRMED, counter -1
goal_status  one PASSED   HEALED,    counter  0
base source  one FAILED   PREFAILED, counter -1
base source  three FAILED CONFIRMED, counter -3

The last two lines are the check that the filtering the demo exists to show still works.

Also in this branch: a flaky check in the OTA demo smoke test

Not related to the TurtleBot profile, but it fails on this branch and on main, so it is fixed
here.

ota-demo-narrative fails on and off since 25 August, always on the same check:
fault detail has >=1 environment_data snapshot. Per-topic snapshots are captured on the fault
manager capture thread pool, so they are written a bit after the fault reports CONFIRMED, and
CONFIRMED is the status the step before it waits for. The check read the fault detail once at
that moment. In the failing run on this branch it ran 124 ms before the fault manager logged
Captured 3/3 snapshots for the same fault code, so it read an empty list from a capture that
was still running.

The rosbag check right below it already polls, and for the same reason. The snapshot check now
polls too, for up to 30s.

Checked against a stub API that serves the fault detail, to make sure the new form still fails
when the snapshots are genuinely missing:

snapshot appears after 3s   before: FAIL   after: PASS
snapshot never appears      before: FAIL   after: FAIL
snapshot present at once    before: PASS   after: PASS

The middle line is the one that matters: polling did not turn the check into one that always
passes.

The debounce profile sets confirmation_threshold -3, which suits the
no-progress check: it repeats every five seconds while the robot is stuck, so
three reports arrive and the profile filters roughly fifteen seconds of it.
That is the contrast with the storm profile.

The other two faults do not repeat. NAVIGATION_GOAL_ABORTED and
NAVIGATION_GOAL_CANCELED are raised from a status change, guarded so they fire
once, and cleared by one PASSED when a later goal succeeds. Under -3 the
counter reaches -1 and stops, so both stayed PREFAILED for good and the default
CONFIRMED-only fault list never showed them. Measured against a running
fault_manager: one FAILED leaves PREFAILED at -1, silence changes nothing, and
the PASSED only moves the counter to 0.

They now report under their own source_id and take their thresholds from a
per-source file, -1 to confirm on the single event and 0 to heal on the single
clear. The same measurement with that file in place gives CONFIRMED on the
raise and HEALED on the recovery, while the no-progress fault still needs its
three reports.

healing_threshold was 3 with a comment saying three PASSED events heal. Healing
costs healing_threshold minus the counter at recovery, so from -3 it would have
taken six, and the detector sends one. It is 0.

The storm profile set confirmation_threshold 0, which the fault manager rejects
and replaces with -1 while logging a warning. It now says -1.
Per-topic snapshots are written on the fault manager capture thread pool.
They land a bit after the fault reports CONFIRMED, and CONFIRMED is the
status the previous step waits for. The check read the fault detail once
at that moment, so it sometimes saw an empty snapshot list while the
capture was still running.

In one run the check ran 124 ms before the fault manager logged
"Captured 3/3 snapshots" for the same fault code.

The rosbag check below already polls for the same reason. This makes the
snapshot check poll too, with a 30s budget.
# prefix, so the entry below applies only to the goal-status reporter and the
# global thresholds still cover the no-progress one.

/anomaly_detector/goal_status:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This key never matches. launch/demo.launch.py:208-209 starts the detector as name="anomaly_detector", namespace="bridge", so get_fully_qualified_name() is /bridge/anomaly_detector and anomaly_detector.py:299 reports source_id = "/bridge/anomaly_detector/goal_status". The resolver is an anchored prefix compare (entity_threshold_resolver.cpp:37-39), so resolve() falls back to the global -3 and the goal-status faults stay PREFAILED exactly as before; the replay table must have used the bare key. /bridge/anomaly_detector/goal_status: fixes it; worth a comment tying the key to the launch namespace.

# Healing costs healing_threshold minus the counter at recovery, so from -3
# a value of 3 would need six PASSED events. The detector sends one per
# recovery, so anything above 0 never heals.
healing_threshold: 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment reads as if 0 makes the no-progress fault heal on its one PASSED. It does not: a CONFIRMED no-progress fault sits at counter -3 (clamped), one PASSED takes it to -2, and HEALED needs counter >= 0, so three PASSED (compute_debounce_status). The detector sends exactly one and active_faults.discard() (anomaly_detector.py:305) stops further ones, so a confirmed no-progress fault still never heals; only one caught at PREFAILED -1 does. Either keep sending PASSED (throttled) while progress continues until healed, or say here that 0 is the minimum cost and the confirmed fault still needs three.

# so they are written slightly AFTER the fault reports CONFIRMED - the status
# the previous step polled for. Checking once races that write and reads an
# empty snapshot list from a fault whose capture is still in flight.
if poll_until "/${NAV_ENTITY}/faults/${NAV_CODE}" \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only test change in the PR and it is the OTA demo's snapshot race (NAV_ENTITY=apps/bt-navigator, ACTION_NAVIGATE_TO_POSE_ABORTED), nothing to do with the title. Nothing in tests/ or .github/ starts docker-compose.debounce.yml or asserts a goal-status fault confirming and healing, which is how the key above slipped through. One smoke run of the debounce profile with one FAILED and one PASSED goal transition, asserting CONFIRMED then HEALED, would pin the behaviour this PR is about.

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.

Debounce profile leaves the goal-status faults permanently unconfirmed

2 participants