From bd15c72cbd95e13bf007caf7bb197404314f9c9d Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 31 Jul 2025 23:08:33 -0700 Subject: [PATCH 01/40] Add cscore + cameraserver --- simulation/SyntheSimJava/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/simulation/SyntheSimJava/build.gradle b/simulation/SyntheSimJava/build.gradle index e8631a287e..8946c0b3b1 100644 --- a/simulation/SyntheSimJava/build.gradle +++ b/simulation/SyntheSimJava/build.gradle @@ -53,6 +53,8 @@ dependencies { implementation "edu.wpi.first.wpilibj:wpilibj-java:$WPI_Version" implementation "edu.wpi.first.wpiutil:wpiutil-java:$WPI_Version" implementation "edu.wpi.first.hal:hal-java:$WPI_Version" + implementation "edu.wpi.first.cscore:cscore-java:$WPI_Version" + implementation "edu.wpi.first.cameraserver:cameraserver-java:$WPI_Version" // REVRobotics implementation "com.revrobotics.frc:REVLib-java:$REV_Version" From 3cf39fe57366a1cccd5116b64742d29bc5d71aaa Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 31 Jul 2025 23:08:52 -0700 Subject: [PATCH 02/40] Add json processing --- simulation/SyntheSimJava/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/simulation/SyntheSimJava/build.gradle b/simulation/SyntheSimJava/build.gradle index 8946c0b3b1..e92e45fa98 100644 --- a/simulation/SyntheSimJava/build.gradle +++ b/simulation/SyntheSimJava/build.gradle @@ -64,6 +64,9 @@ dependencies { // KAUAI implementation "com.kauailabs.navx.frc:navx-frc-java:$KAUAI_Version" + + // JSON processing + implementation 'org.json:json:20231013' } java { From bade902842681e235f6473ab39224ab640a231a5 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 31 Jul 2025 23:09:03 -0700 Subject: [PATCH 03/40] Add opencv --- simulation/SyntheSimJava/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/simulation/SyntheSimJava/build.gradle b/simulation/SyntheSimJava/build.gradle index e92e45fa98..66e72d99f8 100644 --- a/simulation/SyntheSimJava/build.gradle +++ b/simulation/SyntheSimJava/build.gradle @@ -67,6 +67,9 @@ dependencies { // JSON processing implementation 'org.json:json:20231013' + + // OpenCV (Note: WPILib includes OpenCV, but we need to make sure it's available) + implementation "edu.wpi.first.thirdparty.frc2024.opencv:opencv-java:4.8.0-2" } java { From 5ed3124b4fad8a5661e5bb9e08ff3f4378d898b0 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 31 Jul 2025 23:10:38 -0700 Subject: [PATCH 04/40] Add Camera --- .../java/com/autodesk/synthesis/Camera.java | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/Camera.java diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/Camera.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/Camera.java new file mode 100644 index 0000000000..0822729a5f --- /dev/null +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/Camera.java @@ -0,0 +1,104 @@ +package com.autodesk.synthesis; + +import edu.wpi.first.hal.SimBoolean; +import edu.wpi.first.hal.SimDevice; +import edu.wpi.first.hal.SimDevice.Direction; +import edu.wpi.first.hal.SimDouble; + +public class Camera { + private SimDevice m_device; + + private SimBoolean m_connected; + private SimDouble m_width; + private SimDouble m_height; + private SimDouble m_fps; + + /** + * Creates a Camera sim device in accordance with the WebSocket API Specification. + * + * @param name Name of the Camera. This is generally the class name of the originating camera. + * @param deviceId ID of the Camera. + */ + public Camera(String name, int deviceId) { + m_device = SimDevice.create("Camera:" + name, deviceId); + + m_connected = m_device.createBoolean("connected", Direction.kInput, false); + m_width = m_device.createDouble("width", Direction.kBidir, 320); + m_height = m_device.createDouble("height", Direction.kBidir, 240); + m_fps = m_device.createDouble("fps", Direction.kBidir, 30); + } + + /** + * Get whether the camera is connected. + * + * @return true if connected, false otherwise + */ + public boolean isConnected() { + return m_connected.get(); + } + + /** + * Set the camera connected status. + * + * @param connected connection status + */ + public void setConnected(boolean connected) { + m_connected.set(connected); + } + + /** + * Get the camera width. + * + * @return width in pixels + */ + public double getWidth() { + return m_width.get(); + } + + /** + * Set the camera width. + * + * @param width width in pixels + */ + public void setWidth(double width) { + m_width.set(width); + } + + /** + * Get the camera height. + * + * @return height in pixels + */ + public double getHeight() { + return m_height.get(); + } + + /** + * Set the camera height. + * + * @param height height in pixels + */ + public void setHeight(double height) { + m_height.set(height); + } + + /** + * Get the camera frame rate. + * + * @return fps (frames per second) + */ + public double getFPS() { + return m_fps.get(); + } + + /** + * Set the camera frame rate. + * + * @param fps frames per second + */ + public void setFPS(double fps) { + m_fps.set(fps); + } +} + + From c0822d491d24ca9c58d625b3e05f2855431d9c31 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 31 Jul 2025 23:11:04 -0700 Subject: [PATCH 05/40] Add Camera extra functions --- .../java/com/autodesk/synthesis/Camera.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/Camera.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/Camera.java index 0822729a5f..9375abf6b0 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/Camera.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/Camera.java @@ -12,6 +12,9 @@ public class Camera { private SimDouble m_width; private SimDouble m_height; private SimDouble m_fps; + private SimDouble m_brightness; + private SimDouble m_exposure; + private SimBoolean m_autoExposure; /** * Creates a Camera sim device in accordance with the WebSocket API Specification. @@ -26,6 +29,9 @@ public Camera(String name, int deviceId) { m_width = m_device.createDouble("width", Direction.kBidir, 320); m_height = m_device.createDouble("height", Direction.kBidir, 240); m_fps = m_device.createDouble("fps", Direction.kBidir, 30); + m_brightness = m_device.createDouble("brightness", Direction.kBidir, 50); + m_exposure = m_device.createDouble("exposure", Direction.kBidir, 50); + m_autoExposure = m_device.createBoolean("auto_exposure", Direction.kBidir, true); } /** @@ -99,6 +105,60 @@ public double getFPS() { public void setFPS(double fps) { m_fps.set(fps); } + + /** + * Get the camera brightness. + * + * @return brightness (0-100) + */ + public double getBrightness() { + return m_brightness.get(); + } + + /** + * Set the camera brightness. + * + * @param brightness brightness (0-100) + */ + public void setBrightness(double brightness) { + m_brightness.set(brightness); + } + + /** + * Get the camera exposure. + * + * @return exposure value + */ + public double getExposure() { + return m_exposure.get(); + } + + /** + * Set the camera exposure. + * + * @param exposure exposure value + */ + public void setExposure(double exposure) { + m_exposure.set(exposure); + } + + /** + * Get whether auto exposure is enabled. + * + * @return true if auto exposure is enabled + */ + public boolean getAutoExposure() { + return m_autoExposure.get(); + } + + /** + * Set auto exposure mode. + * + * @param autoExposure true to enable auto exposure + */ + public void setAutoExposure(boolean autoExposure) { + m_autoExposure.set(autoExposure); + } } From 4c59da2e5cb68bf5f3395d6dbc2dacea7ea1bf78 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 31 Jul 2025 23:15:51 -0700 Subject: [PATCH 06/40] Add SimInput --- .../simulation/wpilib_brain/SimInput.ts | 171 +++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index 34296c3ea6..1a9145a908 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -6,6 +6,7 @@ import Jolt from "@azaleacolburn/jolt-physics" import JOLT from "@/util/loading/JoltSyncLoader" import { convertJoltQuatToThreeQuaternion, convertJoltVec3ToThreeVector3 } from "@/util/TypeConversions" import * as THREE from "three" +import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" export abstract class SimInput { constructor(protected _device: string) {} @@ -114,7 +115,7 @@ export class SimAccelInput extends SimInput { const x = (newVel.x - this._prevVel.x) / deltaT const y = (newVel.y - this._prevVel.y) / deltaT - const z = (newVel.y - this._prevVel.y) / deltaT + const z = (newVel.z - this._prevVel.z) / deltaT SimAccel.setX(this._device, x) SimAccel.setY(this._device, y) @@ -124,6 +125,174 @@ export class SimAccelInput extends SimInput { } } +export class SimCameraInput extends SimInput { + private _defaultWidth: number = 320 + private _defaultHeight: number = 240 + private _defaultFPS: number = 30 + private _isInitialized: boolean = false + private _cameraRenderer?: SimCameraRenderer + private _robot: MirabufSceneObject + private _frameInterval: number = 0 + private _lastFrameTime: number = 0 + + constructor(device: string, robot: MirabufSceneObject, width?: number, height?: number, fps?: number) { + super(device) + this._robot = robot + if (width) this._defaultWidth = width + if (height) this._defaultHeight = height + if (fps) this._defaultFPS = fps + this._frameInterval = 1000 / this._defaultFPS // ms between frames + + console.log(`๐ŸŽฌ [CONSTRUCTOR] SimCameraInput created for ${device} (${this._defaultWidth}x${this._defaultHeight} @ ${this._defaultFPS}fps, interval=${this._frameInterval}ms)`) + } + + public update(deltaT: number) { + // Add occasional logging to confirm update is being called + if (Math.random() < 0.01) { // ~1% chance per frame + console.log(`๐Ÿ”„ [UPDATE] SimCameraInput.update() called for ${this.device} (initialized: ${this._isInitialized})`) + } + + if (!this._isInitialized) { + this.initializeCamera() + this._isInitialized = true + } + + this.updateCameraSettings() + this.generateVideoFrame(deltaT) + } + + private initializeCamera() { + console.log(`๐ŸŽฅ [INIT] Starting camera initialization for ${this.device}`) + + // Initialize metadata + SimCamera.setConnected(this.device, true) + SimCamera.setResolutionWidth(this.device, this._defaultWidth) + SimCamera.setResolutionHeight(this.device, this._defaultHeight) + SimCamera.setFPS(this.device, this._defaultFPS) + SimCamera.setBrightness(this.device, 50) + SimCamera.setExposure(this.device, 50) + SimCamera.setAutoExposure(this.device, true) + + console.log(`๐ŸŽฅ [INIT] Camera metadata set, creating renderer...`) + + try { + // Initialize video renderer for 3D scene capture + this._cameraRenderer = new SimCameraRenderer(this._robot, this._defaultWidth, this._defaultHeight) + console.log(`โœ… [INIT] Camera ${this.device} initialized successfully - 3D frames will be generated`) + + // Force immediate test frame to verify renderer works + console.log(`๐Ÿงช [TEST] Attempting immediate test frame capture...`) + this._cameraRenderer.captureFrameAsJPEG().then(blob => { + console.log(`๐Ÿงช [TEST] Initial test frame captured: ${blob.size} bytes - renderer is working!`) + this.sendFrameToRobot(blob) + }).catch(error => { + console.error(`โŒ [TEST] Initial test frame failed:`, error) + }) + } catch (error) { + console.error(`โŒ [INIT] Failed to create camera renderer:`, error) + } + } + + private updateCameraSettings() { + const requestedWidth = SimCamera.getRequestedWidth(this._device) + const requestedHeight = SimCamera.getRequestedHeight(this._device) + const requestedFPS = SimCamera.getRequestedFPS(this._device) + + // Check if resolution changed + const currentWidth = SimCamera.getRequestedWidth(this._device) + const currentHeight = SimCamera.getRequestedHeight(this._device) + if (requestedWidth !== currentWidth || requestedHeight !== currentHeight) { + this.updateResolution(requestedWidth, requestedHeight) + } + + // Check if frame rate changed - ensure FPS is valid + if (requestedFPS && !isNaN(requestedFPS) && requestedFPS > 0 && requestedFPS !== this._defaultFPS) { + this.updateFrameRate(requestedFPS) + this._defaultFPS = requestedFPS + } + + SimCamera.setResolutionWidth(this.device, requestedWidth) + SimCamera.setResolutionHeight(this.device, requestedHeight) + SimCamera.setFPS(this.device, requestedFPS) + + const requestedBrightness = SimCamera.getRequestedBrightness(this._device) + const requestedExposure = SimCamera.getRequestedExposure(this._device) + const requestedAutoExposure = SimCamera.getRequestedAutoExposure(this._device) + + SimCamera.setBrightness(this.device, requestedBrightness) + SimCamera.setExposure(this.device, requestedExposure) + SimCamera.setAutoExposure(this.device, requestedAutoExposure) + } + + private generateVideoFrame(deltaT: number) { + if (!this._cameraRenderer) { + console.warn(`๐Ÿ“น [FRAME] No camera renderer for ${this.device} - skipping frame generation`) + return + } + + this._lastFrameTime += deltaT * 1000 // Convert to ms + + // Add timing debug logs occasionally + if (Math.random() < 0.01) { // ~1% chance per frame + console.log(`โฑ๏ธ [TIMING] ${this.device}: lastFrameTime=${this._lastFrameTime.toFixed(1)}ms, interval=${this._frameInterval}ms, deltaT=${(deltaT*1000).toFixed(1)}ms`) + } + + // Generate frame at specified FPS + if (this._lastFrameTime >= this._frameInterval) { + this._lastFrameTime = 0 + + console.log(`๐Ÿ“น [FRAME] Capturing 3D frame for ${this.device}`) + + // Capture frame from 3D scene (robot perspective) + this._cameraRenderer.captureFrameAsJPEG().then(blob => { + console.log(`๐Ÿ“น [FRAME] Successfully captured ${blob.size} bytes, sending to robot`) + this.sendFrameToRobot(blob) + }).catch(error => { + console.error(`โŒ [FRAME] Failed to capture camera frame:`, error) + }) + } + } + + private async sendFrameToRobot(frameBlob: Blob) { + try { + // Convert blob to base64 for WebSocket transmission + const arrayBuffer = await frameBlob.arrayBuffer() + const base64Frame = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))) + + console.log(`๐Ÿš€ [SEND] Converting frame: ${arrayBuffer.byteLength} bytes โ†’ ${base64Frame.length} chars`) + + console.log(`๐Ÿ“ก [SEND] WebSocket frame sent for ${this.device}: ${success ? 'SUCCESS' : 'FAILED'}`) + + } catch (error) { + console.error(`โŒ [SEND] Failed to send camera frame:`, error) + } + } + + private updateResolution(width: number, height: number) { + if (this._cameraRenderer) { + this._cameraRenderer.setResolution(width, height) + } + } + + private updateFrameRate(fps: number) { + this._frameInterval = 1000 / fps + } + + public reconnect() { + SimCamera.setConnected(this._device, true) + this._isInitialized = false // set false so reinitialize on next update + } + + public disconnect() { + SimCamera.setConnected(this._device, false) + this._isInitialized = false + if (this._cameraRenderer) { + this._cameraRenderer.dispose() + this._cameraRenderer = undefined + } + } +} + export class SimDigitalInput extends SimInput { private _valueSupplier: () => boolean From f76957f0d659c5d79be7370d4f5a6af948e3e0a6 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 31 Jul 2025 23:16:58 -0700 Subject: [PATCH 07/40] Add camera to wpilibbrain --- .../simulation/wpilib_brain/WPILibBrain.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 1ec0cd5908..f34df86457 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -6,7 +6,7 @@ import { SimulationLayer } from "../SimulationSystem" import World from "@/systems/World" import { SimAnalogOutput, SimDigitalOutput, SimOutput } from "./SimOutput" -import { SimAccelInput, SimAnalogInput, SimDigitalInput, SimGyroInput, SimInput } from "./SimInput" +import { SimAccelInput, SimAnalogInput, SimCameraInput, SimDigitalInput, SimGyroInput, SimInput } from "./SimInput" import { random } from "@/util/Random" import { NoraNumber, NoraNumber2, NoraNumber3, NoraTypes } from "../Nora" import { SimFlow, SimReceiver, SimSupplier, validate } from "./SimDataFlow" @@ -41,6 +41,7 @@ export enum SimType { CAN_ENCODER = "CANEncoder", GYRO = "Gyro", ACCELEROMETER = "Accel", + CAMERA = "Camera", DIO = "DIO", AI = "AI", AO = "AO", @@ -70,6 +71,7 @@ export const supplierTypeMap: { [k in SimType]: NoraTypes | undefined } = { [SimType.CAN_ENCODER]: undefined, [SimType.GYRO]: undefined, [SimType.ACCELEROMETER]: undefined, + [SimType.CAMERA]: undefined, [SimType.DIO]: NoraTypes.NUMBER, // ? [SimType.AI]: undefined, [SimType.AO]: NoraTypes.NUMBER, @@ -84,6 +86,7 @@ export const receiverTypeMap: { [k in SimType]: NoraTypes | undefined } = { [SimType.CAN_ENCODER]: NoraTypes.NUMBER2, [SimType.GYRO]: NoraTypes.NUMBER3, // Wrong but its fine [SimType.ACCELEROMETER]: NoraTypes.NUMBER3, + [SimType.CAMERA]: undefined, [SimType.DIO]: NoraTypes.NUMBER, // ? [SimType.AI]: NoraTypes.NUMBER, [SimType.AO]: undefined, @@ -567,6 +570,8 @@ class WPILibBrain extends Brain { constructor(assembly: MirabufSceneObject) { super(assembly.mechanism, "wpilib") + console.log(`๐Ÿง  [WPILIBRAIN] Constructor called for assembly: ${assembly.assemblyName}`) + this._assembly = assembly this._simLayer = World.simulationSystem.getSimulationLayer(this._mechanism)! @@ -576,8 +581,11 @@ class WPILibBrain extends Brain { return } + console.log(`๐Ÿง  [WPILIBRAIN] SimulationLayer found, setting up devices...`) + this.addSimInput(new SimGyroInput("Test Gyro[1]", this._mechanism)) this.addSimInput(new SimAccelInput("ADXL362[4]", this._mechanism)) + this.addSimInput(new SimCameraInput("USB Camera 0", assembly, 640, 480, 30)) this.addSimInput(new SimDigitalInput("SYN DI[0]", () => random() > 0.5)) this.addSimOutput(new SimDigitalOutput("SYN DO[1]")) this.addSimInput(new SimAnalogInput("SYN AI[0]", () => random() * 12)) @@ -597,7 +605,9 @@ class WPILibBrain extends Brain { } public addSimInput(input: SimInput) { + console.log(`โž• [WPILIBRAIN] Adding SimInput: ${input.constructor.name} for device "${input.device}"`) this._simInputs.push(input) + console.log(`๐Ÿ“Š [WPILIBRAIN] Total inputs: ${this._simInputs.length}`) } public addSimFlow(flow: SimFlow): boolean { @@ -632,6 +642,11 @@ class WPILibBrain extends Brain { } public update(deltaT: number): void { + // Add occasional logging to confirm update is being called + if (Math.random() < 0.005) { // ~0.5% chance per frame + console.log(`๐Ÿ”„ [WPILIBRAIN] update() called - ${this._simInputs.length} inputs, ${this._simOutputs.length} outputs`) + } + this._simOutputs.forEach(d => d.update(deltaT)) this._simInputs.forEach(i => i.update(deltaT)) this._simFlows.forEach(({ supplier, receiver }) => { From 1f239ca523b73d8868336fa77725735bcb43857f Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 31 Jul 2025 23:17:30 -0700 Subject: [PATCH 08/40] Add SimCamera --- .../simulation/wpilib_brain/WPILibBrain.ts | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index f34df86457..46df82b4f6 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -402,6 +402,66 @@ export class SimAccel { } } +export class SimCamera { + private constructor() {} + + public static setConnected(device: string, connected: boolean): boolean { + return SimGeneric.set(SimType.CAMERA, device, ">connected", connected) + } + + public static getConnected(device: string): boolean { + return SimGeneric.get(SimType.CAMERA, device, ">connected", false) + } + + public static setResolutionWidth(device: string, width: number): boolean { + return SimGeneric.set(SimType.CAMERA, device, ">width", width) + } + + public static setResolutionHeight(device: string, height: number): boolean { + return SimGeneric.set(SimType.CAMERA, device, ">height", height) + } + + public static setFPS(device: string, fps: number): boolean { + return SimGeneric.set(SimType.CAMERA, device, ">fps", fps) + } + + public static getRequestedWidth(device: string): number { + return SimGeneric.get(SimType.CAMERA, device, "brightness", brightness) + } + + public static setExposure(device: string, exposure: number): boolean { + return SimGeneric.set(SimType.CAMERA, device, ">exposure", exposure) + } + + public static setAutoExposure(device: string, autoExposure: boolean): boolean { + return SimGeneric.set(SimType.CAMERA, device, ">auto_exposure", autoExposure) + } + + public static getRequestedBrightness(device: string): number { + return SimGeneric.get(SimType.CAMERA, device, " Date: Thu, 31 Jul 2025 23:18:04 -0700 Subject: [PATCH 09/40] Add camera frame sending --- .../systems/simulation/wpilib_brain/WPILibBrain.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 46df82b4f6..75c8e247e6 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -222,6 +222,17 @@ export class SimGeneric { window.dispatchEvent(new SimMapUpdateEvent(true)) return true } + + public static sendCameraFrame(device: string, frameData: any): boolean { + worker.getValue().postMessage({ + command: "camera_frame", + data: { + device: device, + ...frameData + }, + }) + return true + } } export class SimDriverStation { From 64a0587f88dd84e1f5a49356bd25ff9f51b0c941 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 31 Jul 2025 23:18:53 -0700 Subject: [PATCH 10/40] Update siminput camera frame sending --- .../simulation/wpilib_brain/SimInput.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index 1a9145a908..dd3810aca2 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -1,11 +1,12 @@ import World from "@/systems/World" import EncoderStimulus from "../stimulus/EncoderStimulus" -import { SimCANEncoder, SimGyro, SimAccel, SimDIO, SimAI } from "./WPILibBrain" +import { SimCANEncoder, SimGyro, SimAccel, SimDIO, SimAI, SimCamera, SimGeneric } from "./WPILibBrain" import Mechanism from "@/systems/physics/Mechanism" import Jolt from "@azaleacolburn/jolt-physics" import JOLT from "@/util/loading/JoltSyncLoader" import { convertJoltQuatToThreeQuaternion, convertJoltVec3ToThreeVector3 } from "@/util/TypeConversions" import * as THREE from "three" +import { SimCameraRenderer } from "./SimCameraRenderer" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" export abstract class SimInput { @@ -260,7 +261,21 @@ export class SimCameraInput extends SimInput { const base64Frame = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))) console.log(`๐Ÿš€ [SEND] Converting frame: ${arrayBuffer.byteLength} bytes โ†’ ${base64Frame.length} chars`) - + + // Send frame through WebSocket protocol + const frameMessage = { + type: "CAMERA_FRAME", + device: this.device, + data: { + frame: base64Frame, + width: this._defaultWidth, + height: this._defaultHeight, + timestamp: Date.now() + } + } + + // Send through the existing WebSocket worker + const success = SimGeneric.sendCameraFrame(this.device, frameMessage.data) console.log(`๐Ÿ“ก [SEND] WebSocket frame sent for ${this.device}: ${success ? 'SUCCESS' : 'FAILED'}`) } catch (error) { From 626c9de5e72b16f2dfae8e6d5a4185e808798025 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 31 Jul 2025 23:20:06 -0700 Subject: [PATCH 11/40] Add camera frame websocket --- .../simulation/wpilib_brain/WPILibWSWorker.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts b/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts index 49dca0465b..f4e59f5ec8 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts @@ -89,6 +89,18 @@ self.addEventListener("message", e => { } break } + case "camera_frame": { + if (socketOpen()) { + // Send camera frame data through WebSocket + const frameMessage = { + type: "CAMERA_FRAME", + device: e.data.data.device, + data: e.data.data + } + socket!.send(JSON.stringify(frameMessage)) + } + break + } default: { console.warn(`Unrecognized command '${e.data.command}'`) break From 8aed9caf9238d32601f8d79870124754a048f986 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 31 Jul 2025 23:20:42 -0700 Subject: [PATCH 12/40] Add camera to wsviewpanel --- fission/src/ui/panels/WSViewPanel.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fission/src/ui/panels/WSViewPanel.tsx b/fission/src/ui/panels/WSViewPanel.tsx index 6ff0e731fe..2f17f681cf 100644 --- a/fission/src/ui/panels/WSViewPanel.tsx +++ b/fission/src/ui/panels/WSViewPanel.tsx @@ -49,6 +49,7 @@ function generateTableBody() { // SimType.CANEncoder, // SimType.Gyro, // SimType.Accel, + // SimType.Camera, // SimType.DIO, // SimType.AI, // SimType.AO, @@ -166,7 +167,7 @@ const WSViewPanel: React.FC = ({ panelId }) => { setSelectedType(v as unknown as SimType)} /> {/* {deviceSelect} */} From d2077045824c33691e1f867d63bf66b85abac0e5 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Fri, 1 Aug 2025 09:22:46 -0700 Subject: [PATCH 13/40] Fix: duplicate identifiers --- fission/src/systems/simulation/wpilib_brain/SimInput.ts | 5 ----- fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts | 2 -- 2 files changed, 7 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index 25963da119..61396181ee 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -4,15 +4,10 @@ import { SimCANEncoder, SimGyro, SimAccel, SimDIO, SimAI, SimCamera, SimGeneric import Mechanism from "@/systems/physics/Mechanism" import Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" -import Mechanism from "@/systems/physics/Mechanism" -import World from "@/systems/World" import JOLT from "@/util/loading/JoltSyncLoader" import { convertJoltQuatToThreeQuaternion, convertJoltVec3ToThreeVector3 } from "@/util/TypeConversions" -import * as THREE from "three" import { SimCameraRenderer } from "./SimCameraRenderer" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" -import EncoderStimulus from "../stimulus/EncoderStimulus" -import { SimAccel, SimAI, SimCANEncoder, SimDIO, SimGyro } from "./WPILibBrain" export abstract class SimInput { constructor(protected _device: string) {} diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 581919538b..f186862600 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -11,8 +11,6 @@ import { NoraNumber, NoraNumber2, NoraNumber3, NoraTypes } from "../Nora" import { SimulationLayer } from "../SimulationSystem" import SynthesisBrain from "../synthesis_brain/SynthesisBrain" import { SimFlow, SimReceiver, SimSupplier, validate } from "./SimDataFlow" -import { SimAccelInput, SimAnalogInput, SimDigitalInput, SimGyroInput, SimInput } from "./SimInput" -import { SimAnalogOutput, SimDigitalOutput, SimOutput } from "./SimOutput" import WPILibWSWorker from "./WPILibWSWorker?worker" const worker: Lazy = new Lazy(() => new WPILibWSWorker()) From eab94149f874c8e2fb081bd068eb61a29c1d857f Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Fri, 1 Aug 2025 11:33:56 -0700 Subject: [PATCH 14/40] Add java websocket implementation --- simulation/samples/JavaSample/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/simulation/samples/JavaSample/build.gradle b/simulation/samples/JavaSample/build.gradle index a5850fb40b..a6501e2953 100644 --- a/simulation/samples/JavaSample/build.gradle +++ b/simulation/samples/JavaSample/build.gradle @@ -80,6 +80,7 @@ dependencies { testRuntimeOnly 'org.junit.platform:junit-platform-launcher' implementation "com.autodesk.synthesis:SyntheSimJava:1.0.0" + implementation 'org.java-websocket:Java-WebSocket:1.5.4' } test { From 2c6090b846c00d420eec45e08bfb1b307dce9263 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Fri, 1 Aug 2025 11:34:19 -0700 Subject: [PATCH 15/40] Add java-websocket implementation --- simulation/SyntheSimJava/build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/simulation/SyntheSimJava/build.gradle b/simulation/SyntheSimJava/build.gradle index 66e72d99f8..f06d249249 100644 --- a/simulation/SyntheSimJava/build.gradle +++ b/simulation/SyntheSimJava/build.gradle @@ -68,6 +68,9 @@ dependencies { // JSON processing implementation 'org.json:json:20231013' + // WebSocket server + implementation 'org.java-websocket:Java-WebSocket:1.5.4' + // OpenCV (Note: WPILib includes OpenCV, but we need to make sure it's available) implementation "edu.wpi.first.thirdparty.frc2024.opencv:opencv-java:4.8.0-2" } From 3c317e3484fbb43c53378fcd1b9b779e9aa15e10 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Fri, 1 Aug 2025 11:45:02 -0700 Subject: [PATCH 16/40] chore: formatting --- .../simulation/wpilib_brain/WPILibBrain.ts | 15 +++++++++------ .../simulation/wpilib_brain/WPILibWSWorker.ts | 2 +- fission/src/ui/panels/WSViewPanel.tsx | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index f186862600..cb194bee77 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -84,7 +84,7 @@ export const receiverTypeMap: { [k in SimType]: NoraTypes | undefined } = { [SimType.CAN_ENCODER]: NoraTypes.NUMBER2, [SimType.GYRO]: NoraTypes.NUMBER3, // Wrong but its fine [SimType.ACCELEROMETER]: NoraTypes.NUMBER3, - [SimType.CAMERA]: undefined, + [SimType.CAMERA]: undefined, [SimType.DIO]: NoraTypes.NUMBER, // ? [SimType.AI]: NoraTypes.NUMBER, [SimType.AO]: undefined, @@ -226,7 +226,7 @@ export class SimGeneric { command: "camera_frame", data: { device: device, - ...frameData + ...frameData, }, }) return true @@ -465,7 +465,7 @@ export class SimCamera { public static getRequestedExposure(device: string): number { return SimGeneric.get(SimType.CAMERA, device, " d.update(deltaT)) this._simInputs.forEach(i => i.update(deltaT)) this._simFlows.forEach(({ supplier, receiver }) => { diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts b/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts index f4e59f5ec8..b28cf0a0f5 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts @@ -95,7 +95,7 @@ self.addEventListener("message", e => { const frameMessage = { type: "CAMERA_FRAME", device: e.data.data.device, - data: e.data.data + data: e.data.data, } socket!.send(JSON.stringify(frameMessage)) } diff --git a/fission/src/ui/panels/WSViewPanel.tsx b/fission/src/ui/panels/WSViewPanel.tsx index 10c393fcc5..b370eed165 100644 --- a/fission/src/ui/panels/WSViewPanel.tsx +++ b/fission/src/ui/panels/WSViewPanel.tsx @@ -49,7 +49,7 @@ function generateTableBody() { // SimType.CANEncoder, // SimType.Gyro, // SimType.Accel, - // SimType.Camera, + // SimType.Camera, // SimType.DIO, // SimType.AI, // SimType.AO, From d45511ccae7083d1b4d66a5016e52fde5951b5ba Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Fri, 1 Aug 2025 13:49:48 -0700 Subject: [PATCH 17/40] Add SimCameraRenderer --- .../wpilib_brain/SimCameraRenderer.ts | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts diff --git a/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts new file mode 100644 index 0000000000..9cfbf122c3 --- /dev/null +++ b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts @@ -0,0 +1,122 @@ +import * as THREE from "three" +import World from "@/systems/World" +import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" + +export class SimCameraRenderer { + private _camera: THREE.PerspectiveCamera + private _renderTarget: THREE.WebGLRenderTarget + private _canvas: OffscreenCanvas + private _ctx: OffscreenCanvasRenderingContext2D + private _robot: MirabufSceneObject + private _cameraPosition: THREE.Vector3 + private _cameraQuaternion: THREE.Quaternion + + constructor(robot: MirabufSceneObject, width: number = 640, height: number = 480) { + this._robot = robot + + // Create camera for robot perspective + this._camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000) + + // Create render target for off-screen rendering + this._renderTarget = new THREE.WebGLRenderTarget(width, height, { + minFilter: THREE.LinearFilter, + magFilter: THREE.LinearFilter, + format: THREE.RGBAFormat, + }) + + // Create canvas for frame capture + this._canvas = new OffscreenCanvas(width, height) + this._ctx = this._canvas.getContext("2d")! + + // Camera position relative to robot + this._cameraPosition = new THREE.Vector3(0, 0.5, 0.2) // Mounted on robot + this._cameraQuaternion = new THREE.Quaternion() + + this.updateCameraTransform() + } + + private updateCameraTransform() { + if (!this._robot.mechanism.rootBody) return + + // Get robot's transform + const robotBody = World.physicsSystem.getBody( + this._robot.mechanism.nodeToBody.get(this._robot.mechanism.rootBody)! + ) + + if (!robotBody) return + + // Position camera relative to robot + const robotPos = robotBody.GetPosition() + const robotRot = robotBody.GetRotation() + + // Convert Jolt to Three.js + const robotPosition = new THREE.Vector3(robotPos.GetX(), robotPos.GetY(), robotPos.GetZ()) + const robotQuaternion = new THREE.Quaternion(robotRot.GetX(), robotRot.GetY(), robotRot.GetZ(), robotRot.GetW()) + + // Apply camera offset + const worldCameraPos = this._cameraPosition.clone() + worldCameraPos.applyQuaternion(robotQuaternion) + worldCameraPos.add(robotPosition) + + this._camera.position.copy(worldCameraPos) + this._camera.quaternion.copy(robotQuaternion) + this._camera.updateMatrixWorld() + } + + public renderFrame(): ImageData | null { + if (!World.sceneRenderer) return null + + this.updateCameraTransform() + + // Render scene from camera perspective + const renderer = (World.sceneRenderer as any)._renderer as THREE.WebGLRenderer + const scene = (World.sceneRenderer as any)._scene as THREE.Scene + + // Store original render target + const originalTarget = renderer.getRenderTarget() + + // Render to our target + renderer.setRenderTarget(this._renderTarget) + renderer.render(scene, this._camera) + + // Read pixels + const pixels = new Uint8Array(this._renderTarget.width * this._renderTarget.height * 4) + + // Restore original target + renderer.setRenderTarget(originalTarget) + + // Convert to ImageData + const imageData = new ImageData( + new Uint8ClampedArray(pixels), + this._renderTarget.width, + this._renderTarget.height + ) + + return imageData + } + + public captureFrame(): { + const imageData = this.renderFrame() + + return imageData + } + + public setResolution(width: number, height: number) { + this._renderTarget.setSize(width, height) + this._canvas.width = width + this._canvas.height = height + this._camera.aspect = width / height + this._camera.updateProjectionMatrix() + } + + public setCameraOffset(position: THREE.Vector3, rotation?: THREE.Quaternion) { + this._cameraPosition.copy(position) + if (rotation) { + this._cameraQuaternion.copy(rotation) + } + } + + public dispose() { + this._renderTarget.dispose() + } +} From 75634eb7ee494ba4a8b3611db6ef59b4ac9576ee Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Fri, 1 Aug 2025 13:50:12 -0700 Subject: [PATCH 18/40] Update captureFrame to jpeg --- .../systems/simulation/wpilib_brain/SimCameraRenderer.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts index 9cfbf122c3..fbe7bd93b9 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts @@ -95,10 +95,15 @@ export class SimCameraRenderer { return imageData } - public captureFrame(): { + public captureFrameAsJPEG(): Promise { const imageData = this.renderFrame() + if (!imageData) return Promise.reject("No frame data") - return imageData + // Draw to canvas + this._ctx.putImageData(imageData, 0, 0) + + // Convert to JPEG blob + return this._canvas.convertToBlob({ type: "image/jpeg", quality: 0.8 }) } public setResolution(width: number, height: number) { From c91bb8066bcf63a5d290cd0adbc14e2284783034 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Fri, 1 Aug 2025 13:51:03 -0700 Subject: [PATCH 19/40] Update renderFrame --- .../systems/simulation/wpilib_brain/SimCameraRenderer.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts index fbe7bd93b9..7b9bd36987 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts @@ -81,6 +81,14 @@ export class SimCameraRenderer { // Read pixels const pixels = new Uint8Array(this._renderTarget.width * this._renderTarget.height * 4) + renderer.readRenderTargetPixels( + this._renderTarget, + 0, + 0, + this._renderTarget.width, + this._renderTarget.height, + pixels + ) // Restore original target renderer.setRenderTarget(originalTarget) From 48031cd9ab8368ea30bd03d418d07c17ba5d730b Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:22:56 -0700 Subject: [PATCH 20/40] Add SynthesisWebSocketServer --- .../synthesis/SynthesisWebSocketServer.java | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java new file mode 100644 index 0000000000..0ba8af2c2f --- /dev/null +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java @@ -0,0 +1,83 @@ +package com.autodesk.synthesis; + +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; + +import java.net.InetSocketAddress; + +/** + * WebSocket server that receives messages from the Fission simulator + * and forwards them to the WebSocketMessageHandler + */ +public class SynthesisWebSocketServer extends WebSocketServer { + private static SynthesisWebSocketServer instance; + private boolean isRunning = false; + + private SynthesisWebSocketServer(InetSocketAddress address) { + super(address); + } + + public static SynthesisWebSocketServer getInstance() { + if (instance == null) { + instance = new SynthesisWebSocketServer(new SocketAddress("localhost", 3300)); + } + return instance; + } + + /** + * Called when the server stops + */ + public void onServerStop() { + isRunning = false; + } + + @Override + public void onMessage(WebSocket conn, String message) { + // Forward the message to our handler + WebSocketMessageHandler.getInstance().handleMessage(message); + } + + @Override + public void onError(WebSocket conn, Exception ex) { + System.err.println("WebSocket error: " + ex.getMessage()); + } + + @Override + public void onStart() { + isRunning = true; + System.out.println("WebSocket server started on port 3300"); + System.out.println("Listening for camera frames from Fission simulator..."); + } + + /** + * Start the WebSocket server + */ + public void startServer() { + if (!isRunning) { + try { + start(); + System.out.println("๐Ÿš€ Synthesis WebSocket server starting on ws://localhost:3300/wpilibws"); + } catch (Exception e) { + System.err.println("Error starting WebSocket server: " + e.getMessage()); + } + } else { + System.out.println("WebSocket server is already running"); + } + } + + /** + * Stop the WebSocket server + */ + public void stopServer() { + if (isRunning) { + try { + stop(); + onServerStop(); + System.out.println("WebSocket server stopped"); + } catch (Exception e) { + System.err.println("Error stopping WebSocket server: " + e.getMessage()); + } + } + } +} \ No newline at end of file From 9e80ad2f8f5dfabe1bc71abc439fc47f73612e32 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:23:32 -0700 Subject: [PATCH 21/40] Update SocketAddress to InetSocketAddress --- .../java/com/autodesk/synthesis/SynthesisWebSocketServer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java index 0ba8af2c2f..956a702ad8 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java @@ -20,7 +20,7 @@ private SynthesisWebSocketServer(InetSocketAddress address) { public static SynthesisWebSocketServer getInstance() { if (instance == null) { - instance = new SynthesisWebSocketServer(new SocketAddress("localhost", 3300)); + instance = new SynthesisWebSocketServer(new InetSocketAddress("localhost", 3300)); } return instance; } From 2a4dfba68e9c4783381037f142e70b714d8b0917 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:24:16 -0700 Subject: [PATCH 22/40] Update onOpen & onClose --- .../synthesis/SynthesisWebSocketServer.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java index 956a702ad8..3b89be1ea5 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java @@ -25,16 +25,22 @@ public static SynthesisWebSocketServer getInstance() { return instance; } - /** - * Called when the server stops - */ + @Override + public void onOpen(WebSocket conn, ClientHandshake handshake) { + System.out.println("WebSocket connection opened: " + conn.getRemoteSocketAddress()); + } + + @Override + public void onClose(WebSocket conn, int code, String reason, boolean remote) { + System.out.println("WebSocket connection closed: " + conn.getRemoteSocketAddress()); + } + public void onServerStop() { isRunning = false; } @Override public void onMessage(WebSocket conn, String message) { - // Forward the message to our handler WebSocketMessageHandler.getInstance().handleMessage(message); } @@ -49,10 +55,7 @@ public void onStart() { System.out.println("WebSocket server started on port 3300"); System.out.println("Listening for camera frames from Fission simulator..."); } - - /** - * Start the WebSocket server - */ + public void startServer() { if (!isRunning) { try { @@ -66,9 +69,6 @@ public void startServer() { } } - /** - * Stop the WebSocket server - */ public void stopServer() { if (isRunning) { try { From 94c4284180df6f239f98c5b70c2a1be5c2eb5787 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:26:03 -0700 Subject: [PATCH 23/40] Add WebSocketMessageHandler --- .../synthesis/WebSocketMessageHandler.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java new file mode 100644 index 0000000000..96c4272838 --- /dev/null +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java @@ -0,0 +1,58 @@ +package com.autodesk.synthesis; + +import org.json.JSONObject; + +/** + * Handles incoming WebSocket messages from the simulation. + * This processes various message types including camera frames. + */ +public class WebSocketMessageHandler { + private static WebSocketMessageHandler instance; + + private WebSocketMessageHandler() {} + + public static WebSocketMessageHandler getInstance() { + if (instance == null) { + instance = new WebSocketMessageHandler(); + } + return instance; + } + + /** + * Process incoming WebSocket message + * @param messageJson JSON string containing the message + */ + public void handleMessage(String messageJson) { + JSONObject message = new JSONObject(messageJson); + String type = message.getString("type"); + + System.out.println("DEBUG: Received WebSocket message type: " + type); + + switch (type) { + case "CAMERA_FRAME": + System.out.println("DEBUG: Processing camera frame message..."); + handleCameraFrame(message); + break; + default: + // Handle other message types here + System.out.println("DEBUG: Unhandled message type: " + type); + break; + } + } + + /** + * Handle camera frame message + */ + private void handleCameraFrame(JSONObject message) { + String device = message.getString("device"); + JSONObject data = message.getJSONObject("data"); + + String frameData = data.getString("frame"); + int width = data.getInt("width"); + int height = data.getInt("height"); + + // Forward to camera frame handler + CameraFrameHandler.getInstance().handleFrame(device, frameData, width, height); + + } +} \ No newline at end of file From d9ad7a597a1b9b3e8ea1138f850dd27b9af983ef Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:26:24 -0700 Subject: [PATCH 24/40] Add simulateTestMessage --- .../synthesis/WebSocketMessageHandler.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java index 96c4272838..4501b39961 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java @@ -55,4 +55,25 @@ private void handleCameraFrame(JSONObject message) { CameraFrameHandler.getInstance().handleFrame(device, frameData, width, height); } + + /** + * Simulate receiving a camera frame (for testing) + */ + public void simulateTestMessage() { + String testMessage = """ + { + "type": "CAMERA_FRAME", + "device": "USB Camera 0", + "data": { + "frame": "", + "width": 640, + "height": 480, + "timestamp": %d + } + } + """.formatted(System.currentTimeMillis()); + + System.out.println("Simulating test WebSocket message..."); + handleMessage(testMessage); + } } \ No newline at end of file From 8bea9081de64cd1d3442b47d30171bb5ec49043f Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:27:37 -0700 Subject: [PATCH 25/40] Add message handling error handling --- .../synthesis/WebSocketMessageHandler.java | 47 +++++++++++-------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java index 4501b39961..b87bc6ba30 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/WebSocketMessageHandler.java @@ -23,41 +23,48 @@ public static WebSocketMessageHandler getInstance() { * @param messageJson JSON string containing the message */ public void handleMessage(String messageJson) { - JSONObject message = new JSONObject(messageJson); - String type = message.getString("type"); + try { + JSONObject message = new JSONObject(messageJson); + String type = message.getString("type"); - System.out.println("DEBUG: Received WebSocket message type: " + type); + System.out.println("DEBUG: Received WebSocket message type: " + type); - switch (type) { - case "CAMERA_FRAME": - System.out.println("DEBUG: Processing camera frame message..."); - handleCameraFrame(message); - break; - default: - // Handle other message types here - System.out.println("DEBUG: Unhandled message type: " + type); - break; + switch (type) { + case "CAMERA_FRAME": + System.out.println("DEBUG: Processing camera frame message..."); + handleCameraFrame(message); + break; + default: + System.out.println("DEBUG: Unhandled message type: " + type); + break; } + } catch (Exception e) { + System.err.println("ERROR: Error processing WebSocket message: " + e.getMessage()); + } } /** * Handle camera frame message */ private void handleCameraFrame(JSONObject message) { - String device = message.getString("device"); - JSONObject data = message.getJSONObject("data"); + try { + String device = message.getString("device"); + JSONObject data = message.getJSONObject("data"); - String frameData = data.getString("frame"); - int width = data.getInt("width"); - int height = data.getInt("height"); + String frameData = data.getString("frame"); + int width = data.getInt("width"); + int height = data.getInt("height"); - // Forward to camera frame handler - CameraFrameHandler.getInstance().handleFrame(device, frameData, width, height); + // Forward to camera frame handler + CameraFrameHandler.getInstance().handleFrame(device, frameData, width, height); + } catch (Exception e) { + System.err.println("ERROR: Error processing camera frame: " + e.getMessage()); + } } /** - * Simulate receiving a camera frame (for testing) + * Simulate receiving a camera frame */ public void simulateTestMessage() { String testMessage = """ From c9e20d41a3f3513eb3b4d50dadfb37e453badecd Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:31:08 -0700 Subject: [PATCH 26/40] Add CameraFrameHandler --- .../synthesis/CameraFrameHandler.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java new file mode 100644 index 0000000000..027839918c --- /dev/null +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java @@ -0,0 +1,69 @@ +package com.autodesk.synthesis; + +import edu.wpi.first.cscore.CvSource; +import org.opencv.core.Mat; +import org.opencv.core.CvType; +import org.opencv.core.MatOfByte; +import org.opencv.imgcodecs.Imgcodecs; +import java.util.Base64; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Handles camera frames from the simulation and feeds them to WPILib CvSource objects. + * This bridges the gap between Synthesis 3D rendering and WPILib camera streaming. + */ +public class CameraFrameHandler { + private static CameraFrameHandler instance; + private final ConcurrentHashMap cameraSources = new ConcurrentHashMap<>(); + + private CameraFrameHandler() {} + + public static CameraFrameHandler getInstance() { + if (instance == null) { + instance = new CameraFrameHandler(); + } + return instance; + } + + /** + * Register a CvSource for a camera device + * @param deviceName The name of the camera device (e.g., "USB Camera 0") + * @param source The CvSource to feed frames to + */ + public void registerCamera(String deviceName, CvSource source) { + cameraSources.put(deviceName, source); + System.out.println("Camera registered for: " + deviceName); + } + + /** + * Handle incoming camera frame from simulation + * @param deviceName The camera device name + * @param base64Frame Base64 encoded JPEG frame data + * @param width Frame width + * @param height Frame height + */ + public void handleFrame(String deviceName, String base64Frame, int width, int height) { + CvSource source = cameraSources.get(deviceName); + + try { + byte[] frameData = Base64.getDecoder().decode(base64Frame); + + // Feed frame to CvSource and automatically streams to dashboards + source.putFrame(frame); + + // Clean up + frame.release(); + matOfByte.release(); + + } catch (Exception e) { + System.err.println("ERROR: Error processing camera frame for " + deviceName + ": " + e.getMessage()); + } + } + + /** + * Get registered camera count + */ + public int getCameraCount() { + return cameraSources.size(); + } +} \ No newline at end of file From bc9449b1e20c9b331e78f208ce4744e6e6fb5bfb Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:31:41 -0700 Subject: [PATCH 27/40] Add frame handler debug prints --- .../synthesis/CameraFrameHandler.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java index 027839918c..fd1faa702e 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java @@ -44,10 +44,43 @@ public void registerCamera(String deviceName, CvSource source) { */ public void handleFrame(String deviceName, String base64Frame, int width, int height) { CvSource source = cameraSources.get(deviceName); + if (source == null) { + System.out.println("DEBUG: Camera not registered for " + deviceName); + return; + } + if (base64Frame == null || base64Frame.trim().isEmpty()) { + System.out.println("DEBUG: Skipping empty frame for " + deviceName); + return; + } + + System.out.println("DEBUG: Received real 3D frame for " + deviceName + + " (" + width + "x" + height + ", " + base64Frame.length() + " chars)"); try { byte[] frameData = Base64.getDecoder().decode(base64Frame); + // Check if decoded data is empty + if (frameData.length == 0) { + System.out.println("INFO: Skipping frame with empty data for " + deviceName); + return; + } + + // Convert JPEG bytes to OpenCV Mat + MatOfByte matOfByte = new MatOfByte(frameData); + Mat frame = Imgcodecs.imdecode(matOfByte, Imgcodecs.IMREAD_COLOR); + + if (frame.empty()) { + System.err.println("WARNING: Failed to decode camera frame for " + deviceName); + return; + } + + // Ensure frame has correct dimensions + if (frame.rows() != height || frame.cols() != width) { + System.err.println("WARNING: Frame size mismatch for " + deviceName + + ": expected " + width + "x" + height + + ", got " + frame.cols() + "x" + frame.rows()); + } + // Feed frame to CvSource and automatically streams to dashboards source.putFrame(frame); From b60380dd121843b8edc61ec12b229a44ce66aa74 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:31:56 -0700 Subject: [PATCH 28/40] Add test frame --- .../com/autodesk/synthesis/CameraFrameHandler.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java index fd1faa702e..6debd02a72 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java @@ -93,6 +93,20 @@ public void handleFrame(String deviceName, String base64Frame, int width, int he } } + /** + * Create a test pattern frame + */ + public void sendTestFrame(String deviceName, int width, int height) { + CvSource source = cameraSources.get(deviceName); + if (source == null) return; + + Mat testFrame = new Mat(height, width, CvType.CV_8UC3); + testFrame.setTo(new org.opencv.core.Scalar(100, 150, 200)); // Light blue + + source.putFrame(testFrame); + testFrame.release(); + } + /** * Get registered camera count */ From 1fa32497eb93a42033d4fb73b0ea7a4facce4e92 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:33:48 -0700 Subject: [PATCH 29/40] Update CameraFrameHandler --- .../com/autodesk/synthesis/CameraFrameHandler.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java index 6debd02a72..99b9631eba 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/CameraFrameHandler.java @@ -48,18 +48,18 @@ public void handleFrame(String deviceName, String base64Frame, int width, int he System.out.println("DEBUG: Camera not registered for " + deviceName); return; } + if (base64Frame == null || base64Frame.trim().isEmpty()) { System.out.println("DEBUG: Skipping empty frame for " + deviceName); return; } - + System.out.println("DEBUG: Received real 3D frame for " + deviceName + " (" + width + "x" + height + ", " + base64Frame.length() + " chars)"); try { byte[] frameData = Base64.getDecoder().decode(base64Frame); - // Check if decoded data is empty if (frameData.length == 0) { System.out.println("INFO: Skipping frame with empty data for " + deviceName); return; @@ -81,10 +81,9 @@ public void handleFrame(String deviceName, String base64Frame, int width, int he ", got " + frame.cols() + "x" + frame.rows()); } - // Feed frame to CvSource and automatically streams to dashboards + // Feed frame to CvSource source.putFrame(frame); - - // Clean up + frame.release(); matOfByte.release(); @@ -94,7 +93,7 @@ public void handleFrame(String deviceName, String base64Frame, int width, int he } /** - * Create a test pattern frame + * Create a test pattern frame (for debugging) */ public void sendTestFrame(String deviceName, int width, int height) { CvSource source = cameraSources.get(deviceName); From 93b0e3679342f523d79588dfa9142d281637ce85 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:34:33 -0700 Subject: [PATCH 30/40] Update SimInput --- .../simulation/wpilib_brain/SimInput.ts | 101 +++++++++++------- 1 file changed, 65 insertions(+), 36 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index 61396181ee..ceb369249c 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -7,6 +7,7 @@ import * as THREE from "three" import JOLT from "@/util/loading/JoltSyncLoader" import { convertJoltQuatToThreeQuaternion, convertJoltVec3ToThreeVector3 } from "@/util/TypeConversions" import { SimCameraRenderer } from "./SimCameraRenderer" +import { SimCameraVisualization } from "./SimCameraVisualization" import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" export abstract class SimInput { @@ -132,6 +133,7 @@ export class SimCameraInput extends SimInput { private _defaultFPS: number = 30 private _isInitialized: boolean = false private _cameraRenderer?: SimCameraRenderer + private _cameraVisualization: SimCameraVisualization private _robot: MirabufSceneObject private _frameInterval: number = 0 private _lastFrameTime: number = 0 @@ -140,19 +142,28 @@ export class SimCameraInput extends SimInput { super(device) this._robot = robot if (width) this._defaultWidth = width - if (height) this._defaultHeight = height + if (height) this._defaultHeight = height if (fps) this._defaultFPS = fps this._frameInterval = 1000 / this._defaultFPS // ms between frames - - console.log(`๐ŸŽฌ [CONSTRUCTOR] SimCameraInput created for ${device} (${this._defaultWidth}x${this._defaultHeight} @ ${this._defaultFPS}fps, interval=${this._frameInterval}ms)`) + + // Create camera visualization + this._cameraVisualization = new SimCameraVisualization(robot) + this._cameraVisualization.setup() + + console.log( + `๐ŸŽฌ [CONSTRUCTOR] SimCameraInput created for ${device} (${this._defaultWidth}x${this._defaultHeight} @ ${this._defaultFPS}fps, interval=${this._frameInterval}ms)` + ) } public update(deltaT: number) { // Add occasional logging to confirm update is being called - if (Math.random() < 0.01) { // ~1% chance per frame - console.log(`๐Ÿ”„ [UPDATE] SimCameraInput.update() called for ${this.device} (initialized: ${this._isInitialized})`) + if (Math.random() < 0.01) { + // ~1% chance per frame + console.log( + `๐Ÿ”„ [UPDATE] SimCameraInput.update() called for ${this.device} (initialized: ${this._isInitialized})` + ) } - + if (!this._isInitialized) { this.initializeCamera() this._isInitialized = true @@ -160,11 +171,14 @@ export class SimCameraInput extends SimInput { this.updateCameraSettings() this.generateVideoFrame(deltaT) + + // Update camera visualization + this._cameraVisualization.update() } private initializeCamera() { console.log(`๐ŸŽฅ [INIT] Starting camera initialization for ${this.device}`) - + // Initialize metadata SimCamera.setConnected(this.device, true) SimCamera.setResolutionWidth(this.device, this._defaultWidth) @@ -180,15 +194,21 @@ export class SimCameraInput extends SimInput { // Initialize video renderer for 3D scene capture this._cameraRenderer = new SimCameraRenderer(this._robot, this._defaultWidth, this._defaultHeight) console.log(`โœ… [INIT] Camera ${this.device} initialized successfully - 3D frames will be generated`) - + + // Show camera visualization in 3D scene + this._cameraVisualization.setVisible(true) + // Force immediate test frame to verify renderer works console.log(`๐Ÿงช [TEST] Attempting immediate test frame capture...`) - this._cameraRenderer.captureFrameAsJPEG().then(blob => { - console.log(`๐Ÿงช [TEST] Initial test frame captured: ${blob.size} bytes - renderer is working!`) - this.sendFrameToRobot(blob) - }).catch(error => { - console.error(`โŒ [TEST] Initial test frame failed:`, error) - }) + this._cameraRenderer + .captureFrameAsJPEG() + .then(blob => { + console.log(`๐Ÿงช [TEST] Initial test frame captured: ${blob.size} bytes - renderer is working!`) + this.sendFrameToRobot(blob) + }) + .catch(error => { + console.error(`โŒ [TEST] Initial test frame failed:`, error) + }) } catch (error) { console.error(`โŒ [INIT] Failed to create camera renderer:`, error) } @@ -230,27 +250,33 @@ export class SimCameraInput extends SimInput { console.warn(`๐Ÿ“น [FRAME] No camera renderer for ${this.device} - skipping frame generation`) return } - + this._lastFrameTime += deltaT * 1000 // Convert to ms - + // Add timing debug logs occasionally - if (Math.random() < 0.01) { // ~1% chance per frame - console.log(`โฑ๏ธ [TIMING] ${this.device}: lastFrameTime=${this._lastFrameTime.toFixed(1)}ms, interval=${this._frameInterval}ms, deltaT=${(deltaT*1000).toFixed(1)}ms`) + if (Math.random() < 0.01) { + // ~1% chance per frame + console.log( + `โฑ๏ธ [TIMING] ${this.device}: lastFrameTime=${this._lastFrameTime.toFixed(1)}ms, interval=${this._frameInterval}ms, deltaT=${(deltaT * 1000).toFixed(1)}ms` + ) } - + // Generate frame at specified FPS if (this._lastFrameTime >= this._frameInterval) { this._lastFrameTime = 0 - + console.log(`๐Ÿ“น [FRAME] Capturing 3D frame for ${this.device}`) - + // Capture frame from 3D scene (robot perspective) - this._cameraRenderer.captureFrameAsJPEG().then(blob => { - console.log(`๐Ÿ“น [FRAME] Successfully captured ${blob.size} bytes, sending to robot`) - this.sendFrameToRobot(blob) - }).catch(error => { - console.error(`โŒ [FRAME] Failed to capture camera frame:`, error) - }) + this._cameraRenderer + .captureFrameAsJPEG() + .then(blob => { + console.log(`๐Ÿ“น [FRAME] Successfully captured ${blob.size} bytes, sending to robot`) + this.sendFrameToRobot(blob) + }) + .catch(error => { + console.error(`โŒ [FRAME] Failed to capture camera frame:`, error) + }) } } @@ -259,9 +285,9 @@ export class SimCameraInput extends SimInput { // Convert blob to base64 for WebSocket transmission const arrayBuffer = await frameBlob.arrayBuffer() const base64Frame = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))) - + console.log(`๐Ÿš€ [SEND] Converting frame: ${arrayBuffer.byteLength} bytes โ†’ ${base64Frame.length} chars`) - + // Send frame through WebSocket protocol const frameMessage = { type: "CAMERA_FRAME", @@ -270,14 +296,13 @@ export class SimCameraInput extends SimInput { frame: base64Frame, width: this._defaultWidth, height: this._defaultHeight, - timestamp: Date.now() - } + timestamp: Date.now(), + }, } - + // Send through the existing WebSocket worker const success = SimGeneric.sendCameraFrame(this.device, frameMessage.data) - console.log(`๐Ÿ“ก [SEND] WebSocket frame sent for ${this.device}: ${success ? 'SUCCESS' : 'FAILED'}`) - + console.log(`๐Ÿ“ก [SEND] WebSocket frame sent for ${this.device}: ${success ? "SUCCESS" : "FAILED"}`) } catch (error) { console.error(`โŒ [SEND] Failed to send camera frame:`, error) } @@ -297,10 +322,14 @@ export class SimCameraInput extends SimInput { SimCamera.setConnected(this._device, true) this._isInitialized = false // set false so reinitialize on next update } - + public disconnect() { SimCamera.setConnected(this._device, false) - this._isInitialized = false + this._isInitialized = false + + // Hide camera visualization + this._cameraVisualization.setVisible(false) + if (this._cameraRenderer) { this._cameraRenderer.dispose() this._cameraRenderer = undefined From e5af40e132839031e4c850f64bc70ddffa0ef83c Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:34:55 -0700 Subject: [PATCH 31/40] Add camera disposing --- fission/src/systems/simulation/wpilib_brain/SimInput.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index ceb369249c..189a8cfc5a 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -335,6 +335,11 @@ export class SimCameraInput extends SimInput { this._cameraRenderer = undefined } } + + public dispose() { + this.disconnect() + this._cameraVisualization.dispose() + } } export class SimDigitalInput extends SimInput { From e573fd8eb7fbbd27bfe21ff5fc77bb1d58df5cc2 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:38:21 -0700 Subject: [PATCH 32/40] Update SimInput --- .../simulation/wpilib_brain/SimInput.ts | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index 189a8cfc5a..44c48a97ef 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -156,9 +156,7 @@ export class SimCameraInput extends SimInput { } public update(deltaT: number) { - // Add occasional logging to confirm update is being called if (Math.random() < 0.01) { - // ~1% chance per frame console.log( `๐Ÿ”„ [UPDATE] SimCameraInput.update() called for ${this.device} (initialized: ${this._isInitialized})` ) @@ -193,13 +191,13 @@ export class SimCameraInput extends SimInput { try { // Initialize video renderer for 3D scene capture this._cameraRenderer = new SimCameraRenderer(this._robot, this._defaultWidth, this._defaultHeight) - console.log(`โœ… [INIT] Camera ${this.device} initialized successfully - 3D frames will be generated`) + console.log(`[INIT] Camera ${this.device} initialized successfully - 3D frames will be generated`) // Show camera visualization in 3D scene this._cameraVisualization.setVisible(true) // Force immediate test frame to verify renderer works - console.log(`๐Ÿงช [TEST] Attempting immediate test frame capture...`) + console.log(`[TEST] Attempting immediate test frame capture...`) this._cameraRenderer .captureFrameAsJPEG() .then(blob => { @@ -251,13 +249,12 @@ export class SimCameraInput extends SimInput { return } - this._lastFrameTime += deltaT * 1000 // Convert to ms + this._lastFrameTime += deltaT * 1000 // Add timing debug logs occasionally if (Math.random() < 0.01) { - // ~1% chance per frame console.log( - `โฑ๏ธ [TIMING] ${this.device}: lastFrameTime=${this._lastFrameTime.toFixed(1)}ms, interval=${this._frameInterval}ms, deltaT=${(deltaT * 1000).toFixed(1)}ms` + `โฑ[TIMING] ${this.device}: lastFrameTime=${this._lastFrameTime.toFixed(1)}ms, interval=${this._frameInterval}ms, deltaT=${(deltaT * 1000).toFixed(1)}ms` ) } @@ -265,24 +262,23 @@ export class SimCameraInput extends SimInput { if (this._lastFrameTime >= this._frameInterval) { this._lastFrameTime = 0 - console.log(`๐Ÿ“น [FRAME] Capturing 3D frame for ${this.device}`) + console.log(`[FRAME] Capturing 3D frame for ${this.device}`) // Capture frame from 3D scene (robot perspective) this._cameraRenderer .captureFrameAsJPEG() .then(blob => { - console.log(`๐Ÿ“น [FRAME] Successfully captured ${blob.size} bytes, sending to robot`) + console.log(`[FRAME] Successfully captured ${blob.size} bytes, sending to robot`) this.sendFrameToRobot(blob) }) .catch(error => { - console.error(`โŒ [FRAME] Failed to capture camera frame:`, error) + console.error(`[FRAME] Failed to capture camera frame:`, error) }) } } private async sendFrameToRobot(frameBlob: Blob) { try { - // Convert blob to base64 for WebSocket transmission const arrayBuffer = await frameBlob.arrayBuffer() const base64Frame = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))) @@ -302,9 +298,9 @@ export class SimCameraInput extends SimInput { // Send through the existing WebSocket worker const success = SimGeneric.sendCameraFrame(this.device, frameMessage.data) - console.log(`๐Ÿ“ก [SEND] WebSocket frame sent for ${this.device}: ${success ? "SUCCESS" : "FAILED"}`) + console.log(`[SEND] WebSocket frame sent for ${this.device}: ${success ? "SUCCESS" : "FAILED"}`) } catch (error) { - console.error(`โŒ [SEND] Failed to send camera frame:`, error) + console.error(`[SEND] Failed to send camera frame:`, error) } } @@ -326,8 +322,6 @@ export class SimCameraInput extends SimInput { public disconnect() { SimCamera.setConnected(this._device, false) this._isInitialized = false - - // Hide camera visualization this._cameraVisualization.setVisible(false) if (this._cameraRenderer) { From d8c5b0dcf0bf89824f11eb6f4de88490edb2dca1 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:39:02 -0700 Subject: [PATCH 33/40] Add SimCameraVisualization --- .../wpilib_brain/SimCameraVisualization.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts diff --git a/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts b/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts new file mode 100644 index 0000000000..4da9b25279 --- /dev/null +++ b/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts @@ -0,0 +1,62 @@ +import * as THREE from "three" +import World from "@/systems/World" +import SceneObject from "@/systems/scene/SceneObject" +import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" + +/** + * Visual representation of the WPILib camera in the 3D scene + * Shows where the camera is mounted and its field of view + */ +export class SimCameraVisualization extends SceneObject { + private _robot: MirabufSceneObject + private _cameraGroup: THREE.Group + private _cameraPosition: THREE.Vector3 + private _isVisible: boolean = false + + constructor(robot: MirabufSceneObject) { + super() + this._robot = robot + + // Same position as SimCameraRenderer + this._cameraPosition = new THREE.Vector3(0, 0.5, 0.2) // Mounted on robot + + this._cameraGroup = new THREE.Group() + } + + public setup(): void { + // Add to scene + World.sceneRenderer.addObject(this._cameraGroup) + console.log("๐Ÿ“น [VISUAL] SimCameraVisualization added to scene") + } + + public setVisible(visible: boolean) { + this._isVisible = visible + this._cameraGroup.visible = visible + + if (visible) { + console.log("๐Ÿ“น [VISUAL] Camera visualization enabled - you should see a camera model on your robot") + } else { + console.log("๐Ÿ“น [VISUAL] Camera visualization disabled") + } + } + + public dispose(): void { + if (this._cameraGroup.parent) { + this._cameraGroup.parent.remove(this._cameraGroup) + } + + // Dispose of geometries and materials + this._cameraGroup.traverse(child => { + if (child instanceof THREE.Mesh) { + child.geometry.dispose() + if (Array.isArray(child.material)) { + child.material.forEach(material => material.dispose()) + } else { + child.material.dispose() + } + } + }) + + console.log("๐Ÿ“น [VISUAL] SimCameraVisualization disposed") + } +} From 42918a43d330e32bff7889cd44c4258e2c3cb559 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:40:07 -0700 Subject: [PATCH 34/40] Add updateCameraTransform --- .../wpilib_brain/SimCameraVisualization.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts b/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts index 4da9b25279..b40783a134 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts @@ -29,6 +29,41 @@ export class SimCameraVisualization extends SceneObject { console.log("๐Ÿ“น [VISUAL] SimCameraVisualization added to scene") } + public update(): void { + if (!this._isVisible) return + + this.updateCameraTransform() + } + + private updateCameraTransform() { + if (!this._robot.mechanism.rootBody) return + + const robotBody = World.physicsSystem.getBody( + this._robot.mechanism.nodeToBody.get(this._robot.mechanism.rootBody)! + ) + + if (!robotBody) return + + const robotPos = robotBody.GetPosition() + const robotRot = robotBody.GetRotation() + + const robotPosition = new THREE.Vector3(robotPos.GetX(), robotPos.GetY(), robotPos.GetZ()) + const robotQuaternion = new THREE.Quaternion(robotRot.GetX(), robotRot.GetY(), robotRot.GetZ(), robotRot.GetW()) + + const worldCameraPos = this._cameraPosition.clone() + worldCameraPos.applyQuaternion(robotQuaternion) + worldCameraPos.add(robotPosition) + + const cameraRotation = new THREE.Quaternion() + cameraRotation.copy(robotQuaternion) + const forwardFix = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI) + const upFix = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), Math.PI) + cameraRotation.multiply(forwardFix).multiply(upFix) + + this._cameraGroup.position.copy(worldCameraPos) + this._cameraGroup.quaternion.copy(cameraRotation) + } + public setVisible(visible: boolean) { this._isVisible = visible this._cameraGroup.visible = visible From 7bca4583007373cda01565477a30729518c991e0 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:41:34 -0700 Subject: [PATCH 35/40] Update SimCameraRenderer --- .../simulation/wpilib_brain/SimCameraRenderer.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts index 7b9bd36987..1d961cb7c6 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts @@ -45,21 +45,26 @@ export class SimCameraRenderer { if (!robotBody) return - // Position camera relative to robot const robotPos = robotBody.GetPosition() const robotRot = robotBody.GetRotation() - // Convert Jolt to Three.js const robotPosition = new THREE.Vector3(robotPos.GetX(), robotPos.GetY(), robotPos.GetZ()) const robotQuaternion = new THREE.Quaternion(robotRot.GetX(), robotRot.GetY(), robotRot.GetZ(), robotRot.GetW()) - // Apply camera offset const worldCameraPos = this._cameraPosition.clone() worldCameraPos.applyQuaternion(robotQuaternion) worldCameraPos.add(robotPosition) + const cameraRotation = new THREE.Quaternion() + cameraRotation.copy(robotQuaternion) + + const forwardFix = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI) + const upFix = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), Math.PI) + + cameraRotation.multiply(forwardFix).multiply(upFix) + this._camera.position.copy(worldCameraPos) - this._camera.quaternion.copy(robotQuaternion) + this._camera.quaternion.copy(cameraRotation) this._camera.updateMatrixWorld() } From 33287c5c53d9d956097f28ae6b29906192c3ddaf Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:42:52 -0700 Subject: [PATCH 36/40] Update JavaSample imports --- .../src/main/java/frc/robot/Robot.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java index f1b05386fc..44f039ac1b 100644 --- a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java +++ b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java @@ -11,15 +11,29 @@ import edu.wpi.first.wpilibj.SPI; import edu.wpi.first.wpilibj.ADXL362; +import edu.wpi.first.wpilibj.AnalogInput; +import edu.wpi.first.wpilibj.AnalogOutput; +import edu.wpi.first.wpilibj.DigitalInput; +import edu.wpi.first.wpilibj.DigitalOutput; import edu.wpi.first.wpilibj.TimedRobot; import edu.wpi.first.wpilibj.motorcontrol.Spark; import edu.wpi.first.wpilibj.smartdashboard.SendableChooser; import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; import edu.wpi.first.wpilibj.XboxController; +import edu.wpi.first.cameraserver.CameraServer; +import edu.wpi.first.cscore.CvSource; +import edu.wpi.first.cscore.VideoMode; +import org.opencv.core.Mat; +import org.opencv.imgproc.Imgproc; +import org.opencv.core.CvType; import com.autodesk.synthesis.revrobotics.CANSparkMax; import com.kauailabs.navx.frc.AHRS; import com.autodesk.synthesis.ctre.TalonFX; +import com.autodesk.synthesis.Camera; +import com.autodesk.synthesis.CameraFrameHandler; +import com.autodesk.synthesis.WebSocketMessageHandler; +import com.autodesk.synthesis.SynthesisWebSocketServer; /** * The VM is configured to automatically run this class, and to call the @@ -43,6 +57,8 @@ public class Robot extends TimedRobot { private ADXL362 m_Accelerometer = new ADXL362(SPI.Port.kMXP, ADXL362.Range.k8G); private AHRS m_Gyro = new AHRS(); + private Camera m_Camera = new Camera("USB Camera 0", 0); + private CvSource m_videoSource; private DigitalInput m_DI = new DigitalInput(0); private DigitalOutput m_DO = new DigitalOutput(1); @@ -188,6 +204,10 @@ public void disabledInit() { m_SparkMax5.set(0.0); m_SparkMax6.set(0.0); m_AO.setVoltage(12.0); + + // Stop WebSocket server when robot is disabled + System.out.println("๐Ÿ›‘ Stopping WebSocket server..."); + SynthesisWebSocketServer.getInstance().stopServer(); } /** This function is called periodically when disabled. */ From 44dfb7fcc4a3bcf861743cc5647c6a0888732d49 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 14:51:29 -0700 Subject: [PATCH 37/40] chore: formatting --- .../src/systems/simulation/wpilib_brain/SimCameraRenderer.ts | 2 +- fission/src/systems/simulation/wpilib_brain/SimInput.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts index 1d961cb7c6..281f2b4f95 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts @@ -60,7 +60,7 @@ export class SimCameraRenderer { const forwardFix = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI) const upFix = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 0, 1), Math.PI) - + cameraRotation.multiply(forwardFix).multiply(upFix) this._camera.position.copy(worldCameraPos) diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index 44c48a97ef..eb7c7de918 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -249,7 +249,7 @@ export class SimCameraInput extends SimInput { return } - this._lastFrameTime += deltaT * 1000 + this._lastFrameTime += deltaT * 1000 // Add timing debug logs occasionally if (Math.random() < 0.01) { From 1a6b41735d23030ac78f6c1e196eb15954ebd840 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Mon, 4 Aug 2025 15:01:50 -0700 Subject: [PATCH 38/40] Cleanup --- .../wpilib_brain/SimCameraVisualization.ts | 19 ++++----- .../simulation/wpilib_brain/SimInput.ts | 40 +++++++++---------- .../simulation/wpilib_brain/WPILibBrain.ts | 8 ++-- .../simulation/wpilib_brain/WPILibWSWorker.ts | 6 +-- .../synthesis/SynthesisWebSocketServer.java | 2 +- .../src/main/java/frc/robot/Robot.java | 2 +- 6 files changed, 35 insertions(+), 42 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts b/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts index b40783a134..47c1a90a49 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts @@ -17,16 +17,14 @@ export class SimCameraVisualization extends SceneObject { super() this._robot = robot - // Same position as SimCameraRenderer - this._cameraPosition = new THREE.Vector3(0, 0.5, 0.2) // Mounted on robot + this._cameraPosition = new THREE.Vector3(0, 0.5, 0.2) this._cameraGroup = new THREE.Group() } public setup(): void { - // Add to scene World.sceneRenderer.addObject(this._cameraGroup) - console.log("๐Ÿ“น [VISUAL] SimCameraVisualization added to scene") + console.log("[VISUAL] SimCameraVisualization added to scene") } public update(): void { @@ -68,11 +66,11 @@ export class SimCameraVisualization extends SceneObject { this._isVisible = visible this._cameraGroup.visible = visible - if (visible) { - console.log("๐Ÿ“น [VISUAL] Camera visualization enabled - you should see a camera model on your robot") - } else { - console.log("๐Ÿ“น [VISUAL] Camera visualization disabled") - } + // if (visible) { + // console.log("๐Ÿ“น [VISUAL] Camera visualization enabled - you should see a camera model on your robot") + // } else { + // console.log("๐Ÿ“น [VISUAL] Camera visualization disabled") + // } } public dispose(): void { @@ -80,7 +78,6 @@ export class SimCameraVisualization extends SceneObject { this._cameraGroup.parent.remove(this._cameraGroup) } - // Dispose of geometries and materials this._cameraGroup.traverse(child => { if (child instanceof THREE.Mesh) { child.geometry.dispose() @@ -92,6 +89,6 @@ export class SimCameraVisualization extends SceneObject { } }) - console.log("๐Ÿ“น [VISUAL] SimCameraVisualization disposed") + console.log("[VISUAL] SimCameraVisualization disposed") } } diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index eb7c7de918..0efff8d41a 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -151,16 +151,16 @@ export class SimCameraInput extends SimInput { this._cameraVisualization.setup() console.log( - `๐ŸŽฌ [CONSTRUCTOR] SimCameraInput created for ${device} (${this._defaultWidth}x${this._defaultHeight} @ ${this._defaultFPS}fps, interval=${this._frameInterval}ms)` + `[CONSTRUCTOR] SimCameraInput created for ${device} (${this._defaultWidth}x${this._defaultHeight} @ ${this._defaultFPS}fps, interval=${this._frameInterval}ms)` ) } public update(deltaT: number) { - if (Math.random() < 0.01) { - console.log( - `๐Ÿ”„ [UPDATE] SimCameraInput.update() called for ${this.device} (initialized: ${this._isInitialized})` - ) - } + // if (Math.random() < 0.01) { + // console.log( + // `๐Ÿ”„ [UPDATE] SimCameraInput.update() called for ${this.device} (initialized: ${this._isInitialized})` + // ) + // } if (!this._isInitialized) { this.initializeCamera() @@ -175,7 +175,7 @@ export class SimCameraInput extends SimInput { } private initializeCamera() { - console.log(`๐ŸŽฅ [INIT] Starting camera initialization for ${this.device}`) + console.log(`[INIT] Starting camera initialization for ${this.device}`) // Initialize metadata SimCamera.setConnected(this.device, true) @@ -186,7 +186,7 @@ export class SimCameraInput extends SimInput { SimCamera.setExposure(this.device, 50) SimCamera.setAutoExposure(this.device, true) - console.log(`๐ŸŽฅ [INIT] Camera metadata set, creating renderer...`) + // console.log(`๐ŸŽฅ [INIT] Camera metadata set, creating renderer...`) try { // Initialize video renderer for 3D scene capture @@ -197,18 +197,18 @@ export class SimCameraInput extends SimInput { this._cameraVisualization.setVisible(true) // Force immediate test frame to verify renderer works - console.log(`[TEST] Attempting immediate test frame capture...`) + // console.log(`[TEST] Attempting immediate test frame capture...`) this._cameraRenderer .captureFrameAsJPEG() .then(blob => { - console.log(`๐Ÿงช [TEST] Initial test frame captured: ${blob.size} bytes - renderer is working!`) + // console.log(`๐Ÿงช [TEST] Initial test frame captured: ${blob.size} bytes - renderer is working!`) this.sendFrameToRobot(blob) }) .catch(error => { - console.error(`โŒ [TEST] Initial test frame failed:`, error) + console.error(`[TEST] Initial test frame failed:`, error) }) } catch (error) { - console.error(`โŒ [INIT] Failed to create camera renderer:`, error) + console.error(`[INIT] Failed to create camera renderer:`, error) } } @@ -245,24 +245,24 @@ export class SimCameraInput extends SimInput { private generateVideoFrame(deltaT: number) { if (!this._cameraRenderer) { - console.warn(`๐Ÿ“น [FRAME] No camera renderer for ${this.device} - skipping frame generation`) + // console.warn(`๐Ÿ“น [FRAME] No camera renderer for ${this.device} - skipping frame generation`) return } this._lastFrameTime += deltaT * 1000 // Add timing debug logs occasionally - if (Math.random() < 0.01) { - console.log( - `โฑ[TIMING] ${this.device}: lastFrameTime=${this._lastFrameTime.toFixed(1)}ms, interval=${this._frameInterval}ms, deltaT=${(deltaT * 1000).toFixed(1)}ms` - ) - } + // if (Math.random() < 0.01) { + // console.log( + // `โฑ[TIMING] ${this.device}: lastFrameTime=${this._lastFrameTime.toFixed(1)}ms, interval=${this._frameInterval}ms, deltaT=${(deltaT * 1000).toFixed(1)}ms` + // ) + // } // Generate frame at specified FPS if (this._lastFrameTime >= this._frameInterval) { this._lastFrameTime = 0 - console.log(`[FRAME] Capturing 3D frame for ${this.device}`) + // console.log(`[FRAME] Capturing 3D frame for ${this.device}`) // Capture frame from 3D scene (robot perspective) this._cameraRenderer @@ -282,7 +282,7 @@ export class SimCameraInput extends SimInput { const arrayBuffer = await frameBlob.arrayBuffer() const base64Frame = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))) - console.log(`๐Ÿš€ [SEND] Converting frame: ${arrayBuffer.byteLength} bytes โ†’ ${base64Frame.length} chars`) + console.log(`[SEND] Converting frame: ${arrayBuffer.byteLength} bytes โ†’ ${base64Frame.length} chars`) // Send frame through WebSocket protocol const frameMessage = { diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index cb194bee77..4b84261126 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -639,7 +639,7 @@ class WPILibBrain extends Brain { constructor(assembly: MirabufSceneObject) { super(assembly.mechanism, "wpilib") - console.log(`๐Ÿง  [WPILIBRAIN] Constructor called for assembly: ${assembly.assemblyName}`) + console.log(`[WPILIBRAIN] Constructor called for assembly: ${assembly.assemblyName}`) this._assembly = assembly @@ -650,7 +650,7 @@ class WPILibBrain extends Brain { return } - console.log(`๐Ÿง  [WPILIBRAIN] SimulationLayer found, setting up devices...`) + console.log(`[WPILIBRAIN] SimulationLayer found, setting up devices...`) this.addSimInput(new SimGyroInput("Test Gyro[1]", this._mechanism)) this.addSimInput(new SimAccelInput("ADXL362[4]", this._mechanism)) @@ -674,9 +674,9 @@ class WPILibBrain extends Brain { } public addSimInput(input: SimInput) { - console.log(`โž• [WPILIBRAIN] Adding SimInput: ${input.constructor.name} for device "${input.device}"`) + console.log(`[WPILIBRAIN] Adding SimInput: ${input.constructor.name} for device "${input.device}"`) this._simInputs.push(input) - console.log(`๐Ÿ“Š [WPILIBRAIN] Total inputs: ${this._simInputs.length}`) + console.log(`[WPILIBRAIN] Total inputs: ${this._simInputs.length}`) } public addSimFlow(flow: SimFlow): boolean { diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts b/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts index b28cf0a0f5..a7ff4d0717 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts @@ -39,7 +39,7 @@ async function tryConnect(port?: number): Promise { socket.addEventListener("message", onMessage) }) - .then(() => console.debug("Mutex released")) + .then(() => { /* console.debug("Mutex released") */ }) } async function tryDisconnect(): Promise { @@ -51,12 +51,9 @@ async function tryDisconnect(): Promise { }) } -// Posts incoming messages function onMessage(event: MessageEvent) { self.postMessage(event.data) } - -// Sends outgoing messages self.addEventListener("message", e => { switch (e.data.command) { case "enable": { @@ -91,7 +88,6 @@ self.addEventListener("message", e => { } case "camera_frame": { if (socketOpen()) { - // Send camera frame data through WebSocket const frameMessage = { type: "CAMERA_FRAME", device: e.data.data.device, diff --git a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java index 3b89be1ea5..85517bc2af 100644 --- a/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java +++ b/simulation/SyntheSimJava/src/main/java/com/autodesk/synthesis/SynthesisWebSocketServer.java @@ -60,7 +60,7 @@ public void startServer() { if (!isRunning) { try { start(); - System.out.println("๐Ÿš€ Synthesis WebSocket server starting on ws://localhost:3300/wpilibws"); + System.out.println("Synthesis WebSocket server starting on ws://localhost:3300/wpilibws"); } catch (Exception e) { System.err.println("Error starting WebSocket server: " + e.getMessage()); } diff --git a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java index 44f039ac1b..7b4d1d8267 100644 --- a/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java +++ b/simulation/samples/JavaSample/src/main/java/frc/robot/Robot.java @@ -206,7 +206,7 @@ public void disabledInit() { m_AO.setVoltage(12.0); // Stop WebSocket server when robot is disabled - System.out.println("๐Ÿ›‘ Stopping WebSocket server..."); + System.out.println("Stopping WebSocket server..."); SynthesisWebSocketServer.getInstance().stopServer(); } From 62a3fa43847b3bb8aff98b5aae4c48bc11dcc050 Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Wed, 6 Aug 2025 10:47:06 -0700 Subject: [PATCH 39/40] chore: biome --- .../simulation/wpilib_brain/SimCameraRenderer.ts | 2 +- .../wpilib_brain/SimCameraVisualization.ts | 4 ++-- .../src/systems/simulation/wpilib_brain/SimInput.ts | 12 ++++++------ .../systems/simulation/wpilib_brain/WPILibBrain.ts | 12 ++++++------ .../simulation/wpilib_brain/WPILibWSWorker.ts | 4 +++- fission/src/ui/panels/WsViewPanel.tsx | 2 +- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts index 281f2b4f95..d6194a7695 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimCameraRenderer.ts @@ -1,6 +1,6 @@ import * as THREE from "three" +import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import World from "@/systems/World" -import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" export class SimCameraRenderer { private _camera: THREE.PerspectiveCamera diff --git a/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts b/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts index 47c1a90a49..f1cf9386d5 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimCameraVisualization.ts @@ -1,7 +1,7 @@ import * as THREE from "three" -import World from "@/systems/World" +import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import SceneObject from "@/systems/scene/SceneObject" -import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import World from "@/systems/World" /** * Visual representation of the WPILib camera in the 3D scene diff --git a/fission/src/systems/simulation/wpilib_brain/SimInput.ts b/fission/src/systems/simulation/wpilib_brain/SimInput.ts index 0efff8d41a..9aa21f4d32 100644 --- a/fission/src/systems/simulation/wpilib_brain/SimInput.ts +++ b/fission/src/systems/simulation/wpilib_brain/SimInput.ts @@ -1,14 +1,14 @@ -import World from "@/systems/World" -import EncoderStimulus from "../stimulus/EncoderStimulus" -import { SimCANEncoder, SimGyro, SimAccel, SimDIO, SimAI, SimCamera, SimGeneric } from "./WPILibBrain" -import Mechanism from "@/systems/physics/Mechanism" -import Jolt from "@azaleacolburn/jolt-physics" +import type Jolt from "@azaleacolburn/jolt-physics" import * as THREE from "three" +import type MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import type Mechanism from "@/systems/physics/Mechanism" +import World from "@/systems/World" import JOLT from "@/util/loading/JoltSyncLoader" import { convertJoltQuatToThreeQuaternion, convertJoltVec3ToThreeVector3 } from "@/util/TypeConversions" +import type EncoderStimulus from "../stimulus/EncoderStimulus" import { SimCameraRenderer } from "./SimCameraRenderer" import { SimCameraVisualization } from "./SimCameraVisualization" -import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import { SimAccel, SimAI, SimCANEncoder, SimCamera, SimDIO, SimGeneric, SimGyro } from "./WPILibBrain" export abstract class SimInput { constructor(protected _device: string) {} diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index 18fc1708cd..5ac20cc8e5 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -1,18 +1,18 @@ import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" +import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import World from "@/systems/World" -import { SimAnalogOutput, SimDigitalOutput, SimOutput } from "./SimOutput" -import { SimAccelInput, SimAnalogInput, SimCameraInput, SimDigitalInput, SimGyroInput, SimInput } from "./SimInput" import Lazy from "@/util/Lazy.ts" import { random } from "@/util/Random" import Brain from "../Brain" +import { type NoraNumber, type NoraNumber2, type NoraNumber3, NoraTypes } from "../Nora" import type { SimulationLayer } from "../SimulationSystem" import SynthesisBrain from "../synthesis_brain/SynthesisBrain" -import { SimFlow, SimReceiver, SimSupplier, validate } from "./SimDataFlow" -import WPILibWSWorker from "./WPILibWSWorker?worker" +import { type SimFlow, type SimReceiver, type SimSupplier, validate } from "./SimDataFlow" +import { SimAccelInput, SimAnalogInput, SimCameraInput, SimDigitalInput, SimGyroInput, type SimInput } from "./SimInput" +import { SimAnalogOutput, SimDigitalOutput, type SimOutput } from "./SimOutput" import { getSimBrain, setConnected } from "./WPILibState" import { SimMapUpdateEvent } from "./WPILibTypes" -import { NoraNumber, NoraNumber2, NoraNumber3, NoraTypes } from "../Nora" -import PreferencesSystem from "@/systems/preferences/PreferencesSystem" +import WPILibWSWorker from "./WPILibWSWorker?worker" const worker: Lazy = new Lazy(() => new WPILibWSWorker()) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts b/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts index a7ff4d0717..2f5162fa30 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibWSWorker.ts @@ -39,7 +39,9 @@ async function tryConnect(port?: number): Promise { socket.addEventListener("message", onMessage) }) - .then(() => { /* console.debug("Mutex released") */ }) + .then(() => { + /* console.debug("Mutex released") */ + }) } async function tryDisconnect(): Promise { diff --git a/fission/src/ui/panels/WsViewPanel.tsx b/fission/src/ui/panels/WsViewPanel.tsx index 97016d8cd4..073cf81c9d 100644 --- a/fission/src/ui/panels/WsViewPanel.tsx +++ b/fission/src/ui/panels/WsViewPanel.tsx @@ -139,7 +139,7 @@ const WSViewPanel: React.FC> = ({ panel }) => { useEffect(() => { configureScreen(panel!, { title: "WS View Panel" }, {}) - }, []) + }, [configureScreen, panel]) return ( From ddb949e94d0228cc8aef91c806621fcc805c738a Mon Sep 17 00:00:00 2001 From: Ryan Zhang Date: Thu, 21 Aug 2025 09:41:01 -0700 Subject: [PATCH 40/40] fix: code connection indicator not showing up --- .../simulation/wpilib_brain/WPILibBrain.ts | 20 +++++++++---------- .../simulation/wpilib_brain/WPILibState.ts | 15 ++++++++++++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts index fa2d98bd25..0e09676282 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibBrain.ts @@ -1,7 +1,6 @@ import MirabufSceneObject from "@/mirabuf/MirabufSceneObject" import PreferencesSystem from "@/systems/preferences/PreferencesSystem" import World from "@/systems/World" -import Lazy from "@/util/Lazy.ts" import { random } from "@/util/Random" import Brain from "../Brain" import { type NoraNumber, type NoraNumber2, type NoraNumber3, NoraTypes } from "../Nora" @@ -10,11 +9,8 @@ import SynthesisBrain from "../synthesis_brain/SynthesisBrain" import { type SimFlow, type SimReceiver, type SimSupplier, validate } from "./SimDataFlow" import { SimAccelInput, SimAnalogInput, SimCameraInput, SimDigitalInput, SimGyroInput, type SimInput } from "./SimInput" import { SimAnalogOutput, SimDigitalOutput, type SimOutput } from "./SimOutput" -import { getSimBrain, setConnected } from "./WPILibState" -import { SimMapUpdateEvent } from "./WPILibTypes" -import WPILibWSWorker from "./WPILibWSWorker?worker" - -const worker: Lazy = new Lazy(() => new WPILibWSWorker()) +import * as WPILibState from "./WPILibState" +import { SimMapUpdateEvent, worker } from "./WPILibTypes" const PWM_SPEED = " { if (eventData.data.status) { switch (eventData.data.status) { case "open": - setConnected(true) + WPILibState.setConnected(true) break case "close": case "error": - setConnected(false) + WPILibState.setConnected(false) break default: return @@ -602,6 +598,8 @@ worker.getValue().addEventListener("message", (eventData: MessageEvent) => { if (!data?.type || !(Object.values(SimType) as string[]).includes(data.type)) return + WPILibState.setConnected(true) + updateSimMap(data.type as SimType, data.device, data.data) }) @@ -728,13 +726,13 @@ class WPILibBrain extends Brain { } public enable(): void { - setSimBrain(this) + WPILibState.setSimBrain(this) // worker.getValue().postMessage({ command: "enable", reconnect: RECONNECT }) } public disable(): void { - if (getSimBrain() == this) { - setSimBrain(undefined) + if (WPILibState.getSimBrain() == this) { + WPILibState.setSimBrain(undefined) } // worker.getValue().postMessage({ command: "disable" }) } diff --git a/fission/src/systems/simulation/wpilib_brain/WPILibState.ts b/fission/src/systems/simulation/wpilib_brain/WPILibState.ts index a8a9e960cd..398faa0e80 100644 --- a/fission/src/systems/simulation/wpilib_brain/WPILibState.ts +++ b/fission/src/systems/simulation/wpilib_brain/WPILibState.ts @@ -42,6 +42,21 @@ export function getIsConnected() { return isConnected } +worker.getValue().addEventListener("message", (eventData: MessageEvent) => { + if (!eventData?.data?.status) return + switch (eventData.data.status) { + case "open": + setConnected(true) + break + case "close": + case "error": + setConnected(false) + break + default: + break + } +}) + export const supplierTypeMap: { [k in SimType]: NoraTypes | undefined } = { [SimType.PWM]: NoraTypes.NUMBER, [SimType.SIM_DEVICE]: undefined,