Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions gcs/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,18 +477,15 @@ app.whenReady().then(() => {
// Load Messages on demand
ipcMain.handle("fla:get-messages", retrieveMessages)

// Save mission file
ipcMain.handle(
"missions:get-save-mission-file-path",
async (event, options) => {
const window = BrowserWindow.fromWebContents(event.sender)
if (!window) {
throw new Error("No active window found")
}
const result = await dialog.showSaveDialog(window, options)
return result
},
)
// Open native save dialog
ipcMain.handle("app:get-save-file-path", async (event, options) => {
const window = BrowserWindow.fromWebContents(event.sender)
if (!window) {
throw new Error("No active window found")
}
const result = await dialog.showSaveDialog(window, options)
return result
})

ipcMain.handle("app:get-node-env", () =>
app.isPackaged ? "production" : "development",
Expand Down
2 changes: 1 addition & 1 deletion gcs/electron/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const ALLOWED_INVOKE_CHANNELS = [
"fla:get-recent-logs",
"fla:clear-recent-logs",
"fla:get-messages",
"missions:get-save-mission-file-path",
"app:get-save-file-path",
"app:get-node-env",
"app:get-version",
"app:is-mac",
Expand Down
50 changes: 38 additions & 12 deletions gcs/src/components/params/paramsToolbar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ rebooting the autopilot
// 3rd party imports
import { Button, TextInput, Tooltip } from "@mantine/core"
import {
IconDownload,
IconEye,
IconPencil,
IconPower,
Expand All @@ -23,6 +24,7 @@ const tailwindColors = resolveConfig(tailwindConfig).theme.colors
// Redux
import { useDispatch, useSelector } from "react-redux"
import {
emitExportParamsToFile,
emitRebootAutopilot,
emitRefreshParams,
emitSetMultipleParams,
Expand Down Expand Up @@ -59,6 +61,29 @@ export default function ParamsToolbar() {
dispatch(resetParamState())
}

async function saveParamsToFile() {
const options = {
title: "Save parameters to a file",
filters: [
{ name: "Param File", extensions: ["param"] },
{ name: "All Files", extensions: ["*"] },
],
}

const result = await window.ipcRenderer.invoke(
"app:get-save-file-path",
options,
)

if (!result.canceled) {
dispatch(
emitExportParamsToFile({
filePath: result.filePath,
}),
)
}
}

return (
<div className="flex justify-center space-x-4">
<Tooltip
Expand All @@ -70,12 +95,7 @@ export default function ParamsToolbar() {
onClick={() => dispatch(toggleShowModifiedParams())}
color={tailwindColors.orange[600]}
>
{" "}
{showModifiedParams ? (
<IconEye size={14} />
) : (
<IconTool size={14} />
)}{" "}
{showModifiedParams ? <IconEye size={14} /> : <IconTool size={14} />}
</Button>
</Tooltip>

Expand All @@ -95,8 +115,7 @@ export default function ParamsToolbar() {
onClick={() => dispatch(emitSetMultipleParams(modifiedParams))}
color={tailwindColors.green[600]}
>
{" "}
Save params{" "}
Write params
</Button>

<Button
Expand All @@ -105,8 +124,7 @@ export default function ParamsToolbar() {
onClick={refreshCallback}
color={tailwindColors.blue[600]}
>
{" "}
Refresh params{" "}
Refresh params
</Button>

<Button
Expand All @@ -115,8 +133,16 @@ export default function ParamsToolbar() {
onClick={rebootCallback}
color={tailwindColors.red[600]}
>
{" "}
Reboot FC{" "}
Reboot FC
</Button>

<Button
size="sm"
rightSection={<IconDownload size={14} />}
onClick={saveParamsToFile}
color={tailwindColors.blue[600]}
>
Save params to file
</Button>
</div>
)
Expand Down
2 changes: 1 addition & 1 deletion gcs/src/missions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export default function Missions() {
}

const result = await window.ipcRenderer.invoke(
"missions:get-save-mission-file-path",
"app:get-save-file-path",
options,
)

Expand Down
9 changes: 9 additions & 0 deletions gcs/src/redux/middleware/emitters.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
showDashboardMissionFetchingNotificationThunk,
} from "../slices/missionSlice"
import {
emitExportParamsToFile,
emitRebootAutopilot,
emitRefreshParams,
emitSetMultipleParams,
Expand Down Expand Up @@ -266,6 +267,14 @@ export function handleEmitters(socket, store, action) {
emitter: emitSetMultipleParams,
callback: () => socket.socket.emit("set_multiple_params", action.payload),
},
{
emitter: emitExportParamsToFile,
callback: () => {
socket.socket.emit("export_params_to_file", {
file_path: action.payload.filePath,
})
},
},

/*
==========
Expand Down
12 changes: 12 additions & 0 deletions gcs/src/redux/middleware/socketMiddleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ const ParamSpecificSocketEvents = Object.freeze({
onParamRequestUpdate: "param_request_update",
onParamSetSuccess: "param_set_success",
onParamError: "params_error",
onExportParamsResult: "export_params_result",
})

const MissionSpecificSocketEvents = Object.freeze({
Expand Down Expand Up @@ -456,6 +457,17 @@ const socketMiddleware = (store) => {
store.dispatch(setFetchingVars(false))
})

socket.socket.on(
ParamSpecificSocketEvents.onExportParamsResult,
(msg) => {
if (msg.success) {
showSuccessNotification(msg.message)
} else {
showErrorNotification(msg.message)
}
},
)

socket.socket.on(
DroneSpecificSocketEvents.onNavRepositionResult,
(msg) => {
Expand Down
4 changes: 3 additions & 1 deletion gcs/src/redux/slices/paramsSlice.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ const paramsSlice = createSlice({
emitRebootAutopilot: () => {},
emitRefreshParams: () => {},
emitSetMultipleParams: () => {},
emitExportParamsToFile: () => {},
},
selectors: {
selectRebootData: (state) => state.rebootData,
Expand Down Expand Up @@ -134,10 +135,11 @@ export const {
updateModifiedParamValue,
deleteModifiedParam,
resetParamState,
setHasFetchedOnce,
emitRebootAutopilot,
emitRefreshParams,
emitSetMultipleParams,
setHasFetchedOnce,
emitExportParamsToFile,
} = paramsSlice.actions
export const {
selectRebootData,
Expand Down
27 changes: 27 additions & 0 deletions radio/app/controllers/paramsController.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,3 +327,30 @@ def getCachedParam(self, params: str) -> Union[CachedParam, dict]:
else:
self.drone.logger.error(f"Invalid params type, got {type(params)}")
return {}

def exportParamsToFile(self, file_path: str) -> Response:
"""
Export all cached parameters to a file.

Args:
file_path (str): The path to the file to export to

Returns:
Response: The response from the export operation
"""
try:
with open(file_path, "w") as f:
# order params alphabetically by param_id
ordered_params = sorted(self.params, key=lambda k: k["param_id"])
for param in ordered_params:
f.write(f"{param['param_id'].upper()},{param['param_value']}\n")
return {
"success": True,
"message": f"Parameters exported successfully to {file_path}",
}
except Exception as e:
self.drone.logger.error(f"Failed to export params to file: {e}")
return {
"success": False,
"message": f"Failed to export params to file: {e}",
}
41 changes: 41 additions & 0 deletions radio/app/endpoints/params.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import time
from typing import Any, List

from typing_extensions import TypedDict

import app.droneStatus as droneStatus
from app import logger, socketio
from app.utils import notConnectedError


class ExportParamsFileType(TypedDict):
file_path: str


@socketio.on("set_multiple_params")
Expand Down Expand Up @@ -82,3 +89,37 @@ def refresh_params() -> None:
time.sleep(0.2)

socketio.emit("params", droneStatus.drone.paramsController.params)


@socketio.on("export_params_to_file")
def export_params_to_file(data: ExportParamsFileType) -> None:
Comment thread
1Blademaster marked this conversation as resolved.
"""
Export parameters to a file.

Args:
data: The data from the client containing the file path.
"""
if droneStatus.state != "params":
socketio.emit(
"params_error",
{"message": "You must be on the params screen to export parameters."},
)
logger.debug(f"Current state: {droneStatus.state}")
return

if not droneStatus.drone:
notConnectedError(action="export params to file")
return

file_path = data.get("file_path", None)
if not file_path:
socketio.emit(
"export_params_result",
{"success": False, "message": "No file path provided."},
)
logger.error("No file path provided for exporting parameters.")
return

result = droneStatus.drone.paramsController.exportParamsToFile(file_path)

socketio.emit("export_params_result", result)