From bc5ec790eb2b094ae928cfc6850d470b12353de3 Mon Sep 17 00:00:00 2001 From: Kush Makkapati Date: Sun, 21 Sep 2025 16:30:56 +0100 Subject: [PATCH 1/4] Fix duplicate mission fetching, show loading notification when fetching mission on dashboard --- gcs/src/components/layout.jsx | 10 ++++- gcs/src/helpers/notification.js | 25 +++++++++++ gcs/src/redux/middleware/emitters.js | 8 +++- gcs/src/redux/middleware/socketMiddleware.js | 18 +++----- gcs/src/redux/slices/missionSlice.js | 45 ++++++++++++++++++++ radio/app/controllers/missionController.py | 12 ++---- 6 files changed, 94 insertions(+), 24 deletions(-) diff --git a/gcs/src/components/layout.jsx b/gcs/src/components/layout.jsx index 860fb8d34..4ef8f54e4 100644 --- a/gcs/src/components/layout.jsx +++ b/gcs/src/components/layout.jsx @@ -27,6 +27,7 @@ import { selectConnectedToDrone, } from "../redux/slices/droneConnectionSlice" import { selectAircraftTypeString } from "../redux/slices/droneInfoSlice" +import { selectShouldFetchAllMissionsOnDashboard } from "../redux/slices/missionSlice" import { notificationShown, selectNotificationQueue, @@ -37,6 +38,9 @@ export default function Layout({ children, currentPage }) { const connectedToDrone = useSelector(selectConnectedToDrone) const notificationQueue = useSelector(selectNotificationQueue) const aircraftTypeString = useSelector(selectAircraftTypeString) + const shouldFetchAllMissionsOnDashboard = useSelector( + selectShouldFetchAllMissionsOnDashboard, + ) // Change current page, there's a single comma because javascript has weird syntax // we don't care about the first variable. @@ -79,8 +83,12 @@ export default function Layout({ children, currentPage }) { dispatch(emitSetState({ state: currentPage })) if (currentPage.toLowerCase() == "dashboard") { - dispatch(emitGetCurrentMissionAll()) dispatch(emitGetHomePosition()) // use actual home position + + if (shouldFetchAllMissionsOnDashboard) { + dispatch(emitGetCurrentMissionAll()) + } + if (aircraftTypeString === "Plane") { dispatch(emitGetLoiterRadius()) } diff --git a/gcs/src/helpers/notification.js b/gcs/src/helpers/notification.js index 05192bfae..17722819b 100644 --- a/gcs/src/helpers/notification.js +++ b/gcs/src/helpers/notification.js @@ -53,3 +53,28 @@ export function showNotification(title, message) { ...notificationTheme, }) } + +export function showLoadingNotification(title, message) { + const id = notifications.show({ + title: title, + message: message, + loading: true, + autoClose: false, + withCloseButton: false, + ...notificationTheme, + }) + + return id +} + +export function closeLoadingNotification(id, title, message) { + notifications.update({ + id: id, + title: title, + message: message, + loading: false, + autoClose: 2000, + color: tailwindColors.green[600], + ...notificationTheme, + }) +} diff --git a/gcs/src/redux/middleware/emitters.js b/gcs/src/redux/middleware/emitters.js index 5a7df3ee2..2ea975921 100644 --- a/gcs/src/redux/middleware/emitters.js +++ b/gcs/src/redux/middleware/emitters.js @@ -15,6 +15,8 @@ import { emitGetTargetInfo, emitImportMissionFromFile, emitWriteCurrentMission, + setShouldFetchAllMissionsOnDashboard, + showDashboardMissionFetchingNotificationThunk, } from "../slices/missionSlice" import { emitRebootAutopilot, @@ -59,7 +61,10 @@ export function handleEmitters(socket, store, action) { }, { emitter: emitGetCurrentMissionAll, - callback: () => socket.socket.emit("get_current_mission_all"), + callback: () => { + socket.socket.emit("get_current_mission_all") + store.dispatch(showDashboardMissionFetchingNotificationThunk()) + }, }, { emitter: emitGetLoiterRadius, @@ -91,6 +96,7 @@ export function handleEmitters(socket, store, action) { type: action.payload.type, items: action.payload.items, }) + store.dispatch(setShouldFetchAllMissionsOnDashboard(true)) }, }, { diff --git a/gcs/src/redux/middleware/socketMiddleware.js b/gcs/src/redux/middleware/socketMiddleware.js index 6061ca0f6..4943d61ad 100644 --- a/gcs/src/redux/middleware/socketMiddleware.js +++ b/gcs/src/redux/middleware/socketMiddleware.js @@ -10,10 +10,7 @@ import { // drone actions import { emitGetComPorts, - emitGetHomePosition, - emitGetLoiterRadius, emitIsConnectedToDrone, - emitSetState, setComPorts, setConnected, setConnecting, @@ -46,6 +43,7 @@ import { } from "../slices/droneInfoSlice" import { addIdToItem, + closeDashboardMissionFetchingNotificationThunk, setCurrentMission, setCurrentMissionItems, setDrawingFenceItems, @@ -53,6 +51,7 @@ import { setDrawingRallyItems, setMissionProgressData, setMissionProgressModal, + setShouldFetchAllMissionsOnDashboard, setTargetInfo, setUnwrittenChanges, setUpdatePlannedHomePositionFromLoadData, @@ -263,19 +262,10 @@ const socketMiddleware = (store) => { store.dispatch(setConnecting(false)) store.dispatch(setConnectionModal(false)) - const currentState = store.getState().droneConnection - store.dispatch(emitSetState(currentState)) - - if (["dashboard", "missions"].includes(currentState.state)) { - store.dispatch(emitGetHomePosition()) // fetch the actual home position of the drone - if (msg.aircraft_type === 1) { - store.dispatch(emitGetLoiterRadius()) - } - } - store.dispatch(setGuidedModePinData({ lat: 0, lon: 0, alt: 0 })) store.dispatch(setRebootData({})) store.dispatch(setAutoPilotRebootModalOpen(false)) + store.dispatch(setShouldFetchAllMissionsOnDashboard(true)) }) // Link stats @@ -410,6 +400,8 @@ const socketMiddleware = (store) => { MissionSpecificSocketEvents.onCurrentMissionAll, (msg) => { store.dispatch(setCurrentMissionItems(msg)) + store.dispatch(setShouldFetchAllMissionsOnDashboard(false)) + store.dispatch(closeDashboardMissionFetchingNotificationThunk()) }, ) diff --git a/gcs/src/redux/slices/missionSlice.js b/gcs/src/redux/slices/missionSlice.js index 1973582fe..72babf660 100644 --- a/gcs/src/redux/slices/missionSlice.js +++ b/gcs/src/redux/slices/missionSlice.js @@ -3,6 +3,10 @@ import { v4 as uuidv4 } from "uuid" import { coordToInt } from "../../helpers/dataFormatters" import { isGlobalFrameHomeCommand } from "../../helpers/filterMissions" import { MAV_FRAME_LIST } from "../../helpers/mavlinkConstants" +import { + closeLoadingNotification, + showLoadingNotification, +} from "../../helpers/notification" const missionInfoSlice = createSlice({ name: "missionInfo", @@ -61,6 +65,8 @@ const missionInfoSlice = createSlice({ gpsCoords: { lat: 0, lng: 0 }, markerId: null, }, + shouldFetchAllMissionsOnDashboard: true, // bool so that the dashboard can refresh its data when switched to if needed + dashboardMissionFetchingNotificationId: null, }, reducers: { setCurrentMission: (state, action) => { @@ -394,6 +400,15 @@ const missionInfoSlice = createSlice({ position: { x: x, y: y }, } }, + setShouldFetchAllMissionsOnDashboard: (state, action) => { + if (action.payload === state.shouldFetchAllMissionsOnDashboard) return + state.shouldFetchAllMissionsOnDashboard = action.payload + }, + setDashboardMissionFetchingNotificationId: (state, action) => { + if (action.payload === state.dashboardMissionFetchingNotificationId) + return + state.dashboardMissionFetchingNotificationId = action.payload + }, // Emits emitGetTargetInfo: () => {}, @@ -421,6 +436,10 @@ const missionInfoSlice = createSlice({ selectMissionProgressData: (state) => state.missionProgressData, selectActiveTab: (state) => state.activeTab, selectContextMenu: (state) => state.contextMenu, + selectShouldFetchAllMissionsOnDashboard: (state) => + state.shouldFetchAllMissionsOnDashboard, + selectDashboardMissionFetchingNotificationId: (state) => + state.dashboardMissionFetchingNotificationId, }, }) @@ -490,6 +509,28 @@ export const setPlannedHomePositionToDronesHomePositionThunk = } } +export const showDashboardMissionFetchingNotificationThunk = + () => (dispatch) => { + const notificationId = showLoadingNotification( + "Fetching the mission", + "Fetching the mission from the drone", + ) + dispatch(setDashboardMissionFetchingNotificationId(notificationId)) + } + +export const closeDashboardMissionFetchingNotificationThunk = + () => (dispatch, getState) => { + const { dashboardMissionFetchingNotificationId } = getState().missionInfo + if (dashboardMissionFetchingNotificationId) { + closeLoadingNotification( + dashboardMissionFetchingNotificationId, + "Mission fetched", + "Successfully fetched the mission from the drone", + ) + } + dispatch(setDashboardMissionFetchingNotificationId(null)) + } + export const getFrameKey = (frame) => parseInt( Object.keys(MAV_FRAME_LIST).find((key) => MAV_FRAME_LIST[key] == frame), @@ -533,6 +574,8 @@ export const { selectMissionProgressData, selectActiveTab, selectContextMenu, + selectShouldFetchAllMissionsOnDashboard, + selectDashboardMissionFetchingNotificationId, } = missionInfoSlice.selectors export const { @@ -564,6 +607,8 @@ export const { emitWriteCurrentMission, emitImportMissionFromFile, emitExportMissionToFile, + setShouldFetchAllMissionsOnDashboard, + setDashboardMissionFetchingNotificationId, } = missionInfoSlice.actions export default missionInfoSlice diff --git a/radio/app/controllers/missionController.py b/radio/app/controllers/missionController.py index c6139aa3a..6509c7dae 100644 --- a/radio/app/controllers/missionController.py +++ b/radio/app/controllers/missionController.py @@ -135,21 +135,15 @@ def getCurrentMissionAll(self) -> Response: rally_items: List[Any] = [] _mission_items = self.getMissionItems(mission_type=TYPE_MISSION) - if not _mission_items.get("success"): - self.drone.logger.warning(_mission_items.get("message")) - else: + if _mission_items.get("success"): mission_items = _mission_items.get("data", []) _fence_items = self.getMissionItems(mission_type=TYPE_FENCE) - if not _fence_items.get("success"): - self.drone.logger.warning(_fence_items.get("message")) - else: + if _fence_items.get("success"): fence_items = _fence_items.get("data", []) _rally_items = self.getMissionItems(mission_type=TYPE_RALLY) - if not _rally_items.get("success"): - self.drone.logger.warning(_rally_items.get("message")) - else: + if _rally_items.get("success"): rally_items = _rally_items.get("data", []) return { From b4f2c21002e83aaf29ee4b1fa17a7edc356f4d07 Mon Sep 17 00:00:00 2001 From: Kush Makkapati Date: Sun, 21 Sep 2025 16:58:30 +0100 Subject: [PATCH 2/4] Display errors on getCurrentMissionAll --- gcs/src/components/dashboard/map.jsx | 8 ++++---- gcs/src/redux/middleware/socketMiddleware.js | 14 ++++++++++++-- gcs/src/redux/slices/missionSlice.js | 7 +++---- radio/app/controllers/missionController.py | 6 ++++++ radio/app/endpoints/mission.py | 5 +++++ 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/gcs/src/components/dashboard/map.jsx b/gcs/src/components/dashboard/map.jsx index db71919d6..19dda8c51 100644 --- a/gcs/src/components/dashboard/map.jsx +++ b/gcs/src/components/dashboard/map.jsx @@ -119,7 +119,7 @@ function MapSectionNonMemo({ passedRef, onDragstart, mapId = "dashboard" }) { }, [gpsData]) useEffect(() => { - setFilteredMissionItems(filterMissionItems(missionItems.mission_items)) + setFilteredMissionItems(filterMissionItems(missionItems.missionItems)) }, [missionItems]) useEffect(() => { @@ -245,12 +245,12 @@ function MapSectionNonMemo({ passedRef, onDragstart, mapId = "dashboard" }) { /> )} - + - + {/* Show mission rally point */} - {missionItems.rally_items.map((item, index) => { + {missionItems.rallyItems.map((item, index) => { return ( { socket.socket.on( MissionSpecificSocketEvents.onCurrentMissionAll, (msg) => { - store.dispatch(setCurrentMissionItems(msg)) - store.dispatch(setShouldFetchAllMissionsOnDashboard(false)) + if (!msg.success) { + store.dispatch(queueErrorNotification(msg.message)) + } else { + store.dispatch( + setCurrentMissionItems({ + missionItems: msg.mission_items, + fenceItems: msg.fence_items, + rallyItems: msg.rally_items, + }), + ) + store.dispatch(setShouldFetchAllMissionsOnDashboard(false)) + } store.dispatch(closeDashboardMissionFetchingNotificationThunk()) }, ) diff --git a/gcs/src/redux/slices/missionSlice.js b/gcs/src/redux/slices/missionSlice.js index 72babf660..0d81674ac 100644 --- a/gcs/src/redux/slices/missionSlice.js +++ b/gcs/src/redux/slices/missionSlice.js @@ -17,10 +17,9 @@ const missionInfoSlice = createSlice({ seq: 0, }, currentMissionItems: { - // These should be camelCase etc but its easier to keep it as snake_case - mission_items: [], - fence_items: [], - rally_items: [], + missionItems: [], + fenceItems: [], + rallyItems: [], }, drawingItems: { // This is for the missions page, used locally and then will update currentMissionItems on save diff --git a/radio/app/controllers/missionController.py b/radio/app/controllers/missionController.py index 6509c7dae..0e1528da0 100644 --- a/radio/app/controllers/missionController.py +++ b/radio/app/controllers/missionController.py @@ -137,14 +137,20 @@ def getCurrentMissionAll(self) -> Response: _mission_items = self.getMissionItems(mission_type=TYPE_MISSION) if _mission_items.get("success"): mission_items = _mission_items.get("data", []) + else: + return _mission_items _fence_items = self.getMissionItems(mission_type=TYPE_FENCE) if _fence_items.get("success"): fence_items = _fence_items.get("data", []) + else: + return _fence_items _rally_items = self.getMissionItems(mission_type=TYPE_RALLY) if _rally_items.get("success"): rally_items = _rally_items.get("data", []) + else: + return _rally_items return { "success": True, diff --git a/radio/app/endpoints/mission.py b/radio/app/endpoints/mission.py index 4949a70f2..ef549d099 100644 --- a/radio/app/endpoints/mission.py +++ b/radio/app/endpoints/mission.py @@ -107,6 +107,10 @@ def getCurrentMissionAll() -> None: result = droneStatus.drone.missionController.getCurrentMissionAll() + if not result.get("success"): + socketio.emit("current_mission_all", result) + return + data: dict[str, Any] = result.get("data", {}) if not isinstance(data, dict): data = {} @@ -114,6 +118,7 @@ def getCurrentMissionAll() -> None: socketio.emit( "current_mission_all", { + "success": True, "mission_items": data.get("mission_items", []), "fence_items": data.get("fence_items", []), "rally_items": data.get("rally_items", []), From 7b229be8d77f88789da569b1c08ce3c2ad76de43 Mon Sep 17 00:00:00 2001 From: Kush Makkapati Date: Sun, 21 Sep 2025 17:07:56 +0100 Subject: [PATCH 3/4] Fix test --- .../test_getCurrentMission_correctState_result.json | 1 + 1 file changed, 1 insertion(+) diff --git a/radio/tests/mission_test_files/test_getCurrentMission_correctState_result.json b/radio/tests/mission_test_files/test_getCurrentMission_correctState_result.json index 9395ccca4..d0ef6a991 100644 --- a/radio/tests/mission_test_files/test_getCurrentMission_correctState_result.json +++ b/radio/tests/mission_test_files/test_getCurrentMission_correctState_result.json @@ -1,4 +1,5 @@ { + "success": true, "mission_items": [ { "autocontinue": 1, From ecb66ab84b93f54e114dd888916a0555664b700b Mon Sep 17 00:00:00 2001 From: Kush Makkapati Date: Tue, 23 Sep 2025 18:47:49 +0100 Subject: [PATCH 4/4] Fix merge --- gcs/src/redux/middleware/socketMiddleware.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/gcs/src/redux/middleware/socketMiddleware.js b/gcs/src/redux/middleware/socketMiddleware.js index f601a59c2..90671471d 100644 --- a/gcs/src/redux/middleware/socketMiddleware.js +++ b/gcs/src/redux/middleware/socketMiddleware.js @@ -263,16 +263,6 @@ const socketMiddleware = (store) => { store.dispatch(setConnecting(false)) store.dispatch(setConnectionModal(false)) - const currentPage = store.getState().droneConnection.currentPage - store.dispatch(emitSetState(currentPage)) - - if (["dashboard", "missions"].includes(currentPage)) { - store.dispatch(emitGetHomePosition()) // fetch the actual home position of the drone - if (msg.aircraft_type === 1) { - store.dispatch(emitGetLoiterRadius()) - } - } - store.dispatch(setGuidedModePinData({ lat: 0, lon: 0, alt: 0 })) store.dispatch(setRebootData({})) store.dispatch(setAutoPilotRebootModalOpen(false))