Skip to content

Support custom Tramp power levels and AUX pit mode - #11887

Open
Raffi1202 wants to merge 8 commits into
iNavFlight:maintenance-10.xfrom
Raffi1202:fix/tramp-custom-power-pitmode
Open

Raffi1202 wants to merge 8 commits into
iNavFlight:maintenance-10.xfrom
Raffi1202:fix/tramp-custom-power-pitmode

Conversation

@Raffi1202

@Raffi1202 Raffi1202 commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Adds configurable Tramp power levels and an AUX pit-mode switch for #9841. Valid consecutive ascending values in vtx_tramp_power_a through e replace the fixed table after device capability detection. Advertised levels and labels reflect the reported or explicitly overridden power limit; duplicate capped levels are collapsed. Existing VTX settings migrate from PG version 2.

VTX PIT MODE uses permanent mode ID 73 and is offered only by drivers supporting pit control. Tramp cancels queued pit-entry requests on arming and rotates fairly through frequency, power and pit commands, so an ignored command cannot starve the others. Pit mismatch retries stop after three attempts; reconnect renews the budget. Blackbox records the third mode word.

The CMS power selector refreshes when device capabilities arrive or change and clamps the displayed index before rendering or saving. An overlarge saved setting remains unchanged until the user explicitly saves a valid selection.

Validation: 36 host tests passed (20 Tramp, 6 CMS, 9 AUX pit-mode, 1 settings migration), including startup metadata, ignored commands, arming before dispatch, reconnect and changing CMS capabilities. SPEEDYBEEF405V4 and MATEKH743 builds passed. Current CI results are tracked in the PR checks. Physical VTX testing is outstanding. Configuration, limits and migration are documented in docs/VTx.md.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Branch Targeting Suggestion

You've targeted the master branch with this PR. Please consider if a version branch might be more appropriate:

  • maintenance-9.x - If your change is backward-compatible and won't create compatibility issues between INAV firmware and Configurator 9.x versions. This will allow your PR to be included in the next 9.x release.

  • maintenance-10.x - If your change introduces compatibility requirements between firmware and configurator that would break 9.x compatibility. This is for PRs which will be included in INAV 10.x

If master is the correct target for this change, no action is needed.


This is an automated suggestion to help route contributions to the appropriate branch.

@Raffi1202
Raffi1202 marked this pull request as ready for review September 9, 2026 18:15
@qodo-code-review

Copy link
Copy Markdown
Contributor

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

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

Copy link
Copy Markdown

PR Summary by Qodo

Add custom Tramp power levels and AUX pit mode

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Adds validated custom Tramp power levels while preserving automatic fallback and power caps.
• Adds AUX-controlled, arming-safe pit mode with bidirectional commands and mismatch retries.
• Corrects MSP labels and covers behavior with host tests and migration documentation.
Diagram

graph TD
  AUX["AUX Mode"] -->|"pit state"| CONTROL["VTX Control"] -->|"guarded request"| DRIVER["Tramp Driver"] -->|"I and P commands"| DEVICE["Tramp VTX"]
  SETTINGS["VTX Settings"] -->|"custom powers"| DRIVER -->|"power labels"| MSP["MSP API"]
  DEVICE -->|"status reply"| DRIVER
Loading
High-Level Assessment

The configuration-driven power table and existing common VTX control path are the best fit. Device-specific tables would not generalize to other Tramp-compatible hardware, while unconditional pit-mode synchronization could overwrite hardware-button or MSP-controlled state; the PR correctly avoids both.

Files changed (11) +436 / -28

Enhancement (4) +71 / -27
fc_msp_box.cExpose VTX pit mode as permanent mode 69 +8/-0

Expose VTX pit mode as permanent mode 69

• Registers 'VTX PIT MODE' with permanent ID 69, exposes it when VTX control is enabled, and includes its active state in MSP mode flags.

src/main/fc/fc_msp_box.c

rc_modes.hAdd the VTX pit-mode RC box identifier +1/-0

Add the VTX pit-mode RC box identifier

• Adds 'BOXVTXPITMODE' to the RC mode enumeration so AUX conditions can target VTX pit mode.

src/main/fc/rc_modes.h

vtx.cApply safe AUX-based pit-mode control +12/-21

Apply safe AUX-based pit-mode control

• Bumps 'PG_VTX_SETTINGS_CONFIG' from version 2 to 3. Pit-mode processing now acts only with an assigned mode and valid receiver signal, enters only while disarmed, and exits when armed.

src/main/io/vtx.c

vtx_tramp.cSupport custom powers and reliable bidirectional pit mode +50/-6

Support custom powers and reliable bidirectional pit mode

• Validates and installs custom ascending power tables with generated labels while preserving automatic fallback and existing power caps. Sends the Tramp 'I' command with correct enter/exit polarity, tracks explicit requests, and retries status mismatches without overriding untouched hardware state.

src/main/io/vtx_tramp.c

Bug fix (1) +3 / -1
fc_msp.cCorrect Tramp power-label indexing in MSP +3/-1

Correct Tramp power-label indexing in MSP

• Uses the one-based Tramp power-name index when serializing VTX table entries, skipping the reserved zero-index '---' label. Other VTX device types retain their existing zero-based lookup.

src/main/fc/fc_msp.c

Tests (3) +281 / -0
CMakeLists.txtRegister VTX pit-mode and Tramp unit tests +6/-0

Register VTX pit-mode and Tramp unit tests

