.pr_agent_auto_best_practices - iNavFlight/inav GitHub Wiki

Pattern 1: Ensure payload and buffer sizes are validated before reading or writing to avoid out-of-bounds access in protocol handlers and I/O code.

Example code before:

switch (cmd) {
  case CMD_X:
    uint8_t id = sbufReadU8(src);
    uint16_t val = sbufReadU16(src);
    handler[id] = val;
    break;
}

Example code after:

switch (cmd) {
  case CMD_X:
    if (dataSize < 3) return MSP_RESULT_ERROR;
    uint8_t id;
    if (!sbufReadU8Safe(&id, src)) return MSP_RESULT_ERROR;
    uint16_t val = sbufReadU16(src);
    if (id >= HANDLER_MAX) return MSP_RESULT_ERROR;
    handler[id] = val;
    break;
}
Relevant past accepted suggestions:
Suggestion 1:

Add payload size validation check

Add a payload size check in the MSP_OSD_CUSTOM_POSITION handler to ensure the incoming data is at least 3 bytes before reading from the buffer.

src/main/fc/fc_msp.c [2718-2731]

 case MSP_OSD_CUSTOM_POSITION: {
+    if (dataSize < 3) {
+        return MSP_RESULT_ERROR;
+    }
     uint8_t item;
     sbufReadU8Safe(&item, src);
     if (item < OSD_ITEM_COUNT){ // item == addr
         osdEraseCustomItem(item);
         osdLayoutsConfigMutable()->item_pos[0][item] = sbufReadU16(src) | (1 << 13);
         osdDrawCustomItem(item);
     }
     else{
         return MSP_RESULT_ERROR;
     }
 
     break;
 }

Suggestion 2:

Avoid using a hardcoded layout index

Instead of hardcoding the OSD layout index to 0, consider using the currently active layout or extending the MSP command to allow specifying the layout index.

src/main/fc/fc_msp.c [2723]

-osdLayoutsConfigMutable()->item_pos[0][item] = sbufReadU16(src) | (1 << 13);
+// Suggestion: use the current layout instead of a hardcoded one.
+// Note: `currentLayout` is a global variable defined in `osd.c` and might need to be made accessible here.
+extern uint8_t currentLayout;
+osdLayoutsConfigMutable()->item_pos[currentLayout][item] = sbufReadU16(src) | (1 << 13);

Suggestion 3:

Handle yaw from MAVLink command

Handle the yaw parameter (param4) from the MAV_CMD_DO_REPOSITION command instead of ignoring it, by assigning it to wp.p1 to control the vehicle's heading.

src/main/telemetry/mavlink.c [1150-1153]

-wp.p1 = wp.p2 = wp.p3 = 0;
+if (!isnan(msg.param4) && msg.param4 >= 0.0f && msg.param4 < 360.0f) {
+    wp.p1 = msg.param4;
+} else {
+    wp.p1 = -1; // Use -1 to indicate no heading change
+}
+wp.p2 = wp.p3 = 0;
 wp.flag = 0;
 
 setWaypoint(255, ℘);

Suggestion 4:

Support additional MAVLink coordinate frames

Add support for MAV_FRAME_GLOBAL_RELATIVE_ALT and MAV_FRAME_GLOBAL_TERRAIN_ALT coordinate frames to improve compatibility with various Ground Control Stations.

src/main/telemetry/mavlink.c [1131-1142]

