Skip to content

Feature: Add MSP AOA sensor support for relaxed static stability aircraft control#11484

Open
Miki4751 wants to merge 5 commits intoiNavFlight:masterfrom
Miki4751:clean/aoa-sensor
Open

Feature: Add MSP AOA sensor support for relaxed static stability aircraft control#11484
Miki4751 wants to merge 5 commits intoiNavFlight:masterfrom
Miki4751:clean/aoa-sensor

Conversation

@Miki4751
Copy link
Copy Markdown

@Miki4751 Miki4751 commented Apr 7, 2026

Summary

This PR adds angle-of-attack (AOA) sensor functionality via the MSP protocol, specifically designed for control of relaxed static stability (RSS) aircraft.

Key Features

For Canard-configured Aircraft

  • Calculates and applies control deflections to the canards based on real-time AOA data
  • Enables stable flight with relaxed static stability

For Conventional-configured Aircraft

  • Enforces AOA limits within safe operating ranges
  • When measured AOA exceeds predefined threshold, a nose-down control input is automatically superimposed to prevent stall
  • Gradual intervention with fw_aoa_intervention_threshold parameter (0-100%, default 80%)

Additional Features

  • AOA sensor driver framework with MSP and virtual sensor support
  • Sideslip angle field in MSP sensor message
  • AOA sensor status display in CLI diagnostics
  • OSD elements for AOA display
  • Logic condition support for AOA values

Configuration

All settings can be configured via CLI:

  • aoa_hardware - AOA sensor type (NONE/MSP/FAKE)

  • aoa_offset - Calibration offset in degrees

  • aoa_max_angle / aoa_min_angle - AOA range limits

  • fw_aoa_control_channel - Enable channel (-1 = always enabled)

  • fw_aoa_upper_limit_angle - Maximum positive AOA limit

  • fw_aoa_lower_limit_angle - Minimum negative AOA limit

  • fw_aoa_aircraft_type - NORMAL or CANARD layout

  • fw_aoa_kp - P gain percentage

  • fw_aoa_intervention_threshold - Intervention threshold percentage

Validation

This feature has been extensively validated over a 5-month period using a SpeedyBee F405 flight controller installed in a Freewing J-10 aircraft, with all performance targets fully met.

Future Work

Further optimization is planned to implement optimal AOA control for cruise flight, improving overall efficiency and performance.

