@wandelbots/wandelbots-js-react-components 3.4.0-pr.feat-add-yaskawa-gp200s.438.0503e4b → 3.4.0-pr.feat-yaskawa-gp200s.437.1b5737e

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.
Files changed (165) hide show
  1. package/README.md +61 -10
  2. package/dist/3d.cjs.js +2 -0
  3. package/dist/3d.cjs.js.map +1 -0
  4. package/dist/3d.d.ts +11 -0
  5. package/dist/3d.d.ts.map +1 -0
  6. package/dist/3d.es.js +16 -0
  7. package/dist/3d.es.js.map +1 -0
  8. package/dist/LoadingCover-Dr9hDTku.js +87 -0
  9. package/dist/LoadingCover-Dr9hDTku.js.map +1 -0
  10. package/dist/LoadingCover-r2yhJZF9.cjs +2 -0
  11. package/dist/LoadingCover-r2yhJZF9.cjs.map +1 -0
  12. package/dist/WandelscriptEditor-Dj7TBCkF.cjs +2 -0
  13. package/dist/WandelscriptEditor-Dj7TBCkF.cjs.map +1 -0
  14. package/dist/WandelscriptEditor-DnJvITTA.js +140 -0
  15. package/dist/WandelscriptEditor-DnJvITTA.js.map +1 -0
  16. package/dist/auth0-spa-js.production.esm-9WdmjTxR.js +1429 -0
  17. package/dist/auth0-spa-js.production.esm-9WdmjTxR.js.map +1 -0
  18. package/dist/auth0-spa-js.production.esm-BcLLh1tx.cjs +5 -0
  19. package/dist/auth0-spa-js.production.esm-BcLLh1tx.cjs.map +1 -0
  20. package/dist/components/3d-viewport/CoordinateSystemTransform.d.ts +1 -1
  21. package/dist/components/3d-viewport/CoordinateSystemTransform.d.ts.map +1 -1
  22. package/dist/components/3d-viewport/SafetyZonesRenderer.d.ts.map +1 -1
  23. package/dist/components/3d-viewport/collider/ColliderCollection.d.ts +1 -1
  24. package/dist/components/3d-viewport/collider/ColliderCollection.d.ts.map +1 -1
  25. package/dist/components/3d-viewport/collider/ColliderElement.d.ts +1 -1
  26. package/dist/components/3d-viewport/collider/ColliderElement.d.ts.map +1 -1
  27. package/dist/components/3d-viewport/collider/CollisionSceneRenderer.d.ts +1 -1
  28. package/dist/components/3d-viewport/collider/CollisionSceneRenderer.d.ts.map +1 -1
  29. package/dist/components/3d-viewport/collider/colliderShapeToBufferGeometry.d.ts +1 -1
  30. package/dist/components/3d-viewport/collider/colliderShapeToBufferGeometry.d.ts.map +1 -1
  31. package/dist/components/ProgramStateIndicator.d.ts +3 -3
  32. package/dist/components/ProgramStateIndicator.d.ts.map +1 -1
  33. package/dist/components/RobotCard.d.ts +4 -3
  34. package/dist/components/RobotCard.d.ts.map +1 -1
  35. package/dist/components/jogging/JoggingBlocked.d.ts +7 -0
  36. package/dist/components/jogging/JoggingBlocked.d.ts.map +1 -0
  37. package/dist/components/jogging/JoggingCartesianTab.d.ts.map +1 -1
  38. package/dist/components/jogging/JoggingOptions.d.ts.map +1 -1
  39. package/dist/components/jogging/JoggingPanel.d.ts +1 -1
  40. package/dist/components/jogging/JoggingPanel.d.ts.map +1 -1
  41. package/dist/components/jogging/JoggingStore.d.ts +10 -5
  42. package/dist/components/jogging/JoggingStore.d.ts.map +1 -1
  43. package/dist/components/jogging/PoseCartesianValues.d.ts +2 -2
  44. package/dist/components/jogging/PoseCartesianValues.d.ts.map +1 -1
  45. package/dist/components/jogging/PoseJointValues.d.ts +1 -2
  46. package/dist/components/jogging/PoseJointValues.d.ts.map +1 -1
  47. package/dist/components/robots/DHRobot.d.ts.map +1 -1
  48. package/dist/components/robots/Robot.d.ts +1 -1
  49. package/dist/components/robots/Robot.d.ts.map +1 -1
  50. package/dist/components/robots/RobotAnimator.d.ts +2 -2
  51. package/dist/components/robots/RobotAnimator.d.ts.map +1 -1
  52. package/dist/components/robots/SupportedRobot.d.ts +3 -3
  53. package/dist/components/robots/SupportedRobot.d.ts.map +1 -1
  54. package/dist/components/robots/manufacturerHomePositions.d.ts +1 -1
  55. package/dist/components/robots/manufacturerHomePositions.d.ts.map +1 -1
  56. package/dist/components/safetyBar/ControllerTypeIndicator.d.ts.map +1 -1
  57. package/dist/components/safetyBar/OperationModeIndicator.d.ts +2 -2
  58. package/dist/components/safetyBar/OperationModeIndicator.d.ts.map +1 -1
  59. package/dist/components/safetyBar/SafetyBar.d.ts +3 -3
  60. package/dist/components/safetyBar/SafetyBar.d.ts.map +1 -1
  61. package/dist/components/safetyBar/SafetyStateIndicator.d.ts +2 -2
  62. package/dist/components/safetyBar/SafetyStateIndicator.d.ts.map +1 -1
  63. package/dist/components/utils/errorHandling.d.ts.map +1 -1
  64. package/dist/components/utils/errorHandling.test.d.ts +2 -0
  65. package/dist/components/utils/errorHandling.test.d.ts.map +1 -0
  66. package/dist/core.cjs.js +2 -0
  67. package/dist/core.cjs.js.map +1 -0
  68. package/dist/core.d.ts +33 -0
  69. package/dist/core.d.ts.map +1 -0
  70. package/dist/core.es.js +54 -0
  71. package/dist/core.es.js.map +1 -0
  72. package/dist/index-CAib4NKw.js +2261 -0
  73. package/dist/index-CAib4NKw.js.map +1 -0
  74. package/dist/index-CqMZL0FV.cjs +2 -0
  75. package/dist/index-CqMZL0FV.cjs.map +1 -0
  76. package/dist/index-CxasuX80.js +5212 -0
  77. package/dist/index-CxasuX80.js.map +1 -0
  78. package/dist/index-DxwppshT.cjs +29 -0
  79. package/dist/index-DxwppshT.cjs.map +1 -0
  80. package/dist/index.cjs.js +2 -0
  81. package/dist/index.cjs.js.map +1 -0
  82. package/dist/index.d.ts +3 -39
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.es.js +69 -0
  85. package/dist/index.es.js.map +1 -0
  86. package/dist/lib/ConnectedMotionGroup.d.ts +90 -0
  87. package/dist/lib/ConnectedMotionGroup.d.ts.map +1 -0
  88. package/dist/lib/JoggerConnection.d.ts +113 -0
  89. package/dist/lib/JoggerConnection.d.ts.map +1 -0
  90. package/dist/lib/JoggerConnection.test.d.ts +2 -0
  91. package/dist/lib/JoggerConnection.test.d.ts.map +1 -0
  92. package/dist/lib/MotionStreamConnection.d.ts +24 -0
  93. package/dist/lib/MotionStreamConnection.d.ts.map +1 -0
  94. package/dist/lib/MotionStreamConnection.test.d.ts +2 -0
  95. package/dist/lib/MotionStreamConnection.test.d.ts.map +1 -0
  96. package/dist/lib/motionStateUpdate.d.ts +7 -0
  97. package/dist/lib/motionStateUpdate.d.ts.map +1 -0
  98. package/dist/lib/motionStateUpdate.test.d.ts +2 -0
  99. package/dist/lib/motionStateUpdate.test.d.ts.map +1 -0
  100. package/dist/manufacturerHomePositions-Ca80ycLi.cjs +2 -0
  101. package/dist/manufacturerHomePositions-Ca80ycLi.cjs.map +1 -0
  102. package/dist/manufacturerHomePositions-CgaG5vaK.js +976 -0
  103. package/dist/manufacturerHomePositions-CgaG5vaK.js.map +1 -0
  104. package/dist/theming-BQcKj8Gp.cjs +133 -0
  105. package/dist/theming-BQcKj8Gp.cjs.map +1 -0
  106. package/dist/theming-Bafjg0Wg.js +23460 -0
  107. package/dist/theming-Bafjg0Wg.js.map +1 -0
  108. package/dist/wandelscript.cjs.js +2 -0
  109. package/dist/wandelscript.cjs.js.map +1 -0
  110. package/dist/wandelscript.d.ts +2 -0
  111. package/dist/wandelscript.d.ts.map +1 -0
  112. package/dist/wandelscript.es.js +5 -0
  113. package/dist/wandelscript.es.js.map +1 -0
  114. package/package.json +49 -21
  115. package/src/3d.ts +15 -0
  116. package/src/components/3d-viewport/CoordinateSystemTransform.tsx +1 -1
  117. package/src/components/3d-viewport/SafetyZonesRenderer.tsx +1 -2
  118. package/src/components/3d-viewport/collider/ColliderCollection.tsx +1 -1
  119. package/src/components/3d-viewport/collider/ColliderElement.tsx +1 -1
  120. package/src/components/3d-viewport/collider/CollisionSceneRenderer.tsx +1 -1
  121. package/src/components/3d-viewport/collider/colliderShapeToBufferGeometry.ts +1 -1
  122. package/src/components/AppHeader.md +1 -1
  123. package/src/components/ProgramStateIndicator.tsx +3 -6
  124. package/src/components/RobotCard.tsx +4 -7
  125. package/src/components/jogging/JoggingBlocked.tsx +37 -0
  126. package/src/components/jogging/JoggingCartesianTab.tsx +13 -11
  127. package/src/components/jogging/JoggingJointLimitDetector.tsx +2 -2
  128. package/src/components/jogging/JoggingJointTab.tsx +4 -4
  129. package/src/components/jogging/JoggingOptions.tsx +6 -5
  130. package/src/components/jogging/JoggingPanel.tsx +6 -3
  131. package/src/components/jogging/JoggingStore.ts +66 -39
  132. package/src/components/jogging/PoseCartesianValues.tsx +3 -4
  133. package/src/components/jogging/PoseJointValues.tsx +3 -4
  134. package/src/components/robots/DHRobot.tsx +2 -3
  135. package/src/components/robots/Robot.tsx +1 -1
  136. package/src/components/robots/RobotAnimator.test.tsx +7 -22
  137. package/src/components/robots/RobotAnimator.tsx +8 -13
  138. package/src/components/robots/SupportedRobot.tsx +3 -6
  139. package/src/components/robots/manufacturerHomePositions.ts +1 -1
  140. package/src/components/safetyBar/ControllerTypeIndicator.tsx +4 -2
  141. package/src/components/safetyBar/OperationModeIndicator.tsx +7 -5
  142. package/src/components/safetyBar/SafetyBar.tsx +3 -6
  143. package/src/components/safetyBar/SafetyStateIndicator.tsx +9 -7
  144. package/src/components/utils/errorHandling.test.ts +41 -0
  145. package/src/components/utils/errorHandling.ts +4 -0
  146. package/src/core.ts +33 -0
  147. package/src/i18n/locales/de/translations.json +3 -0
  148. package/src/i18n/locales/en/translations.json +3 -0
  149. package/src/index.ts +4 -43
  150. package/src/lib/ConnectedMotionGroup.ts +444 -0
  151. package/src/lib/JoggerConnection.test.ts +120 -0
  152. package/src/lib/JoggerConnection.ts +674 -0
  153. package/src/lib/MotionStreamConnection.test.ts +23 -0
  154. package/src/lib/MotionStreamConnection.ts +189 -0
  155. package/src/lib/motionStateUpdate.test.ts +28 -0
  156. package/src/lib/motionStateUpdate.ts +117 -0
  157. package/src/wandelscript.ts +2 -0
  158. package/dist/auth0-spa-js.production.esm-1QXzndwB.js +0 -950
  159. package/dist/auth0-spa-js.production.esm-1QXzndwB.js.map +0 -1
  160. package/dist/auth0-spa-js.production.esm-BLRAk7Yh.cjs +0 -5
  161. package/dist/auth0-spa-js.production.esm-BLRAk7Yh.cjs.map +0 -1
  162. package/dist/index.cjs +0 -155
  163. package/dist/index.cjs.map +0 -1
  164. package/dist/index.js +0 -34927
  165. package/dist/index.js.map +0 -1
