.pr_agent_accepted_suggestions - iNavFlight/inav GitHub Wiki

                     PR 12050 (2026-09-27)                    
[correctness] Worktree builds use the wrong revision
Worktree builds use the wrong revision `get_git_head_revision()` replaces the worktree-specific Git directory with `commondir` before reading `HEAD`, so it resolves the main checkout’s revision instead. When a linked worktree is on a different branch or detached commit, configuration uses that incorrect hash for the revision embedded in the build.

Issue description

Linked worktrees have their own HEAD, but the new commondir handling reads the main checkout’s HEAD and reports its revision.

Fix Focus Areas

  • cmake/GetGitRevisionDescription.cmake[73-90]
  • cmake/GetGitRevisionDescription.cmake.in[18-39]

Recommended Fix

Preserve the worktree Git directory for reading and tracking HEAD. Resolve shared branch refs and packed refs through commondir separately, without replacing the directory used for HEAD.



                     PR 12047 (2026-09-27)                    
[correctness] Stack diagnostics report phantom usage
Stack diagnostics report phantom usage The new `_Min_Stack_Size` makes `taskStackCheck()` scan 8 KB for the `0xa5` watermark, but reset initialization never writes that watermark into the reserved stack region. After a cold or warm reset leaves any byte in the widened range with another value, the scan stops there and the CLI reports unused memory as consumed stack.

Issue description

The widened stack watermark scan assumes the entire reserved stack range contains 0xa5, but the AT32 reset handler initializes only .data and .bss. Initialize the reservation before normal calls begin so stack usage reflects bytes actually overwritten by stack activity.

Fix Focus Areas

  • src/main/target/link/at32_flash_f4_split.ld[22-24]
  • src/main/startup/startup_at32f435_437.s[53-60]
  • src/main/drivers/stack_check.c[48-64]

Recommended Fix

In Reset_Handler, immediately after setting sp and before calling any functions, fill the memory from _estack - _Min_Stack_Size through _estack with 0xa5. Preserve the hot-reboot area above _estack, and ensure the initialization itself does not push stack frames before painting completes.



                     PR 12036 (2026-09-25)                    
[reliability] Half-duplex replies can collide with requests
Half-duplex replies can collide with requests `mavlinkTunnelMspReplyIsBusy()` directly calls `mavlinkFlushTunnelMspReply(ingressPortIndex)` instead of only reporting pending state, bypassing the half-duplex backoff enforced by the telemetry loop. When a second complete tunnel request arrives on the same port while a prior reply is pending, receive processing has just updated `lastRxFrameUs`, yet dispatch can immediately transmit an old reply chunk during the protected guard interval.

Issue description

mavlinkTunnelMspReplyIsBusy() flushes a pending reply while processing an incoming request, bypassing the half-duplex RX guard applied by the telemetry runtime. A pipelined request received on the pending reply's ingress port can therefore trigger transmission during the protected interval instead of being safely dropped.

Fix Focus Areas

  • src/main/fc/fc_mavlink.c[92-96]
  • src/main/mavlink/mavlink_runtime.c[295-305]
  • src/main/mavlink/mavlink_runtime.c[322-337]

Recommended Fix

Make mavlinkTunnelMspReplyIsBusy() non-transmitting so it only reports whether a reply remains pending, and leave transmission to mavlinkRuntimeHandle(), whose telemetry-cycle flush already enforces half-duplex backoff. The incoming pipelined request should then be dropped as documented. Add a same-port half-duplex test proving that a pipelined request neither writes pending reply chunks nor executes until the backoff expires.



                     PR 12032 (2026-09-25)                    
[correctness] Slow device transfers run too fast
Slow device transfers run too fast `spiDivisorMapFast[SPI_CLOCK_SLOW]` uses DIV32, although the public slow setting is documented as approximately 1 MHz and the corresponding half-rate map uses DIV64, so the full-rate map requires DIV128. After the new assignments route H7 SPI2–4 and F7 SPI4 through that table, any device selecting `BUS_SPEED_SLOW` runs at roughly 3.1–3.75 MHz rather than roughly 0.8–0.94 MHz.

Issue description

The newly assigned fast divisor map contains DIV32 for the slow speed, causing affected devices requesting the approximately 1 MHz setting to run above 3 MHz.

Fix Focus Areas

  • src/main/drivers/bus_spi_hal_ll.c[76-82]
  • src/main/drivers/bus_spi_hal_ll.c[157-169]
  • src/main/drivers/bus_spi_hal_ll.c[248-248]

Recommended Fix

Change the SPI_CLOCK_SLOW entry in spiDivisorMapFast from LL_SPI_BAUDRATEPRESCALER_DIV32 to LL_SPI_BAUDRATEPRESCALER_DIV128, preserving the intended speed relationship with the half-rate map, and update its frequency comment if necessary.



                     PR 12006 (2026-09-22)                    
[correctness] MAVLink SITL tests cannot start
MAVLink SITL tests cannot start The versioned SITL artifact changes from `inav_9.1.1_SITL` to `inav_10.0.0_SITL`, while the MAVLink mission, multi-port, and sanity test configurations retain the old path. After a clean 10.0.0 build, the mission runner exits on its missing-file check and the Python tests attempt to launch a nonexistent executable.

Issue description

The version bump changes the generated SITL executable from inav_9.1.1_SITL to inav_10.0.0_SITL, but checked-in MAVLink test launchers still use the old path, so those tests fail after a clean build.

Fix Focus Areas

  • src/test/mavlink/missions/run_mavlink_mission_tester.sh[10-10]
  • src/test/mavlink/ports/test_config_multiport4_sweep_rc460800_tele115200.yaml[2-2]
  • src/test/mavlink/sanity/mavlink_sitl_sanity.yaml[2-2]

Recommended Fix

Replace the hardcoded inav_9.1.1_SITL references with a version-independent path or update them to the generated inav_10.0.0_SITL artifact. Prefer deriving the path from the configured project version so future version bumps do not leave these test entry points stale, then run the affected MAVLink SITL tests.



                     PR 11996 (2026-09-21)                    
[reliability] Invalid turn geometry corrupts arc plans
Invalid turn geometry corrupts arc plans `fwLineIntersect` uses an inverted ` minAbsCross` guard rejected it. When non-finite position or direction data reaches the tracking-S or corner planner, it writes NaN coordinates and reports an intersection, so those planners engage an invalid arc instead of their fallback.

Issue description

fwLineIntersect() no longer preserves the old guard's behavior for NaN cross products: fabsf(cross) <= minAbsCross is false for NaN, so the helper calculates and writes NaN outputs and returns true.

Fix Focus Areas

  • src/main/navigation/navigation_fixedwing_turn_math.c[74-80]

Recommended Fix

Use the original positive acceptance condition: only calculate and write the intersection when fabsf(cross) > minAbsCross; otherwise return false. This retains the previous fallback behavior for both near-parallel and non-finite geometry.



                     PR 11986 (2026-09-20)                    
[security] Fork authors can comment on other issues
Fork authors can comment on other issues The `Post or update comment` step parses `prNumber` from the pull-request-controlled `pr_number.txt` artifact and passes it directly as `issue_number` under a token with issue and pull-request write permissions. When a fork changes the unprivileged checker workflow and supplies a chosen artifact value, the successful privileged `workflow_run` job reaches the comment APIs for any repository issue or pull request.

Issue description

The privileged workflow_run comment job trusts a pull-request-controlled artifact to identify the issue or pull request receiving a bot comment. A fork author can alter the source workflow to upload a chosen pr_number.txt, redirecting the job's write-capable token to an unrelated repository target.

Fix Focus Areas

  • .github/workflows/pg-version-check-comment.yml[18-40]
  • .github/workflows/pg-version-check-comment.yml[88-117]
  • .github/workflows/pg-version-check.yml[57-73]

Recommended Fix

Derive the target pull request number exclusively from trusted github.event.workflow_run.pull_requests metadata, verify that exactly one associated pull request exists, and fail closed otherwise. Stop uploading, downloading, or reading pr_number.txt; continue treating the downloaded checker output as untrusted text and post it only to the pull request associated with the triggering workflow run. Optionally validate the originating repository and head SHA before processing the artifact.


[correctness] Three tests cannot start the simulator
Three tests cannot start the simulator `project(INAV VERSION 9.1.1)` changes `FIRMWARE_VERSION`, which `cmake/sitl.cmake` embeds in the executable name, while three runnable test configurations still request `inav_9.1.0_SITL`. After a clean build, the mission launcher fails its file check and the sanity and multi-port runners pass a nonexistent path to their process launchers.

Issue description

The project version bump renames the generated simulator executable, but operational test launchers and configurations still point at the previous versioned filename.

Fix Focus Areas

  • CMakeLists.txt[56-76]
  • cmake/sitl.cmake[124-124]
  • src/test/mavlink/missions/run_mavlink_mission_tester.sh[10-10]
  • src/test/mavlink/missions/run_mavlink_mission_tester.sh[146-162]
  • src/test/mavlink/sanity/mavlink_sitl_sanity.yaml[2-2]
  • src/test/mavlink/ports/test_config_multiport4_sweep_rc460800_tele115200.yaml[2-2]

Recommended Fix

Update all operational simulator paths to the 9.1.1 executable and preferably derive or discover the filename from the CMake project version so future version bumps do not break these test runners.



                     PR 11972 (2026-09-19)                    
[reliability] Scheduled cleanup fails before starting
Scheduled cleanup fails before starting The scheduled `sweep` job sets `permissions: {}` and then invokes `actions/checkout`, although checkout requires repository contents read permission to fetch the script. Scheduled and manual runs reach this step before Python executes, so the safety-net cleanup cannot process releases when the restricted token is rejected.

Issue description

The scheduled cleanup job removes all GITHUB_TOKEN permissions before using actions/checkout, preventing the job from reliably fetching the cleanup script.

Fix Focus Areas

  • .github/workflows/cleanup-pr-test-builds-scheduled.yml[17-23]

Recommended Fix

Replace permissions: {} with an explicit permissions block granting contents: read. Keep all other token permissions disabled.


[correctness] Fresh test builds can be deleted
Fresh test builds can be deleted `main` calculates staleness from the one-time `list_releases()` snapshot and later calls `delete_release(tag)` without confirming that the tag still denotes that release. For an old open PR, the publisher can delete and recreate the same tag between those operations, so the sweep removes newly published firmware rather than the stale snapshot.

Issue description

The scheduled sweep deletes by reusable tag after deciding staleness from an earlier snapshot, allowing a concurrent publisher run to replace the release before deletion.

Fix Focus Areas

  • .github/scripts/cleanup-old-pr-test-builds.py[86-118]
  • .github/workflows/pr-test-builds.yml[70-103]

Recommended Fix

Before an age-based deletion, fetch the current release again and verify that its publication timestamp still matches the listed release and remains beyond the threshold. If it changed or disappeared, skip it so a concurrently refreshed build is preserved.



                     PR 11970 (2026-09-18)                    
[correctness] Some motor firmware fails to compile
Some motor firmware fails to compile `impl_pwmBurstDMASetCircular` is defined with the invalid return-type token `nemvoid` instead of `void`. Any HAL target enabling burst DMA compiles this section, so compilation stops before the motor output code can be linked.

Issue description

The burst-DMA implementation uses nemvoid, which is not a valid C return type and causes affected HAL firmware builds to fail.

Fix Focus Areas

  • src/main/drivers/timer_impl_hal.c[543-543]

Recommended Fix

Replace nemvoid with void so the implementation matches the declaration in timer_impl.h and compiles for HAL targets using burst DMA.



                     PR 11969 (2026-09-18)                    
[correctness] Fixed-wing gains are falsely attenuated
Fixed-wing gains are falsely attenuated `pidResetTPAFilter` still seeds `fixedWingTpaFilter` with the idle throttle value, while `tpaPitchThrottleAdjustment` now filters a zero-centered pitch adjustment and adds that result to the current throttle. With pitch compensation and throttle-based TPA enabled, initialization and repeated launch resets therefore begin near a +1000 adjustment, making the effective throttle approach its maximum and reducing fixed-wing gains until the default two-second filter converges.

Issue description

The fixed-wing TPA filter now processes a zero-centered pitch adjustment, but its lifecycle still initializes it with an absolute idle-throttle value and can preserve stale state while TPA is inactive.

Fix Focus Areas

  • src/main/flight/pid.c[467-472]
  • src/main/flight/pid.c[1300-1331]
  • src/main/navigation/navigation_fw_launch.c[318-322]

Recommended Fix

Reset fixedWingTpaFilter to zero now that its input is an adjustment rather than absolute throttle. Ensure its state is also updated or reset while pitch TPA is inactive so reactivation cannot reuse an adjustment from an earlier attitude.


[maintainability] Protocol docs point to a removed field
Protocol docs point to a removed field The profile member is renamed to `throttle.tpa_breakpoint`, but the protocol message definitions and generated reference still describe it as `throttle.pa_breakpoint`. Developers following those references encounter a field that no longer exists across several tuning request and response definitions.

Issue description

The protocol documentation retains references to the removed throttle.pa_breakpoint member after the profile field was renamed to throttle.tpa_breakpoint.

