Skip to content

MAVLink: allow mission upload and clear in flight when the mission is not being executed - #11851

Open
raphaelhunziker1202-stack wants to merge 1 commit into
iNavFlight:maintenance-10.xfrom
raphaelhunziker1202-stack:mavlink-inflight-mission-upload-10x
Open

MAVLink: allow mission upload and clear in flight when the mission is not being executed#11851
raphaelhunziker1202-stack wants to merge 1 commit into
iNavFlight:maintenance-10.xfrom
raphaelhunziker1202-stack:mavlink-inflight-mission-upload-10x

Conversation

@raphaelhunziker1202-stack

Copy link
Copy Markdown

Summary

Since #10273 (INAV 8.0) the MSP path accepts mission uploads while armed as long as the WP mission is not actively being flown - that is what enables in-flight mission management over a serial telemetry radio. The MAVLink mission handlers still deny every transfer and clear outright while armed (MAV_MISSION_DENIED), so the transport that ELRS and Crossfire bridge natively cannot do what MSP can. This PR aligns the two policies.

What changes

  • A new mavlinkMissionEditBlocked() gate replaces the blanket ARMING_FLAG(ARMED) rejections in MISSION_COUNT, MISSION_CLEAR_ALL, MISSION_ITEM and MISSION_ITEM_INT. Edits are refused while armed AND (WP mode active OR the mission's own RTH leg running OR the on-the-fly mission planner active). The RTH-leg term protects the land/loiter decision at home, which reads the live list; the planner term prevents two writers on one list; the ARMED term keeps the disarmed path provably unchanged.
  • If the refused sender owns the receiving transfer, it is aborted via mavlinkAbortMissionUpload(MAV_MISSION_DENIED) so the retry engine stops soliciting items from a partner that was just denied.
  • Guided fly-to-here / altitude-target items are dispatched on the item itself (NAV_WAYPOINT with current == 2/3) instead of on transfer state, so a guided click can never be absorbed into a running upload.
  • mavlinkCommitMissionUpload() / mavlinkClearPersistedMission(): while armed, commit and clear apply to RAM only and skip persistence - saveNonVolatileWaypointList() refuses while armed, and a flash write mid-flight would stall the main loop. The uploaded mission survives disarm but not a reboot; persisting after landing remains the GCS's responsibility. An in-flight upload also collapses a loaded multi-mission set to the uploaded mission for the session.
  • Armed commits enforce the arm-time JUMP rules (no JUMP as first item, no self/adjacent target, sane repeat count, geo-referenced target) inside mavlinkResolveUploadedMissionJumps(), since an in-flight upload bypasses navigationIsBlockingArming(). Ground uploads are left to the arm-time check, keeping disarmed behaviour unchanged.
  • navigation.c: setWaypoint()'s post-upload clamp of activeWaypointIndex uses >= instead of > (0-based index, so index == waypointCount is already out of range), and a public isWpMissionPlannerActive() accessor is added.
  • Unit tests: MissionCountWhileArmedIsRejected becomes MissionCountWhileArmedStartsTransfer; new tests cover the WP-mode and mission-RTH rejections, the RAM-only armed commit and clear (persist not called), and the preserved rejection of clears during WP mode.

Why it is safe

The staged upload buffer with atomic commit and snapshot rollback means an in-flight upload never exposes a partially written list to the navigation state machine. One MSP-parity semantic is deliberately carried over from #10273 and worth an explicit maintainer sign-off: with nav_wp_mission_restart = RESUME, replacing the mission mid-flight keeps the waypoint index when the new mission is at least as long as that index.

Testing

  • CI on this branch: all targets, SITL on all platforms, unit tests green. (The Parameter Group Version Check failure is unrelated - check-pg-versions.sh currently errors with local: can only be used in a function on any PR; this change touches no parameter group.)
  • SITL end-to-end (Windows CI artifact, --path only, MSP receiver + fake GPS, pymavlink GCS): disarmed upload/download roundtrip byte-identical to the unpatched nightly; arming via MSP RC; upload while armed: ACCEPTED with the mission verified by download; clear while armed: ACCEPTED, RAM only. On the unpatched nightly the same suite shows MAV_MISSION_DENIED for both armed cases - a full A/B.

Motivation (measured)

Mission transfer over a radio link is latency-bound, not bandwidth-bound: over an RFD868x at 57600 baud (SiK 3.57, MAVLink framing off) a 40-waypoint MSP mission uploads in 10.7 s at 0.3 % link utilisation, with the cost per waypoint tracking twice the modem's MAX_WINDOW. Enabling the same in-flight capability on the MAVLink transport removes the need for a second radio on ELRS/Crossfire aircraft entirely.

A 9.x backport of the policy (without the staging machinery, matching the one-item-at-a-time semantics MSP has there) is prepared on raphaelhunziker1202-stack:mavlink-inflight-mission-upload with green CI, should it be wanted for master.

… not being executed

Aligns the MAVLink mission path with the MSP policy introduced in iNavFlight#10273,
where setWaypoint() accepts mission uploads while armed as long as the WP
mission is not actively being flown. The MAVLink handlers denied every
mission transfer and clear outright while armed.

Changes:
- New mavlinkMissionEditBlocked() gate used by MISSION_COUNT,
  MISSION_CLEAR_ALL, MISSION_ITEM and MISSION_ITEM_INT: edits are refused
  while armed AND (WP mode active OR the mission's own RTH leg is running
  OR the on-the-fly mission planner is active). The RTH-leg term protects
  the land/loiter decision at home, which reads the live list; the planner
  term prevents two writers on the same list. The ARMED term keeps the
  disarmed path provably unchanged.
- When the refused sender owns the receiving transfer, the transfer is
  aborted via mavlinkAbortMissionUpload(MAV_MISSION_DENIED) so the retry
  engine stops soliciting items from a partner that was just denied.
- Guided fly-to-here / altitude-target items are now dispatched on the
  item itself (NAV_WAYPOINT with current == 2 or 3) instead of on transfer
  state, so a guided click can never be absorbed into a running upload.
- mavlinkCommitMissionUpload() / mavlinkClearPersistedMission(): while
  armed the commit or clear applies to RAM only and skips persistence -
  saveNonVolatileWaypointList() refuses to run while armed, and a flash
  write mid-flight would stall the main loop. The uploaded mission
  survives disarm but not a reboot; persisting after landing remains the
  GCS's responsibility. An in-flight upload also collapses a loaded
  multi-mission set to the uploaded mission for the rest of the session.
- mavlinkResolveUploadedMissionJumps(): armed commits now enforce the same
  JUMP rules as the arm-time validation they bypass (no JUMP as first
  item, no self/adjacent targets, sane repeat count, geo-referenced
  target). Ground uploads are left to the arm-time check.
- navigation.c: setWaypoint()'s post-upload clamp of activeWaypointIndex
  uses >= instead of > (the index is 0-based, so index == waypointCount is
  already out of range); new public isWpMissionPlannerActive() accessor.
- Unit tests: the old MissionCountWhileArmedIsRejected asserts the new
  policy as MissionCountWhileArmedStartsTransfer; new tests cover the
  WP-mode and mission-RTH rejections, RAM-only armed commit and clear
  (persist not called), and the preserved clear rejection during WP mode.

The staged upload buffer with atomic commit and snapshot rollback means an
in-flight upload never exposes a partially written list to the navigation
state machine within a main-loop tick. Note the deliberate MSP-parity
semantics carried over from iNavFlight#10273: with nav_wp_mission_restart = RESUME,
replacing the mission mid-flight keeps the waypoint index when the new
mission is at least as long as the index.
@qodo-code-review

Copy link
Copy Markdown
Contributor

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Allow safe MAVLink mission edits while armed

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Allows MAVLink mission edits while armed unless the mission is actively executing.
• Keeps in-flight edits RAM-only and validates JUMP safety before atomic commit.
• Corrects waypoint bounds and tests allowed and blocked in-flight states.
Diagram

graph TD
    GCS["Ground station"] --> MAV["Mission handlers"] --> Gate{"Edit blocked?"} -->|Yes| Deny["Deny or abort"]
    Gate -->|No| Edit["Commit or clear"] --> RAM["RAM mission"] -->|Disarmed only| Store["Mission storage"]
    Nav["Navigation state"] --> Gate
Loading
High-Level Assessment

The gated, staged-edit approach is appropriate because it preserves atomic mission replacement while matching existing MSP behavior. Retaining the blanket armed prohibition would prevent the intended telemetry workflow, while persisting in flight would introduce flash-write latency and conflict with existing storage safeguards.

Files changed (4) +255 / -35

Enhancement (2) +97 / -12
mavlink_mission.cPermit guarded, RAM-only MAVLink mission edits in flight +96/-12

Permit guarded, RAM-only MAVLink mission edits in flight

• Replaces blanket armed-state rejection with a gate covering active WP mode, mission RTH, and on-the-fly planner activity. Armed uploads and clears remain RAM-only, unsafe JUMP commands receive arm-time-equivalent validation, denied owned transfers are aborted, and guided items are identified independently of transfer state.

src/main/mavlink/mavlink_mission.c

navigation.hDeclare the mission planner activity accessor +1/-0

Declare the mission planner activity accessor

• Publishes the navigation API used by the MAVLink mission-edit safety gate to detect planner activity.

src/main/navigation/navigation.h

Bug fix (1) +8 / -2
navigation.cExpose planner activity and correct active waypoint bounds +8/-2

Expose planner activity and correct active waypoint bounds

• Adds an accessor for on-the-fly mission planner activity so MAVLink can avoid concurrent mission edits. Corrects the active waypoint clamp because an index equal to the 0-based waypoint count is already invalid.

src/main/navigation/navigation.c

Tests (1) +150 / -21
mavlink_unittest.ccCover allowed and blocked in-flight mission edits +150/-21

Cover allowed and blocked in-flight mission edits

• Updates the armed upload expectation and adds tests for RAM-only upload and clear behavior. Covers rejection during active WP mode and mission RTH, with navigation-state stubs supporting the new gate.

src/test/unit/mavlink_unittest.cc

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Stale planner corrupts missions 🐞 Bug ≡ Correctness
Description
The new gate permits armed MAVLink upload or clear after the planner is disabled, but those
operations do not reset wpPlannerActiveWPIndex, which planner deactivation also preserves.
Reactivating the planner then writes at the stale index and derives the mission count from it,
resurrecting cleared waypoints or overwriting/truncating the newly uploaded mission.
Code

src/main/mavlink/mavlink_mission.c[R101-102]

+    return ARMING_FLAG(ARMED) &&
+        (FLIGHT_MODE(NAV_WP_MODE) || isWaypointMissionRTHActive() || isWpMissionPlannerActive());
Evidence
The new predicate allows editing as soon as wpMissionPlannerActive becomes false. Planner shutdown
only clears that flag and leaves wpPlannerActiveWPIndex intact; both MAVLink edit paths call
resetWaypointList(), which also omits the index. On reactivation, the planner skips its list reset
when the stale index is nonzero, writes at that index, increments it, and assigns the resulting
value as the new waypoint count.

src/main/mavlink/mavlink_mission.c[99-103]
src/main/mavlink/mavlink_mission.c[203-219]
src/main/mavlink/mavlink_mission.c[446-477]
src/main/navigation/navigation.c[5502-5514]
src/main/navigation/navigation.c[6440-6480]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Armed MAVLink mission upload and clear are now allowed after the on-the-fly planner is disabled, but the planner's progress index remains stale. Reset planner progress whenever an external operation replaces or clears the waypoint list so later planner activation starts from a consistent list.

## Issue Context
Planner deactivation does not clear `wpPlannerActiveWPIndex`. MAVLink clear and commit reset the mission metadata without resetting that index, while `missionPlannerSetWaypoint()` uses any nonzero index directly and then sets the mission count from it.

## Fix Focus Areas
- src/main/mavlink/mavlink_mission.c[203-219]
- src/main/mavlink/mavlink_mission.c[446-477]
- src/main/navigation/navigation.c[5502-5514]
- src/main/navigation/navigation.c[6440-6480]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +101 to +102
return ARMING_FLAG(ARMED) &&
(FLIGHT_MODE(NAV_WP_MODE) || isWaypointMissionRTHActive() || isWpMissionPlannerActive());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Stale planner corrupts missions 🐞 Bug ≡ Correctness

The new gate permits armed MAVLink upload or clear after the planner is disabled, but those
operations do not reset wpPlannerActiveWPIndex, which planner deactivation also preserves.
Reactivating the planner then writes at the stale index and derives the mission count from it,
resurrecting cleared waypoints or overwriting/truncating the newly uploaded mission.
Agent Prompt
## Issue description
Armed MAVLink mission upload and clear are now allowed after the on-the-fly planner is disabled, but the planner's progress index remains stale. Reset planner progress whenever an external operation replaces or clears the waypoint list so later planner activation starts from a consistent list.

## Issue Context
Planner deactivation does not clear `wpPlannerActiveWPIndex`. MAVLink clear and commit reset the mission metadata without resetting that index, while `missionPlannerSetWaypoint()` uses any nonzero index directly and then sets the mission count from it.

## Fix Focus Areas
- src/main/mavlink/mavlink_mission.c[203-219]
- src/main/mavlink/mavlink_mission.c[446-477]
- src/main/navigation/navigation.c[5502-5514]
- src/main/navigation/navigation.c[6440-6480]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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