-if (msg.frame != MAV_FRAME_GLOBAL) {
+if (msg.frame != MAV_FRAME_GLOBAL && msg.frame != MAV_FRAME_GLOBAL_RELATIVE_ALT && msg.frame != MAV_FRAME_GLOBAL_TERRAIN_ALT) {
 
         mavlink_msg_command_ack_pack(mavSystemId, mavComponentId, &mavSendMsg,
                                     msg.command,
                                     MAV_RESULT_UNSUPPORTED,
                                     0,  // progress
                                     0,  // result_param2
                                     mavRecvMsg.sysid,
                                     mavRecvMsg.compid);
         mavlinkSendMessage();
         return true;
     }

Pattern 2: Normalize angles and maintain consistent units through calculations, converting once at boundaries to avoid wrap and precision errors.

Example code before:

int deg = CENTIDEGREES_TO_DEGREES(angleCd + 18000);
while (deg < 0) deg += 360;
while (deg >= 360) deg -= 360;
rel = -deg;
if (rel <= -180) rel = 180 + (rel + 180);

Example code after:

int32_t angCd = angleCd + 18000;
while (angCd < 0) angCd += 36000;
while (angCd >= 36000) angCd -= 36000;
int32_t relCd = angCd - yawDeci * 10;
while (relCd <= -18000) relCd += 36000;
while (relCd > 18000) relCd -= 36000;
int relDeg = CENTIDEGREES_TO_DEGREES(relCd);
Relevant past accepted suggestions:
Suggestion 1:

Harmonize operand semantics and docs

Mirror the sign and invalid-return conventions with the new relative wind field in docs and keep operand meanings consistent; update docs and ensure both operands follow the same invalid sentinel and frame definitions.

src/main/programming/logic_condition.c [763-785]

-case LOGIC_CONDITION_OPERAND_FLIGHT_WIND_DIRECTION: // deg
+case LOGIC_CONDITION_OPERAND_FLIGHT_WIND_DIRECTION: // deg, 0..359, -1 invalid
 #ifdef USE_WIND_ESTIMATOR
     {
         if (isEstimatedWindSpeedValid()) {
-            uint16_t windAngle;
-            getEstimatedHorizontalWindSpeed(&windAngle);
-            int32_t windHeading = windAngle + 18000; // Correct heading to display correctly.
-    
-            windHeading = (CENTIDEGREES_TO_DEGREES((int)windHeading));
-            while (windHeading < 0) {
-                windHeading += 360;
-            }
-            while (windHeading >= 360) {
-                windHeading -= 360;
-            }
-            return windHeading;
-        } else
+            uint16_t windAngleCd;
+            getEstimatedHorizontalWindSpeed(&windAngleCd);
+            // Convert wind vector angle (towards) to heading-from (meteorological) in degrees [0,360)
+            int32_t headingDeg = CENTIDEGREES_TO_DEGREES((int)(windAngleCd + 18000));
+            while (headingDeg < 0) headingDeg += 360;
+            while (headingDeg >= 360) headingDeg -= 360;
+            return headingDeg;
+        } else {
             return -1;
+        }
     }
 #else
-        return -1;
+    return -1;
 #endif
-        break;
+    break;

Suggestion 2:

Normalize angles in one unit

Keep all math in centidegrees until final conversion and normalize once; avoid redundant casts and flips that can introduce off-by-one errors.

src/main/programming/logic_condition.c [787-816]

-case LOGIC_CONDITION_OPERAND_FLIGHT_RELATIVE_WIND_OFFSET: // deg
+case LOGIC_CONDITION_OPERAND_FLIGHT_RELATIVE_WIND_OFFSET: // deg, signed [-180,180], 0 = headwind, left negative
 #ifdef USE_WIND_ESTIMATOR
     {
         if (isEstimatedWindSpeedValid()) {
-            uint16_t windAngle;
-            getEstimatedHorizontalWindSpeed(&windAngle);
-            gvSet(3, DECIDEGREES_TO_DEGREES(attitude.values.yaw));
-            int32_t relativeWindHeading = windAngle + 18000 - DECIDEGREES_TO_CENTIDEGREES(attitude.values.yaw);
-    
-            relativeWindHeading = (CENTIDEGREES_TO_DEGREES((int)relativeWindHeading));
-            while (relativeWindHeading < 0) {
-                relativeWindHeading += 360;
-            }
-            while (relativeWindHeading >= 360) {
-                relativeWindHeading -= 360;
-            }
-            
-            relativeWindHeading = -relativeWindHeading;
-            if (relativeWindHeading <= -180) {
-                relativeWindHeading = 180 + (relativeWindHeading + 180);
-            }
-
-            return relativeWindHeading;
-        } else
+            uint16_t windAngleCd;
+            getEstimatedHorizontalWindSpeed(&windAngleCd); // [0,36000)
+            // Convert wind vector angle (towards) to heading-from
+            int32_t windHeadingCd = (int32_t)windAngleCd + 18000; // centidegrees
+            // Normalize to [0,36000)
+            while (windHeadingCd < 0) windHeadingCd += 36000;
+            while (windHeadingCd >= 36000) windHeadingCd -= 36000;
+            // Aircraft yaw is in decidegrees; convert to centidegrees
+            int32_t yawCd = DECIDEGREES_TO_CENTIDEGREES(attitude.values.yaw);
+            // Relative offset: wind heading relative to aircraft nose (right positive)
+            int32_t relCd = windHeadingCd - yawCd; // centidegrees
+            // Normalize to (-18000, 18000]
+            while (relCd <= -18000) relCd += 36000;
+            while (relCd > 18000) relCd -= 36000;
+            // Convert to degrees with sign convention: left negative
+            int32_t relDeg = CENTIDEGREES_TO_DEGREES(relCd);
+            return relDeg;
+        } else {
             return 0;
+        }
     }
 #else
