From 9f747fa6cc29306022aba6b5f6479fd624fcda67 Mon Sep 17 00:00:00 2001 From: Julian Jones Date: Sun, 7 Sep 2025 19:22:22 +0100 Subject: [PATCH 01/21] bruhe i hate ai --- gcs/src/components/realtimeGraph.jsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/gcs/src/components/realtimeGraph.jsx b/gcs/src/components/realtimeGraph.jsx index 456864be4..6df802445 100644 --- a/gcs/src/components/realtimeGraph.jsx +++ b/gcs/src/components/realtimeGraph.jsx @@ -56,12 +56,9 @@ const options = { ticks: { callback: function (value) { // Only show every 10 seconds, avoid creating Date unless needed - const timestamp = this.getLabelForValue(value) - const seconds = Math.floor((timestamp / 1000) % 60) - if (seconds % 10 === 0) { - return new Date(timestamp).toLocaleTimeString() - } - return null + const timestamp = this.getLabelForValue(value); + const date = new Date(timestamp); + return date.getSeconds() % 10 === 0 ? date.toLocaleTimeString() : null; }, }, }, From f7f8326f2e433e99294d6fb32b31adf72f289b63 Mon Sep 17 00:00:00 2001 From: Julian Jones Date: Sun, 7 Sep 2025 19:56:06 +0100 Subject: [PATCH 02/21] moved as much to redux as possible - now need to work on socket --- gcs/src/components/realtimeGraph.jsx | 6 +- gcs/src/params.jsx | 81 +++++++++++--------- gcs/src/redux/middleware/socketMiddleware.js | 8 ++ gcs/src/redux/store.js | 6 +- 4 files changed, 60 insertions(+), 41 deletions(-) diff --git a/gcs/src/components/realtimeGraph.jsx b/gcs/src/components/realtimeGraph.jsx index 6df802445..db1239839 100644 --- a/gcs/src/components/realtimeGraph.jsx +++ b/gcs/src/components/realtimeGraph.jsx @@ -56,9 +56,9 @@ const options = { ticks: { callback: function (value) { // Only show every 10 seconds, avoid creating Date unless needed - const timestamp = this.getLabelForValue(value); - const date = new Date(timestamp); - return date.getSeconds() % 10 === 0 ? date.toLocaleTimeString() : null; + const timestamp = this.getLabelForValue(value) + const date = new Date(timestamp) + return date.getSeconds() % 10 === 0 ? date.toLocaleTimeString() : null }, }, }, diff --git a/gcs/src/params.jsx b/gcs/src/params.jsx index 1cd537d87..00585edd6 100644 --- a/gcs/src/params.jsx +++ b/gcs/src/params.jsx @@ -31,41 +31,43 @@ import { import { socket } from "./helpers/socket.js" // Redux -import { useSelector } from "react-redux" +import { useDispatch, useSelector } from "react-redux" import { selectConnectedToDrone } from "./redux/slices/droneConnectionSlice.js" +import { appendModifiedParams, selectAutoPilotRebootModalOpen, selectFetchingVars, selectFetchingVarsProgress, selectModifiedParams, selectParams, selectRebootData, selectShowModifiedParams, selectShownParams, setAutoPilotRebootModalOpen, setFetchingVars, setFetchingVarsProgress, setParams, setRebootData, setShownParams, toggleShowModifiedParams } from "./redux/slices/paramsSlice.js" export default function Params() { + const dispatch = useDispatch() const connected = useSelector(selectConnectedToDrone) // Parameter states - const [params, paramsHandler] = useListState([]) - const [shownParams, shownParamsHandler] = useListState([]) - const [modifiedParams, modifiedParamsHandler] = useListState([]) - const [showModifiedParams, showModifiedParamsToggle] = useToggle() + const params = useSelector(selectParams) + const shownParams = useSelector(selectShownParams) + const modifiedParams = useSelector(selectModifiedParams) + const showModifiedParams = useSelector(selectShowModifiedParams) // Autopilot reboot states - const [rebootData, setRebootData] = useState({}) - const [opened, { open, close }] = useDisclosure(false) + const rebootData = useSelector(selectRebootData) + const opened = useSelector(selectAutoPilotRebootModalOpen) // Searchbar states const [searchValue, setSearchValue] = useState("") const [debouncedSearchValue] = useDebouncedValue(searchValue, 150) // Fetch progress states - const [fetchingVars, setFetchingVars] = useState(false) - const [fetchingVarsProgress, setFetchingVarsProgress] = useState(0) + const fetchingVars = useSelector(selectFetchingVars) + const fetchingVarsProgress = useSelector(selectFetchingVarsProgress) /** * Resets the state of the parameters page to the initial states */ function resetState() { - setFetchingVars(false) - setFetchingVarsProgress(0) - paramsHandler.setState([]) - shownParamsHandler.setState([]) - modifiedParamsHandler.setState([]) + dispatch(setFetchingVars(false)) + dispatch(setFetchingVarsProgress(0)) + dispatch(setParams([])) + dispatch(setShownParams([])) + dispatch(selectModifiedParams([])) + dispatch(setRebootData({})) setSearchValue("") - setRebootData({}) } /** @@ -73,7 +75,7 @@ export default function Params() { */ function rebootAutopilot() { socket.emit("reboot_autopilot") - open() + dispatch(setAutoPilotRebootModalOpen(true)) resetState() } @@ -81,10 +83,10 @@ export default function Params() { * Refreshes the params on the drone then fetches them */ function refreshParams() { - paramsHandler.setState([]) - shownParamsHandler.setState([]) + dispatch(setParams([])) + dispatch(setShownParams([])) socket.emit("refresh_params") - setFetchingVars(true) + dispatch(setFetchingVars(true)) } /** @@ -106,6 +108,8 @@ export default function Params() { * @param {*} value */ function updateParamValue(handler, param, value) { + // TODO: THIS NEEDS MODIFYING BEFORE REDUX BECAUSE OF APPLY WHERE + // DON'T FORGET BEFORE MERGE! handler.applyWhere( (item) => item.param_id === param.param_id, (item) => ({ ...item, param_value: value }), @@ -124,22 +128,25 @@ export default function Params() { if (value === "") return // If param has already been modified since last save then update it - if (isModified(param)) updateParamValue(modifiedParamsHandler, param, value) + // TODO: THIS NEEDS TO BE EDITED BEFORE MERGE + // if (isModified(param)) updateParamValue(modifiedParamsHandler, param, value) else { // Otherwise add it to modified params param.param_value = value - modifiedParamsHandler.append(param) + dispatch(appendModifiedParams(param)) } - updateParamValue(paramsHandler, param, value) + // TODO: THIS NEEDS MODIFYING BEFORE REDUX BECAUSE OF APPLY WHERE + // DON'T FORGET BEFORE MERGE! + // updateParamValue(paramsHandler, param, value) } useEffect(() => { // Updates the autopilot modal depending on the success of the reboot socket.on("reboot_autopilot", (msg) => { - setRebootData(msg) + dispatch(setRebootData(msg)) if (msg.success) { - close() + dispatch(setAutoPilotRebootModalOpen(false)) } }) @@ -151,35 +158,35 @@ export default function Params() { // Fetch params on connection to drone if (connected && Object.keys(params).length === 0 && !fetchingVars) { - setFetchingVars(true) + dispatch(setFetchingVars(true)) } - // Update parameter states when params are receieved from drone + // Update parameter states when params are received from drone socket.on("params", (params) => { - paramsHandler.setState(params) - shownParamsHandler.setState(params) - setFetchingVars(false) - setFetchingVarsProgress(0) + dispatch(setParams(params)) + dispatch(setShownParams(params)) + dispatch(setFetchingVars(false)) + dispatch(setFetchingVarsProgress(0)) setSearchValue("") }) // Set fetch progress on update from drone socket.on("param_request_update", (msg) => { - setFetchingVarsProgress( + dispatch(setFetchingVarsProgress( (msg.current_param_index / msg.total_number_of_params) * 100, - ) + )) }) // Show success on saving modified params socket.on("param_set_success", (msg) => { showSuccessNotification(msg.message) - modifiedParamsHandler.setState([]) + dispatch(selectModifiedParams([])) }) // Show error message on drone error socket.on("params_error", (err) => { showErrorNotification(err.message) - setFetchingVars(false) + dispatch(setFetchingVars(false)) }) // @@ -203,7 +210,7 @@ export default function Params() { ) // Show the filtered parameters - shownParamsHandler.setState(filteredParams) + dispatch(setShownParams(filteredParams)) }, [debouncedSearchValue, showModifiedParams]) return ( @@ -213,7 +220,7 @@ export default function Params() { {dispatch(setAutoPilotRebootModalOpen(false))}} /> {fetchingVars && ( @@ -232,7 +239,7 @@ export default function Params() { showModifiedParams={showModifiedParams} refreshCallback={refreshParams} rebootCallback={rebootAutopilot} - modifiedCallback={showModifiedParamsToggle} + modifiedCallback={() => dispatch(toggleShowModifiedParams())} searchCallback={setSearchValue} /> diff --git a/gcs/src/redux/middleware/socketMiddleware.js b/gcs/src/redux/middleware/socketMiddleware.js index 3f2c4034d..a0d94a0e9 100644 --- a/gcs/src/redux/middleware/socketMiddleware.js +++ b/gcs/src/redux/middleware/socketMiddleware.js @@ -77,6 +77,7 @@ const DroneSpecificSocketEvents = Object.freeze({ onNavResult: "nav_result", onHomePositionResult: "home_position_result", onIncomingMsg: "incoming_msg", + onRebootAutopilot: "reboot_autopilot", }) const MissionSpecificSocketEvents = Object.freeze({ @@ -282,6 +283,13 @@ const socketMiddleware = (store) => { }, ) + socket.socket.on(DroneSpecificSocketEvents.onRebootAutopilot, (msg) => { + // setRebootData(msg) + // if (msg.success) { + // close() + // } + }) + /* Missions */ diff --git a/gcs/src/redux/store.js b/gcs/src/redux/store.js index b4e2d2d06..4a8ffd8d9 100644 --- a/gcs/src/redux/store.js +++ b/gcs/src/redux/store.js @@ -62,7 +62,11 @@ if (droneConnection !== undefined) { if (droneConnection.port !== undefined) { store.dispatch(setPort(droneConnection.port)) } - if (droneInfo !== undefined && droneInfo.graphs && droneInfo.graphs.selectedGraphs !== undefined) { + if ( + droneInfo !== undefined && + droneInfo.graphs && + droneInfo.graphs.selectedGraphs !== undefined + ) { store.dispatch(setGraphValues(droneInfo.graphs.selectedGraphs)) } } From b8cb644ffe0ebf6a5d3b07fd8951d34d1b235b93 Mon Sep 17 00:00:00 2001 From: Julian Jones Date: Sun, 7 Sep 2025 20:15:13 +0100 Subject: [PATCH 03/21] fixed wrong export --- gcs/src/params.jsx | 82 ++++++++++++++++++++++++++---------------- gcs/src/redux/store.js | 2 ++ 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/gcs/src/params.jsx b/gcs/src/params.jsx index 00585edd6..1fa42c674 100644 --- a/gcs/src/params.jsx +++ b/gcs/src/params.jsx @@ -33,7 +33,26 @@ import { socket } from "./helpers/socket.js" // Redux import { useDispatch, useSelector } from "react-redux" import { selectConnectedToDrone } from "./redux/slices/droneConnectionSlice.js" -import { appendModifiedParams, selectAutoPilotRebootModalOpen, selectFetchingVars, selectFetchingVarsProgress, selectModifiedParams, selectParams, selectRebootData, selectShowModifiedParams, selectShownParams, setAutoPilotRebootModalOpen, setFetchingVars, setFetchingVarsProgress, setParams, setRebootData, setShownParams, toggleShowModifiedParams } from "./redux/slices/paramsSlice.js" +import { + appendModifiedParams, + selectAutoPilotRebootModalOpen, + selectFetchingVars, + selectFetchingVarsProgress, + selectModifiedParams, + selectParams, + selectRebootData, + selectShowModifiedParams, + selectShownParams, + setAutoPilotRebootModalOpen, + setFetchingVars, + setFetchingVarsProgress, + setModifiedParams, + setParams, + setRebootData, + setShownParams, + toggleShowModifiedParams, + updateParamValue, +} from "./redux/slices/paramsSlice.js" export default function Params() { const dispatch = useDispatch() @@ -65,7 +84,7 @@ export default function Params() { dispatch(setFetchingVarsProgress(0)) dispatch(setParams([])) dispatch(setShownParams([])) - dispatch(selectModifiedParams([])) + dispatch(setModifiedParams([])) dispatch(setRebootData({})) setSearchValue("") } @@ -90,7 +109,7 @@ export default function Params() { } /** - * Checks if a paramter has been modified since the last save + * Checks if a parameter has been modified since the last save * @param {*} param the parameter to check * @returns true if the given parameter is in modifiedParams, otherwise false */ @@ -100,21 +119,21 @@ export default function Params() { }) } - /** - * Updates the parameter value in the given useListState handler - * - * @param {*} handler - * @param {*} param - * @param {*} value - */ - function updateParamValue(handler, param, value) { - // TODO: THIS NEEDS MODIFYING BEFORE REDUX BECAUSE OF APPLY WHERE - // DON'T FORGET BEFORE MERGE! - handler.applyWhere( - (item) => item.param_id === param.param_id, - (item) => ({ ...item, param_value: value }), - ) - } + // /** + // * Updates the parameter value in the given useListState handler + // * + // * @param {*} handler + // * @param {*} param + // * @param {*} value + // */ + // function updateParamValue(handler, param, value) { + // // TODO: THIS NEEDS MODIFYING BEFORE REDUX BECAUSE OF APPLY WHERE + // // DON'T FORGET BEFORE MERGE! + // handler.applyWhere( + // (item) => item.param_id === param.param_id, + // (item) => ({ ...item, param_value: value }), + // ) + // } /** * Adds a parameter to the list of parameters that have been modified since the @@ -127,18 +146,17 @@ export default function Params() { function addToModifiedParams(value, param) { if (value === "") return - // If param has already been modified since last save then update it - // TODO: THIS NEEDS TO BE EDITED BEFORE MERGE - // if (isModified(param)) updateParamValue(modifiedParamsHandler, param, value) - else { + if (isModified(param)) { + dispatch( + updateParamValue({ param_id: param.param_id, param_value: value }), + ) + } else { // Otherwise add it to modified params param.param_value = value dispatch(appendModifiedParams(param)) } - // TODO: THIS NEEDS MODIFYING BEFORE REDUX BECAUSE OF APPLY WHERE - // DON'T FORGET BEFORE MERGE! - // updateParamValue(paramsHandler, param, value) + dispatch(updateParamValue({ param_id: param.param_id, param_value: value })) } useEffect(() => { @@ -172,15 +190,17 @@ export default function Params() { // Set fetch progress on update from drone socket.on("param_request_update", (msg) => { - dispatch(setFetchingVarsProgress( - (msg.current_param_index / msg.total_number_of_params) * 100, - )) + dispatch( + setFetchingVarsProgress( + (msg.current_param_index / msg.total_number_of_params) * 100, + ), + ) }) // Show success on saving modified params socket.on("param_set_success", (msg) => { showSuccessNotification(msg.message) - dispatch(selectModifiedParams([])) + dispatch(setModifiedParams([])) }) // Show error message on drone error @@ -220,7 +240,9 @@ export default function Params() { {dispatch(setAutoPilotRebootModalOpen(false))}} + onClose={() => { + dispatch(setAutoPilotRebootModalOpen(false)) + }} /> {fetchingVars && ( diff --git a/gcs/src/redux/store.js b/gcs/src/redux/store.js index 4a8ffd8d9..0be69fc11 100644 --- a/gcs/src/redux/store.js +++ b/gcs/src/redux/store.js @@ -15,6 +15,7 @@ import droneConnectionSlice, { import missionInfoSlice from "./slices/missionSlice" import statusTextSlice from "./slices/statusTextSlice" import notificationSlice from "./slices/notificationSlice" +import paramsSlice from "./slices/paramsSlice" const rootReducer = combineSlices( logAnalyserSlice, @@ -24,6 +25,7 @@ const rootReducer = combineSlices( missionInfoSlice, statusTextSlice, notificationSlice, + paramsSlice, ) // Get the persisted state, we only want to take a couple of things from here. From ba72c550cfbe0c8824392569d588ee9abd375ad0 Mon Sep 17 00:00:00 2001 From: Julian Jones Date: Sun, 7 Sep 2025 21:48:30 +0100 Subject: [PATCH 04/21] moved sockets and emitters over --- gcs/src/components/params/paramsToolbar.jsx | 12 ++- gcs/src/params.jsx | 108 ++++--------------- gcs/src/redux/middleware/emitters.js | 15 +++ gcs/src/redux/middleware/socketMiddleware.js | 62 +++++++++-- 4 files changed, 101 insertions(+), 96 deletions(-) diff --git a/gcs/src/components/params/paramsToolbar.jsx b/gcs/src/components/params/paramsToolbar.jsx index 4e88eafb5..191d1714b 100644 --- a/gcs/src/components/params/paramsToolbar.jsx +++ b/gcs/src/components/params/paramsToolbar.jsx @@ -23,10 +23,15 @@ import tailwindConfig from "../../../tailwind.config.js" import resolveConfig from "tailwindcss/resolveConfig" const tailwindColors = resolveConfig(tailwindConfig).theme.colors +// Redux +import { useSelector } from "react-redux" +import { + selectModifiedParams, + selectShowModifiedParams, +} from "../../redux/slices/paramsSlice.js" + export default function ParamsToolbar({ searchValue, - modifiedParams, - showModifiedParams, refreshCallback, rebootCallback, modifiedCallback, @@ -39,6 +44,9 @@ export default function ParamsToolbar({ socket.emit("set_multiple_params", modifiedParams) } + const modifiedParams = useSelector(selectModifiedParams) + const showModifiedParams = useSelector(selectShowModifiedParams) + return (
item.param_id === param.param_id, - // (item) => ({ ...item, param_value: value }), - // ) - // } - /** * Adds a parameter to the list of parameters that have been modified since the * last save @@ -152,72 +130,28 @@ export default function Params() { ) } else { // Otherwise add it to modified params - param.param_value = value - dispatch(appendModifiedParams(param)) + dispatch( + appendModifiedParams({ + param_id: param.param_id, + param_value: value, + param_type: param.param_type, + }), + ) } dispatch(updateParamValue({ param_id: param.param_id, param_value: value })) } + // Reset state if we loose connection useEffect(() => { - // Updates the autopilot modal depending on the success of the reboot - socket.on("reboot_autopilot", (msg) => { - dispatch(setRebootData(msg)) - if (msg.success) { - dispatch(setAutoPilotRebootModalOpen(false)) - } - }) - - // Drone has lost connection if (!connected) { resetState() - return } - // Fetch params on connection to drone if (connected && Object.keys(params).length === 0 && !fetchingVars) { dispatch(setFetchingVars(true)) } - - // Update parameter states when params are received from drone - socket.on("params", (params) => { - dispatch(setParams(params)) - dispatch(setShownParams(params)) - dispatch(setFetchingVars(false)) - dispatch(setFetchingVarsProgress(0)) - setSearchValue("") - }) - - // Set fetch progress on update from drone - socket.on("param_request_update", (msg) => { - dispatch( - setFetchingVarsProgress( - (msg.current_param_index / msg.total_number_of_params) * 100, - ), - ) - }) - - // Show success on saving modified params - socket.on("param_set_success", (msg) => { - showSuccessNotification(msg.message) - dispatch(setModifiedParams([])) - }) - - // Show error message on drone error - socket.on("params_error", (err) => { - showErrorNotification(err.message) - dispatch(setFetchingVars(false)) - }) - - // - return () => { - socket.off("params") - socket.off("param_request_update") - socket.off("param_set_success") - socket.off("params_error") - socket.off("reboot_autopilot") - } - }, [connected]) // useEffect + }, [connected]) useEffect(() => { if (!params) return @@ -257,12 +191,10 @@ export default function Params() {
dispatch(toggleShowModifiedParams())} - searchCallback={setSearchValue} + searchCallback={(value) => dispatch(setParamSearchValue(value))} />
diff --git a/gcs/src/redux/middleware/emitters.js b/gcs/src/redux/middleware/emitters.js index 650115087..bc85eabf1 100644 --- a/gcs/src/redux/middleware/emitters.js +++ b/gcs/src/redux/middleware/emitters.js @@ -14,6 +14,7 @@ import { emitImportMissionFromFile, emitWriteCurrentMission, } from "../slices/missionSlice" +import { emitRebootAutopilot, emitRefreshParams } from "../slices/paramsSlice" export function handleEmitters(socket, store, action) { if (!socket) return @@ -98,6 +99,20 @@ export function handleEmitters(socket, store, action) { }) }, }, + + /* + ========== + = PARAMS = + ========== + */ + { + emitter: emitRebootAutopilot, + callback: () => socket.socket.emit("reboot_autopilot"), + }, + { + emitter: emitRefreshParams, + callback: () => socket.socket.emit("refresh_params"), + }, ] for (const { emitter, callback } of emitHandlers) { diff --git a/gcs/src/redux/middleware/socketMiddleware.js b/gcs/src/redux/middleware/socketMiddleware.js index a0d94a0e9..5c6d4a6e1 100644 --- a/gcs/src/redux/middleware/socketMiddleware.js +++ b/gcs/src/redux/middleware/socketMiddleware.js @@ -55,10 +55,21 @@ import { import { queueErrorNotification, queueNotification, + queueSuccessNotification, } from "../slices/notificationSlice" import { pushMessage } from "../slices/statusTextSlice.js" import { handleEmitters } from "./emitters.js" import { dataFormatters } from "../../helpers/dataFormatters.js" +import { + setAutoPilotRebootModalOpen, + setFetchingVars, + setFetchingVarsProgress, + setModifiedParams, + setParams, + setParamSearchValue, + setRebootData, + setShownParams, +} from "../slices/paramsSlice.js" const SocketEvents = Object.freeze({ // socket.on events @@ -77,7 +88,14 @@ const DroneSpecificSocketEvents = Object.freeze({ onNavResult: "nav_result", onHomePositionResult: "home_position_result", onIncomingMsg: "incoming_msg", +}) + +const ParamSpecificSocketEvents = Object.freeze({ onRebootAutopilot: "reboot_autopilot", + onParamsMessage: "params", + onParamRequestUpdate: "param_request_update", + onParamSetSuccess: "param_set_success", + onParamError: "params_error", }) const MissionSpecificSocketEvents = Object.freeze({ @@ -283,11 +301,40 @@ const socketMiddleware = (store) => { }, ) - socket.socket.on(DroneSpecificSocketEvents.onRebootAutopilot, (msg) => { - // setRebootData(msg) - // if (msg.success) { - // close() - // } + socket.socket.on(ParamSpecificSocketEvents.onRebootAutopilot, (msg) => { + store.dispatch(setRebootData(msg)) + if (msg.success) { + store.dispatch(setAutoPilotRebootModalOpen(false)) + } + }) + + socket.socket.on(ParamSpecificSocketEvents.onParamsMessage, (msg) => { + store.dispatch(setParams(msg)) + store.dispatch(setShownParams(msg)) + store.dispatch(setFetchingVars(false)) + store.dispatch(setFetchingVarsProgress(0)) + store.dispatch(setParamSearchValue("")) + }) + + socket.socket.on( + ParamSpecificSocketEvents.onParamRequestUpdate, + (msg) => { + store.dispatch( + setFetchingVarsProgress( + (msg.current_param_index / msg.total_number_of_params) * 100, + ), + ) + }, + ) + + socket.socket.on(ParamSpecificSocketEvents.onParamSetSuccess, (msg) => { + store.dispatch(queueSuccessNotification(msg.message)) + store.dispatch(setModifiedParams([])) + }) + + socket.socket.on(ParamSpecificSocketEvents.onParamError, (msg) => { + store.dispatch(queueErrorNotification(msg.message)) + store.dispatch(setFetchingVars(false)) }) /* @@ -540,10 +587,13 @@ const socketMiddleware = (store) => { ) }) } else { + // Turn off socket events Object.values(DroneSpecificSocketEvents).map((event) => socket.socket.off(event), ) - + Object.values(ParamSpecificSocketEvents).map((event) => + socket.socket.off(event), + ) Object.values(MissionSpecificSocketEvents).map((event) => socket.socket.off(event), ) From fa872794587eaf358d060c2cb648242306ea68ca Mon Sep 17 00:00:00 2001 From: Julian Jones Date: Sun, 7 Sep 2025 22:01:40 +0100 Subject: [PATCH 05/21] moved more components to redux (i love redux this is fun) --- .../params/autopilotRebootModal.jsx | 12 ++++- gcs/src/components/params/paramsToolbar.jsx | 49 ++++++++++------- gcs/src/params.jsx | 52 ++----------------- gcs/src/redux/middleware/emitters.js | 6 ++- 4 files changed, 50 insertions(+), 69 deletions(-) diff --git a/gcs/src/components/params/autopilotRebootModal.jsx b/gcs/src/components/params/autopilotRebootModal.jsx index eb3ab4d77..7824f45aa 100644 --- a/gcs/src/components/params/autopilotRebootModal.jsx +++ b/gcs/src/components/params/autopilotRebootModal.jsx @@ -10,11 +10,19 @@ import tailwindConfig from "../../../tailwind.config.js" import resolveConfig from "tailwindcss/resolveConfig" const tailwindColors = resolveConfig(tailwindConfig).theme.colors -export default function AutopilotRebootModal({ rebootData, opened, onClose }) { +// Redux +import { useDispatch, useSelector } from "react-redux" +import { selectAutoPilotRebootModalOpen, selectRebootData, setAutoPilotRebootModalOpen } from "../../redux/slices/paramsSlice.js" + +export default function AutopilotRebootModal() { + const dispatch = useDispatch() + const rebootData = useSelector(selectRebootData) + const opened = useSelector(selectAutoPilotRebootModalOpen) + return ( dispatch(setAutoPilotRebootModalOpen(false))} title="Rebooting autopilot" closeOnClickOutside={false} closeOnEscape={false} diff --git a/gcs/src/components/params/paramsToolbar.jsx b/gcs/src/components/params/paramsToolbar.jsx index 191d1714b..4093faa18 100644 --- a/gcs/src/components/params/paramsToolbar.jsx +++ b/gcs/src/components/params/paramsToolbar.jsx @@ -24,28 +24,41 @@ import resolveConfig from "tailwindcss/resolveConfig" const tailwindColors = resolveConfig(tailwindConfig).theme.colors // Redux -import { useSelector } from "react-redux" +import { useDispatch, useSelector } from "react-redux" import { + emitRebootAutopilot, + emitRefreshParams, + emitSetMultipleParams, + resetParamState, selectModifiedParams, + selectParamSearchValue, selectShowModifiedParams, + setAutoPilotRebootModalOpen, + setFetchingVars, + setParams, + setParamSearchValue, + setShownParams, + toggleShowModifiedParams, } from "../../redux/slices/paramsSlice.js" -export default function ParamsToolbar({ - searchValue, - refreshCallback, - rebootCallback, - modifiedCallback, - searchCallback, -}) { - /** - * Sets all the modified parameters to their new values on the drone - */ - function saveModifiedParams() { - socket.emit("set_multiple_params", modifiedParams) - } - +export default function ParamsToolbar() { + const dispatch = useDispatch() const modifiedParams = useSelector(selectModifiedParams) const showModifiedParams = useSelector(selectShowModifiedParams) + const searchValue = useSelector(selectParamSearchValue) + + function refreshCallback() { + dispatch(setParams([])) + dispatch(setShownParams([])) + dispatch(emitRefreshParams()) + dispatch(setFetchingVars(true)) + } + + function rebootCallback() { + dispatch(emitRebootAutopilot()) + dispatch(setAutoPilotRebootModalOpen(true)) + dispatch(resetParamState()) + } return (
@@ -55,7 +68,7 @@ export default function ParamsToolbar({ >
} > - } + )}
{ className="w-3/12" /> -
{paramDef?.Description} diff --git a/gcs/src/components/params/valueInput.jsx b/gcs/src/components/params/valueInput.jsx index 77518aa2a..ee5bed784 100644 --- a/gcs/src/components/params/valueInput.jsx +++ b/gcs/src/components/params/valueInput.jsx @@ -6,7 +6,7 @@ */ // 3rd party imports -import { NumberInput, Select, Tooltip } from "@mantine/core" +import { NumberInput, Select } from "@mantine/core" // Custom components, helpers and data import BitmaskSelect from "./bitmaskSelect" @@ -14,18 +14,17 @@ import BitmaskSelect from "./bitmaskSelect" // Redux import { useSelector } from "react-redux" import { selectShownParams } from "../../redux/slices/paramsSlice" -import { IconInfoCircle } from "@tabler/icons-react" const PARAM_INPUT_ENUM = { Number: 0, Select: 1, - BitMask: 2 + BitMask: 2, } export default function ValueInput({ index, paramDef, onChange, className }) { const shownParams = useSelector(selectShownParams) const param = shownParams[index] - + // Select param enum type let paramInputType = PARAM_INPUT_ENUM.Number if (paramDef?.Values && !paramDef?.Range) { From 76a84ca17cc5a9e6eaad62b161b30e0e7aca6a1c Mon Sep 17 00:00:00 2001 From: Julian Jones Date: Sun, 14 Sep 2025 15:01:01 +0100 Subject: [PATCH 15/21] cleaned up --- gcs/src/components/params/rowItem.jsx | 4 ++-- gcs/src/components/params/valueInput.jsx | 18 ++---------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/gcs/src/components/params/rowItem.jsx b/gcs/src/components/params/rowItem.jsx index 868f11f3b..337bdd0b8 100644 --- a/gcs/src/components/params/rowItem.jsx +++ b/gcs/src/components/params/rowItem.jsx @@ -45,11 +45,11 @@ const RowItem = memo(({ index, style, onChange }) => { className="self-center" label={
- {Object.keys(paramDef?.Values).map((key, index) => { + {Object.keys(paramDef?.Values).map((key) => { return (

{key}:{" "} - {paramDef?.Values[Object.keys(paramDef?.Values)[index]]} + {paramDef?.Values[key]}

) })} diff --git a/gcs/src/components/params/valueInput.jsx b/gcs/src/components/params/valueInput.jsx index ee5bed784..4943c046d 100644 --- a/gcs/src/components/params/valueInput.jsx +++ b/gcs/src/components/params/valueInput.jsx @@ -15,24 +15,10 @@ import BitmaskSelect from "./bitmaskSelect" import { useSelector } from "react-redux" import { selectShownParams } from "../../redux/slices/paramsSlice" -const PARAM_INPUT_ENUM = { - Number: 0, - Select: 1, - BitMask: 2, -} - export default function ValueInput({ index, paramDef, onChange, className }) { const shownParams = useSelector(selectShownParams) const param = shownParams[index] - // Select param enum type - let paramInputType = PARAM_INPUT_ENUM.Number - if (paramDef?.Values && !paramDef?.Range) { - paramInputType = PARAM_INPUT_ENUM.Select - } else if (paramDef?.Range && !paramDef?.Values) { - paramInputType = PARAM_INPUT_ENUM.BitMask - } - // Try to handle floats because mantine handles keys internally as strings // Which leads to floating point rounding errors function sanitiseInput(value, toString = false) { @@ -56,7 +42,7 @@ export default function ValueInput({ index, paramDef, onChange, className }) { return value } - if (paramInputType == PARAM_INPUT_ENUM.Select) { + if (paramDef?.Values && !paramDef?.Range) { return ( onChange(sanitiseInput(value), param)} + onChange={(value) => addToModifiedParams(sanitiseInput(value), param)} data={Object.keys(paramDef?.Values).map((key) => ({ value: `${key}`, label: `${key}: ${paramDef?.Values[key]}`, @@ -62,7 +103,7 @@ export default function ValueInput({ index, paramDef, onChange, className }) { @@ -79,7 +120,7 @@ export default function ValueInput({ index, paramDef, onChange, className }) { : "" } value={param.param_value} - onChange={(value) => onChange(value, param)} + onChange={(value) => addToModifiedParams(value, param)} decimalScale={5} hideControls min={paramDef?.Range ? paramDef?.Range.low : null} diff --git a/gcs/src/params.jsx b/gcs/src/params.jsx index 29b13acc7..6c2578025 100644 --- a/gcs/src/params.jsx +++ b/gcs/src/params.jsx @@ -24,7 +24,6 @@ import { Row } from "./components/params/row.jsx" import { useDispatch, useSelector } from "react-redux" import { selectConnectedToDrone } from "./redux/slices/droneConnectionSlice.js" import { - appendModifiedParams, resetParamState, selectFetchingVars, selectFetchingVarsProgress, @@ -35,8 +34,6 @@ import { selectShownParams, setFetchingVars, setShownParams, - updateModifiedParamValue, - updateParamValue, } from "./redux/slices/paramsSlice.js" export default function Params() { @@ -57,38 +54,6 @@ export default function Params() { const fetchingVars = useSelector(selectFetchingVars) const fetchingVarsProgress = useSelector(selectFetchingVarsProgress) - // Checks if a parameter has been modified since the last save - function isModified(param) { - return modifiedParams.find((obj) => { - return obj.param_id === param.param_id - }) - } - - // Adds a parameter to the list of parameters that have been modified since the last save - function addToModifiedParams(value, param) { - if (value === "") return - - if (isModified(param)) { - dispatch( - updateModifiedParamValue({ - param_id: param.param_id, - param_value: value, - }), - ) - } else { - // Otherwise add it to modified params - dispatch( - appendModifiedParams({ - param_id: param.param_id, - param_value: value, - param_type: param.param_type, - }), - ) - } - - dispatch(updateParamValue({ param_id: param.param_id, param_value: value })) - } - // Reset state if we loose connection useEffect(() => { if (!connected) { @@ -140,9 +105,6 @@ export default function Params() { width={width} itemSize={120} itemCount={shownParams.length} - itemData={{ - onChange: addToModifiedParams, - }} > {Row} diff --git a/gcs/src/redux/slices/paramsSlice.js b/gcs/src/redux/slices/paramsSlice.js index fdd95531c..0a03e00ae 100644 --- a/gcs/src/redux/slices/paramsSlice.js +++ b/gcs/src/redux/slices/paramsSlice.js @@ -52,6 +52,12 @@ const paramsSlice = createSlice({ }, appendModifiedParams: (state, action) => { state.modifiedParams = state.modifiedParams.concat(action.payload) + + // Delete where initial_value and param_value are the same, this is the case when someone deletes the input and puts it in again + // as the same - very niche case but can happen + state.modifiedParams = state.modifiedParams.filter( + (item) => item.initial_value !== item.param_value, + ) }, updateParamValue: (state, action) => { state.params = state.params.map((item) => @@ -61,11 +67,27 @@ const paramsSlice = createSlice({ ) }, updateModifiedParamValue: (state, action) => { + // Update param_value state.modifiedParams = state.modifiedParams.map((item) => item.param_id === action.payload.param_id ? { ...item, param_value: action.payload.param_value } : item, ) + + // Delete where initial_value and param_value are the same + state.modifiedParams = state.modifiedParams.filter( + (item) => item.initial_value !== item.param_value, + ) + }, + deleteModifiedParam: (state, action) => { + state.modifiedParams = state.modifiedParams.filter( + (item) => item.param_id !== action.payload.param_id, + ) + state.params = state.params.map((item) => + item.param_id === action.payload.param_id + ? { ...item, param_value: action.payload.initial_value } + : item, + ) }, resetParamState: (state) => { state.fetchingVars = false @@ -108,6 +130,7 @@ export const { appendModifiedParams, updateParamValue, updateModifiedParamValue, + deleteModifiedParam, resetParamState, emitRebootAutopilot, emitRefreshParams, From ab93049b900655ac63dd21bba65fcdd99abe602c Mon Sep 17 00:00:00 2001 From: Kush Makkapati Date: Sun, 14 Sep 2025 19:21:56 +0100 Subject: [PATCH 18/21] Fix some bugs with params --- gcs/src/components/params/paramsToolbar.jsx | 2 +- gcs/src/components/params/rowItem.jsx | 6 +++--- gcs/src/components/params/valueInput.jsx | 2 +- gcs/src/redux/middleware/socketMiddleware.js | 5 +++-- gcs/src/redux/slices/paramsSlice.js | 1 - radio/app/controllers/paramsController.py | 9 +++++---- 6 files changed, 13 insertions(+), 12 deletions(-) diff --git a/gcs/src/components/params/paramsToolbar.jsx b/gcs/src/components/params/paramsToolbar.jsx index 014d5e875..b2384350d 100644 --- a/gcs/src/components/params/paramsToolbar.jsx +++ b/gcs/src/components/params/paramsToolbar.jsx @@ -16,8 +16,8 @@ import { } from "@tabler/icons-react" // Styling imports -import tailwindConfig from "../../../tailwind.config.js" import resolveConfig from "tailwindcss/resolveConfig" +import tailwindConfig from "../../../tailwind.config.js" const tailwindColors = resolveConfig(tailwindConfig).theme.colors // Redux diff --git a/gcs/src/components/params/rowItem.jsx b/gcs/src/components/params/rowItem.jsx index 845c81aab..5f71013de 100644 --- a/gcs/src/components/params/rowItem.jsx +++ b/gcs/src/components/params/rowItem.jsx @@ -14,6 +14,7 @@ import apmParamDefsPlane from "../../../data/gen_apm_params_def_plane.json" import ValueInput from "./valueInput" // Redux +import { IconArrowBack, IconInfoCircle } from "@tabler/icons-react" import { useDispatch, useSelector } from "react-redux" import { selectAircraftType } from "../../redux/slices/droneInfoSlice" import { @@ -21,7 +22,6 @@ import { selectModifiedParams, selectShownParams, } from "../../redux/slices/paramsSlice" -import { IconArrowBack, IconInfoCircle } from "@tabler/icons-react" const RowItem = memo(({ index, style }) => { const dispatch = useDispatch() @@ -57,7 +57,7 @@ const RowItem = memo(({ index, style }) => { return (
- +

{param.param_id}

@@ -85,7 +85,7 @@ const RowItem = memo(({ index, style }) => { {hasBeenModified && ( item.param_id == param.param_id).initial_value}`} + label={`Reset to previous value of ${modifiedParams.find((item) => item.param_id == param.param_id).initial_value}`} > { }) socket.socket.on(ParamSpecificSocketEvents.onParamsMessage, (msg) => { + console.log(msg) store.dispatch(setParams(msg)) store.dispatch(setShownParams(msg)) store.dispatch(setFetchingVars(false)) diff --git a/gcs/src/redux/slices/paramsSlice.js b/gcs/src/redux/slices/paramsSlice.js index 0a03e00ae..097d23b53 100644 --- a/gcs/src/redux/slices/paramsSlice.js +++ b/gcs/src/redux/slices/paramsSlice.js @@ -24,7 +24,6 @@ const paramsSlice = createSlice({ }, setParams: (state, action) => { if (action.payload === state.params) return - if (state.params.length > 0 && action.payload.length > 0) return // Ignore unless the params have been reset or we are reseting to [] state.params = action.payload }, setShownParams: (state, action) => { diff --git a/radio/app/controllers/paramsController.py b/radio/app/controllers/paramsController.py index 2358d63d4..bf466525c 100644 --- a/radio/app/controllers/paramsController.py +++ b/radio/app/controllers/paramsController.py @@ -148,10 +148,11 @@ def setMultipleParams(self, params_list: list[IncomingParam]) -> bool: return False for param in params_list: - param_id = param.get("param_id") - param_value = param.get("param_value") - param_type = param.get("param_type") - if not param_id or not param_value or not param_type: + param_id = param.get("param_id", None) + param_value = param.get("param_value", None) + param_type = param.get("param_type", None) + if param_id is None or param_value is None or param_type is None: + self.drone.logger.error(f"Invalid parameter data: {param}, skipping") continue done = self.setParam(param_id, param_value, param_type) From 20a6b9aa5094680fb90ce88a91724d0f0e9ff373 Mon Sep 17 00:00:00 2001 From: Kush Makkapati Date: Sun, 14 Sep 2025 19:26:40 +0100 Subject: [PATCH 19/21] Address copilot review comments --- gcs/src/components/params/rowItem.jsx | 6 +++--- gcs/src/redux/middleware/socketMiddleware.js | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/gcs/src/components/params/rowItem.jsx b/gcs/src/components/params/rowItem.jsx index 5f71013de..a4eb5a005 100644 --- a/gcs/src/components/params/rowItem.jsx +++ b/gcs/src/components/params/rowItem.jsx @@ -31,12 +31,12 @@ const RowItem = memo(({ index, style }) => { const [paramDef, setParamDef] = useState({}) const param = shownParams[index] const hasBeenModified = modifiedParams.find( - (item) => item.param_id == param.param_id, + (item) => item.param_id === param.param_id, ) function removeModified(param) { let initial_value = modifiedParams.find( - (item) => item.param_id == param.param_id, + (item) => item.param_id === param.param_id, ).initial_value dispatch( deleteModifiedParam({ @@ -85,7 +85,7 @@ const RowItem = memo(({ index, style }) => { {hasBeenModified && ( item.param_id == param.param_id).initial_value}`} + label={`Reset to previous value of ${modifiedParams.find((item) => item.param_id === param.param_id).initial_value}`} > { }) socket.socket.on(ParamSpecificSocketEvents.onParamsMessage, (msg) => { - console.log(msg) store.dispatch(setParams(msg)) store.dispatch(setShownParams(msg)) store.dispatch(setFetchingVars(false)) From e636a776cefbb6c01ea470d83efb8eb0e9d4a445 Mon Sep 17 00:00:00 2001 From: Kush Makkapati Date: Sun, 14 Sep 2025 21:05:53 +0100 Subject: [PATCH 20/21] Fix param refresh bug --- gcs/src/components/config/flightModes.jsx | 2 +- gcs/src/components/params/rowItem.jsx | 6 +++++- gcs/src/components/params/valueInput.jsx | 19 +++++++++++------- gcs/src/redux/middleware/socketMiddleware.js | 5 +++++ gcs/src/redux/slices/paramsSlice.js | 14 ++++++++----- radio/app/controllers/paramsController.py | 21 ++++++++++++++++---- radio/app/endpoints/params.py | 10 ++++------ 7 files changed, 53 insertions(+), 24 deletions(-) diff --git a/gcs/src/components/config/flightModes.jsx b/gcs/src/components/config/flightModes.jsx index 798bc7fbe..80092ea08 100644 --- a/gcs/src/components/config/flightModes.jsx +++ b/gcs/src/components/config/flightModes.jsx @@ -178,7 +178,7 @@ export default function FlightModes() { {flightModes.map((flightModeNumber, idx) => ( addToModifiedParams(sanitiseInput(value), param)} data={Object.keys(paramDef?.Values).map((key) => ({ value: `${key}`, @@ -102,7 +107,7 @@ export default function ValueInput({ index, paramDef, className }) { return ( addToModifiedParams(value, param)} decimalScale={5} hideControls diff --git a/gcs/src/redux/middleware/socketMiddleware.js b/gcs/src/redux/middleware/socketMiddleware.js index 3a34d804d..5f57ec1e5 100644 --- a/gcs/src/redux/middleware/socketMiddleware.js +++ b/gcs/src/redux/middleware/socketMiddleware.js @@ -71,6 +71,7 @@ import { setParamSearchValue, setRebootData, setShownParams, + updateParamValue, } from "../slices/paramsSlice.js" import { pushMessage } from "../slices/statusTextSlice.js" import { handleEmitters } from "./emitters.js" @@ -345,6 +346,10 @@ const socketMiddleware = (store) => { socket.socket.on(ParamSpecificSocketEvents.onParamSetSuccess, (msg) => { store.dispatch(queueSuccessNotification(msg.message)) store.dispatch(setModifiedParams([])) + // Update the param in the params list also + for (let param of msg.data) { + store.dispatch(updateParamValue(param)) + } }) socket.socket.on(ParamSpecificSocketEvents.onParamError, (msg) => { diff --git a/gcs/src/redux/slices/paramsSlice.js b/gcs/src/redux/slices/paramsSlice.js index 097d23b53..4798e50dd 100644 --- a/gcs/src/redux/slices/paramsSlice.js +++ b/gcs/src/redux/slices/paramsSlice.js @@ -82,11 +82,11 @@ const paramsSlice = createSlice({ state.modifiedParams = state.modifiedParams.filter( (item) => item.param_id !== action.payload.param_id, ) - state.params = state.params.map((item) => - item.param_id === action.payload.param_id - ? { ...item, param_value: action.payload.initial_value } - : item, - ) + // state.params = state.params.map((item) => + // item.param_id === action.payload.param_id + // ? { ...item, param_value: action.payload.initial_value } + // : item, + // ) }, resetParamState: (state) => { state.fetchingVars = false @@ -107,6 +107,9 @@ const paramsSlice = createSlice({ selectRebootData: (state) => state.rebootData, selectAutoPilotRebootModalOpen: (state) => state.autoPilotRebootModalOpen, selectParams: (state) => state.params, + selectSingleParam(state, param_id) { + return state.params.find((param) => param.param_id === param_id) + }, selectShownParams: (state) => state.shownParams, selectModifiedParams: (state) => state.modifiedParams, selectShowModifiedParams: (state) => state.showModifiedParams, @@ -139,6 +142,7 @@ export const { selectRebootData, selectAutoPilotRebootModalOpen, selectParams, + selectSingleParam, selectShownParams, selectModifiedParams, selectFetchingVars, diff --git a/radio/app/controllers/paramsController.py b/radio/app/controllers/paramsController.py index bf466525c..621d8558a 100644 --- a/radio/app/controllers/paramsController.py +++ b/radio/app/controllers/paramsController.py @@ -134,7 +134,7 @@ def getAllParamsThreadFunc(self) -> None: self.drone.logger.error("Serial exception while getting all params") return - def setMultipleParams(self, params_list: list[IncomingParam]) -> bool: + def setMultipleParams(self, params_list: list[IncomingParam]) -> Response: """ Sets multiple parameters on the drone. @@ -145,21 +145,34 @@ def setMultipleParams(self, params_list: list[IncomingParam]) -> bool: bool: True if all parameters were set, False if any failed """ if not params_list: - return False + return {"success": False, "message": "No parameters to set"} + + params_set_successfully = [] for param in params_list: param_id = param.get("param_id", None) param_value = param.get("param_value", None) param_type = param.get("param_type", None) + del param["initial_value"] # Remove initial value if it exists + if param_id is None or param_value is None or param_type is None: self.drone.logger.error(f"Invalid parameter data: {param}, skipping") continue done = self.setParam(param_id, param_value, param_type) if not done: - return False + return { + "success": False, + "message": f"Failed to set parameter {param_id}", + } + else: + params_set_successfully.append(param) - return True + return { + "success": True, + "message": "All parameters set successfully", + "data": params_set_successfully, + } @sendingCommandLock def setParam( diff --git a/radio/app/endpoints/params.py b/radio/app/endpoints/params.py index 8eca26645..3032edb64 100644 --- a/radio/app/endpoints/params.py +++ b/radio/app/endpoints/params.py @@ -25,13 +25,11 @@ def set_multiple_params(params_list: List[Any]) -> None: if not droneStatus.drone: return - success = droneStatus.drone.paramsController.setMultipleParams(params_list) - if success: - socketio.emit( - "param_set_success", {"message": "Parameters saved successfully."} - ) + response = droneStatus.drone.paramsController.setMultipleParams(params_list) + if response.get("success"): + socketio.emit("param_set_success", response) else: - socketio.emit("params_error", {"message": "Failed to save parameters."}) + socketio.emit("params_error", response) @socketio.on("refresh_params") From cbafc2f43afaa898ace42140cfb97c187f690794 Mon Sep 17 00:00:00 2001 From: Kush Makkapati Date: Sun, 14 Sep 2025 21:23:16 +0100 Subject: [PATCH 21/21] Fix mypy issue, address copilot review comments, fix params tests --- .../params/autopilotRebootModal.jsx | 4 +- gcs/src/components/params/valueInput.jsx | 2 - gcs/src/redux/slices/paramsSlice.js | 5 - radio/app/controllers/paramsController.py | 2 +- radio/app/customTypes.py | 1 + radio/tests/test_params.py | 177 ++++++++++-------- 6 files changed, 102 insertions(+), 89 deletions(-) diff --git a/gcs/src/components/params/autopilotRebootModal.jsx b/gcs/src/components/params/autopilotRebootModal.jsx index 807d3a2f4..4b3c075a2 100644 --- a/gcs/src/components/params/autopilotRebootModal.jsx +++ b/gcs/src/components/params/autopilotRebootModal.jsx @@ -6,8 +6,8 @@ import { Button, Loader, Modal } from "@mantine/core" // Styling imports -import tailwindConfig from "../../../tailwind.config.js" import resolveConfig from "tailwindcss/resolveConfig" +import tailwindConfig from "../../../tailwind.config.js" const tailwindColors = resolveConfig(tailwindConfig).theme.colors // Redux @@ -48,7 +48,7 @@ export default function AutopilotRebootModal() { {rebootData.message} You will need to reconnect.