@wandelbots/nova-js 2.1.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -1
- package/dist/{chunk-6WCKJOFL.js → chunk-4FP7NTKS.js} +51 -1
- package/dist/chunk-4FP7NTKS.js.map +1 -0
- package/dist/{chunk-V3NJLR6P.js → chunk-DOFCSS2H.js} +1 -51
- package/dist/chunk-DOFCSS2H.js.map +1 -0
- package/dist/index.js +8 -8
- package/dist/lib/v1/index.js +6 -6
- package/dist/lib/v2/NovaCellAPIClient.d.ts +9 -11
- package/dist/lib/v2/NovaCellAPIClient.d.ts.map +1 -1
- package/dist/lib/v2/NovaClient.d.ts +0 -8
- package/dist/lib/v2/NovaClient.d.ts.map +1 -1
- package/dist/lib/v2/index.cjs +28 -771
- package/dist/lib/v2/index.cjs.map +1 -1
- package/dist/lib/v2/index.d.ts +0 -4
- package/dist/lib/v2/index.d.ts.map +1 -1
- package/dist/lib/v2/index.js +32 -764
- package/dist/lib/v2/index.js.map +1 -1
- package/dist/lib/v2/mock/MockNovaInstance.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/lib/v2/NovaCellAPIClient.ts +22 -22
- package/src/lib/v2/NovaClient.ts +0 -28
- package/src/lib/v2/index.ts +0 -4
- package/src/lib/v2/mock/MockNovaInstance.ts +9 -32
- package/dist/chunk-6WCKJOFL.js.map +0 -1
- package/dist/chunk-V3NJLR6P.js.map +0 -1
- package/dist/lib/v2/ConnectedMotionGroup.d.ts +0 -41
- package/dist/lib/v2/ConnectedMotionGroup.d.ts.map +0 -1
- package/dist/lib/v2/JoggerConnection.d.ts +0 -53
- package/dist/lib/v2/JoggerConnection.d.ts.map +0 -1
- package/dist/lib/v2/MotionStreamConnection.d.ts +0 -25
- package/dist/lib/v2/MotionStreamConnection.d.ts.map +0 -1
- package/dist/lib/v2/ProgramStateConnection.d.ts +0 -53
- package/dist/lib/v2/ProgramStateConnection.d.ts.map +0 -1
- package/dist/lib/v2/motionStateUpdate.d.ts +0 -4
- package/dist/lib/v2/motionStateUpdate.d.ts.map +0 -1
- package/src/lib/v2/ConnectedMotionGroup.ts +0 -216
- package/src/lib/v2/JoggerConnection.ts +0 -207
- package/src/lib/v2/MotionStreamConnection.ts +0 -201
- package/src/lib/v2/ProgramStateConnection.ts +0 -249
- package/src/lib/v2/motionStateUpdate.ts +0 -55
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Controller,
|
|
3
|
-
MotionGroupPhysical,
|
|
4
|
-
MotionGroupSpecification,
|
|
5
|
-
MotionGroupState,
|
|
6
|
-
RobotTcp,
|
|
7
|
-
SafetySetup,
|
|
8
|
-
} from "@wandelbots/nova-api/v2"
|
|
9
|
-
import { AxiosError } from "axios"
|
|
10
|
-
import { makeAutoObservable, runInAction } from "mobx"
|
|
11
|
-
import type { AutoReconnectingWebsocket } from "../AutoReconnectingWebsocket"
|
|
12
|
-
import { tryParseJson } from "../converters"
|
|
13
|
-
import { jointValuesEqual, tcpPoseEqual } from "./motionStateUpdate"
|
|
14
|
-
import type { NovaClient } from "./NovaClient"
|
|
15
|
-
|
|
16
|
-
const MOTION_DELTA_THRESHOLD = 0.0001
|
|
17
|
-
|
|
18
|
-
export type MotionGroupOption = {
|
|
19
|
-
selectionId: string
|
|
20
|
-
} & MotionGroupPhysical
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Store representing the current state of a connected motion group.
|
|
24
|
-
*/
|
|
25
|
-
export class ConnectedMotionGroup {
|
|
26
|
-
static async connect(
|
|
27
|
-
nova: NovaClient,
|
|
28
|
-
motionGroupId: string,
|
|
29
|
-
controllers: Controller[],
|
|
30
|
-
) {
|
|
31
|
-
const [_motionGroupIndex, controllerId] = motionGroupId.split("@") as [
|
|
32
|
-
string,
|
|
33
|
-
string,
|
|
34
|
-
]
|
|
35
|
-
const controller = controllers.find((c) => c.controller === controllerId)
|
|
36
|
-
const motionGroup = controller?.motion_groups.find(
|
|
37
|
-
(mg) => mg.motion_group === motionGroupId,
|
|
38
|
-
)
|
|
39
|
-
if (!controller || !motionGroup) {
|
|
40
|
-
throw new Error(
|
|
41
|
-
`Controller ${controllerId} or motion group ${motionGroupId} not found`,
|
|
42
|
-
)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const motionStateSocket = nova.openReconnectingWebsocket(
|
|
46
|
-
`/motion-groups/${motionGroupId}/state-stream`,
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
// Wait for the first message to get the initial state
|
|
50
|
-
const firstMessage = await motionStateSocket.firstMessage()
|
|
51
|
-
const initialMotionState = tryParseJson(firstMessage.data)
|
|
52
|
-
?.result as MotionGroupState
|
|
53
|
-
|
|
54
|
-
if (!initialMotionState) {
|
|
55
|
-
throw new Error(
|
|
56
|
-
`Unable to parse initial motion state message ${firstMessage.data}`,
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
console.log(
|
|
61
|
-
`Connected motion state websocket to motion group ${motionGroup.motion_group}. Initial state:\n `,
|
|
62
|
-
initialMotionState,
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
// This is used to determine if the robot is virtual or physical
|
|
66
|
-
let isVirtual = false
|
|
67
|
-
try {
|
|
68
|
-
const opMode =
|
|
69
|
-
await nova.api.virtualRobotMode.getOperationMode(controllerId)
|
|
70
|
-
|
|
71
|
-
if (opMode) isVirtual = true
|
|
72
|
-
} catch (err) {
|
|
73
|
-
if (err instanceof AxiosError) {
|
|
74
|
-
console.log(
|
|
75
|
-
`Received ${err.status} from getOperationMode, concluding that ${controllerId} is physical`,
|
|
76
|
-
)
|
|
77
|
-
} else {
|
|
78
|
-
throw err
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Find out what TCPs this motion group has (we need it for jogging)
|
|
83
|
-
const { tcps } = await nova.api.motionGroupInfos.listTcps(motionGroupId)
|
|
84
|
-
|
|
85
|
-
const motionGroupSpecification =
|
|
86
|
-
await nova.api.motionGroupInfos.getMotionGroupSpecification(motionGroupId)
|
|
87
|
-
|
|
88
|
-
const safetySetup =
|
|
89
|
-
await nova.api.motionGroupInfos.getSafetySetup(motionGroupId)
|
|
90
|
-
|
|
91
|
-
return new ConnectedMotionGroup(
|
|
92
|
-
nova,
|
|
93
|
-
controller,
|
|
94
|
-
motionGroup,
|
|
95
|
-
initialMotionState,
|
|
96
|
-
motionStateSocket,
|
|
97
|
-
isVirtual,
|
|
98
|
-
tcps!,
|
|
99
|
-
motionGroupSpecification,
|
|
100
|
-
safetySetup,
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
connectedJoggingCartesianSocket: WebSocket | null = null
|
|
105
|
-
connectedJoggingJointsSocket: WebSocket | null = null
|
|
106
|
-
planData: any | null // tmp
|
|
107
|
-
joggingVelocity: number = 10
|
|
108
|
-
|
|
109
|
-
// Not mobx-observable as this changes very fast; should be observed
|
|
110
|
-
// using animation frames
|
|
111
|
-
rapidlyChangingMotionState: MotionGroupState
|
|
112
|
-
|
|
113
|
-
constructor(
|
|
114
|
-
readonly nova: NovaClient,
|
|
115
|
-
readonly controller: Controller,
|
|
116
|
-
readonly motionGroup: MotionGroupPhysical,
|
|
117
|
-
readonly initialMotionState: MotionGroupState,
|
|
118
|
-
readonly motionStateSocket: AutoReconnectingWebsocket,
|
|
119
|
-
readonly isVirtual: boolean,
|
|
120
|
-
readonly tcps: RobotTcp[],
|
|
121
|
-
readonly motionGroupSpecification: MotionGroupSpecification,
|
|
122
|
-
readonly safetySetup: SafetySetup,
|
|
123
|
-
) {
|
|
124
|
-
this.rapidlyChangingMotionState = initialMotionState
|
|
125
|
-
|
|
126
|
-
motionStateSocket.addEventListener("message", (event) => {
|
|
127
|
-
const motionStateResponse = tryParseJson(event.data)?.result as
|
|
128
|
-
| MotionGroupState
|
|
129
|
-
| undefined
|
|
130
|
-
|
|
131
|
-
if (!motionStateResponse) {
|
|
132
|
-
throw new Error(
|
|
133
|
-
`Failed to get motion state for ${this.motionGroupId}: ${event.data}`,
|
|
134
|
-
)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// handle motionState message
|
|
138
|
-
if (
|
|
139
|
-
!jointValuesEqual(
|
|
140
|
-
this.rapidlyChangingMotionState.joint_position.joints,
|
|
141
|
-
motionStateResponse.joint_position.joints,
|
|
142
|
-
MOTION_DELTA_THRESHOLD,
|
|
143
|
-
)
|
|
144
|
-
) {
|
|
145
|
-
runInAction(() => {
|
|
146
|
-
this.rapidlyChangingMotionState = motionStateResponse
|
|
147
|
-
})
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// handle tcpPose message
|
|
151
|
-
if (
|
|
152
|
-
!tcpPoseEqual(
|
|
153
|
-
this.rapidlyChangingMotionState.tcp_pose,
|
|
154
|
-
motionStateResponse.tcp_pose,
|
|
155
|
-
MOTION_DELTA_THRESHOLD,
|
|
156
|
-
)
|
|
157
|
-
) {
|
|
158
|
-
runInAction(() => {
|
|
159
|
-
this.rapidlyChangingMotionState.tcp_pose =
|
|
160
|
-
motionStateResponse.tcp_pose
|
|
161
|
-
})
|
|
162
|
-
}
|
|
163
|
-
})
|
|
164
|
-
makeAutoObservable(this)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
get motionGroupId() {
|
|
168
|
-
return this.motionGroup.motion_group
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
get controllerId() {
|
|
172
|
-
return this.controller.controller
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
get modelFromController() {
|
|
176
|
-
return this.motionGroup.model_from_controller
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
get wandelscriptIdentifier() {
|
|
180
|
-
const num = this.motionGroupId.split("@")[0]
|
|
181
|
-
return `${this.controllerId.replaceAll("-", "_")}_${num}`
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/** Jogging velocity in radians for rotation and joint movement */
|
|
185
|
-
get joggingVelocityRads() {
|
|
186
|
-
return (this.joggingVelocity * Math.PI) / 180
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
get joints() {
|
|
190
|
-
return this.initialMotionState.joint_position.joints.map((_, i) => {
|
|
191
|
-
return {
|
|
192
|
-
index: i,
|
|
193
|
-
}
|
|
194
|
-
})
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
get dhParameters() {
|
|
198
|
-
return this.motionGroupSpecification.dh_parameters
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
get safetyZones() {
|
|
202
|
-
return this.safetySetup.safety_zones
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
dispose() {
|
|
206
|
-
this.motionStateSocket.close()
|
|
207
|
-
if (this.connectedJoggingCartesianSocket)
|
|
208
|
-
this.connectedJoggingCartesianSocket.close()
|
|
209
|
-
if (this.connectedJoggingJointsSocket)
|
|
210
|
-
this.connectedJoggingJointsSocket.close()
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
setJoggingVelocity(velocity: number) {
|
|
214
|
-
this.joggingVelocity = velocity
|
|
215
|
-
}
|
|
216
|
-
}
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
InitializeJoggingRequest,
|
|
3
|
-
JoggingResponse,
|
|
4
|
-
JointVelocityRequest,
|
|
5
|
-
MotionGroupState,
|
|
6
|
-
TcpVelocityRequest,
|
|
7
|
-
} from "@wandelbots/nova-api/v2"
|
|
8
|
-
import type { AutoReconnectingWebsocket } from "../AutoReconnectingWebsocket"
|
|
9
|
-
import { tryParseJson } from "../converters"
|
|
10
|
-
import type { NovaClient } from "./NovaClient"
|
|
11
|
-
import { axisToIndex } from "./vectorUtils"
|
|
12
|
-
|
|
13
|
-
export type JoggerConnectionOpts = {
|
|
14
|
-
/**
|
|
15
|
-
* When an error message is received from the jogging websocket, it
|
|
16
|
-
* will be passed here. If this handler is not provided, the error will
|
|
17
|
-
* instead be thrown as an unhandled error.
|
|
18
|
-
*/
|
|
19
|
-
onError?: (err: unknown) => void
|
|
20
|
-
} & Omit<InitializeJoggingRequest, "message_type">
|
|
21
|
-
|
|
22
|
-
export class JoggerConnection {
|
|
23
|
-
// Currently a separate websocket is needed for each mode, pester API people
|
|
24
|
-
// to merge these for simplicity
|
|
25
|
-
joggingWebsocket: AutoReconnectingWebsocket | null = null
|
|
26
|
-
lastVelocityRequest: JointVelocityRequest | TcpVelocityRequest | null = null
|
|
27
|
-
lastResponse: JoggingResponse | null = null
|
|
28
|
-
|
|
29
|
-
static async open(
|
|
30
|
-
nova: NovaClient,
|
|
31
|
-
cell: string,
|
|
32
|
-
motionGroupId: string,
|
|
33
|
-
opts: JoggerConnectionOpts,
|
|
34
|
-
) {
|
|
35
|
-
const motionGroupState =
|
|
36
|
-
await nova.api.motionGroupInfos.getCurrentMotionGroupState(motionGroupId)
|
|
37
|
-
|
|
38
|
-
return new JoggerConnection(
|
|
39
|
-
nova,
|
|
40
|
-
cell,
|
|
41
|
-
motionGroupId,
|
|
42
|
-
motionGroupState,
|
|
43
|
-
opts,
|
|
44
|
-
)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
constructor(
|
|
48
|
-
readonly nova: NovaClient,
|
|
49
|
-
readonly cell: string,
|
|
50
|
-
readonly motionGroupId: string,
|
|
51
|
-
readonly motionGroupState: MotionGroupState,
|
|
52
|
-
readonly opts: JoggerConnectionOpts,
|
|
53
|
-
) {
|
|
54
|
-
this.joggingWebsocket = nova.openReconnectingWebsocket(
|
|
55
|
-
`/cells/${cell}/execution/jogging`,
|
|
56
|
-
)
|
|
57
|
-
this.joggingWebsocket.addEventListener("message", (ev: MessageEvent) => {
|
|
58
|
-
const data = tryParseJson(ev.data) as JoggingResponse
|
|
59
|
-
if (data && "error" in data) {
|
|
60
|
-
if (this.opts.onError) {
|
|
61
|
-
this.opts.onError(ev.data)
|
|
62
|
-
} else {
|
|
63
|
-
throw new Error(ev.data)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
this.joggingWebsocket.addEventListener("message", (ev: MessageEvent) => {
|
|
69
|
-
const data = tryParseJson(ev.data)
|
|
70
|
-
if (data && "error" in data) {
|
|
71
|
-
if (this.opts.onError) {
|
|
72
|
-
this.opts.onError(ev.data)
|
|
73
|
-
} else {
|
|
74
|
-
throw new Error(ev.data)
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
get jointCount() {
|
|
81
|
-
return this.motionGroupState.joint_current?.joints.length
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
get activeWebsocket() {
|
|
85
|
-
return this.joggingWebsocket
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async stop() {
|
|
89
|
-
// Why not call the stopJogging API endpoint?
|
|
90
|
-
// Because this results in the websocket closing and we
|
|
91
|
-
// would like to keep it open for now.
|
|
92
|
-
if (!this.joggingWebsocket) {
|
|
93
|
-
return
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (this.lastVelocityRequest?.message_type === "JointVelocityRequest") {
|
|
97
|
-
this.joggingWebsocket.sendJson({
|
|
98
|
-
message_type: "JointVelocityRequest",
|
|
99
|
-
velocity: {
|
|
100
|
-
joints: Array.from(new Array(this.jointCount).keys()).map(() => 0),
|
|
101
|
-
},
|
|
102
|
-
} as JointVelocityRequest)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (this.lastVelocityRequest?.message_type === "TcpVelocityRequest") {
|
|
106
|
-
this.joggingWebsocket.sendJson({
|
|
107
|
-
message_type: "TcpVelocityRequest",
|
|
108
|
-
rotation: [0, 0, 0],
|
|
109
|
-
translation: [0, 0, 0],
|
|
110
|
-
} as TcpVelocityRequest)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
dispose() {
|
|
115
|
-
if (this.joggingWebsocket) {
|
|
116
|
-
this.joggingWebsocket.dispose()
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Start rotation of a single robot joint at the specified velocity
|
|
122
|
-
*/
|
|
123
|
-
async startJointRotation({
|
|
124
|
-
joint,
|
|
125
|
-
velocityRadsPerSec,
|
|
126
|
-
}: {
|
|
127
|
-
/** Index of the joint to rotate */
|
|
128
|
-
joint: number
|
|
129
|
-
/** Speed of the rotation in radians per second */
|
|
130
|
-
velocityRadsPerSec: number
|
|
131
|
-
}) {
|
|
132
|
-
if (!this.joggingWebsocket) {
|
|
133
|
-
throw new Error(
|
|
134
|
-
"Joint jogging websocket not connected. Wait for reconnect or open new jogging connection",
|
|
135
|
-
)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const jointVelocities = new Array(this.jointCount).fill(0)
|
|
139
|
-
jointVelocities[joint] = velocityRadsPerSec
|
|
140
|
-
|
|
141
|
-
this.joggingWebsocket.sendJson({
|
|
142
|
-
message_type: "JointVelocityRequest",
|
|
143
|
-
velocity: {
|
|
144
|
-
joints: jointVelocities,
|
|
145
|
-
},
|
|
146
|
-
} as JointVelocityRequest)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Start the TCP moving along a specified axis at a given velocity
|
|
151
|
-
*/
|
|
152
|
-
async startTCPTranslation({
|
|
153
|
-
axis,
|
|
154
|
-
velocityMmPerSec,
|
|
155
|
-
useToolCoordinateSystem,
|
|
156
|
-
}: {
|
|
157
|
-
axis: "x" | "y" | "z"
|
|
158
|
-
velocityMmPerSec: number
|
|
159
|
-
useToolCoordinateSystem: boolean
|
|
160
|
-
}) {
|
|
161
|
-
if (!this.joggingWebsocket) {
|
|
162
|
-
throw new Error(
|
|
163
|
-
"Cartesian jogging websocket not connected. Wait for reconnect or open new jogging connection",
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const joggingVector = [0, 0, 0]
|
|
168
|
-
joggingVector[axisToIndex(axis)] = velocityMmPerSec
|
|
169
|
-
|
|
170
|
-
this.joggingWebsocket.sendJson({
|
|
171
|
-
message_type: "TcpVelocityRequest",
|
|
172
|
-
|
|
173
|
-
translation: joggingVector,
|
|
174
|
-
rotation: [0, 0, 0],
|
|
175
|
-
use_tool_coordinate_system: useToolCoordinateSystem,
|
|
176
|
-
} as TcpVelocityRequest)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Start the TCP rotating around a specified axis at a given velocity
|
|
181
|
-
*/
|
|
182
|
-
async startTCPRotation({
|
|
183
|
-
axis,
|
|
184
|
-
velocityRadsPerSec,
|
|
185
|
-
useToolCoordinateSystem,
|
|
186
|
-
}: {
|
|
187
|
-
axis: "x" | "y" | "z"
|
|
188
|
-
velocityRadsPerSec: number
|
|
189
|
-
useToolCoordinateSystem: boolean
|
|
190
|
-
}) {
|
|
191
|
-
if (!this.joggingWebsocket) {
|
|
192
|
-
throw new Error(
|
|
193
|
-
"Cartesian jogging websocket not connected. Wait for reconnect or open new jogging connection",
|
|
194
|
-
)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const joggingVector = [0, 0, 0]
|
|
198
|
-
joggingVector[axisToIndex(axis)] = velocityRadsPerSec
|
|
199
|
-
|
|
200
|
-
this.joggingWebsocket.sendJson({
|
|
201
|
-
message_type: "TcpVelocityRequest",
|
|
202
|
-
rotation: joggingVector,
|
|
203
|
-
translation: [0, 0, 0],
|
|
204
|
-
use_tool_coordinate_system: useToolCoordinateSystem,
|
|
205
|
-
} as TcpVelocityRequest)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
Controller,
|
|
3
|
-
MotionGroupPhysical,
|
|
4
|
-
MotionGroupState,
|
|
5
|
-
} from "@wandelbots/nova-api/v2"
|
|
6
|
-
import { makeAutoObservable, runInAction } from "mobx"
|
|
7
|
-
import * as THREE from "three"
|
|
8
|
-
import type { AutoReconnectingWebsocket } from "../AutoReconnectingWebsocket"
|
|
9
|
-
import { tryParseJson } from "../converters"
|
|
10
|
-
import { jointValuesEqual, tcpPoseEqual } from "./motionStateUpdate"
|
|
11
|
-
import type { NovaClient } from "./NovaClient"
|
|
12
|
-
import { Vector3d, vector3ToArray } from "./vectorUtils"
|
|
13
|
-
|
|
14
|
-
const MOTION_DELTA_THRESHOLD = 0.0001
|
|
15
|
-
|
|
16
|
-
function unwrapRotationVector(
|
|
17
|
-
newRotationVectorApi: Vector3d,
|
|
18
|
-
currentRotationVectorApi: Vector3d,
|
|
19
|
-
): Vector3d {
|
|
20
|
-
const currentRotationVector = new THREE.Vector3(
|
|
21
|
-
currentRotationVectorApi[0],
|
|
22
|
-
currentRotationVectorApi[1],
|
|
23
|
-
currentRotationVectorApi[2],
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
const newRotationVector = new THREE.Vector3(
|
|
27
|
-
newRotationVectorApi[0],
|
|
28
|
-
newRotationVectorApi[1],
|
|
29
|
-
newRotationVectorApi[2],
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
const currentAngle = currentRotationVector.length()
|
|
33
|
-
const currentAxis = currentRotationVector.normalize()
|
|
34
|
-
|
|
35
|
-
let newAngle = newRotationVector.length()
|
|
36
|
-
let newAxis = newRotationVector.normalize()
|
|
37
|
-
|
|
38
|
-
// Align rotation axes
|
|
39
|
-
if (newAxis.dot(currentAxis) < 0) {
|
|
40
|
-
newAngle = -newAngle
|
|
41
|
-
newAxis = newAxis.multiplyScalar(-1.0)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Shift rotation angle close to previous one to extend domain of rotation angles beyond [0, pi]
|
|
45
|
-
// - this simplifies interpolation and prevents abruptly changing signs of the rotation angles
|
|
46
|
-
let angleDifference = newAngle - currentAngle
|
|
47
|
-
angleDifference -=
|
|
48
|
-
2.0 * Math.PI * Math.floor((angleDifference + Math.PI) / (2.0 * Math.PI))
|
|
49
|
-
|
|
50
|
-
newAngle = currentAngle + angleDifference
|
|
51
|
-
|
|
52
|
-
return vector3ToArray(newAxis.multiplyScalar(newAngle))
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Store representing the current state of a connected motion group.
|
|
57
|
-
*/
|
|
58
|
-
export class MotionStreamConnection {
|
|
59
|
-
static async open(nova: NovaClient, motionGroupId: string) {
|
|
60
|
-
const { controllers: controllers } =
|
|
61
|
-
await nova.api.controller.listControllers()
|
|
62
|
-
|
|
63
|
-
const [_motionGroupIndex, controllerId] = motionGroupId.split("@") as [
|
|
64
|
-
string,
|
|
65
|
-
string,
|
|
66
|
-
]
|
|
67
|
-
const controller = controllers.find((c) => c.controller === controllerId)
|
|
68
|
-
const motionGroup = controller?.motion_groups.find(
|
|
69
|
-
(mg) => mg.motion_group === motionGroupId,
|
|
70
|
-
)
|
|
71
|
-
if (!controller || !motionGroup) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`Controller ${controllerId} or motion group ${motionGroupId} not found`,
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const motionStateSocket = nova.openReconnectingWebsocket(
|
|
78
|
-
`/motion-groups/${motionGroupId}/state-stream`,
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
// Wait for the first message to get the initial state
|
|
82
|
-
const firstMessage = await motionStateSocket.firstMessage()
|
|
83
|
-
console.log("got first message", firstMessage)
|
|
84
|
-
const initialMotionState = tryParseJson(firstMessage.data)
|
|
85
|
-
?.result as MotionGroupState
|
|
86
|
-
|
|
87
|
-
if (!initialMotionState) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
`Unable to parse initial motion state message ${firstMessage.data}`,
|
|
90
|
-
)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
console.log(
|
|
94
|
-
`Connected motion state websocket to motion group ${motionGroup.motion_group}. Initial state:\n `,
|
|
95
|
-
initialMotionState,
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
return new MotionStreamConnection(
|
|
99
|
-
nova,
|
|
100
|
-
controller,
|
|
101
|
-
motionGroup,
|
|
102
|
-
initialMotionState,
|
|
103
|
-
motionStateSocket,
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Not mobx-observable as this changes very fast; should be observed
|
|
108
|
-
// using animation frames
|
|
109
|
-
rapidlyChangingMotionState: MotionGroupState
|
|
110
|
-
|
|
111
|
-
constructor(
|
|
112
|
-
readonly nova: NovaClient,
|
|
113
|
-
readonly controller: Controller,
|
|
114
|
-
readonly motionGroup: MotionGroupPhysical,
|
|
115
|
-
readonly initialMotionState: MotionGroupState,
|
|
116
|
-
readonly motionStateSocket: AutoReconnectingWebsocket,
|
|
117
|
-
) {
|
|
118
|
-
this.rapidlyChangingMotionState = initialMotionState
|
|
119
|
-
|
|
120
|
-
motionStateSocket.addEventListener("message", (event) => {
|
|
121
|
-
const motionState = tryParseJson(event.data)?.result as
|
|
122
|
-
| MotionGroupState
|
|
123
|
-
| undefined
|
|
124
|
-
|
|
125
|
-
if (!motionState) {
|
|
126
|
-
throw new Error(
|
|
127
|
-
`Failed to get motion state for ${this.motionGroupId}: ${event.data}`,
|
|
128
|
-
)
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// handle motionState message
|
|
132
|
-
if (
|
|
133
|
-
!jointValuesEqual(
|
|
134
|
-
this.rapidlyChangingMotionState.joint_position.joints,
|
|
135
|
-
motionState.joint_position.joints,
|
|
136
|
-
MOTION_DELTA_THRESHOLD,
|
|
137
|
-
)
|
|
138
|
-
) {
|
|
139
|
-
runInAction(() => {
|
|
140
|
-
this.rapidlyChangingMotionState = motionState
|
|
141
|
-
})
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// handle tcpPose message
|
|
145
|
-
if (
|
|
146
|
-
!tcpPoseEqual(
|
|
147
|
-
this.rapidlyChangingMotionState.tcp_pose,
|
|
148
|
-
motionState.tcp_pose,
|
|
149
|
-
MOTION_DELTA_THRESHOLD,
|
|
150
|
-
)
|
|
151
|
-
) {
|
|
152
|
-
runInAction(() => {
|
|
153
|
-
if (this.rapidlyChangingMotionState.tcp_pose == undefined) {
|
|
154
|
-
this.rapidlyChangingMotionState.tcp_pose = motionState.tcp_pose
|
|
155
|
-
} else {
|
|
156
|
-
this.rapidlyChangingMotionState.tcp_pose = {
|
|
157
|
-
position: motionState.tcp_pose!.position,
|
|
158
|
-
orientation: unwrapRotationVector(
|
|
159
|
-
motionState.tcp_pose!.orientation as Vector3d,
|
|
160
|
-
this.rapidlyChangingMotionState.tcp_pose!
|
|
161
|
-
.orientation as Vector3d,
|
|
162
|
-
),
|
|
163
|
-
tcp: motionState.tcp_pose!.tcp,
|
|
164
|
-
coordinate_system: motionState.tcp_pose!.coordinate_system,
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
})
|
|
170
|
-
makeAutoObservable(this)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
get motionGroupId() {
|
|
174
|
-
return this.motionGroup.motion_group
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
get controllerId() {
|
|
178
|
-
return this.controller.controller
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
get modelFromController() {
|
|
182
|
-
return this.motionGroup.model_from_controller
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
get wandelscriptIdentifier() {
|
|
186
|
-
const num = this.motionGroupId.split("@")[0]
|
|
187
|
-
return `${this.controllerId.replaceAll("-", "_")}_${num}`
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
get joints() {
|
|
191
|
-
return this.initialMotionState.joint_position.joints.map((_, i) => {
|
|
192
|
-
return {
|
|
193
|
-
index: i,
|
|
194
|
-
}
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
dispose() {
|
|
199
|
-
this.motionStateSocket.close()
|
|
200
|
-
}
|
|
201
|
-
}
|