Fix Focus Areas

  • docs/development/msp/msp_messages.json[4022-4027]
  • docs/development/msp/msp_messages.json[5127-5132]
  • docs/development/msp/msp_messages.json[7447-7452]
  • docs/development/msp/msp_messages.json[7541-7546]
  • docs/development/msp/README.md[1994-2000]
  • docs/development/msp/README.md[2421-2427]
  • docs/development/msp/README.md[3315-3321]
  • docs/development/msp/README.md[3336-3342]

Recommended Fix

Replace every protocol-documentation reference to throttle.pa_breakpoint with throttle.tpa_breakpoint, then regenerate the rendered protocol README from the corrected message definitions.



                     PR 11951 (2026-09-14)                    
[reliability] Fork detections cannot post warnings
Fork detections cannot post warnings The repaired output handoff now reaches `github.rest.issues` from a workflow triggered directly by `pull_request`, whose token is read-only for fork-originated runs. When a fork changes a persisted layout without a version bump, the create or update request reaches the catch handler and fails the job without posting the diagnostic comment.

Issue description

The checker executes untrusted pull-request code and then tries to post its result using the same pull-request workflow token. Fork runs cannot obtain the required write access, so genuine detections fail during comment creation instead of publishing the warning.

Fix Focus Areas

  • .github/workflows/pg-version-check.yml[37-143]
  • .github/workflows/pr-test-builds.yml[1-36]

Recommended Fix

Keep checking in the unprivileged pull_request workflow, upload the checker result and validated pull-request number as artifacts, and add a privileged workflow_run consumer that downloads those artifacts and posts or updates the comment. Follow the repository's existing pr-test-builds.yml pattern and do not execute pull-request code in the privileged workflow.


[correctness] Renamed settings structs skip checks
Renamed settings structs skip checks `check()` builds `changed` with `git diff --name-only`, then loads both revisions using each resulting path rather than preserving the old and new names of a rename. When a persisted struct's file is renamed while its layout changes, the old definition never enters `old_structs`, so the intersection at the comparison loop silently excludes it.

Issue description

The checker loses the base-side definition when a parameter-group struct is modified as part of a detected file rename. Because comparison requires the alias in both structure maps, this allows a persisted layout change to pass without a version increase.

Fix Focus Areas

  • .github/scripts/check-pg-versions.py[147-163]
  • .github/scripts/test-check-pg-versions.py[53-67]

Recommended Fix

Collect both old and new paths for changed files, such as by disabling rename collapsing with git diff --no-renames --name-only, and load each path from whichever tree contains it. Add a fixture that renames a registered struct header, changes its layout without increasing the version, and expects exit code 1.



                     PR 11947 (2026-09-14)                    
[correctness] Slow telemetry drops the ESC link
Slow telemetry drops the ESC link `srxl2MotorSetTelemetryRate()` maps the 0.2 Hz setting to one request every 25 control frames, exactly 500 ms, while `SRXL2_RUNNING` declares the link dead after 500 ms without receiving a frame. Before the first reply to that request can be drained, the same processing pass meets the `>= 500` timeout, returns the port to low baud, and leaves the powered ESC behind at the negotiated baud.

Issue description

The 0.2 Hz telemetry mode schedules its next possible reply no earlier than the link timeout, so the driver disconnects before receiving it.

Fix Focus Areas

  • src/main/io/motor_srxl2.c[184-188]
  • src/main/io/motor_srxl2.c[811-824]
  • src/main/io/motor_srxl2.c[1011-1029]

Recommended Fix

Base the link timeout on the selected request interval with allowance for multiple unanswered requests, or use a separate sufficiently frequent link-health exchange independent of the telemetry delivery rate.


[reliability] Powered ESCs can receive full throttle
Powered ESCs can receive full throttle `srxl2MotorCalibrationManual()` checks only armed state and port presence before accepting the manual-high phase, omitting the battery-presence guard used by unattended calibration. Running `esc_calibrate high` or sending the corresponding MSP command while a sensed battery is connected makes the next control frame command 2000 microseconds to an ESC that can already drive the motor.

Issue description

Manual-high calibration can command full throttle while a battery-sensed ESC is already powered.

Fix Focus Areas

  • src/main/io/motor_srxl2.c[826-876]
  • src/main/fc/cli.c[4807-4812]
  • src/main/fc/fc_msp.c[3831-3834]

Recommended Fix

For SRXL2_CAL_HIGH_MANUAL, reject the request with SRXL2_CAL_REJECT_BATTERY_PRESENT whenever battery sensing is configured and the battery state is not BATTERY_NOT_PRESENT; retain the manual fallback only where voltage cannot be sensed.


[correctness] Unsupported boards can leave every motor unwritten
Unsupported boards can leave every motor unwritten `validateAndFixConfig()` preserves `PWM_TYPE_SRXL2` when `USE_MOTOR_SRXL2` is absent, although the branch assigning `motorWritePtr` and the SRXL2 link-health arming check are compiled out while the protocol remains selectable and classified as non-timer-based. Selecting or restoring SRXL2 on such firmware retains the null motor writer, bypasses timer initialization and the insufficient-output error, and can still permit arming without functional motor output.

Issue description

Firmware built without USE_MOTOR_SRXL2 can retain and select PWM_TYPE_SRXL2 even though no compiled driver case assigns a motor writer and the SRXL2 link-health arming check is absent. Since SRXL2 is classified as a non-timer output, timer allocation is also bypassed, leaving motor writes as no-ops without a visible PWM initialization or insufficient-output error.

Fix Focus Areas

  • src/main/fc/config.c[267-277]
  • src/main/drivers/pwm_output.c[599-634]
  • src/main/drivers/pwm_mapping.c[66-94]
  • src/main/drivers/pwm_mapping.c[438-455]

Recommended Fix

Add explicit validation for builds without USE_MOTOR_SRXL2 so PWM_TYPE_SRXL2 is rejected or replaced with a supported protocol such as PWM_TYPE_MULTISHOT, with a visible configuration or output error where rejection is used. Keep the existing DSHOT-specific validation separate, prevent unavailable firmware from advertising SRXL2 as selectable, and ensure an unsupported protocol cannot reach the non-timer initialization path.


[correctness] Some Smart ESCs can never establish a link
Some Smart ESCs can never establish a link `srxl2ProcessEsc()` always sends discovery handshakes only to `SRXL2_ESC_ID_FIRST` (`0x40`) despite accepting device IDs through `0x4F`. An ESC configured with a nonzero unit ID does not announce itself and is never addressed by this poll, so its port remains in polling and arming stays blocked.

The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution Issue description SRXL2 discovery polls only device ID 0x40, although the driver recognizes ESC IDs 0x40 through 0x4F and documents that nonzero-unit ESCs require polling because they do not announce. Such an ESC therefore cannot complete the handshake. Fix Focus Areas

  • src/main/io/motor_srxl2.c[74-77]
  • src/main/io/motor_srxl2.c[980-998] Recommended Fix Track a discovery device ID per ESC instance and rotate through 0x40 to 0x4F while polling, resetting the scan when a valid handshake is received. Preserve the one-ESC-per-port behavior and only finalize negotiation with the ID that replied.

[correctness] Slow telemetry vanishes between samples
Slow telemetry vanishes between samples `srxl2MotorGetTelemetry()` rejects every reading after a fixed one-second age, and `escSensorUpdate()` then increments its age on every busy-loop callback rather than on expected telemetry opportunities. At the offered 0.5 Hz rate, readings disappear for roughly half of each interval, while consumers using voltage, current, temperature, and combined ESC data repeatedly see no valid sensor.

Issue description

The fixed one-second freshness window is shorter than supported telemetry intervals and invalidation is advanced at busy-loop frequency.

Fix Focus Areas

  • src/main/io/motor_srxl2.c[187-188]
  • src/main/io/motor_srxl2.c[1094-1103]
  • src/main/sensors/esc_sensor.c[273-303]

Recommended Fix

Derive freshness from the configured delivery interval with jitter and missed-response allowance, and update dataAge only when an expected sample opportunity is missed rather than on every realtime callback.


[correctness] Missing telemetry becomes a zero reading
Missing telemetry becomes a zero reading `srxl2DecodeEscTelemetry()` clears every field and skips sentinel-valued measurements, but then sets one overall `valid` flag without recording which individual values were absent. When an ESC omits current, voltage, temperature, or RPM while reporting other fields, downstream consumers receive a fresh zero rather than an unavailable measurement.

Issue description

Per-field no-data sentinels are converted into valid zero readings because telemetry has only one aggregate validity flag.

Fix Focus Areas

  • src/main/io/motor_srxl2.c[462-492]
  • src/main/io/motor_srxl2.h[52-69]
  • src/main/sensors/esc_sensor.c[282-299]

Recommended Fix

Add validity flags for each exported measurement and propagate them into consumers, or reject/retain fields explicitly so sentinel values are never presented as fresh numeric zeroes.


[correctness] Reverse mode always reports inactive
Reverse mode always reports inactive `initActiveBoxIds()` advertises `BOXTHRUSTREVERSE`, but `packBoxModeFlags()` never copies `IS_RC_MODE_ACTIVE(BOXTHRUSTREVERSE)` into the outgoing active-box bitmask with `CHECK_ACTIVE_BOX`. When the reverse switch is active, the motor driver can consume its runtime state internally while Configurator and other MSP clients still receive a clear bit and display the mode as inactive.

Issue description

The new Thrust Reverse mode is advertised in the active-box list and consumed internally, but omitted from MSP active-mode status serialization, preventing MSP clients from displaying its active state.

Fix Focus Areas

  • src/main/fc/fc_msp_box.c[399-419]
  • src/main/fc/fc_msp_box.c[426-525]
  • src/main/fc/fc_tasks.c[335-340]

Recommended Fix

Add CHECK_ACTIVE_BOX(IS_RC_MODE_ACTIVE(BOXTHRUSTREVERSE), BOXTHRUSTREVERSE) to packBoxModeFlags() alongside the other mode-state serialization entries. Apply the same feature conditions used when advertising the box so the serialized flag corresponds to its active-box-list position.


[correctness] Invalid reverse settings fail silently
Invalid reverse settings fail silently The setting range accepts channels 1 through 4 and `initActiveBoxIds()` advertises reverse for every nonzero value, although the documented ESC range is 5 through 9 and channel 1 aliases throttle. A user can therefore configure and activate a visible reverse mode that either gets disabled internally or sends a channel the ESC does not support, with no validation error.

Issue description

Configuration accepts unsupported reverse channels and still advertises an operational reverse mode.

Fix Focus Areas

  • src/main/fc/settings.yaml[884-890]
  • src/main/fc/fc_msp_box.c[414-419]
  • src/main/io/motor_srxl2.c[761-808]

Recommended Fix

Validate the value as exactly zero or 5 through 9 during configuration loading, reject invalid CLI/MSP writes, and advertise the mode only when the normalized channel is usable.


[correctness] Some builds lose the calibration CLI
Some builds lose the calibration CLI Both `cliEscCalibrate()` and its command-table entry are nested under `USE_USB_MSC` in addition to `USE_MOTOR_SRXL2`, despite calibration having no mass-storage dependency. SRXL2 builds without the optional MSC feature—including the explicitly enabled SITL configuration and applicable H7 and AT32 targets—omit the documented `esc_calibrate` command while retaining the driver and MSP calibration paths.

Issue description

The SRXL2 calibration CLI implementation and command registration are accidentally compiled only when unrelated USB mass-storage support is enabled. SRXL2 targets without the optional MSC build feature therefore cannot use the documented CLI calibration flow even though the driver API requires only SRXL2 support.

Fix Focus Areas

  • src/main/fc/cli.c[4745-4828]
  • src/main/fc/cli.c[5107-5115]
  • src/main/target/SITL/target.h[57-65]
  • cmake/stm32.cmake[327-338]
  • src/main/io/motor_srxl2.c[826-876]

Recommended Fix

Move the SRXL2 calibration helper and command registration outside the USE_USB_MSC blocks while retaining their USE_MOTOR_SRXL2 guards. Leave only the existing msc command under USE_USB_MSC.


[reliability] Malformed traffic can corrupt link negotiation
Malformed traffic can corrupt link negotiation `srxl2DrainRx()` accepts every CRC-valid frame from five bytes onward, and `srxl2HandleFrame()` dispatches a `Handshake` without requiring `sizeof(Srxl2HandshakeFrame)`. A short frame marked as a handshake then makes `srxl2HandleHandshake()` consume payload fields that were not received, allowing stale receive-buffer bytes to determine the discovered device and baud negotiation.

The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution The issue below was found during a code review. Follow the provided context and guidance below and implement a solution Issue description The SRXL2 receive parser accepts a CRC-valid five-byte frame and dispatches it as a handshake without checking the handshake structure length. The handshake handler then reads fields outside the logical received frame, so stale buffer contents can affect link setup. Fix Focus Areas

  • src/main/io/motor_srxl2.c[498-553]
  • src/main/io/motor_srxl2.c[567-588]
  • src/main/io/motor_srxl2.c[590-618]
  • src/main/rx/srxl2_types.h[81-86] Recommended Fix Before calling srxl2HandleHandshake(), require len >= sizeof(Srxl2HandshakeFrame) (or require the protocol-defined exact handshake length). Reject malformed handshake frames without updating state, device ID, handshake counters, or baud capabilities.


                     PR 11936 (2026-09-13)                    