-        return 0;
+    return 0;
 #endif
-        break;
+    break;

Suggestion 3:

Fix incorrect manual yaw rate display

Correct the OSD element for Manual Yaw Rate (OSD_MANUAL_YAW_RATE) to use the manual yaw rate value and adjustment function instead of the stabilized ones.

src/main/io/osd.c [3344-3346]

 case OSD_MANUAL_YAW_RATE:
-    osdDisplayAdjustableDecimalValue(elemPosX, elemPosY, "MYR", 0, currentControlProfile->stabilized.rates[FD_YAW], 3, 0, ADJUSTMENT_YAW_RATE);
+    osdDisplayAdjustableDecimalValue(elemPosX, elemPosY, "MYR", 0, currentControlProfile->manual.rates[FD_YAW], 3, 0, ADJUSTMENT_MANUAL_YAW_RATE);
     return true;

Suggestion 4:

Avoid premature type casting to float

In osdGet3DSpeed, declare vert_speed and hor_speed as float to prevent loss of precision from premature casting before the Pythagorean calculation.

src/main/io/osd_common.c [202-207]

 int16_t osdGet3DSpeed(void)
 {
-    int16_t vert_speed = getEstimatedActualVelocity(Z);
-    int16_t hor_speed = gpsSol.groundSpeed;
+    float vert_speed = getEstimatedActualVelocity(Z);
+    float hor_speed = gpsSol.groundSpeed;
     return (int16_t)calc_length_pythagorean_2D(hor_speed, vert_speed);
 }

Pattern 3: Guard against uninitialized or invalid state by providing defaults and safe fallbacks in control logic and APIs.

Example code before:

switch (item) {
  case ITEM_A: idx = 0; break;
  case ITEM_B: idx = 1; break;
}
use(buffer[idx]);

Example code after:

switch (item) {
  case ITEM_A: idx = 0; break;
  case ITEM_B: idx = 1; break;
  default: return; // unknown item
}
// defaults
static uint8_t sysId = 1;
use(buffer[idx]);
Relevant past accepted suggestions:
Suggestion 1:

Prevent use of uninitialized variable

In osdEraseCustomItem, add a default case to the switch statement that returns from the function. This prevents unintended side effects when an item that is not a custom element is passed.

src/main/io/osd.c [6593-6620]

 switch(item){
     case 147:
         customElementIndex = 0;
         break;
     case 148:
         customElementIndex = 1;
         break;
     case 149:
         customElementIndex = 2;
         break;
     case 154:
         customElementIndex = 3;
         break;
     case 155:
         customElementIndex = 4;
         break;
     case 156:
         customElementIndex = 5;
         break;
     case 157:
         customElementIndex = 6;
         break;
     case 158:
         customElementIndex = 7;
         break;
+    default:
+        return;
 }
 
 uint8_t len = customElementLength(customElementIndex);

Suggestion 2:

Initialize static variable at declaration

Initialize the static variable mavSystemId to a default value of 1 at declaration. This prevents it from being zero-initialized to an invalid system ID, making the code more robust.

src/main/telemetry/mavlink.c [186-187]

 // Set mavSystemId from telemetryConfig()->mavlink.sysid
-static uint8_t mavSystemId;
+static uint8_t mavSystemId = 1;

Suggestion 3:

Handle yaw from MAVLink command

Handle the yaw parameter (param4) from the MAV_CMD_DO_REPOSITION command instead of ignoring it, by assigning it to wp.p1 to control the vehicle's heading.

src/main/telemetry/mavlink.c [1150-1153]

-wp.p1 = wp.p2 = wp.p3 = 0;
+if (!isnan(msg.param4) && msg.param4 >= 0.0f && msg.param4 < 360.0f) {
+    wp.p1 = msg.param4;
+} else {
+    wp.p1 = -1; // Use -1 to indicate no heading change
+}
+wp.p2 = wp.p3 = 0;
 wp.flag = 0;
 
 setWaypoint(255, ℘);

Suggestion 4:

