From fbf89ccbd11a0e082a980eb9af064da0f07859f9 Mon Sep 17 00:00:00 2001 From: Stefan Wagner Date: Fri, 6 Dec 2024 11:26:46 +0100 Subject: [PATCH] feat: get trajectories since last call (#30) --- package-lock.json | 8 +-- package.json | 4 +- src/index.ts | 1 + src/lib/ProgramStateConnection.ts | 90 +++++++++++++++++++------------ src/lib/getLatestTrajectories.ts | 36 +++++++++++++ 5 files changed, 100 insertions(+), 39 deletions(-) create mode 100644 src/lib/getLatestTrajectories.ts diff --git a/package-lock.json b/package-lock.json index 721f71e..80f95b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@auth0/auth0-spa-js": "^2.1.3", "@types/three": "^0.167.1", - "@wandelbots/wandelbots-api-client": "^24.7.0", + "@wandelbots/wandelbots-api-client": "^24.8.1", "axios": "^1.7.2", "mobx": "^6.12.4", "path-to-regexp": "^7.1.0", @@ -1739,9 +1739,9 @@ } }, "node_modules/@wandelbots/wandelbots-api-client": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/@wandelbots/wandelbots-api-client/-/wandelbots-api-client-24.7.0.tgz", - "integrity": "sha512-qNunr6P+UR+rFMslyv1UBvODz8kBiPUAw8YUnebayF3WkxrA47VaCFxSsn9DyZgwKKCgwBU2fP+9hLT9AuDx3w==", + "version": "24.8.1", + "resolved": "https://registry.npmjs.org/@wandelbots/wandelbots-api-client/-/wandelbots-api-client-24.8.1.tgz", + "integrity": "sha512-U+Ae+PvDz4zck3ly1F6y8aaxiRn/kVm3mhP/zgAY1EoYbubx8UbSmFlagUOF3+P5zORjKCpOnGP1fj75dmDfag==", "license": "Apache-2.0", "dependencies": { "axios": "^1.6.8" diff --git a/package.json b/package.json index 5becf50..8e97935 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "dependencies": { "@auth0/auth0-spa-js": "^2.1.3", "@types/three": "^0.167.1", - "@wandelbots/wandelbots-api-client": "^24.7.0", + "@wandelbots/wandelbots-api-client": "^24.8.1", "axios": "^1.7.2", "mobx": "^6.12.4", "path-to-regexp": "^7.1.0", @@ -55,4 +55,4 @@ "three": "^0.167.1", "url-join": "^5.0.0" } -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 0665f6b..822765a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ export * from "./lib/AutoReconnectingWebsocket" export * from "./lib/ConnectedMotionGroup" export * from "./lib/converters" export * from "./lib/errorHandling" +export * from "./lib/getLatestTrajectories" export * from "./lib/JoggerConnection" export * from "./lib/MotionStreamConnection" export * from "./lib/NovaCellAPIClient" diff --git a/src/lib/ProgramStateConnection.ts b/src/lib/ProgramStateConnection.ts index 6527333..a35dca9 100644 --- a/src/lib/ProgramStateConnection.ts +++ b/src/lib/ProgramStateConnection.ts @@ -1,4 +1,5 @@ import { AxiosError } from "axios" +import { makeAutoObservable, runInAction } from "mobx" import type { NovaClient } from "../NovaClient" import { AutoReconnectingWebsocket } from "./AutoReconnectingWebsocket" import { tryParseJson } from "./converters" @@ -25,10 +26,13 @@ export type CurrentProgram = { } type ProgramStateMessage = { - id: string - state: ProgramState - start_time?: number | null - execution_time?: number | null + type: string + runner: { + id: string + state: ProgramState + start_time?: number | null + execution_time?: number | null + } } /** @@ -45,6 +49,8 @@ export class ProgramStateConnection { programStateSocket: AutoReconnectingWebsocket constructor(readonly nova: NovaClient) { + makeAutoObservable(this, {}, { autoBind: true }) + this.programStateSocket = nova.openReconnectingWebsocket(`/programs/state`) this.programStateSocket.addEventListener("message", (ev) => { @@ -54,20 +60,25 @@ export class ProgramStateConnection { console.error("Failed to parse program state message", ev.data) return } - - this.handleProgramStateMessage(msg) + if (msg.type === "update") { + this.handleProgramStateMessage(msg) + } }) } /** Handle a program state update from the backend */ async handleProgramStateMessage(msg: ProgramStateMessage) { + const { runner } = msg + // Ignoring other programs for now // TODO - show if execution state is busy from another source - if (msg.id !== this.currentlyExecutingProgramRunnerId) return + if (runner.id !== this.currentlyExecutingProgramRunnerId) return - if (msg.state === ProgramState.Failed) { + if (runner.state === ProgramState.Failed) { try { - const runnerState = await this.nova.api.program.getProgramRunner(msg.id) + const runnerState = await this.nova.api.program.getProgramRunner( + runner.id, + ) // TODO - wandelengine should send print statements in real time over // websocket as well, rather than at the end @@ -76,20 +87,22 @@ export class ProgramStateConnection { this.log(stdout) } this.logError( - `Program runner ${msg.id} failed with error: ${runnerState.error}\n${runnerState.traceback}`, + `Program runner ${runner.id} failed with error: ${runnerState.error}\n${runnerState.traceback}`, ) } catch (err) { this.logError( - `Failed to retrieve results for program ${msg.id}: ${err}`, + `Failed to retrieve results for program ${runner.id}: ${err}`, ) } this.currentProgram.state = ProgramState.Failed this.gotoIdleState() - } else if (msg.state === ProgramState.Stopped) { + } else if (runner.state === ProgramState.Stopped) { try { - const runnerState = await this.nova.api.program.getProgramRunner(msg.id) + const runnerState = await this.nova.api.program.getProgramRunner( + runner.id, + ) const stdout = (runnerState as any).stdout if (stdout) { @@ -97,41 +110,43 @@ export class ProgramStateConnection { } this.currentProgram.state = ProgramState.Stopped - this.log(`Program runner ${msg.id} stopped`) + this.log(`Program runner ${runner.id} stopped`) } catch (err) { this.logError( - `Failed to retrieve results for program ${msg.id}: ${err}`, + `Failed to retrieve results for program ${runner.id}: ${err}`, ) } this.gotoIdleState() - } else if (msg.state === ProgramState.Completed) { + } else if (runner.state === ProgramState.Completed) { try { - const runnerState = await this.nova.api.program.getProgramRunner(msg.id) + const runnerState = await this.nova.api.program.getProgramRunner( + runner.id, + ) const stdout = (runnerState as any).stdout if (stdout) { this.log(stdout) } this.log( - `Program runner ${msg.id} finished successfully in ${msg.execution_time?.toFixed(2)} seconds`, + `Program runner ${runner.id} finished successfully in ${runner.execution_time?.toFixed(2)} seconds`, ) this.currentProgram.state = ProgramState.Completed } catch (err) { this.logError( - `Failed to retrieve results for program ${msg.id}: ${err}`, + `Failed to retrieve results for program ${runner.id}: ${err}`, ) } this.gotoIdleState() - } else if (msg.state === ProgramState.Running) { + } else if (runner.state === ProgramState.Running) { this.currentProgram.state = ProgramState.Running - this.log(`Program runner ${msg.id} now running`) - } else if (msg.state !== ProgramState.NotStarted) { - console.error(msg) + this.log(`Program runner ${runner.id} now running`) + } else if (runner.state !== ProgramState.NotStarted) { + console.error(runner) this.logError( - `Program runner ${msg.id} entered unexpected state: ${msg.state}`, + `Program runner ${runner.id} entered unexpected state: ${runner.state}`, ) this.currentProgram.state = ProgramState.NotStarted this.gotoIdleState() @@ -140,7 +155,9 @@ export class ProgramStateConnection { /** Call when a program is no longer executing */ gotoIdleState() { - this.executionState = "idle" + runInAction(() => { + this.executionState = "idle" + }) this.currentlyExecutingProgramRunnerId = null } @@ -156,8 +173,9 @@ export class ProgramStateConnection { const { currentProgram: openProgram } = this if (!openProgram) return - - this.executionState = "starting" + runInAction(() => { + this.executionState = "starting" + }) // Jogging can cause program execution to fail for some time after // So we need to explicitly stop jogging before running a program @@ -189,8 +207,9 @@ export class ProgramStateConnection { ) this.log(`Created program runner ${programRunnerRef.id}"`) - - this.executionState = "executing" + runInAction(() => { + this.executionState = "executing" + }) this.currentlyExecutingProgramRunnerId = programRunnerRef.id } catch (error) { if (error instanceof AxiosError && error.response && error.request) { @@ -200,14 +219,17 @@ export class ProgramStateConnection { } else { this.logError(JSON.stringify(error)) } - this.executionState = "idle" + runInAction(() => { + this.executionState = "idle" + }) } } async stopProgram() { if (!this.currentlyExecutingProgramRunnerId) return - - this.executionState = "stopping" + runInAction(() => { + this.executionState = "stopping" + }) try { await this.nova.api.program.stopProgramRunner( @@ -215,7 +237,9 @@ export class ProgramStateConnection { ) } catch (err) { // Reactivate the stop button so user can try again - this.executionState = "executing" + runInAction(() => { + this.executionState = "executing" + }) throw err } } diff --git a/src/lib/getLatestTrajectories.ts b/src/lib/getLatestTrajectories.ts new file mode 100644 index 0000000..c65f767 --- /dev/null +++ b/src/lib/getLatestTrajectories.ts @@ -0,0 +1,36 @@ +import type { GetTrajectoryResponse } from "@wandelbots/wandelbots-api-client" +import type { NovaCellAPIClient } from "./NovaCellAPIClient" + +let lastMotionIds: Set = new Set() + +export async function getLatestTrajectories( + apiClient: NovaCellAPIClient, + sampleTime: number = 50, + responsesCoordinateSystem?: string, +): Promise { + const newTrajectories: GetTrajectoryResponse[] = [] + + try { + const motions = await apiClient.motion.listMotions() + const currentMotionIds = new Set(motions.motions) + + const newMotionIds = Array.from(currentMotionIds).filter( + (id) => !lastMotionIds.has(id), + ) + + for (const motionId of newMotionIds) { + const trajectory = await apiClient.motion.getMotionTrajectory( + motionId, + sampleTime, + responsesCoordinateSystem, + ) + newTrajectories.push(trajectory) + } + + lastMotionIds = currentMotionIds + } catch (error) { + console.error("Failed to get latest trajectories:", error) + } + + return newTrajectories +}