[observability] Technicians see stale probe results
Technicians see stale probe results `Connect` writes the new debug slots only after `BL_ConnectEx` succeeds, without clearing them before each probe, and the earlier successful `Stk_ConnectEx` return bypasses those writes entirely. With ESC debugging enabled, a failed probe therefore retains a previous identifier and family while an Atmel device detected through the STK path never records family 2, so the displayed diagnostics do not describe the current attempt.

Issue description

The new ESC debug values are updated only along the BL bootloader success path, leaving stale values after failures and omitting successful STK detections.

Fix Focus Areas

  • src/main/io/serial_4way.c[347-393]

Recommended Fix

Clear the ESC identifier and family debug slots at the start of every Connect call. Record the signature and appropriate family before returning from either the STK or BL success path, including family 2 for Atmel devices.



                     PR 11933 (2026-09-12)                    
[correctness] Sampling forces second-gyro logging
Sampling forces second-gyro logging `testBlackboxConditionUncached()` enables `gyroRaw2` solely from `gyro.secondaryInitialized` instead of requiring a dedicated Blackbox include flag. Whenever `gyro_secondary_enabled` successfully initializes the sensor, every log includes the channel and users cannot independently avoid its bandwidth cost or enable it through `blackbox GYRO_2`.

Issue description

Secondary gyro sampling automatically enables gyroRaw2 logging, so the independently configurable blackbox GYRO_2 switch described by the feature is unavailable.

Fix Focus Areas

  • src/main/blackbox/blackbox.c[864-867]
  • src/main/blackbox/blackbox.h[24-39]
  • src/main/fc/cli.c[185-204]
  • docs/Blackbox.md[171-175]

Recommended Fix

Add a dual-gyro Blackbox feature-mask bit and matching GYRO_2 CLI name, then require both that include flag and gyro.secondaryInitialized in the secondary field condition. Keep the new flag disabled by default and update the documentation to describe sampling and logging as separate switches.


[correctness] Some boards never log the second gyro
Some boards never log the second gyro `gyroInit()` derives the secondary bus tag as `1` only for primary tag `0` and as `0` for every other configured tag, instead of selecting another registered physical sensor. AETH743Basic registers its two sensor positions as tags `0` and `2`, so the default tag-0 primary probes nonexistent tag `1`, leaves `secondaryInitialized` false, and causes Blackbox to omit `gyroRaw2`; several other dual-sensor targets assign both positions tag `0` and are unreachable as well.

Issue description

Secondary gyro selection assumes every dual-sensor target uses tags 0 and 1, but repository targets also use 0 and 2 or duplicate tag 0. This silently prevents detection and Blackbox logging on those boards.

Fix Focus Areas

  • src/main/sensors/gyro.c[343-358]
  • src/main/target/AETH743Basic/target.c[30-36]
  • src/main/target/BRAHMA_H7/target.c[29-30]

Recommended Fix

Replace arithmetic tag inversion with an explicit target-declared physical sensor-slot mapping. Normalize duplicate physical descriptors to unique logical slots where necessary, support the existing 0-and-2 layout, and add coverage for both conventional and nonconventional tag pairs.


[correctness] Simulator builds fail on a warning
Simulator builds fail on a warning `performGyroCalibration()` adds the `persist` parameter, but its sole reference is inside `#ifndef USE_IMU_FAKE`, so preprocessing leaves that parameter unused in fake-sensor builds. SITL defines `USE_IMU_FAKE`, and the CI target combines `-Wall -Wextra` with warnings-as-errors, so compilation stops on `-Wunused-parameter`.

Issue description

The new persist parameter is unused when USE_IMU_FAKE removes its only reference, causing warnings-as-errors SITL builds to fail.

Fix Focus Areas

  • src/main/sensors/gyro.c[418-442]

Recommended Fix

Add UNUSED(persist) in the USE_IMU_FAKE branch, or restructure the conditional so persist is evaluated in every build configuration. Verify the SITL target with warnings-as-errors enabled.


[reliability] Vibration can zero the second-gyro log
Vibration can zero the second-gyro log `gyroStartCalibration()` starts the secondary's two-second calibration with `allowFailure` set to false, while `gyroIsCalibrationComplete()` continues to gate arming on the primary alone. If the aircraft arms before that window completes and vibration exceeds the threshold, calibration restarts indefinitely and `gyroUpdate()` writes zeroes to `gyroRaw2` each cycle, making the second-gyro channel useless for that flight.

Issue description

Secondary calibration may continue retrying after arming, causing every secondary Blackbox sample to remain zero under sustained vibration.

Fix Focus Areas

  • src/main/sensors/gyro.c[383-415]
  • src/main/sensors/gyro.c[600-610]

Recommended Fix

Make secondary calibration terminate after one window by allowing failure, then continue producing aligned and scaled samples with a zero offset when calibration fails instead of restarting forever. Add coverage for noisy secondary input during early arming and verify that primary updates remain unaffected.



                     PR 11930 (2026-09-11)                    
[correctness] Valid firmware builds are skipped
Valid firmware builds are skipped Both consumer jobs now require `github.event.workflow_run.path` to equal the firmware workflow path, but no repository fixture or convention establishes that this property is populated for `workflow_run` events. When the property is absent, the condition evaluates false and valid pull-request builds are skipped, preventing both artifact consumers from publishing their reports or builds.

Issue description

The consumers require github.event.workflow_run.path, but the repository does not establish that this property is present in workflow_run event payloads. An absent property makes the condition false, so valid pull-request firmware runs do not reach the artifact consumers.

Fix Focus Areas

  • .github/workflows/pr-test-builds.yml[17-20]
  • .github/workflows/ci-size-report.yml[138-142]

Recommended Fix

Replace the path-based guard with a reliably available identifier for the real firmware workflow, such as its workflow ID or another supported discriminator. Preserve the existing pull-request and successful-conclusion checks, and ensure both consumers reject non-code-change.yaml without excluding valid .github/workflows/ci.yml runs.



                     PR 11924 (2026-09-10)                    
[reliability] Bus faults skew external compass fits
Bus faults skew external compass fits `compassUpdate` records `magADCRaw` whenever the compass driver returns true, but `mlx90393Read` ignores the bus-read result, parses its zero-initialized buffer, and unconditionally reports success. When an MLX90393 transaction fails, three artificial zero coordinates reach both the debug channel and blackbox log as valid samples, contaminating any external ellipsoid fit that consumes them.

Issue description

The new raw magnetometer logger trusts each driver's success result, but mlx90393Read reports success after a failed bus transaction and supplies three zero values. Those values are consequently recorded as real measurements and can skew external calibration.

Fix Focus Areas

  • src/main/drivers/compass/compass_mlx90393.c[103-114]
  • src/main/sensors/compass.c[417-426]

Recommended Fix

Check the return value from busReadBuf in mlx90393Read and return false without replacing magADCRaw when the transaction fails. This allows the existing failure branch in compassUpdate to skip publishing the invalid sample.



                     PR 11911 (2026-09-10)                    
[correctness] Old jolts become valid after 36 minutes
Old jolts become valid after 36 minutes `wasForwardAccelerationHigh` treats every negative result from `cmpTimeUs` as less than the one-second limit, while `forwardAccelHighTimeUs` is retained after its intended window expires. Once a launch wait continues roughly 35.8 minutes beyond an earlier acceleration event, signed-delta overflow makes that stale event qualify again and GPS speed can complete detection.

Issue description

The remembered acceleration timestamp is never expired, and the signed 32-bit comparison becomes negative after about 35 minutes, causing an old event to satisfy the one-second recency check.

Fix Focus Areas

  • src/main/navigation/navigation_fw_launch.c[426-435]
  • src/main/common/time.h[27-30]
  • src/main/common/time.h[63-64]

Recommended Fix

Explicitly clear the remembered timestamp once its one-second window expires and require the computed delta to be nonnegative before accepting it as recent. Keep expiry evaluation running on every detection cycle so the timestamp cannot survive until signed-delta overflow.


[correctness] An aborted attempt can still launch
An aborted attempt can still launch `fwLaunchState_FW_LAUNCH_STATE_WAIT_DETECTION` records `forwardAccelHighTimeUs` but does not clear it when low throttle returns the controller to `WAIT_THROTTLE`. When the pilot raises throttle again within one second, the previous attempt's acceleration can combine with qualifying GPS speed and enter detection without a new throw.

Issue description

A throttle-low transition aborts detection and returns the controller to WAIT_THROTTLE, but the newly added acceleration timestamp survives that transition and remains usable by the next attempt.

Fix Focus Areas

  • src/main/navigation/navigation_fw_launch.c[416-431]

Recommended Fix

Clear forwardAccelHighTimeUs when low throttle exits WAIT_DETECTION, ensuring every newly armed detection attempt requires its own acceleration event.



                     PR 11900 (2026-09-10)                    
[correctness] Rejected upload keeps old mission active
Rejected upload keeps old mission active `setWaypoint()` returns for an invalid first JUMP before `resetWaypointList()`, preserving the prior waypoint list, count, and validity flag. When waypoint 1 of an MSP upload or nonvolatile reload has an out-of-range target, the caller receives no failure and the previously loaded mission remains executable.

Issue description

Rejecting an invalid JUMP at waypoint 1 returns before the existing mission is reset, so stale valid mission state remains available to execute.

Fix Focus Areas

  • src/main/navigation/navigation.c[5471-5483]
  • src/main/fc/fc_msp.c[3323-3354]

Recommended Fix

Reorder first-waypoint handling so resetWaypointList() runs before JUMP validation when wpNumber == 1, while still validating before copying or converting the new waypoint. Ensure a rejected first waypoint leaves the count zero and waypointListValid false.



                     PR 11899 (2026-09-10)                    
[correctness] Short frames can execute stale commands
Short frames can execute stale commands The new MSPv1 length guard in `handleMspFrame()` returns without clearing `mspStarted`, so an incomplete request and its sequence state remain active. On CRSF, a two-byte start frame received between chunks lets the next matching continuation finish and execute the earlier command instead of discarding it.

Issue description

Rejecting a short MSPv1 start frame leaves the previous partial request active, allowing later continuation chunks to complete and execute that stale command.

Fix Focus Areas

  • src/main/telemetry/msp_shared.c[116-206]

Recommended Fix

Set mspStarted to zero before returning for a short MSPv1 start frame, ensuring the next frame cannot continue the previous request. Apply the same reset consistently to malformed start-frame rejection paths.



                     PR 11898 (2026-09-10)                    
[correctness] Flash capacity is overstated eightfold
Flash capacity is overstated eightfold `ALEXF722V3.md` describes both supported flash chips as providing 16 MB, although the driver's geometry table gives the smaller variant 32 sectors instead of the 256 sectors assigned to the 16 MB variant. On boards fitted with the smaller chip, users therefore receive only 2 MB of blackbox storage and may plan recording duration around an eightfold capacity overstatement.

Issue description

The board documentation labels both supported flash variants as 16 MB, but the firmware geometry defines one as 2 MB and the other as 16 MB.

Fix Focus Areas

  • docs/boards/ALEXF722V3.md[13-13]

Recommended Fix

List the capacity separately for each flash variant, describing the smaller variant as 2 MB and the larger variant as 16 MB.



                     PR 11892 (2026-09-10)                    
[correctness] Enum reference repeats tagged types
Enum reference repeats tagged types `get_all_inav_enums_h.py` extracts tagged typedefs in both its typedef and plain-tag passes, and the broadened `RE_ENUM_START` in `gen_enum_md.py` now parses and renders both copies with identical names and anchors. Any declaration such as `typedef enum ADCDevice` reaches duplicate Markdown sections and table-of-contents entries, while links cannot uniquely target either section and JSON rendering silently overwrites the first definition.

Issue description

Tagged typedef enums are emitted twice by the collector—once by the typedef enum pass and again by the plain enum Tag pass—and the broadened parser renders both copies. This creates duplicate Markdown sections, table-of-contents entries, and HTML anchors, while duplicate JSON keys silently overwrite the first definition.

Fix Focus Areas

  • src/utils/gen_enum_md.py[74-81]
  • src/utils/get_all_inav_enums_h.py[34-86]
  • docs/development/msp/inav_enums_ref.md[498-510]

Recommended Fix

Ensure each tagged typedef is collected or parsed exactly once. Prefer changing the collector’s plain tagged-enum extraction so it skips matches whose enum token is preceded by typedef, while preserving extraction of standalone enum Tag { ... }; declarations; also add a duplicate-name guard before rendering to prevent future duplicate anchors and JSON overwrites. Regenerate both enum output files and verify that every enum has exactly one table-of-contents entry, Markdown section, HTML anchor, and JSON definition.


[correctness] External source trees abort generation
External source trees abort generation `get_all_inav_enums_h.py` unconditionally calls `fn.relative_to(REPO_ROOT)` even though `--inav-root` accepts an arbitrary source-tree directory. Supplying a valid tree outside this checkout raises `ValueError` on the first discovered C or header file, before any consolidated enum header can be produced.

Issue description

The --inav-root option accepts external source trees, but source attribution forces every discovered file to be relative to the current repository root, causing valid external paths to raise ValueError.

Fix Focus Areas

  • src/utils/get_all_inav_enums_h.py[97-123]

Recommended Fix

Preserve stable repository-relative attribution for files under REPO_ROOT, but when a discovered file is outside it, fall back to a stable path relative to the supplied base_dir or retain the resolved absolute source path. Ensure valid external --inav-root invocations cannot raise from Path.relative_to(REPO_ROOT), and add coverage that invokes the script with an external temporary source tree.



                     PR 11886 (2026-09-09)                    
[correctness] Fast-code growth disappears from reports
Fast-code growth disappears from reports `compute` drops every section whose address is zero, conflating unallocated metadata with allocated sections at a valid zero-origin memory region. F7 and H7 builds place populated `.tcm_code` sections at the start of zero-origin writable ITCM, so their bytes and any resulting delta never reach the regional report.

Issue description

The regional size calculator discards all sections at address zero, including allocated .tcm_code sections placed in zero-origin ITCM. Distinguish non-allocated metadata from allocated ELF sections using section allocation flags or equivalent map information rather than the address alone.

Issue Context

STM32F7 and STM32H7 linker scripts define writable ITCM at 0x00000000, and repository code populates .tcm_code through FAST_CODE. Keep excluding debug and symbol sections at address zero while counting allocated sections there, and add regression coverage for both cases.

Fix Focus Areas

  • .github/scripts/compute-region-sizes.py[89-103]


                     PR 11876 (2026-09-08)                    
[reliability] A display remains locked after closing
A display remains locked after closing `cmsMenuExit()` treats a nonzero `cmsYieldUntil` as proof that the current display was released, then clears the timer without calling `displayRelease(pDisplay)`. When the HoTT input path calls `cmsMenuOpen()` during an active OSD preview yield, CMS can switch to and grab another reference-counted display while preserving the stale deadline, leaving that replacement display's grab count elevated on exit.

Issue description

A CMS exit can skip releasing a newly selected display because cmsYieldUntil records that an earlier display was yielded rather than whether the current display is actually grabbed. Track current display ownership explicitly and use that state consistently across yielding, display switching, reacquisition, and exit.

Issue Context

cmsYieldDisplay() releases the original display and records a deadline. During that deadline, the HoTT display path can call cmsMenuOpen() while a menu is active, causing CMS to cycle displays, release the prior display, and grab a newly selected display without clearing the yield deadline. Because display ownership is reference-counted, exit must release the current display according to its actual ownership instead of clearing the timer and inferring that no release is needed.

Fix Focus Areas

  • src/main/cms/cms.c[854-926]
  • src/main/cms/cms.c[996-1003]
  • src/main/cms/cms.c[1038-1047]
  • src/main/io/displayport_hott.c[146-168]

[correctness] Battery warnings use stale flight limits
Battery warnings use stale flight limits `cmsMenuExit()` now bypasses every `onExit` handler for `CMS_EXIT`, including `cmsx_menuBattSettings_onExit()`. When an armed pilot selects another battery profile and enters SETTINGS before an auto-close, its `onEnter` switches the active profile but the skipped handler never recalculates cell count and voltage thresholds for it.

Issue description

Forced in-flight CMS closure can leave the active battery profile changed while its calculated cell count and voltage warning thresholds still belong to the previous profile.

Issue Context

Entering the battery SETTINGS submenu immediately activates the selected profile. Its onExit is the only armed-path operation that updates calculated thresholds, but the new CMS_EXIT behavior deliberately skips all onExit callbacks.

Fix Focus Areas

  • src/main/cms/cms.c[979-984]
  • src/main/cms/cms_menu_battery.c[99-116]
  • src/main/cms/cms_menu_battery.c[173-190]

[correctness] Layout previews vanish immediately
Layout previews vanish immediately `osdUpdate` clears every active `layoutOverride` whenever `cmsInMenu` is false, although layout overrides are also created by the configuration protocol. When the configurator requests a ten-second preview outside the menu, the next display update cancels it instead of honoring its timeout.

Issue description

The new menu-close cleanup treats every layout override as menu-owned, cancelling timed configurator previews. Restrict forced cleanup to overrides established by the menu layout editor while preserving timeout-based overrides from other callers.

Issue Context

Both the menu editor and the configuration protocol call osdOverrideLayout(). Ownership or lifetime must therefore be represented explicitly rather than inferred solely from cmsInMenu.

Fix Focus Areas

  • src/main/io/osd.c[6026-6046]
  • src/main/io/osd.c[6107-6114]
  • src/main/cms/cms_menu_osd.c[379-394]
  • src/main/fc/fc_msp.c[3706-3714]


                     PR 11872 (2026-09-07)                    
[correctness] Some landing approaches still descend
Some landing approaches still descend The LAND conversion copies altitude from the immediately preceding stored item rather than locating the preceding positional waypoint. When a stored jump, heading, or point-of-interest action appears between the last flight waypoint and LAND, its zero or unrelated altitude becomes the approach target and can preserve the original descent or introduce another incorrect altitude.

Issue description

The LAND adjustment reads the immediately preceding stored item, although stored mission actions do not necessarily carry the aircraft's preceding flight altitude.

Issue Context

Commands such as MAV_CMD_DO_JUMP, MAV_CMD_DO_SET_ROI, and MAV_CMD_CONDITION_YAW are staged as waypoints. Search backward for the applicable positional waypoint whose altitude defines the final approach, and add coverage with intervening stored actions.

Fix Focus Areas

  • src/main/mavlink/mavlink_mission.c[792-796]
  • src/main/mavlink/mavlink_mission.c[799-816]
  • src/main/mavlink/mavlink_mission.c[900-945]

[correctness] Valid low landing altitudes are lost
Valid low landing altitudes are lost The `wp.alt <= 0` condition treats every non-positive LAND altitude as the ground-station zero sentinel and replaces it with the preceding altitude. This changes accepted landings below the selected reference, including negative relative altitudes and absolute locations at or below sea level, whenever a rotary mission has an earlier waypoint.

Issue description

The LAND workaround overwrites all negative altitudes even though mission validation explicitly accepts them.

Issue Context

Restrict the sentinel handling to the exact frame/value combination intended to represent an unspecified touchdown altitude. Add tests for negative relative altitude and non-positive absolute altitude so valid elevations remain intact.

Fix Focus Areas

  • src/main/mavlink/mavlink_mission.c[752-796]
  • src/main/mavlink/mavlink_mission.c[214-228]

[correctness] Some helicopter landings still descend
Some helicopter landings still descend The alternate-profile checks enumerate multirotor and tricopter but omit the helicopter platform that `isMultirotorTypePlatform` also classifies as rotary. A fixed-wing VTOL configured to switch to a helicopter-type landing profile therefore skips the approach-altitude correction and continues using the LAND touchdown altitude across its final leg.

Issue description

Rotary landing detection omits an alternate helicopter mixer profile even though the repository's shared platform classifier includes helicopters.

Issue Context

Use consistent multirotor-type classification for the configured landing profile, or explicitly include every platform accepted by isMultirotorTypePlatform. Add a test for a fixed-wing current profile with a helicopter-type alternate profile.

Fix Focus Areas

  • src/main/mavlink/mavlink_mission.c[788-790]
  • src/main/flight/mixer.h[36-51]
  • src/main/flight/mixer_profile.c[931-947]


                     PR 11869 (2026-09-06)                    
[correctness] Return planning reverses vertical wind
Return planning reverses vertical wind `calculateRemainingEnergyBeforeRTH` passes the positive-up wind value into `estimateRTHAltitudeChangeTime`, whose aircraft climb term uses negative pitch and therefore the opposite sign. Whenever vertical wind is nonzero, an updraft offsets rather than augments the modeled climb component, affecting both altitude-change distance and energy.

Issue description

The RTH altitude-change calculation combines a negative-sign climb component with a positive-up NEU wind value. Convert the estimated vertical wind to the sign convention used by the calculation before passing it into distance and energy estimation.

Issue Context

getEstimatedWindSpeed(Z) returns positive-up wind, while RTHInitialAltitudeChangePitchAngle() returns a negative angle for climbing and estimateRTHAltitudeChangeTime() adds the resulting negative sine component directly to vertical wind.

Fix Focus Areas

  • src/main/flight/rth_estimator.c[73-94]
  • src/main/flight/rth_estimator.c[147-177]


                     PR 11865 (2026-09-05)                    
[correctness] Capacity triggers voltage warning
Capacity triggers voltage warning When capacity thresholds are enabled, `getBatteryState()` returns capacity-derived state, so the multifunction voltage warning duplicates the capacity warning and can report `VBATT LOW/LAND` despite healthy voltage. It can also omit a genuine low-voltage warning while remaining capacity is healthy.

Issue description

Preserve voltage-specific warning semantics while avoiding references to checkBatteryVoltageState() on builds without USE_ADC. The aggregate getBatteryState() must not drive the explicitly voltage-based warning because it becomes capacity-derived when capacity thresholds are active.

Issue Context

batteryUpdate() selects checkBatteryCapacityState() when batteryUseCapacityThresholds is true. The following capacity-warning block already reports that aggregate state separately, so using it for the voltage block produces incorrect and duplicate warnings.

Fix Focus Areas

  • src/main/fc/multifunction.c[268-280]
  • src/main/sensors/battery.c[397-435]
  • src/main/sensors/battery.c[498-510]

[correctness] Capacity controls voltage blinking
Capacity controls voltage blinking When capacity thresholds are enabled, `getBatteryState()` makes the displayed voltage blink according to remaining capacity rather than the voltage thresholds. Consequently, healthy voltage can blink on low capacity while genuinely low voltage may not blink when capacity remains healthy.

Issue description

Keep voltage-display blinking based on voltage thresholds while preventing non-ADC builds from referencing an unavailable symbol. Do not substitute the aggregate battery state because it can represent remaining capacity.

Issue Context

checkBatteryVoltageState() evaluates batteryWarningVoltage and batteryCriticalVoltage, but getBatteryState() returns the shared state that batteryUpdate() derives from capacity whenever capacity thresholds are enabled. Use compile-time guarding or an always-defined voltage-state API with an appropriate non-ADC fallback.

Fix Focus Areas

  • src/main/io/osd.c[1588-1607]
  • src/main/sensors/battery.c[397-435]
  • src/main/sensors/battery.c[498-510]
  • src/main/sensors/battery.h[76-80]


                     PR 11854 (2026-09-02)                    
[correctness] Dynamic notch silently disabled
Dynamic notch silently disabled RP2350 compiles and exposes dynamic gyro notch filtering, but its FFT and vector-math functions return success without initializing or modifying their outputs. Enabling the setting therefore runs the flight-control filter using nonexistent frequency analysis instead of the requested dynamic notch behavior.

Issue description

RP2350 supplies no-op implementations of the CMSIS DSP functions used by dynamic gyro notch analysis. Although the target disables the filter only by default, users can re-enable it and receive silently invalid filtering.

Issue Context

Either provide functional ARMv8-M-compatible DSP implementations or compile USE_DYNAMIC_FILTERS out for RP2350 so the unsupported setting and execution path cannot be enabled.

Fix Focus Areas

  • src/main/drivers/system_rp2350.c[220-287]
  • src/main/target/RP2350_PICO/target.h[150-175]
  • src/main/target/RP2350_PICO/config.c[24-31]
  • src/main/flight/gyroanalyse.c[72-89]


                     PR 11846 (2026-08-31)                    
[correctness] Upgrade corrupts alternate layouts
Upgrade corrupts alternate layouts Appending `OSD_TERRAIN_AGL` changes every row's stride in the persisted two-dimensional `item_pos` array, but the unchanged parameter-group version causes old bytes to be copied directly into the enlarged structure. After upgrade, layout 0's first value is consumed as the new terrain slot and every subsequent alternate-layout item is shifted, changing positions/visibility and assigning values to the wrong OSD elements.

Issue description

Adding an item changes the stride of each row in persisted item_pos[OSD_LAYOUT_COUNT][OSD_ITEM_COUNT]. Because the registered PG version remains 3, old four-layout payloads are copied as a flat prefix into the new shape and alternate layouts become misaligned.

Issue Context

The generic PG loader only performs a size-limited memcpy when versions match; it does not migrate two-dimensional arrays. Either bump the layout PG version so incompatible data resets safely, or add an explicit migration that copies each old layout row into the matching new row and initializes the Terrain AGL slot.

Fix Focus Areas

  • src/main/io/osd.h[382-383]
  • src/main/io/osd.c[235-236]


                     PR 11844 (2026-08-31)                    
[correctness] Stale branch base reset
Stale branch base reset `ConditionStack.closed` remains valid after intervening enumerators, so a later opposite-polarity block for the same symbol is incorrectly treated as the prior block's alternate and resets `current_numeric` to a stale base. For example, after `A, #ifdef X B, #endif C = 10, #ifndef X D`, this PR emits `D` as `(1)` instead of the correct `(11)`, whereas the old sequential counter handled the explicit reset correctly.

Issue description

The sibling-branch cache survives ordinary enum members, causing a later opposite-polarity conditional to reuse a stale numeric base even though the two blocks are not adjacent alternate branches.

Issue Context

Only an immediately corresponding opposite-polarity sibling should reuse the closed branch base. Parsing any intervening enumerator must invalidate the closed frame at that nesting level; add regression coverage including an intervening explicit assignment.

Fix Focus Areas

  • docs/development/msp/gen_enum_md.py[107-114]
  • docs/development/msp/gen_enum_md.py[262-297]


                     PR 11838 (2026-08-29)                    
[correctness] Circular radius omission accepted
Circular radius omission accepted `MSP2_INAV_SET_GEOZONE_VERTEX` now accepts a 10–13 byte payload for a circular zone, writes the center, skips the required 4-byte radius, and returns success. The resulting circular geozone retains no newly supplied radius (or a stale prior one), so enforcement can use an unintended boundary.

Issue description

MSP2_INAV_SET_GEOZONE_VERTEX accepts a circular-zone payload without the required four-byte radius, mutates the center, and reports success.

Issue Context

Polygon updates require a 10-byte prefix, while circular updates require that prefix plus a 4-byte radius. Preserve lenient acceptance of bytes beyond the known complete encoding, but reject circular payloads shorter than 14 bytes before mutating vertex state.

Fix Focus Areas

  • src/main/fc/fc_msp.c[3922-3936]


                     PR 11835 (2026-08-29)                    
[reliability] Release notes evade pruning
Release notes evade pruning `list_per_commit_baselines` anchors its branch capture against the entire release body, but `publish_asset` creates a two-line body, so normal per-commit releases produce no jq record and are invisible to pruning. The documented per-branch and global caps therefore do not bound repository growth.

Issue description

The pruning query attempts to match a one-line anchored regex against the complete multi-line release body, causing releases created by this script to be omitted.

Issue Context

Extract and validate the first body line, and ensure malformed notes still emit a record under the fallback branch group rather than disappearing from the stream.

Fix Focus Areas

  • .github/scripts/publish-size-baseline.sh[74-76]
  • .github/scripts/publish-size-baseline.sh[87-92]

[reliability] Old baselines are never deleted
Old baselines are never deleted The first AWK pass stores only the newest 50 tags per branch in `keepTag`, and the `END` block iterates only `keepTag`, so tags beyond the first 50 can never be printed for deletion. Even after fixing release-note parsing, the per-branch cap is not enforced and old releases accumulate indefinitely unless they happen to be selected for a separate global-cap deletion.

Issue description

The pruning AWK program only considers retained tags when producing deletions and loses every tag outside the per-branch keep set.

Issue Context

Track every input tag, retain only the newest configured count per branch and then the global count, and print every input tag absent from the final keep set.

Fix Focus Areas

  • .github/scripts/publish-size-baseline.sh[99-120]


                     PR 11834 (2026-08-28)                    
[correctness] Baseline capture loses changes
Baseline capture loses changes `serialPassthrough()` captures the host baseline only after draining both ports, so a host baud/parity change during either potentially multi-iteration wait is treated as the initial value and never mirrored to the right port. This can still break fast passthrough clients, especially when the target UART has queued traffic while the host switches coding after receiving the CLI handoff message.

Issue description

The new host line-coding baseline is captured after both transmit-drain waits. If the host changes its COM-port baud or framing while either wait is running, polling sees no difference from the captured baseline and never applies that change to the passthrough UART.

Issue Context

The CLI prints its passthrough handoff message before entering serialPassthrough(), and each drain wait sleeps repeatedly until its port's software TX buffer empties. Capture the VCP baseline at actual function entry, before those waits, while preserving the USE_VCP guards and existing non-VCP behavior.

Fix Focus Areas

  • src/main/io/serial.c[603-629]


                     PR 11831 (2026-08-26)                    
[correctness] Wrapped COG spikes D-term
Wrapped COG spikes D-term Without `PID_DTERM_FROM_ERROR`, `navPidApply2` differentiates raw course over ground, so a normal 359.99°→0° crossing is treated as an approximately −360° step and produces a large roll-command transient. The fixed-wing navigation D gain is enabled by default, and output clamping only turns this spike into a maximum-bank command rather than preventing it.

Issue description

The new derivative-on-measurement path differentiates wrapped COG directly. Crossing 0°/360° therefore creates an artificial full-circle delta and a large D-term roll correction.

Issue Context

navHeadingError is wrapped safely, but actualState.cog is represented as a wrapped angular value. Preserve derivative-on-measurement while making the measurement delta wrap-aware, for example by maintaining a continuous/unwrapped COG value or adding an angular-difference-aware PID input path; reset that state whenever the navigation PID is reset.

Fix Focus Areas

  • src/main/navigation/navigation_fixedwing.c[555-561]
  • src/main/common/fp_pid.c[60-73]
  • src/main/navigation/navigation_pos_estimator.c[742-752]


                     PR 11827 (2026-08-26)                    
[correctness] LED buffer sizes use wrong width
LED buffer sizes use wrong width The WS2812 example calculates both the old 6,230-byte array and the 384-byte circular buffer using 2-byte elements, but this codebase defines `timerDMASafeType_t` as 4 bytes on every supported F4/F7/H7/AT32 family, making those buffers 12,460 and 768 bytes respectively. This also conflicts with the guide's later instruction to retain `timerDMASafeType_t`, so contributors cannot reproduce the stated RAM figures.

Issue description

The WS2812 RAM figures assume 2-byte DMA elements even though the supported timer DMA-safe type is 4 bytes.

Issue Context

The full array has 3,115 elements and the proposed 2×4-LED circular buffer has 192 elements. Keep the stated byte counts consistent with the guide's later requirement to use timerDMASafeType_t.

Fix Focus Areas

  • docs/development/ram-and-flash-optimization.md[52-55]


                     PR 11820 (2026-08-24)                    
[correctness] Sweep default is inconsistent
Sweep default is inconsistent The settings schema declares `ledstrip_rainbow_sweep_rate` default as 100, while `pgResetFn_ledStripConfig` initializes it to 10. Factory reset and older saved configurations therefore use a different sweep speed than the generated settings metadata/configurator default.

Issue description

The settings schema advertises a sweep-rate default of 100, but firmware reset and migration initialize the field to 10. Make both default paths use the intended value from the PR specification.

Issue Context

The runtime reset function is authoritative after factory reset and supplies missing tail fields when loading older PG records, while settings generation exposes the YAML default to API/configurator consumers.

Fix Focus Areas

  • src/main/fc/settings.yaml[4510-4515]
  • src/main/io/ledstrip.c[146-158]

[correctness] Unrelated LEDs shift hues
Unrelated LEDs shift hues The hue calculation multiplies the delta by the absolute configured LED index, even though the setting defines the offset between adjacent LEDs carrying the rainbow overlay. Interspersing a non-rainbow LED therefore adds an extra delta and changes the intended rainbow spacing.

Issue description

Hue spacing currently advances for every configured LED rather than only for LEDs carrying the rainbow overlay. Track a rainbow-LED ordinal and use that ordinal in the hue calculation.

Issue Context

The loop index includes non-rainbow entries, while the setting explicitly promises an offset between adjacent rainbow-overlay LEDs.

Fix Focus Areas

  • src/main/io/ledstrip.c[868-877]
  • src/main/fc/settings.yaml[4516-4520]

[correctness] Rainbow suppresses other overlays
Rainbow suppresses other overlays The new rainbow layer runs after Blink and Larson and unconditionally rewrites full HSV values, so valid combinations with Blink, Strobe, Landing Flash, or Larson never display those effects. This breaks the existing combinable-overlay model and documented behavior such as blinking the current active color.

Issue description

Rainbow is applied after existing effect overlays and overwrites their color or brightness output. Reorder the layer so rainbow supplies the active color before Blink, Strobe, Landing Flash, and Larson modify it.

Issue Context

LED configuration parsing ORs multiple overlay bits together, and every layer runs in table order. Rainbow writes hue, saturation, and value on every update, erasing effects produced by earlier layers.

Fix Focus Areas

  • src/main/io/ledstrip.c[971-985]
  • src/main/io/ledstrip.c[846-879]
  • src/main/io/ledstrip.c[822-843]
  • src/main/io/ledstrip.c[881-909]


                     PR 11812 (2026-08-22)                    
[correctness] DIRECT mode cuts corners
DIRECT mode cuts corners In DIRECT mode, the code can still arm the FLY_BY anticipation path and `isWaypointReached()` will advance the mission as soon as the anticipated turn-start is reached, causing corner-cut/early waypoint advance even though DIRECT is supposed to be legacy behavior.

Issue description

DIRECT (legacy) waypoint turning is not respected: FLY_BY turn anticipation can still arm in DIRECT, and the mission can advance early (waypoint considered reached at turn start).

Issue Context

  • calculateAndSetActiveWaypoint() currently populates activeWaypoint.nextTurnAngle for all modes except COORD_FLYOVER.
  • calculateVirtualPositionTarget_FW() uses nextTurnAngle to arm wpTurnSmoothingActive without checking that the configured turn mode is COORD_FLYBY (or landing-approach forced FLY_BY).
  • isWaypointReached() returns true immediately when wpTurnSmoothingActive is set, which advances the mission early.

Fix Focus Areas

  • src/main/navigation/navigation.c[4300-4313]
  • src/main/navigation/navigation_fixedwing.c[1123-1144]
  • src/main/navigation/navigation.c[3103-3115]

Suggested change

  • Only compute activeWaypoint.nextTurnAngle for modes that actually need anticipation (COORD_FLYBY, COORD_FLYINTO, and landing approach override).
  • Additionally (defense-in-depth), gate the FLY_BY corner-cut arming (wpTurnSmoothingActive) so it only runs when wp_turn_mode == NAV_FW_WP_TURN_COORD_FLY_BY (or landing approach state forces FLY_BY).
  • Ensure DIRECT mode never sets wpTurnSmoothingActive and therefore never triggers early isWaypointReached().

[correctness] DIRECT mode gets turn feedforward
DIRECT mode gets turn feedforward Even when `nav_fw_wp_turn_mode` is DIRECT, the roll controller still adds coordinated-turn feed-forward (`getFwTurnFeedForward()`), so DIRECT cannot reproduce legacy heading-PID behavior.

Issue description

DIRECT mode is documented as legacy heading-PID behavior, but the new coordinated-turn feed-forward is applied unconditionally whenever WP tracking is active.

Issue Context

  • updatePositionHeadingController_FW() always executes rollAdjustment += getFwTurnFeedForward(navHeadingError); when not in an arc.
  • getFwTurnFeedForward() does not check navConfig()->fw.wp_turn_mode; it only checks WP tracking/heading error/speed.

Fix Focus Areas

  • src/main/navigation/navigation_fixedwing.c[1341-1347]
  • src/main/navigation/navigation_fixedwing.c[551-565]

Suggested change

  • Gate feed-forward by turn mode, e.g. return 0 (or skip calling) when navConfig()->fw.wp_turn_mode == NAV_FW_WP_TURN_DIRECT.
  • If you still want FF in DIRECT for some reason, update docs/setting semantics accordingly; otherwise DIRECT should be a true opt-out.


                     PR 11804 (2026-08-20)                    
[maintainability] Smoother max-time inconsistency
Smoother max-time inconsistency Roll smoothing is documented as “max 900 ms” (because `control_smoothness` max is 9), but the code/comment defines a 1000 ms cap, which is currently unreachable and can confuse future maintainers or doc users. This mismatch increases the risk of later changing the setting max (or reuse) and silently diverging from docs.

Issue description

Docs/settings say the roll easing window is n * 100ms (max 900ms) and the setting max is 9, but code comments/constants claim a 1000ms cap. Today the effective max is still 900ms, but the inconsistency is misleading.

Issue Context

  • nav_fw_control_smoothness max is 9.
  • Docs describe max 900ms.
  • Code defines NAV_FW_SMOOTH_TCONST_MAX_MS 1000.0f and comment says “capped at 1000 ms”.

Fix Focus Areas

  • src/main/navigation/navigation_fixedwing.c[62-68]
  • src/main/fc/settings.yaml[3058-3064]
  • docs/Settings.md[3641-3644]

Suggested fix

Either:

  • Change NAV_FW_SMOOTH_TCONST_MAX_MS and the comment to 900ms, matching the setting range and docs, or
  • Keep 1000ms and update docs/setting max accordingly (e.g., allow 10) so behavior matches documentation.


                     PR 11798 (2026-08-18)                    
[correctness] Null ws2811TCH deref
Null ws2811TCH deref ws2811SetIdleHigh() writes through timerCCR(ws2811TCH) without checking ws2811TCH/ws2811Initialised, so a failed/absent LED-strip init can cause a hard fault when PINIO calls it. ws2811LedStripInit() has multiple early-return paths that leave ws2811TCH NULL.

Issue description

ws2811SetIdleHigh() unconditionally dereferences ws2811TCH via timerCCR(ws2811TCH). If LED-strip initialization never ran or returned early (no suitable timer, DMA config failure, etc.), ws2811TCH can be NULL and this becomes a hard-fault path.

Issue Context

pinioSetDuty() routes index == 0 directly to ws2811SetIdleHigh(), so this can be triggered even when LED strip init did not succeed.

Fix

Add a defensive guard at the top of ws2811SetIdleHigh():

  • if (!ws2811Initialised || !ws2811TCH) return; Optionally also avoid touching lineIdleLow/lastLowAtUs when not initialized.

Fix Focus Areas

  • src/main/drivers/light_ws2811strip.c[293-300]
  • src/main/drivers/pinio.c[195-201]
  • src/main/drivers/light_ws2811strip.c[144-171]

[correctness] Idle-high overridden at end
Idle-high overridden at end ws2811StopTransfer() always forces `*timerCCR(...)=0` and `lineIdleLow=true`, so an earlier ws2811SetIdleHigh(true) request (PINIO) can be lost when the DMA refill ISR stops the transfer. This leaves the LED strip/PINIO line in the wrong electrical state until the next PINIO update.

