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
12 changes: 12 additions & 0 deletions gcs/ekfStatus.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo_dark_icon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/ekfStatus.jsx"></script>
</body>
</html>
19 changes: 13 additions & 6 deletions gcs/electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import registerAboutIPC, {
destroyAboutWindow,
openAboutPopout,
} from "./modules/aboutWindow"
import registerEkfStatusIPC, {
destroyEkfStatusWindow,
} from "./modules/ekfStatus"
import registerLinkStatsIPC, {
destroyLinkStatsWindow,
openLinkStatsWindow,
Expand Down Expand Up @@ -220,6 +223,7 @@ function createWindow() {
registerWebcamIPC(win)
registerAboutIPC()
registerLinkStatsIPC()
registerEkfStatusIPC()

// Open links in browser, not within the electron window.
// Note, links must have target="_blank"
Expand Down Expand Up @@ -371,14 +375,19 @@ function startBackend() {
})
}

function closeWindows() {
destroyWebcamWindow()
destroyAboutWindow()
destroyLinkStatsWindow()
destroyEkfStatusWindow()
}

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
function closeWithBackend() {
// Always close all popout windows first
destroyWebcamWindow()
destroyAboutWindow()
destroyLinkStatsWindow()
closeWindows()
console.log("Killing backend")
// kill any processes with the name "fgcs_backend.exe"
// Windows
Expand All @@ -400,9 +409,7 @@ app.on("before-quit", () => {
console.log("Stopping backend")
spawnSync("pkill", ["-f", "fgcs_backend"])
pythonBackend = null
destroyWebcamWindow()
destroyAboutWindow()
destroyLinkStatsWindow()
closeWindows()
}
})

Expand Down
61 changes: 61 additions & 0 deletions gcs/electron/modules/ekfStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { BrowserWindow, ipcMain } from "electron"
import path from "path"

const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]

let ekfStatusWin: BrowserWindow | null = null

export function openEkfStatusWindow() {
if (ekfStatusWin === null) {
ekfStatusWin = new BrowserWindow({
width: 700,
height: 400,
frame: true,
icon: path.join(process.env.VITE_PUBLIC, "app_icon.ico"),
show: false,
title: "EKF Status",
webPreferences: {
preload: path.join(__dirname, "preload.js"),
contextIsolation: true,
},
fullscreen: false,
fullscreenable: false,
alwaysOnTop: true,
})
}

if (VITE_DEV_SERVER_URL) {
ekfStatusWin?.loadURL(VITE_DEV_SERVER_URL + "ekfStatus.html")
} else {
ekfStatusWin?.loadFile(path.join(process.env.DIST, "ekfStatus.html"))
}

ekfStatusWin.on("close", () => {
ekfStatusWin = null
})
ekfStatusWin.setMenuBarVisibility(false)
ekfStatusWin.show()
}

export function closeEkfStatusWindow() {
destroyEkfStatusWindow()
}

export function destroyEkfStatusWindow() {
ekfStatusWin?.close()
ekfStatusWin = null
}

export default function registerEkfStatusIPC() {
ipcMain.removeHandler("app:open-ekf-status-window")
ipcMain.removeHandler("app:close-ekf-status-window")
ipcMain.removeHandler("app:update-ekf-status")

ipcMain.handle("app:open-ekf-status-window", () => {
openEkfStatusWindow()
})
ipcMain.handle("app:close-ekf-status-window", () => closeEkfStatusWindow())
ipcMain.handle("app:update-ekf-status", (_, ekfStatusData) => {
Comment thread
1Blademaster marked this conversation as resolved.
Comment thread
1Blademaster marked this conversation as resolved.
ekfStatusWin?.webContents.send("app:send-ekf-status", ekfStatusData)
})
}
2 changes: 1 addition & 1 deletion gcs/electron/modules/webcam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export function openWebcamPopout(
},
fullscreen: false,
fullscreenable: false,
alwaysOnTop: true,
Comment thread
1Blademaster marked this conversation as resolved.
})
} else {
console.warn("2nd webcam window requested, ignoring")
Expand Down Expand Up @@ -85,7 +86,6 @@ export function openWebcamPopout(
}

export function closeWebcamPopout(mainWindow: BrowserWindow | null) {
console.log("Destroying webcam window")
destroyWebcamWindow()
mainWindow?.webContents.send("app:webcam-closed")
}
Expand Down
3 changes: 3 additions & 0 deletions gcs/electron/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const ALLOWED_INVOKE_CHANNELS = [
"app:close-link-stats-window",
"app:update-link-stats",
"window:select-file-in-explorer",
"app:update-ekf-status",
Comment thread
1Blademaster marked this conversation as resolved.
"app:open-ekf-status-window",
]

const ALLOWED_SEND_CHANNELS = [
Expand All @@ -41,6 +43,7 @@ const ALLOWED_ON_CHANNELS = [
"app:webcam-closed",
"app:send-link-stats",
"fla:log-parse-progress",
"app:send-ekf-status",
]

contextBridge.exposeInMainWorld("ipcRenderer", {
Expand Down
37 changes: 37 additions & 0 deletions gcs/src/components/dashboard/ekfDisplay.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useMemo } from "react"
import { useSelector } from "react-redux"
import { selectEkfCalculatedStatus } from "../../redux/slices/droneInfoSlice"

// Tailwind styling
import resolveConfig from "tailwindcss/resolveConfig"
import tailwindConfig from "../../../tailwind.config"
const tailwindColors = resolveConfig(tailwindConfig).theme.colors

const RED = tailwindColors.red[500]
const YELLOW = tailwindColors.yellow[500]

export default function EkfDisplay({ telemetryFontSize }) {
const ekfCalculatedStatus = useSelector(selectEkfCalculatedStatus)

const textColour = useMemo(() => {
if (ekfCalculatedStatus > 0.8) return RED
if (ekfCalculatedStatus > 0.5) return YELLOW
return ""
}, [ekfCalculatedStatus])

return (
<div
className="font-bold hover:cursor-pointer"
style={{
fontSize: `${telemetryFontSize * 1.25}rem`,
lineHeight: `${telemetryFontSize * 1.75}rem`,
color: textColour,
}}
onClick={() => {
window.ipcRenderer.invoke("app:open-ekf-status-window")
}}
>
EKF
</div>
)
}
76 changes: 46 additions & 30 deletions gcs/src/components/dashboard/telemetry.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
*/

// Custom Components
import { MAV_STATE } from "../../helpers/mavlinkConstants"
import { AttitudeIndicator, HeadingIndicator } from "./indicator"
import TelemetryValueDisplay from "./telemetryValueDisplay"
import { MAV_STATE } from "../../helpers/mavlinkConstants"

// Redux
import { useSelector } from "react-redux"
Expand All @@ -21,6 +21,7 @@ import {
selectPrearmEnabled,
selectTelemetry,
} from "../../redux/slices/droneInfoSlice"
import EkfDisplay from "./ekfDisplay"

export default function TelemetrySection({
calcIndicatorSize,
Expand Down Expand Up @@ -175,36 +176,51 @@ export default function TelemetrySection({
</div>
</div>

{/* Battery information */}
<div className="flex flex-col items-center">
<p>BATTERY</p>

<table>
<tbody>
{batteryData.map((battery) => (
<tr className="w-full" key={battery.id}>
<td className="px-4">BATTERY{battery.id}</td>
<td className="font-bold px-2 text-xl text-right">
{(battery.voltages ? battery.voltages[0] / 1000 : 0).toFixed(
2,
)}
V
</td>
<td className="font-bold px-2 text-xl text-right">
{(battery.current_battery
? battery.current_battery / 100
: 0
).toFixed(2)}
A
</td>
<td className="font-bold px-2 text-xl text-right">
{battery.battery_remaining ? battery.battery_remaining : 0}%
</td>
</tr>
))}
</tbody>
</table>
{/* EKF and VIBE labels */}
<div className="flex flex-row items-center justify-center gap-10 my-4">
<EkfDisplay telemetryFontSize={telemetryFontSize} />
<div
className="font-bold hover:cursor-pointer"
style={{
fontSize: `${telemetryFontSize * 1.25}rem`,
lineHeight: `${telemetryFontSize * 1.75}rem`,
}}
>
VIBE
</div>
</div>

{/* Battery information */}
{batteryData.length > 0 && (
<div className="flex flex-col items-center my-4">
<table>
<tbody>
{batteryData.map((battery) => (
<tr className="w-full" key={battery.id}>
<td className="px-4">BATTERY{battery.id}</td>
<td className="font-bold px-2 text-xl text-right">
{(battery.voltages
? battery.voltages[0] / 1000
: 0
).toFixed(2)}
V
</td>
<td className="font-bold px-2 text-xl text-right">
{(battery.current_battery
? battery.current_battery / 100
: 0
).toFixed(2)}
A
</td>
<td className="font-bold px-2 text-xl text-right">
{battery.battery_remaining ? battery.battery_remaining : 0}%
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
)
}
Loading
Loading