• Adds compile definitions and production-source dependencies required to build the two new VTX test suites against the actual control and Tramp driver implementations.

src/test/unit/CMakeLists.txt

vtx_pit_mode_unittest.ccTest AUX pit-mode safety behavior +78/-0

Test AUX pit-mode safety behavior

• Adds six tests covering unassigned modes, bidirectional switch transitions, arming, receiver loss, matching state, and CLI suppression. The suite exercises the production VTX update path with controlled mode and device stubs.

src/test/unit/vtx_pit_mode_unittest.cc

vtx_tramp_unittest.ccTest Tramp power tables and protocol retries +197/-0

Test Tramp power tables and protocol retries

• Adds a serial-level Tramp harness covering automatic and custom power tables, invalid configurations, maximum caps, explicit override, and invalid indices. It also verifies command checksums and polarity, lost-command retries, rapid requests, preserved hardware state, and corrupt-status recovery.

src/test/unit/vtx_tramp_unittest.cc

Documentation (1) +50 / -0
VTx.mdDocument custom Tramp powers and AUX pit mode +50/-0

Document custom Tramp powers and AUX pit mode

• Documents custom power-table validation, maximum-power clamping, BLITZ configuration, and the version 3 settings migration. It also explains permanent mode 69, receiver and arming safeguards, command polarity, and hardware validation requirements.

docs/VTx.md

Other (2) +31 / -0
settings.yamlDefine five custom Tramp power settings +30/-0

Define five custom Tramp power settings

• Adds CLI settings 'vtx_tramp_power_a' through 'vtx_tramp_power_e', each accepting zero or up to 10,000 mW. Descriptions specify ascending consecutive values, automatic fallback, reboot requirements, and maximum-power limits.

src/main/fc/settings.yaml

vtx.hPersist optional Tramp power levels +1/-0

Persist optional Tramp power levels

• Extends VTX settings with five 16-bit custom Tramp power values, where trailing zeros terminate the configured table.

src/main/io/vtx.h

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

qodo-free-for-open-source-projects Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Signal loss does not stop pit changes 🐞 Bug ☼ Reliability
Description
impl_Process() dispatches queued Tramp pit-mode updates without rechecking receiver validity, even
though vtxProcessPitMode() checks it only when initially creating the request. If signal is lost
while the protocol state machine is busy, a pending command or mismatch retry can subsequently
change pit mode from stale switch state.
Code

src/main/io/vtx_tramp.c[R361-363]