Issue description

At the end of a circular DMA LED transfer, ws2811DMARefillCallback() calls ws2811StopTransfer(), which unconditionally drives the line low (CCR=0) and marks it idle-low. This can override a PINIO-driven idle-high request made via ws2811SetIdleHigh(true).

Issue Context

  • pinioSetDuty(index==0) uses ws2811SetIdleHigh(duty>0).
  • When the last LED group completes, the DMA refill callback stops the DMA and forces low.

Fix

Track the desired idle level separately (e.g. static volatile bool idleHighRequested; updated by ws2811SetIdleHigh()), and in ws2811StopTransfer() set CCR to either 0 or a constant-high value based on that desired level instead of always forcing low. If latch/reset timing requires a low gap before returning high, enforce that before restoring high.

Fix Focus Areas

  • src/main/drivers/light_ws2811strip.c[225-233]
  • src/main/drivers/light_ws2811strip.c[235-249]
  • src/main/drivers/light_ws2811strip.c[293-300]
  • src/main/drivers/pinio.c[195-201]


                     PR 11788 (2026-08-17)                    
[maintainability] Stale enum protocol docs
Stale enum protocol docs The PR changes wire values of `resolutionType_e` (e.g., `HD_6022` becomes 3 and `HD_5320` becomes 4, adding `HD_3016=2`), but the committed MSP enum reference docs still list the old ordinals. This leaves the repository’s protocol documentation inconsistent with the firmware, risking mis-implementation by tooling/third parties relying on these references.

Issue description

resolutionType_e is a wire-protocol enum (sent as a raw byte via MSP_DP_OPTIONS). This PR updates its explicit values, but the committed enum reference artifacts under docs/development/msp/ still document the old ordinals, causing protocol documentation drift.

Issue Context

The repo contains auto-generated enum reference files (inav_enums.json, inav_enums_ref.md) produced by docs/development/msp/gen_docs.sh / gen_enum_md.py. These should reflect the updated resolutionType_e members and values (including the newly reintroduced HD_3016=2).

Fix Focus Areas

  • src/main/io/displayport_msp_osd.c[64-73]
  • docs/development/msp/inav_enums.json[3300-3306]
  • docs/development/msp/inav_enums_ref.md[4824-4834]

What to change

  • Regenerate/update docs/development/msp/inav_enums.json and docs/development/msp/inav_enums_ref.md so resolutionType_e matches the new wire values:
  • SD_3016 = 0
  • HD_5018 = 1
  • HD_3016 = 2
  • HD_6022 = 3
  • HD_5320 = 4
  • Prefer running the documented generator (docs/development/msp/gen_docs.sh) and committing the updated artifacts, rather than hand-editing.


                     PR 11782 (2026-08-13)                    
[correctness] Auto-close skips onExit
Auto-close skips onExit cmsUpdate() force-closes the in-flight CMS with cmsMenuExit(..., CMS_EXIT) on safety loss/timeout, but cmsMenuExit() does not run per-menu onExit callbacks for CMS_EXIT. This can leave temporary state enabled after the menu collapses (e.g., OSD layout preview override is only cleared in menuOsdElements.onExit).

Issue description

In-flight auto-close paths call cmsMenuExit(..., CMS_EXIT), but cmsMenuExit() does not dispatch onExit callbacks for CMS_EXIT. Menus that rely on onExit for cleanup (e.g. OSD layout preview override) can leave that transient state active after an emergency/timeout close.

Issue Context

  • In-flight main menu includes the OSD submenu (cmsx_menuOsd), which can enter menuOsdElements that sets an OSD override on enter and clears it on exit.
  • Safety close / inactivity close uses CMS_EXIT, not cmsMenuBack(), so no stack unwinding happens.

Fix Focus Areas

  • Ensure auto-close (safety/timeout/switch-off) triggers the same cleanup as backing out of menus:
  • Call currentCtx.menu->onExit(...) if present.
  • Optionally iterate menuStack[] and call onExit for each stacked menu (similar to the popup-save traversal) to guarantee cleanup.
  • Keep the “no EEPROM save/reboot while armed” restriction intact.
  • src/main/cms/cms.c[908-944]
  • src/main/cms/cms.c[1443-1457]

[reliability] batteryInit resets live state
batteryInit resets live state The battery CMS menus call batteryInit() when exiting while armed, which resets batteryState and voltage thresholds to zero. If throttle VBAT compensation is enabled, mixerThrottleCommand will use calculateThrottleCompensationFactor() based on batteryFullVoltage=0 until batteryUpdate recomputes, changing throttle scaling transiently.

Issue description

Calling batteryInit() while armed resets live battery runtime state (batteryState, cell count, and voltage thresholds). This can transiently change behaviors that depend on those values (notably throttle VBAT compensation which uses batteryFullVoltage).

Issue Context

The CMS battery menus already write settings directly; they only need to refresh derived thresholds/state, not reset battery presence.

Fix Focus Areas

  • Replace batteryInit() (armed path) with a new targeted refresh that:
  • recomputes batteryFullVoltage/batteryWarningVoltage/batteryCriticalVoltage based on current batteryCellCount and current battery profile, without forcing batteryState = BATTERY_NOT_PRESENT.
  • avoids transiently zeroing values used in the mixer.
  • If a new battery API is needed, implement it in sensors/battery.c and expose via sensors/battery.h.
  • src/main/cms/cms_menu_battery.c[60-68]
  • src/main/cms/cms_menu_battery.c[100-109]


                     PR 11771 (2026-08-08)                    
[correctness] MAVLink not transmit-only
MAVLink not transmit-only docs/Telemetry.md states MAVLink in INAV is “transmit-only”, but the current MAVLink telemetry implementation reads and processes incoming MAVLink messages (missions/params/RC override/etc). This misleads users about MAVLink capabilities and is in-scope because the PR edited this sentence but kept the incorrect claim.

Issue description

docs/Telemetry.md says MAVLink is transmit-only, but the implementation processes inbound MAVLink messages. This is incorrect documentation and can lead users to wrong configuration/expectations.

Issue Context

The telemetry MAVLink code contains an explicit receive loop and dispatches received message IDs.

Fix Focus Areas

  • docs/Telemetry.md[205-210]
  • src/main/telemetry/mavlink.c[1388-1433]
  • src/main/telemetry/mavlink.c[1444-1456]

[maintainability] SoftSerial baud docs conflict
SoftSerial baud docs conflict This PR removes the “softserial is limit to 19200” note from the iBus telemetry section, but other docs still state SoftSerial is limited to 19200 baud. This leaves contradictory guidance across the documentation set and will confuse users configuring SoftSerial-based telemetry/logging.

Issue description

After this PR, docs/Telemetry.md no longer mentions a 19200 SoftSerial limit for iBus, but docs/Serial.md and docs/Blackbox.md still claim SoftSerial is limited to 19200 baud. This is conflicting documentation.

Issue Context

The PR’s stated goal is to fix misleading SoftSerial-limit docs; this should be applied consistently across the documentation set.

Fix Focus Areas

  • docs/Telemetry.md[231-237]
  • docs/Serial.md[46-48]
  • docs/Blackbox.md[68-75]

[maintainability] Softserial limit docs conflict
Softserial limit docs conflict docs/Telemetry.md removes the “softserial limited to 19200” guidance, but other docs still claim SoftSerial is capped at 19200, creating contradictory configuration guidance. The SoftSerial implementation appears to accept an arbitrary baud (no explicit 19200 clamp), so the remaining “19200 limit” statements are likely misleading or at least need qualification as a practical recommendation rather than a hard limit.

Issue description

docs/Telemetry.md no longer states SoftSerial is limited to 19200 baud, but docs/Serial.md and docs/Blackbox.md still state (as a hard limit) that SoftSerial is limited to 19200. This creates contradictory documentation after this PR.

Issue Context

The SoftSerial code path takes a baud parameter and configures timers from it without a clear 19200 cap, so documentation that presents 19200 as a strict limit is inconsistent with the current implementation.

Fix Focus Areas

  • docs/Telemetry.md[205-236]
  • docs/Serial.md[36-49]
  • docs/Blackbox.md[68-76]
  • src/main/drivers/serial_softserial.c[170-189]
  • src/main/drivers/serial_softserial.c[204-345]

Suggested change

Update the other docs to match the new reality (e.g., remove the “limited to 19200” hard-limit phrasing) or rephrase consistently as a practical/recommended limit if that’s the intended guidance (e.g., “SoftSerial may be unreliable at high baud rates; prefer hardware UART for >19200 / high bandwidth”).


[maintainability] MAVLink sentence punctuation
MAVLink sentence punctuation The new sentence “transmit-only, is usable…” is a comma splice and reads ambiguously. This reduces clarity in the updated documentation.

Issue description

The updated MAVLink line uses a comma splice (“transmit-only, is usable”), which is grammatically awkward and can be misread.

Issue Context

This is purely a documentation clarity/readability fix.

Fix Focus Areas

  • docs/Telemetry.md[205-210]

Suggested change

Reword to something like:

  • “MAVLink implementation in INAV is transmit-only and usable at low baud rates. MAVLink V1 and V2 are supported.”

[correctness] MAVLink not transmit-only
MAVLink not transmit-only docs/Telemetry.md states MAVLink in INAV is “transmit-only”, but the current MAVLink telemetry implementation reads and processes incoming MAVLink messages (missions/params/RC override/etc). This misleads users about MAVLink capabilities and is in-scope because the PR edited this sentence but kept the incorrect claim.

Issue description

docs/Telemetry.md says MAVLink is transmit-only, but the implementation processes inbound MAVLink messages. This is incorrect documentation and can lead users to wrong configuration/expectations.

Issue Context

The telemetry MAVLink code contains an explicit receive loop and dispatches received message IDs.

Fix Focus Areas

  • docs/Telemetry.md[205-210]
  • src/main/telemetry/mavlink.c[1388-1433]
  • src/main/telemetry/mavlink.c[1444-1456]

[maintainability] Softserial limit docs conflict
Softserial limit docs conflict docs/Telemetry.md removes the “softserial limited to 19200” guidance, but other docs still claim SoftSerial is capped at 19200, creating contradictory configuration guidance. The SoftSerial implementation appears to accept an arbitrary baud (no explicit 19200 clamp), so the remaining “19200 limit” statements are likely misleading or at least need qualification as a practical recommendation rather than a hard limit.

Issue description

docs/Telemetry.md no longer states SoftSerial is limited to 19200 baud, but docs/Serial.md and docs/Blackbox.md still state (as a hard limit) that SoftSerial is limited to 19200. This creates contradictory documentation after this PR.

Issue Context

The SoftSerial code path takes a baud parameter and configures timers from it without a clear 19200 cap, so documentation that presents 19200 as a strict limit is inconsistent with the current implementation.

Fix Focus Areas

  • docs/Telemetry.md[205-236]
  • docs/Serial.md[36-49]
  • docs/Blackbox.md[68-76]
  • src/main/drivers/serial_softserial.c[170-189]
  • src/main/drivers/serial_softserial.c[204-345]

Suggested change

Update the other docs to match the new reality (e.g., remove the “limited to 19200” hard-limit phrasing) or rephrase consistently as a practical/recommended limit if that’s the intended guidance (e.g., “SoftSerial may be unreliable at high baud rates; prefer hardware UART for >19200 / high bandwidth”).


[maintainability] SoftSerial baud docs conflict
SoftSerial baud docs conflict This PR removes the “softserial is limit to 19200” note from the iBus telemetry section, but other docs still state SoftSerial is limited to 19200 baud. This leaves contradictory guidance across the documentation set and will confuse users configuring SoftSerial-based telemetry/logging.

Issue description

After this PR, docs/Telemetry.md no longer mentions a 19200 SoftSerial limit for iBus, but docs/Serial.md and docs/Blackbox.md still claim SoftSerial is limited to 19200 baud. This is conflicting documentation.

Issue Context

The PR’s stated goal is to fix misleading SoftSerial-limit docs; this should be applied consistently across the documentation set.

Fix Focus Areas

  • docs/Telemetry.md[231-237]
  • docs/Serial.md[46-48]
  • docs/Blackbox.md[68-75]

[maintainability] Softserial limit docs conflict
Softserial limit docs conflict docs/Telemetry.md removes the “softserial limited to 19200” guidance, but other docs still claim SoftSerial is capped at 19200, creating contradictory configuration guidance. The SoftSerial implementation appears to accept an arbitrary baud (no explicit 19200 clamp), so the remaining “19200 limit” statements are likely misleading or at least need qualification as a practical recommendation rather than a hard limit.

Issue description

docs/Telemetry.md no longer states SoftSerial is limited to 19200 baud, but docs/Serial.md and docs/Blackbox.md still state (as a hard limit) that SoftSerial is limited to 19200. This creates contradictory documentation after this PR.

Issue Context

The SoftSerial code path takes a baud parameter and configures timers from it without a clear 19200 cap, so documentation that presents 19200 as a strict limit is inconsistent with the current implementation.

Fix Focus Areas

  • docs/Telemetry.md[205-236]
  • docs/Serial.md[36-49]
  • docs/Blackbox.md[68-76]
  • src/main/drivers/serial_softserial.c[170-189]
  • src/main/drivers/serial_softserial.c[204-345]

