Skip to content

AP_Compass: report specific failure reason when fit rejected#32757

Open
christianpetri wants to merge 1 commit into
ArduPilot:masterfrom
christianpetri:compass-cal-early-abort
Open

AP_Compass: report specific failure reason when fit rejected#32757
christianpetri wants to merge 1 commit into
ArduPilot:masterfrom
christianpetri:compass-cal-early-abort

Conversation

@christianpetri

@christianpetri christianpetri commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Extend the CompassCalibrator::Status enum with three new terminal failure values (FAILED_OFFSETS=8, FAILED_DIAG_SCALING=9, FAILED_RESIDUALS_HIGH=10) and refactor fit_acceptable() into a pure-query fit_acceptable_status() const so that MAG_CAL_REPORT.cal_status reports the specific reason calibration was rejected instead of always returning the generic FAILED.

Classification & Testing (check all that apply and add your own)

  • Checked by a human programmer
  • Automated test(s) verify changes (e.g. unit test, autotest)
  • Tested manually, description below (e.g. SITL)
  • Tested on hardware (MatekF405-Wing V2 / ArduPlane)

Description

Previously fit_acceptable() returned a silent false — the GCS and user had no way to know why calibration was rejected. This PR maps each rejection condition to a specific Status value that is transmitted in the MAG_CAL_REPORT.cal_status field.

New Status enum values (extend MAG_CAL_STATUS MAVLink enum):

Value Name Condition
8 FAILED_OFFSETS Any offset component ≥ COMPASS_OFFS_MAX
9 FAILED_DIAG_SCALING Diagonal or off-diagonal scaling out of range
10 FAILED_RESIDUALS_HIGH _fitness > sq(_tolerance) or NaN

FAILED_RADIUS (7) was already present. All four conditions were previously collapsed into FAILED (5).

Behaviour changes:

  • fit_acceptable() replaced by fit_acceptable_status() const — pure query, no side effects; returns the specific Status directly.
  • All call sites explicitly call set_status() with the returned value.
  • set_status(FAILED) is guarded against overwriting any of the five specific FAILED_* statuses (previously only FAILED_ORIENTATION and FAILED_RADIUS were protected).
  • Retry-hold timer (COMPASS_CAL_RETRY_REPORT_HOLD_MS = 500 ms) ensures a GCS polling MAG_CAL_REPORT at a few Hz reliably observes the specific failure reason before the next retry overwrites it.
  • update_cal_status(), failed(), is_calibrating(), and send_mag_cal_report() all handle the three new cases identically to FAILED_RADIUS.
  • Flash cost +64 bytes on MatekF405-Wing plane (measured locally).

Note: The new integer values (8/9/10) are transmitted immediately. GCS will display the raw number until a matching MAG_CAL_STATUS entry is added in the upstream mavlink/mavlink XML and a pymavlink bump lands in ArduPilot. The autotest uses getattr(mavutil.mavlink, 'MAG_CAL_FAILED_RESIDUALS_HIGH', 10) fallbacks pending that bump.

Related

Testing

Builds clean on SITL and MatekF405-Wing (./waf configure --board MatekF405-Wing && ./waf plane). Flash delta vs master: +64 bytes on MatekF405-Wing plane.

SITL autotest SITLCompassCalibration passes:

Tools/autotest/autotest.py test.Copter.SITLCompassCalibration

Two subtests verify the new codes:

  1. Tight-fit test (COMPASS_CAL_FIT=0.001) — all compasses report MAG_CAL_FAILED_RESIDUALS_HIGH (10) instead of MAG_CAL_FAILED (5).
  2. Selective-failure test (SIM_MAG1_OFS_X/Y/Z=2000) — one compass reports MAG_CAL_FAILED_OFFSETS (8); remaining compasses still succeed.

Hardware test on MatekF405-Wing V2 with ArduPlane: successful calibration completes normally. With a strong nearby magnet (field ~1500 mGauss), FAILED_RADIUS is reported in Mission Planner messages, confirming the specific status is visible instead of the previous silent generic failure.

