Refactor camera feed handling to support dynamic camera IDs and improve context management

This commit is contained in:
2025-12-17 14:19:23 +00:00
parent cc8b3a5691
commit 775fce7900
19 changed files with 211 additions and 248 deletions

View File

@@ -0,0 +1,12 @@
// Camera configuration - add more cameras here as needed
export const CAMERA_IDS = ["A", "B", "C"] as const;
export type CameraID = (typeof CAMERA_IDS)[number];
export const DEFAULT_REGIONS = [
{ name: "Bay 1", brushColour: "#ff0000" },
{ name: "Bay 2", brushColour: "#00ff00" },
{ name: "Bay 3", brushColour: "#0400ff" },
{ name: "Bay 4", brushColour: "#ffff00" },
{ name: "Bay 5", brushColour: "#fc35db" },
];

View File

@@ -17,9 +17,7 @@ type CameraSocketState = {
export type WebSocketConextValue = {
info: InfoSocketState;
cameraFeedA: CameraSocketState;
cameraFeedB: CameraSocketState;
cameraFeedC: CameraSocketState;
cameraFeed: CameraSocketState;
};
export const WebsocketContext = createContext<WebSocketConextValue | null>(null);
@@ -31,6 +29,4 @@ const useWebSocketContext = () => {
};
export const useInfoSocket = () => useWebSocketContext().info;
export const useCameraFeedASocket = () => useWebSocketContext().cameraFeedA;
export const useCameraFeedBSocket = () => useWebSocketContext().cameraFeedB;
export const useCameraFeedCSocket = () => useWebSocketContext().cameraFeedC;
export const useCameraFeedSocket = () => useWebSocketContext().cameraFeed;

View File

@@ -3,13 +3,12 @@ import { CameraFeedContext } from "../context/CameraFeedContext";
import { initialState, reducer } from "../reducers/cameraFeedReducer";
import { useBlackBoard } from "../../hooks/useBlackBoard";
import type { CameraFeedState } from "../../types/types";
import { useCameraZoom } from "../../features/cameras/hooks/useCameraZoom";
import { CAMERA_IDS } from "../config/cameraConfig";
import CameraZoomFetcher from "./CameraZoomFetcher";
export const CameraFeedProvider = ({ children }: { children: ReactNode }) => {
const { blackboardMutation } = useBlackBoard();
const { cameraZoomQuery: cameraZoomQueryA } = useCameraZoom("A");
const { cameraZoomQuery: cameraZoomQueryB } = useCameraZoom("B");
const { cameraZoomQuery: cameraZoomQueryC } = useCameraZoom("C");
const [state, dispatch] = useReducer(reducer, initialState);
@@ -23,52 +22,25 @@ export const CameraFeedProvider = ({ children }: { children: ReactNode }) => {
const cameraFeedData: CameraFeedState = result.result;
const recontructedState = {
...cameraFeedData,
paintedCells: {
A: new Map(cameraFeedData.paintedCells.A),
B: new Map(cameraFeedData.paintedCells.B),
C: new Map(cameraFeedData.paintedCells.C),
},
paintedCells: CAMERA_IDS.reduce(
(acc, id) => {
acc[id] = new Map(cameraFeedData.paintedCells[id]);
return acc;
},
{} as typeof cameraFeedData.paintedCells,
),
};
dispatch({ type: "SET_CAMERA_FEED_DATA", cameraState: recontructedState });
};
fetchBlackBoardData();
}, []);
useEffect(() => {
const fetchZoomLevels = async () => {
const [resultA, resultB, resultC] = await Promise.all([
cameraZoomQueryA.refetch(),
cameraZoomQueryB.refetch(),
cameraZoomQueryC.refetch(),
]);
const zoomLevelAnumber = parseFloat(resultA.data?.propPhysCurrent?.value);
const zoomLevelBnumber = parseFloat(resultB.data?.propPhysCurrent?.value);
const zoomLevelCnumber = parseFloat(resultC.data?.propPhysCurrent?.value);
if (resultA.data) {
dispatch({
type: "SET_ZOOM_LEVEL",
payload: { cameraFeedID: "A", zoomLevel: zoomLevelAnumber },
});
}
if (resultB.data) {
dispatch({
type: "SET_ZOOM_LEVEL",
payload: { cameraFeedID: "B", zoomLevel: zoomLevelBnumber },
});
}
if (resultC.data) {
dispatch({
type: "SET_ZOOM_LEVEL",
payload: { cameraFeedID: "C", zoomLevel: zoomLevelCnumber },
});
}
};
fetchZoomLevels();
}, []);
return <CameraFeedContext.Provider value={{ state, dispatch }}>{children}</CameraFeedContext.Provider>;
return (
<CameraFeedContext.Provider value={{ state, dispatch }}>
{CAMERA_IDS.map((cameraId) => (
<CameraZoomFetcher key={cameraId} cameraId={cameraId} dispatch={dispatch} />
))}
{children}
</CameraFeedContext.Provider>
);
};