Suggested change

Update the other docs to match the new reality (e.g., remove the “limited to 19200” hard-limit phrasing) or rephrase consistently as a practical/recommended limit if that’s the intended guidance (e.g., “SoftSerial may be unreliable at high baud rates; prefer hardware UART for >19200 / high bandwidth”).


[maintainability] MAVLink sentence punctuation
MAVLink sentence punctuation The new sentence “transmit-only, is usable…” is a comma splice and reads ambiguously. This reduces clarity in the updated documentation.

Issue description

The updated MAVLink line uses a comma splice (“transmit-only, is usable”), which is grammatically awkward and can be misread.

Issue Context

This is purely a documentation clarity/readability fix.

Fix Focus Areas

  • docs/Telemetry.md[205-210]

Suggested change

Reword to something like:

  • “MAVLink implementation in INAV is transmit-only and usable at low baud rates. MAVLink V1 and V2 are supported.”

[maintainability] MAVLink sentence punctuation
MAVLink sentence punctuation The new sentence “transmit-only, is usable…” is a comma splice and reads ambiguously. This reduces clarity in the updated documentation.

Issue description

The updated MAVLink line uses a comma splice (“transmit-only, is usable”), which is grammatically awkward and can be misread.

Issue Context

This is purely a documentation clarity/readability fix.

Fix Focus Areas

  • docs/Telemetry.md[205-210]

Suggested change

Reword to something like:

  • “MAVLink implementation in INAV is transmit-only and usable at low baud rates. MAVLink V1 and V2 are supported.”


                     PR 11769 (2026-08-08)                    
[correctness] Invalid RSSI becomes -255
Invalid RSSI becomes -255 In mavlinkParseRxStats() GENERIC mode now negates msg->rssi without handling MAVLink’s invalid/unknown sentinel (UINT8_MAX/255), producing uplinkRSSI = -255. This value is consumed as a real dBm reading (e.g., by OSD alarm checks), causing false low-RSSI alarms/logic triggers when RSSI is actually unknown.

Issue description

mavlinkParseRxStats() in GENERIC mode unconditionally assigns rxLinkStatistics.uplinkRSSI = -msg->rssi;. MAVLink defines rssi == UINT8_MAX (255) as invalid/unknown, so the new code turns this into -255, which downstream code interprets as an extremely poor (but valid) dBm.

Issue Context

  • MAVLink RADIO_STATUS.rssi is uint8_t with values [0..254], 255 = invalid/unknown.
  • The same function already treats msg->rssi != 255 specially for LQ, indicating the sentinel is expected.
  • OSD displays rxLinkStatistics.uplinkRSSI directly and compares it to osdConfig()->rssi_dbm_alarm, so -255 can trigger alarms even though RSSI is unknown.

Fix Focus Areas

  • src/main/telemetry/mavlink.c[1318-1328]

Suggested change

  • Only negate when msg->rssi != UINT8_MAX; otherwise set uplinkRSSI to a defined “unknown” representation that won’t be misinterpreted as a real low dBm (e.g. 0, or a dedicated sentinel plus consumer handling).
  • Make the cast explicit to avoid any ambiguity: -(int16_t)msg->rssi.

[correctness] Invalid RSSI becomes -255
Invalid RSSI becomes -255 In mavlinkParseRxStats() GENERIC mode now negates msg->rssi without handling MAVLink’s invalid/unknown sentinel (UINT8_MAX/255), producing uplinkRSSI = -255. This value is consumed as a real dBm reading (e.g., by OSD alarm checks), causing false low-RSSI alarms/logic triggers when RSSI is actually unknown.

Issue description

mavlinkParseRxStats() in GENERIC mode unconditionally assigns rxLinkStatistics.uplinkRSSI = -msg->rssi;. MAVLink defines rssi == UINT8_MAX (255) as invalid/unknown, so the new code turns this into -255, which downstream code interprets as an extremely poor (but valid) dBm.

Issue Context

  • MAVLink RADIO_STATUS.rssi is uint8_t with values [0..254], 255 = invalid/unknown.
  • The same function already treats msg->rssi != 255 specially for LQ, indicating the sentinel is expected.
  • OSD displays rxLinkStatistics.uplinkRSSI directly and compares it to osdConfig()->rssi_dbm_alarm, so -255 can trigger alarms even though RSSI is unknown.

Fix Focus Areas

  • src/main/telemetry/mavlink.c[1318-1328]

Suggested change

  • Only negate when msg->rssi != UINT8_MAX; otherwise set uplinkRSSI to a defined “unknown” representation that won’t be misinterpreted as a real low dBm (e.g. 0, or a dedicated sentinel plus consumer handling).
  • Make the cast explicit to avoid any ambiguity: -(int16_t)msg->rssi.

[correctness] Invalid RSSI becomes -255
Invalid RSSI becomes -255 In mavlinkParseRxStats() GENERIC mode now negates msg->rssi without handling MAVLink’s invalid/unknown sentinel (UINT8_MAX/255), producing uplinkRSSI = -255. This value is consumed as a real dBm reading (e.g., by OSD alarm checks), causing false low-RSSI alarms/logic triggers when RSSI is actually unknown.

Issue description

mavlinkParseRxStats() in GENERIC mode unconditionally assigns rxLinkStatistics.uplinkRSSI = -msg->rssi;. MAVLink defines rssi == UINT8_MAX (255) as invalid/unknown, so the new code turns this into -255, which downstream code interprets as an extremely poor (but valid) dBm.

Issue Context

  • MAVLink RADIO_STATUS.rssi is uint8_t with values [0..254], 255 = invalid/unknown.
  • The same function already treats msg->rssi != 255 specially for LQ, indicating the sentinel is expected.
  • OSD displays rxLinkStatistics.uplinkRSSI directly and compares it to osdConfig()->rssi_dbm_alarm, so -255 can trigger alarms even though RSSI is unknown.

Fix Focus Areas

  • src/main/telemetry/mavlink.c[1318-1328]

Suggested change

  • Only negate when msg->rssi != UINT8_MAX; otherwise set uplinkRSSI to a defined “unknown” representation that won’t be misinterpreted as a real low dBm (e.g. 0, or a dedicated sentinel plus consumer handling).
  • Make the cast explicit to avoid any ambiguity: -(int16_t)msg->rssi.


                     PR 11766 (2026-08-07)                    
[correctness] HRST counter never resets
HRST counter never resets hottPrepareGPSResponse() uses a function-static hrstSent counter to limit BOXHOMERESET (“HRST”) display, but never resets it when the mode is released. After it reaches the limit once, later home-reset activations will never show HRST again until reboot.

Issue description

hrstSent is a function-static counter used to rate-limit the "HRST" indication when BOXHOMERESET is active, but it is never reset when the mode becomes inactive. This permanently disables the HRST indication after the first limited run.

Issue Context

A similar flight-mode text implementation resets hrstSent when BOXHOMERESET is not active, allowing each new activation to show the indication again.

Fix Focus Areas

  • src/main/telemetry/hott.c[229-382]

Implementation notes

  • After the flight-mode selection logic, add something like:
  • if (!IS_RC_MODE_ACTIVE(BOXHOMERESET) && hrstSent > 0) hrstSent = 0;
  • Optionally also reset hrstSent when disarmed to avoid carrying state across arm cycles.

[correctness] Stale free-char status
Stale free-char status hottPrepareGPSResponse() still returns early when there is no GPS fix, but the PR’s new free_char1..3 status text is set after that return and is not cleared per call. After a GPS fix drop, the HoTT GPS message can continue transmitting stale free_char values from a previous state.

Issue description

The function returns early on !STATE(GPS_FIX) (or !STATE(GPS_FIX) && !STATE(GPS_ESTIMATED_FIX)), skipping the newly added free_char status updates. Because the message struct is reused across calls (not memset each time), the free_char bytes can retain stale values after GPS fix loss.

Issue Context

initialiseGPSMessage() zeros the struct once at init; hottPrepareGPSResponse() mutates fields in-place and has an early return on no-fix.

Fix Focus Areas

  • src/main/telemetry/hott.c[180-187]
  • src/main/telemetry/hott.c[229-381]

Implementation notes

Choose one of:

  1. Set a safe default for free_char1..3 (e.g. blanks or "---" / "NOF") before the GPS-fix early return and also set them in the no-fix branch.
  2. Move the free_char flight-mode/status computation above the GPS-fix early return so it always runs. Also consider clearing home_direction/flight_direction in the no-fix path to avoid other stale fields.

[correctness] HRST counter never resets
HRST counter never resets hottPrepareGPSResponse() uses a function-static hrstSent counter to limit BOXHOMERESET (“HRST”) display, but never resets it when the mode is released. After it reaches the limit once, later home-reset activations will never show HRST again until reboot.

Issue description

hrstSent is a function-static counter used to rate-limit the "HRST" indication when BOXHOMERESET is active, but it is never reset when the mode becomes inactive. This permanently disables the HRST indication after the first limited run.

Issue Context

A similar flight-mode text implementation resets hrstSent when BOXHOMERESET is not active, allowing each new activation to show the indication again.

Fix Focus Areas

  • src/main/telemetry/hott.c[229-382]

Implementation notes

  • After the flight-mode selection logic, add something like:
  • if (!IS_RC_MODE_ACTIVE(BOXHOMERESET) && hrstSent > 0) hrstSent = 0;
  • Optionally also reset hrstSent when disarmed to avoid carrying state across arm cycles.

[correctness] Stale free-char status
Stale free-char status hottPrepareGPSResponse() still returns early when there is no GPS fix, but the PR’s new free_char1..3 status text is set after that return and is not cleared per call. After a GPS fix drop, the HoTT GPS message can continue transmitting stale free_char values from a previous state.

Issue description

The function returns early on !STATE(GPS_FIX) (or !STATE(GPS_FIX) && !STATE(GPS_ESTIMATED_FIX)), skipping the newly added free_char status updates. Because the message struct is reused across calls (not memset each time), the free_char bytes can retain stale values after GPS fix loss.

Issue Context

initialiseGPSMessage() zeros the struct once at init; hottPrepareGPSResponse() mutates fields in-place and has an early return on no-fix.

Fix Focus Areas

  • src/main/telemetry/hott.c[180-187]
  • src/main/telemetry/hott.c[229-381]

Implementation notes

Choose one of:

  1. Set a safe default for free_char1..3 (e.g. blanks or "---" / "NOF") before the GPS-fix early return and also set them in the no-fix branch.
  2. Move the free_char flight-mode/status computation above the GPS-fix early return so it always runs. Also consider clearing home_direction/flight_direction in the no-fix path to avoid other stale fields.

[correctness] HRST counter never resets
HRST counter never resets hottPrepareGPSResponse() uses a function-static hrstSent counter to limit BOXHOMERESET (“HRST”) display, but never resets it when the mode is released. After it reaches the limit once, later home-reset activations will never show HRST again until reboot.

Issue description

hrstSent is a function-static counter used to rate-limit the "HRST" indication when BOXHOMERESET is active, but it is never reset when the mode becomes inactive. This permanently disables the HRST indication after the first limited run.

Issue Context

A similar flight-mode text implementation resets hrstSent when BOXHOMERESET is not active, allowing each new activation to show the indication again.

Fix Focus Areas

  • src/main/telemetry/hott.c[229-382]

Implementation notes

  • After the flight-mode selection logic, add something like:
  • if (!IS_RC_MODE_ACTIVE(BOXHOMERESET) && hrstSent > 0) hrstSent = 0;
  • Optionally also reset hrstSent when disarmed to avoid carrying state across arm cycles.

[correctness] Stale free-char status
Stale free-char status hottPrepareGPSResponse() still returns early when there is no GPS fix, but the PR’s new free_char1..3 status text is set after that return and is not cleared per call. After a GPS fix drop, the HoTT GPS message can continue transmitting stale free_char values from a previous state.

Issue description

The function returns early on !STATE(GPS_FIX) (or !STATE(GPS_FIX) && !STATE(GPS_ESTIMATED_FIX)), skipping the newly added free_char status updates. Because the message struct is reused across calls (not memset each time), the free_char bytes can retain stale values after GPS fix loss.

Issue Context

initialiseGPSMessage() zeros the struct once at init; hottPrepareGPSResponse() mutates fields in-place and has an early return on no-fix.

Fix Focus Areas

  • src/main/telemetry/hott.c[180-187]
  • src/main/telemetry/hott.c[229-381]

Implementation notes

Choose one of:

  1. Set a safe default for free_char1..3 (e.g. blanks or "---" / "NOF") before the GPS-fix early return and also set them in the no-fix branch.
  2. Move the free_char flight-mode/status computation above the GPS-fix early return so it always runs. Also consider clearing home_direction/flight_direction in the no-fix path to avoid other stale fields.


                     PR 11765 (2026-08-06)                    
[reliability] Retry loop off-by-one
Retry loop off-by-one icm40609dDeviceDetect() uses a uint8_t post-decrement do/while which performs 6 WHO_AM_I reads for attemptsRemaining=5, adding an extra 150ms delay and making retry behavior misleading.

Issue description

The retry loop in icm40609dDeviceDetect() runs one extra time due to do { ... } while (attemptsRemaining--); with an unsigned counter. This adds an extra delay on failed detection and obscures intent.

Issue Context

This does not cause an infinite loop, but it does perform 6 attempts when initialized to 5.