AI-assisted contribution: This PR was developed with GitHub Copilot (Claude Sonnet 4.6). The human author has reviewed every line, run the tests, and is fully responsible for the submitted code.

@christianpetri

This comment was marked as outdated.

@IamPete1

Copy link
Copy Markdown
Member

I think 950 is too low, that is within the range of offsets that we might see in normal operation. I think we might be better off to try and improve the reporting at the end of a calibration attempt rather than guessing at the beginning.

@christianpetri
christianpetri force-pushed the compass-cal-early-abort branch from f4e053b to a428227 Compare April 13, 2026 23:03
@christianpetri

This comment was marked as outdated.

@rmackay9

Copy link
Copy Markdown
Contributor

Hi @christianpetri,

Thanks for this. One small thing is we don't accept merge commits so could you rebase on master to remove it?

@christianpetri
christianpetri force-pushed the compass-cal-early-abort branch 2 times, most recently from a6d793b to b0f7dc6 Compare April 14, 2026 11:15

@peterbarker peterbarker left a comment

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.

Create an autotest

  • want other compasses to keep calibrating if one of them gets a long field
  • we have lots of autotests already doing calibration, steal code from there
  • does this make calibration faster with a badly-affected compass?

Generally in favour of this at DevCallEU

What's the flash cost?

@christianpetri

This comment was marked as outdated.

@IamPete1

Copy link
Copy Markdown
Member

Not keen on the short circuit stuff, but I like the improved reporting at the end of the calibration. To get the most out of this we might need some GCS changes, in mission planner you can't see messages on the compass calibration page, you would have to go and look manualy.

@christianpetri
christianpetri force-pushed the compass-cal-early-abort branch from 560b8e1 to 09774b4 Compare April 19, 2026 20:26
@christianpetri

christianpetri commented Apr 19, 2026

Copy link
Copy Markdown
Contributor Author

Dropped the early-abort in pull_sample() as requested. Kept and improved the fit_acceptable() diagnostics: each failure condition (fitness NaN, radius out of range, offsets too large, diagonal/offdiagonal out of range, fitness over tolerance) now emits its own GCS message with the actual values, so all failures are visible in a single calibration attempt rather than just the first hit. Removed const from fit_acceptable() since GCS_SEND_TEXT is a side effect. This is consistent with the pre-existing messages already in calculate_orientation() and fix_radius().

@christianpetri

christianpetri commented Apr 20, 2026

Copy link
Copy Markdown
Contributor Author

SITL Test Plan

Manual SITL verification for the Mag(N) bad ... STATUSTEXT capture in the compass calibration dialog.

  • Mission Planner now shows the Mag(N) bad fit diagnostics directly in the compass calibration panel, so the failure reason is visible immediately without leaving the page. image

  • Close-up of the Onboard Mag Calibration box showing a specific rejection reason (offsets over limit) alongside the live calibration progress. image

Related PRs

Setup

sim_vehicle.py -v ArduCopter -f calibration --console --map

Connect Mission Planner via TCP 127.0.0.1:5762, navigate to Initial Setup → Mandatory Hardware → Compass.

Reset between tests

param set COMPASS_CAL_FIT 16
param set SIM_MAG1_OFS_X 0 ; param set SIM_MAG1_OFS_Y 0 ; param set SIM_MAG1_OFS_Z 0
param set SIM_MAG2_OFS_X 0 ; param set SIM_MAG2_OFS_Y 0 ; param set SIM_MAG2_OFS_Z 0
param set SIM_MAG3_OFS_X 0 ; param set SIM_MAG3_OFS_Y 0 ; param set SIM_MAG3_OFS_Z 0
sitl_stop ; sitl_attitude 0 0 0

Tests