View File

@@ -0,0 +1,31 @@
import { useEffect } from "react";
import { useCameraZoom } from "../../features/cameras/hooks/useCameraZoom";
import type { CameraFeedAction } from "../../types/types";
import type { CameraID } from "../config/cameraConfig";
type CameraZoomFetcherProps = {
cameraId: CameraID;
dispatch: (action: CameraFeedAction) => void;
};
const CameraZoomFetcher = ({ cameraId, dispatch }: CameraZoomFetcherProps) => {
const { cameraZoomQuery } = useCameraZoom(cameraId);
useEffect(() => {
const fetchZoomLevel = async () => {
const result = await cameraZoomQuery.refetch();
if (result.data && typeof result.data.zoomLevel === "number") {
dispatch({
type: "SET_ZOOM_LEVEL",
payload: { cameraFeedID: cameraId, zoomLevel: result.data.zoomLevel },
});
}
};
fetchZoomLevel();
}, [cameraId, cameraZoomQuery, dispatch]);
return null;
};
export default CameraZoomFetcher;

View File

@@ -3,18 +3,28 @@ import { WebsocketContext, type WebSocketConextValue } from "../context/WebSocke
import useWebSocket from "react-use-websocket";
import { wsConfig } from "../config/wsconfig";
import type { CameraZoomData, InfoBarData } from "../../types/types";
import { CAMERA_IDS } from "../config/cameraConfig";
import { CAMBASE_WS } from "../../utils/config";
import { useCameraFeedContext } from "../context/CameraFeedContext";
type WebSocketProviderProps = {
children: ReactNode;
};
export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
const { state } = useCameraFeedContext();
const [systemData, setSystemData] = useState<InfoBarData | null>(null);
const [socketData, setSocketData] = useState<CameraZoomData | null>(null);
const infoSocket = useWebSocket(wsConfig.infoBar, { share: true, shouldReconnect: () => true });
const cameraFeedASocket = useWebSocket(wsConfig.cameraFeedA, { share: true, shouldReconnect: () => true });
const cameraFeedBSocket = useWebSocket(wsConfig.cameraFeedB, { share: true, shouldReconnect: () => true });
const cameraFeedCSocket = useWebSocket(wsConfig.cameraFeedC, { share: true, shouldReconnect: () => true });
const sockets = CAMERA_IDS.reduce(
(acc, id) => {
acc[id] = `${CAMBASE_WS}/websocket-Camera${id}-live-video`;
return acc;
},
{} as Record<string, string>,
);
const cameraFeedID = state.cameraFeedID;
const cameraFeed = useWebSocket(sockets[cameraFeedID], { share: true, shouldReconnect: () => true });
useEffect(() => {
async function parseData() {
@@ -23,20 +33,15 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
const data = JSON.parse(text);
setSystemData(data);
}
if (cameraFeedASocket.lastMessage || cameraFeedBSocket.lastMessage || cameraFeedCSocket.lastMessage) {
const message = cameraFeedASocket.lastMessage || cameraFeedBSocket.lastMessage || cameraFeedCSocket.lastMessage;
if (cameraFeed.lastMessage) {
const message = cameraFeed.lastMessage;
const data = await message?.data.text();
const parsedData: CameraZoomData = JSON.parse(data || "");
setSocketData(parsedData);
}
}
parseData();
}, [
cameraFeedASocket.lastMessage,
cameraFeedBSocket.lastMessage,
cameraFeedCSocket.lastMessage,
infoSocket.lastMessage,
]);
}, [cameraFeed.lastMessage, infoSocket.lastMessage]);
const value = useMemo<WebSocketConextValue>(
() => ({
@@ -45,32 +50,16 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => {
readyState: infoSocket.readyState,
sendJson: infoSocket.sendJsonMessage,
},
cameraFeedA: {
cameraFeed: {
data: socketData,
readyState: cameraFeedASocket.readyState,
readyState: cameraFeed.readyState,
send: cameraFeedASocket.sendMessage,
},
cameraFeedB: {
data: socketData,
readyState: cameraFeedBSocket.readyState,
send: cameraFeedBSocket.sendMessage,
},
cameraFeedC: {
data: socketData,
readyState: cameraFeedCSocket.readyState,
send: cameraFeedCSocket.sendMessage,
send: cameraFeed.sendMessage,
},
}),
[
cameraFeedASocket.readyState,
cameraFeedASocket.sendMessage,
cameraFeedBSocket.readyState,
cameraFeedBSocket.sendMessage,
cameraFeedCSocket.readyState,
cameraFeedCSocket.sendMessage,
cameraFeed.readyState,
cameraFeed.sendMessage,
infoSocket.readyState,
infoSocket.sendJsonMessage,
socketData,

View File

@@ -1,47 +1,38 @@
import type { CameraFeedAction, CameraFeedState, PaintedCell } from "../../types/types";
import { CAMERA_IDS, DEFAULT_REGIONS, type CameraID } from "../config/cameraConfig";
export const initialState: CameraFeedState = {
cameraFeedID: "A",
paintedCells: {
A: new Map<string, PaintedCell>(),
B: new Map<string, PaintedCell>(),
C: new Map<string, PaintedCell>(),
},
regionsByCamera: {
A: [
{ name: "Bay 1", brushColour: "#ff0000" },
{ name: "Bay 2", brushColour: "#00ff00" },
{ name: "Bay 3", brushColour: "#0400ff" },
{ name: "Bay 4", brushColour: "#ffff00" },
{ name: "Bay 5", brushColour: "#fc35db" },
],
B: [
{ name: "Bay 1", brushColour: "#ff0000" },
{ name: "Bay 2", brushColour: "#00ff00" },
{ name: "Bay 3", brushColour: "#0400ff" },
{ name: "Bay 4", brushColour: "#ffff00" },
{ name: "Bay 5", brushColour: "#fc35db" },
],
C: [
{ name: "Bay 1", brushColour: "#ff0000" },
{ name: "Bay 2", brushColour: "#00ff00" },
{ name: "Bay 3", brushColour: "#0400ff" },
{ name: "Bay 4", brushColour: "#ffff00" },
{ name: "Bay 5", brushColour: "#fc35db" },
],
},
cameraFeedID: CAMERA_IDS[0],
paintedCells: CAMERA_IDS.reduce(
(acc, id) => {
acc[id] = new Map<string, PaintedCell>();
return acc;
},
{} as Record<string, Map<string, PaintedCell>>,
),
regionsByCamera: CAMERA_IDS.reduce(
(acc, id) => {
acc[id] = DEFAULT_REGIONS;
return acc;
},
{} as Record<string, { name: string; brushColour: string }[]>,
),
selectedRegionIndex: 0,
modeByCamera: {
A: "painter",
B: "painter",
C: "painter",
},
zoomLevel: {
A: 1,
B: 1,
C: 1,
},
modeByCamera: CAMERA_IDS.reduce(
(acc, id) => {
acc[id] = "painter";
return acc;
},
{} as Record<CameraID, string>,
),
zoomLevel: CAMERA_IDS.reduce(
(acc, id) => {
acc[id] = 1;
return acc;
},
{} as Record<CameraID, number>,
),
};
export function reducer(state: CameraFeedState, action: CameraFeedAction) {