Skip to content

899 add a failsafes config page#1021

Open
bensgilbert wants to merge 20 commits intomainfrom
899-add-a-failsafes-config-page
Open

899 add a failsafes config page#1021
bensgilbert wants to merge 20 commits intomainfrom
899-add-a-failsafes-config-page

Conversation

@bensgilbert
Copy link
Copy Markdown
Contributor

Please double check this is how failsafes are supposed to work (I've never used them and am going purely off terrible documentation)

Also any comments on the loading of params when the failsafe tab is clicked would be appreciated (you'll see what I mean)

…tion

this isn't how enum's are supposed to be used in the first place (consistently using `.value`) but this makes the codebase clearer at least until a proper refactor can take place
@bensgilbert bensgilbert linked an issue Feb 27, 2026 that may be closed by this pull request
@bensgilbert bensgilbert self-assigned this Feb 27, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a failsafes configuration page to the Ground Control Station (GCS), allowing users to configure battery, radio, GCS, and EKF failsafe parameters for both copter and plane aircraft types. It also refactors hardcoded aircraft type values throughout the codebase to use the VehicleType enum for improved maintainability.

Changes:

  • Added new backend endpoint /endpoints/failsafe.py to handle failsafe parameter retrieval and configuration
  • Updated tests and multiple controller files to use VehicleType enum instead of magic numbers
  • Added Redux state management, socket middleware, and emitters for failsafe configuration in the frontend
  • Created a new React component for the failsafe configuration UI with vehicle-specific parameters

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 19 comments.

Show a summary per file
File Description
radio/app/endpoints/failsafe.py New endpoint implementing get/set operations for failsafe parameters with vehicle-type-specific parameter lists
radio/app/endpoints/init.py Imports the new failsafe endpoint module
radio/tests/conftest.py Updated aircraft type checks to use VehicleType enum instead of magic numbers
radio/app/controllers/navController.py Refactored to use VehicleType.FIXED_WING.value instead of hardcoded 1
radio/app/controllers/frameController.py Refactored to use VehicleType.FIXED_WING.value instead of hardcoded 1
radio/app/controllers/flightModesController.py Refactored to use VehicleType.FIXED_WING.value instead of hardcoded 1
gcs/src/redux/slices/configSlice.js Added failsafe state management including config data, refresh state, actions, and emitters
gcs/src/redux/middleware/socketMiddleware.js Added socket event handlers for failsafe config retrieval and parameter updates
gcs/src/redux/middleware/emitters.js Added emitters for failsafe config operations
gcs/src/config.jsx Added Failsafes tab and panel to the config page
gcs/src/components/config/failsafe.jsx New React component rendering failsafe configuration UI with vehicle-specific sections

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread radio/app/endpoints/failsafe.py Outdated
if failsafe_params[param]["success"]:
failsafe_config[param] = failsafe_params[param]["data"].param_value
else:
failsafe_config[param] = "UNKNOWN"
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

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

When a parameter fails to load, it's set to "UNKNOWN" (a string), but the NumberInput components expect numeric values. This could cause the input to malfunction if any parameter fails to load. Consider handling the "UNKNOWN" case by either setting a default numeric value (e.g., 0 or undefined) or disabling the input with a visual indicator that the parameter couldn't be loaded.

Suggested change
failsafe_config[param] = "UNKNOWN"
failsafe_config[param] = None

Copilot uses AI. Check for mistakes.
Comment thread gcs/src/components/config/failsafe.jsx Outdated
Comment thread gcs/src/components/config/failsafe.jsx Outdated
Comment thread gcs/src/components/config/failsafe.jsx Outdated
Comment thread gcs/src/components/config/failsafe.jsx Outdated
Comment thread gcs/src/components/config/failsafe.jsx Outdated
Comment thread radio/app/endpoints/failsafe.py
Comment thread gcs/src/components/config/failsafe.jsx Outdated
Comment thread gcs/src/components/config/failsafe.jsx Outdated
Comment thread radio/app/endpoints/failsafe.py Outdated
@bensgilbert
Copy link
Copy Markdown
Contributor Author

some tests pass with Copter simulator, the other tests pass on the Plane simulator

@1Blademaster
Copy link
Copy Markdown
Member

1Blademaster commented Feb 27, 2026

  • Can we show a success notification when a parameter is set successfully so it's 100% clear it's actually getting set.
  • I don't think we need the "stage 1" and "stage 2" in the battery failsafe section; you can have one without the other for example
  • I'd prefer if the styling was more similar to the other pages where it's left-aligned and without container boxes
  • Can the loading overlay not overlay the entire screen; just the failsafes config section is enough
  • Copter EKF threshold value is not in s, it's just a value from 0-1 (it's for error score which doesn't have a unit)
  • GCS failsafe is a value field, so it can't be toggleable as it is right now:
image

@1Blademaster
Copy link
Copy Markdown
Member

Also follow convention and make a failsafe controller which does all failsafe parameter management etc

@bensgilbert
Copy link
Copy Markdown
Contributor Author

I don't think a controller is needed as there isn't any logic behind failsafes, it's just a more convenient way to set relevant parameters so it uses the params controller instead

@bensgilbert
Copy link
Copy Markdown
Contributor Author

I've done everything else you said but for the battery failsafe stage 1 and 2 that was taken straight from the docs, how would you want it presented instead @1Blademaster ?

@1Blademaster
Copy link
Copy Markdown
Member

1Blademaster commented Mar 14, 2026

I don't think a controller is needed as there isn't any logic behind failsafes, it's just a more convenient way to set relevant parameters so it uses the params controller instead

A controller is needed; it makes it organised to understand what is responsible for what. It also will remove the list of variables at the top of the current failsafe route definition file. It will also allow us to do more things in the future, may not be applicable to failsafes but it doesn't mean it shouldn't follow procedure.

There is also some logic that is needed, e.g. if there is low voltage failsafe set then critical voltage failsafe cannot be a higher value than the low voltage. Etc.

@1Blademaster
Copy link
Copy Markdown
Member

I've done everything else you said but for the battery failsafe stage 1 and 2 that was taken straight from the docs, how would you want it presented instead @1Blademaster ?

"Low" and "Critical" are just fine and it makes it clear what params it's changing as well.

@bensgilbert
Copy link
Copy Markdown
Contributor Author

not sure why the other tests are failing but ready for review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +77 to +79
Gets the gripper related parameters from the drone.
"""
self.drone.logger.debug("Fetching gripper parameters")
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

getFailsafeParams docstring and log messages look copy/pasted from the gripper controller (mentions "gripper" and "from the drone"), but this method is reading from the cached params controller. This makes debugging confusing; please update the wording to refer to failsafe params and clarify it's reading from cache.

Suggested change
Gets the gripper related parameters from the drone.
"""
self.drone.logger.debug("Fetching gripper parameters")
Loads the failsafe-related parameters via the cached params controller.
"""
self.drone.logger.debug("Refreshing cached failsafe parameters")

Copilot uses AI. Check for mistakes.
Comment on lines +367 to +368
onChange={(value) =>
debouncedUpdate("THR_FAILSAFE", Number(value))
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

Mantine Switch onChange receives an event, but this handler treats the argument as a boolean and does Number(value), which will yield NaN and send an invalid parameter value. Use event.currentTarget.checked (and map to 1/0 if the param is numeric) instead.

Suggested change
onChange={(value) =>
debouncedUpdate("THR_FAILSAFE", Number(value))
onChange={(event) =>
debouncedUpdate(
"THR_FAILSAFE",
event.currentTarget.checked ? 1 : 0,
)

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +73
value={failsafeConfig.BATT_LOW_VOLT}
onChange={(value) =>
debouncedUpdate("BATT_LOW_VOLT", Number(value))
}
disabled={failsafeConfig.BATT_LOW_VOLT == undefined}
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

NumberInput onChange can provide ""/null during editing; coercing with Number(value) will turn empty input into 0 (or NaN) and can unintentionally push param updates. Consider guarding like the gripper config does (ignore empty/NaN) or using a shared sanitisation helper.

Copilot uses AI. Check for mistakes.
Comment on lines +88 to +97
<Select
label="Failsafe Low Action"
allowDeselect={false}
value={String(failsafeConfig.BATT_FS_LOW_ACT)}
data={Object.keys(paramDefs.BATT_FS_LOW_ACT.Values).map(
(key) => ({
value: `${key}`,
label: `${key}: ${paramDefs.BATT_FS_LOW_ACT.Values[key]}`,
}),
)}
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

This Select assumes paramDefs.BATT_FS_LOW_ACT is always present and has a Values map. If the loaded param definitions are missing this param for a given vehicle/version, this will throw at render time. Please use optional chaining / fallback (e.g., paramDefs.BATT_FS_LOW_ACT?.Values) and handle the empty-options case; the same pattern applies to the other paramDefs.<PARAM>.Values usages in this component.

Copilot uses AI. Check for mistakes.
Comment on lines +1319 to +1326
if (msg.success) {
showSuccessNotification(msg.message)
store.dispatch(
updateFailsafeConfigParam({
param_id: msg.param_id,
value: msg.value,
}),
)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you also update the params dict in redux when a param is set successfully so it updates on the params page as well; look at the flight modes config to see how that's done.

Comment on lines +77 to +79
Gets the gripper related parameters from the drone.
"""
self.drone.logger.debug("Fetching gripper parameters")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Gripper?


return self.drone.paramsController.setParam(param_id, value, param_type)

def getFailsafeParams(self) -> None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What's the difference between getFailsafeParams and getConfig? They look like they do the same thing?

Comment on lines +66 to +79
success = droneStatus.drone.failsafeController.setFailsafeParam(param_id, value)
if success:
result = {
"success": True,
"message": f"Parameter {param_id} successfully set to {value}.",
"param_id": param_id,
"value": value,
}
else:
result = {
"success": False,
"message": f"Failed to set parameter {param_id} to {value}.",
}
socketio.emit("set_failsafe_param_result", result)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Result object should be returned by the controller, not generated inside this endpoint.


socketio.emit(
"failsafe_config",
{"params": failsafe_config},
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Responses need to be generated inside the controller. Also needs to follow the Response type.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you separate these tests so each test is it's own function and is only testing one thing? I want to do that in all the tests as a future ticket so would help doing it now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a failsafes config page

3 participants