Fix Focus Areas

  • src/main/drivers/accgyro/accgyro_icm40609d.c[174-198]

Suggested fix approach

Replace with an explicit bounded loop, e.g.:


[reliability] Retry loop off-by-one
Retry loop off-by-one icm40609dDeviceDetect() uses a uint8_t post-decrement do/while which performs 6 WHO_AM_I reads for attemptsRemaining=5, adding an extra 150ms delay and making retry behavior misleading.

Issue description

The retry loop in icm40609dDeviceDetect() runs one extra time due to do { ... } while (attemptsRemaining--); with an unsigned counter. This adds an extra delay on failed detection and obscures intent.

Issue Context

This does not cause an infinite loop, but it does perform 6 attempts when initialized to 5.

Fix Focus Areas

  • src/main/drivers/accgyro/accgyro_icm40609d.c[174-198]

Suggested fix approach

Replace with an explicit bounded loop, e.g.:



                     PR 11759 (2026-08-02)                    
[correctness] Stale wind values
Stale wind values MSP2_INAV_WIND always serializes getEstimatedHorizontalWindSpeed() even when isEstimatedWindSpeedValid() is false, so it can return a previous (stale) non-zero estimate after the estimator invalidates on timeout. This violates the new MSP message contract that says speed/angle are 0 when unavailable and can mislead MSP consumers that don’t strictly gate on the validity bit.

Issue description

MSP2_INAV_WIND currently writes wind speed/angle values unconditionally, and only sets a validity flag afterward. Because the wind estimator can become invalid without clearing the last estimated wind vector, the MSP reply can contain non-zero (stale) values while flags.bit0 == 0, contradicting the documented contract (“0 if unavailable”).

Issue Context

  • getEstimatedHorizontalWindSpeed() computes magnitude/angle from cached estimatedWind[] regardless of validity.
  • The estimator invalidation path clears hasValidWindEstimate but does not reset estimatedWind[].
  • The MSP docs for MSP2_INAV_WIND state the fields are zero when unavailable.

Fix Focus Areas

  • src/main/fc/fc_msp.c[1603-1618]

Recommended implementation approach

  • Check isEstimatedWindSpeedValid() first.
  • If invalid: write windSpeed=0, windAngle=0, flags=0.
  • If valid: compute and write speed/angle, set flags=1.
  • Optionally clamp the float speed to [0, UINT16_MAX] before casting to avoid wrap/truncation surprises.


                     PR 11757 (2026-08-01)                    
[correctness] Waypoint speed sign wrap
Waypoint speed sign wrap getActiveSpeed() reads signed waypoint params (p1/p2 are int16_t) into a uint16_t and returns it directly for AIRPLANE, so negative/sentinel values wrap to large positive speeds. In WP-enabled Auto Speed, this wrapped value is then constrained, effectively commanding max Auto Speed unexpectedly.

Issue description

getActiveSpeed() assigns navWaypoint_t.p1/p2 (signed int16_t) to a uint16_t temporary and returns it for AIRPLANE without validating it. Negative values (e.g. -1) become large unsigned values (e.g. 65535) and later get clamped to maxSpeed, producing an unintended full-speed demand.

Issue Context

Waypoint params are stored in navWaypoint_t as signed integers. The fixed-wing WP Auto Speed path uses getActiveSpeed() to derive speed demand and constrains it between fw_auto_speed_min_speed and fw_auto_speed_max_speed.

Fix Focus Areas

  • src/main/navigation/navigation.c[4321-4353]
  • src/main/navigation/navigation.h[553-560]
  • src/main/navigation/navigation_fixedwing.c[939-948]

Proposed fix

  • Keep waypoint speed as a signed type (int16_t) when reading p1/p2.
  • Treat <= 0 as “no WP speed set” (return 0 or fall back to configured default).
  • Optionally clamp to sane bounds before returning (e.g., MIN(wpSpeed, navConfig()->general.max_auto_speed) for MR; for FW, either clamp or rely on the later constrain but only after ensuring non-negative).

[maintainability] Autospeed flag has linkage
Autospeed flag has linkage autoSpeedIsActive is introduced as a non-static file-scope variable even though it is only used within navigation_fixedwing.c. This unnecessarily exports a new global symbol and increases the risk of future linkage collisions or accidental external modification.

Issue description

autoSpeedIsActive is defined at file scope without static, giving it external linkage even though it is only referenced inside navigation_fixedwing.c.

Issue Context

There is already a public accessor isFixedwingAutoSpeedActive(), so the backing flag does not need external linkage.

Fix Focus Areas

  • src/main/navigation/navigation_fixedwing.c[74-78]

Proposed fix

Change the definition to static bool autoSpeedIsActive = false; (or move it into a private module struct) to keep the symbol local to the translation unit.



                     PR 11756 (2026-08-01)                    
[correctness] BMI088 not configured
BMI088 not configured SYNERDUINOSTM_F405 enables USE_IMU_BMI088 but does not define the required BMI088_* SPI/I2C bus and chip-select macros, so no BMI088 bus devices are registered and BMI088 detection/initialization will fail. As written, the file defines BMI160_* macros instead, which are ignored by the BMI088 registration path.

Issue description

SYNERDUINOSTM_F405/target.h defines USE_IMU_BMI088 but does not provide the BMI088-specific configuration macros (BMI088_SPI_BUS + BMI088_GYRO_CS_PIN/BMI088_ACC_CS_PIN, or BMI088_I2C_BUS). Because common_hardware.c only registers BMI088 bus devices when those BMI088_* macros exist, the firmware will have no BMI088 device descriptors and BMI088 detection will fail.

Issue Context

This target currently sets BMI160_CS_PIN / BMI160_SPI_BUS in the BMI088 section, which doesn’t satisfy the BMI088 registration logic.

Fix Focus Areas

  • src/main/target/SYNERDUINOSTM_F405/target.h[59-67]
  • src/main/target/common_hardware.c[68-75]

[maintainability] SPI pin macro mismatch
SPI pin macro mismatch SYNERDUINOH7 enables `USE_SPI_DEVICE_1` and assigns IMUs to `BUS_SPI1`, but defines `SPI2_*_PIN` macros for PA5/PA6/PA7; these macros do not configure SPI1 and make the target configuration misleading/fragile. SPI1 will instead use `SPI1_*` macros (or their defaults), so future non-default pin changes here won’t take effect.

Issue description

SYNERDUINOH7/target.h selects SPI1 (USE_SPI_DEVICE_1, BUS_SPI1) but defines SPI2_SCK_PIN/SPI2_MISO_PIN/SPI2_MOSI_PIN. The SPI driver maps SPI1 using the SPI1_* macros, so these SPI2_* definitions are ignored for the active bus and can confuse future maintenance.

Issue Context

Currently the defined values happen to match SPI1 defaults on many boards (PA5/PA6/PA7), but the naming mismatch means any future non-default wiring/pin changes will silently not apply.

Fix Focus Areas

  • src/main/target/SYNERDUINOH7/target.h[99-116]
  • src/main/drivers/bus_spi.c[31-36]
  • src/main/drivers/bus_spi.c[83-91]


                     PR 11755 (2026-07-31)                    
[reliability] PWM beeper not enabled
PWM beeper not enabled The target defines BEEPER_PWM_FREQUENCY and provides a TIM_USE_BEEPER timer mapping on PA15, but targetConfiguration() never enables beeperConfigMutable()->pwmMode, so the PWM beeper path is never initialized. On boards with a passive beeper (which requires PWM to generate tone), this results in a non-functional beeper despite the PWM configuration being present.

Issue description

The target defines PWM-beeper parameters (BEEPER_PWM_FREQUENCY and a TIM_USE_BEEPER timer mapping), but targetConfiguration() does not enable beeperConfig()->pwmMode. As a result, beeperPwmInit() is not called and the timer-based beeper output is never configured.

Issue Context

sound_beeper.c only initializes PWM beeper when beeperConfig()->pwmMode is true; otherwise it uses GPIO toggling.

Fix Focus Areas

  • src/main/target/HUMMINGBIRD_FC305_H7/config.c[18-29]

Suggested fix

  1. Include fc/config.h in config.c.
  2. Set beeperConfigMutable()->pwmMode = true; in targetConfiguration() (if the hardware is passive / intended to use PWM).
  3. If the hardware is actually an active buzzer, remove the PWM-specific configuration (timer mapping / frequency) to avoid confusion.

[correctness] Board ID collision
Board ID collision HUMMINGBIRD_FC305_H7 sets TARGET_BOARD_IDENTIFIER to "HBRD", which is already used by HUMMINGBIRD_FC305, making the MSP-reported boardIdentifier ambiguous for tooling that keys off this field. This can cause target mis-identification when selecting board-specific defaults/resources or in support/debug workflows.

Issue description

TARGET_BOARD_IDENTIFIER is duplicated between two different targets (HUMMINGBIRD_FC305 and HUMMINGBIRD_FC305_H7). Since MSP exports this identifier, tools/users relying on boardIdentifier cannot reliably distinguish which firmware/target is running.

Issue Context

The new target defines TARGET_BOARD_IDENTIFIER "HBRD", but the existing HUMMINGBIRD_FC305 target already uses the same identifier.

Fix Focus Areas

  • src/main/target/HUMMINGBIRD_FC305_H7/target.h[20-22]

Suggested fix

Change TARGET_BOARD_IDENTIFIER for HUMMINGBIRD_FC305_H7 to a unique (ideally 4-character, uppercase) identifier, and ensure any documentation/release tooling that references the identifier is updated accordingly.



                     PR 11688 (2026-07-06)                    
[correctness] Reboots churn persisted node IDs
Reboots churn persisted node IDs When a known peripheral restarts within the 10-second NodeStatus stale window, `dnaLookupOrAssignNode()` treats that peripheral's own cached node ID as an active conflict and overwrites its persisted allocation with a new ID. Repeated quick restarts can therefore consume successive IDs and trigger unnecessary configuration saves instead of returning the stable mapping.

Issue description

A rebooting peripheral can be mistaken for a conflicting live node because its previous NodeStatus entry remains cached for 10 seconds. This causes its persisted node ID to be replaced during rapid restart.

Issue Context

The live-node table contains only node IDs and retains them until the stale timeout, so presence in that table does not prove another node is still broadcasting. Preserve the stored mapping unless the occupant is verified as a different currently-live node, or defer allocation until the cached entry expires.

Fix Focus Areas

  • src/main/drivers/dronecan/dronecan_dna_server.c[177-192]
  • src/main/drivers/dronecan/dronecan_node_status.c[150-161]
  • src/main/drivers/dronecan/dronecan.h[58-58]


                     PR 11683 (2026-07-02)                    
[reliability] Timeout checked before RX
Timeout checked before RX dronecanUpdate() calls dronecanAsyncCheckTimeout() before draining the CAN RX FIFO, so a response already queued can be discarded as ERROR when the millis() delta crosses the threshold in that tick. Because the response handler ignores non-PENDING slots, this can produce false timeouts even when the response arrived within DRONECAN_ASYNC_TIMEOUT_MS.

Issue description

dronecanAsyncCheckTimeout() is executed before processing queued RX frames in dronecanUpdate(). If a response arrives just before the timeout boundary but the next dronecanUpdate() tick occurs at/after the boundary, the slot can be marked ERROR and the queued response will then be ignored by dronecanAsyncHandleServiceResponse() (it only processes when state==PENDING).

Issue Context

The timeout logic uses millis() granularity, so boundary effects are realistic. A correct implementation should always give queued RX frames a chance to resolve the slot before expiring it.

Fix Focus Areas

  • src/main/drivers/dronecan/dronecan.c[145-168]
  • src/main/drivers/dronecan/dronecan_async.c[159-168]

Suggested fix

Reorder the NORMAL-state loop so it drains RX (and any resulting TX) first, then calls dronecanAsyncCheckTimeout() after RX handling. Optionally, call the timeout check once per loop iteration after RX+TX drain, so a response in the FIFO always wins over expiring the slot.



                     PR 11681 (2026-07-02)                    
[correctness] AAF freq overflow
AAF freq overflow In getGyroAafConfig(), selectedFreq is stored as int8_t but the AAF lookup table contains frequencies like 258 and 303, so selectedFreq overflows and can corrupt the “closest frequency” comparison, selecting the wrong AAF parameters for ICM42688P/ICM42686P.

Issue description

getGyroAafConfig() uses int8_t selectedFreq to hold LUT frequencies. For the 42688/42686 path the LUT includes values >127 (e.g. 258, 303), which overflow int8_t and can cause the function to pick the wrong AAF candidate.

Issue Context

This function selects the closest supported AAF cutoff frequency by comparing ABS(desiredFreq - aafConfigs[i].freq) against ABS(desiredFreq - selectedFreq). If selectedFreq wraps, the comparison becomes invalid.

Fix

  • Change selectedFreq to a wide signed type (e.g. int32_t or uint16_t, but prefer int32_t for safe signed subtraction).
  • Ensure the subtraction is performed in a sufficiently wide signed type, e.g.:
    • const int32_t desired = desiredFreq;
    • compare ABS(desired - (int32_t)aafConfigs[i].freq)

Fix Focus Areas

  • src/main/drivers/accgyro/accgyro_icm42605.c[415-443]


⚠️ **GitHub.com Fallback** ⚠️