From 054ed8b6a1e958016c206f7d047545cfb2d093e5 Mon Sep 17 00:00:00 2001 From: Alan <40917647+alaninnovates@users.noreply.github.com> Date: Tue, 21 Oct 2025 17:53:22 -0700 Subject: [PATCH] Add camera mismatch banner to dashboard (#1921) ## Description Detects if a camera mismatch is present in any camera and displays a banner in the dashboard for better visibility to the user. All detection occurs in the backend, and is sent to the frontend via use of a mismatch boolean included in each vision module. image Closes #1920 ## Meta Merge checklist: - [x] Pull Request title is [short, imperative summary](https://cbea.ms/git-commit/) of proposed changes - [x] The description documents the _what_ and _why_ - [ ] If this PR changes behavior or adds a feature, user documentation is updated - [ ] If this PR touches photon-serde, all messages have been regenerated and hashes have not changed unexpectedly - [x] If this PR touches configuration, this is backwards compatible with settings back to v2024.3.1 - [x] If this PR touches pipeline settings or anything related to data exchange, the frontend typing is updated - [ ] If this PR addresses a bug, a regression test for it is added --------- Co-authored-by: Sam Freund Co-authored-by: samfreund Co-authored-by: Matt Morley --- .../stores/settings/CameraSettingsStore.ts | 3 +- photon-client/src/types/SettingTypes.ts | 4 +- photon-client/src/types/WebsocketDataTypes.ts | 1 + .../src/views/CameraMatchingView.vue | 127 ++++++++---------- photon-client/src/views/DashboardView.vue | 27 ++++ .../networktables/NetworkTablesManager.java | 11 ++ .../websocket/UICameraConfiguration.java | 1 + .../vision/camera/PVCameraInfo.java | 40 +++++- .../vision/processes/VisionModule.java | 6 + .../vision/processes/VisionSourceManager.java | 105 ++++++++++++++- .../processes/VisionSourceManagerTest.java | 52 +++++++ 11 files changed, 299 insertions(+), 78 deletions(-) diff --git a/photon-client/src/stores/settings/CameraSettingsStore.ts b/photon-client/src/stores/settings/CameraSettingsStore.ts index ef7799e3a..32c3f94ed 100644 --- a/photon-client/src/stores/settings/CameraSettingsStore.ts +++ b/photon-client/src/stores/settings/CameraSettingsStore.ts @@ -142,7 +142,8 @@ export const useCameraSettingsStore = defineStore("cameraSettings", { maxWhiteBalanceTemp: d.maxWhiteBalanceTemp, matchedCameraInfo: d.matchedCameraInfo, isConnected: d.isConnected, - hasConnected: d.hasConnected + hasConnected: d.hasConnected, + mismatch: d.mismatch }; return acc; }, {}); diff --git a/photon-client/src/types/SettingTypes.ts b/photon-client/src/types/SettingTypes.ts index 693f14fda..59fa0d39a 100644 --- a/photon-client/src/types/SettingTypes.ts +++ b/photon-client/src/types/SettingTypes.ts @@ -266,6 +266,7 @@ export interface UiCameraConfiguration { matchedCameraInfo: PVCameraInfo; isConnected: boolean; hasConnected: boolean; + mismatch: boolean; } export interface CameraSettingsChangeRequest { @@ -388,7 +389,8 @@ export const PlaceholderCameraSettings: UiCameraConfiguration = { PVUsbCameraInfo: undefined }, isConnected: true, - hasConnected: true + hasConnected: true, + mismatch: false }; export enum CalibrationBoardTypes { diff --git a/photon-client/src/types/WebsocketDataTypes.ts b/photon-client/src/types/WebsocketDataTypes.ts index b68fd1df7..4ca05d789 100644 --- a/photon-client/src/types/WebsocketDataTypes.ts +++ b/photon-client/src/types/WebsocketDataTypes.ts @@ -69,6 +69,7 @@ export interface WebsocketCameraSettingsUpdate { matchedCameraInfo: PVCameraInfo; isConnected: boolean; hasConnected: boolean; + mismatch: boolean; } export interface WebsocketNTUpdate { connected: boolean; diff --git a/photon-client/src/views/CameraMatchingView.vue b/photon-client/src/views/CameraMatchingView.vue index 35d8401aa..093cb83fb 100644 --- a/photon-client/src/views/CameraMatchingView.vue +++ b/photon-client/src/views/CameraMatchingView.vue @@ -168,64 +168,7 @@ const deleteThisCamera = (cameraName: string) => { }); }; -const camerasMatch = (camera1: PVCameraInfo, camera2: PVCameraInfo) => { - if (camera1.PVUsbCameraInfo && camera2.PVUsbCameraInfo) - return ( - camera1.PVUsbCameraInfo.name === camera2.PVUsbCameraInfo.name && - camera1.PVUsbCameraInfo.vendorId === camera2.PVUsbCameraInfo.vendorId && - camera1.PVUsbCameraInfo.productId === camera2.PVUsbCameraInfo.productId && - camera1.PVUsbCameraInfo.uniquePath === camera2.PVUsbCameraInfo.uniquePath - ); - else if (camera1.PVCSICameraInfo && camera2.PVCSICameraInfo) - return ( - camera1.PVCSICameraInfo.uniquePath === camera2.PVCSICameraInfo.uniquePath && - camera1.PVCSICameraInfo.baseName === camera2.PVCSICameraInfo.baseName - ); - else if (camera1.PVFileCameraInfo && camera2.PVFileCameraInfo) - return ( - camera1.PVFileCameraInfo.uniquePath === camera2.PVFileCameraInfo.uniquePath && - camera1.PVFileCameraInfo.name === camera2.PVFileCameraInfo.name - ); - else return false; -}; - -const cameraInfoFor = (camera: PVCameraInfo | null): PVUsbCameraInfo | PVCSICameraInfo | PVFileCameraInfo | any => { - if (!camera) return null; - if (camera.PVUsbCameraInfo) { - return camera.PVUsbCameraInfo; - } - if (camera.PVCSICameraInfo) { - return camera.PVCSICameraInfo; - } - if (camera.PVFileCameraInfo) { - return camera.PVFileCameraInfo; - } - return {}; -}; - -/** - * Find the PVCameraInfo currently occupying the same uniquepath as the the given module - */ -const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => { - if (!info) { - return { - PVFileCameraInfo: undefined, - PVCSICameraInfo: undefined, - PVUsbCameraInfo: undefined - }; - } - return ( - useStateStore().vsmState.allConnectedCameras.find( - (it) => cameraInfoFor(it).uniquePath === cameraInfoFor(info).uniquePath - ) || { - PVFileCameraInfo: undefined, - PVCSICameraInfo: undefined, - PVUsbCameraInfo: undefined - } - ); -}; - -const cameraCononected = (uniquePath: string): boolean => { +const cameraConnected = (uniquePath: string): boolean => { return ( useStateStore().vsmState.allConnectedCameras.find((it) => cameraInfoFor(it).uniquePath === uniquePath) !== undefined ); @@ -252,8 +195,8 @@ const activeVisionModules = computed(() => // Display connected cameras first .sort( (first, second) => - (cameraCononected(cameraInfoFor(second.matchedCameraInfo).uniquePath) ? 1 : 0) - - (cameraCononected(cameraInfoFor(first.matchedCameraInfo).uniquePath) ? 1 : 0) + (cameraConnected(cameraInfoFor(second.matchedCameraInfo).uniquePath) ? 1 : 0) - + (cameraConnected(cameraInfoFor(first.matchedCameraInfo).uniquePath) ? 1 : 0) ) ); @@ -274,6 +217,45 @@ const setCameraDeleting = (camera: UiCameraConfiguration | WebsocketCameraSettin cameraToDelete.value = camera; }; const yesDeleteMySettingsText = ref(""); + +/** + * Get the connection-type-specific camera info from the given PVCameraInfo object. + */ +const cameraInfoFor = (camera: PVCameraInfo | null): PVUsbCameraInfo | PVCSICameraInfo | PVFileCameraInfo | any => { + if (!camera) return null; + if (camera.PVUsbCameraInfo) { + return camera.PVUsbCameraInfo; + } + if (camera.PVCSICameraInfo) { + return camera.PVCSICameraInfo; + } + if (camera.PVFileCameraInfo) { + return camera.PVFileCameraInfo; + } + return {}; +}; + +/** + * Find the PVCameraInfo currently occupying the same uniquePath as the the given module + */ +const getMatchedDevice = (info: PVCameraInfo | undefined): PVCameraInfo => { + if (!info) { + return { + PVFileCameraInfo: undefined, + PVCSICameraInfo: undefined, + PVUsbCameraInfo: undefined + }; + } + return ( + useStateStore().vsmState.allConnectedCameras.find( + (it) => cameraInfoFor(it).uniquePath === cameraInfoFor(info).uniquePath + ) || { + PVFileCameraInfo: undefined, + PVCSICameraInfo: undefined, + PVUsbCameraInfo: undefined + } + ); +};