From c123a24b55ca72a0fa3f51ebd70cd3171a233f64 Mon Sep 17 00:00:00 2001 From: breadoven <56191411+breadoven@users.noreply.github.com> Date: Mon, 5 May 2025 00:08:40 +0100 Subject: [PATCH 1/6] toilet bowling detection --- docs/Settings.md | 10 ++++++ src/main/fc/settings.yaml | 6 ++++ src/main/io/osd.c | 3 ++ src/main/io/osd.h | 1 + src/main/navigation/navigation.c | 2 ++ src/main/navigation/navigation.h | 3 +- src/main/navigation/navigation_multicopter.c | 33 ++++++++++++++++++++ src/main/navigation/navigation_private.h | 1 + 8 files changed, 58 insertions(+), 1 deletion(-) diff --git a/docs/Settings.md b/docs/Settings.md index 256dd05a005..c957427a952 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -3992,6 +3992,16 @@ P gain of altitude PID controller (Multirotor) --- +### nav_mc_toiletbowl_detection + +Sets sensitivity of toilet bowling detection for multirotors. On detection a heading correction is applied which should stop the toilet bowling. A setting of 2 works well for 5 inch multirotors. Increasing the setting will reduce sensitivity and delay detection. Set to 0 to disable. (Toilet bowling occurs most obviously during position hold when an inaccurate compass heading causes a rapidly increasing fast sweeping bowl shaped flight path). + +| Default | Min | Max | +| --- | --- | --- | +| 0 | 0 | 20 | + +--- + ### nav_mc_vel_xy_d D gain of Position-Rate (Velocity to Acceleration) PID controller. It can damp P and I. Increasing D might help when drone overshoots target. diff --git a/src/main/fc/settings.yaml b/src/main/fc/settings.yaml index 5fe29261630..e8954c85fc3 100644 --- a/src/main/fc/settings.yaml +++ b/src/main/fc/settings.yaml @@ -2516,6 +2516,12 @@ groups: field: mc.inverted_crash_detection min: 0 max: 15 + - name: nav_mc_toiletbowl_detection + description: "Sets sensitivity of toilet bowling detection for multirotors. On detection a heading correction is applied which should stop the toilet bowling. A setting of 2 works well for 5 inch multirotors. Increasing the setting will reduce sensitivity and delay detection. Set to 0 to disable. (Toilet bowling occurs most obviously during position hold when an inaccurate compass heading causes a rapidly increasing fast sweeping bowl shaped flight path)." + default_value: 0 + field: mc.toiletbowl_detection + min: 0 + max: 20 - name: nav_mc_althold_throttle description: "If set to STICK the FC remembers the throttle stick position when enabling ALTHOLD and treats it as the neutral midpoint for holding altitude. If set to MID_STICK or HOVER the neutral midpoint is set to the mid stick position or the hover throttle position respectively." default_value: "STICK" diff --git a/src/main/io/osd.c b/src/main/io/osd.c index d44ea0a8988..025a238d7a7 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6156,6 +6156,9 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter } } } + if (STATE(MULTIROTOR) && isNavHoldPositionActive() && toiletBowlingHeadingCorrection) { + messages[messageCount++] = OSD_MESSAGE_STR(OSD_MSG_TOILET_BOWL); + } } else if (ARMING_FLAG(ARMING_DISABLED_ALL_FLAGS)) { /* ADDS MAXIMUM OF 2 MESSAGES TO TOTAL */ unsigned invalidIndex; diff --git a/src/main/io/osd.h b/src/main/io/osd.h index b2e94b52729..a08c79d8017 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -122,6 +122,7 @@ #define OSD_MSG_ANGLEHOLD_PITCH "(ANGLEHOLD PITCH)" #define OSD_MSG_ANGLEHOLD_LEVEL "(ANGLEHOLD LEVEL)" #define OSD_MSG_MOVE_STICKS "MOVE STICKS TO ABORT" +#define OSD_MSG_TOILET_BOWL "TOILET BOWLING !" #ifdef USE_DEV_TOOLS #define OSD_MSG_GRD_TEST_MODE "GRD TEST > MOTORS DISABLED" diff --git a/src/main/navigation/navigation.c b/src/main/navigation/navigation.c index b5e9e8a7e57..f9257543a6c 100644 --- a/src/main/navigation/navigation.c +++ b/src/main/navigation/navigation.c @@ -201,6 +201,7 @@ PG_RESET_TEMPLATE(navConfig_t, navConfig, .slowDownForTurning = SETTING_NAV_MC_WP_SLOWDOWN_DEFAULT, .althold_throttle_type = SETTING_NAV_MC_ALTHOLD_THROTTLE_DEFAULT, // STICK .inverted_crash_detection = SETTING_NAV_MC_INVERTED_CRASH_DETECTION_DEFAULT, // 0 - disarm time delay for inverted crash detection + .toiletbowl_detection = SETTING_NAV_MC_TOILETBOWL_DETECTION_DEFAULT, // 0 - sensitivity factor for toilet bowling detection }, // Fixed wing @@ -252,6 +253,7 @@ static navWapointHeading_t wpHeadingControl; navigationPosControl_t posControl; navSystemStatus_t NAV_Status; static bool landingDetectorIsActive; +int16_t toiletBowlingHeadingCorrection; // Indicates toilet bowling detected multirotor EXTENDED_FASTRAM multicopterPosXyCoefficients_t multicopterPosXyCoefficients; diff --git a/src/main/navigation/navigation.h b/src/main/navigation/navigation.h index 9132974e480..9711195fc81 100644 --- a/src/main/navigation/navigation.h +++ b/src/main/navigation/navigation.h @@ -197,7 +197,7 @@ typedef struct geozone_s { int32_t distanceHorToNearestZone; int32_t distanceVertToNearestZone; int32_t zoneInfo; - int32_t currentzoneMaxAltitude; + int32_t currentzoneMaxAltitude; int32_t currentzoneMinAltitude; bool nearestHorZoneHasAction; bool sticksLocked; @@ -464,6 +464,7 @@ typedef struct navConfig_s { bool slowDownForTurning; // Slow down during WP missions when changing heading on next waypoint uint8_t althold_throttle_type; // throttle zero datum type for alt hold uint8_t inverted_crash_detection; // Enables inverted crash detection, setting defines disarm time delay (0 = disabled) + uint8_t toiletbowl_detection; // Enables toilet bowling detection and heading correction (0 = disabled) } mc; struct { diff --git a/src/main/navigation/navigation_multicopter.c b/src/main/navigation/navigation_multicopter.c index 811186d0bda..f180fd5adfd 100644 --- a/src/main/navigation/navigation_multicopter.c +++ b/src/main/navigation/navigation_multicopter.c @@ -483,6 +483,33 @@ static float getVelocityExpoAttenuationFactor(float velTotal, float velMax) return 1.0f - posControl.posResponseExpo * (1.0f - (velScale * velScale)); // x^3 expo factor } +static void isToiletBowlingDetected(void) +{ + static timeMs_t startTime = 0; + + uint16_t courseToHoldPoint = calculateBearingToDestination(&posControl.desiredState.pos); + int16_t courseError = wrap_18000(courseToHoldPoint - 10 * gpsSol.groundCourse); + bool courseErrorCheck = ABS(courseError) > 3000 && ABS(courseError) < 15500; + + uint16_t distanceToHoldPoint = calculateDistanceToDestination(&posControl.desiredState.pos); + bool distanceSpeedCheck = posControl.actualState.velXY * distanceToHoldPoint > (navConfig()->mc.toiletbowl_detection * 10000); + + if (toiletBowlingHeadingCorrection) { + uint16_t correctedHeading = wrap_36000(posControl.actualState.yaw - 0.67 * toiletBowlingHeadingCorrection); + posControl.actualState.sinYaw = sin_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); + posControl.actualState.cosYaw = cos_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); + } else if (posControl.actualState.velXY > 100 && distanceToHoldPoint > 100 && courseErrorCheck && distanceSpeedCheck) { + if (startTime == 0) { + startTime = millis(); + } else if (millis() - startTime > 1000) { + // Try to correct heading error + toiletBowlingHeadingCorrection = courseError; + } + } else { + startTime = 0; + } +} + static void updatePositionVelocityController_MC(const float maxSpeed) { if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE)) { @@ -498,6 +525,12 @@ static void updatePositionVelocityController_MC(const float maxSpeed) } } + if (navConfig()->mc.toiletbowl_detection && isNavHoldPositionActive()) { + isToiletBowlingDetected(); + } else { + toiletBowlingHeadingCorrection = 0; + } + const float posErrorX = posControl.desiredState.pos.x - navGetCurrentActualPositionAndVelocity()->pos.x; const float posErrorY = posControl.desiredState.pos.y - navGetCurrentActualPositionAndVelocity()->pos.y; diff --git a/src/main/navigation/navigation_private.h b/src/main/navigation/navigation_private.h index 15fe5e5a99d..abb7891c829 100644 --- a/src/main/navigation/navigation_private.h +++ b/src/main/navigation/navigation_private.h @@ -525,6 +525,7 @@ PG_DECLARE_ARRAY(navWaypoint_t, NAV_MAX_WAYPOINTS, nonVolatileWaypointList); extern navigationPosControl_t posControl; extern multicopterPosXyCoefficients_t multicopterPosXyCoefficients; +extern int16_t toiletBowlingHeadingCorrection; /* Internally used functions */ const navEstimatedPosVel_t * navGetCurrentActualPositionAndVelocity(void); From fa8b10dd54103f5d3052736204bec3121d9eccc9 Mon Sep 17 00:00:00 2001 From: breadoven <56191411+breadoven@users.noreply.github.com> Date: Thu, 8 May 2025 11:07:24 +0100 Subject: [PATCH 2/6] improvements --- src/main/io/osd.c | 2 +- src/main/io/osd.h | 2 +- src/main/navigation/navigation.c | 3 +- src/main/navigation/navigation_multicopter.c | 73 +++++++++++--------- src/main/navigation/navigation_private.h | 2 +- 5 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 025a238d7a7..9f6860b5cc0 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6156,7 +6156,7 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter } } } - if (STATE(MULTIROTOR) && isNavHoldPositionActive() && toiletBowlingHeadingCorrection) { + if (STATE(MULTIROTOR) && mcToiletBowlingHeadingCorrection) { messages[messageCount++] = OSD_MESSAGE_STR(OSD_MSG_TOILET_BOWL); } } else if (ARMING_FLAG(ARMING_DISABLED_ALL_FLAGS)) { /* ADDS MAXIMUM OF 2 MESSAGES TO TOTAL */ diff --git a/src/main/io/osd.h b/src/main/io/osd.h index a08c79d8017..f4eccfd5168 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -122,7 +122,7 @@ #define OSD_MSG_ANGLEHOLD_PITCH "(ANGLEHOLD PITCH)" #define OSD_MSG_ANGLEHOLD_LEVEL "(ANGLEHOLD LEVEL)" #define OSD_MSG_MOVE_STICKS "MOVE STICKS TO ABORT" -#define OSD_MSG_TOILET_BOWL "TOILET BOWLING !" +#define OSD_MSG_TOILET_BOWL "!TOILET BOWLING!" #ifdef USE_DEV_TOOLS #define OSD_MSG_GRD_TEST_MODE "GRD TEST > MOTORS DISABLED" diff --git a/src/main/navigation/navigation.c b/src/main/navigation/navigation.c index f9257543a6c..114dec9f80e 100644 --- a/src/main/navigation/navigation.c +++ b/src/main/navigation/navigation.c @@ -253,7 +253,7 @@ static navWapointHeading_t wpHeadingControl; navigationPosControl_t posControl; navSystemStatus_t NAV_Status; static bool landingDetectorIsActive; -int16_t toiletBowlingHeadingCorrection; // Indicates toilet bowling detected multirotor +int16_t mcToiletBowlingHeadingCorrection; // Indicates toilet bowling detected multirotor EXTENDED_FASTRAM multicopterPosXyCoefficients_t multicopterPosXyCoefficients; @@ -1282,6 +1282,7 @@ static navigationFSMEvent_t navOnEnteringState_NAV_STATE_IDLE(navigationFSMState resetAltitudeController(false); resetHeadingController(); resetPositionController(); + mcToiletBowlingHeadingCorrection = 0; #ifdef USE_FW_AUTOLAND resetFwAutoland(); #endif diff --git a/src/main/navigation/navigation_multicopter.c b/src/main/navigation/navigation_multicopter.c index f180fd5adfd..30e911a1a0b 100644 --- a/src/main/navigation/navigation_multicopter.c +++ b/src/main/navigation/navigation_multicopter.c @@ -483,33 +483,6 @@ static float getVelocityExpoAttenuationFactor(float velTotal, float velMax) return 1.0f - posControl.posResponseExpo * (1.0f - (velScale * velScale)); // x^3 expo factor } -static void isToiletBowlingDetected(void) -{ - static timeMs_t startTime = 0; - - uint16_t courseToHoldPoint = calculateBearingToDestination(&posControl.desiredState.pos); - int16_t courseError = wrap_18000(courseToHoldPoint - 10 * gpsSol.groundCourse); - bool courseErrorCheck = ABS(courseError) > 3000 && ABS(courseError) < 15500; - - uint16_t distanceToHoldPoint = calculateDistanceToDestination(&posControl.desiredState.pos); - bool distanceSpeedCheck = posControl.actualState.velXY * distanceToHoldPoint > (navConfig()->mc.toiletbowl_detection * 10000); - - if (toiletBowlingHeadingCorrection) { - uint16_t correctedHeading = wrap_36000(posControl.actualState.yaw - 0.67 * toiletBowlingHeadingCorrection); - posControl.actualState.sinYaw = sin_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); - posControl.actualState.cosYaw = cos_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); - } else if (posControl.actualState.velXY > 100 && distanceToHoldPoint > 100 && courseErrorCheck && distanceSpeedCheck) { - if (startTime == 0) { - startTime = millis(); - } else if (millis() - startTime > 1000) { - // Try to correct heading error - toiletBowlingHeadingCorrection = courseError; - } - } else { - startTime = 0; - } -} - static void updatePositionVelocityController_MC(const float maxSpeed) { if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE)) { @@ -525,12 +498,6 @@ static void updatePositionVelocityController_MC(const float maxSpeed) } } - if (navConfig()->mc.toiletbowl_detection && isNavHoldPositionActive()) { - isToiletBowlingDetected(); - } else { - toiletBowlingHeadingCorrection = 0; - } - const float posErrorX = posControl.desiredState.pos.x - navGetCurrentActualPositionAndVelocity()->pos.x; const float posErrorY = posControl.desiredState.pos.y - navGetCurrentActualPositionAndVelocity()->pos.y; @@ -587,8 +554,48 @@ static float computeVelocityScale( return constrainf(scale, 0, attenuationFactor); } +static void checkForToiletBowling(void) +{ + bool isHoldingPosition = ((FLIGHT_MODE(NAV_COURSE_HOLD_MODE) && posControl.cruise.multicopterSpeed < 50) || navGetCurrentStateFlags() & NAV_CTL_HOLD); + uint16_t distanceToHoldPoint = calculateDistanceToDestination(&posControl.desiredState.pos); + + if (posControl.actualState.velXY < 100 || distanceToHoldPoint < 100 || !isHoldingPosition || posControl.flags.isAdjustingPosition) { + return; + } + + /* Compare required course back to hold point against actual GPS CoG + * Toilet bowling likely if heading error > 30 dges. Also ignore errors > 155 degs -> could be caused by gusting wind */ + int16_t courseError = wrap_18000(calculateBearingToDestination(&posControl.desiredState.pos) - 10 * gpsSol.groundCourse); + bool courseErrorCheck = ABS(courseError) > 3000 && ABS(courseError) < 15500; + + /* vel x distanceToHoldPoint provides good indication of toilet bowling with value rising rapidly when hold control is lost */ + bool distanceSpeedCheck = posControl.actualState.velXY * distanceToHoldPoint > (navConfig()->mc.toiletbowl_detection * 10000); + + static timeMs_t startTime = 0; + if (courseErrorCheck && distanceSpeedCheck) { + if (startTime == 0) { + startTime = millis(); + } else if (millis() - startTime > 1000) { + /* Set heading correction if check conditions exist > 1s. 2/3 of actual course error seems to work best */ + mcToiletBowlingHeadingCorrection = 0.67 * courseError; + } + } else { + startTime = 0; + } +} + static void updatePositionAccelController_MC(timeDelta_t deltaMicros, float maxAccelLimit, const float maxSpeed) { + if (navConfig()->mc.toiletbowl_detection) { + if (mcToiletBowlingHeadingCorrection) { + uint16_t correctedHeading = wrap_36000(posControl.actualState.yaw - mcToiletBowlingHeadingCorrection); + posControl.actualState.sinYaw = sin_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); + posControl.actualState.cosYaw = cos_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); + } else { + checkForToiletBowling(); + } + } + const float measurementX = navGetCurrentActualPositionAndVelocity()->vel.x; const float measurementY = navGetCurrentActualPositionAndVelocity()->vel.y; diff --git a/src/main/navigation/navigation_private.h b/src/main/navigation/navigation_private.h index abb7891c829..3427e36c0ad 100644 --- a/src/main/navigation/navigation_private.h +++ b/src/main/navigation/navigation_private.h @@ -525,7 +525,7 @@ PG_DECLARE_ARRAY(navWaypoint_t, NAV_MAX_WAYPOINTS, nonVolatileWaypointList); extern navigationPosControl_t posControl; extern multicopterPosXyCoefficients_t multicopterPosXyCoefficients; -extern int16_t toiletBowlingHeadingCorrection; +extern int16_t mcToiletBowlingHeadingCorrection; /* Internally used functions */ const navEstimatedPosVel_t * navGetCurrentActualPositionAndVelocity(void); From 7c9a2f1826be6ba172d312390d8e310ad73974ea Mon Sep 17 00:00:00 2001 From: breadoven <56191411+breadoven@users.noreply.github.com> Date: Tue, 1 Jul 2025 22:59:21 +0100 Subject: [PATCH 3/6] fixes --- src/main/io/osd.h | 2 +- src/main/navigation/navigation.c | 1 - src/main/navigation/navigation_multicopter.c | 6 ++++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/io/osd.h b/src/main/io/osd.h index f4eccfd5168..5f8a16d435a 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -122,7 +122,7 @@ #define OSD_MSG_ANGLEHOLD_PITCH "(ANGLEHOLD PITCH)" #define OSD_MSG_ANGLEHOLD_LEVEL "(ANGLEHOLD LEVEL)" #define OSD_MSG_MOVE_STICKS "MOVE STICKS TO ABORT" -#define OSD_MSG_TOILET_BOWL "!TOILET BOWLING!" +#define OSD_MSG_TOILET_BOWL "!MAG BAD>FIX ATTEMPTED!" #ifdef USE_DEV_TOOLS #define OSD_MSG_GRD_TEST_MODE "GRD TEST > MOTORS DISABLED" diff --git a/src/main/navigation/navigation.c b/src/main/navigation/navigation.c index 114dec9f80e..16fdaed32c9 100644 --- a/src/main/navigation/navigation.c +++ b/src/main/navigation/navigation.c @@ -1282,7 +1282,6 @@ static navigationFSMEvent_t navOnEnteringState_NAV_STATE_IDLE(navigationFSMState resetAltitudeController(false); resetHeadingController(); resetPositionController(); - mcToiletBowlingHeadingCorrection = 0; #ifdef USE_FW_AUTOLAND resetFwAutoland(); #endif diff --git a/src/main/navigation/navigation_multicopter.c b/src/main/navigation/navigation_multicopter.c index 30e911a1a0b..4311c1c0199 100644 --- a/src/main/navigation/navigation_multicopter.c +++ b/src/main/navigation/navigation_multicopter.c @@ -559,7 +559,8 @@ static void checkForToiletBowling(void) bool isHoldingPosition = ((FLIGHT_MODE(NAV_COURSE_HOLD_MODE) && posControl.cruise.multicopterSpeed < 50) || navGetCurrentStateFlags() & NAV_CTL_HOLD); uint16_t distanceToHoldPoint = calculateDistanceToDestination(&posControl.desiredState.pos); - if (posControl.actualState.velXY < 100 || distanceToHoldPoint < 100 || !isHoldingPosition || posControl.flags.isAdjustingPosition) { + if (posControl.actualState.velXY < 100 || distanceToHoldPoint < 100 || !isHoldingPosition || (posControl.flags.isAdjustingPosition && navConfig()->general.flags.user_control_mode == NAV_GPS_ATTI)) { + return; } @@ -576,7 +577,7 @@ static void checkForToiletBowling(void) if (startTime == 0) { startTime = millis(); } else if (millis() - startTime > 1000) { - /* Set heading correction if check conditions exist > 1s. 2/3 of actual course error seems to work best */ + /* Set heading correction if check conditions exist > 1 sec. 2/3 of actual course error seems to work best */ mcToiletBowlingHeadingCorrection = 0.67 * courseError; } } else { @@ -1030,6 +1031,7 @@ void calculateMulticopterInitialHoldPosition(fpVector3_t * pos) void resetMulticopterHeadingController(void) { updateHeadingHoldTarget(CENTIDEGREES_TO_DEGREES(posControl.actualState.yaw)); + mcToiletBowlingHeadingCorrection = 0; } static void applyMulticopterHeadingController(void) From 72b51d71b4ff54b69c243df3520af14ff6380e3b Mon Sep 17 00:00:00 2001 From: breadoven <56191411+breadoven@users.noreply.github.com> Date: Wed, 22 Apr 2026 20:58:34 +0100 Subject: [PATCH 4/6] change setting functionality + osd message --- docs/Settings.md | 4 +- src/main/fc/settings.yaml | 4 +- src/main/io/osd.c | 115 +++++++++---------- src/main/io/osd.h | 2 +- src/main/navigation/navigation_multicopter.c | 15 ++- 5 files changed, 73 insertions(+), 67 deletions(-) diff --git a/docs/Settings.md b/docs/Settings.md index 9d630845368..abf296f7781 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -4054,11 +4054,11 @@ P gain of altitude PID controller (Multirotor) ### nav_mc_toiletbowl_detection -Sets sensitivity of toilet bowling detection for multirotors. On detection a heading correction is applied which should stop the toilet bowling. A setting of 2 works well for 5 inch multirotors. Increasing the setting will reduce sensitivity and delay detection. Set to 0 to disable. (Toilet bowling occurs most obviously during position hold when an inaccurate compass heading causes a rapidly increasing fast sweeping bowl shaped flight path). +Sets the sensitivity of toilet bowling detection for multirotors and whether a heading correction is applied on detection which should stop the toilet bowling occuring. Sensitivity can be set from 1 to 9. A setting of 2 works well for 5 inch multirotors. Increasing the setting will reduce sensitivity and delay detection. If a heading correction is required on detection the sensitivity setting should be multiplied by 10, e.g. set to 20 for a sensitivity of 2 with heading correction on detection. If no heading correction is required, e.g. setting of 2, then toilet bowling detection will be indicated only by an OSD system message. Set to 0 to disable detection entirely. | Default | Min | Max | | --- | --- | --- | -| 0 | 0 | 20 | +| 0 | 0 | 90 | --- diff --git a/src/main/fc/settings.yaml b/src/main/fc/settings.yaml index bf5ebb5fefb..71275e6365d 100644 --- a/src/main/fc/settings.yaml +++ b/src/main/fc/settings.yaml @@ -2572,11 +2572,11 @@ groups: min: 0 max: 15 - name: nav_mc_toiletbowl_detection - description: "Sets sensitivity of toilet bowling detection for multirotors. On detection a heading correction is applied which should stop the toilet bowling. A setting of 2 works well for 5 inch multirotors. Increasing the setting will reduce sensitivity and delay detection. Set to 0 to disable. (Toilet bowling occurs most obviously during position hold when an inaccurate compass heading causes a rapidly increasing fast sweeping bowl shaped flight path)." + description: "Sets the sensitivity of toilet bowling detection for multirotors and whether a heading correction is applied on detection which should stop the toilet bowling occuring. Sensitivity can be set from 1 to 9. A setting of 2 works well for 5 inch multirotors. Increasing the setting will reduce sensitivity and delay detection. If a heading correction is required on detection the sensitivity setting should be multiplied by 10, e.g. set to 20 for a sensitivity of 2 with heading correction on detection. If no heading correction is required, e.g. setting of 2, then toilet bowling detection will be indicated only by an OSD system message. Set to 0 to disable detection entirely." default_value: 0 field: mc.toiletbowl_detection min: 0 - max: 20 + max: 90 - name: nav_mc_althold_throttle description: "If set to STICK the FC remembers the throttle stick position when enabling ALTHOLD and treats it as the neutral midpoint for holding altitude. If set to MID_STICK or HOVER the neutral midpoint is set to the mid stick position or the hover throttle position respectively." default_value: "STICK" diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 2c701cbc05a..450ad653f1e 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6213,66 +6213,66 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter } else if (STATE(LANDING_DETECTED)) { messages[messageCount++] = OSD_MESSAGE_STR(OSD_MSG_LANDED); } else { + /* Messages shown only when Failsafe, WP, RTH or Emergency Landing not active and landed state inactive */ + /* ADDS MAXIMUM OF 3 MESSAGES TO TOTAL */ #ifdef USE_GEOZONE - char buf[12], buf1[12]; - switch (geozone.messageState) { - case GEOZONE_MESSAGE_STATE_NFZ: - messages[messageCount++] = OSD_MSG_NFZ; - break; - case GEOZONE_MESSAGE_STATE_LEAVING_FZ: + char buf[12], buf1[12]; + switch (geozone.messageState) { + case GEOZONE_MESSAGE_STATE_NFZ: + messages[messageCount++] = OSD_MSG_NFZ; + break; + case GEOZONE_MESSAGE_STATE_LEAVING_FZ: + osdFormatDistanceSymbol(buf, geozone.distanceToZoneBorder3d, 0, 3); + tfp_sprintf(messageBuf, OSD_MSG_LEAVING_FZ, buf); + messages[messageCount++] = messageBuf; + break; + case GEOZONE_MESSAGE_STATE_OUTSIDE_FZ: + messages[messageCount++] = OSD_MSG_OUTSIDE_FZ; + break; + case GEOZONE_MESSAGE_STATE_ENTERING_NFZ: osdFormatDistanceSymbol(buf, geozone.distanceToZoneBorder3d, 0, 3); - tfp_sprintf(messageBuf, OSD_MSG_LEAVING_FZ, buf); - messages[messageCount++] = messageBuf; - break; - case GEOZONE_MESSAGE_STATE_OUTSIDE_FZ: - messages[messageCount++] = OSD_MSG_OUTSIDE_FZ; - break; - case GEOZONE_MESSAGE_STATE_ENTERING_NFZ: - osdFormatDistanceSymbol(buf, geozone.distanceToZoneBorder3d, 0, 3); - if (geozone.zoneInfo == INT32_MAX) { - tfp_sprintf(buf1, "%s%c", "INF", SYM_ALT_M); - } else { - osdFormatAltitudeSymbol(buf1, geozone.zoneInfo); - } - tfp_sprintf(messageBuf, OSD_MSG_ENTERING_NFZ, buf, buf1); - messages[messageCount++] = messageBuf; - break; - case GEOZONE_MESSAGE_STATE_AVOIDING_FB: - messages[messageCount++] = OSD_MSG_AVOIDING_FB; - if (!posControl.sendTo.lockSticks) { - messages[messageCount++] = OSD_MSG_MOVE_STICKS; - } - break; - case GEOZONE_MESSAGE_STATE_RETURN_TO_ZONE: - messages[messageCount++] = OSD_MSG_RETURN_TO_ZONE; - if (!posControl.sendTo.lockSticks) { - messages[messageCount++] = OSD_MSG_MOVE_STICKS; - } - break; - case GEOZONE_MESSAGE_STATE_AVOIDING_ALTITUDE_BREACH: - messages[messageCount++] = OSD_MSG_AVOIDING_ALT_BREACH; - if (!posControl.sendTo.lockSticks) { - messages[messageCount++] = OSD_MSG_MOVE_STICKS; - } - break; - case GEOZONE_MESSAGE_STATE_FLYOUT_NFZ: - messages[messageCount++] = OSD_MSG_FLYOUT_NFZ; - if (!posControl.sendTo.lockSticks) { - messages[messageCount++] = OSD_MSG_MOVE_STICKS; - } - break; - case GEOZONE_MESSAGE_STATE_POS_HOLD: - messages[messageCount++] = OSD_MSG_AVOIDING_FB; - if (!geozone.sticksLocked) { - messages[messageCount++] = OSD_MSG_MOVE_STICKS; - } - break; - case GEOZONE_MESSAGE_STATE_NONE: - break; - } + if (geozone.zoneInfo == INT32_MAX) { + tfp_sprintf(buf1, "%s%c", "INF", SYM_ALT_M); + } else { + osdFormatAltitudeSymbol(buf1, geozone.zoneInfo); + } + tfp_sprintf(messageBuf, OSD_MSG_ENTERING_NFZ, buf, buf1); + messages[messageCount++] = messageBuf; + break; + case GEOZONE_MESSAGE_STATE_AVOIDING_FB: + messages[messageCount++] = OSD_MSG_AVOIDING_FB; + if (!posControl.sendTo.lockSticks) { + messages[messageCount++] = OSD_MSG_MOVE_STICKS; + } + break; + case GEOZONE_MESSAGE_STATE_RETURN_TO_ZONE: + messages[messageCount++] = OSD_MSG_RETURN_TO_ZONE; + if (!posControl.sendTo.lockSticks) { + messages[messageCount++] = OSD_MSG_MOVE_STICKS; + } + break; + case GEOZONE_MESSAGE_STATE_AVOIDING_ALTITUDE_BREACH: + messages[messageCount++] = OSD_MSG_AVOIDING_ALT_BREACH; + if (!posControl.sendTo.lockSticks) { + messages[messageCount++] = OSD_MSG_MOVE_STICKS; + } + break; + case GEOZONE_MESSAGE_STATE_FLYOUT_NFZ: + messages[messageCount++] = OSD_MSG_FLYOUT_NFZ; + if (!posControl.sendTo.lockSticks) { + messages[messageCount++] = OSD_MSG_MOVE_STICKS; + } + break; + case GEOZONE_MESSAGE_STATE_POS_HOLD: + messages[messageCount++] = OSD_MSG_AVOIDING_FB; + if (!geozone.sticksLocked) { + messages[messageCount++] = OSD_MSG_MOVE_STICKS; + } + break; + case GEOZONE_MESSAGE_STATE_NONE: + break; + } #endif - /* Messages shown only when Failsafe, WP, RTH or Emergency Landing not active and landed state inactive */ - /* ADDS MAXIMUM OF 3 MESSAGES TO TOTAL */ if (STATE(AIRPLANE)) { /* ADDS MAXIMUM OF 3 MESSAGES TO TOTAL */ #ifdef USE_FW_AUTOLAND if (canFwLandingBeCancelled()) { @@ -6309,7 +6309,6 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter messages[messageCount++] = OSD_MESSAGE_STR(OSD_MSG_ANGLEHOLD_PITCH); } } - } else if (STATE(MULTIROTOR)) { /* ADDS MAXIMUM OF 2 MESSAGES TO TOTAL */ if (FLIGHT_MODE(NAV_COURSE_HOLD_MODE)) { if (posControl.cruise.multicopterSpeed >= 50.0f) { diff --git a/src/main/io/osd.h b/src/main/io/osd.h index b9582883c82..7977654dbe9 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -122,7 +122,7 @@ #define OSD_MSG_ANGLEHOLD_PITCH "(ANGLEHOLD PITCH)" #define OSD_MSG_ANGLEHOLD_LEVEL "(ANGLEHOLD LEVEL)" #define OSD_MSG_MOVE_STICKS "MOVE STICKS TO ABORT" -#define OSD_MSG_TOILET_BOWL "!MAG BAD>FIX ATTEMPTED!" +#define OSD_MSG_TOILET_BOWL "!TOILET BOWLING DETECTED!" #ifdef USE_DEV_TOOLS #define OSD_MSG_GRD_TEST_MODE "GRD TEST > MOTORS DISABLED" diff --git a/src/main/navigation/navigation_multicopter.c b/src/main/navigation/navigation_multicopter.c index a18f4e9e7fa..7a154282483 100644 --- a/src/main/navigation/navigation_multicopter.c +++ b/src/main/navigation/navigation_multicopter.c @@ -569,8 +569,13 @@ static void checkForToiletBowling(void) int16_t courseError = wrap_18000(calculateBearingToDestination(&posControl.desiredState.pos) - 10 * gpsSol.groundCourse); bool courseErrorCheck = ABS(courseError) > 3000 && ABS(courseError) < 15500; + uint8_t sensitivity = navConfig()->mc.toiletbowl_detection; + if (sensitivity > 9) { + sensitivity /= 10; + } + /* vel x distanceToHoldPoint provides good indication of toilet bowling with value rising rapidly when hold control is lost */ - bool distanceSpeedCheck = posControl.actualState.velXY * distanceToHoldPoint > (navConfig()->mc.toiletbowl_detection * 10000); + bool distanceSpeedCheck = posControl.actualState.velXY * distanceToHoldPoint > (sensitivity * 10000); static timeMs_t startTime = 0; if (courseErrorCheck && distanceSpeedCheck) { @@ -589,9 +594,11 @@ static void updatePositionAccelController_MC(timeDelta_t deltaMicros, float maxA { if (navConfig()->mc.toiletbowl_detection) { if (mcToiletBowlingHeadingCorrection) { - uint16_t correctedHeading = wrap_36000(posControl.actualState.yaw - mcToiletBowlingHeadingCorrection); - posControl.actualState.sinYaw = sin_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); - posControl.actualState.cosYaw = cos_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); + if (navConfig()->mc.toiletbowl_detection > 9) { + uint16_t correctedHeading = wrap_36000(posControl.actualState.yaw - mcToiletBowlingHeadingCorrection); + posControl.actualState.sinYaw = sin_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); + posControl.actualState.cosYaw = cos_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); + } } else { checkForToiletBowling(); } From 63348772baad8302777358afce62bf93445c083c Mon Sep 17 00:00:00 2001 From: breadoven <56191411+breadoven@users.noreply.github.com> Date: Thu, 28 May 2026 12:11:54 +0100 Subject: [PATCH 5/6] change to warning only, setting removed --- docs/Settings.md | 11 +------ src/main/fc/settings.yaml | 6 ---- src/main/io/osd.c | 2 +- src/main/navigation/navigation.c | 10 +++--- src/main/navigation/navigation.h | 1 - src/main/navigation/navigation_multicopter.c | 34 ++++++-------------- src/main/navigation/navigation_private.h | 2 +- 7 files changed, 17 insertions(+), 49 deletions(-) diff --git a/docs/Settings.md b/docs/Settings.md index c43fe30acec..7750369a90f 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -4261,16 +4261,6 @@ P gain of altitude PID controller (Multirotor) --- -### nav_mc_toiletbowl_detection - -Sets the sensitivity of toilet bowling detection for multirotors and whether a heading correction is applied on detection which should stop the toilet bowling occuring. Sensitivity can be set from 1 to 9. A setting of 2 works well for 5 inch multirotors. Increasing the setting will reduce sensitivity and delay detection. If a heading correction is required on detection the sensitivity setting should be multiplied by 10, e.g. set to 20 for a sensitivity of 2 with heading correction on detection. If no heading correction is required, e.g. setting of 2, then toilet bowling detection will be indicated only by an OSD system message. Set to 0 to disable detection entirely. - -| Default | Min | Max | -| --- | --- | --- | -| 0 | 0 | 90 | - ---- - ### nav_mc_vel_xy_d D gain of Position-Rate (Velocity to Acceleration) PID controller. It can damp P and I. Increasing D might help when drone overshoots target. @@ -5946,6 +5936,7 @@ Selection of pitot hardware. VIRTUAL only works if a GPS is enabled. | FAKE | | | MSP | | | DLVR-L10D | | +| MS5525 | | --- diff --git a/src/main/fc/settings.yaml b/src/main/fc/settings.yaml index 960c5d71b57..2dbdf9e474b 100644 --- a/src/main/fc/settings.yaml +++ b/src/main/fc/settings.yaml @@ -2571,12 +2571,6 @@ groups: field: mc.inverted_crash_detection min: 0 max: 15 - - name: nav_mc_toiletbowl_detection - description: "Sets the sensitivity of toilet bowling detection for multirotors and whether a heading correction is applied on detection which should stop the toilet bowling occuring. Sensitivity can be set from 1 to 9. A setting of 2 works well for 5 inch multirotors. Increasing the setting will reduce sensitivity and delay detection. If a heading correction is required on detection the sensitivity setting should be multiplied by 10, e.g. set to 20 for a sensitivity of 2 with heading correction on detection. If no heading correction is required, e.g. setting of 2, then toilet bowling detection will be indicated only by an OSD system message. Set to 0 to disable detection entirely." - default_value: 0 - field: mc.toiletbowl_detection - min: 0 - max: 90 - name: nav_mc_althold_throttle description: "If set to STICK the FC remembers the throttle stick position when enabling ALTHOLD and treats it as the neutral midpoint for holding altitude. If set to MID_STICK or HOVER the neutral midpoint is set to the mid stick position or the hover throttle position respectively." default_value: "STICK" diff --git a/src/main/io/osd.c b/src/main/io/osd.c index 88e42ad10d6..76cf6515468 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6463,7 +6463,7 @@ textAttributes_t osdGetSystemMessage(char *buff, size_t buff_size, bool isCenter } } } - if (STATE(MULTIROTOR) && mcToiletBowlingHeadingCorrection) { + if (STATE(MULTIROTOR) && mcToiletBowlingDetected) { messages[messageCount++] = OSD_MESSAGE_STR(OSD_MSG_TOILET_BOWL); } } else if (ARMING_FLAG(ARMING_DISABLED_ALL_FLAGS)) { /* ADDS MAXIMUM OF 2 MESSAGES TO TOTAL */ diff --git a/src/main/navigation/navigation.c b/src/main/navigation/navigation.c index e77b04fc6bd..82d65761632 100644 --- a/src/main/navigation/navigation.c +++ b/src/main/navigation/navigation.c @@ -201,7 +201,6 @@ PG_RESET_TEMPLATE(navConfig_t, navConfig, .slowDownForTurning = SETTING_NAV_MC_WP_SLOWDOWN_DEFAULT, .althold_throttle_type = SETTING_NAV_MC_ALTHOLD_THROTTLE_DEFAULT, // STICK .inverted_crash_detection = SETTING_NAV_MC_INVERTED_CRASH_DETECTION_DEFAULT, // 0 - disarm time delay for inverted crash detection - .toiletbowl_detection = SETTING_NAV_MC_TOILETBOWL_DETECTION_DEFAULT, // 0 - sensitivity factor for toilet bowling detection }, // Fixed wing @@ -253,7 +252,6 @@ static navWapointHeading_t wpHeadingControl; navigationPosControl_t posControl; navSystemStatus_t NAV_Status; static bool landingDetectorIsActive; -int16_t mcToiletBowlingHeadingCorrection; // Indicates toilet bowling detected multirotor EXTENDED_FASTRAM multicopterPosXyCoefficients_t multicopterPosXyCoefficients; @@ -3876,10 +3874,10 @@ void getWaypoint(uint8_t wpNumber, navWaypoint_t * wpData) int isGCSValid(void) { - return (ARMING_FLAG(ARMED) && - (posControl.flags.estPosStatus >= EST_TRUSTED) && - posControl.gpsOrigin.valid && - posControl.flags.isGCSAssistedNavigationEnabled && + return (ARMING_FLAG(ARMED) && + (posControl.flags.estPosStatus >= EST_TRUSTED) && + posControl.gpsOrigin.valid && + posControl.flags.isGCSAssistedNavigationEnabled && (posControl.navState == NAV_STATE_POSHOLD_3D_IN_PROGRESS)); } diff --git a/src/main/navigation/navigation.h b/src/main/navigation/navigation.h index b207d3cf98d..3bd62a72c00 100644 --- a/src/main/navigation/navigation.h +++ b/src/main/navigation/navigation.h @@ -464,7 +464,6 @@ typedef struct navConfig_s { bool slowDownForTurning; // Slow down during WP missions when changing heading on next waypoint uint8_t althold_throttle_type; // throttle zero datum type for alt hold uint8_t inverted_crash_detection; // Enables inverted crash detection, setting defines disarm time delay (0 = disabled) - uint8_t toiletbowl_detection; // Enables toilet bowling detection and heading correction (0 = disabled) } mc; struct { diff --git a/src/main/navigation/navigation_multicopter.c b/src/main/navigation/navigation_multicopter.c index 8d4716ef8b0..de3b0a41c64 100644 --- a/src/main/navigation/navigation_multicopter.c +++ b/src/main/navigation/navigation_multicopter.c @@ -63,6 +63,7 @@ static int16_t altHoldThrottleRCZero = 1500; static pt1Filter_t altholdThrottleFilterState; static bool prepareForTakeoffOnReset = false; static sqrt_controller_t alt_hold_sqrt_controller; +bool mcToiletBowlingDetected; float getSqrtControllerVelocity(float targetAltitude, timeDelta_t deltaMicros) { @@ -568,8 +569,7 @@ static void checkForToiletBowling(void) bool isHoldingPosition = ((FLIGHT_MODE(NAV_COURSE_HOLD_MODE) && posControl.cruise.multicopterSpeed < 50) || navGetCurrentStateFlags() & NAV_CTL_HOLD); uint16_t distanceToHoldPoint = calculateDistanceToDestination(&posControl.desiredState.pos); - if (posControl.actualState.velXY < 100 || distanceToHoldPoint < 100 || !isHoldingPosition || (posControl.flags.isAdjustingPosition && navConfig()->general.flags.user_control_mode == NAV_GPS_ATTI)) { - + if (!isHoldingPosition || posControl.actualState.velXY < 100 || distanceToHoldPoint < 100 || (posControl.flags.isAdjustingPosition && navConfig()->general.flags.user_control_mode == NAV_GPS_ATTI)) { return; } @@ -578,21 +578,17 @@ static void checkForToiletBowling(void) int16_t courseError = wrap_18000(calculateBearingToDestination(&posControl.desiredState.pos) - 10 * gpsSol.groundCourse); bool courseErrorCheck = ABS(courseError) > 3000 && ABS(courseError) < 15500; - uint8_t sensitivity = navConfig()->mc.toiletbowl_detection; - if (sensitivity > 9) { - sensitivity /= 10; - } - - /* vel x distanceToHoldPoint provides good indication of toilet bowling with value rising rapidly when hold control is lost */ - bool distanceSpeedCheck = posControl.actualState.velXY * distanceToHoldPoint > (sensitivity * 10000); + /* vel x distanceToHoldPoint provides good indication of toilet bowling with value rising rapidly when hold control is lost + * 30000 should provide a reliable detection threshold without excessive delay or false triggers */ + bool distanceSpeedCheck = posControl.actualState.velXY * distanceToHoldPoint > (30000); static timeMs_t startTime = 0; if (courseErrorCheck && distanceSpeedCheck) { if (startTime == 0) { startTime = millis(); - } else if (millis() - startTime > 1000) { - /* Set heading correction if check conditions exist > 1 sec. 2/3 of actual course error seems to work best */ - mcToiletBowlingHeadingCorrection = 0.67 * courseError; + } else { + // Set detection flag if check conditions exist > 1 sec + mcToiletBowlingDetected = millis() - startTime > 1000; } } else { startTime = 0; @@ -601,17 +597,7 @@ static void checkForToiletBowling(void) static void updatePositionAccelController_MC(timeDelta_t deltaMicros, float maxAccelLimit, const float maxSpeed) { - if (navConfig()->mc.toiletbowl_detection) { - if (mcToiletBowlingHeadingCorrection) { - if (navConfig()->mc.toiletbowl_detection > 9) { - uint16_t correctedHeading = wrap_36000(posControl.actualState.yaw - mcToiletBowlingHeadingCorrection); - posControl.actualState.sinYaw = sin_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); - posControl.actualState.cosYaw = cos_approx(CENTIDEGREES_TO_RADIANS(correctedHeading)); - } - } else { - checkForToiletBowling(); - } - } + if (!mcToiletBowlingDetected) checkForToiletBowling(); const float measurementX = navGetCurrentActualPositionAndVelocity()->vel.x; const float measurementY = navGetCurrentActualPositionAndVelocity()->vel.y; @@ -1058,7 +1044,7 @@ void calculateMulticopterInitialHoldPosition(fpVector3_t * pos) void resetMulticopterHeadingController(void) { updateHeadingHoldTarget(CENTIDEGREES_TO_DEGREES(posControl.actualState.yaw)); - mcToiletBowlingHeadingCorrection = 0; + mcToiletBowlingDetected = false; } static void applyMulticopterHeadingController(void) diff --git a/src/main/navigation/navigation_private.h b/src/main/navigation/navigation_private.h index 7b2bfac7537..cf444e263b3 100644 --- a/src/main/navigation/navigation_private.h +++ b/src/main/navigation/navigation_private.h @@ -526,7 +526,7 @@ PG_DECLARE_ARRAY(navWaypoint_t, NAV_MAX_WAYPOINTS, nonVolatileWaypointList); extern navigationPosControl_t posControl; extern multicopterPosXyCoefficients_t multicopterPosXyCoefficients; -extern int16_t mcToiletBowlingHeadingCorrection; +extern bool mcToiletBowlingDetected; /* Internally used functions */ const navEstimatedPosVel_t * navGetCurrentActualPositionAndVelocity(void); From d0b3c5fc29b9ea1ba793d5d0e18b15af8aabaff4 Mon Sep 17 00:00:00 2001 From: breadoven <56191411+breadoven@users.noreply.github.com> Date: Thu, 28 May 2026 12:43:28 +0100 Subject: [PATCH 6/6] Update navigation_multicopter.c --- src/main/navigation/navigation_multicopter.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/navigation/navigation_multicopter.c b/src/main/navigation/navigation_multicopter.c index de3b0a41c64..602574dbea2 100644 --- a/src/main/navigation/navigation_multicopter.c +++ b/src/main/navigation/navigation_multicopter.c @@ -63,7 +63,6 @@ static int16_t altHoldThrottleRCZero = 1500; static pt1Filter_t altholdThrottleFilterState; static bool prepareForTakeoffOnReset = false; static sqrt_controller_t alt_hold_sqrt_controller; -bool mcToiletBowlingDetected; float getSqrtControllerVelocity(float targetAltitude, timeDelta_t deltaMicros) { @@ -299,6 +298,7 @@ bool adjustMulticopterHeadingFromRCInput(void) * XY-position controller for multicopter aircraft *-----------------------------------------------------------*/ static float lastAccelTargetX = 0.0f, lastAccelTargetY = 0.0f; +bool mcToiletBowlingDetected; void resetMulticopterBrakingMode(void) {