UNPKG

@wandelbots/nova-js

Version:

Official JS client for the Wandelbots API

738 lines (732 loc) 20.5 kB
import { n as availableStorage, r as AutoReconnectingWebsocket, t as loginWithAuth0 } from "../../LoginWithAuth0-wQB-Sol1.mjs"; import axios, { AxiosError, isAxiosError } from "axios"; import urlJoin from "url-join"; import * as pathToRegexp from "path-to-regexp"; import { ApplicationApi, BUSInputsOutputsApi, CellApi, ControllerApi, ControllerInputsOutputsApi, JoggingApi, KinematicsApi, MotionGroupApi, MotionGroupModelsApi, StoreCollisionComponentsApi, StoreCollisionSetupsApi, StoreObjectApi, SystemApi, TrajectoryCachingApi, TrajectoryExecutionApi, TrajectoryPlanningApi, VirtualControllerApi, VirtualControllerBehaviorApi, VirtualControllerInputsOutputsApi } from "@wandelbots/nova-api/v2"; export * from "@wandelbots/nova-api/v2" //#region src/lib/v2/NovaCellAPIClient.ts /** * API client providing type-safe access to all the Nova API REST endpoints * associated with a specific cell id. */ var NovaCellAPIClient = class { constructor(cellId, opts) { this.cellId = cellId; this.opts = opts; this.system = this.withUnwrappedResponsesOnly(SystemApi); this.cell = this.withUnwrappedResponsesOnly(CellApi); this.motionGroup = this.withCellId(MotionGroupApi); this.motionGroupModels = this.withCellId(MotionGroupModelsApi); this.controller = this.withCellId(ControllerApi); this.controllerIOs = this.withCellId(ControllerInputsOutputsApi); this.trajectoryPlanning = this.withCellId(TrajectoryPlanningApi); this.trajectoryExecution = this.withCellId(TrajectoryExecutionApi); this.trajectoryCaching = this.withCellId(TrajectoryCachingApi); this.application = this.withCellId(ApplicationApi); this.applicationGlobal = this.withUnwrappedResponsesOnly(ApplicationApi); this.jogging = this.withCellId(JoggingApi); this.kinematics = this.withCellId(KinematicsApi); this.busInputsOutputs = this.withCellId(BUSInputsOutputsApi); this.virtualController = this.withCellId(VirtualControllerApi); this.virtualControllerBehavior = this.withCellId(VirtualControllerBehaviorApi); this.virtualControllerIOs = this.withCellId(VirtualControllerInputsOutputsApi); this.storeObject = this.withCellId(StoreObjectApi); this.storeCollisionComponents = this.withCellId(StoreCollisionComponentsApi); this.storeCollisionSetups = this.withCellId(StoreCollisionSetupsApi); } /** * Some TypeScript sorcery which alters the API class methods so you don't * have to pass the cell id to every single one, and de-encapsulates the * response data */ withCellId(ApiConstructor) { const apiClient = new ApiConstructor({ ...this.opts, isJsonMime: (mime) => { return mime === "application/json"; } }, this.opts.basePath ?? "", this.opts.axiosInstance ?? axios.create()); for (const key of Reflect.ownKeys(Reflect.getPrototypeOf(apiClient))) if (key !== "constructor" && typeof apiClient[key] === "function") { const originalFunction = apiClient[key]; apiClient[key] = (...args) => { return originalFunction.apply(apiClient, [this.cellId, ...args]).then((res) => res.data); }; } return apiClient; } /** * As withCellId, but only does the response unwrapping */ withUnwrappedResponsesOnly(ApiConstructor) { const apiClient = new ApiConstructor({ ...this.opts, isJsonMime: (mime) => { return mime === "application/json"; } }, this.opts.basePath ?? "", this.opts.axiosInstance ?? axios.create()); for (const key of Reflect.ownKeys(Reflect.getPrototypeOf(apiClient))) if (key !== "constructor" && typeof apiClient[key] === "function") { const originalFunction = apiClient[key]; apiClient[key] = (...args) => { return originalFunction.apply(apiClient, args).then((res) => res.data); }; } return apiClient; } }; //#endregion //#region src/lib/v2/mock/MockNovaInstance.ts /** * Ultra-simplified mock Nova server for testing stuff */ var MockNovaInstance = class { constructor() { this.connections = []; } async handleAPIRequest(config) { const apiHandlers = [ { method: "GET", path: "/cells/:cellId/controllers", handle() { return ["mock-ur5e"]; } }, { method: "GET", path: "/cells/:cellId/controllers/:controllerId", handle() { return { configuration: { initial_joint_position: "[0,-1.571,-1.571,-1.571,1.571,-1.571,0]", kind: "VirtualController", manufacturer: "universalrobots", type: "universalrobots-ur5e" }, name: "mock-ur5" }; } }, { method: "GET", path: "/cells/:cellId/controllers/:controllerId/state", handle() { return { mode: "MODE_CONTROL", last_error: [], timestamp: "2025-10-16T09:19:26.634534092Z", sequence_number: 1054764, controller: "mock-ur5e", operation_mode: "OPERATION_MODE_AUTO", safety_state: "SAFETY_STATE_NORMAL", velocity_override: 100, motion_groups: [{ timestamp: "2025-10-16T09:19:26.634534092Z", sequence_number: 1054764, motion_group: "0@mock-ur5e", controller: "mock-ur5e", joint_position: [ 1.487959623336792, -1.8501918315887451, 1.8003005981445312, 6.034560203552246, 1.4921919107437134, 1.593459963798523 ], joint_limit_reached: { limit_reached: [ false, false, false, false, false, false ] }, joint_torque: [], joint_current: [ 0, 0, 0, 0, 0, 0 ], flange_pose: { position: [ 107.6452433732927, -409.0402987746852, 524.2402132330305 ], orientation: [ .9874434028353319, -.986571714997442, 1.3336589451098142 ] }, tcp: "Flange", tcp_pose: { position: [ 107.6452433732927, -409.0402987746852, 524.2402132330305 ], orientation: [ .9874434028353319, -.986571714997442, 1.3336589451098142 ] }, payload: "", coordinate_system: "", standstill: true }] }; } }, { method: "GET", path: "/cells/:cellId/controllers/:controllerId/motion-groups/:motionGroupId/description", handle() { return { motion_group_model: "UniversalRobots_UR5e", mounting: { position: [ 0, 0, 0 ], orientation: [ 0, 0, 0 ] }, tcps: { Flange: { name: "Default-Flange", pose: { position: [ 0, 0, 0 ], orientation: [ 0, 0, 0 ] } } }, payloads: { "FPay-0": { name: "FPay-0", payload: 0, center_of_mass: [ 0, 0, 0 ], moment_of_inertia: [ 0, 0, 0 ] } }, cycle_time: 8, dh_parameters: [ { alpha: 1.5707963267948966, d: 162.25 }, { a: -425 }, { a: -392.2 }, { alpha: 1.5707963267948966, d: 133.3 }, { alpha: -1.5707963267948966, d: 99.7 }, { d: 99.6 } ], operation_limits: { auto_limits: { joints: [ { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 } ], tcp: { velocity: 5e3 }, elbow: { velocity: 5e3 }, flange: { velocity: 5e3 } }, manual_limits: { joints: [ { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 } ], tcp: { velocity: 5e3 } }, manual_t1_limits: { joints: [ { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 } ], tcp: { velocity: 5e3 } }, manual_t2_limits: { joints: [ { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 150 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 }, { position: { lower_limit: -6.283185307179586, upper_limit: 6.283185307179586 }, velocity: 3.34159255027771, acceleration: 40, torque: 28 } ], tcp: { velocity: 5e3 } } }, serial_number: "WBVirtualRobot" }; } }, { method: "GET", path: "/cells/:cellId/controllers/:controllerId/coordinate-systems", handle() { return [{ coordinate_system: "", name: "world", reference_coordinate_system: "", position: [ 0, 0, 0 ], orientation: [ 0, 0, 0 ], orientation_type: "ROTATION_VECTOR" }, { coordinate_system: "CS-0", name: "Default-CS", reference_coordinate_system: "", position: [ 0, 0, 0 ], orientation: [ 0, 0, 0 ], orientation_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); } handleWebsocketConnection(socket) { 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("/execution/jogging")) socket.dispatchEvent(new MessageEvent("message", { data: JSON.stringify({ result: { message: "string", kind: "INITIALIZE_RECEIVED" } }) })); }, 10); } handleWebsocketMessage(socket, message) { console.log(`Received message on ${socket.url}`, message); } }; const defaultMotionState = { result: { motion_group: "0@universalrobots-ur5e", controller: "universalrobots-ur5e", timestamp: (/* @__PURE__ */ new Date()).toISOString(), sequence_number: 1, joint_position: [ 1.1699999570846558, -1.5700000524520874, 1.3600000143051147, 1.0299999713897705, 1.2899999618530273, 1.2799999713897705 ], joint_limit_reached: { limit_reached: [ false, false, false, false, false, false ] }, standstill: false, flange_pose: { position: [ 1.3300010259703043, -409.2680714682808, 531.0203477065281 ], orientation: [ 1.7564919306270736, -1.7542521568325058, .7326972590614671 ] }, tcp_pose: { position: [ 1.3300010259703043, -409.2680714682808, 531.0203477065281 ], orientation: [ 1.7564919306270736, -1.7542521568325058, .7326972590614671 ] } } }; //#endregion //#region src/lib/v2/NovaClient.ts function permissiveInstanceUrlParse(url) { if (!url.startsWith("http")) url = `http://${url}`; return new URL(url).toString(); } /** * * Client for connecting to a Nova instance and controlling robots. */ var NovaClient = class { constructor(config) { this.authPromise = null; this.accessToken = null; const cellId = config.cellId ?? "cell"; this.config = { cellId, ...config }; this.accessToken = config.accessToken || availableStorage.getString("wbjs.access_token") || null; if (this.config.instanceUrl === "https://mock.example.com") this.mock = new MockNovaInstance(); else this.config.instanceUrl = permissiveInstanceUrlParse(this.config.instanceUrl); const axiosInstance = axios.create({ baseURL: urlJoin(this.config.instanceUrl, "/api/v2"), headers: typeof window !== "undefined" && window.location.origin.includes("localhost") ? {} : { "X-Wandelbots-Client": "Wandelbots-Nova-JS-SDK" } }); axiosInstance.interceptors.request.use(async (request) => { if (!request.headers.Authorization) { if (this.accessToken) request.headers.Authorization = `Bearer ${this.accessToken}`; else if (this.config.username && this.config.password) request.headers.Authorization = `Basic ${btoa(`${config.username}:${config.password}`)}`; } return request; }); if (typeof window !== "undefined") axiosInstance.interceptors.response.use((r) => r, async (error) => { if (isAxiosError(error)) { if (error.response?.status === 401) try { await this.renewAuthentication(); if (error.config) { if (this.accessToken) error.config.headers.Authorization = `Bearer ${this.accessToken}`; else delete error.config.headers.Authorization; return axiosInstance.request(error.config); } } catch (err) { return Promise.reject(err); } else if (error.response?.status === 503) { if ((await fetch(window.location.href)).status === 503) window.location.reload(); } } return Promise.reject(error); }); this.api = new NovaCellAPIClient(cellId, { ...config, basePath: urlJoin(this.config.instanceUrl, "/api/v2"), isJsonMime: (mime) => { return mime === "application/json"; }, baseOptions: { ...this.mock ? { adapter: (config$1) => { return this.mock.handleAPIRequest(config$1); } } : {}, ...config.baseOptions }, axiosInstance }); } async renewAuthentication() { if (this.authPromise) return; this.authPromise = loginWithAuth0(this.config.instanceUrl); try { this.accessToken = await this.authPromise; if (this.accessToken) availableStorage.setString("wbjs.access_token", this.accessToken); else availableStorage.delete("wbjs.access_token"); } finally { this.authPromise = null; } } makeWebsocketURL(path) { const url = new URL(urlJoin(this.config.instanceUrl, `/api/v2/cells/${this.config.cellId}`, path)); url.protocol = url.protocol.replace("http", "ws"); url.protocol = url.protocol.replace("https", "wss"); if (this.accessToken) url.searchParams.append("token", this.accessToken); else if (this.config.username && this.config.password) { url.username = this.config.username; url.password = this.config.password; } return url.toString(); } /** * Retrieve an AutoReconnectingWebsocket to the given path on the Nova instance. * If you explicitly want to reconnect an existing websocket, call `reconnect` * on the returned object. */ openReconnectingWebsocket(path) { return new AutoReconnectingWebsocket(this.makeWebsocketURL(path), { mock: this.mock }); } }; //#endregion //#region src/lib/v2/wandelscriptUtils.ts /** * Convert a Pose object representing a motion group position * into a string which represents that pose in Wandelscript. */ function poseToWandelscriptString(pose) { const position = [ pose.position?.[0] ?? 0, pose.position?.[1] ?? 0, pose.position?.[2] ?? 0 ]; const orientation = [ pose.orientation?.[0] ?? 0, pose.orientation?.[1] ?? 0, pose.orientation?.[2] ?? 0 ]; const positionValues = position.map((v) => v.toFixed(1)); const rotationValues = orientation.map((v) => v.toFixed(4)); return `(${positionValues.concat(rotationValues).join(", ")})`; } //#endregion export { NovaCellAPIClient, NovaClient, poseToWandelscriptString }; //# sourceMappingURL=index.mjs.map