Repository navigation
Conversation
Previous reviewAutomated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Full report: https://uav.tridgell.net/DevCallReviews/2026_09_05/devcall_pr_reviews.html#pr34292 Reviewed at head The parameter metadata doesn't parseThe Deterministic. Moving Checked and sound
Two things to know before merge1. The Replay record grew a field, and Growing a DAL record is normal practice and this isn't a reason to hold the PR. But this is the first ROFH field whose garbage value changes behaviour rather than being inert, and #32471 does the same thing to 2. Two commits here are byte-identical to two in #33484. One thing I'm not passing on: a cross-check pass reported that Minor: the cutoff uses rangefinder height at the IMU origin rather than at the flow sensor, so a large |
Previous review, kept for the recordAutomated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Full report, with what was checked and the carried-over context: https://uav.tridgell.net/DevCallReviews/2026_09_05/devcall_pr_reviews.html#pr34292 REQUEST CHANGESBoth findings from my previous round are resolved — details at the end. The core idea and the EKF3 consumer are sound. One concrete replay-plumbing bug remains, and separately @peterbarker's CHANGES_REQUESTED of 2026-09-04 is against this exact head with none of its seven comments answered yet. A second log in the same power cycle carries no
|
e0b5bc0 to
84ec31a
Compare
Previous review, kept for the recordAutomated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Full report, with the replay-compatibility check and the carried-over context: https://uav.tridgell.net/DevCallReviews/followups/2026_09_06_1710/devcall_pr_reviews.html#pr34292 COMMENT — was REQUEST CHANGESMy blocker is fixed, and it is fixed the better of the two ways I offered. I raised it as a Resolved
Your one failing check is master's, not yours
Small, not blocking
The actual gate@peterbarker's CHANGES_REQUESTED of 2026-09-04 is still standing and the thread has had no reply at all. Four of his six inline points are now answered in the code — Replay compatibility checked and clean in both directions: |
4c6ae1f to
76d3538
Compare
|
Force-pushed 4c6ae1f98a -> 76d3538247. The tree is unchanged - The Also folded the |
Previous review (2026-09-11)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Reviewed at head Full report: https://uav.tridgell.net/DevCallReviews/followups/2026_09_06_1955/devcall_pr_reviews.html#pr34292 APPROVE — was COMMENTEverything I have raised across four rounds on this PR is now closed. You pushed the Two other things landed with it:
A correction to my previous commentI said The underlying concern survives on the other member, and is sharper than I described. Your Everything else from the previous round is unchanged and still holds: the DAL retry behaviour, the recalled-sample gate, the tilt check, the parameter clamp, the |
337cf08 to
946708d
Compare
Previous review (superseded 2026-09-12)Automated review note — AI-generated (Claude), validated against the live diff. Please sanity-check before acting. Re-reviewed at head
The delta is a fast-forward, not a force-push: two commits, 2 files, +10/−3. My last automated round was an APPROVE with nothing outstanding, so there is no previous AI finding to triage — but @peterbarker's Verdict: REQUEST CHANGES, on the finding below. BUG — the new terrain withhold is bypassed whenever range data arrives in the same cycle
On frequency: the range-sampling gate at Fix: OR the flag into ISSUE (pre-existing, not yours) — range-only calls try to fuse fabricated zero flow anywaySame defect shape, already in the merge base: when the flow recall fails the vectors are zero-initialised, yet Notes
Reviewed by Claude + an independent Codex cross-check: 7 confirmed, 9 adjusted, 2 refuted, 1 added. |
5def155 to
a204212
Compare
|
This round was taken at The terrain withhold bug is fixed, and the fix is the one this round proposes - The pre-existing availability gap goes with it. A failed
Test coverage for the terrain path is still missing, and I am not claiming otherwise. Copter defaults One thing the round does not raise, which belongs on the record because it is where the head now differs from what was flown. Discarding is not free where zeroing was not: a zeroed sample was fused, which kept (Posted by Claude.) |
Every caller passes it, so a missing argument is a compile error rather than a silent 0.
Every caller passes it, so a missing argument is a compile error rather than a silent 0.
Every caller passes it, so a missing argument is a compile error rather than a silent 0.
Applies AP_RangeFinder's on-ground rule to the logged status and distance and the logged downward ground clearance, so Replay reaches the same answer as the flight without a new log field.
After touchdown the range sits at the ground clearance, so the focus hold discarded every flow sample even with FLOW_HGT_MIN at 0, relative aiding timed out 5 s later and readyToUseOptFlow() kept it off. An armed vehicle in LOITER then had no position: SITL, flow only with a GCS-set origin and the default DISARM_DELAY, gave "EKF variance" and an EKF failsafe to LAND about 6 s after touchdown. On the ground below the floor, fuse zero flow as the pre-takeoff path does. "On the ground" is the range finder's on_ground(): reading the ground clearance, or too close to measure. Vehicle landed and flying flags are not used, as they read wrong in flight on some vehicles. Off the ground below the floor the samples are still discarded. A vehicle whose range finder reads more than its ground clearance on the ground, as a taller one with RNGFNDx_GNDCLR left at its default does, misses that test, so a reading within 5 cm of the one taken on the ground before takeoff counts too. It is taken while disarmed on the ground from each stored flow sample, and kept only under the focus floor. SITL, LOITER landing reading 0.19 m with RNGFND1_GNDCLR 0.10 and FLOW_HGT_MIN 0.3: "EKF variance" without it, a normal disarm with it. The reading is cleared on disarm, so a flight uses only one taken before its own takeoff. A sensor that stores no sample on the ground, as one reporting quality 0 there, would otherwise carry the last flight's reading to ground that reads closer, and a hover within 5 cm of it would fuse zero flow. SITL, two flights without a reboot, the first from ground reading 0.79 m, the second from ground reading 0 m with no flow sample stored in between, hovering at 0.41-0.47 m under a 1 m floor: 40 and 45 flow fusions in the hover without the clear, 0 and 0 with it. What this does not cover. Too close counts as the ground, so where RNGFND_MIN is above the ground clearance plus 0.05, as with the 0.20 and 0.10 defaults, a hover below the minimum also fuses zero flow, as does a dead sensor that reads out of range low until the hold releases. A vehicle that moves on the ground with its range finder reading the ground, a rover or a plane rolling out, fuses zero while it moves. A landing that reads more than 5 cm further than before takeoff still discards the flow. The RNGFND_MIN and FLOW_HGT_MIN descriptions say so. Replay of 14 flow copter logs with the on_ground() test alone: the change starts on the ground, before liftoff or after touchdown. After one landing the replayed position wandered 2.2 m before disarm where the flight's own cores held 0.01 m; with this it is 0.02 m. Airborne flow-lane velocity against the GPS lane is unchanged to 0.015 m/s. The reading before takeoff changed none of the 12 replayed again with it.
Feeds the focus height to the DAL ahead of the flow sample it applies to.
5240bea to
f9cae55
Compare
|
Thanks. The carry-over your 2026-10-05 review raised was real, and the reply that day missed it: the rest reading was only cleared at EKF init, so after the first flight a sensor that stored no flow sample on the ground kept the previous flight's reading. It is now cleared on disarm, and FlowFocusHoldAfterLanding has a two-flight leg without a reboot: with the reading kept, a 0.44-0.47 m hover under a 1 m floor fused zero flow 40-45 times; cleared, the flow is discarded and aiding stops.
|
Previous review (2026-10-11)Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at f9cae55. The head has moved since my last review at 5240bea: same base (1f1251a), now 18 commits, with four files changed (the disarm reset in AP_NavEKF3_Control.cpp, a comment in AP_NavEKF3_core.h, the FLOW_HGT_MIN description and the new leg in arducopter.py). Nothing newer than f9cae55 is covered, and CI was still pending when this was pinned. Nothing here is blocking. Thanks for the 2026-10-11 reply. Each point checks out at this head:
One remaining point: a reading learned before the first arming can go stale (AP_NavEKF3_OptFlowFusion.cpp:77). The reading is only refreshed from a stored flow sample while disarmed, and the disarm reset does not fire before the first flight. So if the sensor stores a sample with the range under the floor, then stops storing them (quality 0, or a rate over 4.2 rad/s), and the range on the ground then drops before arming, the old reading is kept. Any hover below the floor that reads at or below the old value plus 0.05 then counts as resting and fuses zero flow (:93). A SITL probe at this head, built like your two-flight leg but without the first flight, shows it: 0.79 m learned after boot, flow samples stopped, ground then reading 0.00 m, and a 0.38 m hover under a 1.0 m floor fused zero flow 33 times with aiding kept (25 on a second run). With samples left running the same hover fused none and aiding stopped. Your 2026-10-05 note about falling back to on_ground() covers a sensor that never stored a sample. This is one that stored a sample earlier in the same disarmed period. It needs a non-default FLOW_HGT_MIN and only acts below the floor, so I'm not treating it as blocking. One way to close it is to clear the reading while disarmed once no flow sample has been stored for about a second. I have not tested that change. Unchanged notes, no change asked:
Not covered: hardware, your flight logs, CI, ChibiOS builds and the full Copter suite. The two limits and the resend ordering were checked by reading and by small probes built from the source, not flown. |
|
Thanks, confirmed and fixed in 16506a6: while disarmed the rest reading is now also cleared once no flow sample has been stored for a second, alongside the disarm clear; it is a commit on top rather than squashed, since the commits below it have flown. FlowFocusHoldAfterLanding has your probe as a second variant of the stale-reading leg (9e086e9): 0.79 m learned after boot, samples stopped, ground then 0 m, and a 0.42-0.44 m hover under a 1 m floor fused zero flow 42 and 39 times without the new clear, 0 with it, with aiding stopped. |
Previous review (2026-10-13)Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at 9e086e9. The head has moved since my last review at f9cae55: the same 18 commits plus 16506a6 and 9e086e9 on top, same base (1f1251a), now 20 commits. Nothing newer than 9e086e9 is covered, and CI for this head was still queued when I checked. Nothing here is blocking. Thanks for the 2026-10-12 reply. The fix checks out. With the new clear (AP_NavEKF3_OptFlowFusion.cpp:78) FlowFocusHoldAfterLanding passed 3 SITL runs of 3 with 0 flow fusions in both stale-reading hovers. With those three lines removed it failed 2 of 2 in the after-boot leg with 37 and 44 fusions, in line with your 42 and 39, while the first-flight leg still passed. That point is closed. The test's low climbs can miss their altitude band on a loaded machine (arducopter.py:18337, same pattern at :18228). Each climb waits for one SIM_STATE.alt sample between 0.25 and 0.5 m, and each sample is a poll_message round trip. When the harness runs slow against the simulation, one late poll steps over the band, the vehicle keeps climbing and the wait ends in 'Failed to attain Altitude'. The new after-boot leg makes it three such climbs. With the box quiet the test passed 14 runs of 14. With busy-loop CPU load added it failed 6 runs of 78, all from line 18337; in one, the progress lines a simulated second apart read 584.29 and 584.69, either side of a 584.34 to 584.59 band. A variant that sets SIM_SPEEDUP to 5 for just the climb and restores it after the hover throttle passed 40 of 40 alternated with the unmodified test under the same load, and still failed on a build without the new clear. I have not seen this fail in CI, and the load was artificial, so treat it as a possible flake rather than a known one. The PR description is out of date in three places. It says FlowFocusHoldAfterLanding shows relative aiding stopping once on the ground and staying off, but the test now fails if aiding stops through the landing (arducopter.py:18208). 'Aiding that times out while the vehicle sits on the floor stays off' describes the behaviour before zero flow was fused at rest. The ROFM paragraph still gives the read-past-the-record reason, where 121a9b6's message has the padding one. It also does not mention zero flow on the ground, the learned rest reading or its two clears. The commit messages are current. Unchanged notes, no change asked:
Not covered: hardware, your flight logs, CI at this head, the full Copter suite, a two range finder flight and a real log restart and Replay cycle. The three notes were checked by reading and by a small probe built from the source, not flown. |
Four tests on a flow-only copter, most of them using SIM_FLOW_OFS to inject a flow rate the vehicle is not moving at. OpticalFlowFocusHeight: with FLOW_HGT_MIN above the hover height the EKF must hold ground speed near zero; with it disabled, and with it set so low it never fires, the phantom must appear. Only the first leg fails on a revert of the EKF gate, so it is the revert detector and the other two are controls. The test height is reached in GUIDED, where an RC descent overshoots by an amount that depends on the speedup, and the phantom is measured in ALT_HOLD, since a position-controlled mode flies it away. FLOW_HGT_MIN is held far above any real sensor to keep the floor active long enough to measure, which is also long enough for the EKF to drop to constant position mode. It then lands with FLOW_HGT_MIN 0.3 and RNGFND1_MIN 0.2, and 0, injects a 2 rad/s flow offset after touchdown and requires both that flow is still fused, as zero at rest, and that the position estimate does not walk with the offset. The landing is in ALT_HOLD at LAND's descent speed, so the vehicle sits armed on the ground for longer than a carried range height is trusted. SITL's range finder reads 0 m on the ground, so the minimum is raised once airborne, or the vehicle could not arm on flow. A last leg has the range finder read 1.5 m short, as over an obstacle, then go out of range high, and more than 5 s later descend 0.5 m, which takes the carried height below the floor at a true 1.5 m. Flow has to keep being fused: 83 flow innovation updates in 8 s with the 5 s bound, and 0 in both runs without it. FlowHeightMinTerrainPath: EK3_FLOW_USE=2 sends flow to the 1-state terrain estimator, which Copter's default of 1 never reaches. It flies a leg below FLOW_HGT_MIN and one above, fast enough for the estimator's 5 m/s floor, and reads XKF5.AFI, the terrain estimator's own flow innovation, written only when a sample reaches its fusion step. The low leg must stay at zero and the high leg must not, so a rig that never fuses terrain flow cannot pass by accident. Without !flowDataToFuse in cantFuseFlowData the low leg reports an innovation of 2462 against 62 once the sensor can focus. FlowFocusHoldReleasesWithDeadRangeFinder: past the carried height's 5 s a hold is sustained by out of range low, which is also what a failed sensor reports. With the range finder stuck low after a landing, a climb to 5 m has to end the hold: aiding must not stop, and a flow offset must then move the EKF's velocity, as zero flow fused through a hold that never released would keep aiding up too. FlowFocusHoldAfterLanding: relative aiding has to carry on through a landing below the floor and a climb clear, with and without 1.5 m of baro ground effect, checked against the true height. A low hover after a touchdown under a 1 m floor must discard the flow and stop aiding rather than fuse zero. A LOITER landing with an origin and the default DISARM_DELAY must not end in "EKF variance" or a failsafe, also when it lands reading 0.2 m more than RNGFND1_GNDCLR under a 0.3 m floor, where the rest test goes by the reading before takeoff. Last, two flights without a reboot: the first takes off from ground reading 0.79 m, a 5 rad/s flow rate stands in for quality 0 from its touchdown until the second is armed on ground reading 0 m, and the second hovers at 0.41-0.47 m under a 1 m floor. The flow has to be discarded there, 0 fusions and aiding stopped; with the first flight's reading kept it was fused as zero, 40 and 45 fusions in two runs. The replay test records with FLOW_HGT_MIN 0.30, so ROFM carries a value rather than the disabled 0 and check_replay compares the feature switched on. It does not make the gate fire, since OpticalFlowLimits flies well above 0.30 m; what it covers is the record surviving the write, the log and the parse.
The reading is cleared on disarm, which does not happen before the first flight. A reading learned after boot was kept if the sensor then stopped storing flow samples (quality 0, or a rate over the EKF's limit) while the ground under it came to read closer, and a hover within 5 cm of it fused zero flow. While disarmed, also clear it once no flow sample has been stored for a second. SITL, ground reading 0.79 m after boot, then no flow sample stored while it drops to 0 m, then a 0.41-0.47 m hover under a 1 m floor: 42 and 39 flow fusions in the hover without the clear, 0 with it.
FlowFocusHoldAfterLanding's stale-reading leg now also runs with the reading taken after boot and no flight before it: 0.79 m learned, a 5 rad/s flow rate standing in for quality 0 until the vehicle is armed on ground reading 0 m, then a hover at 0.41-0.47 m under a 1 m floor. The flow has to be discarded there, 0 fusions and aiding stopped; with the old reading kept it was fused as zero, 42 and 39 fusions.
9e086e9 to
8cd6410
Compare
Previous review (2026-10-13)Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at 8cd6410. The head has moved since my last review at 9e086e9: still 20 commits on the same base, and the only change is the slowed climb in arducopter.py. Nothing newer is covered, and CI for this head was still queued when I checked. Thanks for taking the slowed climb. Under added CPU load, alternated run for run on one binary, the previous test file failed 12 of 63 and this one 1 of 62, and it still fails 2 of 2 with the ground-reading clear reverted. That point is closed. The one remaining failure was the same step over the band at speedup 5 with the load average near 40; I am not asking for anything more there. One new point, and it is blocking: an armed Rover with a downward range finder fuses zero flow while it drives (AP_NavEKF3_OptFlowFusion.cpp:114). I missed this in the earlier rounds, where I only traced the Copter callers; the code is unchanged since then. Rover sets VehicleClass::GROUND (Rover/system.cpp:316), so an armed Rover has onGround false (AP_NavEKF3_VehicleStatus.cpp:379) and takeOffDetected latches on its first turn over 0.1 rad/s (:476). The ground check then runs with FLOW_HGT_MIN at 0, because the floor is at least rngOnGnd + 0.05 (:87), and on_ground() is true whenever the range finder reads within 0.05 of RNGFNDx_GNDCLR (:94). On a Rover that is all the time, so every flow sample is zeroed and fused. In SITL, a flow-only Rover (EK3_SRC1_VELXY 5, FLOW_HGT_OVR set, FLOW_HGT_MIN 0) with a downward range finder reading Good at its ground clearance, driven in a circle at 0.40 m/s, read 0.03 m/s from EKF3 at this head (2 runs of 2) and 0.40 m/s on the merge base (2 of 2). It stays in relative aiding and reports the vehicle as still. At this head it read 0.40 with no range finder, 0.40 with the range finder reading well above its ground clearance, and 0.40 with the block skipped for VehicleClass::GROUND. For the probe I had to patch the SITL flow model, which gives no translational flow at zero height, and I used a 1.5 m sensor height to stay under the flow rate limit, so this is one artificial geometry and not hardware. The FLOW_HGT_MIN text does say zero flow does not suit a vehicle that drives on the ground, but the ground clearance check applies at the default, so an existing Rover setup changes without setting anything. Skipping the check for ground vehicles (or when FLOW_HGT_OVR is in use) and a Rover test would cover it. The PR description is still out of date in the same three places as last time: FlowFocusHoldAfterLanding 'stops once and stays off' (the test now fails if aiding stops, arducopter.py:18219), aiding that 'stays off' on the floor, and the read-past-the-record reason for ROFM where 121a9b6's message has the padding one. It also does not mention zero flow on the ground, the learned ground reading or its two clears. The notes from last time are unchanged and need no action: the landing reading more than 0.05 above both references (:96), two downward range finders sharing one learned reading and clearance (AP_DAL_RangeFinder.cpp:116), and rofm_resend not serialised with the flow write path (AP_DAL.cpp:361). Not covered: hardware, your flight logs, CI at this head, the full Copter and Rover suites, a two range finder flight and a real log restart and Replay cycle. The Rover result is from one SITL setup with a review-only sensor model change. |
|
Thanks, reproduced: with the autotest and 8 busy loops pinned to four cores the unmodified test failed 4 of 24, at all three climbs, each time one sample just off the ground and the next at 0.51-0.56 m. The climbs now share a helper that drops SIM_SPEEDUP to 5 until the hover throttle, as you suggested: 20 of 20 under the same load, 6 of 6 without, and still a fail with 44-48 fusions without the one-second clear or without both clears. |
A Rover that sets FLOW_HGT_OVR and has a downward range finder reads its ground clearance all the time, so once it had turned, which the EKF takes as a takeoff, every flow sample was zeroed as a vehicle resting below its focus floor. A flow-only Rover driving at 0.39 m/s read 0.01 m/s. A sensor at a declared height is on a vehicle that drives on the ground and is already fused at that height, so the floor is skipped for it, with both its flags cleared so a hold set before the height was declared cannot outlive it.
The model gave no translational flow at zero height above ground, so a Rover's flow sensor saw nothing however high it was mounted. The range is now taken from the sensor's own height, the height above ground less its mounting offset, and only a sensor at or below the ground gets none, as before.
Both sensors sit 1.5 m up with FLOW_HGT_OVR 1.5, so the range finder reads its ground clearance throughout. Driving a circle in MANUAL, the EKF read 0.01 m/s against 0.39 m/s before the focus floor skipped declared heights, and 0.40 m/s after.
|
Reproduced (a flow-only Rover with FLOW_HGT_OVR 1.5 and a downward range finder at its ground clearance read 0.01 m/s while driving at 0.39 m/s) and fixed in 244d608: a flow sample with a declared sensor height now skips the focus floor and clears its flags, 0.40 m/s in OpticalFlowRangefinderOnGround (c0498ea; a0ab050 lets the SITL flow model see a sensor mounted above a vehicle at zero height). The VehicleClass condition was not used because the EKF keeps no vehicle-specific code; FLOW_HGT_OVR is what a ground vehicle already declares, and a Rover with a downward range finder but no FLOW_HGT_OVR stays the stated limitation in the description. |
Previous review (2026-10-14)Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at c0498ea. The head has moved since my last review at 8cd6410: three new commits (244d608, a0ab050, c0498ea) on the same base, 23 in all, changing AP_NavEKF3_OptFlowFusion.cpp, AP_OpticalFlow_SITL.cpp and rover.py. Nothing newer is covered, and CI for this head was still queued when I checked (76 of 77 check runs at 2026-10-14T00:44Z). Thanks for the fix and the test. The setup I measured last time is fixed: with FLOW_HGT_OVR 1.5 the new OpticalFlowRangefinderOnGround reads 0.40 m/s against 0.39 here, and 0.01 with 244d608 reverted, which are your numbers. The description is now up to date in every place I listed, so that point is closed, and so is the slowed climb. Still blocking in my view: a flow Rover with a downward range finder and FLOW_HGT_OVR left at 0 fuses zero flow while it drives (AP_NavEKF3_OptFlowFusion.cpp:82). First my part in this: last time I wrote "ground vehicles (or when FLOW_HGT_OVR is in use)" as if either would do. They are not the same, and you took the one that covers the setup I had measured. Without a declared height the block still runs: the floor is at least rngOnGnd + 0.05 (:91), on_ground() is true at the ground clearance (:98), and the flow is zeroed and fused (:121). Your test with only FLOW_HGT_OVR changed to 0 reads 0.01 m/s at this head (3 runs of 3) and 0.40 on the merge base (3 of 3); the merge base does not need FLOW_HGT_OVR there because the range finder gives the EKF its height. A small-rover geometry, both sensors 0.14 m up with RNGFND1_GNDCLR at its default 0.10, reads 0.00 at this head and 0.43 on the merge base (2 of 2 each). I also tried a speed-controlled mode this time: in ACRO, at a stick position that holds 1.00 m/s on the merge base and at this head with FLOW_HGT_OVR set, this head with FLOW_HGT_OVR 0 drove at 1.83 m/s (2 of 2). On the EKF keeping no vehicle-specific code: EKF3 already tests the vehicle class in assume_zero_sideslip() (AP_NavEKF3_Control.cpp:714), and the use of heightOverride is itself inside Smaller, not blocking: FLOW_HGT_OVR set on a Copter now switches the focus floor off. The parameter is hidden off Rover but can still be set by name, and line 82 does not look at the vehicle. Before this head the override did nothing off Rover. With FLOW_HGT_OVR 1.5 added to OpticalFlowFocusHeight, the floor-active subtest reached 1.97 m/s where the test allows 0.5 (3 of 3), against a pass unmodified (2 of 2). The vehicle class test above passes that variant too (2 of 2), so the one condition would cover both. Smaller, not blocking: the FLOW_HGT_MIN text says the ground clearance check applies "whatever this is set to" (AP_OpticalFlow.cpp:110). With a declared height neither that check nor FLOW_HGT_MIN applies: FLOW_HGT_OVR 1.5 with FLOW_HGT_MIN 2.0 reads 0.40 m/s on Rover. One clause would cover it, whichever condition line 82 ends up with. Notes, no action needed. The new Rover test does not check that the range finder reads its ground clearance (rover.py:7394), so with the fix in it passes whatever the range finder reads. The three notes from last time are unchanged: the landing reading more than 0.05 above both references (:100), two downward range finders sharing one learned reading and clearance (AP_DAL_RangeFinder.cpp:116), and rofm_resend not serialised with the flow write path (AP_DAL.cpp:361). CI at the previous head finished with one failure, sitltest-copter-tests1e on Copter.MaxAltFence, a test with no flow or range finder. That group passed whole here at this head, so I read it as unrelated, though I could not read the job log. Not covered: hardware, your flight logs, CI at this head, the full Copter suite, ChibiOS and other vehicle builds, a two range finder flight and a real log restart and Replay cycle. The full Rover suite ran once at this head: 135 passed and 3 failed, none of which I attribute to this PR, though one (NetworkingWebServerPPP) has no merge-base control. |
A Rover's range finder reads its ground clearance all the time it drives, which the floor takes as resting on the ground, so it fused zero flow while the vehicle moved. Declaring the sensor height (FLOW_HGT_OVR) only covered the Rovers that set it: one with a downward range finder and FLOW_HGT_OVR 0 read 0.01 m/s driving at 0.39 m/s, and a small Rover with both sensors 0.14 m up read 0.00. That bypass also let FLOW_HGT_OVR, set by name on a Copter where it is hidden, switch the floor off. The floor is for a vehicle that lands, so it is compiled out of Rover builds, as the EKF's other FLOW_HGT_OVR use already is, and the bypass is dropped. Rovers behave as before the floor whatever FLOW_HGT_OVR is, and nothing else changes for other vehicles. Replay is its own build, so like that other use it runs the floor on a Rover log.
OpticalFlowRangefinderOnGround now drives three Rovers: both sensors 1.5 m up with FLOW_HGT_OVR 1.5, the same with FLOW_HGT_OVR 0, and both sensors 0.14 m up with the default 0.10 m ground clearance. The EKF speed reads 0.40, 0.40 and 0.43 m/s against 0.39 true; with the floor built into Rover the second reads 0.01 m/s and fails. Each leg also checks the range finder reads within 5 cm of its ground clearance, the reading that puts a vehicle that flies on the ground, and the message waits allow 5 s, as one GPS_RAW_INT took longer than the default.
Previous review (2026-10-16)Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at b582cc7. The head has moved since my last review at c0498ea by three commits (b43ace7, a0e43dc, b582cc7) on the same base, 26 in all. Nothing newer is covered, and CI at this head was still pending. Thanks, the blocker is fixed. With the floor left out of Rover builds, OpticalFlowRangefinderOnGround passes 3 of 3 here, reading 0.40, 0.40 and 0.43 m/s against 0.39 for declared height, FLOW_HGT_OVR 0 and the small rover. With b43ace7's EKF hunk reverted it fails at 0.01. The new range-at-clearance check (rover.py:7441) closes my note that the test passed whatever the range finder read. FLOW_HGT_OVR on a Copter no longer turns the floor off (OpticalFlowFocusHeight with it set to 1.5 passes), and the FLOW_HGT_MIN text is accurate for the vehicle again. Those points are closed. One point, not blocking: Replay of a Rover flow log diverges (AP_NavEKF3_OptFlowFusion.cpp:56). Your commit message says Replay is its own build and runs the floor. Here is what that does. I replayed a SITL Rover log (flow, range finder at its 1.5 m ground clearance, FLOW_HGT_OVR 0) with the head's Replay. check_replay gave 104133 mismatches, and the replayed EKF read 0.02 m/s against 0.39 live. With the block also left out of Replay it matched exactly. The vehicle is unaffected, and the existing Rover-only heightOverride already has the same Replay gap, so I am not treating this as blocking. But it now hits the default flow-plus-range-finder Rover setup. A runtime The three earlier notes are unchanged and need no action: the landing reading more than 0.05 above both references (:97), two range finders sharing one learned reading and clearance (AP_DAL_RangeFinder.cpp:116), and rofm_resend not being serialised (AP_DAL.cpp:361). Not covered: hardware, your flight logs, CI at this head, the full Copter and Rover suites, ChibiOS and Plane/Sub builds, a two range finder flight and a real log restart. Everything above is SITL with simulated sensor heights. |
Rover is built without the focus floor, as it drives at its range finder ground clearance, where the floor fuses zero flow. Replay is built as its own vehicle and kept it, so a replayed Rover flow log fused zero flow from the first turn: on SITL logs the replayed speed was 0.01-0.04 m/s against 0.31-0.34 m/s flown, and the positions 4.4-4.6 m apart after 20 s. In Replay the floor now follows the logged vehicle class, so a Rover log replays as Rover ran it; the same logs now replay with no difference in speed or position. Vehicle builds are unchanged. A Rover log with FLOW_HGT_OVR set still differs slightly, as Replay does not build the existing Rover-only height override.
|
Replay now follows the logged vehicle class, compiled into Replay alone (3917ef3), so a Rover log replays without the floor as Rover ran it; vehicle builds are unchanged and keep the compile-time exclusion rather than a run-time class test. On three SITL Rover flow logs the replayed speed was 0.01-0.04 m/s against 0.31-0.34 flown before, and matches to 0.000 m/s and 0.00 m after; the log with |
|
Automated review note — AI-generated (Claude+Codex), validated against the live diff. Please sanity-check before acting. Reviewed at head Re-reviewed at 3917ef3, one commit after b582cc7 on the same base (27 in all). Nothing newer is covered, and CI at this head was pending when this review started. Thanks, the Replay point is fixed. With the class test in Replay, I rebuilt Replay at this head and replayed the same SITL Rover flow log that gave 104133 check_replay mismatches at b582cc7. It now passes with 0 errors over 34649 messages. A Copter log flown under FLOW_HGT_MIN still replays with the floor: it gives 0 field errors, where a Replay with the floor forced off gives 17834 mismatches and a 1.42 m/s phantom. The Rover and Copter flow tests pass at this head, and outside Replay the extracted function's code is unchanged. On your check_replay remark: on my log, recorded from boot with LOG_REPLAY 1, check_replay passes outright at this head, and every live XKF1 timestamp appears in the replay. So it can confirm this fix on a log like that. I can't tell why your logs start a frame late, because I don't have them. No change requested. The three earlier notes are unchanged and need no action: the landing reading more than 0.05 above both references (:103), two range finders sharing one learned reading and clearance (AP_DAL_RangeFinder.cpp:116), and rofm_resend not being serialised (AP_DAL.cpp:361). Not covered: hardware, your logs, CI at this head, a FLOW_HGT_OVR Rover log at this head, the full suites, ChibiOS and Plane/Sub builds, a two range finder flight and a real log restart. Everything above is SITL with simulated sensor heights. |
Summary
If you land a copter on optical flow, the sensor goes out of focus over the last part of the descent, and what it returns there is not motion. EKF3 fuses it anyway and dead reckons a phantom horizontal velocity, which the position controller then chases on the way down.
FLOW_HGT_MINtells the filter the height below which this sensor's output is discarded instead. Discarding it on the ground too would let relative aiding time out after touchdown and drop an armed vehicle in LOITER into an EKF failsafe, so once the vehicle rests on the ground the flow is fused as zero, as it is before takeoff.Classification & Testing (check all that apply and add your own)
SITL,
Copter.OpticalFlowFocusHeight: hover in an asserted altitude band below the floor on flow-only nav, then inject a one-axis flow rate offset withSIM_FLOW_OFS_Xto stand in for an unfocused reading. Peak EKF groundspeed is 0.03 m/s with the floor set and 0.90-1.22 m/s with it disabled, over three runs, so the test's bounds sit either side of a 40x separation rather than close to either result.Copter.FlowFocusHoldAfterLandingfails if relative aiding stops through a flow-only landing below the floor, a dwell armed on the ground and a climb clear. A LOITER landing with an origin and the defaultDISARM_DELAYhas to disarm normally, also when the range finder reads 0.19 m on the ground againstRNGFND1_GNDCLR0.10; discarding the flow there gave "EKF variance" and a failsafe LAND. In a hover below the floor the flow has to be discarded and aiding stop. A hover within 5 cm of an old ground reading fused zero flow 39-45 times while that reading was kept, and 0 with the clears described below.The mechanism flew on a 4-inch flow quad as
EK3_FLOW_MIN_Hon the #33484 branch: on a descent through the floor (rangefinder 0.115 -> 0.054 m) the phantomVN/VEfell from +/-0.5 to +/-0.1 m/s and commanded roll/pitch from +/-14 to +/-2 deg. That rangefinder is valid to 1 cm, which is what made a 0.1 m floor reachable on it.This PR moves the parameter into the flow library and routes its value to the filter through the DAL, discards the sample where the flown build zeroed it, holds the check below the rangefinder minimum and adds a floor at the ground clearance. The discard and that floor have since flown on an ARK Flow octaquad with FLOW_HGT_MIN raised to 2.0 m, and zero flow at rest with FLOW_HGT_MIN 0.15: flow innovations stopped updating in a hover below the floor, relative aiding was kept through 8 s armed on the ground, and there was no failsafe. The learned ground reading and its two clears have not flown.
Description
Detail for reviewers below; not needed to review the change in outline.
The parameter lives in the flow library because the height is a property of the sensor, not of the estimator, and it sits beside the mounting position and
FLOW_HGT_OVRwhere someone configuring a flow sensor will look for it. It reaches the EKF as data travelling with the sample it applies to rather than as something the filter reads out of another library's parameters, which is what keeps Replay working.It rides in its own
ROFMrecord rather than inROFH.log_ROFHends in auint8_tand the DAL structs are not packed, so a float appended after the quality brings three bytes of padding the format string cannot describe: the struct is 47 bytes against 44 from the format, and SITL rejects the log structures at boot. Put before the quality instead, the float takes the byte an older log holds the quality in, and since Replay copies only the logged length and zeroes the rest, every flow sample of an old log would replay with quality 0 and be thrown away.ROFMis written only when the value changes and once at the start of each log, so it costs one record rather than four bytes a sample, and a log without one replays with the feature disabled. 97b5b04 splitRISJout ofRISIthe same way.Writing it only on change has one trap worth naming, since the next person to add a DAL record will hit it.
WriteLogMessagedrops everything written before logging starts, and a record that is only written when it changes never gets a second chance - the struct latches the value, the comparison then matches forever, and no record ever carries it.RISJis safe becausestart_frame()runs on the frame that forces a write when logging starts; a record written from a sensor callback has no such frame, so the value is held back until it can actually be logged, andROFMis written again when a new log starts. A SITL run withFLOW_HGT_MIN=3logsROFM3.0 then 0.0 across 430ROFH.AP_NavEKF2gains no behaviour but has to round-trip the field, because both estimators write the shared replay record and the two writes would otherwise differ on every sample and log it twice.The decision stays in the EKF rather than moving to the flow library with the parameter. Two reasons. The library has no height above ground, so it would need its own rangefinder dependency and its own tilt correction, giving a second and worse answer to a question the EKF already answers at the fusion time horizon. And suppressing the sample in the library is not the same behaviour: reporting quality 0 would stop the filter fusing on the ground too, where it fuses zero to keep aiding, while zeroing the rate leaves the body rate term in
flowRadXYcompso the filter sees apparent motion equal to the gyro. "This sample is untrustworthy" is a sensor statement; "therefore assume zero motion" is an estimator one.FLOW_HGT_MIN defaults to 0: sensors do not share a focus height, so set it from your own sensor. Independently of it, EKF3 holds flow off within 5 cm of the rangefinder ground clearance (the larger of
RNGFNDx_GNDCLRand 0.05 m), where the range is clamped and the flow is not motion. FLOW_HGT_MIN only raises that floor. On the 4-inch airframe (RNGFND1_GNDCLR0) that floor is the flown 0.10 m; a default airframe (0.10) gets 0.15 m.The height compared with the floor is the last range sample carried forward by the vertical position change since. That covers both the lag behind the rangefinder's median filter and the stretch below its minimum range, where it stops reporting. A carried height is trusted for 5 s, because it cannot see the ground change under a vehicle that has moved. Beyond that, a hold continues while the rangefinder that gave the sample reports out of range low, which it does on the ground until disarm, and the carried height still has the vehicle within half a metre of the floor. Both are needed: out of range low is also what a dead sensor reports, and it reads as on the ground, so without the height term such a sensor would have zero flow fused through a climb. Height resets move the carried position with them, so a reset does not read as movement.
Below the floor the flow is fused as zero when the vehicle is at rest and discarded otherwise. At rest is the range finder's
on_ground(), reading its ground clearance or too close to measure, so vehicle landed and flying flags, which read wrong in flight on some vehicles, are not used. A vehicle whose range finder reads more than its ground clearance on the ground, as a taller one withRNGFNDx_GNDCLRleft at its default does, misses that test, so a reading within 5 cm of the one taken on the ground before takeoff counts too. That reading is learned while disarmed from each stored flow sample, kept only under the floor, and cleared on disarm and once no flow sample has been stored for a second: a sensor that stops storing samples on the ground, as one reporting quality 0 does, would otherwise carry an old reading to ground that reads closer, and a hover within 5 cm of it would fuse zero flow. Off the ground below the floor, in a low hover, held-off flow counts as not ready to use, so aiding that times out there stays off rather than restarting from the fresh samples and timing out again every 5 s.What zero at rest does not cover, also said in the
RNGFNDx_MINandFLOW_HGT_MINdescriptions: too close counts as the ground, so whereRNGFNDx_MINis above the ground clearance plus 0.05 a hover below the minimum also fuses zero; a vehicle that moves on the ground with its range finder reading the ground, a plane rolling out, fuses zero while it moves. Rover builds leave the floor out altogether, as the EKF's otherFLOW_HGT_OVRuse already is Rover-only:OpticalFlowRangefinderOnGrounddrives a Rover withFLOW_HGT_OVR1.5, with it at 0, and a small one with both sensors 0.14 m up, at 0.40, 0.40 and 0.43 m/s against 0.39 (0.01 m/s withFLOW_HGT_OVR0 while the floor was built into Rover). Replay is its own build, so like that other use it runs the floor on a Rover log; and a landing that reads more than 5 cm further than before takeoff still discards the flow.Rover is built without the floor: it drives at its range finder ground clearance, where the floor would fuse zero flow, and a run-time vehicle-class test would put vehicle-specific code in the EKF. Replay is its own build, so it follows the logged vehicle class there and replays a Rover log without the floor, as Rover ran it; vehicle builds are unchanged. SITL Rover flow logs now replay to 0.000 m/s and 0.00 m of the flight, from 0.3 m/s and 4.5 m apart. A Rover log with
FLOW_HGT_OVRset still differs slightly, from the existing Rover-only height override, which Replay does not build.The cutoff uses rangefinder height at the vehicle origin rather than at the sensor, so a large
FLOW_POSlever arm biases it slightly.Split out of #33484 after review feedback there that the parameter belonged in the flow library. The first two commits here, adding
SIM_FLOW_OFS, are the same two commits that open #33484 - whichever lands first, the other should drop them. The rest is independent and the two can be reviewed in either order.