# Scenario Setup Action Expected
1 Success baseline (defaults) Start → sitl_magcal → Accept Progress/report lines, no bad lines
2 Bad fit COMPASS_CAL_FIT 0.001 Start → sitl_magcal Mag(N) bad fit: fitness X tolerance Y per compass
3 Bad offset (early abort) SIM_MAG1_OFS_X/Y/Z 2000 Start → sitl_magcal Mag(0) bad fit: ofs (2000,2000,2000)>=1800
4 Cancel/restart (defaults) Start → Cancel mid-run → Start Cancel/restart of calibration via MP — diagnostics state resets cleanly (verified with #3706)

Pass criteria

  • Success path shows no bad lines.
  • Failure path shows Mag(N) bad ... lines, line-separated, latest-per-compass.
  • Restart is clean after Cancel.
  • Non-compass STATUSTEXT still flows to HUD/messages.
  • No UI freeze or crash.

@christianpetri
christianpetri force-pushed the compass-cal-early-abort branch from 09774b4 to 4fbb08d Compare April 20, 2026 01:01
@christianpetri

This comment was marked as outdated.

@christianpetri
christianpetri force-pushed the compass-cal-early-abort branch from 4fbb08d to 774fc4c Compare April 20, 2026 01:50
@christianpetri christianpetri changed the title AP_Compass: early abort calibration on extreme magnetic field AP_Compass: report specific failure reason when fit rejected Apr 20, 2026
@christianpetri
christianpetri force-pushed the compass-cal-early-abort branch from 774fc4c to 88799cf Compare April 20, 2026 22:28

@IamPete1 IamPete1 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like this! And Thanks for doing the Mission Planner fix too!

Comment thread libraries/AP_Compass/CompassCalibrator.cpp Outdated
Comment thread libraries/AP_Compass/CompassCalibrator.cpp Outdated
Comment thread libraries/AP_Compass/CompassCalibrator.cpp Outdated
Comment thread libraries/AP_Compass/CompassCalibrator.cpp Outdated
Comment thread libraries/AP_Compass/CompassCalibrator.cpp
christianpetri added a commit to christianpetri/mavlink that referenced this pull request Jun 2, 2026
…AL_STATUS

Add three new failure reason enum values (8/9/10) to MAG_CAL_STATUS, and add descriptions to existing entries 6 and 7 for consistency.

Implemented in ArduPilot firmware: ArduPilot/ardupilot#32757
@christianpetri
christianpetri marked this pull request as draft June 2, 2026 10:43
@hamishwillee

hamishwillee commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Merged in mavlink/mavlink.

@christianpetri

Copy link
Copy Markdown
Contributor Author

Hi @hamishwillee and @peterbarker, quick question about the MAVLink to ArduPilot flow.

Is opening that PR sufficient, or are there additional steps we should complete first before it can be brought into ArduPilot?
ArduPilot/mavlink#506

Thank you very much for your guidance.

@christianpetri

christianpetri commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Hi @hamishwillee and @peterbarker, quick question about the MAVLink to ArduPilot flow.

The mavlink enums from ArduPilot/mavlink#506 are now merged. The ardupilot submodule pointer still needs to be bumped to 13f2f735 - I opened #33642 for that. Is that the right process, or should the bump be included in this PR instead?

@hamishwillee

Copy link
Copy Markdown
Contributor

Yes. Looks like Peter merged it now.

Comment thread libraries/AP_Compass/CompassCalibrator.cpp Outdated
@christianpetri
christianpetri force-pushed the compass-cal-early-abort branch 8 times, most recently from bda23d2 to 70dd6df Compare July 11, 2026 12:15
@christianpetri
christianpetri force-pushed the compass-cal-early-abort branch from 70dd6df to 94b0ca3 Compare July 11, 2026 12:21
christianpetri added a commit to christianpetri/MissionPlanner that referenced this pull request Jul 11, 2026
…l dialogs

ArduPilot/ardupilot#32757 adds three new MAG_CAL_STATUS values sent in
MAG_CAL_REPORT.cal_status when calibration fit is rejected:
  8 = BAD_OFFSETS  - offset component >= COMPASS_OFFS_MAX
  9 = BAD_DIAG     - diagonal/off-diagonal scaling out of range
 10 = BAD_FITNESS  - RMS residual exceeds tolerance

Previously these fell through silently, leaving the cal dialog stuck.

Changes:
- Use calStatus > MAG_CAL_SUCCESS guard (future-proof for any further codes)
- Track failure status per compass in lastFailureStatus dictionary
- Show failure code in result label; set progress/picture to red on failure
- Add unit tests covering the guard logic and wire values

Note: Mavlink.cs is NOT changed - it is auto-generated. Named enum members
MAG_CAL_BAD_OFFSETS/DIAG/FITNESS arrive when mavlink/mavlink#2478 merges
and Mavlink.cs is regenerated. Tests use raw byte casts until then.
@christianpetri

christianpetri commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Physical Hardware Test Results

Tested on MatekF405-Wing with Mission Planner (using ArduPilot/MissionPlanner#3510 - MAVLink bump that includes new compass cal status values).

Firmware: commit 94b0ca3 (compass-cal-early-abort branch)
Flash cost: +64 bytes on MatekF405-Wing plane (measured)
Failure Mode Put a magnet close to the compass (Status: magfield). Note: Magnet should not be too close otherwise it gets stuck at approx. 33%.

The PR now correctly reports specific failure reasons instead of generic "Compass calibration failed":

Baseline Test: Success

image

Test 1: Radius Failure (Attempt 2, 74% complete)

image

Message: "Compass calibration failed: the radius of the fitted sphere is unrealistically small or large"

Test 2: Offset Magnitude Failure (Attempt 2, 12% complete)

image

Message: "Compass calibration failed: offset magnitude too large. [x:408.8 y:1988.0 z:-1747.3 limit ±1800]"


Notes

  • With latest Mission Planner PR (with MAVLink bump from mavlink/mavlink#2478): Shows descriptive failure messages as seen above
  • With older Mission Planner (no MAVLink bump): Shows raw MAVLink cal_status enum value (e.g., status=8 for MAG_CAL_FAILED_OFFSETS, status=9 for MAG_CAL_FAILED_DIAG_SCALING, status=10 for MAG_CAL_FAILED_RESIDUALS_HIGH)

The specific failure reporting works as designed! Users can now understand why calibration failed and take appropriate corrective action.

@christianpetri
christianpetri marked this pull request as ready for review July 11, 2026 20:53
@christianpetri

christianpetri commented Jul 11, 2026

Copy link
Copy Markdown
Contributor Author

Additional Notes: Progress Stall Detection

During testing, I observed a scenario where calibration progress can stall indefinitely:

Scenario: Magnetic Field Too Strong

When the magnetic field is extremely strong (magnet very close to compass), the calibration can get stuck at approximately 33% progress (during step two sample collection). The field distortion is so severe that the algorithm cannot collect valid samples across the required orientations, causing the progress bar to freeze.

Current Behavior

  • The PR correctly reports specific failure reasons when the fit is rejected after sample collection completes
  • However, if sample collection never completes (stalls), the calibration remains in the running state indefinitely
  • No timeout or stall detection is implemented

Attempted Solution

I explored adding a timeout/stall detection mechanism to abort calibration when progress stops advancing. The challenge is:

  • The calibration state machine uses AP_HAL::millis() for timing
  • SITL autotest uses simulated time, which makes timeout testing complex
  • Need to ensure timeout logic works correctly in both real hardware and SITL environments

Future Work

A stall detection mechanism would be a valuable addition:

  • Detect when no new samples are collected for N seconds
  • Report FAILED_SAMPLE_TIMEOUT or similar status
  • Requires careful design to work with both real time (hardware) and sim time (SITL)

I decided to defer this work to keep the current PR focused on the core improvement (specific failure reporting when fit validation fails). The stall detection feature can be addressed in a separate PR with proper SITL testing infrastructure.


Note: Users can manually cancel stalled calibrations via Mission Planner/GCS. The stall scenario requires unusually strong magnetic interference (magnet within a few cm / mm of compass).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants