From 67ba624624819b141202fe5ab9b44374f1922955 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 39e3de90d52..0d676b47ea6 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -4052,6 +4052,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 6ef471e3bb0..362f89527b6 100644 --- a/src/main/fc/settings.yaml +++ b/src/main/fc/settings.yaml @@ -2571,6 +2571,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 0bdb9e3375f..f34a38f6401 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6359,6 +6359,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 e55aeb22461..f0cc299a519 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -124,6 +124,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 03d4a3da36b..e655d1dce4f 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 eb1621e9f8d..4e69f02a649 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 5d4b7a5b425..e12a6ca25a4 100644 --- a/src/main/navigation/navigation_multicopter.c +++ b/src/main/navigation/navigation_multicopter.c @@ -492,6 +492,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)) { @@ -507,6 +534,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 a1f07e470c0..d656c33c15b 100644 --- a/src/main/navigation/navigation_private.h +++ b/src/main/navigation/navigation_private.h @@ -526,6 +526,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 ff7bbfb4b764e8db560106427d036d85e4667e53 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 f34a38f6401..f27ad480dfb 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6359,7 +6359,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 f0cc299a519..10b6f0fadbd 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -124,7 +124,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 e655d1dce4f..78a1fb93d04 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; @@ -1287,6 +1287,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 e12a6ca25a4..7da751cc009 100644 --- a/src/main/navigation/navigation_multicopter.c +++ b/src/main/navigation/navigation_multicopter.c @@ -492,33 +492,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)) { @@ -534,12 +507,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; @@ -596,8 +563,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 d656c33c15b..7b2bfac7537 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 toiletBowlingHeadingCorrection; +extern int16_t mcToiletBowlingHeadingCorrection; /* Internally used functions */ const navEstimatedPosVel_t * navGetCurrentActualPositionAndVelocity(void); From f95b489987a39a3f3238fc0d04fdf4879f3dcc9c 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 10b6f0fadbd..55ac371077c 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -124,7 +124,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 78a1fb93d04..ad116ebd9fa 100644 --- a/src/main/navigation/navigation.c +++ b/src/main/navigation/navigation.c @@ -1287,7 +1287,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 7da751cc009..758c46786af 100644 --- a/src/main/navigation/navigation_multicopter.c +++ b/src/main/navigation/navigation_multicopter.c @@ -568,7 +568,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; } @@ -585,7 +586,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 { @@ -1050,6 +1051,7 @@ void calculateMulticopterInitialHoldPosition(fpVector3_t * pos) void resetMulticopterHeadingController(void) { updateHeadingHoldTarget(CENTIDEGREES_TO_DEGREES(posControl.actualState.yaw)); + mcToiletBowlingHeadingCorrection = 0; } static void applyMulticopterHeadingController(void) From 1c05da0c750584230b2be94e79b7d2a50b216e04 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.h | 2 +- src/main/navigation/navigation_multicopter.c | 15 +++++++++++---- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/docs/Settings.md b/docs/Settings.md index 0d676b47ea6..317987b4bee 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 362f89527b6..432287d9eb4 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.h b/src/main/io/osd.h index 55ac371077c..0f77de99fdc 100644 --- a/src/main/io/osd.h +++ b/src/main/io/osd.h @@ -124,7 +124,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 758c46786af..8d4716ef8b0 100644 --- a/src/main/navigation/navigation_multicopter.c +++ b/src/main/navigation/navigation_multicopter.c @@ -578,8 +578,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) { @@ -598,9 +603,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 7351a646072867d292fb84ee9788afc58488f5d6 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 | 10 ------ 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, 16 insertions(+), 49 deletions(-) diff --git a/docs/Settings.md b/docs/Settings.md index 317987b4bee..39e3de90d52 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -4052,16 +4052,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. diff --git a/src/main/fc/settings.yaml b/src/main/fc/settings.yaml index 432287d9eb4..6ef471e3bb0 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 f27ad480dfb..d53c9ffc038 100644 --- a/src/main/io/osd.c +++ b/src/main/io/osd.c @@ -6359,7 +6359,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 ad116ebd9fa..ed4fdf3bf38 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 4e69f02a649..44e59898201 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 353c8d9474fc56f4bd4ee03fb4fdf0eed5d74fec 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) {