@wandelbots/nova-js
Version:
Official JS client for the Wandelbots API
1,303 lines (1,288 loc) • 40 kB
text/typescript
import type {
ControllerInstanceList,
MotionGroupSpecification,
MotionGroupStateResponse,
RobotController,
SafetySetup,
} from "@wandelbots/nova-api/v1"
import type { AxiosResponse, InternalAxiosRequestConfig } from "axios"
import { AxiosError } from "axios"
import * as pathToRegexp from "path-to-regexp"
import type { AutoReconnectingWebsocket } from "../../AutoReconnectingWebsocket"
/**
* Ultra-simplified mock Nova server for testing stuff
*/
export class MockNovaInstance {
readonly connections: AutoReconnectingWebsocket[] = []
async handleAPIRequest(
config: InternalAxiosRequestConfig,
): Promise<AxiosResponse> {
const apiHandlers = [
{
method: "GET",
path: "/cells/:cellId/controllers",
handle() {
return {
instances: [
{
controller: "mock-ur5e",
model_name: "UniversalRobots::Controller",
host: "mock-ur5e",
allow_software_install_on_controller: true,
physical_motion_groups: [
{
motion_group: "0@mock-ur5e",
name_from_controller: "UR5e",
active: false,
model_from_controller: "UniversalRobots_UR5e",
},
],
has_error: false,
error_details: "",
},
],
} satisfies ControllerInstanceList
},
},
{
method: "GET",
path: "/cells/:cellId/controllers/:controllerId",
handle() {
return {
configuration: {
kind: "VirtualController",
manufacturer: "universalrobots",
position: "[0,-1.571,-1.571,-1.571,1.571,-1.571,0]",
type: "universalrobots-ur5e",
},
name: "mock-ur5",
} satisfies RobotController
},
},
{
method: "GET",
path: "/cells/:cellId/motion-groups/:motionGroupId/specification",
handle() {
return {
dh_parameters: [
{
alpha: 1.5707963267948966,
theta: 0,
a: 0,
d: 162.25,
reverse_rotation_direction: false,
},
{
alpha: 0,
theta: 0,
a: -425,
d: 0,
reverse_rotation_direction: false,
},
{
alpha: 0,
theta: 0,
a: -392.2,
d: 0,
reverse_rotation_direction: false,
},
{
alpha: 1.5707963267948966,
theta: 0,
a: 0,
d: 133.3,
reverse_rotation_direction: false,
},
{
alpha: -1.5707963267948966,
theta: 0,
a: 0,
d: 99.7,
reverse_rotation_direction: false,
},
{
alpha: 0,
theta: 0,
a: 0,
d: 99.6,
reverse_rotation_direction: false,
},
],
mechanical_joint_limits: [
{
joint: "JOINTNAME_AXIS_1",
lower_limit: -6.335545063018799,
upper_limit: 6.335545063018799,
unlimited: false,
},
{
joint: "JOINTNAME_AXIS_2",
lower_limit: -6.335545063018799,
upper_limit: 6.335545063018799,
unlimited: false,
},
{
joint: "JOINTNAME_AXIS_3",
lower_limit: -6.335545063018799,
upper_limit: 6.335545063018799,
unlimited: false,
},
{
joint: "JOINTNAME_AXIS_4",
lower_limit: -6.335545063018799,
upper_limit: 6.335545063018799,
unlimited: false,
},
{
joint: "JOINTNAME_AXIS_5",
lower_limit: -6.335545063018799,
upper_limit: 6.335545063018799,
unlimited: false,
},
{
joint: "JOINTNAME_AXIS_6",
lower_limit: -6.335545063018799,
upper_limit: 6.335545063018799,
unlimited: false,
},
],
} satisfies MotionGroupSpecification
},
},
{
method: "GET",
path: "/cells/:cellId/motion-groups/:motionGroupId/safety-setup",
handle() {
return {
safety_settings: [
{
safety_state: "SAFETY_NORMAL",
settings: {
joint_position_limits: [
{
joint: "JOINTNAME_AXIS_1",
lower_limit: -2.96705961227417,
upper_limit: 2.96705961227417,
unlimited: false,
},
{
joint: "JOINTNAME_AXIS_2",
lower_limit: -1.7453292608261108,
upper_limit: 2.7925267219543457,
unlimited: false,
},
{
joint: "JOINTNAME_AXIS_3",
lower_limit: -3.3161256313323975,
upper_limit: 0.40142571926116943,
unlimited: false,
},
{
joint: "JOINTNAME_AXIS_4",
lower_limit: -3.4906585216522217,
upper_limit: 3.4906585216522217,
unlimited: false,
},
{
joint: "JOINTNAME_AXIS_5",
lower_limit: -2.4434609413146973,
upper_limit: 2.4434609413146973,
unlimited: false,
},
{
joint: "JOINTNAME_AXIS_6",
lower_limit: -4.71238899230957,
upper_limit: 4.71238899230957,
unlimited: false,
},
],
joint_velocity_limits: [
{
joint: "JOINTNAME_AXIS_1",
limit: 3.1415927410125732,
},
{
joint: "JOINTNAME_AXIS_2",
limit: 3.1415927410125732,
},
{
joint: "JOINTNAME_AXIS_3",
limit: 3.4906585216522217,
},
{
joint: "JOINTNAME_AXIS_4",
limit: 6.108652591705322,
},
{
joint: "JOINTNAME_AXIS_5",
limit: 6.108652591705322,
},
{
joint: "JOINTNAME_AXIS_6",
limit: 6.981317043304443,
},
],
joint_acceleration_limits: [],
joint_torque_limits: [],
tcp_velocity_limit: 1800,
},
},
],
safety_zones: [
{
id: 1,
priority: 0,
geometry: {
compound: {
child_geometries: [
{
convex_hull: {
vertices: [
{
x: -800,
y: -1330,
z: -1820,
},
{
x: 1650,
y: -1330,
z: -1820,
},
{
x: 1650,
y: 1330,
z: -1820,
},
{
x: -800,
y: 1330,
z: -1820,
},
],
},
init_pose: {
position: {
x: 0,
y: 0,
z: 0,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "box",
},
{
convex_hull: {
vertices: [
{
x: -800,
y: -1330,
z: -1820,
},
{
x: 1650,
y: -1330,
z: -1820,
},
{
x: 1650,
y: -1330,
z: 1500,
},
{
x: -800,
y: -1330,
z: 1500,
},
],
},
init_pose: {
position: {
x: 0,
y: 0,
z: 0,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "box",
},
{
convex_hull: {
vertices: [
{
x: -800,
y: -1330,
z: -1820,
},
{
x: -800,
y: 1330,
z: -1820,
},
{
x: -800,
y: 1330,
z: 1500,
},
{
x: -800,
y: -1330,
z: 1500,
},
],
},
init_pose: {
position: {
x: 0,
y: 0,
z: 0,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "box",
},
{
convex_hull: {
vertices: [
{
x: 1650,
y: 1330,
z: 1500,
},
{
x: -800,
y: 1330,
z: 1500,
},
{
x: -800,
y: -1330,
z: 1500,
},
{
x: 1650,
y: -1330,
z: 1500,
},
],
},
init_pose: {
position: {
x: 0,
y: 0,
z: 0,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "box",
},
{
convex_hull: {
vertices: [
{
x: 1650,
y: 1330,
z: 1500,
},
{
x: -800,
y: 1330,
z: 1500,
},
{
x: -800,
y: 1330,
z: -1820,
},
{
x: 1650,
y: 1330,
z: -1820,
},
],
},
init_pose: {
position: {
x: 0,
y: 0,
z: 0,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "box",
},
{
convex_hull: {
vertices: [
{
x: 1650,
y: 1330,
z: 1500,
},
{
x: 1650,
y: -1330,
z: 1500,
},
{
x: 1650,
y: -1330,
z: -1820,
},
{
x: 1650,
y: 1330,
z: -1820,
},
],
},
init_pose: {
position: {
x: 0,
y: 0,
z: 0,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "box",
},
],
},
init_pose: {
position: {
x: 0,
y: 0,
z: 0,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "Cell workzone",
},
motion_group_uid: 1,
},
{
id: 2,
priority: 0,
geometry: {
convex_hull: {
vertices: [
{
x: 1650,
y: 1330,
z: -1850,
},
{
x: 865,
y: 1330,
z: -1850,
},
{
x: 865,
y: -720,
z: -1850,
},
{
x: 1650,
y: -720,
z: -1850,
},
{
x: 1650,
y: 1330,
z: -920,
},
{
x: 865,
y: 1330,
z: -920,
},
{
x: 865,
y: -720,
z: -920,
},
{
x: 1650,
y: -720,
z: -920,
},
],
},
init_pose: {
position: {
x: 0,
y: 0,
z: 0,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "Transport",
},
motion_group_uid: 1,
},
{
id: 3,
priority: 0,
geometry: {
convex_hull: {
vertices: [
{
x: 1650,
y: 1330,
z: -600,
},
{
x: 865,
y: 1330,
z: -600,
},
{
x: 865,
y: 430,
z: -600,
},
{
x: 1650,
y: 430,
z: -600,
},
{
x: 1650,
y: 1330,
z: -1250,
},
{
x: 865,
y: 1330,
z: -1250,
},
{
x: 865,
y: 430,
z: -1250,
},
{
x: 1650,
y: 430,
z: -1250,
},
],
},
init_pose: {
position: {
x: 0,
y: 0,
z: 0,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "Tunel",
},
motion_group_uid: 1,
},
{
id: 4,
priority: 0,
geometry: {
convex_hull: {
vertices: [
{
x: 1650,
y: -760,
z: -440,
},
{
x: 900,
y: -760,
z: -440,
},
{
x: 900,
y: -1330,
z: -440,
},
{
x: 1650,
y: -1330,
z: -440,
},
{
x: 1650,
y: -760,
z: -1800,
},
{
x: 900,
y: -760,
z: -1800,
},
{
x: 900,
y: -1330,
z: -1800,
},
{
x: 1650,
y: -1330,
z: -1800,
},
],
},
init_pose: {
position: {
x: 0,
y: 0,
z: 0,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "Fanuc controller",
},
motion_group_uid: 1,
},
{
id: 6,
priority: 0,
geometry: {
convex_hull: {
vertices: [
{
x: -200,
y: -200,
z: -1900,
},
{
x: 200,
y: -200,
z: -1900,
},
{
x: 200,
y: 200,
z: -1900,
},
{
x: -200,
y: 200,
z: -1900,
},
{
x: -200,
y: -200,
z: -350,
},
{
x: 200,
y: -200,
z: -350,
},
{
x: 200,
y: 200,
z: -350,
},
{
x: -200,
y: 200,
z: -350,
},
],
},
init_pose: {
position: {
x: 0,
y: 0,
z: 0,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "Robot base",
},
motion_group_uid: 1,
},
],
robot_model_geometries: [
{
link_index: 1,
geometry: {
sphere: {
radius: 270,
},
init_pose: {
position: {
x: -70,
y: -70,
z: -50,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "link1_sphere",
},
},
{
link_index: 2,
geometry: {
capsule: {
radius: 160,
cylinder_height: 800,
},
init_pose: {
position: {
x: -450,
y: 40,
z: 170,
},
orientation: {
x: 0,
y: -Math.SQRT1_2,
z: 0,
w: Math.SQRT1_2,
},
},
id: "link2_capsule",
},
},
{
link_index: 3,
geometry: {
sphere: {
radius: 270,
},
init_pose: {
position: {
x: -110,
y: 10,
z: -100,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "link3_sphere",
},
},
{
link_index: 4,
geometry: {
capsule: {
radius: 110,
cylinder_height: 600,
},
init_pose: {
position: {
x: 0,
y: 300,
z: 40,
},
orientation: {
x: -Math.SQRT1_2,
y: 0,
z: 0,
w: Math.SQRT1_2,
},
},
id: "link4_capsule",
},
},
{
link_index: 5,
geometry: {
sphere: {
radius: 75,
},
init_pose: {
position: {
x: 0,
y: 0,
z: -50,
},
orientation: {
x: 0,
y: 0,
z: 0,
w: 1,
},
},
id: "link5_sphere",
},
},
],
tool_geometries: [],
} satisfies SafetySetup
},
},
{
method: "GET",
path: "/cells/:cellId/coordinate-systems",
handle() {
return {
coordinatesystems: [
{
coordinate_system: "",
name: "world",
reference_uid: "",
position: {
x: 0,
y: 0,
z: 0,
},
rotation: {
angles: [0, 0, 0],
type: "ROTATION_VECTOR",
},
},
],
} //satisfies CoordinateSystems
},
},
{
method: "GET",
path: "/cells/:cellId/motion-groups/:motionGroupId/tcps",
handle() {
return {
tcps: [
{
id: "Flange",
readable_name: "Default-Flange",
position: {
x: 0,
y: 0,
z: 0,
},
rotation: {
angles: [0, 0, 0, 0],
type: "ROTATION_VECTOR",
},
},
{
id: "complex-tcp-position",
readable_name: "Complex TCP Position",
position: {
x: -200,
y: 300,
z: 150,
},
rotation: {
angles: [
-0.12139440409113832, -0.06356210998212003,
-0.2023240068185639, 0,
],
type: "ROTATION_VECTOR",
},
},
],
}
},
},
]
const method = config.method?.toUpperCase() || "GET"
const path = `/cells${config.url?.split("/cells")[1]?.split("?")[0]}`
for (const handler of apiHandlers) {
const match = pathToRegexp.match(handler.path)(path || "")
if (method === handler.method && match) {
const json = handler.handle()
return {
status: 200,
statusText: "Success",
data: JSON.stringify(json),
headers: {},
config,
request: {
responseURL: config.url,
},
}
}
}
throw new AxiosError(
`No mock handler matched this request: ${method} ${path}`,
"404",
config,
)
// return {
// status: 404,
// statusText: "Not Found",
// data: "",
// headers: {},
// config,
// request: {
// responseURL: config.url,
// },
// }
}
handleWebsocketConnection(socket: AutoReconnectingWebsocket) {
this.connections.push(socket)
setTimeout(() => {
socket.dispatchEvent(new Event("open"))
console.log("Websocket connection opened from", socket.url)
if (socket.url.includes("/state-stream")) {
socket.dispatchEvent(
new MessageEvent("message", {
data: JSON.stringify(defaultMotionState),
}),
)
}
if (socket.url.includes("/move-joint")) {
socket.dispatchEvent(
new MessageEvent("message", {
data: JSON.stringify({
result: {
motion_group: "0@ur",
state: {
controller: "ur",
operation_mode: "OPERATION_MODE_AUTO",
safety_state: "SAFETY_STATE_NORMAL",
timestamp: "2024-09-18T12:48:26.096266444Z",
velocity_override: 100,
motion_groups: [
{
motion_group: "0@ur",
controller: "ur",
joint_position: {
joints: [
1.3492152690887451, -1.5659207105636597,
1.6653711795806885, -1.0991662740707397,
-1.829018235206604, 1.264623761177063,
],
},
joint_velocity: {
joints: [0, 0, 0, 0, 0, 0],
},
flange_pose: {
position: {
x: 6.437331889439328,
y: -628.4123774830913,
z: 577.0569957147832,
},
orientation: {
x: -1.683333649797158,
y: -1.9783363827298732,
z: -0.4928031860165713,
},
coordinate_system: "",
},
tcp_pose: {
position: {
x: 6.437331889439328,
y: -628.4123774830913,
z: 577.0569957147832,
},
orientation: {
x: -1.683333649797158,
y: -1.9783363827298732,
z: -0.4928031860165713,
},
coordinate_system: "",
tcp: "Flange",
},
velocity: {
linear: {
x: 0,
y: 0,
z: 0,
},
angular: {
x: -0,
y: 0,
z: 0,
},
coordinate_system: "",
},
force: {
force: {
x: 0,
y: 0,
z: 0,
},
moment: {
x: 0,
y: 0,
z: 0,
},
coordinate_system: "",
},
joint_limit_reached: {
limit_reached: [
false,
false,
false,
false,
false,
false,
],
},
joint_current: {
joints: [0, 0, 0, 0, 0, 0],
},
sequence_number: "671259",
},
],
sequence_number: "671259",
},
movement_state: "MOVEMENT_STATE_MOVING",
},
}),
}),
)
}
if (socket.url.includes("/move-tcp")) {
socket.dispatchEvent(
new MessageEvent("message", {
data: JSON.stringify({
result: {
motion_group: "0@ur",
state: {
controller: "ur",
operation_mode: "OPERATION_MODE_AUTO",
safety_state: "SAFETY_STATE_NORMAL",
timestamp: "2024-09-18T12:43:12.188335774Z",
velocity_override: 100,
motion_groups: [
{
motion_group: "0@ur",
controller: "ur",
joint_position: {
joints: [
1.3352527618408203, -1.5659207105636597,
1.6653711795806885, -1.110615611076355,
-1.829018235206604, 1.264623761177063,
],
},
joint_velocity: {
joints: [0, 0, 0, 0, 0, 0],
},
flange_pose: {
position: {
x: -2.763015284002938,
y: -630.2151479701106,
z: 577.524509114342,
},
orientation: {
x: -1.704794877102097,
y: -1.9722372952861567,
z: -0.4852079204210754,
},
coordinate_system: "",
},
tcp_pose: {
position: {
x: -2.763015284002938,
y: -630.2151479701106,
z: 577.524509114342,
},
orientation: {
x: -1.704794877102097,
y: -1.9722372952861567,
z: -0.4852079204210754,
},
coordinate_system: "",
tcp: "Flange",
},
velocity: {
linear: {
x: 0,
y: 0,
z: 0,
},
angular: {
x: -0,
y: 0,
z: 0,
},
coordinate_system: "",
},
force: {
force: {
x: 0,
y: 0,
z: 0,
},
moment: {
x: 0,
y: 0,
z: 0,
},
coordinate_system: "",
},
joint_limit_reached: {
limit_reached: [
false,
false,
false,
false,
false,
false,
],
},
joint_current: {
joints: [0, 0, 0, 0, 0, 0],
},
sequence_number: "627897",
},
],
sequence_number: "627897",
},
movement_state: "MOVEMENT_STATE_MOVING",
},
}),
}),
)
}
}, 10)
}
handleWebsocketMessage(socket: AutoReconnectingWebsocket, message: string) {
console.log(`Received message on ${socket.url}`, message)
}
}
const defaultMotionState = {
result: {
state: {
motion_group: "0@universalrobots-ur5e",
controller: "universalrobots-ur5e",
joint_position: {
joints: [
1.1699999570846558, -1.5700000524520874, 1.3600000143051147,
1.0299999713897705, 1.2899999618530273, 1.2799999713897705,
],
},
joint_velocity: {
joints: [0, 0, 0, 0, 0, 0],
},
flange_pose: {
position: {
x: 1.3300010259703043,
y: -409.2680714682808,
z: 531.0203477065281,
},
orientation: {
x: 1.7564919306270736,
y: -1.7542521568325058,
z: 0.7326972590614671,
},
coordinate_system: "",
},
tcp_pose: {
position: {
x: 1.3300010259703043,
y: -409.2680714682808,
z: 531.0203477065281,
},
orientation: {
x: 1.7564919306270736,
y: -1.7542521568325058,
z: 0.7326972590614671,
},
coordinate_system: "",
tcp: "Flange",
},
velocity: {
linear: {
x: 0,
y: 0,
z: 0,
},
angular: {
x: 0,
y: 0,
z: 0,
},
coordinate_system: "",
},
force: {
force: {
x: 0,
y: 0,
z: 0,
},
moment: {
x: 0,
y: 0,
z: 0,
},
coordinate_system: "",
},
joint_limit_reached: {
limit_reached: [false, false, false, false, false, false],
},
joint_current: {
joints: [0, 0, 0, 0, 0, 0],
},
sequence_number: "1",
},
tcp_pose: {
position: {
x: 302.90748476115556,
y: -152.87065869452337,
z: 424.0454619321661,
},
orientation: {
x: 2.3403056115045353,
y: -1.1706836379431356,
z: 0.9772511964246311,
},
coordinate_system: "",
tcp: "Flange",
},
} satisfies MotionGroupStateResponse,
}