Prevent division by zero during normalization

Add a check to ensure vCoGlocal has a non-zero magnitude before normalization to prevent a potential division-by-zero error.

src/main/flight/imu.c [472-482]

-if (vectorNormSquared(&vHeadingEF) > 0.01f) {
+if (vectorNormSquared(&vHeadingEF) > 0.01f && vectorNormSquared(&vCoGlocal) > 0.01f) {
     // Normalize to unit vector
     vectorNormalize(&vHeadingEF, &vHeadingEF);
     vectorNormalize(&vCoGlocal, &vCoGlocal);
 
     // error is cross product between reference heading and estimated heading (calculated in EF)
     vectorCrossProduct(&vCoGErr, &vCoGlocal, &vHeadingEF);
 
     // Rotate error back into body frame
     quaternionRotateVector(&vCoGErr, &vCoGErr, &orientation);
 }

Pattern 4: Align feature flags, enums, and configuration macros with actual hardware and code paths to avoid mismatches and dead code.

Example code before:

#define SENSORS_SET (SENSOR_ACC|SENSOR_MAG|SENSOR_BARO)
#define MAX_PWM_OUTPUT_PORTS 8

Example code after:

#define SENSORS_SET (SENSOR_ACC|SENSOR_BARO) // no mag on this target
#if defined(TARGET_9_SERVO)
#define MAX_PWM_OUTPUT_PORTS 9
#else
#define MAX_PWM_OUTPUT_PORTS 8
#endif
Relevant past accepted suggestions:
Suggestion 1:

Fix typo in preprocessor directive

Fix the typo NEXUS_9SERVOS to NEXUSX_9SERVOS in the preprocessor directive to ensure correct I2C configuration for that target variant.

src/main/target/NEXUSX/target.h [50-57]

-#if defined(NEXUSX) || defined(NEXUS_9SERVOS)
+#if defined(NEXUSX) || defined(NEXUSX_9SERVOS)
 #define USE_I2C_DEVICE_2 // clashes with UART3
 #define I2C2_SCL                PB10
 #define I2C2_SDA                PB11
 #define DEFAULT_I2C BUS_I2C2
 #else
 #define DEFAULT_I2C BUS_I2C3
 #endif

Suggestion 2:

Correct maximum PWM output ports

Conditionally define MAX_PWM_OUTPUT_PORTS to 9 for the NEXUSX_9SERVOS and NEXUSX_NOI2C variants to match their 9 defined servo outputs.

src/main/target/NEXUSX/target.h [161]

+#if defined(NEXUSX_9SERVOS) || defined(NEXUSX_NOI2C)
+#define MAX_PWM_OUTPUT_PORTS        9
+#else
 #define MAX_PWM_OUTPUT_PORTS        8
+#endif

Suggestion 3:

Remove unsupported magnetometer from sensors

Remove the unsupported SENSOR_MAG from the SENSORS_SET macro, as the target hardware does not include a magnetometer.

src/main/target/NEXUSX/target.h [129]

-#define SENSORS_SET (SENSOR_ACC|SENSOR_MAG|SENSOR_BARO)
+#define SENSORS_SET (SENSOR_ACC|SENSOR_BARO)

Suggestion 4:

Update max enum values

Update CURRENT_SENSOR_MAX and VOLTAGE_SENSOR_MAX in their respective enums to CURRENT_SENSOR_SMARTPORT to correctly reflect the new maximum value.

src/main/sensors/battery_config_structs.h [28-45]

 typedef enum {
     CURRENT_SENSOR_NONE = 0,
     CURRENT_SENSOR_ADC,
     CURRENT_SENSOR_VIRTUAL,
     CURRENT_SENSOR_FAKE,
     CURRENT_SENSOR_ESC,
     CURRENT_SENSOR_SMARTPORT,
-    CURRENT_SENSOR_MAX = CURRENT_SENSOR_FAKE
+    CURRENT_SENSOR_MAX = CURRENT_SENSOR_SMARTPORT
 } currentSensor_e;
 
 typedef enum {
     VOLTAGE_SENSOR_NONE = 0,
     VOLTAGE_SENSOR_ADC,
     VOLTAGE_SENSOR_ESC,
     VOLTAGE_SENSOR_FAKE,
     VOLTAGE_SENSOR_SMARTPORT,
-    VOLTAGE_SENSOR_MAX = VOLTAGE_SENSOR_FAKE
+    VOLTAGE_SENSOR_MAX = VOLTAGE_SENSOR_SMARTPORT
 } voltageSensor_e;

Suggestion 5:
  • [possible issue] Add a definition for the specific magnetometer hardware driver (e.g., `USE_MAG_QMC5883L`) to ensure the device is properly initialized, as simply defining `USE_MAG` is insufficient.

  • Pattern 5: Exclude generated or environment-specific build artifacts from version control and scripts; operate safely on files that actually exist.

    Example code before:

    rm -rf */ 
    for f in *.hex; do mv "$f" "new_$f"; done
    git add CMakeFiles/Makefile.cmake
    

    Example code after:

    # Only remove empty dirs safely
    find . -mindepth 1 -maxdepth 1 -type d -empty -print -delete
    # Rename if files exist
    shopt -s nullglob
    for f in *.hex; do mv "$f" "new_$f"; done
    # .gitignore
    CMakeFiles/
    **/DependInfo.cmake
    **/Makefile.cmake
    
    Relevant past accepted suggestions:
    Suggestion 1:

    Remove generated build file from repository

    Remove the generated CMake build file from the repository. It contains user-specific absolute paths that will cause build failures for other developers and should be added to .gitignore.

    src/src/main/target/AXISFLYINGF7PRO/CMakeFiles/AXISFLYINGF7PRO_for_bl.elf.dir/DependInfo.cmake [1-474]

    -# Consider dependencies only in project.
    -set(CMAKE_DEPENDS_IN_PROJECT_ONLY OFF)
    +# This file should be removed from the repository.
     
    -# The set of languages for which implicit dependencies are needed:
    -set(CMAKE_DEPENDS_LANGUAGES
    -  "ASM"
    -  )
    -# The set of files for implicit dependencies of each language:
    -set(CMAKE_DEPENDS_CHECK_ASM
    -  "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/lib/main/CMSIS/DSP/Source/TransformFunctions/arm_bitreversal2.S" "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/src/src/main/target/AXISFLYINGF7PRO/CMakeFiles/AXISFLYINGF7PRO_for_bl.elf.dir/__/__/__/__/lib/main/CMSIS/DSP/Source/TransformFunctions/arm_bitreversal2.S.obj"
    -  "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/src/main/startup/startup_stm32f722xx.s" "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/src/src/main/target/AXISFLYINGF7PRO/CMakeFiles/AXISFLYINGF7PRO_for_bl.elf.dir/__/__/startup/startup_stm32f722xx.s.obj"
    -  )
    -set(CMAKE_ASM_COMPILER_ID "GNU")
    -
    -# Preprocessor definitions for this target.
    -set(CMAKE_TARGET_DEFINITIONS_ASM
    -...
    -
    -# The include file search paths:
    -set(CMAKE_ASM_TARGET_INCLUDE_PATH
    -  "main/target/AXISFLYINGF7PRO"
    -  "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/lib/main/STM32F7/Drivers/STM32F7xx_HAL_Driver/Inc"
    -  "/Users/ahmed/Desktop/Projects/INAV-RPiOSD/inav/lib/main/STM32F7/Drivers/CMSIS/Device/ST/STM32F7xx/Include"
    -...
    -

    Suggestion 2:

    Remove generated file from version control

    Remove the generated Makefile.cmake file from version control. This file is environment-specific and should be ignored by adding the CMakeFiles directory to .gitignore.

    src/CMakeFiles/Makefile.cmake [1-9]

    -# CMAKE generated file: DO NOT EDIT!
    -# Generated by "Unix Makefiles" Generator, CMake Version 4.1
    +# This file should be removed from the pull request and repository.
     
    -# The generator used is:
    -set(CMAKE_DEPENDS_GENERATOR "Unix Makefiles")
    -
    -# The top level Makefile was generated from the following files:
    -set(CMAKE_MAKEFILE_DEPENDS
    -  "CMakeCache.txt"
    -...
    -

    Suggestion 3:
  • [general] Add a check to the firmware renaming script to ensure `.hex` files exist before attempting to loop through and rename them, preventing potential errors.
    Suggestion 4:
  • [general] Replace the risky `rm -rf */` command with a safer `find` command to delete only the empty subdirectories, preventing accidental deletion of other directories.
    Suggestion 5:
  • [learned best practice] Remove brittle line-number references and add commands to programmatically verify/update versions to prevent drift.

  • [Auto-generated best practices - 2025-12-10]

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