Skip to content

Commit

Permalink
feat: get trajectories since last call (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
bompo authored Dec 6, 2024
1 parent eb705ea commit fbf89cc
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 39 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@
"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",
"reconnecting-websocket": "^4.4.0",
"three": "^0.167.1",
"url-join": "^5.0.0"
}
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
90 changes: 57 additions & 33 deletions src/lib/ProgramStateConnection.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
}
}

/**
Expand All @@ -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) => {
Expand All @@ -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
Expand All @@ -76,62 +87,66 @@ 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) {
this.log(stdout)
}

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()
Expand All @@ -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
}

Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -200,22 +219,27 @@ 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(
this.currentlyExecutingProgramRunnerId,
)
} catch (err) {
// Reactivate the stop button so user can try again
this.executionState = "executing"
runInAction(() => {
this.executionState = "executing"
})
throw err
}
}
Expand Down
36 changes: 36 additions & 0 deletions src/lib/getLatestTrajectories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { GetTrajectoryResponse } from "@wandelbots/wandelbots-api-client"
import type { NovaCellAPIClient } from "./NovaCellAPIClient"

let lastMotionIds: Set<string> = new Set()

export async function getLatestTrajectories(
apiClient: NovaCellAPIClient,
sampleTime: number = 50,
responsesCoordinateSystem?: string,
): Promise<GetTrajectoryResponse[]> {
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
}

0 comments on commit fbf89cc

Please sign in to comment.