微信图片_20260406035321_111_100 ![微信图片_20260406035749_115_100](https://github.com/user-attachments/assets/7925e676-ccc5-4287-abd3-c762b1cb511c) ![微信图片_20260406035748_114_100](https://github.com/user-attachments/assets/4f9ac06e-c03c-47e3-9072-37eff6f43384) ![微信图片_20260406035747_113_100](https://github.com/user-attachments/assets/6f7b3959-4018-4d24-a3b8-b71b41ad3c03) 微信图片_20260406035623_112_100

…rcraft control

- Add AOA sensor driver framework with MSP and virtual sensor support
- Add sideslip angle field to MSP sensor message
- Add NORMAL/CANARD aircraft layout support with gradual intervention
- Add AOA sensor status display in CLI diagnostics
- Add OSD elements for AOA display
- Add logic condition support for AOA values
- Add fw_aoa_intervention_threshold parameter (0-100%, default 80%)
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 7, 2026

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.

@qodo-code-review
Copy link
Copy Markdown
Contributor

Review Summary by Qodo

Add MSP AOA sensor support for relaxed static stability aircraft control

Grey Divider

Walkthroughs

Description
• Add AOA sensor driver framework with MSP and virtual sensor support
• Implement relaxed static stability aircraft control for NORMAL and CANARD layouts
• Add gradual AOA intervention with configurable threshold for conventional aircraft
• Integrate AOA data into OSD, logic conditions, and CLI diagnostics
• Add MSP protocol support for AOA sensor data reception
Diagram
flowchart LR
  MSP["MSP Protocol"] -->|"AOA Data"| AOAMSP["AOA MSP Driver"]
  AOAMSP -->|"Virtual Interface"| AOASENSOR["AOA Sensor Core"]
  AOASENSOR -->|"Raw AOA/Sideslip"| AOAPROCESS["AOA Processing"]
  AOAPROCESS -->|"Filtered Data"| CONTROL["AOA Control Logic"]
  CONTROL -->|"NORMAL Layout"| GRADUAL["Gradual Intervention"]
  CONTROL -->|"CANARD Layout"| DIRECT["Direct Servo Control"]
  GRADUAL -->|"PID Output"| SERVOS["Servo Mixer"]
  DIRECT -->|"PID Output"| SERVOS
  AOAPROCESS -->|"Display Data"| OSD["OSD/CLI/Logic Conditions"]
Loading

Grey Divider

File Changes

1. src/main/drivers/aoa/aoa.h ✨ Enhancement +51/-0

AOA sensor driver interface definition

src/main/drivers/aoa/aoa.h


2. src/main/drivers/aoa/aoa_virtual.c ✨ Enhancement +71/-0

Virtual AOA sensor abstraction layer

src/main/drivers/aoa/aoa_virtual.c


3. src/main/drivers/aoa/aoa_virtual.h ✨ Enhancement +38/-0

Virtual AOA sensor interface header

src/main/drivers/aoa/aoa_virtual.h


View more (27)
4. src/main/sensors/aoa.c ✨ Enhancement +253/-0

AOA sensor core implementation and control logic

src/main/sensors/aoa.c


5. src/main/sensors/aoa.h ✨ Enhancement +85/-0

AOA sensor configuration and control structures

src/main/sensors/aoa.h


6. src/main/io/aoa.h ✨ Enhancement +34/-0

AOA MSP interface header

src/main/io/aoa.h


7. src/main/io/aoa_msp.c ✨ Enhancement +80/-0

MSP AOA sensor data reception implementation

src/main/io/aoa_msp.c


8. src/main/fc/fc_msp.c ✨ Enhancement +18/-0

MSP protocol integration for AOA hardware configuration

src/main/fc/fc_msp.c


9. src/main/fc/fc_tasks.c ✨ Enhancement +28/-1

AOA sensor task scheduling and initialization

src/main/fc/fc_tasks.c


10. src/main/flight/pid.c ✨ Enhancement +7/-0

AOA control integration into pitch PID controller

src/main/flight/pid.c


11. src/main/flight/servos.c ✨ Enhancement +9/-0

AOA PID output integration into servo mixer

src/main/flight/servos.c


12. src/main/io/osd.c ✨ Enhancement +22/-0

OSD element for AOA display with directional symbols

src/main/io/osd.c


13. src/main/io/osd.h ✨ Enhancement +2/-0

OSD AOA element definition and failure message

src/main/io/osd.h


14. src/main/drivers/osd_symbols.h ✨ Enhancement +6/-0

OSD symbols for AOA up/down/neutral indicators

src/main/drivers/osd_symbols.h


15. src/main/programming/logic_condition.c ✨ Enhancement +14/-1

Logic condition support for AOA sensor values

src/main/programming/logic_condition.c


16. src/main/programming/logic_condition.h ✨ Enhancement +1/-0

Logic condition operand for AOA angle

src/main/programming/logic_condition.h


17. src/main/sensors/diagnostics.c ✨ Enhancement +27/-2

AOA sensor health status diagnostics

src/main/sensors/diagnostics.c


18. src/main/sensors/diagnostics.h ✨ Enhancement +1/-0

AOA sensor status function declaration

src/main/sensors/diagnostics.h


19. src/main/sensors/initialisation.c ✨ Enhancement +7/-2

AOA sensor initialization in sensor detection

src/main/sensors/initialisation.c


20. src/main/sensors/sensors.h ✨ Enhancement +4/-2

AOA sensor index and flag definitions

src/main/sensors/sensors.h


21. src/main/fc/cli.c ✨ Enhancement +14/-5

CLI debug mode and sensor status display for AOA

src/main/fc/cli.c


22. src/main/build/debug.h ✨ Enhancement +1/-0

Debug mode enumeration for AOA debugging

src/main/build/debug.h


23. src/main/config/parameter_group_ids.h ⚙️ Configuration changes +3/-1

Parameter group IDs for AOA configuration

src/main/config/parameter_group_ids.h


24. src/main/msp/msp_protocol_v2_sensor.h ✨ Enhancement +2/-1

MSP protocol definition for AOA sensor message

src/main/msp/msp_protocol_v2_sensor.h


25. src/main/msp/msp_protocol_v2_sensor_msg.h ✨ Enhancement +5/-0

MSP AOA data message structure definition

src/main/msp/msp_protocol_v2_sensor_msg.h


26. src/main/scheduler/scheduler.h ✨ Enhancement +3/-0

AOA sensor task scheduling enumeration

src/main/scheduler/scheduler.h


27. src/main/target/common.h ⚙️ Configuration changes +4/-0

Default AOA sensor feature compilation flags

src/main/target/common.h


28. src/main/target/common_post.h ⚙️ Configuration changes +5/-0

AOA MSP feature conditional compilation

src/main/target/common_post.h


29. src/main/fc/settings.yaml ⚙️ Configuration changes +4607/-4509

AOA configuration parameters and CLI settings

src/main/fc/settings.yaml


30. src/main/CMakeLists.txt ⚙️ Configuration changes +9/-0

AOA driver and sensor source files build configuration

src/main/CMakeLists.txt


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown
Contributor

qodo-code-review Bot commented Apr 7, 2026

Code Review by Qodo

🐞 Bugs (3)   📘 Rule violations (2)   📎 Requirement gaps (0)   🎨 UX Issues (0)
🐞\ ≡ Correctness (1) ⚙ Maintainability (1) ◔ Observability (1)
📘\ ☼ Reliability (1) ⛨ Security (1)

Grey Divider


Action required

1. mspAoaReceiveNewData() lacks size validation 📘
Description
The new MSP AOA sensor handler casts and reads fields from an incoming buffer without validating the
payload length, which can cause out-of-bounds reads/crashes on malformed or truncated packets. This
violates the requirement to validate external protocol/payload sizes before use.
Code

src/main/io/aoa_msp.c[R72-77]

+void mspAoaReceiveNewData(uint8_t * bufferPtr)
+{
+    const mspSensorAoaDataMessage_t * pkt = (const mspSensorAoaDataMessage_t *)bufferPtr;
+    sensorAoa = pkt->aoa;
+    sensorSideslip = pkt->sideslip;
+    hasNewData = true;
Evidence
PR Compliance ID 1 requires validating incoming protocol/payload buffer sizes before reading fields.
The code paths added for MSP2_SENSOR_AOA pass a raw pointer into mspAoaReceiveNewData() without
checking dataSize, and mspAoaReceiveNewData() blindly casts bufferPtr to
mspSensorAoaDataMessage_t and reads aoa/sideslip.

src/main/fc/fc_msp.c[4440-4491]
src/main/io/aoa_msp.c[72-78]
Best Practice: Learned patterns

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 MSP AOA sensor message handler reads `aoa` and `sideslip` from an incoming buffer without validating the payload length, risking out-of-bounds reads on malformed/truncated packets.

## Issue Context
`mspProcessSensorCommand()` computes `dataSize` but the `MSP2_SENSOR_AOA` path does not validate it or pass it into `mspAoaReceiveNewData()`. `mspAoaReceiveNewData()` then casts `bufferPtr` to `mspSensorAoaDataMessage_t` and dereferences fields unconditionally.

## Fix Focus Areas
- src/main/fc/fc_msp.c[4440-4491]
- src/main/io/aoa.h[32-34]
- src/main/io/aoa_msp.c[72-78]

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


2. aoaProcess() mishandles AOA_NO_NEW_DATA 📘
Description
aoaProcess() constrains and publishes rawAoa even when it is the no-data sentinel
(AOA_NO_NEW_DATA), and it updates lastValidResponseTimeMs regardless, making stale/invalid data
appear fresh and healthy. This violates the requirement to use consistent sentinel values and avoid
stale partial data on failed/invalid reads.
Code

src/main/sensors/aoa.c[R146-155]

+bool aoaProcess(void)
+{
+    if (aoaSensor.dev.read) {
+        int16_t rawAoa, rawSideslip;
+        aoaSensor.dev.read(&aoaSensor.dev, &rawAoa, &rawSideslip);
+        int16_t aoaWithOffset = rawAoa + DEGREES_TO_DECIDEGREES(aoaConfig()->aoa_offset);
+        aoaSensor.aoa = constrain(aoaWithOffset, DEGREES_TO_DECIDEGREES(aoaConfig()->aoa_min_angle), DEGREES_TO_DECIDEGREES(aoaConfig()->aoa_max_angle));
+        aoaSensor.sideslip = rawSideslip;
+        aoaSensor.lastValidResponseTimeMs = millis();
+        DEBUG_SET(DEBUG_AOA, 0, aoaSensor.aoa);
Evidence
PR Compliance ID 4 requires consistent sentinel values for invalid/no-data states and full-state
resets to avoid stale data. AOA_NO_NEW_DATA is defined as INT16_MAX, but the MSP driver
initializes to this sentinel and aoaProcess() still treats it as a real value (constraining it
into the configured range) and refreshes lastValidResponseTimeMs, which keeps aoaIsHealthy()
reporting healthy even when no new data arrives.

src/main/drivers/aoa/aoa.h[33-37]
src/main/io/aoa_msp.c[40-53]
src/main/sensors/aoa.c[146-155]
src/main/sensors/aoa.c[170-173]
Best Practice: Learned patterns

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

## Issue description
`AOA_NO_NEW_DATA` is not handled as a sentinel: it gets constrained into a valid AOA range and the sensor health timestamp is refreshed, which can mask missing/stale data.

## Issue Context
The MSP AOA driver initializes `sensorAoa`/`sensorSideslip` to `AOA_NO_NEW_DATA`. `aoaProcess()` reads these values, applies offset/constrain, and updates `lastValidResponseTimeMs` unconditionally.

## Fix Focus Areas
- src/main/drivers/aoa/aoa.h[33-37]
- src/main/io/aoa_msp.c[40-78]
- src/main/sensors/aoa.c[146-173]

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


3. Void return expression 🐞
Description
virtualAoaInit/virtualAoaUpdate are declared void but use `return
highLevelDeviceVTable->...()` which is not valid ISO C and can fail compilation (or at least emit
warnings) on stricter toolchains.
Code

src/main/drivers/aoa/aoa_virtual.c[R39-49]

+static void virtualAoaInit(aoaDev_t * dev)
+{
+    UNUSED(dev);
+    return highLevelDeviceVTable->init();
+}
+
+static void virtualAoaUpdate(aoaDev_t * dev)
+{
+    UNUSED(dev);
+    return highLevelDeviceVTable->update();
+}
Evidence
The functions are declared static void ... yet include a return statement with an expression.
This violates the C language rules for void return types and is treated as an error by some
compilers/configurations.

src/main/drivers/aoa/aoa_virtual.c[39-49]
cmake/main.cmake[15-31]

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

### Issue description
`virtualAoaInit()` and `virtualAoaUpdate()` are `void` functions but currently use `return highLevelDeviceVTable->init();` / `return highLevelDeviceVTable->update();`, which is not valid ISO C and may break builds on stricter embedded toolchains.

### Issue Context
These functions are part of the newly added AOA virtual device adapter and are called through the AOA device vtable.

### Fix Focus Areas
- src/main/drivers/aoa/aoa_virtual.c[39-49]

### Expected fix
Change to simple calls:
- `highLevelDeviceVTable->init(); return;` (or just call without `return`)
- `highLevelDeviceVTable->update(); return;`

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



Remediation recommended

4. CLI sensor mask mismatch 🐞
Description
cliStatus() assumes the enabled sensor bit for a sensor index is (1 << i), but AOA is
SENSOR_INDEX_AOA == 7 while SENSOR_AOA is 1<<10, so AOA will never be printed in the CLI
sensor hardware list even when enabled.
Code

src/main/sensors/sensors.h[R25-31]

    SENSOR_INDEX_RANGEFINDER,
    SENSOR_INDEX_PITOT,
    SENSOR_INDEX_OPFLOW,
+    SENSOR_INDEX_AOA,
    SENSOR_INDEX_COUNT
} sensorIndex_e;
Evidence
The CLI iterates i < SENSOR_INDEX_COUNT and uses mask = (1 << i) to test sensorsMask(). With
AOA added as a new sensor index, this logic would only work if the corresponding sensor bit were
1<<7. Instead, AOA is defined as 1<<10, so the (detectedSensorsMask & mask) / `(mask &
SENSOR_NAMES_MASK)` checks cannot match AOA and it won't be displayed.

src/main/fc/cli.c[4109-4121]
src/main/fc/runtime_config.c[105-123]
src/main/sensors/sensors.h[20-62]

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

### Issue description
`cliStatus()` uses `mask = (1 << i)` for `i` in `[0..SENSOR_INDEX_COUNT)`, which implicitly assumes the enabled-sensor bit positions match `sensorIndex_e`. With AOA added as `SENSOR_INDEX_AOA` but defined as `SENSOR_AOA = 1<<10`, AOA won't appear in the CLI sensor hardware list.

### Issue Context
This affects observability/debuggability (CLI output), not flight behavior directly.

### Fix Focus Areas
- src/main/fc/cli.c[4109-4121]
- src/main/sensors/sensors.h[20-62]

### Expected fix
Either:
1) Change `cliStatus()` to use an explicit mapping from `sensorIndex_e` to the corresponding `SENSOR_*` bit (recommended minimal change), e.g. an array like `{SENSOR_GYRO, SENSOR_ACC, ..., SENSOR_AOA}`.

OR
2) Rework `sensors_e` bit assignments so that indices up through AOA align with `(1<<index)` (more invasive; requires checking other code that depends on existing bit positions like GPS).

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


5. Unused AOA task parameter 🐞
Description
taskUpdateAoa(timeUs_t currentTimeUs) never uses currentTimeUs and does not mark it UNUSED,
which triggers -Wunused-parameter under the project’s -Wextra build flags.
Code

src/main/fc/fc_tasks.c[R273-284]

+void taskUpdateAoa(timeUs_t currentTimeUs)
+{
+    if (!sensors(SENSOR_AOA))
+        return;
+
+    const uint32_t newDeadline = aoaUpdate();
+    if (newDeadline != 0) {
+        rescheduleTask(TASK_SELF, newDeadline);
+    }
+
+    aoaProcess();
+}
Evidence
The parameter is unused, while adjacent tasks in the same file use UNUSED(currentTimeUs) when they
don't need it, indicating the codebase expects warnings to be suppressed explicitly.

src/main/fc/fc_tasks.c[272-285]
src/main/fc/fc_tasks.c[253-259]
cmake/main.cmake[23-31]

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

### Issue description
`taskUpdateAoa()` doesn't use its `currentTimeUs` argument, which causes `-Wunused-parameter` warnings with `-Wextra`.

### Issue Context
Other tasks in the same file use the `UNUSED(...)` macro for this exact pattern.

### Fix Focus Areas
- src/main/fc/fc_tasks.c[272-285]

### Expected fix
Add `UNUSED(currentTimeUs);` at the start of `taskUpdateAoa()` (or otherwise use the parameter).

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


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@Miki4751
Copy link
Copy Markdown
Author

Miki4751 commented Apr 7, 2026

微信图片_20260406035749_115_100
微信图片_20260406035748_114_100
微信图片_20260406035747_113_100

Comment thread src/main/io/aoa_msp.c
Comment on lines +72 to +77
void mspAoaReceiveNewData(uint8_t * bufferPtr)
{
const mspSensorAoaDataMessage_t * pkt = (const mspSensorAoaDataMessage_t *)bufferPtr;
sensorAoa = pkt->aoa;
sensorSideslip = pkt->sideslip;
hasNewData = true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. mspaoareceivenewdata() lacks size validation 📘 Rule violation ⛨ Security

The new MSP AOA sensor handler casts and reads fields from an incoming buffer without validating the
payload length, which can cause out-of-bounds reads/crashes on malformed or truncated packets. This
violates the requirement to validate external protocol/payload sizes before use.
Agent Prompt
## Issue description
The MSP AOA sensor message handler reads `aoa` and `sideslip` from an incoming buffer without validating the payload length, risking out-of-bounds reads on malformed/truncated packets.

## Issue Context
`mspProcessSensorCommand()` computes `dataSize` but the `MSP2_SENSOR_AOA` path does not validate it or pass it into `mspAoaReceiveNewData()`. `mspAoaReceiveNewData()` then casts `bufferPtr` to `mspSensorAoaDataMessage_t` and dereferences fields unconditionally.

## Fix Focus Areas
- src/main/fc/fc_msp.c[4440-4491]
- src/main/io/aoa.h[32-34]
- src/main/io/aoa_msp.c[72-78]

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

Comment thread src/main/sensors/aoa.c
Comment on lines +146 to +155
bool aoaProcess(void)
{
if (aoaSensor.dev.read) {
int16_t rawAoa, rawSideslip;
aoaSensor.dev.read(&aoaSensor.dev, &rawAoa, &rawSideslip);
int16_t aoaWithOffset = rawAoa + DEGREES_TO_DECIDEGREES(aoaConfig()->aoa_offset);
aoaSensor.aoa = constrain(aoaWithOffset, DEGREES_TO_DECIDEGREES(aoaConfig()->aoa_min_angle), DEGREES_TO_DECIDEGREES(aoaConfig()->aoa_max_angle));
aoaSensor.sideslip = rawSideslip;
aoaSensor.lastValidResponseTimeMs = millis();
DEBUG_SET(DEBUG_AOA, 0, aoaSensor.aoa);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

2. aoaprocess() mishandles aoa_no_new_data 📘 Rule violation ☼ Reliability

aoaProcess() constrains and publishes rawAoa even when it is the no-data sentinel
(AOA_NO_NEW_DATA), and it updates lastValidResponseTimeMs regardless, making stale/invalid data
appear fresh and healthy. This violates the requirement to use consistent sentinel values and avoid
stale partial data on failed/invalid reads.
Agent Prompt
## Issue description
`AOA_NO_NEW_DATA` is not handled as a sentinel: it gets constrained into a valid AOA range and the sensor health timestamp is refreshed, which can mask missing/stale data.

## Issue Context
The MSP AOA driver initializes `sensorAoa`/`sensorSideslip` to `AOA_NO_NEW_DATA`. `aoaProcess()` reads these values, applies offset/constrain, and updates `lastValidResponseTimeMs` unconditionally.

## Fix Focus Areas
- src/main/drivers/aoa/aoa.h[33-37]
- src/main/io/aoa_msp.c[40-78]
- src/main/sensors/aoa.c[146-173]

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

Comment on lines +39 to +49
static void virtualAoaInit(aoaDev_t * dev)
{
UNUSED(dev);
return highLevelDeviceVTable->init();
}

static void virtualAoaUpdate(aoaDev_t * dev)
{
UNUSED(dev);
return highLevelDeviceVTable->update();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

3. Void return expression 🐞 Bug ≡ Correctness

virtualAoaInit/virtualAoaUpdate are declared void but use `return
highLevelDeviceVTable->...()` which is not valid ISO C and can fail compilation (or at least emit
warnings) on stricter toolchains.
Agent Prompt
### Issue description
`virtualAoaInit()` and `virtualAoaUpdate()` are `void` functions but currently use `return highLevelDeviceVTable->init();` / `return highLevelDeviceVTable->update();`, which is not valid ISO C and may break builds on stricter embedded toolchains.

### Issue Context
These functions are part of the newly added AOA virtual device adapter and are called through the AOA device vtable.

### Fix Focus Areas
- src/main/drivers/aoa/aoa_virtual.c[39-49]

### Expected fix
Change to simple calls:
- `highLevelDeviceVTable->init(); return;` (or just call without `return`)
- `highLevelDeviceVTable->update(); return;`

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

Copy link
Copy Markdown
Author

@Miki4751 Miki4751 left a comment

Choose a reason for hiding this comment

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

avoid no output

@sensei-hacker
Copy link
Copy Markdown
Member

sensei-hacker commented Apr 25, 2026

Thanks for this! It looks like you put a lot of work into it. This was something I had though about doing and I designed a couple of miniature Hall-effect AoA sensors but I never did anything with them.

There may some potential areas of improvement possible. There are some things to look at from a coding perspective, and from an aerodynamics and safety perspective. Also, as-is, the code doesn't compile. I'll make two separate comments for code vs aerodynamics and feature design of the MCAS.

Before really digging into the code myself, here are some things my agent thought might be worth looking at. Sometimes the agent is wrong and sometimes we make different design decisions than the agent recommends, but these are things that might be worth CHECKING to see if the agent is pointing out a real issue or not:


CRITICAL — Block merge

  1. Startup pitch control runaway (aoa.c)                                                                                             
  
  AOA_NO_NEW_DATA is INT16_MAX (32767). On boot, before any MSP data arrives, aoaProcess() calls dev.read(), gets INT16_MAX, clamps it 
  to aoa_max_angle (default 36°), stamps lastValidResponseTimeMs (making aoaIsHealthy() return true), and aoaControlUpdate() applies
  maximum-deflection pitch correction — up to ~352 PWM units. This lasts until the companion computer starts sending, potentially for  
  the entire flight if it never connects.

  ─────────────────────────────────────                                                                                      
  The AOA_NO_NEW_DATA sentinel pattern is correct in concept — using INT16_MAX as "no data" — but requires every consumer of the value
  to explicitly check for it. When a sentinel leaks into numeric processing (constrain, arithmetic), it causes the worst possible      
  outcome rather than a safe no-op. The fix is gating: check health before computing control output, and never stamp
  lastValidResponseTimeMs on a sentinel read.                                                                                          
  ─────────────────────────────────────────────────
                                                                                                                                       
  2. Noise filter is logically inverted (aoa.c)
                                                                                                                                       
  const int16_t filteredAoa = abs(currentAoa - prev_aoa) > 10 ? currentAoa : prev_aoa;                                                 
  This passes large spikes and suppresses small gradual changes — the exact opposite of a noise filter. A genuine stall approach (slow 
  AOA increase) would be sample-by-sample delayed, while an RF glitch causing a sudden jump would pass straight through to the control 
  output.                                                                                                                              
                                                                                                                                       
  3. Buffer overread in mspAoaReceiveNewData() (aoa_msp.c, fc_msp.c)
                                                                                                                                       
  The function casts a raw buffer to mspSensorAoaDataMessage_t (4 bytes) with no size validation. A short malformed packet causes an   
  out-of-bounds read. Compare to the headtracker which passes dataSize for validation.                                                 
                                                                                                                                       
  4. MSP_SENSOR_CONFIG byte inserted mid-message (fc_msp.c)                                                                            
  
  AOA is inserted between rangefinder and opflow, not appended at the end. Any configurator that doesn't know about this change will   
  silently misread all bytes from position 5 onward — opflow hardware selection will appear as the AOA value. INAV convention for
  wire-protocol additions is to append to the end.                                                                                     
                      
  5. SENSOR_AOA bitmask misalignment (sensors.h, cli.c)                                                                                
  
  SENSOR_INDEX_AOA = 7 but SENSOR_AOA = 1<<10. The file comments say "these bits have to be aligned with sensorIndex_e" — they aren't. 
  AOA will never display in CLI status output even when fully working.
                                                                                                                                       
  ---                 
  IMPORTANT — Should fix
                        
  6. aoaControlUpdate() accepts 5 parameters only used inside DEBUG_SET() — in release builds they're all unused, causing compiler
  warnings. (aoa.c)                                                                                                                    
  
  7. Float-to-int16_t implicit truncation in PID output assignment — no rounding, no explicit cast. (aoa.c)                            
                      
  8. CANARD limit calculation: lowerLimit/upperLimit declared int16_t but computed as int16_t * float, with truncation, losing         
  asymmetric limit capability. (aoa.c)
                                                                                                                                       
  9. Struct field comments say "decidegrees" but fields are actually stored in degrees — unit mismatch will confuse future developers. 
  (aoa.h)
                                                                                                                                       
  10. CRSF frame sends only 2 of 4 payload bytes — sideslip is silently dropped with no comment. Magic-number byte layout unexplained. 
  (crsf.c)
                                                                                                                                       
  11. An existing informative comment about OPFLOW task timing was deleted along with an unannounced staticPriority change for that    
  task. (fc_tasks.c)

Additional concern to check:
In the NORMAL aircraft branch, aoaControlUpdate() computes aoaServoOffset and stores it in
aoaPidOutput — but never writes it back to *pidPitchOutput. The CANARD branch correctly does *pidPitchOutput += interventionOffset, but the NORMAL stall-protection path only sets a local copy.

@sensei-hacker
Copy link
Copy Markdown
Member

sensei-hacker commented Apr 25, 2026

If the aircraft is already pitched 12 degrees nose down, should we force it further nose down?
If it's 6 degrees nose down with 90% throttle?

To avoid the catastrophic failure modes of the Boeing's infamous MCAS, we might do something like this to compare the current pitch and throttle vs AoA.

  #include "flight/imu.h"          // attitude.values.pitch
  #include "fc/rc_controls.h"      // rcCommand[THROTTLE]                                                                                     
  #include "fc/config.h"           // currentBatteryProfile                                                                                   
                                                                                                                                              
  // --- Called from inside aoaControlUpdate(), before computing aoaError: ---                                                                            
                       
  const int16_t pitchDeg = DECIDEGREES_TO_DEGREES(attitude.values.pitch);                                                                     
                       
  // 1. Compute expected throttle for current pitch using the same model                                                                      
  //    as fixedWingPitchToThrottleCorrection():
  //      expected = cruise_throttle + (pitch_deg × pitch_to_throttle)                                                                        
  //    Note: pitch_deg is negative when nose-down, so expected throttle
  //    is lower than cruise when nose-down (dive reduces throttle demand).                                                                   
  const int16_t expectedThrottle = constrain(                                                                                                 
      currentBatteryProfile->nav.fw.cruise_throttle +                                                                                         
          pitchDeg * currentBatteryProfile->nav.fw.pitch_to_throttle,                                                                         
      currentBatteryProfile->nav.fw.min_throttle,                                                                                             
      currentBatteryProfile->nav.fw.max_throttle                                                                                              
  );
                                                                                                                                              
  // 2. Throttle excess = how much above expected we actually are.                                                                            
  //    If nose is -12° with pitch_to_throttle=10, expected throttle = 1400-120=1280.
  //    If actual throttle is 1700, excess = 420 PWM — clearly a powered dive.                                                                
  const int16_t throttleExcess = rcCommand[THROTTLE] - expectedThrottle;                                                                      
                                                                                                                                              
  // 3. Suppress nose-down AoA intervention in a powered dive                                                                                 
  if (pitchDeg < -8 && aoaError > 0) {
      aoaError = 0.0f;   // already nose-down: don't push further                                                                             
  }                                                                                                                                           
                                                                                                                                              
  // 4. Attenuate response if throttle is anomalously high for the current pitch                                                                       
  //    (100 PWM margin = 10 degrees at default pitch_to_throttle=10 — adjustable)
  if (throttleExcess > 100 && pitchDeg < -5 && aoaError > 0) {                                                                                
      aoaError *= 0.25f;                                                                                                                      
  }                                                                                                                                           
                                                                                                                                              
  // 5. Fade out if AoA is already decreasing (recovery in progress)                                                                          
  static int16_t prev_aoaDeg = 0;
  const float aoaRate = (aoaDeg - prev_aoaDeg) * SCHEDULER_TASK_HZ_AOA; // deg/s approx                                                       
  prev_aoaDeg = aoaDeg;                                                                                                                       
  if (aoaRate < -2.0f && aoaError > 0) {
      aoaError *= MAX(0.0f, 1.0f + aoaRate / 10.0f);                                                                                          
  }                    

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