@@ -0,0 +1,674 @@
1
+ import {
2
+ type AutoReconnectingWebsocket,
3
+ tryParseJson,
4
+ XYZ_TO_VECTOR,
5
+ } from "@wandelbots/nova-js"
6
+ import type {
7
+ InitializeMovementResponse,
8
+ MotionCommand,
9
+ NovaClient,
10
+ Pose,
11
+ StartMovementResponse,
12
+ } from "@wandelbots/nova-js/v2"
13
+ import { when } from "mobx"
14
+ import { Vector3 } from "three/src/math/Vector3.js"
15
+ import { MotionStreamConnection } from "./MotionStreamConnection"
16
+
17
+ export type Vector3Simple = [number, number, number]
18
+
19
+ const API_ERROR_CONNECTION_BLOCKED = `Movement request rejected. Another client is currently executing a 'Jogging' motion!`
20
+
21
+ export type JoggerConnectionOptions = {
22
+ // The mode of the jogger connection - see type description of JoggerMode for details
23
+ mode?: JoggerMode
24
+
25
+ // Connection timeout in milliseconds to wait for jogging initialization to complete
26
+ timeout?: number
27
+
28
+ /**
29
+ * When an error message is received from the jogging websocket, it
30
+ * will be passed here. If this handler is not provided, the error will
31
+ * instead be thrown as an unhandled error.
32
+ */
33
+ onError?: (err: unknown) => void
34
+
35
+ // The TCP to use for jogging motions.
36
+ tcp?: string
37
+
38
+ // The coordinate system in which jogging takes place in
39
+ // coordinateSystem?: string,
40
+
41
+ // If set to "tool", jogging TcpVelocityRequest will use `use_tool_coordinate_system` option
42
+ orientation?: JoggerOrientation
43
+ }
44
+
45
+ // Three modes are supported:
46
+ // - "jogging": Continuous jogging mode, where joint velocities or TCP velocities can be commanded. Opens a single websocket connection to jogging endpoint
47
+ // - "trajectory": Incremental jogging mode, where fixed distance motions are planned and executed. Opens a short lived websocket for each motion.
48
+ // - "off": No jogging, all websockets are closed.
49
+ export type JoggerMode = "jogging" | "trajectory" | "off"
50
+
51
+ // If set to "tool", will use `use_tool_coordinate_system` option in movement requests
52
+ export type JoggerOrientation = "coordsys" | "tool"
53
+
54
+ export class JoggerConnection {
55
+ ENDPOINT_JOGGING = "/execution/jogging"
56
+ ENDPOINT_TRAJECTORY = "/execution/trajectory"
57
+ DEFAULT_MODE = "off" as JoggerMode
58
+ DEFAULT_TCP = "Flange"
59
+ // DEFAULT_COORDINATE_SYSTEM = "world"
60
+ DEFAULT_INIT_TIMEOUT = 5000
61
+ DEFAULT_ORIENTATION = "coordsys" as JoggerOrientation
62
+
63
+ mode: JoggerMode = "off"
64
+ joggingSocket: AutoReconnectingWebsocket | null = null
65
+ trajectorySocket: AutoReconnectingWebsocket | null = null
66
+ timeout: number = this.DEFAULT_INIT_TIMEOUT
67
+ tcp: string
68
+ // coordinateSystem?: string
69
+ orientation: JoggerOrientation
70
+ onError?: (err: unknown) => void
71
+ onBlocked?: () => void
72
+
73
+ /**
74
+ * Initialize the jogging connection using jogging endpoint or trajectory endpoint depending on the selected mode.
75
+ *
76
+ * @param nova - The Nova client instance
77
+ * @param motionGroupId - The ID of the motion group to connect to
78
+ * @param options - Configuration options for the jogger connection
79
+ * @param options.mode - The jogging mode to initialize:
80
+ * - "jogging": Continuous jogging mode with persistent websocket for real-time velocity commands
81
+ * - "trajectory": Incremental jogging mode for fixed-distance motions via trajectory planning
82
+ * - "off": No jogging enabled, all websockets closed (default)
83
+ * @param options.timeout - Timeout in milliseconds for websocket initialization (default: 5000ms)
84
+ * @param options.tcp - TCP frame to use for motions (default: from motion group)
85
+ * //param options.coordinateSystem - Coordinate system for jogging commands (default: "world"). Please note: Currently not implemented
86
+ * @param options.orientation - If set to "tool", jogging TcpVelocityRequest will use `use_tool_coordinate_system` option (default: "coordsys")
87
+ * @param options.onError - Error handler for websocket errors
88
+ * @returns Promise resolving to initialized JoggerConnection instance
89
+ */
90
+ static async open(
91
+ nova: NovaClient,
92
+ motionGroupId: string,
93
+ options: JoggerConnectionOptions = {},
94
+ ) {
95
+ // Get matching motion stream
96
+ const motionStream = await MotionStreamConnection.open(nova, motionGroupId)
97
+
98
+ // Initialize jogger with options
99
+ const jogger = new JoggerConnection(motionStream, options)
100
+
101
+ // Initialize the jogging websocket, if necessary (mode is "jogging")
102
+ await jogger.setJoggingMode(jogger.mode)
103
+
104
+ // Return the initialized jogger
105
+ return jogger
106
+ }
107
+
108
+ constructor(
109
+ readonly motionStream: MotionStreamConnection,
110
+ readonly options: JoggerConnectionOptions | undefined = {},
111
+ ) {
112
+ this.tcp = options?.tcp || motionStream.motionGroup.tcp || this.DEFAULT_TCP
113
+ // this.coordinateSystem = options?.coordinateSystem || this.DEFAULT_COORDINATE_SYSTEM
114
+ this.orientation = options?.orientation || this.DEFAULT_ORIENTATION
115
+ this.timeout = options?.timeout || this.DEFAULT_INIT_TIMEOUT
116
+ this.mode = options?.mode || this.DEFAULT_MODE
117
+ this.onError = options?.onError
118
+ }
119
+
120
+ // Set a new tcp or other options. If current mode is jogging, will reinitialize the jogging websocket
121
+ async setOptions(options: Partial<JoggerConnectionOptions>) {
122
+ if (options.tcp) {
123
+ this.tcp = options.tcp
124
+ }
125
+
126
+ if (options.orientation) {
127
+ this.orientation = options.orientation
128
+ }
129
+
130
+ // if (options.coordinateSystem) {
131
+ // this.coordinateSystem = options.coordinateSystem
132
+ // }
133
+
134
+ if (options.timeout) {
135
+ this.timeout = options.timeout
136
+ }
137
+
138
+ if (options.mode) {
139
+ this.mode = options.mode
140
+ }
141
+
142
+ if (options.onError) {
143
+ this.onError = options.onError
144
+ }
145
+
146
+ this.setJoggingMode(this.mode, false)
147
+ }
148
+
149
+ get motionGroupId() {
150
+ return this.motionStream.motionGroupId
151
+ }
152
+
153
+ get nova() {
154
+ return this.motionStream.nova
155
+ }
156
+
157
+ get numJoints() {
158
+ return this.motionStream.joints.length
159
+ }
160
+
161
+ // get activeJoggingMode() {
162
+ // return this.mode
163
+ // }
164
+
165
+ // get activeWebsocket() {
166
+ // return this.mode === "jogging" ? this.joggingSocket : this.trajectorySocket;
167
+ // }
168
+
169
+ // Sends stop movement command to robot
170
+ async stop() {
171
+ if (this.joggingSocket) {
172
+ const velocity = new Array(this.numJoints).fill(0)
173
+ this.joggingSocket.sendJson({
174
+ message_type: "JointVelocityRequest",
175
+ velocity,
176
+ })
177
+ }
178
+
179
+ if (this.trajectorySocket) {
180
+ this.trajectorySocket.sendJson({
181
+ message_type: "PauseMovementRequest",
182
+ })
183
+ }
184
+ }
185
+
186
+ // Dispose the jogger, closing all open websockets
187
+ async dispose() {
188
+ // Collect all initialized sockets
189
+ const sockets = [this.joggingSocket, this.trajectorySocket].filter(
190
+ (s) => s !== null,
191
+ ) as AutoReconnectingWebsocket[]
192
+
193
+ // Call them to dispose
194
+ sockets.forEach((s) => {
195
+ s.dispose()
196
+ })
197
+
198
+ // Remove references
199
+ this.joggingSocket = null
200
+ this.trajectorySocket = null
201
+
202
+ // Return promise that resolves when all sockets are closed
203
+ return Promise.all(sockets.map((s) => s.closed()))
204
+ }
205
+
206
+ // Activate jogger in one of the supported modes
207
+ // Will iniitialize or close websockets as necessary
208
+ // If mode is unchanged, does nothing (unless skipReinitializeIfSameMode is false)
209
+ async setJoggingMode(mode: JoggerMode, skipReinitializeIfSameMode = true) {
210
+ // If not changing mode, do nothing
211
+ if (this.mode === mode && skipReinitializeIfSameMode) {
212
+ return
213
+ }
214
+
215
+ // Close any previous websocket connection
216
+ this.dispose()
217
+
218
+ // Set the new mode
219
+ this.mode = mode
220
+
221
+ // Mode is "jogging" - open jogging websocket
222
+ if (this.mode === "jogging") {
223
+ return this.initializeJoggingWebsocket()
224
+ }
225
+
226
+ // Mode is "trajectory" or "off" - nothing more to do
227
+ return
228
+ }
229
+
230
+ // Initializes continuous jogging websocket, called by setJoggingMode("jogging")
231
+ async initializeJoggingWebsocket() {
232
+ // Wait for initialization with timeout
233
+ return new Promise<void>((resolve, reject) => {
234
+ const connectionFailedTimeout = setTimeout(() => {
235
+ reject(
236
+ new Error(
237
+ `Jogging initialization timeout after ${this.timeout} seconds`,
238
+ ),
239
+ )
240
+ }, this.timeout)
241
+
242
+ this.joggingSocket = this.nova.openReconnectingWebsocket(
243
+ this.ENDPOINT_JOGGING,
244
+ )
245
+ this.joggingSocket.addEventListener("message", (ev: MessageEvent) => {
246
+ const data = tryParseJson(ev.data)
247
+
248
+ if (data?.result?.kind === "INITIALIZE_RECEIVED") {
249
+ clearTimeout(connectionFailedTimeout)
250
+ resolve()
251
+ return
252
+ }
253
+
254
+ if (data?.result?.kind === "MOTION_ERROR") {
255
+ clearTimeout(connectionFailedTimeout)
256
+ if (
257
+ this.onBlocked &&
258
+ data?.result?.message.includes(API_ERROR_CONNECTION_BLOCKED)
259
+ ) {
260
+ this.joggingSocket?.dispose()
261
+ this.onBlocked()
262
+ return
263
+ } else if (this.onError) {
264
+ this.onError(ev.data)
265
+ } else {
266
+ reject(new Error(ev.data))
267
+ }
268
+ }
269
+ })
270
+
271
+ this.joggingSocket.sendJson({
272
+ message_type: "InitializeJoggingRequest",
273
+ motion_group: this.motionGroupId,
274
+ tcp: this.tcp,
275
+ })
276
+ })
277
+ }
278
+
279
+ /**
280
+ * Jogging: Start rotation of a single robot joint at the specified velocity
281
+ */
282
+ async rotateJoints({
283
+ joint,
284
+ direction,
285
+ velocityRadsPerSec,
286
+ }: {
287
+ /** Index of the joint to rotate */
288
+ joint: number
289
+ /** Direction of rotation ("+" or "-") */
290
+ direction: "+" | "-"
291
+ /** Speed of the rotation in radians per second */
292
+ velocityRadsPerSec: number
293
+ }) {
294
+ if (!this.joggingSocket || this.mode !== "jogging") {
295
+ throw new Error(
296
+ "Joint jogging websocket not connected; create one by setting jogging mode to 'jogging'",
297
+ )
298
+ }
299
+
300
+ const velocity = new Array(this.numJoints).fill(0)
301
+
302
+ velocity[joint] =
303
+ direction === "-" ? -velocityRadsPerSec : velocityRadsPerSec
304
+
305
+ this.joggingSocket.sendJson({
306
+ message_type: "JointVelocityRequest",
307
+ velocity,
308
+ })
309
+ }
310
+
311
+ /**
312
+ * Jogging: Start the TCP moving along a specified axis at a given velocity
313
+ */
314
+ async translateTCP({
315
+ axis,
316
+ direction,
317
+ velocityMmPerSec,
318
+ }: {
319
+ axis: "x" | "y" | "z"
320
+ direction: "-" | "+"
321
+ velocityMmPerSec: number
322
+ }) {
323
+ if (!this.joggingSocket || this.mode !== "jogging") {
324
+ throw new Error(
325
+ "Continuous jogging websocket not connected; create one by setting jogging mode to 'jogging'",
326
+ )
327
+ }
328
+ const rotation = [0, 0, 0]
329
+ const translation = [0, 0, 0]
330
+ translation[XYZ_TO_VECTOR[axis]] =
331
+ direction === "-" ? -velocityMmPerSec : velocityMmPerSec
332
+
333
+ this.joggingSocket.sendJson({
334
+ message_type: "TcpVelocityRequest",
335
+ translation,
336
+ rotation,
337
+ use_tool_coordinate_system: this.orientation === "tool",
338
+ })
339
+ }
340
+
341
+ /**
342
+ * Jogging: Start the TCP rotating around a specified axis at a given velocity
343
+ */
344
+ async rotateTCP({
345
+ axis,
346
+ direction,
347
+ velocityRadsPerSec,
348
+ }: {
349
+ axis: "x" | "y" | "z"
350
+ direction: "-" | "+"
351
+ velocityRadsPerSec: number
352
+ }) {
353
+ if (!this.joggingSocket || this.mode !== "jogging") {
354
+ throw new Error(
355
+ "Continuous jogging websocket not connected; create one by setting jogging mode to 'jogging'",
356
+ )
357
+ }
358
+ const rotation = [0, 0, 0]
359
+ const translation = [0, 0, 0]
360
+ rotation[XYZ_TO_VECTOR[axis]] =
361
+ direction === "-" ? -velocityRadsPerSec : velocityRadsPerSec
362
+
363
+ this.joggingSocket.sendJson({
364
+ message_type: "TcpVelocityRequest",
365
+ translation,
366
+ rotation,
367
+ })
368
+ }
369
+
370
+ /**
371
+ * Trajectory jogging:
372
+ *
373
+ * Move the robot by a fixed distance in a single cartesian
374
+ * axis, either rotating or translating relative to the TCP.
375
+ * Promise resolves only after the motion has completed.
376
+ */
377
+ async runIncrementalCartesianMotion({
378
+ currentTcpPose,
379
+ currentJoints,
380
+ // coordSystemId,
381
+ velocityInRelevantUnits,
382
+ axis,
383
+ direction,
384
+ motion,
385
+ }: {
386
+ currentTcpPose: Pose
387
+ currentJoints: Vector3Simple
388
+ coordSystemId: string
389
+ velocityInRelevantUnits: number
390
+ axis: "x" | "y" | "z"
391
+ direction: "-" | "+"
392
+ motion:
393
+ | {
394
+ type: "rotate"
395
+ distanceRads: number
396
+ }
397
+ | {
398
+ type: "translate"
399
+ distanceMm: number
400
+ }
401
+ }) {
402
+ const commands: MotionCommand[] = []
403
+
404
+ if (this.mode !== "trajectory") {
405
+ throw new Error(
406
+ "Set jogging mode to 'trajectory' to run incremental cartesian motions",
407
+ )
408
+ }
409
+
410
+ if (motion.type === "translate") {
411
+ if (!currentTcpPose.position) {
412
+ throw new Error(
413
+ "Current pose has no position, cannot perform translation",
414
+ )
415
+ }
416
+
417
+ const targetTcpPosition = [...currentTcpPose.position]
418
+ targetTcpPosition[XYZ_TO_VECTOR[axis]] +=
419
+ motion.distanceMm * (direction === "-" ? -1 : 1)
420
+
421
+ commands.push({
422
+ limits_override: {
423
+ tcp_velocity_limit: velocityInRelevantUnits,
424
+ },
425
+ path: {
426
+ path_definition_name: "PathLine",
427
+ target_pose: {
428
+ position: targetTcpPosition,
429
+ orientation: currentTcpPose.orientation,
430
+ },
431
+ },
432
+ })
433
+ } else if (motion.type === "rotate") {
434
+ // Concatenate rotations expressed by rotation vectors
435
+ // Equations taken from https://physics.stackexchange.com/a/287819
436
+
437
+ if (!currentTcpPose.orientation) {
438
+ throw new Error(
439
+ "Current pose has no orientation, cannot perform rotation",
440
+ )
441
+ }
442
+
443
+ // Compute axis and angle of current rotation vector
444
+ const currentRotationVector = new Vector3(
445
+ currentTcpPose.orientation[0],
446
+ currentTcpPose.orientation[1],
447
+ currentTcpPose.orientation[2],
448
+ )
449
+
450
+ const currentRotationRad = currentRotationVector.length()
451
+ const currentRotationDirection = currentRotationVector.clone().normalize()
452
+
453
+ // Compute axis and angle of difference rotation vector
454
+ const differenceRotationRad =
455
+ motion.distanceRads * (direction === "-" ? -1 : 1)
456
+
457
+ const differenceRotationDirection = new Vector3(0.0, 0.0, 0.0)
458
+ differenceRotationDirection[axis] = 1.0
459
+
460
+ // Some abbreviations to make the following equations more readable
461
+ const f1 =
462
+ Math.cos(0.5 * differenceRotationRad) *
463
+ Math.cos(0.5 * currentRotationRad)
464
+ const f2 =
465
+ Math.sin(0.5 * differenceRotationRad) *
466
+ Math.sin(0.5 * currentRotationRad)
467
+ const f3 =
468
+ Math.sin(0.5 * differenceRotationRad) *
469
+ Math.cos(0.5 * currentRotationRad)
470
+ const f4 =
471
+ Math.cos(0.5 * differenceRotationRad) *
472
+ Math.sin(0.5 * currentRotationRad)
473
+
474
+ const dotProduct = differenceRotationDirection.dot(
475
+ currentRotationDirection,
476
+ )
477
+
478
+ const crossProduct = differenceRotationDirection
479
+ .clone()
480
+ .cross(currentRotationDirection)
481
+
482
+ // Compute angle of concatenated rotation
483
+ const newRotationRad = 2.0 * Math.acos(f1 - f2 * dotProduct)
484
+
485
+ // Compute rotation vector of concatenated rotation
486
+ const f5 = newRotationRad / Math.sin(0.5 * newRotationRad)
487
+
488
+ const targetTcpOrientation = new Vector3()
489
+ .addScaledVector(crossProduct, f2)
490
+ .addScaledVector(differenceRotationDirection, f3)
491
+ .addScaledVector(currentRotationDirection, f4)
492
+ .multiplyScalar(f5)
493
+
494
+ commands.push({
495
+ limits_override: {
496
+ tcp_orientation_velocity_limit: velocityInRelevantUnits,
497
+ },
498
+ path: {
499
+ path_definition_name: "PathLine",
500
+ target_pose: {
501
+ position: currentTcpPose.position,
502
+ orientation: [...targetTcpOrientation],
503
+ },
504
+ },
505
+ })
506
+ }
507
+
508
+ // Plan the motion https://portal.wandelbots.io/docs/api/v2/ui/#/operations/planTrajectory
509
+ const description = this.motionStream.description
510
+ if (description.cycle_time === undefined) {
511
+ console.warn(
512
+ "Current motion group has no cycle time, cannot plan jogging motion",
513
+ )
514
+ return
515
+ }
516
+
517
+ const motion_group_setup = {
518
+ motion_group_model: description.motion_group_model,
519
+ cycle_time: description.cycle_time,
520
+ mounting: description.mounting,
521
+
522
+ // tcp_offset: description.tcp_offset, TODO: implement tcp_offset handling
523
+ // FIXME use proper mode's limits if set
524
+ global: description.operation_limits.auto_limits,
525
+ }
526
+
527
+ const motionPlanRes = await this.nova.api.trajectoryPlanning.planTrajectory(
528
+ {
529
+ motion_group_setup,
530
+ start_joint_position: currentJoints,
531
+ motion_commands: commands,
532
+ },
533
+ )
534
+
535
+ const trajectoryData = motionPlanRes.response
536
+ if (!trajectoryData) {
537
+ throw new Error(
538
+ `Failed to plan jogging increment motion ${JSON.stringify(motionPlanRes)}`,
539
+ )
540
+ }
541
+
542
+ if (this.trajectorySocket) {
543
+ console.warn("Trajectory jogging websocket already open; will close")
544
+ this.trajectorySocket.dispose()
545
+ }
546
+
547
+ // Execute the planned motion https://portal.wandelbots.io/docs/api/v2/ui/#/operations/executeTrajectory
548
+ this.trajectorySocket = this.nova.openReconnectingWebsocket(
549
+ this.ENDPOINT_TRAJECTORY,
550
+ )
551
+
552
+ const messageInitializeMovementResponse = (
553
+ result: InitializeMovementResponse | undefined,
554
+ ) => {
555
+ // Handle errorous response
556
+ if (!result || result.add_trajectory_error || result.message) {
557
+ if (this.onError) {
558
+ this.onError(result)
559
+ } else {
560
+ throw new Error(
561
+ result?.add_trajectory_error?.message ||
562
+ result?.message ||
563
+ "Failed to execute trajectory, unknown error",
564
+ )
565
+ }
566
+ }
567
+
568
+ // Handle socket gone
569
+ if (!this.trajectorySocket) {
570
+ throw new Error(
571
+ `Failed to execute trajectory, websocket not available anymore`,
572
+ )
573
+ }
574
+
575
+ // Trajectory locked, now start movement
576
+ this.trajectorySocket.sendJson({
577
+ message_type: "StartMovementRequest",
578
+ direction: "DIRECTION_FORWARD",
579
+ })
580
+ }
581
+
582
+ const waitForMovementToStartAndFinish = async () => {
583
+ // Wait for robot to start moving (standstill becomes false)
584
+ await when(() => !this.motionStream.rapidlyChangingMotionState.standstill)
585
+
586
+ // Then wait for robot to stop moving (standstill becomes true)
587
+ await when(() => this.motionStream.rapidlyChangingMotionState.standstill)
588
+
589
+ // Close connection and free robot
590
+ this.trajectorySocket?.dispose()
591
+ this.trajectorySocket = null
592
+ }
593
+
594
+ const waitForMovementToFinish = async () => {
595
+ // Wait for robot to stop moving (standstill becomes true)
596
+ await when(() => this.motionStream.rapidlyChangingMotionState.standstill)
597
+
598
+ // Close connection and free robot
599
+ this.trajectorySocket?.dispose()
600
+ this.trajectorySocket = null
601
+ }
602
+
603
+ const messageStartMovementResponse = async (
604
+ data: StartMovementResponse,
605
+ ) => {
606
+ if (data?.message) {
607
+ if (this.onError) {
608
+ this.onError(data)
609
+ return
610
+ } else {
611
+ throw new Error(
612
+ data.message || "Failed to execute trajectory, unknown error",
613
+ )
614
+ }
615
+ }
616
+
617
+ // Movement started we now wait to verify the robot is moving
618
+ // by observing changes to motion state
619
+ if (this.motionStream.rapidlyChangingMotionState.standstill) {
620
+ await waitForMovementToStartAndFinish()
621
+ } else {
622
+ await waitForMovementToFinish()
623
+ }
624
+ }
625
+
626
+ this.trajectorySocket.addEventListener("message", (ev: MessageEvent) => {
627
+ const data = tryParseJson(ev.data)
628
+
629
+ if (!data?.result?.kind) {
630
+ throw new Error(
631
+ `Failed to execute trajectory: Received invalid message ${ev.data}`,
632
+ )
633
+ }
634
+
635
+ if (
636
+ this.onBlocked &&
637
+ data.result.message?.includes(API_ERROR_CONNECTION_BLOCKED)
638
+ ) {
639
+ this.onBlocked()
640
+ return
641
+ }
642
+
643
+ if (data.result.kind === "INITIALIZE_RECEIVED") {
644
+ messageInitializeMovementResponse(data.result)
645
+ } else if (data.result.kind === "START_RECEIVED") {
646
+ messageStartMovementResponse(data)
647
+ } else if (data.result.kind === "PAUSE_RECEIVED") {
648
+ // do nothing
649
+ } else if (data.result.kind === "MOTION_ERROR" && data.result.message) {
650
+ if (this.onError) {
651
+ this.onError(data)
652
+ return
653
+ } else {
654
+ throw new Error(data.result.message)
655
+ }
656
+ } else {
657
+ throw new Error(
658
+ `Failed to execute trajectory, cannot handle message type "${data.result.kind}"`,
659
+ )
660
+ }
661
+ })
662
+
663
+ // Send initialization/movement request
664
+ this.trajectorySocket.sendJson({
665
+ message_type: "InitializeMovementRequest",
666
+ trajectory: {
667
+ message_type: "TrajectoryData",
668
+ motion_group: this.motionGroupId,
669
+ data: trajectoryData,
670
+ tcp: this.tcp,
671
+ },
672
+ })
673
+ }
674
+ }
@@ -0,0 +1,23 @@
1
+ import { NovaClient } from "@wandelbots/nova-js/v2"
2
+ import { expect, test } from "vitest"
3
+ import { MotionStreamConnection } from "./MotionStreamConnection"
4
+
5
+ test("motion stream", async () => {
6
+ const nova = new NovaClient({
7
+ instanceUrl: "https://mock.example.com",
8
+ })
9
+
10
+ const motionStream = await MotionStreamConnection.open(nova, "0@mock-ur5e")
11
+ expect(motionStream.joints.length).toBe(6)
12
+
13
+ // Test changing the url
14
+ motionStream.motionStateSocket.changeUrl(
15
+ nova.makeWebsocketURL("/motion-groups/0@mock-ur5e/state-stream?tcp=foo"),
16
+ )
17
+
18
+ await motionStream.motionStateSocket.firstMessage()
19
+
20
+ expect(motionStream.motionStateSocket.url).toBe(
21
+ "wss://mock.example.com/api/v2/cells/cell/motion-groups/0@mock-ur5e/state-stream?tcp=foo",
22
+ )
23
+ })