+                else if (updateBit == VTX_UPDATE_REQ_PITMODE) {
+                    vtxProtoSetPitMode(vtxState.request.pitMode);
+                    vtxProtoSetState(VTX_STATE_QUERY_DELAY);
Evidence
The generic handler checks receiver validity only before queueing a request, while the Tramp driver
later sends and retries that request independently; its only execution-time cancellation handles
arming, not signal loss.

src/main/io/vtx.c[140-155]
src/main/io/vtx_tramp.c[294-300]
src/main/io/vtx_tramp.c[361-363]
src/main/io/vtx_tramp.c[401-410]
docs/VTx.md[80-87]

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

## Issue description
A Tramp pit-mode request accepted while receiver data is valid remains queued after signal loss and can later be dispatched or retried from stale AUX state.
## Fix Focus Areas
- src/main/io/vtx.c[140-155]
- src/main/io/vtx_tramp.c[294-300]
- src/main/io/vtx_tramp.c[361-363]
- src/main/io/vtx_tramp.c[401-410]
## Recommended Fix
Track whether a pending pit request is AUX-owned and cancel both its queued update and mismatch retries when receiver signal becomes invalid, without disturbing unassigned hardware-button or MSP control. Add tests where signal is lost while the driver is busy and after a failed command has entered the retry path.

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


2. Opening the VTX menu can crash ✓ Resolved 🐞 Bug ☼ Reliability
Description
vtxProcessPower() clamps only its local requestedPower before sending it, leaving
vtxSettingsConfig()->power at an index that no longer exists in a shortened custom Tramp table.
When that saved index is loaded into CMS, the menu dereferences a null/out-of-range custom
power-name slot; for example, vtx_power = 5 with a two-level custom table reaches
cmsDrawMenuItemValue() with customPowerNamePointers[5] unset.
Code

src/main/io/vtx.c[R124-129]

+    uint8_t requestedPower = runtimeSettings->power;
+    if (vtxCommonGetDeviceType(vtxDevice) == VTXDEV_TRAMP && vtxDevice->capability.powerCount) {
+        requestedPower = MIN(requestedPower, vtxDevice->capability.powerCount);
+    }
+    if (vtxPower != requestedPower) {
+        vtxCommonSetPowerByIndex(vtxDevice, requestedPower);
Evidence
The PR deliberately preserves an overlarge saved setting while only clamping the transmitted
request. CMS copies that persisted value into its tab control, then renders the tab by dereferencing
its names array without bounds or null validation; custom name pointers are initialized only through
the configured count.

src/main/io/vtx.c[122-129]
src/main/io/vtx_tramp.c[650-666]
src/main/cms/cms_menu_vtx.c[114-137]
src/main/cms/cms.c[466-470]
src/main/fc/settings.yaml[4428-4434]

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

## Issue description
A persisted `vtx_power` can exceed the number of configured custom Tramp levels. Runtime transmission is clamped, but CMS initializes its tab value from the unchanged persisted index and renders `names[value]`, which can dereference an unset custom-name pointer.
## Fix Focus Areas
- src/main/cms/cms_menu_vtx.c[114-137]
- src/main/io/vtx.c[122-129]
## Recommended Fix
When initializing the CMS VTX menu, clamp the displayed power index to the current device capability's `powerCount` before the tab can be rendered. Keep the existing runtime clamp, and ensure the tab value always falls in `0..powerCount` even when a saved setting predates a shortened custom table.

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


3. Aircraft can enter pit mode after arming ✓ Resolved 🐞 Bug ≡ Correctness
Description
vtxProcessPitMode() only revisits the desired state on its scheduled slot, while
impl_SetPitMode() leaves an already queued true request intact and impl_Process() later
transmits it without checking arming. If the craft arms after that request is queued but before the
Tramp state machine reaches its pit branch, the command still enters pit mode in flight and mismatch
handling can queue it again.
Code

src/main/io/vtx_tramp.c[R340-342]

+                else if (vtxState.updateReqMask & VTX_UPDATE_REQ_PITMODE) {
+                    vtxState.updateReqMask &= ~VTX_UPDATE_REQ_PITMODE;
+                    vtxProtoSetPitMode(vtxState.request.pitMode);
Evidence
The generic handler derives an off request when armed, but it runs only once per three-entry
schedule and calls the setter only when the reported hardware state differs. The 5 Hz task can
therefore arm between invocations while the Tramp driver retains and later transmits the previously
queued enable request; its status mismatch logic can also requeue that stale desired state.

src/main/io/vtx.c[129-186]
src/main/io/vtx_tramp.c[338-343]
src/main/io/vtx_tramp.c[381-389]
src/main/io/vtx_tramp.c[470-478]
src/main/fc/fc_tasks.c[746-751]

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

## Issue description
A Tramp pit-mode enable request queued while disarmed can still be transmitted after the aircraft arms because pending requests are not reconciled with the new desired state before driver processing.
## Fix Focus Areas
- src/main/io/vtx.c[129-186]
- src/main/io/vtx_tramp.c[338-343]
- src/main/io/vtx_tramp.c[470-478]
## Recommended Fix
Track changes to the desired AUX pit state independently of the currently reported hardware state, and overwrite or cancel a queued enable request before `vtxCommonProcess()` whenever arming requires pit mode off. Add a test that queues an enable request, arms before transmission, and verifies that no enter-pit command is sent.

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


View high (3)
4. Upgrades reset every saved VTX setting ✓ Resolved 🐞 Bug ≡ Correctness
Description
Changing PG_VTX_SETTINGS_CONFIG to version 3 makes pgLoad() reject the existing version-2 record
and reset the complete vtxSettingsConfig_t structure. Users upgrading with configured band,
channel, power, low-power behavior, pit frequency, or maximum-power override therefore start from
VTX defaults unless they manually save and restore their configuration.
Code

src/main/io/vtx.c[49]

+PG_REGISTER_WITH_RESET_TEMPLATE(vtxSettingsConfig_t, vtxSettingsConfig, PG_VTX_SETTINGS_CONFIG, 3);
Evidence
The changed registration makes the stored v2 group incompatible, and the PG loader resets rather
than partially loading incompatible records. The inserted field also precedes an existing field,
making the old binary layout incompatible without a migration-compatible layout change.

src/main/io/vtx.c[49-60]
src/main/io/vtx.h[41-45]
src/main/config/parameter_group.c[39-48]
src/main/config/parameter_group.c[73-80]

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

## Issue description
Increasing the parameter-group version discards all existing VTX settings because parameter groups only load persisted data when versions match. The new fields can be made backward-compatible by placing them after the existing persisted fields, allowing the reset template to initialize only the new tail fields when an older, shorter record is loaded.
## Fix Focus Areas
- src/main/io/vtx.c[49-49]
- src/main/io/vtx.h[41-45]
## Recommended Fix
Move `trampPowerLevels` to the end of `vtxSettingsConfig_t` after the existing fields and retain the compatible PG version so version-2 records are copied into the existing prefix after reset. This preserves prior VTX configuration while the reset-initialized appended array remains zeroed.

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


5. Pit failures block channel and power ✓ Resolved 🐞 Bug ☼ Reliability
Description
impl_Process requeues every reported pit-mode mismatch without a retry limit, and the idle
dispatcher always services pit mode before frequency and power. If a Tramp-compatible device rejects
the command or never reaches the requested state, each status response restores the highest-priority
request and leaves all pending channel and power changes unsent indefinitely.
Code

src/main/io/vtx_tramp.c[R373-375]

+                    if (vtxState.request.pitModeRequested && vtxState.state.pitMode != vtxState.request.pitMode) {
+                        vtxState.updateReqMask |= VTX_UPDATE_REQ_PITMODE;
+                    }
Evidence
The idle state checks the pit bit first, ahead of frequency and power, and clears it only when
sending the command. The newly added status logic sets that bit again on every mismatch, so a
permanent mismatch recreates it before the next idle dispatch and the lower-priority bits can never
be selected.

src/main/io/vtx_tramp.c[318-337]
src/main/io/vtx_tramp.c[353-378]
src/main/io/vtx_tramp.c[452-458]

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

## Issue description
Persistent pit-mode mismatches continuously recreate the highest-priority update request. A device that does not implement the command can therefore prevent frequency and power updates from ever being transmitted.
## Fix Focus Areas
- src/main/io/vtx_tramp.c[318-375]
- src/main/io/vtx_tramp.c[452-458]
## Recommended Fix
Track bounded retries for each explicit pit-mode request and schedule pending update types fairly rather than always prioritizing pit mode. Stop automatically requeuing after the retry budget is exhausted while continuing normal status polling and allowing channel and power requests to run.

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


6. A saved power level can be ignored ✓ Resolved 🐞 Bug ≡ Correctness
Description
vtxProtoUseCustomPowerTable() replaces the device capability count with the number of configured
custom levels, while vtxProcessPower() continues submitting the persisted vtx_power index
unchanged. When an existing setting is higher than the custom table count, impl_SetPowerByIndex()
rejects it without updating the requested state, so startup and subsequent retries send no power
command and leave the VTX at its prior power.
Code

src/main/io/vtx_tramp.c[R616-619]

+    vtxState.metadata.powerTablePtr = levels;
+    vtxState.metadata.powerTableCount = count;
+    impl_vtxDevice.capability.powerCount = count;
+    impl_vtxDevice.capability.powerNames = customPowerNamePointers;
Evidence
The PR changes the advertised and accepted level count to the custom table length, but the persisted
setting still allows up to eight levels and is passed directly into the driver. An index above the
custom count is explicitly returned before a request is queued, so no later protocol processing can
transmit the configured power.

src/main/io/vtx_tramp.c[435-449]
src/main/io/vtx_tramp.c[591-620]
src/main/io/vtx.c[114-124]
src/main/fc/settings.yaml[4071-4076]
src/main/drivers/vtx_common.h[35-44]

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

## Issue description
A persisted `vtx_power` value can exceed the newly configured custom Tramp table length. The driver rejects that index and never queues a power update, leaving the VTX at its previous output power.
## Fix Focus Areas
- src/main/io/vtx_tramp.c[616-619]
- src/main/io/vtx.c[114-124]
## Recommended Fix
When custom metadata is installed, ensure the configured/requested power index is representable by the custom table. Clamp an out-of-range persisted index to the highest configured custom level before submitting it, or make the Tramp setter map indices above `powerTableCount` to that highest valid level; retain rejection for index zero.

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



Remediation recommended

7. Pit switch changes emit no log event 🐞 Bug ◔ Observability
Description
blackboxCheckAndLogFlightMode() still compares and stores only one 32-bit mode word even though
BOXVTXPITMODE now occupies the newly logged third word. Toggling that switch without changing a
lower-numbered mode therefore produces no flight-mode event, and the existing event payload cannot
represent its state or transition.
Code

src/main/blackbox/blackbox.c[1518]

+    slow->rcModeFlags3 = rcModeActivationMask.bits[2]; // box IDs 64 and above
Evidence
The PR populates the third slow-state word from bitmask word two, but Blackbox's history variable,
comparison size, and event payload remain single-word values, so a change isolated to bit 64 is
invisible to event generation.

src/main/fc/rc_modes.h[89-93]
src/main/blackbox/blackbox.c[1515-1518]
src/main/blackbox/blackbox.c[670-670]
src/main/blackbox/blackbox.c[2241-2246]
src/main/blackbox/blackbox_fielddefs.h[179-182]

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

## Issue description
Blackbox slow frames now contain the third RC mode word, but flight-mode change detection and event serialization remain limited to the first 32-bit word.
## Fix Focus Areas
- src/main/blackbox/blackbox.c[1515-1518]
- src/main/blackbox/blackbox.c[1708-1708]
- src/main/blackbox/blackbox.c[2198-2202]
- src/main/blackbox/blackbox.c[2241-2246]
- src/main/blackbox/blackbox_fielddefs.h[179-182]
## Recommended Fix
Expand the stored mode history and change comparison to cover every `boxBitmask_t` word, then extend the Blackbox flight-mode event format and corresponding decoder contract to serialize the additional current and previous words. Add a test proving that toggling mode ID 64 emits an event containing the transition.

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


8. MSP status clients misparse the final field 🐞 Bug ≡ Correctness
Description
BOXVTXPITMODE increases CHECKBOX_ITEM_COUNT to 65, but the generated MSP enum references still
declare it as 63. MSP2_INAV_STATUS is documented as emitting one 32-bit active-mode word per count
and firmware writes the full boxBitmask_t, so tools following that metadata consume two words and
decode the third mode word as the mixer-profile byte.
Code

src/main/fc/rc_modes.h[R92-93]

+    BOXVTXPITMODE    = 64,
   CHECKBOX_ITEM_COUNT
Evidence
The changed enum creates 65 checkbox entries, requiring three 32-bit words. The checked-in generated
enum still reports 63, while the MSP status documentation specifies that active-mode output length
derives from this value; the firmware serializes the complete bitmask.

src/main/fc/rc_modes.h[89-97]
docs/development/msp/inav_enums.json[640-662]
docs/development/msp/inav_enums_ref.md[1249-1249]
docs/development/msp/README.md[3173-3185]
src/main/fc/fc_msp.c[570-583]

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

## Issue description
The new checkbox ID raises `CHECKBOX_ITEM_COUNT` to 65, which expands `boxBitmask_t` from two 32-bit words to three. The generated MSP enum metadata still reports 63, so consumers that size `MSP2_INAV_STATUS.activeModes` from this metadata will read the new third word as the following `mixerProfile` field.
## Fix Focus Areas
- src/main/fc/rc_modes.h[92-93]
- docs/development/msp/inav_enums.json[640-662]
- docs/development/msp/inav_enums_ref.md[1249-1249]
## Recommended Fix
Regenerate the MSP documentation using the repository's enum documentation generator and commit the resulting enum JSON and reference Markdown updates. Verify the generated enum includes both `BOXTHRUSTREVERSE`, `BOXVTXPITMODE`, and `CHECKBOX_ITEM_COUNT = 65`.

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


9. Rejected settings disable pit mode ✓ Resolved 🐞 Bug ☼ Reliability
Description
impl_Process now dispatches pit mode after frequency and power, while status mismatches requeue
those higher-priority requests without any retry bound. If a Tramp device rejects or quantizes a
requested frequency or custom power value, the resulting command/status loop continuously wins
dispatch and the pending pit-mode request never reaches the device.
Code

src/main/io/vtx_tramp.c[R351-354]

+                else if (vtxState.updateReqMask & VTX_UPDATE_REQ_PITMODE) {
+                    vtxState.updateReqMask &= ~VTX_UPDATE_REQ_PITMODE;
+                    vtxProtoSetPitMode(vtxState.request.pitMode);
+                    vtxProtoSetState(VTX_STATE_QUERY_DELAY);
Evidence
The idle dispatcher handles frequency and power before the newly moved pit-mode branch, while each
status response restores frequency and power bits whenever the reported values differ and only pit
mode has a retry limit. Custom tables accept arbitrary configured milliwatt values and the setter
transmits the selected value directly, so a device that rounds or rejects such a value can sustain
the higher-priority mismatch indefinitely.

src/main/io/vtx_tramp.c[339-354]
src/main/io/vtx_tramp.c[383-400]
src/main/io/vtx_tramp.c[459-473]
src/main/io/vtx_tramp.c[625-667]

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

## Issue description
Frequency and power mismatches are retried indefinitely and have priority over pit mode, so a device that rejects or quantizes either requested value can prevent pit-mode commands from ever being transmitted.
## Fix Focus Areas
- src/main/io/vtx_tramp.c[339-354]
- src/main/io/vtx_tramp.c[383-400]
## Recommended Fix
Add bounded mismatch retry tracking for frequency and power, analogous to pit-mode retry handling. Reset each budget when its requested value changes, the reported state matches, or the device reconnects, and stop requeuing an exhausted request so lower-priority pit-mode updates can run.

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


View medium (6)
10. CMS shows the wrong power levels ✓ Resolved 🐞 Bug ≡ Correctness
Description
vtxProtoUseCustomPowerTable() replaces the live Tramp capability count and names only after the
asynchronous capabilities response, whereas CMS copies those fields into its static power tab only
when the menu is entered. If CMS is opened before that response, the open menu retains the fallback
automatic labels and range after the custom table becomes active, so selections are displayed as
different power levels from those sent to the VTX until the menu is reopened.
Code

src/main/io/vtx_tramp.c[R663-666]

+    vtxState.metadata.powerTablePtr = customPowerLevels;
+    vtxState.metadata.powerTableCount = count;
+    impl_vtxDevice.capability.powerCount = count;
+    impl_vtxDevice.capability.powerNames = customPowerNamePointers;
Evidence
The driver intentionally retains an automatic startup table until it knows the VTX power limit, then
mutates the capability fields. CMS does not retain a live capability reference: it copies the count
and names to static menu state exclusively during its enter callback, while the renderer uses that
cached state.

src/main/io/vtx_tramp.c[625-630]
src/main/io/vtx_tramp.c[650-666]
src/main/io/vtx_tramp.c[739-759]
src/main/cms/cms_menu_vtx.c[93-142]
src/main/cms/cms.c[466-470]

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

## Issue description
Tramp starts with an automatic fallback power table and installs the custom table only after receiving capabilities. CMS snapshots the capability names and count when entered, so a menu opened during that interval continues to display the fallback table after the device starts using custom levels.
## Fix Focus Areas
- src/main/io/vtx_tramp.c[625-667]
- src/main/cms/cms_menu_vtx.c[93-142]
## Recommended Fix
Ensure an already-open CMS VTX power tab is refreshed when Tramp replaces its metadata after capability discovery, or defer exposing the VTX power controls until the device is ready. The tab's count and name pointer must match the table used by the power setter.

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


11. Power controls briefly offer only 0 mW ✓ Resolved 🐞 Bug ≡ Correctness
Description
vtxProtoUseCustomPowerTable() clamps configured levels against vtxState.capabilities.powerMax,
but vtxTrampInit() has just zeroed that state when it calls the metadata updater. Until the
capability reply supplies the maximum, valid custom tables are collapsed and published as a single 0
mW level, which CMS and MSP capability consumers can read without waiting for the device to become
ready.
Code

src/main/io/vtx_tramp.c[R649-650]

+        const uint16_t effectivePower = MIN(levels[i], vtxState.capabilities.powerMax);
+        if (count && effectivePower == customPowerLevels[count - 1]) {
Evidence
The custom-table builder derives every advertised power from the current capability maximum and
immediately replaces the device metadata. Initialization clears that capability state and invokes
the metadata updater before the later protocol response fills the maximum; meanwhile capability
retrieval and the CMS menu consume the published metadata without a readiness guard.

src/main/io/vtx_tramp.c[625-662]
src/main/io/vtx_tramp.c[236-252]
src/main/io/vtx_tramp.c[736-752]
src/main/drivers/vtx_common.c[140-147]
src/main/cms/cms_menu_vtx.c[108-123]

Agent prompt
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
A valid configured custom Tramp power table is installed during driver initialization before the VTX capability response has populated `vtxState.capabilities.powerMax`. Every entry is therefore capped to zero and deduplicated into one advertised 0 mW level until detection completes.
Fix Focus Areas
- src/main/io/vtx_tramp.c[625-662]
- src/main/io/vtx_tramp.c[736-752]
Recommended Fix
Only apply the custom table after a successful capability response has established a nonzero effective maximum. Keep the automatic metadata installed during initial/offline state, then rebuild and publish the capped custom table from the capability-response update path.

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


12. Flight logs omit the pit switch state ✓ Resolved 🐞 Bug ◔ Observability
Description
BOXVTXPITMODE is assigned bit 64, but loadSlowState() serializes only boxBitmask_t.bits[0] and
bits[1] into the two Blackbox mode fields. Whenever the new AUX mode is active, its third-word bit
is silently absent from the flight log, so pit-mode activation cannot be reconstructed during
diagnosis.
Code

src/main/fc/rc_modes.h[92]

+    BOXVTXPITMODE    = 64,
Evidence
The enum extension raises the mode count to 65, which makes boxBitmask_t contain three 32-bit
words. Blackbox's slow state defines only two mode fields and explicitly copies only words zero and
one, leaving the new bit outside the recorded data.

src/main/fc/rc_modes.h[91-97]
src/main/common/bitarray.h[29-31]
src/main/blackbox/blackbox.c[624-629]
src/main/blackbox/blackbox.c[1508-1512]

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

## Issue description
The new pit mode occupies bit 64 in a third bitmask word, while Blackbox records only the first two words and therefore drops its state from flight logs.
## Fix Focus Areas
- src/main/fc/rc_modes.h[91-97]
- src/main/blackbox/blackbox.c[624-629]
- src/main/blackbox/blackbox.c[1508-1512]
## Recommended Fix
Extend the Blackbox slow-state representation and serialization to encode the third RC-mode word while preserving the required log-format compatibility. Add coverage proving that an active mode at bit 64 is present in the serialized slow-state record.

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


13. Selected custom power can be lower ✓ Resolved 🐞 Bug ≡ Correctness
Description
vtxProtoUseCustomPowerTable() advertises every configured custom entry without considering
vtxState.capabilities.powerMax, while impl_SetPowerByIndex() silently clamps its command to that
maximum. A device reporting 400 mW can consequently offer a 2500 mW custom level through MSP and CMS
but receive a 400 mW command when the pilot selects it.
Code

src/main/io/vtx_tramp.c[R637-640]

+    vtxState.metadata.powerTablePtr = levels;
+    vtxState.metadata.powerTableCount = count;
+    impl_vtxDevice.capability.powerCount = count;
+    impl_vtxDevice.capability.powerNames = customPowerNamePointers;
Evidence
The new custom path installs the raw configured table and count into the VTX capability metadata.
Power transmission separately clamps that raw selection to the detected maximum, while MSP uses the
capability count and names to expose the selectable levels.

src/main/io/vtx_tramp.c[612-640]
src/main/io/vtx_tramp.c[453-464]
src/main/io/vtx_tramp.c[222-239]
src/main/fc/fc_msp.c[5124-5143]

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

## Issue description
Custom power metadata exposes configured levels above the detected or overridden VTX maximum even though the transmit path clamps those levels. This lets the user select and see a power label that cannot be sent to the VTX.
## Fix Focus Areas
- src/main/io/vtx_tramp.c[612-640]
- src/main/io/vtx_tramp.c[453-464]
## Recommended Fix
While building the custom metadata, only include levels at or below `vtxState.capabilities.powerMax` (or generate capped duplicate labels consistently with the automatic tables). Set the advertised count and name pointers from that effective table so every selectable level corresponds to its transmitted power.

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


14. Pit mode cannot recover after reconnect ✓ Resolved 🐞 Bug ☼ Reliability
Description
The new retry gate stops enqueuing pit requests after pitModeRetries reaches three, but protocol
reset leaves that counter and the same requested state intact. If the VTX disconnects after
exhausting its retries, later status mismatches after detection cannot schedule another command
unless the pilot first changes the AUX switch state.
Code

src/main/io/vtx_tramp.c[R381-385]

+                    if (vtxState.request.pitModeRequested && vtxState.state.pitMode != vtxState.request.pitMode) {
+                        if (vtxState.request.pitModeRetries < VTX_PITMODE_MAX_RETRIES) {
+                            vtxState.request.pitModeRetries++;
+                            vtxState.updateReqMask |= VTX_UPDATE_REQ_PITMODE;
+                        }
Evidence
The added logic only requeues while the counter is below the limit, whereas reset clears only
protocol timeout and pending requests. Repeated requests for the already requested state
intentionally leave the counter unchanged, so the generic pit-mode controller cannot restore the
exhausted budget on its own.

src/main/io/vtx_tramp.c[292-297]
src/main/io/vtx_tramp.c[381-389]
src/main/io/vtx_tramp.c[470-479]
src/main/io/vtx.c[134-149]

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

## Issue description
An exhausted pit-mode retry budget survives protocol reset and reconnection. Since same-state requests intentionally do not refill the budget, the normal AUX control loop cannot resend pit mode after the VTX becomes available again.
## Fix Focus Areas
- src/main/io/vtx_tramp.c[292-297]
- src/main/io/vtx_tramp.c[381-389]
- src/main/io/vtx_tramp.c[470-479]
## Recommended Fix
Clear `vtxState.request.pitModeRetries` when entering or processing `VTX_STATE_RESET`, while retaining the requested pit state. The first post-reconnect status mismatch can then enqueue a fresh bounded retry sequence; add a host test covering exhausted retries followed by timeout/reset and a successful reconnect.

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


15. Some transmitters ignore the pit switch ✓ Resolved 🐞 Bug ≡ Correctness
Description
initActiveBoxIds exposes the new pit-mode assignment for every enabled VTX, while the generic
handler requires working pit-mode read and write callbacks. FFPV implements writes as a no-op and
SmartAudio 2.x rejects reads, so users of those devices can assign the visible switch without it
controlling pit mode.
Code

src/main/fc/fc_msp_box.c[R327-330]

+#ifdef USE_VTX_CONTROL
+    if (feature(FEATURE_VTX)) {
+        ADD_ACTIVE_BOX(BOXVTXPITMODE);
+    }
Evidence
The new registration checks only the general VTX feature, and the generic handler refuses to issue a
write unless the device first provides a pit-mode reading. FFPV reports pit mode off but discards
every write, while SmartAudio 2.x returns false from its reader even though newer versions have
partial write support, proving that the advertised mode is non-functional for these devices.

src/main/fc/fc_msp_box.c[327-330]
src/main/io/vtx.c[134-147]
src/main/io/vtx_ffpv24g.c[392-430]
src/main/io/vtx_smartaudio.c[840-918]

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

## Issue description
The pit-mode box is exposed whenever the general VTX feature is enabled, even though some selected VTX implementations cannot read or set pit mode. This presents users with an assignable AUX mode that has no effect on those devices.
## Fix Focus Areas
- src/main/fc/fc_msp_box.c[327-330]
- src/main/io/vtx.c[134-147]
- src/main/io/vtx_ffpv24g.c[392-430]
- src/main/io/vtx_smartaudio.c[840-918]
## Recommended Fix
Add an explicit pit-mode capability to VTX devices and use it both when advertising the box and before processing the AUX assignment. Mark only implementations with functional read and write behavior as supporting the mode.

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


Grey Divider

Tip of the day
💡 Did you know, you can keep summaries lean with Finding overflow, which tucks the rest behind 'View more'

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/main/fc/fc_msp_box.c Outdated
Comment thread src/main/io/vtx_tramp.c
Comment thread src/main/io/vtx_tramp.c Outdated
@Raffi1202
Raffi1202 force-pushed the fix/tramp-custom-power-pitmode branch from 1496112 to ec6ea3b Compare September 11, 2026 04:59
@Raffi1202
Raffi1202 changed the base branch from master to maintenance-10.x September 11, 2026 04:59
…g the VTX

Three defects in this pull request, found while going back over it.

**The documented mode ID was wrong.** docs/VTx.md told the reader to assign
permanent mode ID 69 and gave "aux 2 69 4 1800 2100" as the example. 69 is
BOXAUTOSPEED; VTX PIT MODE is 72 (fc_msp_box.c:120). Following the instructions
armed an autonomous speed mode on the switch instead of pit mode. Corrected in
both places.

**The switch never appeared.** initActiveBoxIds() gated BOXVTXPITMODE on
feature(FEATURE_VTX). That flag has exactly two references in the whole tree:
its own enum entry and that check. No target lists it in DEFAULT_FEATURES and
COMMON_DEFAULT_FEATURES is (FEATURE_TX_PROF_SEL) alone, so on a stock
configuration the mode was offered on no board at all. The gate is dropped;
BOXVTXPITMODE is now added under USE_VTX_CONTROL the same way the camera boxes
below it are, since VTX control is enabled by assigning a serial port function
rather than by that flag.

**Pit mode could starve channel and power indefinitely.** The IDLE dispatcher
is a strict priority chain and pit mode was first in it, while every status
response unconditionally restored the request whenever the reported state did
not match. A Tramp-compatible device that will not enter pit mode - common,
many only allow pit at power-up - therefore kept the highest-priority bit set
forever, and no frequency or power command was ever sent again. Only a link
timeout broke the loop, so a healthy link made it permanent.

Frequency and power are now serviced before pit mode, and the requeue is
bounded by VTX_PITMODE_MAX_RETRIES. The budget is refilled in impl_SetPitMode()
only when the requested state actually changes, because io/vtx.c re-issues the
same request roughly twice a second and would otherwise defeat the cap.
@Raffi1202

Copy link
Copy Markdown
Contributor Author

Went back over this one against the sources and found three defects, two of them raised by the bot review above and one it did not mention. All three are fixed in d79d4d46.

The documented mode ID was wrong, and dangerously so

docs/VTx.md told the reader to assign permanent mode ID 69 and gave aux 2 69 4 1800 2100 as the example. The code assigns VTX PIT MODE 72 (fc_msp_box.c:120). ID 69 is BOXAUTOSPEED (fc_msp_box.c:117).

Following the instructions as written put an autonomous speed mode on that switch instead of pit mode. Corrected in both places.

The switch never appeared on any board

initActiveBoxIds() gated BOXVTXPITMODE on feature(FEATURE_VTX). That flag has exactly two references in the whole tree — its own enum entry in fc/config.h:65 and that check. No target lists it in DEFAULT_FEATURES (0 of ~250 target headers) and COMMON_DEFAULT_FEATURES is (FEATURE_TX_PROF_SEL) alone (target/common.h:71).

So on a stock configuration the new mode was offered nowhere. The gate is dropped and the box is added under USE_VTX_CONTROL, the same way the camera boxes immediately below it are — VTX control is enabled by assigning a serial port function, not by that flag.

Pit mode could starve channel and power for good

This is the bot's first finding, and it is real and introduced here. The VTX_STATE_IDLE dispatcher is a strict priority chain with pit mode first, and every status response restored the request unconditionally whenever the reported state did not match:

if (vtxState.request.pitModeRequested && vtxState.state.pitMode != vtxState.request.pitMode) {
    vtxState.updateReqMask |= VTX_UPDATE_REQ_PITMODE;
}

A Tramp-compatible device that will not enter pit mode — common, many only allow pit at power-up — therefore keeps the highest-priority bit set forever, and no frequency or power command is ever sent again. Only a link timeout clears it, so a healthy link makes the starvation permanent.

Frequency and power are now serviced before pit mode, and the requeue is bounded by VTX_PITMODE_MAX_RETRIES. The budget is refilled in impl_SetPitMode() only when the requested state actually changes, because io/vtx.c re-issues the same request roughly twice a second and would otherwise defeat the cap.

On the bot's other two findings

"A saved power level can be ignored" is real but pre-existing, not introduced here. vtx.c:114-128 submits the persisted vtx_power index with no clamp, and both vtx_common.c:92 and vtx_tramp.c:439 drop an out-of-range index without updating the requested state, so it never converges. A 200 mW Tramp with a saved vtx_power = 4 reproduces it identically on unmodified maintenance-10.x, because the automatic tables give powerCount 3 to 5 while VTX_SETTINGS_MAX_POWER is 8. What this PR adds is a new way in, since a valid index can fall outside a smaller custom table. It wants its own fix — a clamp at the caller in vtx.c — rather than being folded in here.

"Some transmitters ignore the pit switch" is also pre-existing driver behaviour: vtx_ffpv24g.c:392 implements impl_SetPitMode as UNUSED(onoff), and vtx_smartaudio.c:911 reads pit mode only for SA 1.0 (version < SA_2_0), so SA 2.0 and 2.1 never report it. Neither file is touched by this PR.

Not built

There is no ARM toolchain on the machine this was written on, and INAV does not build natively on Windows — cmake/settings.cmake:44 passes CFLAGS="${cflags}" through cmake -E env, and the quoting does not survive the batch wrapper, so -DUSE_USB_MSC arrives as a separate argument. Reproduced with CMake 3.25.3 and 3.31.6. These changes are therefore unbuilt and want CI before they are taken seriously.

@sensei-hacker sensei-hacker added this to the 10.1 milestone Sep 20, 2026
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

RAM / Flash usage vs. base commit 86a0441 — commit 4865f05

Target Flash Δ RAM Δ
MATEKF405 +3392 B (+0.47%) CCM: ±0 B (±0.00%)
RAM: +168 B (+0.15%)
MATEKF722 +2496 B (+0.52%) ITCM_RAM: ±0 B (±0.00%)
RAM: +108 B (+0.12%)
TCM: +4 B (+0.02%)
MATEKF765 +1416 B (+0.19%) DTCM_RAM: ±0 B (±0.00%)
SRAM1: +120 B (+0.09%)
MATEKH743 +1032 B (+0.13%) D2_RAM: ±0 B (±0.00%)
DTCM_RAM: ±0 B (±0.00%)
ITCM_RAM: -8 B (-0.05%)
RAM: +64 B (+0.04%)

See RAM/flash optimization guide for techniques to reduce usage.

@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Test firmware build ready — commit 4865f05

Download firmware for PR #11887

250 targets built. Find your board's .hex file by name on that page (e.g. MATEKF405SE.hex). Files are individually downloadable — no GitHub login required.

Development build for testing only. Use Full Chip Erase when flashing.

@Raffi1202

Copy link
Copy Markdown
Contributor Author

/agentic_review

Comment thread src/main/io/vtx_tramp.c Outdated
Comment thread src/main/fc/rc_modes.h
Comment thread src/main/io/vtx.c
Comment thread src/main/io/vtx_tramp.c Outdated
Comment thread src/main/io/vtx_tramp.c
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit f01c881

@Raffi1202

Copy link
Copy Markdown
Contributor Author

/agentic_review

Comment thread src/main/io/vtx_tramp.c
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 2671dd7

@Raffi1202

Raffi1202 commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor Author

/agentic_review

Comment thread src/main/io/vtx_tramp.c Outdated
Comment thread src/main/io/vtx.c
Comment thread src/main/io/vtx_tramp.c
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 43e8b72

@Raffi1202

Copy link
Copy Markdown
Contributor Author

/agentic_review

Comment thread src/main/io/vtx_tramp.c
Comment thread src/main/blackbox/blackbox.c
Comment thread src/main/fc/rc_modes.h
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 5c51bee

MSP_BOXIDS and the activity bitmask follow the order of the active box
list. A configurator that does not know VTX PIT MODE drops it from its
mode list and maps the remaining activity bits by position, so a box in
the middle shifted the active indicator of every later mode by one
(CAMERA 1-3, USER1-4, TURTLE, MIXER PROFILE, ...). Appending it last, as
IN FLIGHT MENU and THRUST REVERSE were, keeps older configurators right.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants