From 35c4087d16ce44a83438db197cbe8417676f3c22 Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Sun, 9 Jun 2024 22:57:25 -0700
Subject: [PATCH 01/14] page set up

---
 .../views/active/rocket-monitoring-view.tsx   | 87 +++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100644 client/src/views/active/rocket-monitoring-view.tsx

diff --git a/client/src/views/active/rocket-monitoring-view.tsx b/client/src/views/active/rocket-monitoring-view.tsx
new file mode 100644
index 000000000..48c36be3f
--- /dev/null
+++ b/client/src/views/active/rocket-monitoring-view.tsx
@@ -0,0 +1,87 @@
+import React, { useCallback, useEffect, useState } from 'react';
+
+import api from '../../services/api';
+import { IRocketPopulated } from '../../utils/entities';
+import { Box, Button, ButtonGroup, Stack, Tooltip } from '@mui/material';
+import Header, { Breadcrumb } from '../../components/Header';
+import { ViewKeys } from '../../utils/viewProviderContext';
+import { useActiveMission } from '../../utils/ActiveMissionContext';
+import { Settings, WifiTethering } from '@mui/icons-material';
+
+interface IRocketMonitoringViewProps {
+    // rocketId: string;
+}
+
+const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRocketMonitoringViewProps) => {
+    const [rocket, setRocket] = useState<IRocketPopulated>({} as IRocketPopulated);
+
+    // const getActiveRocket = useCallback(async (): Promise<IRocketPopulated> => {
+    //     const response = await api.getRocket(rocketId || '');
+    //     const data = response.data as IRocketPopulated;
+    //     setRocket(data);
+    //     return data;
+    // }, [rocketId]);
+
+    useEffect(() => {
+        // getActiveRocket();
+    }, []);
+    
+    const activeContext = useActiveMission();
+
+    const breadCrumbs: Breadcrumb[] = [
+		{ name: "Ground Support", viewKey: ViewKeys.PLATFORM_SELECTION_KEY, active: false },
+		{ name: activeContext.rocket.Name, viewKey: ViewKeys.ROCKET_DETAILS_KEY, active: false },
+		{ name: activeContext.activeMission.Name || "New Mission", viewKey: ViewKeys.ACTIVE_FLIGHT_KEY, active: true }
+	];
+
+    return (
+        <Box sx={{ width: '100vw', height: '100vh' }}>
+            <Stack
+                height={'100%'}
+                padding={4}
+                direction="column"
+                gap={4}
+                overflow={'none'}
+            >
+                <Stack direction="row" alignItems={'center'} justifyContent={'space-between'}>
+                    <Header icon="ROCKET_MONITORING" breadCrumbs={breadCrumbs} />
+                    <Stack direction="row" spacing={1}>
+                        <Tooltip title="Configuration" placement="top">
+                            <Button variant={'text'} onClick={() => {}}>
+                                <Settings />
+                            </Button>
+                        </Tooltip>
+                        <ButtonGroup
+                            variant="contained"
+                        >
+                            <Tooltip title={"Serial Status"}>
+                                <Button 
+                                    aria-readonly
+                                    disabled
+                                    color={"success"}
+                                >
+                                    Telemetry Lock
+                                </Button>
+                            </Tooltip>
+                            <Button 
+                                variant="contained" 
+                                size={'large'} 
+                                startIcon={<WifiTethering/>} 
+                                sx={{ width: 180 }}
+                                onClick={() => {
+                                    // setOpenConnection(!openConnection);  
+                                    // socketContext.toggleConnection();
+                                }}
+                            >
+                                Connect
+                                {/* {socketContext.connect ? "Disconnect" : "Connect"} */}
+                            </Button>
+                        </ButtonGroup>
+                    </Stack>
+                </Stack>
+            </Stack>
+        </Box>
+    );
+};
+
+export default RocketMonitoringView;
\ No newline at end of file

From b029269e8ad604a4337fa677b70143857c51fce9 Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Sun, 9 Jun 2024 22:58:11 -0700
Subject: [PATCH 02/14] View foundation

---
 client/src/utils/viewProviderContext.tsx           | 3 ++-
 client/src/views/active/rocket-monitoring-view.tsx | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/client/src/utils/viewProviderContext.tsx b/client/src/utils/viewProviderContext.tsx
index e445b9cb5..3e9c0eab2 100644
--- a/client/src/utils/viewProviderContext.tsx
+++ b/client/src/utils/viewProviderContext.tsx
@@ -15,6 +15,7 @@ import ActiveMissionView from "../views/active-mission";
 import { ActiveMissionProvider } from "./ActiveMissionContext";
 import PlatformSelector from "../views/platform-selector";
 import EngineMonitoringView from "../views/pdp-monitoring/engine-monitoring-view";
+import RocketMonitoringView from "../views/active/rocket-monitoring-view";
 
 
 export enum ViewKeys {
@@ -84,7 +85,7 @@ export const ViewContextProvider = ({ children }: PropsWithChildren<any>) => {
             case ViewKeys.ACTIVE_FLIGHT_KEY:
                 return {
                     viewKey: ViewKeys.ACTIVE_FLIGHT_KEY,
-                    currentView: <ActiveMissionView />
+                    currentView: <RocketMonitoringView />
                 }
             case ViewKeys.MISSION_REPLAY_KEY:
                 return {
diff --git a/client/src/views/active/rocket-monitoring-view.tsx b/client/src/views/active/rocket-monitoring-view.tsx
index 48c36be3f..b23540256 100644
--- a/client/src/views/active/rocket-monitoring-view.tsx
+++ b/client/src/views/active/rocket-monitoring-view.tsx
@@ -60,7 +60,7 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                                     disabled
                                     color={"success"}
                                 >
-                                    Telemetry Lock
+                                    GPS Lock
                                 </Button>
                             </Tooltip>
                             <Button 

From fbd1a66ebf8b4935c35dfad1aafd6139e60c0126 Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Mon, 10 Jun 2024 12:41:18 -0700
Subject: [PATCH 03/14] rocket status component setup

---
 .../rocket-monitoring/RocketStatus.tsx        | 21 ++++++++++++++++
 .../views/active/rocket-monitoring-view.tsx   | 25 ++++++++++++-------
 2 files changed, 37 insertions(+), 9 deletions(-)
 create mode 100644 client/src/components/rocket-monitoring/RocketStatus.tsx

diff --git a/client/src/components/rocket-monitoring/RocketStatus.tsx b/client/src/components/rocket-monitoring/RocketStatus.tsx
new file mode 100644
index 000000000..459555a1c
--- /dev/null
+++ b/client/src/components/rocket-monitoring/RocketStatus.tsx
@@ -0,0 +1,21 @@
+import { Paper } from '@mui/material';
+import React, { useEffect, useState } from 'react';
+
+interface IRocketStatusProps {
+}
+
+const RocketStatus: React.FC = (props: IRocketStatusProps) => {
+    const {} = props;
+    return (
+        <Paper
+            sx={{     
+                borderRadius: 2,
+                padding: 2
+            }}
+        >
+            <h1>Rocket Status</h1>
+        </Paper>
+    );
+}
+
+export default RocketStatus;
\ No newline at end of file
diff --git a/client/src/views/active/rocket-monitoring-view.tsx b/client/src/views/active/rocket-monitoring-view.tsx
index b23540256..90f832b80 100644
--- a/client/src/views/active/rocket-monitoring-view.tsx
+++ b/client/src/views/active/rocket-monitoring-view.tsx
@@ -7,6 +7,7 @@ import Header, { Breadcrumb } from '../../components/Header';
 import { ViewKeys } from '../../utils/viewProviderContext';
 import { useActiveMission } from '../../utils/ActiveMissionContext';
 import { Settings, WifiTethering } from '@mui/icons-material';
+import RocketStatus from '../../components/rocket-monitoring/RocketStatus';
 
 interface IRocketMonitoringViewProps {
     // rocketId: string;
@@ -54,15 +55,20 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                         <ButtonGroup
                             variant="contained"
                         >
-                            <Tooltip title={"Serial Status"}>
-                                <Button 
-                                    aria-readonly
-                                    disabled
-                                    color={"success"}
-                                >
-                                    GPS Lock
-                                </Button>
-                            </Tooltip>
+                            <Button 
+                                aria-readonly
+                                disabled
+                                color={"success"}
+                            >
+                                GPS Lock
+                            </Button>
+                            <Button 
+                                aria-readonly
+                                disabled
+                                color={"success"}
+                            >
+                                GPS Lock
+                            </Button>
                             <Button 
                                 variant="contained" 
                                 size={'large'} 
@@ -79,6 +85,7 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                         </ButtonGroup>
                     </Stack>
                 </Stack>
+                    <RocketStatus />
             </Stack>
         </Box>
     );

From e7d42f9127c1390d1557719561aef5e3a923aa07 Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Tue, 11 Jun 2024 21:54:43 -0700
Subject: [PATCH 04/14] Socket context to useWebSocket hook

---
 client/src/utils/socket-context.tsx | 135 +++++++++++++++++-----------
 1 file changed, 81 insertions(+), 54 deletions(-)

diff --git a/client/src/utils/socket-context.tsx b/client/src/utils/socket-context.tsx
index f3f0ca515..8f32193b6 100644
--- a/client/src/utils/socket-context.tsx
+++ b/client/src/utils/socket-context.tsx
@@ -1,37 +1,48 @@
-import { createContext, PropsWithChildren, useContext, useEffect, useState, useReducer } from 'react';
+import { 
+	createContext, 
+	PropsWithChildren, 
+	useContext, 
+	useEffect, 
+	useState, 
+	useReducer 
+} from 'react';
+import useWebSocket from 'react-use-websocket';
 
 interface IPacket {
 	Data: {};
 	Type: string;
 }
 
+enum Protocol {
+	APRS = 'APRS',
+	LoRa = 'LoRa'
+}
+
 export interface SocketContext {
+	gpsLock: boolean;
+	isConnected: boolean;
+	toggleConnection: () => void;
 	logs: string[];
-	aprsPacket: IPacket;
-	loRaPacket: IPacket;
-	setPacketFrequency: (frequency: number) => void;
-	setProtocol: (protocol: string) => void;
+	packet: IPacket;
+	frequency: number;
+	setFrequency: (frequency: number) => void;
+	protocol: string;
+	setProtocol: (protocol: Protocol) => void;
 }
 
 export const Context = createContext<SocketContext>({
+	gpsLock: false,
+	isConnected: false,
+	toggleConnection: () => {},
 	logs: [],
-	aprsPacket: {} as IPacket,
-	loRaPacket: {} as IPacket,
-	setPacketFrequency: (frequency: number) => {},
-	setProtocol: (protocol: string) => {}
+	packet: {} as IPacket,
+	frequency: 433.92,
+	setFrequency: (frequency: number) => {},
+	protocol: Protocol.APRS,
+	setProtocol: (protocol: Protocol) => {}
 });
 
-function aprsReducer(state: IPacket, action: { type: string; payload: any }) {
-	switch (action.type) {
-		case 'SET_PACKET':
-			return action.payload;
-
-		default:
-			throw new Error(`Unhandled action type: ${action.type}`);
-	}
-}
-
-function loraReducer(state: IPacket, action: { type: string; payload: any }) {
+function packetReducer(state: IPacket, action: { type: string; payload: any }) {
 	switch (action.type) {
 		case 'SET_PACKET':
 			return action.payload;
@@ -43,55 +54,71 @@ function loraReducer(state: IPacket, action: { type: string; payload: any }) {
 
 export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
 	const [logs, setLogs] = useState<string[]>([]);
-	const [aprsPacket, aprsDispatch] = useReducer(aprsReducer, {} as IPacket);
-	const [loRaPacket, loraDispatch] = useReducer(loraReducer, {} as IPacket);
-	const [packetFrequency, setPacketFrequency] = useState<number>(3);
+	const [packet, packetDispatch] = useReducer(packetReducer, {} as IPacket);
+	const [frequency, setFrequency] = useState<number>(433.92);
 	const [protocol, setProtocol] = useState<string>('APRS');
+    const [isConnected, setIsConnect] = useState<boolean>(false);
+	const [gpsLock, setGpsLock] = useState<boolean>(false);
 
 	const port = import.meta.env.TELEMETRY_SERVER_PORT ? import.meta.env.TELEMETRY_SERVER_PORT : 9193;
-	const ws = new WebSocket(`ws://localhost:${port}`);
-
-	const enableLiveMode = () => {
-		ws.send(JSON.stringify({ type: 'establish_stream', frequency: packetFrequency }));
-	};
-
-	const changePacketFrequency = (frequency: number) => {
-		ws.send(JSON.stringify({ type: 'change_frequency', frequency: frequency }));
-	};
+	const [uri, setUri] = useState<string | null>(isConnected ? `ws://localhost:${port}` : null);
+
+	const {
+		sendJsonMessage,
+        lastMessage,
+        lastJsonMessage
+	} = useWebSocket(uri, {
+		shouldReconnect: (closeEvent) => isConnected,
+		onClose: (closeEvent) => {
+			setIsConnect(false);
+			console.log('Socket Closed:', closeEvent);
+		}, 
+		onError: (error) => {
+			console.log('Socket Error:', error);
+		},
+		onOpen: () => {
+			console.log('Telemetry socket Opened');
+			setIsConnect(true);
+		},
+		share: true
+	});
 
-	const changeProtocol = (protocol: string) => {
-		ws.send(JSON.stringify({ type: 'change_protocol', protocol: protocol }));
-	};
+	const toggleConnection = () => {
+        setIsConnect(prevIsConnected => {
+            if (prevIsConnected) {
+                // If the WebSocket is currently connected, disconnect it
+                setUri(null);
+            } else {
+                // If the WebSocket is currently disconnected, connect it
+                setUri(`ws://localhost:${port}`);
+            }
+            return !prevIsConnected;
+        });
+    };
 
 	useEffect(() => {
-		if (ws.readyState === ws.OPEN) {
-			changePacketFrequency(packetFrequency);
+		if (lastMessage) {
+			setLogs([...logs, lastMessage]);
 		}
-	}, [packetFrequency]);
+	}, [lastMessage]);
 
 	useEffect(() => {
-		if (ws.readyState === ws.OPEN) {
-			changeProtocol(protocol);
+		if (lastJsonMessage) {
+			packetDispatch({ type: 'SET_PACKET', payload: lastJsonMessage });
 		}
-	}, [protocol]);
-
-	ws.addEventListener('open', () => {
-		console.log('Socket Connected');
-		enableLiveMode();
-	});
-
-	ws.addEventListener('message', (message) => {
-		const data = JSON.parse(message.data);
-		console.log(data);
-	});
+	}, [lastJsonMessage]);
 
 	return (
 		<Context.Provider
 			value={{
+				isConnected,
+				gpsLock,
+				toggleConnection,
 				logs,
-				aprsPacket,
-				loRaPacket,
-				setPacketFrequency,
+				packet,
+				frequency,
+				setFrequency,
+				protocol,
 				setProtocol
 			}}
 		>

From 9eb37fd6b8725809dc15432db8d3d340adfc33cb Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Tue, 11 Jun 2024 21:55:25 -0700
Subject: [PATCH 05/14] Telemetry status + packet + dialog base for components

---
 .../rocket-monitoring/ConnectionDialog.tsx    | 108 ++++++++++++++++++
 .../rocket-monitoring/RocketStatus.tsx        |  21 ----
 .../rocket-monitoring/TelemetryPacket.tsx     |  77 +++++++++++++
 .../rocket-monitoring/TelemetryStatus.tsx     |  76 ++++++++++++
 .../views/active/rocket-monitoring-view.tsx   |  23 ++--
 5 files changed, 276 insertions(+), 29 deletions(-)
 create mode 100644 client/src/components/rocket-monitoring/ConnectionDialog.tsx
 delete mode 100644 client/src/components/rocket-monitoring/RocketStatus.tsx
 create mode 100644 client/src/components/rocket-monitoring/TelemetryPacket.tsx
 create mode 100644 client/src/components/rocket-monitoring/TelemetryStatus.tsx

diff --git a/client/src/components/rocket-monitoring/ConnectionDialog.tsx b/client/src/components/rocket-monitoring/ConnectionDialog.tsx
new file mode 100644
index 000000000..d2eb48b22
--- /dev/null
+++ b/client/src/components/rocket-monitoring/ConnectionDialog.tsx
@@ -0,0 +1,108 @@
+import { Autocomplete, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField, Typography } from "@mui/material";
+import React, { useEffect, useState } from "react";
+import { useSocketContext } from "../../utils/socket-context";
+
+interface IConnectionDialogProps {
+    isOpen: boolean;
+    onClose: () => void;
+}
+
+const ConnectionDialog: React.FC<IConnectionDialogProps> = (props: IConnectionDialogProps) => {
+    const socketContext = useSocketContext();
+    const [protocol, setProtocol] = useState<string>('APRS');
+    const [frequency, setFrequency] = useState<number>(433.92);
+    const [launchAltitude, setLaunchAltitude] = useState<number>(0);
+
+    // useEffect(() => {
+    //     setProtocol(socketContext.protocol);
+    //     setFrequency(socketContext.frequency);
+    // }, [socketContext.protocol, socketContext.frequency]);
+
+    return(
+        <Dialog open={props.isOpen}>
+            <DialogTitle sx={{ fontWeight: 600 }}>Connection Status</DialogTitle>
+            <DialogContent>
+                <Stack direction="column" spacing={2} alignItems="center" sx={{ paddingTop: 1 }}>
+                    <Autocomplete  
+                        fullWidth
+                        options={['APRS', 'LoRa' ]}
+                        value={protocol}
+                        renderInput={
+                            (params) => 
+                            <TextField 
+                                {...params} 
+                                label="Protocol" 
+                                variant="outlined" 
+                                size="small" 
+                            />
+                        }
+                        onChange={(_: any, value: any) => {
+                            setProtocol(value);
+                        }}
+                    />
+                    <TextField 
+                        fullWidth
+                        required
+                        value={frequency}
+                        onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
+                            setFrequency(event.target.value as number);
+                        }}
+                        label="Frequency"
+                        variant="outlined"
+                        size="small"
+                        type="number"
+                        InputProps={{
+                            endAdornment: <Typography>MHz</Typography>,
+                        }}
+                        InputLabelProps={{
+                            shrink: true,
+                        }}
+                    />
+                    <TextField
+                        fullWidth
+                        required
+                        value={launchAltitude}
+                        onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
+                            setLaunchAltitude(event.target.value as number);
+                        }}
+                        label="Launch Altitude"
+                        variant="outlined"
+                        size="small"
+                        type="number"
+                        InputProps={{
+                            endAdornment: <Typography>FT</Typography>,
+                        }}
+                        InputLabelProps={{
+                            shrink: true,
+                        }}
+                    />
+                </Stack>
+            </DialogContent>
+            <DialogActions>
+                <Button 
+                    variant={"text"} 
+                    component="label" 
+                    onClick={props.onClose}
+                >
+                    Cancel
+                </Button>
+                <Button 
+                    variant={"contained"} 
+                    component="label" 
+                    onClick={
+                        () => {
+                            // socketContext.setProtocol(protocol);
+                            socketContext.setFrequency(frequency);
+                            // socketContext.toggleConnection();
+                            props.onClose()
+                        }
+                    }
+                >
+                    Connect
+                </Button>
+            </DialogActions>
+        </Dialog>
+    );
+}
+
+export default ConnectionDialog;
\ No newline at end of file
diff --git a/client/src/components/rocket-monitoring/RocketStatus.tsx b/client/src/components/rocket-monitoring/RocketStatus.tsx
deleted file mode 100644
index 459555a1c..000000000
--- a/client/src/components/rocket-monitoring/RocketStatus.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Paper } from '@mui/material';
-import React, { useEffect, useState } from 'react';
-
-interface IRocketStatusProps {
-}
-
-const RocketStatus: React.FC = (props: IRocketStatusProps) => {
-    const {} = props;
-    return (
-        <Paper
-            sx={{     
-                borderRadius: 2,
-                padding: 2
-            }}
-        >
-            <h1>Rocket Status</h1>
-        </Paper>
-    );
-}
-
-export default RocketStatus;
\ No newline at end of file
diff --git a/client/src/components/rocket-monitoring/TelemetryPacket.tsx b/client/src/components/rocket-monitoring/TelemetryPacket.tsx
new file mode 100644
index 000000000..08e6a707b
--- /dev/null
+++ b/client/src/components/rocket-monitoring/TelemetryPacket.tsx
@@ -0,0 +1,77 @@
+import { Button, Chip, Icon, Paper, Stack, Typography } from '@mui/material';
+import React, { useEffect, useState } from 'react';
+import { useSocketContext } from '../../utils/socket-context';
+import { Badge, Public, Height, ShowChart, HorizontalRule } from '@mui/icons-material';
+
+interface IPacketContent {
+    label: string;
+    value: string;
+    icon: any;
+}
+
+const TelemetryPacket: React.FC = () => {
+    const socketContext = useSocketContext();
+
+    const content: IPacketContent[] = [
+        {
+            label: 'Call Sign',
+            value: 'N/A',
+            icon: <Badge />
+        },
+        {
+            label: 'Altitude',
+            value: 'ft (AGL)',
+            icon: <ShowChart />
+        },
+        {
+            label: 'Longitude',
+            value: '0.00 °',
+            icon: <Height />
+        },
+        {
+            label: 'Latitude',
+            value: '0.00 °',
+            icon: <HorizontalRule />
+        }
+    ]
+
+    useEffect(() => {
+        console.log(socketContext.packet);
+    }, [socketContext.packet]);
+
+    return (
+        <Paper
+            sx={{     
+                borderRadius: 2,
+                padding: 2,
+                width: 250
+            }}
+        >
+            <Stack spacing={2} direction={'column'}>
+                <Typography variant='h6' fontWeight={600}>
+                    Telemetry Packet
+                </Typography>
+                {content.map((item: IPacketContent, index: number) => (
+                    <Stack direction={'row'} justifyContent={'space-between'} key={index}>
+                        <Stack spacing={1} direction={'row'} alignItems={'center'}>
+                            {item.icon}
+                            <Typography fontWeight={500}>
+                                {item.label}
+                            </Typography>
+                        </Stack>
+                        <Chip
+                            label={item.value} 
+                            sx={{ 
+                                fontWeight: 600, 
+                                borderRadius: 1 
+                            }} 
+                            color='default' 
+                        />
+                    </Stack>
+                ))}
+            </Stack>
+        </Paper>
+    );
+}
+
+export default TelemetryPacket;
\ No newline at end of file
diff --git a/client/src/components/rocket-monitoring/TelemetryStatus.tsx b/client/src/components/rocket-monitoring/TelemetryStatus.tsx
new file mode 100644
index 000000000..38a41773b
--- /dev/null
+++ b/client/src/components/rocket-monitoring/TelemetryStatus.tsx
@@ -0,0 +1,76 @@
+import { Button, Chip, IconButton, Paper, Stack, Tooltip, Typography } from '@mui/material';
+import React, { useEffect, useState } from 'react';
+import { useSocketContext } from '../../utils/socket-context';
+
+import { GpsFixed, GpsOff, GpsNotFixed } from '@mui/icons-material';
+
+const TelemetryStatus: React.FC = () => {
+    const socketContext = useSocketContext();
+
+    const [protocol, setProtocol] = useState<string>('APRS');
+    useEffect(() => {
+        setProtocol(socketContext.protocol);
+    }, [socketContext.protocol]);
+
+    const [frequency, setFrequency] = useState<number>(433.92);
+    useEffect(() => {
+        setFrequency(socketContext.frequency);
+    }, [socketContext.frequency]);
+
+    const [ launchAltitude, setLaunchAltitude ] = useState<number>(0);
+
+    return (
+        <Paper
+            sx={{     
+                borderRadius: 2,
+                padding: 2
+            }}
+        >
+            <Stack spacing={2} direction={'row'} alignItems={'center'} justifyContent={'space-between'}>
+                <Typography variant='h6' fontWeight={600}>Telemetry Information</Typography>
+                <Stack spacing={2} direction={'row'} alignItems={'center'}>
+                    <Tooltip title={'GPS Lock if green'}>
+                        <IconButton 
+                            disabled={!socketContext.isConnected}
+                            color={socketContext.gpsLock ? 'success' : 'error'}
+                        >
+                            {socketContext.isConnected ? 
+                                socketContext.gpsLock ? 
+                                    (<GpsNotFixed />) 
+                                    : ( <GpsFixed />) 
+                            : (
+                                <GpsOff />
+                            )}   
+                        </IconButton>
+                    </Tooltip>
+                    <Chip
+                        label={`Launch Altitude: ${launchAltitude} FT`} 
+                        sx={{ 
+                            fontWeight: 600, 
+                            borderRadius: 1 
+                        }} 
+                        color='default' 
+                    />
+                    <Chip
+                        label={`Frequency: ${frequency} MHz`} 
+                        sx={{ 
+                            fontWeight: 600, 
+                            borderRadius: 1 
+                        }} 
+                        color='default' 
+                    />
+                    <Chip 
+                        label={`Protocol: ${protocol}`} 
+                        sx={{ 
+                            fontWeight: 600, 
+                            borderRadius: 1 
+                        }} 
+                        color='default' 
+                    />
+                </Stack>
+            </Stack>
+        </Paper>
+    );
+}
+
+export default TelemetryStatus;
\ No newline at end of file
diff --git a/client/src/views/active/rocket-monitoring-view.tsx b/client/src/views/active/rocket-monitoring-view.tsx
index 90f832b80..c80aa4bcc 100644
--- a/client/src/views/active/rocket-monitoring-view.tsx
+++ b/client/src/views/active/rocket-monitoring-view.tsx
@@ -7,7 +7,10 @@ import Header, { Breadcrumb } from '../../components/Header';
 import { ViewKeys } from '../../utils/viewProviderContext';
 import { useActiveMission } from '../../utils/ActiveMissionContext';
 import { Settings, WifiTethering } from '@mui/icons-material';
-import RocketStatus from '../../components/rocket-monitoring/RocketStatus';
+import TelemetryStatus from '../../components/rocket-monitoring/TelemetryStatus';
+import ConnectionDialog from '../../components/rocket-monitoring/ConnectionDialog';
+import { useSocketContext } from '../../utils/socket-context';
+import TelemetryPacket from '../../components/rocket-monitoring/TelemetryPacket';
 
 interface IRocketMonitoringViewProps {
     // rocketId: string;
@@ -35,13 +38,17 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
 		{ name: activeContext.activeMission.Name || "New Mission", viewKey: ViewKeys.ACTIVE_FLIGHT_KEY, active: true }
 	];
 
+    const [openConnection, setOpenConnection] = useState<boolean>(false);
+    const [frequency, setFrequency] = useState<number>(0);
+    const socketContext = useSocketContext();
+
     return (
         <Box sx={{ width: '100vw', height: '100vh' }}>
             <Stack
                 height={'100%'}
                 padding={4}
                 direction="column"
-                gap={4}
+                gap={2}
                 overflow={'none'}
             >
                 <Stack direction="row" alignItems={'center'} justifyContent={'space-between'}>
@@ -67,7 +74,7 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                                 disabled
                                 color={"success"}
                             >
-                                GPS Lock
+                                Rocket Connected
                             </Button>
                             <Button 
                                 variant="contained" 
@@ -75,18 +82,18 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                                 startIcon={<WifiTethering/>} 
                                 sx={{ width: 180 }}
                                 onClick={() => {
-                                    // setOpenConnection(!openConnection);  
-                                    // socketContext.toggleConnection();
+                                    setOpenConnection(!openConnection);  
                                 }}
                             >
-                                Connect
-                                {/* {socketContext.connect ? "Disconnect" : "Connect"} */}
+                                {socketContext.isConnected ? "Disconnect" : "Connect"}
                             </Button>
                         </ButtonGroup>
                     </Stack>
                 </Stack>
-                    <RocketStatus />
+                <TelemetryStatus />
+                <TelemetryPacket />
             </Stack>
+            <ConnectionDialog isOpen={openConnection} onClose={() => setOpenConnection(false)} />
         </Box>
     );
 };

From 964c50eb9017f39582ec8813f2977bca3f7f29b0 Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Tue, 11 Jun 2024 22:59:10 -0700
Subject: [PATCH 06/14] fixed socket context & altitude

---
 .../rocket-monitoring/ConnectionDialog.tsx    | 13 ++++---
 .../rocket-monitoring/TelemetryStatus.tsx     | 12 ++++++-
 client/src/utils/socket-context.tsx           | 34 +++++++++++++++----
 client/src/utils/viewProviderContext.tsx      |  5 ++-
 .../views/active/rocket-monitoring-view.tsx   | 15 +++-----
 5 files changed, 55 insertions(+), 24 deletions(-)

diff --git a/client/src/components/rocket-monitoring/ConnectionDialog.tsx b/client/src/components/rocket-monitoring/ConnectionDialog.tsx
index d2eb48b22..a1dcee4e4 100644
--- a/client/src/components/rocket-monitoring/ConnectionDialog.tsx
+++ b/client/src/components/rocket-monitoring/ConnectionDialog.tsx
@@ -5,13 +5,14 @@ import { useSocketContext } from "../../utils/socket-context";
 interface IConnectionDialogProps {
     isOpen: boolean;
     onClose: () => void;
+    updateAltitude: (altitude: number) => void;
 }
 
 const ConnectionDialog: React.FC<IConnectionDialogProps> = (props: IConnectionDialogProps) => {
     const socketContext = useSocketContext();
-    const [protocol, setProtocol] = useState<string>('APRS');
-    const [frequency, setFrequency] = useState<number>(433.92);
-    const [launchAltitude, setLaunchAltitude] = useState<number>(0);
+    const [protocol, setProtocol] = useState<string>('');
+    const [frequency, setFrequency] = useState<number>();
+    const [launchAltitude, setLaunchAltitude] = useState<number>();
 
     // useEffect(() => {
     //     setProtocol(socketContext.protocol);
@@ -89,10 +90,12 @@ const ConnectionDialog: React.FC<IConnectionDialogProps> = (props: IConnectionDi
                 <Button 
                     variant={"contained"} 
                     component="label" 
+                    disabled={!protocol || !frequency || !launchAltitude}
                     onClick={
                         () => {
-                            // socketContext.setProtocol(protocol);
-                            socketContext.setFrequency(frequency);
+                            socketContext.updateProtocol(protocol);
+                            socketContext.updateFrequency(frequency);
+                            props.updateAltitude(launchAltitude);
                             // socketContext.toggleConnection();
                             props.onClose()
                         }
diff --git a/client/src/components/rocket-monitoring/TelemetryStatus.tsx b/client/src/components/rocket-monitoring/TelemetryStatus.tsx
index 38a41773b..34ac82a06 100644
--- a/client/src/components/rocket-monitoring/TelemetryStatus.tsx
+++ b/client/src/components/rocket-monitoring/TelemetryStatus.tsx
@@ -4,7 +4,11 @@ import { useSocketContext } from '../../utils/socket-context';
 
 import { GpsFixed, GpsOff, GpsNotFixed } from '@mui/icons-material';
 
-const TelemetryStatus: React.FC = () => {
+interface ITelemetryStatusProps {
+    launchAltitude?: number;
+}
+
+const TelemetryStatus: React.FC<ITelemetryStatusProps> = (props: ITelemetryStatusProps) => {
     const socketContext = useSocketContext();
 
     const [protocol, setProtocol] = useState<string>('APRS');
@@ -19,6 +23,12 @@ const TelemetryStatus: React.FC = () => {
 
     const [ launchAltitude, setLaunchAltitude ] = useState<number>(0);
 
+    useEffect(() => {
+        if (props.launchAltitude) {
+            setLaunchAltitude(props.launchAltitude);
+        }
+    }, [props.launchAltitude]);
+
     return (
         <Paper
             sx={{     
diff --git a/client/src/utils/socket-context.tsx b/client/src/utils/socket-context.tsx
index 8f32193b6..0e0b609ab 100644
--- a/client/src/utils/socket-context.tsx
+++ b/client/src/utils/socket-context.tsx
@@ -25,9 +25,9 @@ export interface SocketContext {
 	logs: string[];
 	packet: IPacket;
 	frequency: number;
-	setFrequency: (frequency: number) => void;
+	updateFrequency: (frequency: number) => void;
 	protocol: string;
-	setProtocol: (protocol: Protocol) => void;
+	updateProtocol: (protocol: Protocol) => void;
 }
 
 export const Context = createContext<SocketContext>({
@@ -37,9 +37,9 @@ export const Context = createContext<SocketContext>({
 	logs: [],
 	packet: {} as IPacket,
 	frequency: 433.92,
-	setFrequency: (frequency: number) => {},
+	updateFrequency: (frequency: number) => {},
 	protocol: Protocol.APRS,
-	setProtocol: (protocol: Protocol) => {}
+	updateProtocol: (protocol: Protocol) => {}
 });
 
 function packetReducer(state: IPacket, action: { type: string; payload: any }) {
@@ -63,12 +63,32 @@ export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
 	const port = import.meta.env.TELEMETRY_SERVER_PORT ? import.meta.env.TELEMETRY_SERVER_PORT : 9193;
 	const [uri, setUri] = useState<string | null>(isConnected ? `ws://localhost:${port}` : null);
 
+	useEffect(() => {
+		if (frequency) {
+			console.log('Frequency:', frequency);	
+		}
+
+		if (protocol) {
+			console.log('Protocol:', protocol);
+		}
+	}, [frequency, protocol]);
+
+	const updateFrequency = (frequency: number) => {
+		console.log('Frequency:', frequency);
+		setFrequency(frequency);
+	};
+
+	const updateProtocol = (protocol: Protocol) => {
+		console.log('Protocol:', protocol);
+		setProtocol(protocol);
+	};
+
 	const {
 		sendJsonMessage,
         lastMessage,
         lastJsonMessage
 	} = useWebSocket(uri, {
-		shouldReconnect: (closeEvent) => isConnected,
+		shouldReconnect: (_) => isConnected,
 		onClose: (closeEvent) => {
 			setIsConnect(false);
 			console.log('Socket Closed:', closeEvent);
@@ -117,9 +137,9 @@ export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
 				logs,
 				packet,
 				frequency,
-				setFrequency,
+				updateFrequency,
 				protocol,
-				setProtocol
+				updateProtocol
 			}}
 		>
 			{children}
diff --git a/client/src/utils/viewProviderContext.tsx b/client/src/utils/viewProviderContext.tsx
index 3e9c0eab2..b9732a952 100644
--- a/client/src/utils/viewProviderContext.tsx
+++ b/client/src/utils/viewProviderContext.tsx
@@ -16,6 +16,7 @@ import { ActiveMissionProvider } from "./ActiveMissionContext";
 import PlatformSelector from "../views/platform-selector";
 import EngineMonitoringView from "../views/pdp-monitoring/engine-monitoring-view";
 import RocketMonitoringView from "../views/active/rocket-monitoring-view";
+import { SocketGateway } from "./socket-context";
 
 
 export enum ViewKeys {
@@ -85,7 +86,9 @@ export const ViewContextProvider = ({ children }: PropsWithChildren<any>) => {
             case ViewKeys.ACTIVE_FLIGHT_KEY:
                 return {
                     viewKey: ViewKeys.ACTIVE_FLIGHT_KEY,
-                    currentView: <RocketMonitoringView />
+                    currentView: <SocketGateway>
+                        <RocketMonitoringView />
+                    </SocketGateway>
                 }
             case ViewKeys.MISSION_REPLAY_KEY:
                 return {
diff --git a/client/src/views/active/rocket-monitoring-view.tsx b/client/src/views/active/rocket-monitoring-view.tsx
index c80aa4bcc..cb6db1e43 100644
--- a/client/src/views/active/rocket-monitoring-view.tsx
+++ b/client/src/views/active/rocket-monitoring-view.tsx
@@ -42,6 +42,8 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
     const [frequency, setFrequency] = useState<number>(0);
     const socketContext = useSocketContext();
 
+    const [launchAltitude, setLaunchAltitude] = useState<number>(0);
+
     return (
         <Box sx={{ width: '100vw', height: '100vh' }}>
             <Stack
@@ -67,14 +69,7 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                                 disabled
                                 color={"success"}
                             >
-                                GPS Lock
-                            </Button>
-                            <Button 
-                                aria-readonly
-                                disabled
-                                color={"success"}
-                            >
-                                Rocket Connected
+                                Server Connected
                             </Button>
                             <Button 
                                 variant="contained" 
@@ -90,10 +85,10 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                         </ButtonGroup>
                     </Stack>
                 </Stack>
-                <TelemetryStatus />
+                <TelemetryStatus launchAltitude={launchAltitude} />
                 <TelemetryPacket />
             </Stack>
-            <ConnectionDialog isOpen={openConnection} onClose={() => setOpenConnection(false)} />
+            <ConnectionDialog updateAltitude={setLaunchAltitude} isOpen={openConnection} onClose={() => setOpenConnection(false)} />
         </Box>
     );
 };

From 1d62c29684850a1ee3975fe03b388ea566e8acad Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Tue, 11 Jun 2024 23:40:15 -0700
Subject: [PATCH 07/14] Telemetry log + condition rendering

---
 .../src/components/logging/TelemetryLog.tsx   | 76 +++++++++----------
 .../rocket-monitoring/ConnectionDialog.tsx    |  4 +-
 .../rocket-monitoring/TelemetryPacket.tsx     | 43 ++++++++---
 client/src/utils/socket-context.tsx           |  2 +-
 .../views/active/rocket-monitoring-view.tsx   | 16 +++-
 5 files changed, 83 insertions(+), 58 deletions(-)

diff --git a/client/src/components/logging/TelemetryLog.tsx b/client/src/components/logging/TelemetryLog.tsx
index 305e6b5fd..5a08486c4 100644
--- a/client/src/components/logging/TelemetryLog.tsx
+++ b/client/src/components/logging/TelemetryLog.tsx
@@ -1,5 +1,5 @@
 import React, { useState, useEffect } from "react";
-import { Button, InputAdornment, Stack, TextField, Tooltip } from "@mui/material";
+import { Button, InputAdornment, Paper, Stack, TextField, TextareaAutosize, Tooltip } from "@mui/material";
 import { Box } from "@mui/material";
 import { Download } from "@mui/icons-material";
 import GpsOffIcon from '@mui/icons-material/GpsOff';
@@ -10,10 +10,10 @@ import { useActiveMission } from "../../utils/ActiveMissionContext";
 import { saveAs } from "file-saver";
 
 interface TelemetryLogProps {
-    packet: any;
-    width: string;
-    maxRows: number;
-    telemetryConnected: boolean;
+    packet?: any;
+    width?: string;
+    maxRows?: number;
+    telemetryConnected?: boolean;
 }
 
 const TelemetryLog: React.FC<TelemetryLogProps> = (props: TelemetryLogProps) => {
@@ -29,19 +29,19 @@ const TelemetryLog: React.FC<TelemetryLogProps> = (props: TelemetryLogProps) =>
 
     const [log, setLog] = useState<string[]>(activeContext.logs.map((log) => log.Log));
 
-    useEffect(() => {
-        if (props.packet) {
-            setLog((prev) => [...prev, JSON.stringify(props.packet)]);
-            setTimeSincePacket(0);
-        }
-    }, [props.packet]);
+    // useEffect(() => {
+    //     if (props.packet) {
+    //         setLog((prev) => [...prev, JSON.stringify(props.packet)]);
+    //         setTimeSincePacket(0);
+    //     }
+    // }, [props.packet]);
 
-    useEffect(() => {
-        setTelemetryConnected(props.telemetryConnected);
-        if (telemetryConnected) {
-            setTimeSincePacket(0);
-        }
-    }, [props.telemetryConnected]);
+    // useEffect(() => {
+    //     setTelemetryConnected(props.telemetryConnected);
+    //     if (telemetryConnected) {
+    //         setTimeSincePacket(0);
+    //     }
+    // }, [props.telemetryConnected]);
 
     useEffect(() => {
         if (telemetryConnected) {        
@@ -53,34 +53,26 @@ const TelemetryLog: React.FC<TelemetryLogProps> = (props: TelemetryLogProps) =>
     }, [timeSincePacket, telemetryConnected]);
 
     return (
-        <Box style={{
-                backgroundColor: "#282C34",
-                borderRadius: 5,
-                width: props.width
+        <Paper 
+            sx={{
+                borderRadius: 2,
+                width: '100%',
+                minWidth: 'fit-content',
+                height: 'fit-content'
             }}
-            boxShadow={3}
         >
             <Stack direction={"column"}>
-                <TextField 
-                    value={log}
-                    variant={"standard"}
-                    multiline
-                    rows={props.maxRows}
+                <TextareaAutosize
                     style={{
-                        color: "#CA4D33",
-                        backgroundColor: "#282C34",
-                        borderRadius: 13,
-                        padding: "0px 0px 0px 20px"
-                    }}
-                    InputProps={{
-                        disableUnderline: true,
-                        readOnly: true,
-                        style: {
-                            color: "white",
-                        },
-                        multiline: true,
-                        rows: props.maxRows,
+                        border: "none",
+                        backgroundColor: "#23282F",
+                        color: "#ffffff"
                     }}
+                    minRows={10}
+                    maxRows={10}
+                    readOnly
+                    placeholder="Valve Cart Log"
+                    value={log}
                 />
                 <Stack direction={'row'} bottom={0}>
                     {[
@@ -108,7 +100,7 @@ const TelemetryLog: React.FC<TelemetryLogProps> = (props: TelemetryLogProps) =>
                                     endAdornment: <InputAdornment position="end">s</InputAdornment>,
                                 }}
                                 value={timeSincePacket}
-                                sx={{ width: 110 }}
+                                sx={{ width: 100 }}
                             />
                         </Tooltip>
                     </Stack>
@@ -122,7 +114,7 @@ const TelemetryLog: React.FC<TelemetryLogProps> = (props: TelemetryLogProps) =>
                     </Button>
                 </Stack>
             </Stack>
-        </Box>
+        </Paper>
     );
 }
 
diff --git a/client/src/components/rocket-monitoring/ConnectionDialog.tsx b/client/src/components/rocket-monitoring/ConnectionDialog.tsx
index a1dcee4e4..87849b005 100644
--- a/client/src/components/rocket-monitoring/ConnectionDialog.tsx
+++ b/client/src/components/rocket-monitoring/ConnectionDialog.tsx
@@ -10,8 +10,8 @@ interface IConnectionDialogProps {
 
 const ConnectionDialog: React.FC<IConnectionDialogProps> = (props: IConnectionDialogProps) => {
     const socketContext = useSocketContext();
-    const [protocol, setProtocol] = useState<string>('');
-    const [frequency, setFrequency] = useState<number>();
+    const [protocol, setProtocol] = useState<string>(socketContext.protocol);
+    const [frequency, setFrequency] = useState<number>(socketContext.frequency);
     const [launchAltitude, setLaunchAltitude] = useState<number>();
 
     // useEffect(() => {
diff --git a/client/src/components/rocket-monitoring/TelemetryPacket.tsx b/client/src/components/rocket-monitoring/TelemetryPacket.tsx
index 08e6a707b..a598fcc2a 100644
--- a/client/src/components/rocket-monitoring/TelemetryPacket.tsx
+++ b/client/src/components/rocket-monitoring/TelemetryPacket.tsx
@@ -1,17 +1,30 @@
 import { Button, Chip, Icon, Paper, Stack, Typography } from '@mui/material';
 import React, { useEffect, useState } from 'react';
 import { useSocketContext } from '../../utils/socket-context';
-import { Badge, Public, Height, ShowChart, HorizontalRule } from '@mui/icons-material';
+import { Badge, Height, ShowChart, HorizontalRule, Timer } from '@mui/icons-material';
+
 
-interface IPacketContent {
-    label: string;
-    value: string;
-    icon: any;
-}
 
 const TelemetryPacket: React.FC = () => {
     const socketContext = useSocketContext();
 
+    const [timeSincePacket, setTimeSincePacket] = useState<number>(0);
+
+    useEffect(() => {
+        if ((socketContext.packet && socketContext.isConnected)) {        
+            const interval = setInterval(() => {
+                setTimeSincePacket(timeSincePacket + 1);    
+            }, 1000);
+            return () => clearInterval(interval);
+        }
+    }, [timeSincePacket, socketContext.packet]);
+
+    interface IPacketContent {
+        label: string;
+        value: string;
+        icon: any;
+    }
+
     const content: IPacketContent[] = [
         {
             label: 'Call Sign',
@@ -20,7 +33,7 @@ const TelemetryPacket: React.FC = () => {
         },
         {
             label: 'Altitude',
-            value: 'ft (AGL)',
+            value: 'ft (ALG)',
             icon: <ShowChart />
         },
         {
@@ -44,12 +57,13 @@ const TelemetryPacket: React.FC = () => {
             sx={{     
                 borderRadius: 2,
                 padding: 2,
-                width: 250
+                width: 300,
+                minWidth: 'fit-content',
             }}
         >
             <Stack spacing={2} direction={'column'}>
                 <Typography variant='h6' fontWeight={600}>
-                    Telemetry Packet
+                    Current Packet
                 </Typography>
                 {content.map((item: IPacketContent, index: number) => (
                     <Stack direction={'row'} justifyContent={'space-between'} key={index}>
@@ -66,9 +80,20 @@ const TelemetryPacket: React.FC = () => {
                                 borderRadius: 1 
                             }} 
                             color='default' 
+                            size='small'
                         />
                     </Stack>
                 ))}
+                <Chip 
+                    label={`Time Since: ${timeSincePacket}s`}
+                    icon={<Timer />}
+                    size='medium'
+                    sx={{ 
+                        fontWeight: 600, 
+                        borderRadius: 1 
+                    }} 
+                    color='default'
+                />
             </Stack>
         </Paper>
     );
diff --git a/client/src/utils/socket-context.tsx b/client/src/utils/socket-context.tsx
index 0e0b609ab..310a25505 100644
--- a/client/src/utils/socket-context.tsx
+++ b/client/src/utils/socket-context.tsx
@@ -61,7 +61,7 @@ export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
 	const [gpsLock, setGpsLock] = useState<boolean>(false);
 
 	const port = import.meta.env.TELEMETRY_SERVER_PORT ? import.meta.env.TELEMETRY_SERVER_PORT : 9193;
-	const [uri, setUri] = useState<string | null>(isConnected ? `ws://localhost:${port}` : null);
+	const [uri, setUri] = useState<string | null>(isConnected ? `ws://localhost:${9193}` : null);
 
 	useEffect(() => {
 		if (frequency) {
diff --git a/client/src/views/active/rocket-monitoring-view.tsx b/client/src/views/active/rocket-monitoring-view.tsx
index cb6db1e43..8f762730c 100644
--- a/client/src/views/active/rocket-monitoring-view.tsx
+++ b/client/src/views/active/rocket-monitoring-view.tsx
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react';
 
 import api from '../../services/api';
 import { IRocketPopulated } from '../../utils/entities';
-import { Box, Button, ButtonGroup, Stack, Tooltip } from '@mui/material';
+import { Alert, Box, Button, ButtonGroup, Stack, Tooltip } from '@mui/material';
 import Header, { Breadcrumb } from '../../components/Header';
 import { ViewKeys } from '../../utils/viewProviderContext';
 import { useActiveMission } from '../../utils/ActiveMissionContext';
@@ -11,6 +11,7 @@ import TelemetryStatus from '../../components/rocket-monitoring/TelemetryStatus'
 import ConnectionDialog from '../../components/rocket-monitoring/ConnectionDialog';
 import { useSocketContext } from '../../utils/socket-context';
 import TelemetryPacket from '../../components/rocket-monitoring/TelemetryPacket';
+import TelemetryLog from '../../components/logging/TelemetryLog';
 
 interface IRocketMonitoringViewProps {
     // rocketId: string;
@@ -39,9 +40,7 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
 	];
 
     const [openConnection, setOpenConnection] = useState<boolean>(false);
-    const [frequency, setFrequency] = useState<number>(0);
     const socketContext = useSocketContext();
-
     const [launchAltitude, setLaunchAltitude] = useState<number>(0);
 
     return (
@@ -86,7 +85,16 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                     </Stack>
                 </Stack>
                 <TelemetryStatus launchAltitude={launchAltitude} />
-                <TelemetryPacket />
+                {socketContext.isConnected ? (
+                    <Stack direction="row" gap={2}>
+                        <TelemetryPacket />
+                        <TelemetryLog />
+                    </Stack>
+                ) : (
+                    <Alert severity="info">
+                        Connect to telemetry to see data
+                    </Alert>
+                )}
             </Stack>
             <ConnectionDialog updateAltitude={setLaunchAltitude} isOpen={openConnection} onClose={() => setOpenConnection(false)} />
         </Box>

From 7a2566a3d323bff25117c4a58a151d763c5b8015 Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Wed, 12 Jun 2024 00:48:36 -0700
Subject: [PATCH 08/14] Add logging animation

---
 .../src/components/logging/TelemetryLog.tsx   |  6 ++--
 .../rocket-monitoring/ConnectionDialog.tsx    |  4 +--
 .../rocket-monitoring/TelemetryStatus.tsx     |  4 +--
 client/src/utils/socket-context.tsx           | 12 ++++++++
 .../views/active/rocket-monitoring-view.tsx   | 29 +++++++++++++++++--
 5 files changed, 46 insertions(+), 9 deletions(-)

diff --git a/client/src/components/logging/TelemetryLog.tsx b/client/src/components/logging/TelemetryLog.tsx
index 5a08486c4..a65cdafbb 100644
--- a/client/src/components/logging/TelemetryLog.tsx
+++ b/client/src/components/logging/TelemetryLog.tsx
@@ -66,12 +66,14 @@ const TelemetryLog: React.FC<TelemetryLogProps> = (props: TelemetryLogProps) =>
                     style={{
                         border: "none",
                         backgroundColor: "#23282F",
-                        color: "#ffffff"
+                        color: "#ffffff",
+                        paddingTop: 10,
+                        paddingLeft: 10
                     }}
                     minRows={10}
                     maxRows={10}
                     readOnly
-                    placeholder="Valve Cart Log"
+                    placeholder="Telemetry Log"
                     value={log}
                 />
                 <Stack direction={'row'} bottom={0}>
diff --git a/client/src/components/rocket-monitoring/ConnectionDialog.tsx b/client/src/components/rocket-monitoring/ConnectionDialog.tsx
index 87849b005..d94337060 100644
--- a/client/src/components/rocket-monitoring/ConnectionDialog.tsx
+++ b/client/src/components/rocket-monitoring/ConnectionDialog.tsx
@@ -96,12 +96,12 @@ const ConnectionDialog: React.FC<IConnectionDialogProps> = (props: IConnectionDi
                             socketContext.updateProtocol(protocol);
                             socketContext.updateFrequency(frequency);
                             props.updateAltitude(launchAltitude);
-                            // socketContext.toggleConnection();
+                            socketContext.toggleConnection();
                             props.onClose()
                         }
                     }
                 >
-                    Connect
+                    {socketContext.isConnected ? 'Disconnect' : 'Connect' }
                 </Button>
             </DialogActions>
         </Dialog>
diff --git a/client/src/components/rocket-monitoring/TelemetryStatus.tsx b/client/src/components/rocket-monitoring/TelemetryStatus.tsx
index 34ac82a06..169b7c7e2 100644
--- a/client/src/components/rocket-monitoring/TelemetryStatus.tsx
+++ b/client/src/components/rocket-monitoring/TelemetryStatus.tsx
@@ -46,8 +46,8 @@ const TelemetryStatus: React.FC<ITelemetryStatusProps> = (props: ITelemetryStatu
                         >
                             {socketContext.isConnected ? 
                                 socketContext.gpsLock ? 
-                                    (<GpsNotFixed />) 
-                                    : ( <GpsFixed />) 
+                                    (<GpsFixed />) 
+                                    : ( <GpsNotFixed/>) 
                             : (
                                 <GpsOff />
                             )}   
diff --git a/client/src/utils/socket-context.tsx b/client/src/utils/socket-context.tsx
index 310a25505..5d98850ac 100644
--- a/client/src/utils/socket-context.tsx
+++ b/client/src/utils/socket-context.tsx
@@ -1,3 +1,4 @@
+import { Alert, Snackbar } from '@mui/material';
 import { 
 	createContext, 
 	PropsWithChildren, 
@@ -59,6 +60,7 @@ export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
 	const [protocol, setProtocol] = useState<string>('APRS');
     const [isConnected, setIsConnect] = useState<boolean>(false);
 	const [gpsLock, setGpsLock] = useState<boolean>(false);
+	const [wsError, setWsError] = useState<any | null>(null);
 
 	const port = import.meta.env.TELEMETRY_SERVER_PORT ? import.meta.env.TELEMETRY_SERVER_PORT : 9193;
 	const [uri, setUri] = useState<string | null>(isConnected ? `ws://localhost:${9193}` : null);
@@ -94,6 +96,7 @@ export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
 			console.log('Socket Closed:', closeEvent);
 		}, 
 		onError: (error) => {
+			setWsError(error);
 			console.log('Socket Error:', error);
 		},
 		onOpen: () => {
@@ -117,12 +120,14 @@ export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
     };
 
 	useEffect(() => {
+		console.log('Last Message:', lastMessage);
 		if (lastMessage) {
 			setLogs([...logs, lastMessage]);
 		}
 	}, [lastMessage]);
 
 	useEffect(() => {
+		console.log('Last JSON Message:', lastJsonMessage);
 		if (lastJsonMessage) {
 			packetDispatch({ type: 'SET_PACKET', payload: lastJsonMessage });
 		}
@@ -142,6 +147,13 @@ export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
 				updateProtocol
 			}}
 		>
+			<Snackbar
+				open={wsError !== null}
+				message="Could not connect to socket server"
+				autoHideDuration={6000}
+			>
+				<Alert variant='filled' severity="error" onClose={() => setWsError(null)}>Could not connect to socket server</Alert>				
+			</Snackbar>
 			{children}
 		</Context.Provider>
 	);
diff --git a/client/src/views/active/rocket-monitoring-view.tsx b/client/src/views/active/rocket-monitoring-view.tsx
index 8f762730c..4ed86ec85 100644
--- a/client/src/views/active/rocket-monitoring-view.tsx
+++ b/client/src/views/active/rocket-monitoring-view.tsx
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useState } from 'react';
 
 import api from '../../services/api';
 import { IRocketPopulated } from '../../utils/entities';
-import { Alert, Box, Button, ButtonGroup, Stack, Tooltip } from '@mui/material';
+import { Alert, Box, Button, ButtonGroup, Skeleton, Stack, Tooltip } from '@mui/material';
 import Header, { Breadcrumb } from '../../components/Header';
 import { ViewKeys } from '../../utils/viewProviderContext';
 import { useActiveMission } from '../../utils/ActiveMissionContext';
@@ -42,6 +42,20 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
     const [openConnection, setOpenConnection] = useState<boolean>(false);
     const socketContext = useSocketContext();
     const [launchAltitude, setLaunchAltitude] = useState<number>(0);
+    const [loading, setLoading] = useState<boolean>(true);
+
+    useEffect(() => {
+        if (socketContext.isConnected) {
+            const interval = setInterval(() => {
+                setLoading(false);
+            }, 2500);
+            return () => {
+                clearInterval(interval);
+                setLoading(true);
+            }
+        }
+    }, [socketContext.isConnected]);
+
 
     return (
         <Box sx={{ width: '100vw', height: '100vh' }}>
@@ -85,7 +99,12 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                     </Stack>
                 </Stack>
                 <TelemetryStatus launchAltitude={launchAltitude} />
-                {socketContext.isConnected ? (
+                {socketContext.isConnected ? loading ? (
+                    <Stack direction="row" gap={2}>
+                        <Skeleton variant="rectangular" height={300} width={'30%'} sx={{ borderRadius: 2 }} />
+                        <Skeleton variant="rectangular" height={300} width={'70%'} sx={{ borderRadius: 2 }} />
+                    </Stack>
+                ) :(
                     <Stack direction="row" gap={2}>
                         <TelemetryPacket />
                         <TelemetryLog />
@@ -96,7 +115,11 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                     </Alert>
                 )}
             </Stack>
-            <ConnectionDialog updateAltitude={setLaunchAltitude} isOpen={openConnection} onClose={() => setOpenConnection(false)} />
+            <ConnectionDialog 
+                updateAltitude={setLaunchAltitude} 
+                isOpen={openConnection} 
+                onClose={() => setOpenConnection(false)} 
+            />
         </Box>
     );
 };

From 582997ed980605c5975a0d3e74b30f4adc4f6ac6 Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Fri, 14 Jun 2024 02:47:22 -0700
Subject: [PATCH 09/14] Map + altitude components

---
 client/index.html                             |   6 +
 client/package-lock.json                      |  59 ++++++
 client/package.json                           |   4 +
 .../src/components/logging/TelemetryLog.tsx   | 107 +++++-----
 .../rocket-monitoring/ConnectionDialog.tsx    |  50 +++--
 .../rocket-monitoring/PublishMission.tsx      |  41 ++++
 .../TelemetryAltitudeGraph.tsx                |  73 +++++++
 .../rocket-monitoring/TelemetryMap.tsx        | 183 ++++++++++++++++++
 .../rocket-monitoring/TelemetryPacket.tsx     |  37 ++--
 .../rocket-monitoring/TelemetryStatus.tsx     |  11 +-
 client/src/utils/socket-context.tsx           |  68 ++++---
 .../views/active/rocket-monitoring-view.tsx   | 115 +++++++----
 services/telemetry/tools/decode_test.sh       |   2 +-
 13 files changed, 576 insertions(+), 180 deletions(-)
 create mode 100644 client/src/components/rocket-monitoring/PublishMission.tsx
 create mode 100644 client/src/components/rocket-monitoring/TelemetryAltitudeGraph.tsx
 create mode 100644 client/src/components/rocket-monitoring/TelemetryMap.tsx

diff --git a/client/index.html b/client/index.html
index 612412ff3..2754bb795 100644
--- a/client/index.html
+++ b/client/index.html
@@ -4,6 +4,12 @@
     <meta charset="UTF-8" />
     <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     <link rel="icon" href="./src/static/images/PlatformHubLogo.png" />
+    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
+    integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
+    crossorigin=""/>
+    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
+     integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
+     crossorigin=""></script>
     <title>Ground Support</title>
   </head>
   <body>
diff --git a/client/package-lock.json b/client/package-lock.json
index 381e8163e..d49906101 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -24,11 +24,14 @@
 				"concurrently": "^7.5.0",
 				"google-map-react": "^2.2.1",
 				"http": "^0.0.1-security",
+				"leaflet": "^1.9.4",
+				"leaflet.offline": "^3.0.0-rc.4",
 				"lodash": "^4.17.21",
 				"mui-datatables": "^4.3.0",
 				"react": "^18.2.0",
 				"react-dom": "^18.2.0",
 				"react-hotkeys-hook": "^4.5.0",
+				"react-leaflet": "^4.2.1",
 				"react-material-file-upload": "^0.0.4",
 				"react-syntax-highlighter": "^15.5.0",
 				"react-use": "^17.5.0",
@@ -42,6 +45,7 @@
 			"devDependencies": {
 				"@types/file-saver": "^2.0.5",
 				"@types/google-map-react": "^2.1.7",
+				"@types/leaflet": "^1.9.12",
 				"@types/lodash": "^4.14.191",
 				"@types/mui-datatables": "^4.3.5",
 				"@types/react-syntax-highlighter": "^15.5.7",
@@ -1414,6 +1418,16 @@
 			"resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz",
 			"integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg=="
 		},
+		"node_modules/@react-leaflet/core": {
+			"version": "2.1.0",
+			"resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz",
+			"integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==",
+			"peerDependencies": {
+				"leaflet": "^1.9.0",
+				"react": "^18.0.0",
+				"react-dom": "^18.0.0"
+			}
+		},
 		"node_modules/@reactflow/background": {
 			"version": "11.3.13",
 			"resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.13.tgz",
@@ -2185,6 +2199,15 @@
 			"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.7.tgz",
 			"integrity": "sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA=="
 		},
+		"node_modules/@types/leaflet": {
+			"version": "1.9.12",
+			"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz",
+			"integrity": "sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==",
+			"dev": true,
+			"dependencies": {
+				"@types/geojson": "*"
+			}
+		},
 		"node_modules/@types/lodash": {
 			"version": "4.14.200",
 			"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz",
@@ -3758,6 +3781,11 @@
 			"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.5.tgz",
 			"integrity": "sha512-fedL7PRwmeVkgyhu9hLeTBaI6wcGk7JGJswdaRsa5aUbkXI1kr1xZwTPBtaYPpwf56878iDek6VbVnuWMebJmw=="
 		},
+		"node_modules/idb": {
+			"version": "6.1.5",
+			"resolved": "https://registry.npmjs.org/idb/-/idb-6.1.5.tgz",
+			"integrity": "sha512-IJtugpKkiVXQn5Y+LteyBCNk1N8xpGV3wWZk9EVtZWH8DYkjBn0bX1XnGP9RkyZF0sAcywa6unHqSWKe7q4LGw=="
+		},
 		"node_modules/import-fresh": {
 			"version": "3.3.0",
 			"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -4175,6 +4203,24 @@
 				"node": ">=6"
 			}
 		},
+		"node_modules/leaflet": {
+			"version": "1.9.4",
+			"resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz",
+			"integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="
+		},
+		"node_modules/leaflet.offline": {
+			"version": "3.0.0-rc.4",
+			"resolved": "https://registry.npmjs.org/leaflet.offline/-/leaflet.offline-3.0.0-rc.4.tgz",
+			"integrity": "sha512-M5Vw7Lzm19KYV+WpeVmETRwBCtbG76hFkJDydyrk+eD2gonK2U4aRgxVXwDv5wcmbr35Kgxi8YV/xnLE+XUSrA==",
+			"dependencies": {
+				"idb": "^6.1.4",
+				"leaflet": "^1.6.0",
+				"typescript": "^4.5.5"
+			},
+			"engines": {
+				"node": ">=16"
+			}
+		},
 		"node_modules/lines-and-columns": {
 			"version": "1.2.4",
 			"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -4813,6 +4859,19 @@
 			"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
 			"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
 		},
+		"node_modules/react-leaflet": {
+			"version": "4.2.1",
+			"resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz",
+			"integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==",
+			"dependencies": {
+				"@react-leaflet/core": "^2.1.0"
+			},
+			"peerDependencies": {
+				"leaflet": "^1.9.0",
+				"react": "^18.0.0",
+				"react-dom": "^18.0.0"
+			}
+		},
 		"node_modules/react-lifecycles-compat": {
 			"version": "3.0.4",
 			"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
diff --git a/client/package.json b/client/package.json
index 6c8b12889..10610cae9 100644
--- a/client/package.json
+++ b/client/package.json
@@ -19,11 +19,14 @@
 		"concurrently": "^7.5.0",
 		"google-map-react": "^2.2.1",
 		"http": "^0.0.1-security",
+		"leaflet": "^1.9.4",
+		"leaflet.offline": "^3.0.0-rc.4",
 		"lodash": "^4.17.21",
 		"mui-datatables": "^4.3.0",
 		"react": "^18.2.0",
 		"react-dom": "^18.2.0",
 		"react-hotkeys-hook": "^4.5.0",
+		"react-leaflet": "^4.2.1",
 		"react-material-file-upload": "^0.0.4",
 		"react-syntax-highlighter": "^15.5.0",
 		"react-use": "^17.5.0",
@@ -61,6 +64,7 @@
 	"devDependencies": {
 		"@types/file-saver": "^2.0.5",
 		"@types/google-map-react": "^2.1.7",
+		"@types/leaflet": "^1.9.12",
 		"@types/lodash": "^4.14.191",
 		"@types/mui-datatables": "^4.3.5",
 		"@types/react-syntax-highlighter": "^15.5.7",
diff --git a/client/src/components/logging/TelemetryLog.tsx b/client/src/components/logging/TelemetryLog.tsx
index a65cdafbb..00593eb71 100644
--- a/client/src/components/logging/TelemetryLog.tsx
+++ b/client/src/components/logging/TelemetryLog.tsx
@@ -1,5 +1,5 @@
 import React, { useState, useEffect } from "react";
-import { Button, InputAdornment, Paper, Stack, TextField, TextareaAutosize, Tooltip } from "@mui/material";
+import { Button, Chip, InputAdornment, Paper, Stack, TextField, TextareaAutosize, Tooltip } from "@mui/material";
 import { Box } from "@mui/material";
 import { Download } from "@mui/icons-material";
 import GpsOffIcon from '@mui/icons-material/GpsOff';
@@ -8,49 +8,44 @@ import TimerIcon from '@mui/icons-material/Timer';
 import { IAprsTelemetryPacket, formatPacket } from "../../utils/TelemetryTypes";
 import { useActiveMission } from "../../utils/ActiveMissionContext";
 import { saveAs } from "file-saver";
+import { Packet, useSocketContext } from "../../utils/socket-context";
+import { useTheme } from "@emotion/react";
 
-interface TelemetryLogProps {
-    packet?: any;
-    width?: string;
-    maxRows?: number;
-    telemetryConnected?: boolean;
-}
-
-const TelemetryLog: React.FC<TelemetryLogProps> = (props: TelemetryLogProps) => {
-    const [locked, setLocked] = useState<boolean>(false);
+const TelemetryLog: React.FC = () => {
     const [timeSincePacket, setTimeSincePacket] = useState<number>(0);
-    const [telemetryConnected, setTelemetryConnected] = useState<boolean>(false);
-    const activeContext = useActiveMission();
+    const [logs, setLogs] = useState<string[]>([]);
+    const socketContext = useSocketContext();
+    const theme = useTheme();
 
     const exportLog = () => {
-        const blob = new Blob([JSON.stringify(activeContext.logs)], {type: "text/plain;charset=utf-8"});
+        const blob = new Blob([JSON.stringify(socketContext.logs)], {type: "text/plain;charset=utf-8"});
         saveAs(blob, "packet-log.txt");
     }
 
-    const [log, setLog] = useState<string[]>(activeContext.logs.map((log) => log.Log));
-
-    // useEffect(() => {
-    //     if (props.packet) {
-    //         setLog((prev) => [...prev, JSON.stringify(props.packet)]);
-    //         setTimeSincePacket(0);
-    //     }
-    // }, [props.packet]);
-
-    // useEffect(() => {
-    //     setTelemetryConnected(props.telemetryConnected);
-    //     if (telemetryConnected) {
-    //         setTimeSincePacket(0);
-    //     }
-    // }, [props.telemetryConnected]);
+    useEffect(() => {
+        formatLog();
+        setTimeSincePacket(0);
+    }, [socketContext.packet]);
 
     useEffect(() => {
-        if (telemetryConnected) {        
-            const interval = setInterval(() => {
-                setTimeSincePacket(timeSincePacket + 1);    
-            }, 1000);
-            return () => clearInterval(interval);
+        const interval = setInterval(() => {
+            setTimeSincePacket((prev) => prev + 1);
+        }, 1000);
+        return () => {
+            clearInterval(interval);
         }
-    }, [timeSincePacket, telemetryConnected]);
+    }, [timeSincePacket]);
+
+    const formatLog = () => {
+        const newLogs = socketContext.logs.map((packet: Packet) => {
+            if (socketContext.packet.id - packet.id < 2) {
+                return `[${new Date().toLocaleTimeString()}] Id:${packet.id} - Altitude: ${packet.data.altitude}`;
+            }
+            return undefined;
+        }).filter(log => log !== undefined);
+
+        setLogs([...logs, ...newLogs as string[]]);
+    };
 
     return (
         <Paper 
@@ -61,20 +56,23 @@ const TelemetryLog: React.FC<TelemetryLogProps> = (props: TelemetryLogProps) =>
                 height: 'fit-content'
             }}
         >
-            <Stack direction={"column"}>
+            <Stack direction={"column"} width={'100%'}>
                 <TextareaAutosize
                     style={{
                         border: "none",
-                        backgroundColor: "#23282F",
+                        //@ts-ignore
+                        backgroundColor: theme.palette.background.default,
                         color: "#ffffff",
-                        paddingTop: 10,
-                        paddingLeft: 10
+                        paddingTop: '20px',
+                        paddingLeft: '20px',
+                        maxWidth: `100%`,
+                        width: '99%',
                     }}
-                    minRows={10}
-                    maxRows={10}
+                    minRows={50}
+                    maxRows={13}
                     readOnly
                     placeholder="Telemetry Log"
-                    value={log}
+                    value={logs.join('\n')}
                 />
                 <Stack direction={'row'} bottom={0}>
                     {[
@@ -86,25 +84,14 @@ const TelemetryLog: React.FC<TelemetryLogProps> = (props: TelemetryLogProps) =>
                 </Stack>
                 <Stack direction='row' padding={2} alignItems={'center'} justifyContent={'space-between'}>
                     <Stack direction='row' gap={2}>
-                        <Tooltip title="GPS lock">
-                            <Button 
-                                disabled
-                                variant={"contained"} 
-                                sx={{ color: 'grey' }}
-                            > {locked ? <GpsFixedIcon color="success"/> : <GpsOffIcon color="error"/>} </Button>
-                            
-                        </Tooltip>
-                        <Tooltip title="Time since last packet">
-                            <TextField
-                                size="small"
-                                InputProps={{
-                                    startAdornment: <InputAdornment position="start"><TimerIcon/></InputAdornment>,
-                                    endAdornment: <InputAdornment position="end">s</InputAdornment>,
-                                }}
-                                value={timeSincePacket}
-                                sx={{ width: 100 }}
-                            />
-                        </Tooltip>
+                        <Chip
+                            icon={<TimerIcon />}
+                            sx={{ 
+                                borderRadius: 2,
+                                fontWeight: 600 
+                            }}
+                            label={`Time since: ${timeSincePacket} s`}
+                        />
                     </Stack>
                     <Button 
                         variant={"contained"} 
diff --git a/client/src/components/rocket-monitoring/ConnectionDialog.tsx b/client/src/components/rocket-monitoring/ConnectionDialog.tsx
index d94337060..274329a86 100644
--- a/client/src/components/rocket-monitoring/ConnectionDialog.tsx
+++ b/client/src/components/rocket-monitoring/ConnectionDialog.tsx
@@ -1,4 +1,4 @@
-import { Autocomplete, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField, Typography } from "@mui/material";
+import { Autocomplete, Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField, Tooltip, Typography } from "@mui/material";
 import React, { useEffect, useState } from "react";
 import { useSocketContext } from "../../utils/socket-context";
 
@@ -12,12 +12,7 @@ const ConnectionDialog: React.FC<IConnectionDialogProps> = (props: IConnectionDi
     const socketContext = useSocketContext();
     const [protocol, setProtocol] = useState<string>(socketContext.protocol);
     const [frequency, setFrequency] = useState<number>(socketContext.frequency);
-    const [launchAltitude, setLaunchAltitude] = useState<number>();
-
-    // useEffect(() => {
-    //     setProtocol(socketContext.protocol);
-    //     setFrequency(socketContext.frequency);
-    // }, [socketContext.protocol, socketContext.frequency]);
+    const [packetRetrievalFrequency, setPacketRetrievalFrequency] = useState<number>(socketContext.packetStreamingInterval);
 
     return(
         <Dialog open={props.isOpen}>
@@ -59,24 +54,25 @@ const ConnectionDialog: React.FC<IConnectionDialogProps> = (props: IConnectionDi
                             shrink: true,
                         }}
                     />
-                    <TextField
-                        fullWidth
-                        required
-                        value={launchAltitude}
-                        onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
-                            setLaunchAltitude(event.target.value as number);
-                        }}
-                        label="Launch Altitude"
-                        variant="outlined"
-                        size="small"
-                        type="number"
-                        InputProps={{
-                            endAdornment: <Typography>FT</Typography>,
-                        }}
-                        InputLabelProps={{
-                            shrink: true,
-                        }}
-                    />
+                    <Tooltip 
+                        title="How often packets are retrieved from the Telemetry server"
+                    >
+                        <TextField
+                            fullWidth
+                            required
+                            value={packetRetrievalFrequency}
+                            onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
+                                setPacketRetrievalFrequency(event.target.value as number);
+                            }}
+                            label="Packet Retrieval Frequency"
+                            variant="outlined"
+                            size="small"
+                            type="number"
+                            InputProps={{
+                                endAdornment: <Typography>s</Typography>,
+                            }}
+                        />
+                    </Tooltip>
                 </Stack>
             </DialogContent>
             <DialogActions>
@@ -90,12 +86,12 @@ const ConnectionDialog: React.FC<IConnectionDialogProps> = (props: IConnectionDi
                 <Button 
                     variant={"contained"} 
                     component="label" 
-                    disabled={!protocol || !frequency || !launchAltitude}
+                    disabled={!protocol || !frequency || !packetRetrievalFrequency}
                     onClick={
                         () => {
                             socketContext.updateProtocol(protocol);
                             socketContext.updateFrequency(frequency);
-                            props.updateAltitude(launchAltitude);
+                            socketContext.updatePacketStreamingInterval(packetRetrievalFrequency);
                             socketContext.toggleConnection();
                             props.onClose()
                         }
diff --git a/client/src/components/rocket-monitoring/PublishMission.tsx b/client/src/components/rocket-monitoring/PublishMission.tsx
new file mode 100644
index 000000000..93df346fb
--- /dev/null
+++ b/client/src/components/rocket-monitoring/PublishMission.tsx
@@ -0,0 +1,41 @@
+import React, { useState, useEffect, useMemo } from 'react';
+import { useSocketContext } from '../../utils/socket-context';
+import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from '@mui/material';
+
+interface IPublishMissionDialogProps {
+    isOpen: boolean;
+    onClose: () => void;
+}
+
+const PublishMissionDialog: React.FC<IPublishMissionDialogProps> = (props: IPublishMissionDialogProps) => {
+    const { isOpen, onClose } = props;
+    return (
+        <Dialog
+            open={isOpen}
+            onClose={() => onClose()}
+        >
+            <DialogTitle>
+                Publish Mission
+            </DialogTitle>
+            <DialogContent>
+                
+            </DialogContent>
+            <DialogActions>
+            <Button
+                    variant='text'
+                    onClick={() => onClose()}
+                >
+                    Cancel
+                </Button>
+                <Button
+                    variant='text'
+                    onClick={() => {}}
+                >
+                    Continue
+                </Button>
+            </DialogActions>
+        </Dialog>
+    );
+}
+
+export default PublishMissionDialog;
\ No newline at end of file
diff --git a/client/src/components/rocket-monitoring/TelemetryAltitudeGraph.tsx b/client/src/components/rocket-monitoring/TelemetryAltitudeGraph.tsx
new file mode 100644
index 000000000..45f03909e
--- /dev/null
+++ b/client/src/components/rocket-monitoring/TelemetryAltitudeGraph.tsx
@@ -0,0 +1,73 @@
+import { useTheme } from '@emotion/react';
+import { Paper, Typography } from '@mui/material';
+import React, { useEffect, useState } from 'react';
+import { CartesianGrid, Legend, Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
+import { useSocketContext } from '../../utils/socket-context';
+
+const TelemetryAltitudeGraph: React.FC = () => {
+    const theme = useTheme();
+    const socketContext = useSocketContext();
+    interface IAltitudeData {
+        Id: number;
+        Altitude: number;
+    }
+    const [data, setData] = useState<IAltitudeData[]>([]);
+    useEffect(() => {
+        if ((socketContext.packet && socketContext.isConnected)) {        
+            setData((prev) => [
+                ...prev, 
+                {
+                    Id: socketContext.packet.id,
+                    Altitude: socketContext.packet.data.altitude
+                }
+            ]);
+        }
+    }, [socketContext.packet]); 
+
+    return (
+        <Paper
+            sx={{
+                borderRadius: 2,
+                padding: 2,
+                zIndex: 4,
+                minWidth: 'fit-content',
+                height: '100%'
+            }}
+        >
+            <Typography variant='h6' fontWeight={600}>
+                Real Time Altitude
+            </Typography>
+            <ResponsiveContainer 
+                width="100%" 
+                height={208} 
+            >
+                <LineChart 
+                    data={data}
+                >
+                    <XAxis 
+                        dataKey="Id" 
+                        domain={['auto', 'auto']} 
+                    />
+                    <YAxis 
+                        orientation='right' 
+                        dataKey={"Altitude"}  
+                        domain={[0, 'auto']}
+                    />
+                    <Tooltip />
+                    {/* <Legend/> */}
+                    <Line
+                        type="monotone"
+                        dataKey="Altitude"
+                        //@ts-ignore
+                        stroke={theme.palette.uvr.red}
+                        strokeWidth={3}
+                        activeDot={{ r: 8 }}
+                        isAnimationActive={false}
+                    />
+                </LineChart>
+            </ResponsiveContainer>
+        </Paper>
+    );
+};
+
+export default TelemetryAltitudeGraph;
diff --git a/client/src/components/rocket-monitoring/TelemetryMap.tsx b/client/src/components/rocket-monitoring/TelemetryMap.tsx
new file mode 100644
index 000000000..bec3c71e8
--- /dev/null
+++ b/client/src/components/rocket-monitoring/TelemetryMap.tsx
@@ -0,0 +1,183 @@
+import React, { useState, useEffect, useMemo, useCallback } from 'react';
+import { useSocketContext } from '../../utils/socket-context';
+import "leaflet.offline";
+import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
+import { Button, Chip, Drawer, Fab, Paper, Stack, Typography, useTheme } from '@mui/material';
+import { useActiveMission } from '../../utils/ActiveMissionContext';
+import { LocationOn } from '@mui/icons-material';
+
+interface ITelemetryMapProps {
+    width?: number;
+}
+
+const TelemetryMap: React.FC<ITelemetryMapProps> = (props: ITelemetryMapProps) => {
+    const { width } = props;
+
+    const theme = useTheme();
+    const socketContext = useSocketContext();
+    const activeMissionContext = useActiveMission();
+
+    const [offlineMap, setOfflineMap] = useState<boolean>(false);
+    const [mapState, setMapState] = useState(null);
+    const [mapDetails, setMapDetails] = useState<boolean>(false);
+        
+    // UVic Coordinates 48.461140, -123.310637
+    const position = [
+        activeMissionContext.activeMission.Coordinates.Latitude, 
+        activeMissionContext.activeMission.Coordinates.Longitude,
+    ];
+
+
+    const displayMap = useMemo(() => (
+        <MapContainer 
+            //@ts-ignore
+            center={position}
+            //@ts-ignore
+            ref={setMapState}
+            zoom={13} 
+            scrollWheelZoom={true} 
+            style={{ 
+                height: 180, 
+                width: '100%', 
+                borderRadius: '5px', 
+                zIndex: 4 
+            }}
+        >
+            
+            <TileLayer
+                attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
+                url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
+            />
+            <Marker 
+                //@ts-ignore
+                position={position}
+            >
+                <Popup>
+                    Launch Position
+                </Popup>
+            </Marker>
+        </MapContainer>
+    ), []);
+
+    // @ts-ignore
+    interface IMapProps {
+        map: any;
+    }
+
+    const CurrentPosition = (props: IMapProps) => {
+        const { map } = props;
+
+        const [currentMapPosition, setCurrentMapPosition] = useState(() => map.getCenter() as any);
+    
+        const onMove = useCallback(() => {
+            setCurrentMapPosition(map.getCenter())
+        }, [map]);
+    
+        useEffect(() => {
+            map.on('move', onMove)
+            return () => {
+                map.off('move', onMove)
+            }
+        }, [map, onMove]);
+
+        return (
+            <Stack direction='row' spacing={1}>
+                <Chip
+                    icon={<LocationOn />}
+                    //@ts-ignore
+                    label={`(${currentMapPosition.lat.toFixed(4)}, ${currentMapPosition.lng.toFixed(4)})`}
+                    color='default'
+                    sx={{ 
+                        fontWeight: 600, 
+                        borderRadius: 2 
+                    }}
+                />
+            </Stack>
+        );
+    };
+
+    return (
+        <Paper
+            sx={{
+                borderRadius: 2,
+                padding: 2,
+                width: width || '100%',
+                minWidth: 'fit-content',
+                height: 'fit-content'
+            }}
+        >
+            <Stack direction={'column'} spacing={2}>
+                <Drawer
+                    sx={{
+                        position: 'relative',
+                        flexShrink: 0,
+                        '& .MuiDrawer-paper': {
+                            borderRadius: 2,
+                            padding: 2,
+                            boxShadow: 5,
+                            height: 'fit-content',
+                            width: '100%',
+                            boxSizing: 'border-box',
+                            position: 'absolute',
+                            backgroundColor: theme.palette.background.paper
+                        },
+                        backgroundColor: theme.palette.background.paper,
+                        width: 400,
+                        height: 'fit-content',
+                        zIndex: 5,
+                        // boxShadow: 5
+                    }}
+                    elevation={3}
+                    variant="persistent"
+                    open={mapDetails}
+                    onClose={() => setMapDetails(false)}
+                    transitionDuration={{ 
+                        enter: theme.transitions.duration.leavingScreen, 
+                        exit: theme.transitions.duration.leavingScreen 
+                    }}
+                    anchor='right'
+                >
+                    <Typography 
+                        variant='subtitle1'
+                        fontWeight={600}
+                    >
+                        Launch Coordinates
+                    </Typography>
+                    <Stack direction='row' spacing={1}>
+                        <Chip 
+                            sx={{
+                                width: '100%',
+                                borderRadius: 2,
+                                fontWeight: 600
+                            }}
+                            label={`Longitude: ${activeMissionContext.activeMission.Coordinates.Longitude}`}
+                            color='default'
+                        />
+                        <Chip 
+                            sx={{
+                                width: '100%',
+                                borderRadius: 2,
+                                fontWeight: 600
+                            }}
+                            label={`Latitude: ${activeMissionContext.activeMission.Coordinates.Latitude}`}
+                            color='default'
+                        />
+                    </Stack>
+                </Drawer>
+                { displayMap }
+                <Stack direction={'row'} justifyContent={'space-between'} alignItems={'center'}>
+                    { mapState ? <CurrentPosition map={mapState} /> : null }
+                    <Button
+                        variant='contained'
+                        onClick={() => setMapDetails(!mapDetails)}
+                    >
+                        Map Details
+                    </Button>
+                </Stack>
+            </Stack>
+           
+        </Paper>
+    );
+}
+
+export default TelemetryMap;
\ No newline at end of file
diff --git a/client/src/components/rocket-monitoring/TelemetryPacket.tsx b/client/src/components/rocket-monitoring/TelemetryPacket.tsx
index a598fcc2a..c0641d39a 100644
--- a/client/src/components/rocket-monitoring/TelemetryPacket.tsx
+++ b/client/src/components/rocket-monitoring/TelemetryPacket.tsx
@@ -1,27 +1,26 @@
 import { Button, Chip, Icon, Paper, Stack, Typography } from '@mui/material';
 import React, { useEffect, useState } from 'react';
 import { useSocketContext } from '../../utils/socket-context';
-import { Badge, Height, ShowChart, HorizontalRule, Timer } from '@mui/icons-material';
+import { Badge, Height, ShowChart, HorizontalRule, Timer, Numbers } from '@mui/icons-material';
 
 
 
 const TelemetryPacket: React.FC = () => {
     const socketContext = useSocketContext();
 
-    const [timeSincePacket, setTimeSincePacket] = useState<number>(0);
+    const [packetId, setPacketId] = useState<number>(0);
+    const [altitude, setAltitude] = useState<number>(0);
 
     useEffect(() => {
         if ((socketContext.packet && socketContext.isConnected)) {        
-            const interval = setInterval(() => {
-                setTimeSincePacket(timeSincePacket + 1);    
-            }, 1000);
-            return () => clearInterval(interval);
+            setPacketId(socketContext.packet.id);
+            setAltitude(socketContext.packet.data.altitude);
         }
-    }, [timeSincePacket, socketContext.packet]);
+    }, [packetId, socketContext.packet]);
 
     interface IPacketContent {
         label: string;
-        value: string;
+        value: string | number;
         icon: any;
     }
 
@@ -33,37 +32,33 @@ const TelemetryPacket: React.FC = () => {
         },
         {
             label: 'Altitude',
-            value: 'ft (ALG)',
+            value: `${altitude} ft`,
             icon: <ShowChart />
         },
         {
             label: 'Longitude',
-            value: '0.00 °',
+            value: 'NAN °',
             icon: <Height />
         },
         {
             label: 'Latitude',
-            value: '0.00 °',
+            value: 'NAN °',
             icon: <HorizontalRule />
         }
-    ]
-
-    useEffect(() => {
-        console.log(socketContext.packet);
-    }, [socketContext.packet]);
+    ];
 
     return (
         <Paper
             sx={{     
                 borderRadius: 2,
                 padding: 2,
-                width: 300,
                 minWidth: 'fit-content',
+                height: 'fit-content'
             }}
         >
             <Stack spacing={2} direction={'column'}>
                 <Typography variant='h6' fontWeight={600}>
-                    Current Packet
+                    Most Recent Packet
                 </Typography>
                 {content.map((item: IPacketContent, index: number) => (
                     <Stack direction={'row'} justifyContent={'space-between'} key={index}>
@@ -85,14 +80,14 @@ const TelemetryPacket: React.FC = () => {
                     </Stack>
                 ))}
                 <Chip 
-                    label={`Time Since: ${timeSincePacket}s`}
-                    icon={<Timer />}
+                    label={`Packet Id: ${packetId}`}
+                    icon={<Numbers />}
                     size='medium'
                     sx={{ 
                         fontWeight: 600, 
                         borderRadius: 1 
                     }} 
-                    color='default'
+                    color='primary'
                 />
             </Stack>
         </Paper>
diff --git a/client/src/components/rocket-monitoring/TelemetryStatus.tsx b/client/src/components/rocket-monitoring/TelemetryStatus.tsx
index 169b7c7e2..389a24913 100644
--- a/client/src/components/rocket-monitoring/TelemetryStatus.tsx
+++ b/client/src/components/rocket-monitoring/TelemetryStatus.tsx
@@ -21,13 +21,10 @@ const TelemetryStatus: React.FC<ITelemetryStatusProps> = (props: ITelemetryStatu
         setFrequency(socketContext.frequency);
     }, [socketContext.frequency]);
 
-    const [ launchAltitude, setLaunchAltitude ] = useState<number>(0);
-
+    const [packetRetrievalFrequency, setPacketRetrievalFrequency] = useState<number>(socketContext.packetStreamingInterval);
     useEffect(() => {
-        if (props.launchAltitude) {
-            setLaunchAltitude(props.launchAltitude);
-        }
-    }, [props.launchAltitude]);
+        setPacketRetrievalFrequency(socketContext.packetStreamingInterval);
+    }, [socketContext.packetStreamingInterval]);
 
     return (
         <Paper
@@ -54,7 +51,7 @@ const TelemetryStatus: React.FC<ITelemetryStatusProps> = (props: ITelemetryStatu
                         </IconButton>
                     </Tooltip>
                     <Chip
-                        label={`Launch Altitude: ${launchAltitude} FT`} 
+                        label={`Packet retrieval rate: ${packetRetrievalFrequency} s`} 
                         sx={{ 
                             fontWeight: 600, 
                             borderRadius: 1 
diff --git a/client/src/utils/socket-context.tsx b/client/src/utils/socket-context.tsx
index 5d98850ac..d6a04e313 100644
--- a/client/src/utils/socket-context.tsx
+++ b/client/src/utils/socket-context.tsx
@@ -9,9 +9,14 @@ import {
 } from 'react';
 import useWebSocket from 'react-use-websocket';
 
-interface IPacket {
-	Data: {};
-	Type: string;
+export type Packet = {
+	data: {
+		altitude: number;
+		latitude?: number;
+		longitude?: number;
+		call_sign?: string;
+	};
+	id: number;
 }
 
 enum Protocol {
@@ -19,12 +24,20 @@ enum Protocol {
 	LoRa = 'LoRa'
 }
 
+type IncomingPacket = {
+	type: 'data' | 'no_data_available';
+	packets: Packet[];
+	last_id: number;
+}; 
+
 export interface SocketContext {
 	gpsLock: boolean;
 	isConnected: boolean;
+	packetStreamingInterval: number;
+	updatePacketStreamingInterval: (interval: number) => void;
 	toggleConnection: () => void;
-	logs: string[];
-	packet: IPacket;
+	logs: Packet[];
+	packet: Packet;
 	frequency: number;
 	updateFrequency: (frequency: number) => void;
 	protocol: string;
@@ -34,16 +47,18 @@ export interface SocketContext {
 export const Context = createContext<SocketContext>({
 	gpsLock: false,
 	isConnected: false,
+	packetStreamingInterval: 2,
+	updatePacketStreamingInterval: (interval: number) => {},
 	toggleConnection: () => {},
 	logs: [],
-	packet: {} as IPacket,
+	packet: {} as Packet,
 	frequency: 433.92,
 	updateFrequency: (frequency: number) => {},
 	protocol: Protocol.APRS,
 	updateProtocol: (protocol: Protocol) => {}
 });
 
-function packetReducer(state: IPacket, action: { type: string; payload: any }) {
+function packetReducer(state: Packet, action: { type: string; payload: any }) {
 	switch (action.type) {
 		case 'SET_PACKET':
 			return action.payload;
@@ -54,40 +69,33 @@ function packetReducer(state: IPacket, action: { type: string; payload: any }) {
 }
 
 export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
-	const [logs, setLogs] = useState<string[]>([]);
-	const [packet, packetDispatch] = useReducer(packetReducer, {} as IPacket);
+	const [logs, setLogs] = useState<Packet[]>([]);
+	const [packet, packetDispatch] = useReducer(packetReducer, {} as Packet);
 	const [frequency, setFrequency] = useState<number>(433.92);
 	const [protocol, setProtocol] = useState<string>('APRS');
     const [isConnected, setIsConnect] = useState<boolean>(false);
 	const [gpsLock, setGpsLock] = useState<boolean>(false);
 	const [wsError, setWsError] = useState<any | null>(null);
+	const [packetStreamingInterval, setPacketStreamingInterval] = useState<number>(2); // Packets collected from the telemetry server sent every X seconds
 
 	const port = import.meta.env.TELEMETRY_SERVER_PORT ? import.meta.env.TELEMETRY_SERVER_PORT : 9193;
 	const [uri, setUri] = useState<string | null>(isConnected ? `ws://localhost:${9193}` : null);
 
-	useEffect(() => {
-		if (frequency) {
-			console.log('Frequency:', frequency);	
-		}
-
-		if (protocol) {
-			console.log('Protocol:', protocol);
-		}
-	}, [frequency, protocol]);
 
 	const updateFrequency = (frequency: number) => {
-		console.log('Frequency:', frequency);
 		setFrequency(frequency);
 	};
 
 	const updateProtocol = (protocol: Protocol) => {
-		console.log('Protocol:', protocol);
 		setProtocol(protocol);
 	};
 
+	const updatePacketStreamingInterval = (interval: number) => {
+		setPacketStreamingInterval(interval);
+	}
+
 	const {
 		sendJsonMessage,
-        lastMessage,
         lastJsonMessage
 	} = useWebSocket(uri, {
 		shouldReconnect: (_) => isConnected,
@@ -102,6 +110,7 @@ export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
 		onOpen: () => {
 			console.log('Telemetry socket Opened');
 			setIsConnect(true);
+			sendJsonMessage({ type: 'establish_stream', frequency: packetStreamingInterval });
 		},
 		share: true
 	});
@@ -119,17 +128,18 @@ export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
         });
     };
 
-	useEffect(() => {
-		console.log('Last Message:', lastMessage);
-		if (lastMessage) {
-			setLogs([...logs, lastMessage]);
+
+	const handleIncomingPacket = (data: IncomingPacket) => {
+		if (data.type === 'data') {
+			const packet = data.packets.find((packet) => packet.id === data.last_id);
+			packetDispatch({ type: 'SET_PACKET', payload: packet });
+			setLogs((prevLogs) => [...prevLogs, ...data.packets]);	
 		}
-	}, [lastMessage]);
+	}
 
 	useEffect(() => {
-		console.log('Last JSON Message:', lastJsonMessage);
 		if (lastJsonMessage) {
-			packetDispatch({ type: 'SET_PACKET', payload: lastJsonMessage });
+			handleIncomingPacket(lastJsonMessage as IncomingPacket);
 		}
 	}, [lastJsonMessage]);
 
@@ -138,6 +148,8 @@ export const SocketGateway = ({ children }: PropsWithChildren<any>) => {
 			value={{
 				isConnected,
 				gpsLock,
+				packetStreamingInterval,
+				updatePacketStreamingInterval,
 				toggleConnection,
 				logs,
 				packet,
diff --git a/client/src/views/active/rocket-monitoring-view.tsx b/client/src/views/active/rocket-monitoring-view.tsx
index 4ed86ec85..f044bbac7 100644
--- a/client/src/views/active/rocket-monitoring-view.tsx
+++ b/client/src/views/active/rocket-monitoring-view.tsx
@@ -2,16 +2,19 @@ import React, { useCallback, useEffect, useState } from 'react';
 
 import api from '../../services/api';
 import { IRocketPopulated } from '../../utils/entities';
-import { Alert, Box, Button, ButtonGroup, Skeleton, Stack, Tooltip } from '@mui/material';
+import { Alert, AlertTitle, Box, Button, ButtonGroup, IconButton, Link, Skeleton, Snackbar, SnackbarContent, Stack, Tooltip, Typography } from '@mui/material';
 import Header, { Breadcrumb } from '../../components/Header';
 import { ViewKeys } from '../../utils/viewProviderContext';
 import { useActiveMission } from '../../utils/ActiveMissionContext';
-import { Settings, WifiTethering } from '@mui/icons-material';
+import { Chat, Settings, WifiTethering, Save, Close } from '@mui/icons-material';
 import TelemetryStatus from '../../components/rocket-monitoring/TelemetryStatus';
 import ConnectionDialog from '../../components/rocket-monitoring/ConnectionDialog';
 import { useSocketContext } from '../../utils/socket-context';
 import TelemetryPacket from '../../components/rocket-monitoring/TelemetryPacket';
 import TelemetryLog from '../../components/logging/TelemetryLog';
+import TelemetryAltitudeGraph from '../../components/rocket-monitoring/TelemetryAltitudeGraph';
+import TelemetryMap from '../../components/rocket-monitoring/TelemetryMap';
+import { useTheme } from '@emotion/react';
 
 interface IRocketMonitoringViewProps {
     // rocketId: string;
@@ -32,6 +35,7 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
     }, []);
     
     const activeContext = useActiveMission();
+    const theme = useTheme();
 
     const breadCrumbs: Breadcrumb[] = [
 		{ name: "Ground Support", viewKey: ViewKeys.PLATFORM_SELECTION_KEY, active: false },
@@ -43,6 +47,7 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
     const socketContext = useSocketContext();
     const [launchAltitude, setLaunchAltitude] = useState<number>(0);
     const [loading, setLoading] = useState<boolean>(true);
+    const [publishMission, setPublishMission] = useState<boolean>(false);
 
     useEffect(() => {
         if (socketContext.isConnected) {
@@ -58,7 +63,7 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
 
 
     return (
-        <Box sx={{ width: '100vw', height: '100vh' }}>
+        <Box sx={{ width: '100vw', height: '100vh', overflowX: 'none' }}>
             <Stack
                 height={'100%'}
                 padding={4}
@@ -69,49 +74,87 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                 <Stack direction="row" alignItems={'center'} justifyContent={'space-between'}>
                     <Header icon="ROCKET_MONITORING" breadCrumbs={breadCrumbs} />
                     <Stack direction="row" spacing={1}>
-                        <Tooltip title="Configuration" placement="top">
-                            <Button variant={'text'} onClick={() => {}}>
-                                <Settings />
-                            </Button>
-                        </Tooltip>
-                        <ButtonGroup
-                            variant="contained"
-                        >
-                            <Button 
-                                aria-readonly
-                                disabled
-                                color={"success"}
+                        <Stack direction="column" spacing={1}>
+                            <ButtonGroup
+                                variant="contained"
                             >
-                                Server Connected
-                            </Button>
-                            <Button 
-                                variant="contained" 
-                                size={'large'} 
-                                startIcon={<WifiTethering/>} 
-                                sx={{ width: 180 }}
-                                onClick={() => {
-                                    setOpenConnection(!openConnection);  
-                                }}
+                                <Button
+                                    startIcon={<Save/>}
+                                    disabled={!socketContext.isConnected}
+                                    onClick={() => setPublishMission(true)}
+                                >
+                                    Publish Mission
+                                </Button>
+                                <Button 
+                                    variant="contained" 
+                                    size={'large'} 
+                                    startIcon={<WifiTethering/>} 
+                                    onClick={() => {
+                                        if (socketContext.isConnected) {
+                                            socketContext.toggleConnection();
+                                        } else {
+                                            setOpenConnection(!openConnection);  
+                                        }
+                                    }}
+                                >
+                                    {socketContext.isConnected ? "Disconnect" : "Connect"}
+                                </Button>
+                            </ButtonGroup>
+                            <Snackbar
+                                open={publishMission}
+                                anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
                             >
-                                {socketContext.isConnected ? "Disconnect" : "Connect"}
-                            </Button>
-                        </ButtonGroup>
+                                <Alert 
+                                    severity="warning"
+                                    onClose={() => setPublishMission(false)}
+                                >
+                                    <AlertTitle>Are you sure?</AlertTitle>
+                                    <Stack alignItems={'end'} gap={1}>
+                                        Once a mission is published no more data can be recorded and the mission be available in replay mode only.  
+                                        <Button
+                                            color="inherit"
+                                            variant='contained'
+                                            size="small"
+                                            sx={{
+                                                width: 'fit-content',
+                                            }}
+                                            onClick={() => {
+                                                activeContext.activeMission.Published = true;
+                                                // TODO: update mission with published status & data
+                                                setPublishMission(false)
+                                            }}
+                                        >
+                                            Continue
+                                        </Button>
+                                    </Stack>
+                                </Alert>
+                            </Snackbar>
+                        </Stack>
                     </Stack>
                 </Stack>
                 <TelemetryStatus launchAltitude={launchAltitude} />
                 {socketContext.isConnected ? loading ? (
-                    <Stack direction="row" gap={2}>
-                        <Skeleton variant="rectangular" height={300} width={'30%'} sx={{ borderRadius: 2 }} />
-                        <Skeleton variant="rectangular" height={300} width={'70%'} sx={{ borderRadius: 2 }} />
+                    <Stack gap={2}>
+                        <Stack direction="row" gap={2}>
+                            <Skeleton variant="rectangular" height={300} width={'20%'} sx={{ borderRadius: 2 }} />
+                            <Skeleton variant="rectangular" height={300} width={'40%'} sx={{ borderRadius: 2 }} />
+                            <Skeleton variant="rectangular" height={300} width={'40%'} sx={{ borderRadius: 2 }} />
+                        </Stack>
+                        <Skeleton variant="rectangular" height={300} width={'100%'} sx={{ borderRadius: 2 }} />
                     </Stack>
+
                 ) :(
-                    <Stack direction="row" gap={2}>
-                        <TelemetryPacket />
-                        <TelemetryLog />
-                    </Stack>
+                        <Stack direction="column" gap={2} width={'100%'}>
+                            <Stack direction="row" gap={2}>
+                                <TelemetryPacket />
+                                <TelemetryLog />
+                                <TelemetryMap />
+                            </Stack>
+                            <TelemetryAltitudeGraph />
+                        </Stack>
                 ) : (
                     <Alert severity="info">
-                        Connect to telemetry to see data
+                        Connect to telemetry to see data. <Link href='#' color={'inherit'}>FAQ</Link> for common issues and solutions.
                     </Alert>
                 )}
             </Stack>
diff --git a/services/telemetry/tools/decode_test.sh b/services/telemetry/tools/decode_test.sh
index 753783a52..817810ba6 100755
--- a/services/telemetry/tools/decode_test.sh
+++ b/services/telemetry/tools/decode_test.sh
@@ -1,2 +1,2 @@
 # -u = no buffering on stdout.
-python3 -u ./tools/test_data.py
\ No newline at end of file
+python -u ./tools/test_data.py
\ No newline at end of file

From d995a3128091fd2f7a8848eb7f3cc7086bc6721c Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Sat, 15 Jun 2024 22:05:33 -0700
Subject: [PATCH 10/14] Mission Data Saving + mission Publishing logic + export
 data on publish mission click

---
 .../src/components/logging/TelemetryLog.tsx   |  2 +-
 client/src/utils/entities.ts                  | 11 ++++
 .../views/active/rocket-monitoring-view.tsx   | 63 ++++++++++++++++---
 client/src/views/tabs/rocket-missions-tab.tsx | 49 ++++++++++-----
 services/server/src/models/MissionModel.ts    |  9 ++-
 .../server/src/models/TelemetryPacketModel.ts | 27 ++++++++
 services/server/src/routes/MissionRoute.ts    |  3 +-
 7 files changed, 133 insertions(+), 31 deletions(-)
 create mode 100644 services/server/src/models/TelemetryPacketModel.ts

diff --git a/client/src/components/logging/TelemetryLog.tsx b/client/src/components/logging/TelemetryLog.tsx
index 00593eb71..b80017684 100644
--- a/client/src/components/logging/TelemetryLog.tsx
+++ b/client/src/components/logging/TelemetryLog.tsx
@@ -69,7 +69,7 @@ const TelemetryLog: React.FC = () => {
                         width: '99%',
                     }}
                     minRows={50}
-                    maxRows={13}
+                    maxRows={12}
                     readOnly
                     placeholder="Telemetry Log"
                     value={logs.join('\n')}
diff --git a/client/src/utils/entities.ts b/client/src/utils/entities.ts
index b0671d3e1..5816535a5 100644
--- a/client/src/utils/entities.ts
+++ b/client/src/utils/entities.ts
@@ -107,3 +107,14 @@ export interface IDataPoint {
     FieldId: string;
     PacketNumber: number;
 };
+
+export interface ITelemetryPacket {
+    _id?: string;
+    Data: {
+        Altitude: number;
+        Latitude?: number;
+        Longitude?: number;
+        CallSign?: string;
+    };
+    PacketId: number;
+}
diff --git a/client/src/views/active/rocket-monitoring-view.tsx b/client/src/views/active/rocket-monitoring-view.tsx
index f044bbac7..c9239ef99 100644
--- a/client/src/views/active/rocket-monitoring-view.tsx
+++ b/client/src/views/active/rocket-monitoring-view.tsx
@@ -1,10 +1,10 @@
 import React, { useCallback, useEffect, useState } from 'react';
 
 import api from '../../services/api';
-import { IRocketPopulated } from '../../utils/entities';
+import { IRocketPopulated, ITelemetryPacket } from '../../utils/entities';
 import { Alert, AlertTitle, Box, Button, ButtonGroup, IconButton, Link, Skeleton, Snackbar, SnackbarContent, Stack, Tooltip, Typography } from '@mui/material';
 import Header, { Breadcrumb } from '../../components/Header';
-import { ViewKeys } from '../../utils/viewProviderContext';
+import { ViewKeys, useViewProvider } from '../../utils/viewProviderContext';
 import { useActiveMission } from '../../utils/ActiveMissionContext';
 import { Chat, Settings, WifiTethering, Save, Close } from '@mui/icons-material';
 import TelemetryStatus from '../../components/rocket-monitoring/TelemetryStatus';
@@ -15,13 +15,13 @@ import TelemetryLog from '../../components/logging/TelemetryLog';
 import TelemetryAltitudeGraph from '../../components/rocket-monitoring/TelemetryAltitudeGraph';
 import TelemetryMap from '../../components/rocket-monitoring/TelemetryMap';
 import { useTheme } from '@emotion/react';
+// import upsertMissionData from '../../services/api';
 
 interface IRocketMonitoringViewProps {
     // rocketId: string;
 }
 
 const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRocketMonitoringViewProps) => {
-    const [rocket, setRocket] = useState<IRocketPopulated>({} as IRocketPopulated);
 
     // const getActiveRocket = useCallback(async (): Promise<IRocketPopulated> => {
     //     const response = await api.getRocket(rocketId || '');
@@ -35,6 +35,8 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
     }, []);
     
     const activeContext = useActiveMission();
+    const socketContext = useSocketContext();
+    const viewProviderContext = useViewProvider();
     const theme = useTheme();
 
     const breadCrumbs: Breadcrumb[] = [
@@ -44,10 +46,11 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
 	];
 
     const [openConnection, setOpenConnection] = useState<boolean>(false);
-    const socketContext = useSocketContext();
     const [launchAltitude, setLaunchAltitude] = useState<number>(0);
     const [loading, setLoading] = useState<boolean>(true);
     const [publishMission, setPublishMission] = useState<boolean>(false);
+    const [publishFeedback, setPublishFeedback] = useState<boolean>(false);
+    const [publishErrorMessage, setPublishErrorMessage] = useState<string>('');
 
     useEffect(() => {
         if (socketContext.isConnected) {
@@ -61,6 +64,29 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
         }
     }, [socketContext.isConnected]);
 
+    const publishMissionData = async () => {
+        if (!activeContext.activeMission._id) {
+            setPublishErrorMessage("No mission id attached to active mission.");
+            setPublishFeedback(false);
+            return
+        }
+        const missionData = socketContext.logs.map((packet) => (
+            {
+                PacketId: packet.id,
+                Data: {
+                    Altitude: packet.data.altitude,
+                    Latitude: packet.data.latitude,
+                    Longitude: packet.data.longitude,
+                    CallSign: packet.data.call_sign
+                }
+            } as ITelemetryPacket
+            ));
+        console.log(missionData);
+        const response = await api.upsertMissionData(activeContext.activeMission._id, missionData);
+        response.error.error ? setPublishErrorMessage(`Api error: ${response.error.status}`) : setPublishFeedback(true);
+
+    };
+
 
     return (
         <Box sx={{ width: '100vw', height: '100vh', overflowX: 'none' }}>
@@ -119,12 +145,18 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                                                 width: 'fit-content',
                                             }}
                                             onClick={() => {
-                                                activeContext.activeMission.Published = true;
-                                                // TODO: update mission with published status & data
-                                                setPublishMission(false)
+                                                publishMissionData();
+                                                setPublishMission(false);
+                                                // wait a few seconds before changing view
+                                                
+                                                // if (publishFeedback) {
+                                                    setTimeout(() => {
+                                                        viewProviderContext.updateViewKey(ViewKeys.ROCKET_DETAILS_KEY);
+                                                    }, 2000);
+                                                // }
                                             }}
                                         >
-                                            Continue
+                                            Publish
                                         </Button>
                                     </Stack>
                                 </Alert>
@@ -143,7 +175,7 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                         <Skeleton variant="rectangular" height={300} width={'100%'} sx={{ borderRadius: 2 }} />
                     </Stack>
 
-                ) :(
+                ) : (
                         <Stack direction="column" gap={2} width={'100%'}>
                             <Stack direction="row" gap={2}>
                                 <TelemetryPacket />
@@ -154,10 +186,21 @@ const RocketMonitoringView: React.FC<IRocketMonitoringViewProps> = (props: IRock
                         </Stack>
                 ) : (
                     <Alert severity="info">
-                        Connect to telemetry to see data. <Link href='#' color={'inherit'}>FAQ</Link> for common issues and solutions.
+                        Connect to telemetry to see data. See the <Link href='#' color={'inherit'}>FAQ</Link> for common issues and solutions.
                     </Alert>
                 )}
             </Stack>
+            <Snackbar  
+                open={publishFeedback}
+                autoHideDuration={6000}
+                onClose={() => setPublishFeedback(false)}
+            >
+                <Alert 
+                    severity={publishErrorMessage ? 'error' : 'success'}
+                >
+                    { publishErrorMessage ? publishErrorMessage : "Mission data published successfully." }
+                </Alert>
+            </Snackbar>
             <ConnectionDialog 
                 updateAltitude={setLaunchAltitude} 
                 isOpen={openConnection} 
diff --git a/client/src/views/tabs/rocket-missions-tab.tsx b/client/src/views/tabs/rocket-missions-tab.tsx
index 133a620c8..35f85acb1 100644
--- a/client/src/views/tabs/rocket-missions-tab.tsx
+++ b/client/src/views/tabs/rocket-missions-tab.tsx
@@ -6,17 +6,19 @@ import { ViewKeys, useViewProvider } from '../../utils/viewProviderContext';
 import { Alert, Chip, Icon, IconButton, Snackbar, Stack, Typography } from '@mui/material';
 import { CheckBox, CheckBoxOutlineBlank, ShowChart, Height, HorizontalRule, Edit, DateRange } from '@mui/icons-material';
 import MissionConfig from '../../components/MissionConfig';
+import { saveAs } from 'file-saver';
 
 interface FormattedMissionData {
     _id?: string;
     Name: string;
     Date: Date;
-    IsTest: string;
+    IsTest: boolean;
     Longitude: number;
     Latitude: number;
     LaunchAltitude: Number;
-    Published: string;
+    Published: boolean;
     Components: string[];
+    Data?: any;
 }
 
 interface ITableColumns {
@@ -40,18 +42,29 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
     const viewProviderContext = useViewProvider();
     const activeMissionContext = useActiveMission();
 
+    const exportMissionData = (mission: FormattedMissionData) => {
+        if (!mission.Data) {
+            return;
+        }
+        if (mission.Published) {
+            const blob = new Blob([JSON.stringify(mission.Data)], { type: 'application/json' });
+            saveAs(blob, `${mission.Name}-data.json`);
+        }
+    }
+
     const getMissions = useCallback(async () => {
         rocket.Missions.map(async (mission: IMission) => {
             const m: FormattedMissionData = {
                 _id: mission._id,
                 Name: mission.Name,
                 Date: mission.Date,
-                IsTest: String(mission.IsTest),
+                IsTest: mission.IsTest,
                 Longitude: mission.Coordinates.Longitude,
                 Latitude: mission.Coordinates.Latitude,
                 LaunchAltitude: mission.LaunchAltitude,
-                Published: String(mission.Published),
-                Components: mission.Components
+                Published: mission.Published,
+                Components: mission.Components,
+                Data: mission.Data,
             };
             if (missions.length === 0) {
                 missions.push(m);
@@ -69,15 +82,19 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
     const options: MUIDataTableOptions = {
         filter: true,
         responsive: 'standard',
-        onCellClick: (colData, cellMeta) => {
+        onCellClick: (_, cellMeta) => {
             if (cellMeta.colIndex === 0) {
                 return;
             }
-            console.log(colData, cellMeta);
-            console.log('Navigating to mission replay view');
             activeMissionContext.updateMission(rocket.Missions[cellMeta.dataIndex]);
             activeMissionContext.updateRocket(rocket);
-            viewProviderContext.updateViewKey(ViewKeys.ACTIVE_FLIGHT_KEY)
+            if (rocket.Missions[cellMeta.dataIndex].Published) {
+                exportMissionData(missions[cellMeta.dataIndex]);
+                // TODO: uncomment when view is completed
+                // viewProviderContext.updateViewKey(ViewKeys.MISSION_REPLAY_KEY);
+            } else {
+                viewProviderContext.updateViewKey(ViewKeys.ACTIVE_FLIGHT_KEY)
+            }
         }
     };
     
@@ -115,7 +132,7 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
                 filter: true,
                 sort: true,
                 viewColumns: true,
-                customBodyRender(value, tableMeta, updateValue) {
+                customBodyRender(value) {
                     return (
                         <Typography
                             variant="body1"
@@ -136,7 +153,7 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
                 filter: true,
                 sort: true,
                 viewColumns: true,
-                customBodyRender(value, tableMeta, updateValue) {
+                customBodyRender(value) {
                     return (
                         <Chip
                             icon={<DateRange />}
@@ -157,7 +174,7 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
                 filter: true,
                 sort: true,
                 viewColumns: true,
-                customBodyRender(value, tableMeta, updateValue) {
+                customBodyRender(value) {
                     return (
                         <Chip
                             label={value === "true" ? "Test" : "Mission"}
@@ -177,7 +194,7 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
                 filter: true,
                 sort: true,
                 viewColumns: true,
-                customBodyRender(value, tableMeta, updateValue) {
+                customBodyRender(value) {
                     return (
                         <Stack direction={'row'}>
                             <Height /> 
@@ -194,7 +211,7 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
                 filter: true,
                 sort: true,
                 viewColumns: true,
-                customBodyRender(value, tableMeta, updateValue) {
+                customBodyRender(value) {
                     return (
                         <Stack direction={'row'} spacing={1}>
                             <HorizontalRule /> 
@@ -211,7 +228,7 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
                 filter: true,
                 sort: true,
                 viewColumns: true,
-                customBodyRender(value, tableMeta, updateValue) {
+                customBodyRender(value) {
                     return (
                         <Stack direction={'row'} spacing={1}>
                             <ShowChart /> 
@@ -228,7 +245,7 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
                 filter: true,
                 sort: true,
                 viewColumns: true,
-                customBodyRender(value, tableMeta, updateValue) {
+                customBodyRender(value) {
                     return value ? (<CheckBox color='success' />) : (<CheckBoxOutlineBlank color='grey' />);                  
                 }
             }
diff --git a/services/server/src/models/MissionModel.ts b/services/server/src/models/MissionModel.ts
index ebda1e025..55b52123b 100644
--- a/services/server/src/models/MissionModel.ts
+++ b/services/server/src/models/MissionModel.ts
@@ -1,6 +1,6 @@
 import mongoose, { Document, Schema, Types } from "mongoose";
 import { isValidLongitude, isValidLatitude } from "../library/CoordinateValidation";
-import { DataPointSchema, IDataPoint } from "./DataPointModel";
+import { ITelemetryPacket, TelemetryPacketSchema } from "./TelemetryPacketModel";
 
 interface ICoordinates {
     Latitude: number;
@@ -16,7 +16,7 @@ export interface IMission {
     LaunchAltitude: number;
     Components: [Types.ObjectId];
     Published: boolean;
-    Data?: IDataPoint[];
+    Data?: ITelemetryPacket[];
 };
 
 export interface IMissionModel extends IMission, Document { };
@@ -73,7 +73,10 @@ export const MissionSchema: Schema = new Schema(
             default: false,
             required: true
         },
-        Data: [DataPointSchema]
+        Data: {
+            type: [TelemetryPacketSchema],
+            required: false
+        }
     },
     {
         versionKey: false,
diff --git a/services/server/src/models/TelemetryPacketModel.ts b/services/server/src/models/TelemetryPacketModel.ts
new file mode 100644
index 000000000..fb5180509
--- /dev/null
+++ b/services/server/src/models/TelemetryPacketModel.ts
@@ -0,0 +1,27 @@
+import mongoose, { Document, Schema, Types } from "mongoose";
+
+export interface ITelemetryPacket {
+    Data: {
+        Altitude: number;
+        Latitude?: number;
+        Longitude?: number;
+        CallSign?: string;
+    };
+    Id: number;
+};
+
+export const TelemetryPacketSchema: Schema = new Schema(
+    {
+        Data: {
+            Altitude: Number,
+            Latitude: Number,
+            Longitude: Number,
+            CallSign: String
+        },
+        PacketId: Number
+    },
+    {
+        versionKey: false,
+        timestamps: false
+    }
+);
\ No newline at end of file
diff --git a/services/server/src/routes/MissionRoute.ts b/services/server/src/routes/MissionRoute.ts
index bf499b1a2..6f1f83d46 100644
--- a/services/server/src/routes/MissionRoute.ts
+++ b/services/server/src/routes/MissionRoute.ts
@@ -28,7 +28,8 @@ router.put('/data/:id', async (req: Request, res: Response, next: NextFunction)
                 { 
                     $push: {
                         Data: req.body
-                    }
+                    },
+                    Published: true
                 },
                 {
                     new: true,

From 6d53e47a24bf3fdce17babe39e53f215237be6a1 Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Sat, 15 Jun 2024 22:36:06 -0700
Subject: [PATCH 11/14] Mission Data export dialog + json/txt supported

---
 .../ExportMissionDataDialog.tsx               | 80 +++++++++++++++++++
 client/src/views/tabs/rocket-missions-tab.tsx | 23 +++---
 2 files changed, 94 insertions(+), 9 deletions(-)
 create mode 100644 client/src/components/rocket-monitoring/ExportMissionDataDialog.tsx

diff --git a/client/src/components/rocket-monitoring/ExportMissionDataDialog.tsx b/client/src/components/rocket-monitoring/ExportMissionDataDialog.tsx
new file mode 100644
index 000000000..f3284b72b
--- /dev/null
+++ b/client/src/components/rocket-monitoring/ExportMissionDataDialog.tsx
@@ -0,0 +1,80 @@
+import { Download } from '@mui/icons-material';
+import { Autocomplete, Button, Dialog, DialogActions, DialogContent, DialogTitle, IconButton, Stack, TextField, Tooltip } from '@mui/material';
+import { saveAs } from 'file-saver';
+import React, { useState } from 'react';
+
+interface IExportMissionDataDialogProps {
+    isOpen: boolean;
+    onClose: () => void;
+    mission: any;
+}
+
+const ExportMissionDataDialog: React.FC<IExportMissionDataDialogProps> = (props: IExportMissionDataDialogProps) => {
+    const { isOpen, onClose, mission } = props;
+    const [extensionType, setExtensionType] = useState<any>();
+
+    const exportData = () => {
+        const blob = new Blob([JSON.stringify(mission.Data)], { type: extensionType.value });
+        saveAs(blob, `${mission.Name}-data.${extensionType.label}`);
+        console.log('Exporting mission data');
+    }
+
+    return (
+        <Dialog
+            open={isOpen}
+            onClose={() => onClose()}
+        >
+            <DialogTitle
+                sx={{fontWeight: 600 }}
+            >
+                Export Mission 
+            </DialogTitle>
+            <DialogContent>
+                <Stack direction='row' spacing={2} p={1}>
+                    <Autocomplete 
+                        options={[
+                            {
+                                label: 'json',
+                                value: 'application/json'
+                            },
+                            {
+                                label: 'txt',
+                                value: 'text/plain;charset=utf-8'
+                            }
+                        ]}
+                        fullWidth
+                        value={extensionType}
+                        onChange={(_, value) => setExtensionType(value)}
+                        getOptionLabel={(option) => option.label}
+                        renderInput={(params) => <TextField {...params} label='Extension Type' />}
+                    />
+                    <IconButton
+                        onClick={exportData}
+                        disabled={!extensionType}
+                    >
+                        <Download />
+                    </IconButton>
+                </Stack>
+
+            </DialogContent>
+            <DialogActions>
+                <Button
+                    variant='text'
+                    onClick={() => onClose()}
+                >
+                    Cancel
+                </Button>
+            
+                <Button
+                    variant='text'
+                    onClick={() => {}}
+                    disabled
+                >
+                    Go to Mission Replay
+                </Button>
+            </DialogActions>
+        </Dialog>
+    );
+}
+
+export default ExportMissionDataDialog;
\ No newline at end of file
diff --git a/client/src/views/tabs/rocket-missions-tab.tsx b/client/src/views/tabs/rocket-missions-tab.tsx
index 35f85acb1..4f5bc9cdc 100644
--- a/client/src/views/tabs/rocket-missions-tab.tsx
+++ b/client/src/views/tabs/rocket-missions-tab.tsx
@@ -7,6 +7,7 @@ import { Alert, Chip, Icon, IconButton, Snackbar, Stack, Typography } from '@mui
 import { CheckBox, CheckBoxOutlineBlank, ShowChart, Height, HorizontalRule, Edit, DateRange } from '@mui/icons-material';
 import MissionConfig from '../../components/MissionConfig';
 import { saveAs } from 'file-saver';
+import ExportMissionDataDialog from '../../components/rocket-monitoring/ExportMissionDataDialog';
 
 interface FormattedMissionData {
     _id?: string;
@@ -38,17 +39,14 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
     const [ selectedMissionId, setSelectedMissionId ] = useState<string | null>(null);
     const [missionEditDialog, setMissionEditDialog] = useState<boolean>(false);
     const [tableWarning, setTableWarning] = useState<boolean>(true);
-
+    const [exportMissionDataDialog, setExportMissionDataDialog] = useState<boolean>(false);
+    const [selectedMission, setSelectedMission] = useState<FormattedMissionData | null>(null)
     const viewProviderContext = useViewProvider();
     const activeMissionContext = useActiveMission();
 
     const exportMissionData = (mission: FormattedMissionData) => {
-        if (!mission.Data) {
-            return;
-        }
-        if (mission.Published) {
-            const blob = new Blob([JSON.stringify(mission.Data)], { type: 'application/json' });
-            saveAs(blob, `${mission.Name}-data.json`);
+        if (mission.Data && mission.Published) {
+            setExportMissionDataDialog(true);
         }
     }
 
@@ -86,6 +84,7 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
             if (cellMeta.colIndex === 0) {
                 return;
             }
+            setSelectedMission(missions[cellMeta.dataIndex])
             activeMissionContext.updateMission(rocket.Missions[cellMeta.dataIndex]);
             activeMissionContext.updateRocket(rocket);
             if (rocket.Missions[cellMeta.dataIndex].Published) {
@@ -175,10 +174,11 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
                 sort: true,
                 viewColumns: true,
                 customBodyRender(value) {
+                    console.log(value);
                     return (
                         <Chip
-                            label={value === "true" ? "Test" : "Mission"}
-                            color={value === "true" ? "warning" : "primary"}
+                            label={value ? "Test" : "Mission"}
+                            color={value ? "warning" : "primary"}
                             sx={{
                                 borderRadius: 2,
                             }}
@@ -276,6 +276,11 @@ const RocketDetailsTab: React.FC<Props> = (props: Props) => {
                 onClose={() => setMissionEditDialog(false)}
                 onSave={() => {}}
             />
+            <ExportMissionDataDialog 
+                isOpen={exportMissionDataDialog}
+                onClose={() => setExportMissionDataDialog(false)}
+                mission={selectedMission as FormattedMissionData}
+            />
         </>
     );
 };

From 3a0e1ecb7d2ab2d0e96f72054954cf0751aafcc5 Mon Sep 17 00:00:00 2001
From: Mateo Carreras <mateo.carreras@gmail.com>
Date: Thu, 20 Jun 2024 19:44:27 -0700
Subject: [PATCH 12/14] Add new command line option to telemetry service to use
 binaries in path instead of the vendored

Also fix a bug where the site crashes when empty packets are received
---
 .../TelemetryAltitudeGraph.tsx                | 26 +++++++++----------
 .../rocket-monitoring/TelemetryPacket.tsx     |  4 +--
 services/telemetry/src/main.rs                |  4 +++
 services/telemetry/src/state.rs               |  5 +++-
 services/telemetry/src/telemetry.rs           | 10 +++++--
 services/telemetry/tools/decode_test.sh       |  2 +-
 .../telemetry/tools/decode_with_sys_bin.sh    | 14 ++++++++++
 7 files changed, 46 insertions(+), 19 deletions(-)
 create mode 100644 services/telemetry/tools/decode_with_sys_bin.sh

diff --git a/client/src/components/rocket-monitoring/TelemetryAltitudeGraph.tsx b/client/src/components/rocket-monitoring/TelemetryAltitudeGraph.tsx
index 45f03909e..3673601e8 100644
--- a/client/src/components/rocket-monitoring/TelemetryAltitudeGraph.tsx
+++ b/client/src/components/rocket-monitoring/TelemetryAltitudeGraph.tsx
@@ -13,16 +13,16 @@ const TelemetryAltitudeGraph: React.FC = () => {
     }
     const [data, setData] = useState<IAltitudeData[]>([]);
     useEffect(() => {
-        if ((socketContext.packet && socketContext.isConnected)) {        
+        if ((socketContext.packet && socketContext.isConnected && socketContext.packet?.data)) {
             setData((prev) => [
-                ...prev, 
+                ...prev,
                 {
                     Id: socketContext.packet.id,
                     Altitude: socketContext.packet.data.altitude
                 }
             ]);
         }
-    }, [socketContext.packet]); 
+    }, [socketContext.packet]);
 
     return (
         <Paper
@@ -37,20 +37,20 @@ const TelemetryAltitudeGraph: React.FC = () => {
             <Typography variant='h6' fontWeight={600}>
                 Real Time Altitude
             </Typography>
-            <ResponsiveContainer 
-                width="100%" 
-                height={208} 
+            <ResponsiveContainer
+                width="100%"
+                height={208}
             >
-                <LineChart 
+                <LineChart
                     data={data}
                 >
-                    <XAxis 
-                        dataKey="Id" 
-                        domain={['auto', 'auto']} 
+                    <XAxis
+                        dataKey="Id"
+                        domain={['auto', 'auto']}
                     />
-                    <YAxis 
-                        orientation='right' 
-                        dataKey={"Altitude"}  
+                    <YAxis
+                        orientation='right'
+                        dataKey={"Altitude"}
                         domain={[0, 'auto']}
                     />
                     <Tooltip />
diff --git a/client/src/components/rocket-monitoring/TelemetryPacket.tsx b/client/src/components/rocket-monitoring/TelemetryPacket.tsx
index c0641d39a..d037253d3 100644
--- a/client/src/components/rocket-monitoring/TelemetryPacket.tsx
+++ b/client/src/components/rocket-monitoring/TelemetryPacket.tsx
@@ -12,7 +12,7 @@ const TelemetryPacket: React.FC = () => {
     const [altitude, setAltitude] = useState<number>(0);
 
     useEffect(() => {
-        if ((socketContext.packet && socketContext.isConnected)) {        
+        if ((socketContext.packet && socketContext.isConnected && socketContext.packet?.data)) {
             setPacketId(socketContext.packet.id);
             setAltitude(socketContext.packet.data.altitude);
         }
@@ -94,4 +94,4 @@ const TelemetryPacket: React.FC = () => {
     );
 }
 
-export default TelemetryPacket;
\ No newline at end of file
+export default TelemetryPacket;
diff --git a/services/telemetry/src/main.rs b/services/telemetry/src/main.rs
index b17aae1a8..d1b08555e 100644
--- a/services/telemetry/src/main.rs
+++ b/services/telemetry/src/main.rs
@@ -43,6 +43,10 @@ struct Cli {
     /// server. This is currently meant for testing purposes.
     #[clap(long)]
     client: bool,
+    /// When this option is set, the binaries installed on the system will be
+    /// used instead of the ones in the repo.
+    #[clap(long)]
+    system_binaries: bool
 }
 
 /// A transmission protocol that is used to transmit data from the rocket.
diff --git a/services/telemetry/src/state.rs b/services/telemetry/src/state.rs
index 50a0d5b73..cb2e9dc94 100644
--- a/services/telemetry/src/state.rs
+++ b/services/telemetry/src/state.rs
@@ -19,6 +19,8 @@ pub struct State {
     /// buffer. This offset is used to keep track of the first packet in the
     /// packet buffer.
     pub packet_buffer_offset: u64,
+    /// When true, system-wide binaries are used instead of the ones in the repo.
+    pub system_binaries: bool
 }
 
 impl State {
@@ -31,6 +33,7 @@ impl State {
             last_packet_id: None,
             packet_buffer: Vec::new(),
             packet_buffer_offset: 0,
+            system_binaries: false
         }
     }
 
@@ -86,4 +89,4 @@ impl State {
 
         Some((self_last_id, packets))
     }
-}
\ No newline at end of file
+}
diff --git a/services/telemetry/src/telemetry.rs b/services/telemetry/src/telemetry.rs
index fbf34b793..8a95d4b8e 100644
--- a/services/telemetry/src/telemetry.rs
+++ b/services/telemetry/src/telemetry.rs
@@ -58,13 +58,19 @@ pub async fn start_telemetry(state_ref: Arc<Mutex<State>>) -> io::Result<()> {
     let frequency = state_ref.lock().unwrap().frequency;
     println!("Starting telemetry on {}", frequency);
 
+    let decode_script_path = if state_ref.lock().unwrap().system_binaries {
+        "./tools/decode.sh"
+    } else {
+        "./tools/decode_with_sys_bin.sh"
+    };
+
     let mut decode_aprs = Command::new("sh")
-        .arg("./tools/decode_test.sh")
+        .arg(decode_script_path)
         // .arg(frequency.to_string())
         .stdout(Stdio::piped())
         .spawn()
         .expect("failed to start decoder");
-    
+
     let mut child_out = BufReader::new(decode_aprs.stdout.as_mut().unwrap());
     let mut readbuf = vec![0; 256];
 
diff --git a/services/telemetry/tools/decode_test.sh b/services/telemetry/tools/decode_test.sh
index 817810ba6..bc5366dcd 100755
--- a/services/telemetry/tools/decode_test.sh
+++ b/services/telemetry/tools/decode_test.sh
@@ -1,2 +1,2 @@
 # -u = no buffering on stdout.
-python -u ./tools/test_data.py
\ No newline at end of file
+python3 -u ./tools/test_data.py
diff --git a/services/telemetry/tools/decode_with_sys_bin.sh b/services/telemetry/tools/decode_with_sys_bin.sh
new file mode 100644
index 000000000..713cb33db
--- /dev/null
+++ b/services/telemetry/tools/decode_with_sys_bin.sh
@@ -0,0 +1,14 @@
+#!/usr/bin/env sh
+
+# This script is used to decode APRS packets from the RTL-SDR dongle
+#
+# This version is for using system-wide binaries instead of in the repo.
+
+if [ $# -ge 1 ]
+then
+  FREQ=$1
+else
+  FREQ=433.92M
+fi
+
+rtl_fm -f $FREQ -r 24k -s 260k -o 4 -p 93 -g 49.6 - | direwolf -c ./tools/direwolf.conf -n 1 -r 24000 -b 16 -

From 9f01141698142f81eecb7e421a3700a6086ce9c8 Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Wed, 17 Jul 2024 08:49:47 -0700
Subject: [PATCH 13/14] first 3 comments of PR

---
 client/src/components/MissionConfig.tsx  |  2 +-
 client/src/views/rocket-details-view.tsx | 33 +++++-------------------
 documentation/installation.md            | 18 ++++++-------
 3 files changed, 16 insertions(+), 37 deletions(-)

diff --git a/client/src/components/MissionConfig.tsx b/client/src/components/MissionConfig.tsx
index c7d2c2636..d328af8b6 100644
--- a/client/src/components/MissionConfig.tsx
+++ b/client/src/components/MissionConfig.tsx
@@ -119,7 +119,7 @@ const MissionConfig: React.FC<MissionConfigProps> = (props: MissionConfigProps)
                 Mission Configuration
             </DialogTitle>
             <DialogContent>
-                <Stack direction="column" spacing={3} alignItems="left">
+                <Stack direction="column" spacing={3} alignItems="left" paddingY={1}>
                     <Stack direction="row" spacing={2} alignItems="center">
                         <TextField
                             InputLabelProps={{
diff --git a/client/src/views/rocket-details-view.tsx b/client/src/views/rocket-details-view.tsx
index bc883e1fe..f3232755b 100644
--- a/client/src/views/rocket-details-view.tsx
+++ b/client/src/views/rocket-details-view.tsx
@@ -63,11 +63,7 @@ export default function RocketDetailsView() {
 		'rgba(69, 136, 201, 1)'
 	];
 
-	const breadCrumbs: Breadcrumb[] = [
-        { name: 'Ground Support', viewKey: ViewKeys.PLATFORM_SELECTION_KEY, active: false },
-		{ name: 'Rocket Selection', viewKey: ViewKeys.ROCKET_SELECT_KEY, active: false },
-		{ name: 'Rocket Details', viewKey: ViewKeys.ROCKET_DETAILS_KEY, active: true }
-	];
+	
 
     //value is for tab things
     const [value, setValue] = useState<number>(0);
@@ -124,6 +120,12 @@ export default function RocketDetailsView() {
 		getRocket();
 	}, []);
 
+    const breadCrumbs: Breadcrumb[] = [
+        { name: 'Ground Support', viewKey: ViewKeys.PLATFORM_SELECTION_KEY, active: false },
+		{ name: 'Rocket Selection', viewKey: ViewKeys.ROCKET_SELECT_KEY, active: false },
+		{ name: rocketData?.Name, viewKey: ViewKeys.ROCKET_DETAILS_KEY, active: true }
+	];
+
 	return (
 		<Box sx={{ width: '100vw', height: '100vh' }}>
 			<Stack
@@ -138,27 +140,6 @@ export default function RocketDetailsView() {
 					<Header icon={'ROCKET_MONITORING'} breadCrumbs={breadCrumbs} />
 				</Grid>
                 {/* Rocket Title */}
-                <Grid item>
-					<Paper elevation={2} sx={{ padding: 2 }}>
-                        <Stack direction="row" alignItems={'center'} justifyContent={'space-between'}>
-                            <Stack direction="row" alignItems={'center'} spacing={2}>
-                                <RocketLaunchIcon color={'primary'} /> 
-                                <Typography align='left' variant='h5'>
-                                    {rocketData?.Name || 'Rocket Not found'}
-                                </Typography>
-                            </Stack>
-                            <Button 
-                                variant="contained" 
-                                size={'large'} 
-                                startIcon={<LaunchIcon/>} 
-                                onClick={() => console.log('Start Mission')} 
-                                disabled={!isMissionActive}
-                            >
-                                Continue Mission
-                            </Button>
-                        </Stack>
-                    </Paper>
-				</Grid>
                 <Grid item overflow={'scroll'}>
                     <Box sx={{ padding: 0.5 }}>
                         <Stack direction="row" spacing={3} justifyContent="flex-start">
diff --git a/documentation/installation.md b/documentation/installation.md
index 015684f27..ab12bc755 100644
--- a/documentation/installation.md
+++ b/documentation/installation.md
@@ -209,7 +209,7 @@ TBD (@JackCotter) once rust backend is more functional
 
 ### 1. Install WSL
 1. `Open terminal`
-2. `wsl --install or wsl --install -d Ubuntu --web-download`
+2. `wsl --install` or `wsl --install -d Ubuntu --web-download`
    
 ### 2. Install Docker
 1. `sudo apt-get update` 
@@ -220,22 +220,20 @@ TBD (@JackCotter) once rust backend is more functional
 6. `sudo apt-get update` 
 7. `sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin`
    
-### 3.Git Install
+### 3. Git Install
 1. `sudo apt-get install git`
 2. `git config --global user.name "Your Name"`
 3. `git config --global user.email "youremail@domain.com"`
 
-### 4.Install Rust
+### 4. Install Rust
 1. `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`
 2. `sudo apt-get update`
 3. `sudo apt install build-essential`
    
-### 5.Install Ground-Support
-1. `https://github.com/UVicRocketry/Ground-Support.git`
+### 5. Install Ground-Support
+1. `sudo git clone https://github.com/UVicRocketry/Ground-Support.git`
    
-### 6.Docker Start Up
+### 6. Docker Start Up
 1. `sudo service docker start`
-2. `docker compose build`
-3. `docker compose up`
-
-
+2. `sudo docker compose build`
+3. `sudo docker compose up`

From f4c9f0f545802a0125854e657658d804db723372 Mon Sep 17 00:00:00 2001
From: klemie <lemieuxkristopher@gmail.com>
Date: Wed, 17 Jul 2024 22:18:49 -0700
Subject: [PATCH 14/14] installation guide PR

---
 documentation/installation.md | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/documentation/installation.md b/documentation/installation.md
index ab12bc755..db44050eb 100644
--- a/documentation/installation.md
+++ b/documentation/installation.md
@@ -237,3 +237,10 @@ TBD (@JackCotter) once rust backend is more functional
 1. `sudo service docker start`
 2. `sudo docker compose build`
 3. `sudo docker compose up`
+<<<<<<< Updated upstream
+=======
+
+### 7. Run Ground Support
+1. Open a browser and navigate to `localhost:3000`
+
+>>>>>>> Stashed changes