Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pipe vision/source telemetry up to UI #1521

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/docs/contributing/building-photon.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,5 +290,5 @@ Then, run the examples:
Using the [GitHub CLI](https://cli.github.com/), we can download artifacts from pipelines by run ID and name:

```
~/photonvision$ gh run download 11759699679 -n jar-Linux
~/photonvision$ gh run download 11759699679 -n jar-Linux
```
10 changes: 9 additions & 1 deletion photon-client/src/components/app/photon-sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
<v-icon>mdi-camera</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Cameras</v-list-item-title>
<v-list-item-title>Camera</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item link to="/settings">
Expand All @@ -51,6 +51,14 @@ const mdAndUp = computed<boolean>(() => getCurrentInstance()?.proxy.$vuetify.bre
<v-list-item-title>Settings</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item link to="/cameraConfigs">
<v-list-item-icon>
<v-icon>mdi-notebook</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Camera Configs</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-list-item link to="/docs">
<v-list-item-icon>
<v-icon>mdi-bookshelf</v-icon>
Expand Down
6 changes: 6 additions & 0 deletions photon-client/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import CameraSettingsView from "@/views/CameraSettingsView.vue";
import GeneralSettingsView from "@/views/GeneralSettingsView.vue";
import DocsView from "@/views/DocsView.vue";
import NotFoundView from "@/views/NotFoundView.vue";
import CameraMatchingView from "@/views/CameraMatchingView.vue";

Vue.use(VueRouter);

Expand Down Expand Up @@ -33,6 +34,11 @@ const router = new VueRouter({
name: "Settings",
component: GeneralSettingsView
},
{
path: "/cameraConfigs",
name: "Camera Configs",
component: CameraMatchingView
},
{
path: "/docs",
name: "Docs",
Expand Down
2 changes: 2 additions & 0 deletions photon-client/src/stores/settings/CameraSettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export const useCameraSettingsStore = defineStore("cameraSettings", {
actions: {
updateCameraSettingsFromWebsocket(data: WebsocketCameraSettingsUpdate[]) {
const configuredCameras = data.map<CameraSettings>((d) => ({
cameraPath: d.cameraPath,

nickname: d.nickname,
uniqueName: d.uniqueName,
fov: {
Expand Down
21 changes: 19 additions & 2 deletions photon-client/src/stores/settings/GeneralSettingsStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@
GeneralSettings,
LightingSettings,
MetricData,
NetworkSettings
NetworkSettings,
VisionSourceManagerState
} from "@/types/SettingTypes";
import { NetworkConnectionType } from "@/types/SettingTypes";
import { useStateStore } from "@/stores/StateStore";
import axios from "axios";
import type { WebsocketSettingsUpdate } from "@/types/WebsocketDataTypes";
import type { AprilTagFieldLayout } from "@/types/PhotonTrackingTypes";

Check warning on line 14 in photon-client/src/stores/settings/GeneralSettingsStore.ts

View workflow job for this annotation

GitHub Actions / PhotonClient Lint and Formatting

'AprilTagFieldLayout' is defined but never used

interface GeneralSettingsStore {
general: GeneralSettings;
network: NetworkSettings;
lighting: LightingSettings;
metrics: MetricData;
currentFieldLayout: AprilTagFieldLayout;
visionSourceManagerState: VisionSourceManagerState;
}

export const useSettingsStore = defineStore("settings", {
Expand Down Expand Up @@ -71,6 +72,20 @@
width: 8.2296
},
tags: []
},
visionSourceManagerState: {
knownCameras: [
{
cameraType: "USBCamera",
dev: 1,
name: "foobar",
otherPaths: ["asdf", "bfdsa"],
path: "/some/path",
vendorId: 1,
productId: 2
}
],
unmatchedLoadedConfigs: []
}
}),
getters: {
Expand Down Expand Up @@ -112,6 +127,8 @@
this.lighting = data.lighting;
this.network = data.networkSettings;
this.currentFieldLayout = data.atfl;

this.visionSourceManagerState = data.visionSourceManagerState;
},
updateGeneralSettings(payload: Required<ConfigurableNetworkSettings>) {
return axios.post("/settings/general", payload);
Expand Down
1 change: 1 addition & 0 deletions photon-client/src/types/PhotonTrackingTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export interface MultitagResult {
}

export interface PipelineResult {
sequenceID: number;
fps: number;
latency: number;
targets: PhotonTarget[];
Expand Down
20 changes: 20 additions & 0 deletions photon-client/src/types/SettingTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ export type ConfigurableNetworkSettings = Omit<
"canManage" | "networkInterfaceNames" | "networkingDisabled"
>;

export interface UiCameraConfiguration {}
export interface CameraConfiguration {}

export interface PvCameraInfo {
cameraType: string; // CameraType -- todo
dev: number;
path: string;
name: string;
otherPaths: string[];
vendorId: number;
productId: number;
}

export interface VisionSourceManagerState {
knownCameras: PvCameraInfo[];
unmatchedLoadedConfigs: CameraConfiguration[];
}

export interface LightingSettings {
supported: boolean;
brightness: number;
Expand Down Expand Up @@ -173,6 +191,8 @@ export interface QuirkyCamera {
}

export interface CameraSettings {
cameraPath: string;

nickname: string;
uniqueName: string;

Expand Down
7 changes: 6 additions & 1 deletion photon-client/src/types/WebsocketDataTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import type {
LogLevel,
MetricData,
NetworkSettings,
QuirkyCamera
QuirkyCamera,
VisionSourceManagerState
} from "@/types/SettingTypes";
import type { ActivePipelineSettings } from "@/types/PipelineTypes";
import type { AprilTagFieldLayout, PipelineResult } from "@/types/PhotonTrackingTypes";
Expand All @@ -21,6 +22,7 @@ export interface WebsocketSettingsUpdate {
lighting: Required<LightingSettings>;
networkSettings: NetworkSettings;
atfl: AprilTagFieldLayout;
visionSourceManagerState: VisionSourceManagerState;
}

export interface WebsocketNumberPair {
Expand All @@ -44,7 +46,10 @@ export type WebsocketVideoFormat = Record<
}
>;

// Companion to UICameraConfiguration in Java
export interface WebsocketCameraSettingsUpdate {
cameraPath: string;

calibrations: CameraCalibrationResult[];
currentPipelineIndex: number;
currentPipelineSettings: ActivePipelineSettings;
Expand Down
139 changes: 139 additions & 0 deletions photon-client/src/views/CameraMatchingView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<script setup lang="ts">
import MetricsCard from "@/components/settings/MetricsCard.vue";

Check warning on line 2 in photon-client/src/views/CameraMatchingView.vue

View workflow job for this annotation

GitHub Actions / PhotonClient Lint and Formatting

'MetricsCard' is defined but never used
import { useSettingsStore } from "@/stores/settings/GeneralSettingsStore";
import { useCameraSettingsStore } from "@/stores/settings/CameraSettingsStore";
import { inject } from "vue";
import { useStateStore } from "@/stores/StateStore";

const formatUrl = (port) => `http://${inject("backendHostname")}:${port}/stream.mjpg`;
</script>

<template>
<div class="pa-3">
<v-card dark class="mb-3 pr-6 pb-3" style="background-color: #006492">
<v-card-title style="display: flex; justify-content: space-between">
<span class="ml-3">Active Vision Modules</span>
</v-card-title>

<v-row class="ml-3">
<v-card

Check failure on line 19 in photon-client/src/views/CameraMatchingView.vue

View workflow job for this annotation

GitHub Actions / PhotonClient Lint and Formatting

Custom elements in iteration require 'v-bind:key' directives
dark
class="camera-card pa-4 mb-4 mr-3"
v-for="(module, index) in useCameraSettingsStore().cameras"
:value="index"
>
<v-card-title class="pb-8">{{ module.nickname }}</v-card-title>
<v-card-text>
<v-simple-table dense height="100%" class="camera-card-table mt-2">
<tbody>
<tr>
<td>Matched Path</td>
<td>
{{ module.cameraPath }}
</td>
</tr>
<tr>
<td>Streams:</td>
<td>
<a :href="formatUrl(module.stream.inputPort)" target="_blank"> Input Stream </a>/<a
:href="formatUrl(module.stream.outputPort)"
target="_blank"
>
Output Stream
</a>
</td>
</tr>
<tr>
<td>Pipelines</td>
<td>{{ module.pipelineNicknames.join(", ") }}</td>
</tr>
<tr>
<td>Frames Processed</td>
<td>
{{ useStateStore().backendResults[index].sequenceID }} ({{
useStateStore().backendResults[index].fps
}}
FPS)
</td>
</tr>
<tr>
<td>Connected?</td>
<td>Via [USB, CSI, totally bjork]</td>
</tr>
</tbody>
</v-simple-table>
</v-card-text>
</v-card>
</v-row>
</v-card>
<v-card dark class="mb-3 pr-6 pb-3" style="background-color: #006492">
<v-card-title>
<span> USB Cameras </span>
</v-card-title>

<v-row class="ml-3">
<v-card

Check failure on line 75 in photon-client/src/views/CameraMatchingView.vue

View workflow job for this annotation

GitHub Actions / PhotonClient Lint and Formatting

Custom elements in iteration require 'v-bind:key' directives
dark
class="camera-card pa-4 mb-4 mr-3"
v-for="(camera, index) in useSettingsStore().visionSourceManagerState.knownCameras"
:value="index"
>
<v-card-title class="pb-8">{{ camera.name }}</v-card-title>
<v-card-text>
<v-simple-table dense height="100%" class="camera-card-table mt-2">
<tbody>
<tr>
<td>USB Product String Descriptor</td>
<td>
{{ camera.name }}
</td>
</tr>
<tr>
<td>USB Vendor ID</td>
<td>0x{{ camera.vendorId.toString(16).padStart(4, "0") }}</td>
</tr>
<tr>
<td>USB Product ID</td>
<td>0x{{ camera.productId.toString(16).padStart(4, "0") }}</td>
</tr>
<tr>
<td>Type</td>
<td>
{{ camera.cameraType }}
</td>
</tr>
<tr>
<td>USB Path(s)</td>
<td>
<span
v-for="(path, idx) in [camera.path].concat(camera.otherPaths)"
:key="idx"
style="display: block"
>
{{ path }}
</span>
</td>
</tr>
<tr>
<td>Device Number</td>
<td>
{{ camera.dev }}
</td>
</tr>
</tbody>
</v-simple-table>
</v-card-text>
</v-card>
</v-row>
</v-card>
</div>
</template>

<style scoped>
.camera-card {
background-color: #005281 !important;
}
.camera-card-table {
background-color: #b49b0d !important;
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,18 @@ public String toShortString() {
+ usbVID;
}

/**
* cscore will auto-reconnect to the camera path we give it. v4l does not guarantee that if i swap
* cameras around, the same /dev/videoN ID will be assigned to that camera. So instead default to
* pinning to a particular USB port, or by "path" (appears to be a global identifier on Windows).
*
* <p>This represents our best guess at an immutable path to detect a camera at.
*/
@JsonIgnore
public String getUsbPathOrDefault() {
return getUSBPath().orElse(path);
}

@Override
public String toString() {
return "CameraConfiguration [baseName="
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.photonvision.vision.processes.VisionModule;
import org.photonvision.vision.processes.VisionModuleManager;
import org.photonvision.vision.processes.VisionSource;
import org.photonvision.vision.processes.VisionSourceManager;

public class PhotonConfiguration {
private final HardwareConfig hardwareConfig;
Expand Down Expand Up @@ -161,6 +162,8 @@ public Map<String, Object> toHashMap() {
// AprilTagFieldLayout
settingsSubmap.put("atfl", this.atfl);

settingsSubmap.put("visionSourceManagerState", VisionSourceManager.getInstance().getState());

map.put(
"cameraSettings",
VisionModuleManager.getInstance().getModules().stream()
Expand All @@ -178,21 +181,23 @@ public static class UILightingConfig {
}

public static class UICameraConfiguration {
@SuppressWarnings("unused")
public double fov;
// Path to the camera device. On Linux, this is a special file in /dev/v4l/by-id or /dev/videoN.
// This is the path we hand to CSCore to do auto-reconnect on
public String cameraPath;

public List<UICameraCalibrationCoefficients> calibrations;
public int currentPipelineIndex;
public HashMap<String, Object> currentPipelineSettings;
public double fov;
public int inputStreamPort;
public boolean isFovConfigurable = true;
public boolean isCSICamera;
public String nickname;
public String uniqueName;
public HashMap<String, Object> currentPipelineSettings;
public int currentPipelineIndex;
public int outputStreamPort;
public List<String> pipelineNicknames;
public HashMap<Integer, HashMap<String, Object>> videoFormatList;
public int outputStreamPort;
public int inputStreamPort;
public List<UICameraCalibrationCoefficients> calibrations;
public boolean isFovConfigurable = true;
public QuirkyCamera cameraQuirks;
public boolean isCSICamera;
public double minExposureRaw;
public double maxExposureRaw;
public double minWhiteBalanceTemp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public void accept(CVPipelineResult result) {
if (lastUIResultUpdateTime + 1000.0 / 10.0 > now) return;

var dataMap = new HashMap<String, Object>();
dataMap.put("sequenceID", result.sequenceID);
dataMap.put("fps", result.fps);
dataMap.put("latency", result.getLatencyMillis());
var uiTargets = new ArrayList<HashMap<String, Object>>(result.targets.size());
Expand Down
Loading
Loading