UNPKG

roslib

Version:

The standard ROS Javascript Library

1,623 lines 220 kB
import { EventEmitter as ke } from "eventemitter3"; import { v4 as nt } from "uuid"; import { deserialize as $t } from "bson"; import { decode as jt } from "cbor2"; import { decode as Wt } from "fast-png"; function ut(n) { return n instanceof Object && "op" in n && typeof n.op == "string"; } function Qt(n) { return n.op === "status"; } function _r(n) { return n.op === "set_level"; } function Jt(n) { return n.op === "fragment"; } function Zt(n) { return n.op === "png"; } function Sr(n) { return n.op === "advertise"; } function Or(n) { return n.op === "unadvertise"; } function Bt(n) { return n.op === "publish"; } function Rr(n) { return n.op === "subscribe"; } function Mr(n) { return n.op === "unsubscribe"; } function Ir(n) { return n.op === "advertise_service"; } function xr(n) { return n.op === "unadvertise_service"; } function At(n) { return n.op === "call_service"; } function Ft(n) { return n.op === "service_response"; } function Br(n) { return n.op === "advertise_action"; } function Fr(n) { return n.op === "unadvertise_action"; } function Lt(n) { return n.op === "send_action_goal"; } function Pt(n) { return n.op === "cancel_action_goal"; } function kt(n) { return n.op === "action_feedback"; } function Ut(n) { return n.op === "action_result"; } class re extends ke { /** * @param options * @param options.ros - The ROSLIB.Ros connection handle. * @param options.name - The service name, like '/add_two_ints'. * @param options.serviceType - The service type, like 'rospy_tutorials/AddTwoInts'. */ constructor({ ros: t, name: u, serviceType: s }) { super(), this.#e = null, this.isAdvertised = !1, this.#t = Promise.resolve(), this.#r = !1, this.ros = t, this.name = u, this.serviceType = s; } #e; #t; #r; /** * Call the service. Returns the service response in the * callback. Does nothing if this service is currently advertised. * * @param request - The service request to send. * @param [callback] - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: * @param [timeout] - Optional timeout, in seconds, for the service call. A non-positive value means no timeout. * If not provided, the rosbridge server will use its default value. */ callService(t, u, s = console.error, c) { if (this.isAdvertised) return; const o = `call_service:${this.name}:${nt()}`; this.ros.once(o, function(h) { Ft(h) && (h.result ? u?.(h.values) : s(h.values ?? "")); }), this.ros.callOnConnection({ op: "call_service", id: o, service: this.name, args: t, timeout: c }); } /** * Advertise the service. This turns the Service object from a client * into a server. The callback will be called with every request * that's made on this service. * * @param callback This works similarly to the callback for a C++ service in that you should take care not to overwrite the response object. * Instead, only modify the values within. */ async advertise(t) { return this.#t = this.#t.then(() => { this.isAdvertised && this.#u(), this.#e = (u) => { if (!At(u)) throw new Error( `Invalid message received on service channel: ${JSON.stringify(u)}` ); const s = {}; let c; try { c = t(u.args, s); } catch { c = !1; } c ? this.ros.callOnConnection({ op: "service_response", service: this.name, values: s, result: c, id: u.id }) : this.ros.callOnConnection({ op: "service_response", service: this.name, result: c, id: u.id }); }, this.ros.on(this.name, this.#e), this.ros.callOnConnection({ op: "advertise_service", type: this.serviceType, service: this.name }), this.isAdvertised = !0; }).catch((u) => { throw this.emit("error", u), u; }), this.#t; } /** * Internal method to perform unadvertisement without queueing */ #u() { if (!(!this.isAdvertised || this.#r)) { this.#r = !0; try { this.isAdvertised = !1, this.#e && (this.ros.off(this.name, this.#e), this.#e = null), this.ros.callOnConnection({ op: "unadvertise_service", service: this.name }); } finally { this.#r = !1; } } } async unadvertise() { return this.#t = this.#t.then(() => { this.#u(); }).catch((t) => { throw this.emit("error", t), t; }), this.#t; } /** * An alternate form of Service advertisement that supports a modern Promise-based interface for use with async/await. * @param callback An asynchronous callback processing the request and returning a response. */ async advertiseAsync(t) { return this.#t = this.#t.then(() => { this.isAdvertised && this.#u(), this.#e = (u) => { if (!At(u)) throw new Error( `Invalid message received on service channel: ${JSON.stringify(u)}` ); (async () => { try { this.ros.callOnConnection({ op: "service_response", service: this.name, result: !0, values: await t(u.args), id: u.id }); } catch (s) { this.ros.callOnConnection({ op: "service_response", service: this.name, result: !1, values: String(s), id: u.id }); } })().catch(console.error); }, this.ros.on(this.name, this.#e), this.ros.callOnConnection({ op: "advertise_service", type: this.serviceType, service: this.name }), this.isAdvertised = !0; }).catch((u) => { throw this.emit("error", u), u; }), this.#t; } } class fe extends ke { /** * @param options * @param options.ros - The ROSLIB.Ros connection handle. * @param options.name - The topic name, like '/cmd_vel'. * @param options.messageType - The message type, like 'std_msgs/String'. * @param [options.compression=none] - The type of compression to use, like 'png', 'cbor', or 'cbor-raw'. * @param [options.throttle_rate=0] - The rate (in ms in between messages) at which to throttle the topics. * @param [options.queue_size=100] - The queue created at bridge side for re-publishing webtopics. * @param [options.latch=false] - Latch the topic when publishing. * @param [options.queue_length=0] - The queue length at bridge side used when subscribing. * @param [options.reconnect_on_close=true] - The flag to enable resubscription and readvertisement on close event. */ constructor({ ros: t, name: u, messageType: s, compression: c = "none", throttle_rate: o = 0, latch: h = !1, queue_size: C = 100, queue_length: f = 0, reconnect_on_close: g = !0 }) { super(), this.waitForReconnect = !1, this.reconnectFunc = void 0, this.isAdvertised = !1, this.subscribeId = null, this.#e = (D) => { if (Bt(D)) this.emit("message", D.msg); else throw new Error( `Unexpected message on topic channel: ${JSON.stringify(D)}` ); }, this.ros = t, this.name = u, this.messageType = s, this.compression = c, this.throttle_rate = o, this.latch = h, this.queue_size = C, this.queue_length = f, this.reconnect_on_close = g, this.compression && this.compression !== "png" && this.compression !== "cbor" && this.compression !== "cbor-raw" && this.compression !== "none" && (this.emit( "warning", `${this.compression} compression is not supported. No compression will be used.` ), this.compression = "none"), this.throttle_rate < 0 && (this.emit( "warning", `${this.throttle_rate.toString()} is not allowed. Set to 0` ), this.throttle_rate = 0), this.reconnect_on_close ? this.callForSubscribeAndAdvertise = (D) => { this.ros.callOnConnection(D), this.waitForReconnect = !1, this.reconnectFunc = () => { this.waitForReconnect || (this.waitForReconnect = !0, this.ros.callOnConnection(D), this.ros.once("connection", () => { this.waitForReconnect = !1; })); }, this.ros.on("close", this.reconnectFunc); } : this.callForSubscribeAndAdvertise = (D) => { this.ros.callOnConnection(D); }; } #e; /** * Every time a message is published for the given topic, the callback * will be called with the message object. * * @param callback - Function with the following params: */ subscribe(t) { this.on("message", t), !this.subscribeId && (this.ros.on(this.name, this.#e), this.subscribeId = `subscribe:${this.name}:${nt()}`, this.callForSubscribeAndAdvertise({ op: "subscribe", id: this.subscribeId, type: this.messageType, topic: this.name, compression: this.compression, throttle_rate: this.throttle_rate, queue_length: this.queue_length })); } /** * Unregister as a subscriber for the topic. Unsubscribing will stop * and remove all subscribe callbacks. To remove a callback, you must * explicitly pass the callback function in. * * @param [callback] - The callback to unregister, if * provided and other listeners are registered the topic won't * unsubscribe, just stop emitting to the passed listener. */ unsubscribe(t) { t && (this.off("message", t), this.listeners("message").length) || this.subscribeId && (this.ros.off(this.name, this.#e), this.reconnect_on_close && this.ros.off("close", this.reconnectFunc), this.emit("unsubscribe"), this.ros.callOnConnection({ op: "unsubscribe", id: this.subscribeId, topic: this.name }), this.subscribeId = null); } /** * Register as a publisher for the topic. */ advertise() { this.isAdvertised || (this.advertiseId = `advertise:${this.name}:${nt()}`, this.callForSubscribeAndAdvertise({ op: "advertise", id: this.advertiseId, type: this.messageType, topic: this.name, latch: this.latch, queue_size: this.queue_size }), this.isAdvertised = !0, this.reconnect_on_close || this.ros.on("close", () => { this.isAdvertised = !1; })); } /** * Unregister as a publisher for the topic. */ unadvertise() { this.isAdvertised && (this.reconnect_on_close && this.ros.off("close", this.reconnectFunc), this.emit("unadvertise"), this.ros.callOnConnection({ op: "unadvertise", id: this.advertiseId, topic: this.name }), this.isAdvertised = !1); } /** * Publish the message. * * @param message - The message to publish. */ publish(t) { this.isAdvertised || this.advertise(), this.ros.callOnConnection({ op: "publish", id: `publish:${this.name}:${nt()}`, topic: this.name, msg: t }); } /** * Retrieves list of publishers for this topic. * * @param callback - Function with the following params: * * publishers - The list of publishers. * @param [failedCallback] - The callback function when the service call failed. */ getPublishers(t, u = console.error) { const s = new re({ ros: this.ros, name: "/rosapi/publishers", serviceType: "rosapi/Publishers" }), c = { topic: this.name }; s.callService( c, function(o) { t(o.publishers); }, function(o) { u(o); } ); } } class Kt { /** * @param options * @param options.ros - The ROSLIB.Ros connection handle. * @param options.name - The param name, like max_vel_x. */ constructor({ ros: t, name: u }) { this.ros = t, this.name = u; } /** * Fetch the value of the param. * * @param callback - The callback function. * @param [failedCallback] - The callback function when the service call failed or the parameter retrieval was unsuccessful. */ get(t, u = console.error) { const s = new re({ ros: this.ros, name: "rosapi/get_param", serviceType: "rosapi/GetParam" }), c = { name: this.name }; s.callService( c, function(o) { "successful" in o && !o.successful ? u(o.reason) : t(JSON.parse(o.value)); }, u ); } /** * Set the value of the param in ROS. * * @param value - The value to set param to. * @param [callback] - The callback function. * @param [failedCallback] - The callback function when the service call failed or the parameter setting was unsuccessful. */ set(t, u, s = console.error) { const c = new re({ ros: this.ros, name: "rosapi/set_param", serviceType: "rosapi/SetParam" }), o = { name: this.name, value: JSON.stringify(t) }; c.callService( o, function(h) { "successful" in h && !h.successful ? s(h.reason) : u && u(h); }, s ); } /** * Delete this parameter on the ROS server. * * @param callback - The callback function. * @param [failedCallback] - The callback function when the service call failed or the parameter deletion was unsuccessful. */ delete(t, u = console.error) { const s = new re({ ros: this.ros, name: "rosapi/delete_param", serviceType: "rosapi/DeleteParam" }), c = { name: this.name }; s.callService( c, function(o) { "successful" in o && !o.successful ? u(o.reason) : t(o); }, u ); } } class qt extends ke { /** * @param options * @param options.ros - The ROSLIB.Ros connection handle. * @param options.serverName - The action server name, like '/fibonacci'. * @param options.actionName - The action message name, like 'actionlib_tutorials/FibonacciAction'. * @param [options.timeout] - The timeout length when connecting to the action server. * @param [options.omitFeedback] - The flag to indicate whether to omit the feedback channel or not. * @param [options.omitStatus] - The flag to indicate whether to omit the status channel or not. * @param [options.omitResult] - The flag to indicate whether to omit the result channel or not. */ constructor({ ros: t, serverName: u, actionName: s, timeout: c, omitFeedback: o, omitStatus: h, omitResult: C }) { super(), this.goals = {}, this.receivedStatus = !1, this.ros = t, this.serverName = u, this.actionName = s, this.timeout = c, this.omitFeedback = o, this.omitStatus = h, this.omitResult = C, this.feedbackListener = new fe({ ros: this.ros, name: `${this.serverName}/feedback`, messageType: `${this.actionName}Feedback` }), this.statusListener = new fe({ ros: this.ros, name: `${this.serverName}/status`, messageType: "actionlib_msgs/GoalStatusArray" }), this.resultListener = new fe({ ros: this.ros, name: `${this.serverName}/result`, messageType: `${this.actionName}Result` }), this.goalTopic = new fe({ ros: this.ros, name: `${this.serverName}/goal`, messageType: `${this.actionName}Goal` }), this.cancelTopic = new fe({ ros: this.ros, name: `${this.serverName}/cancel`, messageType: "actionlib_msgs/GoalID" }), this.goalTopic.advertise(), this.cancelTopic.advertise(), this.omitStatus || this.statusListener.subscribe((f) => { this.receivedStatus = !0, f.status_list.forEach((g) => { const D = this.goals[g.goal_id.id]; D && D.emit("status", g); }); }), this.omitFeedback || this.feedbackListener.subscribe((f) => { const g = this.goals[f.status.goal_id.id]; g && (g.emit("status", f.status), g.emit("feedback", f.feedback)); }), this.omitResult || this.resultListener.subscribe((f) => { const g = this.goals[f.status.goal_id.id]; g && (g.emit("status", f.status), g.emit("result", f.result)); }), this.timeout && setTimeout(() => { this.receivedStatus || this.emit("timeout"); }, this.timeout); } /** * Cancel all goals associated with this ActionClient. */ cancel() { const t = {}; this.cancelTopic.publish(t); } /** * Unsubscribe and unadvertise all topics associated with this ActionClient. */ dispose() { this.goalTopic.unadvertise(), this.cancelTopic.unadvertise(), this.omitStatus || this.statusListener.unsubscribe(), this.omitFeedback || this.feedbackListener.unsubscribe(), this.omitResult || this.resultListener.unsubscribe(); } } class er extends ke { /** * @param options * @param options.actionClient - The ROSLIB.ActionClient to use with this goal. * @param options.goalMessage - The JSON object containing the goal for the action server. */ constructor({ actionClient: t, goalMessage: u }) { super(), this.isFinished = !1, this.status = void 0, this.result = void 0, this.feedback = void 0, this.goalID = `goal_${nt()}`, this.actionClient = t, this.goalMessage = { goal_id: { stamp: { secs: 0, nsecs: 0 }, id: this.goalID }, goal: u }, this.on("status", (s) => { this.status = s; }), this.on("result", (s) => { this.isFinished = !0, this.result = s; }), this.on("feedback", (s) => { this.feedback = s; }), this.actionClient.goals[this.goalID] = this; } /** * Send the goal to the action server. * * @param [timeout] - A timeout length for the goal's result. */ send(t) { this.actionClient.goalTopic.publish(this.goalMessage), t && setTimeout(() => { this.isFinished || this.emit("timeout"); }, t); } /** * Cancel the current goal. */ cancel() { const t = { id: this.goalID }; this.actionClient.cancelTopic.publish(t); } } class Oe { constructor(t) { this.x = t?.x ?? 0, this.y = t?.y ?? 0, this.z = t?.z ?? 0; } /** * Set the values of this vector to the sum of itself and the given vector. * * @param v - The vector to add with. */ add(t) { this.x += t.x, this.y += t.y, this.z += t.z; } /** * Set the values of this vector to the difference of itself and the given vector. * * @param v - The vector to subtract with. */ subtract(t) { this.x -= t.x, this.y -= t.y, this.z -= t.z; } /** * Multiply the given Quaternion with this vector. * * @param q - The quaternion to multiply with. */ multiplyQuaternion(t) { const u = t.w * this.x + t.y * this.z - t.z * this.y, s = t.w * this.y + t.z * this.x - t.x * this.z, c = t.w * this.z + t.x * this.y - t.y * this.x, o = -t.x * this.x - t.y * this.y - t.z * this.z; this.x = u * t.w + o * -t.x + s * -t.z - c * -t.y, this.y = s * t.w + o * -t.y + c * -t.x - u * -t.z, this.z = c * t.w + o * -t.z + u * -t.y - s * -t.x; } /** * Clone a copy of this vector. * * @returns The cloned vector. */ clone() { return new Oe(this); } } class Qe { constructor(t) { this.x = t?.x ?? 0, this.y = t?.y ?? 0, this.z = t?.z ?? 0, this.w = typeof t?.w == "number" ? t.w : 1; } /** * Perform a conjugation on this quaternion. */ conjugate() { this.x *= -1, this.y *= -1, this.z *= -1; } /** * Return the norm of this quaternion. */ norm() { return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); } /** * Perform a normalization on this quaternion. */ normalize() { let t = Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); t === 0 ? (this.x = 0, this.y = 0, this.z = 0, this.w = 1) : (t = 1 / t, this.x = this.x * t, this.y = this.y * t, this.z = this.z * t, this.w = this.w * t); } /** * Convert this quaternion into its inverse. */ invert() { this.conjugate(), this.normalize(); } /** * Set the values of this quaternion to the product of itself and the given quaternion. * * @param q - The quaternion to multiply with. */ multiply(t) { const u = this.x * t.w + this.y * t.z - this.z * t.y + this.w * t.x, s = -this.x * t.z + this.y * t.w + this.z * t.x + this.w * t.y, c = this.x * t.y - this.y * t.x + this.z * t.w + this.w * t.z, o = -this.x * t.x - this.y * t.y - this.z * t.z + this.w * t.w; this.x = u, this.y = s, this.z = c, this.w = o; } /** * Clone a copy of this quaternion. * * @returns The cloned quaternion. */ clone() { return new Qe(this); } } class vt { constructor(t) { this.translation = new Oe(t.translation), this.rotation = new Qe(t.rotation); } /** * Clone a copy of this transform. * * @returns The cloned transform. */ clone() { return new vt(this); } } class Gt { /** * @param options * @param options.ros - The ROSLIB.Ros connection handle. * @param [options.fixedFrame=base_link] - The fixed frame. * @param [options.angularThres=2.0] - The angular threshold for the TF republisher. * @param [options.transThres=0.01] - The translation threshold for the TF republisher. * @param [options.rate=10.0] - The rate for the TF republisher. * @param [options.updateDelay=50] - The time (in ms) to wait after a new subscription * to update the TF republisher's list of TFs. * @param [options.topicTimeout=2.0] - The timeout parameter for the TF republisher. * @param [options.serverName="/tf2_web_republisher"] - The name of the tf2_web_republisher server. */ constructor({ ros: t, fixedFrame: u = "base_link", angularThres: s = 2, transThres: c = 0.01, rate: o = 10, updateDelay: h = 50, topicTimeout: C = 2, serverName: f = "/tf2_web_republisher" }) { this.frameInfos = {}, this.republisherUpdateRequested = !1, this.ros = t, this.fixedFrame = u, this.angularThres = s, this.transThres = c, this.rate = o, this.updateDelay = h; const g = C, D = Math.floor(g), p = Math.floor((g - D) * 1e9); this.topicTimeout = { secs: D, nsecs: p }, this.serverName = f; } /** * Process the incoming TF message and send them out using the callback * functions. * * @param tf - The TF message from the server. */ processTFArray(t) { t.transforms.forEach((u) => { let s = u.child_frame_id; s.startsWith("/") && (s = s.substring(1)); const c = this.frameInfos[s]; if (c) { const o = new vt({ translation: u.transform.translation, rotation: u.transform.rotation }); c.transform = o, c.cbs.forEach((h) => { h(o); }); } }, this); } /** * Create and send a new goal (or service request) to the tf2_web_republisher * based on the current list of TFs. * This method should be overridden by subclasses. */ updateGoal() { throw new Error("updateGoal() must be implemented by subclass"); } /** * Subscribe to the given TF frame. * * @param frameID - The TF frame to subscribe to. * @param callback - Function with the following params: */ subscribe(t, u) { t.startsWith("/") && (t = t.substring(1)), this.frameInfos[t] || (this.frameInfos[t] = { cbs: [] }, this.republisherUpdateRequested || (setTimeout(() => { this.updateGoal(); }, this.updateDelay), this.republisherUpdateRequested = !0)); const s = this.frameInfos[t]?.transform; s && u(s), this.frameInfos[t]?.cbs.push(u); } /** * Unsubscribe from the given TF frame. * * @param frameID - The TF frame to unsubscribe from. * @param [callback] - The callback function to remove. */ unsubscribe(t, u) { t.startsWith("/") && (t = t.substring(1)); const s = this.frameInfos[t]; for (var c = s?.cbs ?? [], o = c.length; o--; ) c[o] === u && c.splice(o, 1); (!u || c.length === 0) && delete this.frameInfos[t]; } } class tr extends Gt { /** * @param options * @param options.ros - The ROSLIB.Ros connection handle. * @param [options.fixedFrame=base_link] - The fixed frame. * @param [options.angularThres=2.0] - The angular threshold for the TF republisher. * @param [options.transThres=0.01] - The translation threshold for the TF republisher. * @param [options.rate=10.0] - The rate for the TF republisher. * @param [options.updateDelay=50] - The time (in ms) to wait after a new subscription * to update the TF republisher's list of TFs. * @param [options.topicTimeout=2.0] - The timeout parameter for the TF republisher. * @param [options.serverName="/tf2_web_republisher"] - The name of the tf2_web_republisher server. */ constructor(t) { super(t), this.currentGoal = !1, this.currentTopic = !1, this.#e = void 0, this.#t = !1, this.actionClient = new qt({ ros: this.ros, serverName: this.serverName, actionName: "tf2_web_republisher/TFSubscriptionAction", omitStatus: !0, omitResult: !0 }); } #e; #t; /** * Create and send a new goal (or service request) to the tf2_web_republisher * based on the current list of TFs. */ updateGoal() { const t = { source_frames: Object.keys(this.frameInfos), target_frame: this.fixedFrame, angular_thres: this.angularThres, trans_thres: this.transThres, rate: this.rate }; this.currentGoal && this.currentGoal.cancel(), this.currentGoal = new er({ actionClient: this.actionClient, goalMessage: t }), this.currentGoal.on("feedback", (u) => { this.processTFArray(u); }), this.currentGoal.send(), this.republisherUpdateRequested = !1; } /** * Process the service response and subscribe to the tf republisher * topic. * * @param response - The service response containing the topic name. */ processResponse(t) { this.#t || (this.currentTopic && this.currentTopic.unsubscribe(this.#e), this.currentTopic = new fe({ ros: this.ros, name: t.topic_name, messageType: "tf2_web_republisher/TFArray" }), this.#e = (u) => { this.processTFArray(u); }, this.currentTopic.subscribe(this.#e)); } /** * Unsubscribe and unadvertise all topics associated with this TFClient. */ dispose() { this.#t = !0, this.actionClient.dispose(), this.currentTopic && this.currentTopic.unsubscribe(this.#e); } } class rr extends ke { /** * @param options * @param options.ros - The ROSLIB.Ros connection handle. * @param options.serverName - The action server name, like '/fibonacci'. * @param options.actionName - The action message name, like 'actionlib_tutorials/FibonacciAction'. */ constructor({ ros: t, serverName: u, actionName: s }) { super(), this.currentGoal = null, this.nextGoal = null, this.ros = t, this.serverName = u, this.actionName = s, this.feedbackPublisher = new fe({ ros: this.ros, name: `${this.serverName}/feedback`, messageType: `${this.actionName}Feedback` }), this.feedbackPublisher.advertise(); const c = new fe({ ros: this.ros, name: `${this.serverName}/status`, messageType: "actionlib_msgs/GoalStatusArray" }); c.advertise(), this.resultPublisher = new fe({ ros: this.ros, name: `${this.serverName}/result`, messageType: `${this.actionName}Result` }), this.resultPublisher.advertise(); const o = new fe({ ros: this.ros, name: `${this.serverName}/goal`, messageType: `${this.actionName}Goal` }), h = new fe({ ros: this.ros, name: `${this.serverName}/cancel`, messageType: "actionlib_msgs/GoalID" }); this.statusMessage = { header: { stamp: { secs: 0, nsecs: 100 }, frame_id: "" }, /** @type {{goal_id: any, status: number}[]} */ status_list: [] }, o.subscribe((f) => { this.currentGoal ? (this.nextGoal = f, this.emit("cancel")) : (this.statusMessage.status_list = [ { goal_id: f.goal_id, status: 1 } ], this.currentGoal = f, this.emit("goal", f.goal)); }); const C = function(f, g) { return f.secs > g.secs ? !1 : f.secs < g.secs ? !0 : f.nsecs < g.nsecs; }; h.subscribe((f) => { f.stamp.secs === 0 && f.stamp.nsecs === 0 && f.id === "" ? (this.nextGoal = null, this.currentGoal && this.emit("cancel")) : (f.id === this.currentGoal?.goal_id.id ? this.emit("cancel") : f.id === this.nextGoal?.goal_id.id && (this.nextGoal = null), this.nextGoal && C(this.nextGoal.goal_id.stamp, f.stamp) && (this.nextGoal = null), this.currentGoal && C(this.currentGoal.goal_id.stamp, f.stamp) && this.emit("cancel")); }), setInterval(() => { const f = /* @__PURE__ */ new Date(), g = Math.floor(f.getTime() / 1e3), D = Math.round( 1e9 * (f.getTime() / 1e3 - g) ); this.statusMessage.header = { ...this.statusMessage.header, stamp: { secs: g, nsecs: D } }, c.publish(this.statusMessage); }, 500); } /** * Set action state to succeeded and return to client. * * @param result - The result to return to the client. */ setSucceeded(t) { if (this.currentGoal !== null) { const u = { status: { goal_id: this.currentGoal.goal_id, status: 3 }, result: t }; this.resultPublisher.publish(u), this.statusMessage.status_list = [], this.nextGoal ? (this.currentGoal = this.nextGoal, this.nextGoal = null, this.emit("goal", this.currentGoal.goal)) : this.currentGoal = null; } } /** * Set action state to aborted and return to client. * * @param result - The result to return to the client. */ setAborted(t) { if (this.currentGoal !== null) { const u = { status: { goal_id: this.currentGoal.goal_id, status: 4 }, result: t }; this.resultPublisher.publish(u), this.statusMessage.status_list = [], this.nextGoal ? (this.currentGoal = this.nextGoal, this.nextGoal = null, this.emit("goal", this.currentGoal.goal)) : this.currentGoal = null; } } /** * Send a feedback message. * * @param feedback - The feedback to send to the client. */ sendFeedback(t) { if (this.currentGoal !== null) { const u = { status: { goal_id: this.currentGoal.goal_id, status: 1 }, feedback: t }; this.feedbackPublisher.publish(u); } } /** * Handle case where client requests preemption. */ setPreempted() { if (this.currentGoal !== null) { this.statusMessage.status_list = []; const t = { status: { goal_id: this.currentGoal.goal_id, status: 2 } }; this.resultPublisher.publish(t), this.nextGoal ? (this.currentGoal = this.nextGoal, this.nextGoal = null, this.emit("goal", this.currentGoal.goal)) : this.currentGoal = null; } } } const ur = async (n) => { if (typeof WebSocket == "function") { const o = await import("./NativeWebSocketTransport-CF_ebnyS.js"), { NativeWebSocketTransport: h } = o, C = new WebSocket(n); return C.binaryType = "arraybuffer", new h(C); } const t = await import("ws"), u = await import("./WsWebSocketTransport-6-v9C0gj.js"), { WsWebSocketTransport: s } = u, c = new t.WebSocket(n); return c.binaryType = "arraybuffer", new s(c); }; class Lr extends ke { // private write, public read via getter method #e; constructor({ url: t, transportFactory: u = ur } = {}) { super(), this.#e = !1, this.transportFactory = u, t && this.connect(t).catch(console.error); } get isConnected() { return this.#e; } async connect(t) { if (this.transport && !this.transport.isClosed()) return; const u = await this.transportFactory(t); this.transport = u, u.on("open", (s) => { this.#e = !0, this.emit("connection", s); }), u.on("close", (s) => { this.#e = !1, this.emit("close", s); }), u.on("error", (s) => { this.emit("error", s); }), u.on("message", (s) => { this.handleMessage(s); }); } close() { this.transport?.close(); } handleMessage(t) { Bt(t) ? this.emit(t.topic, t) : Ft(t) ? t.id ? this.emit(t.id, t) : console.error("Received service response without ID") : At(t) ? this.emit(t.service, t) : Lt(t) ? this.emit(t.action, t) : Pt(t) ? this.emit(t.id, t) : kt(t) ? this.emit(t.id, t) : Ut(t) ? this.emit(t.id, t) : Qt(t) && (t.id ? this.emit(`status:${t.id}`, t) : this.emit("status", t)); } /** * Send an authorization request to the server. * * @param mac - MAC (hash) string given by the trusted source. * @param client - IP of the client. * @param dest - IP of the destination. * @param rand - Random string given by the trusted source. * @param t - Time of the authorization request. * @param level - User level as a string given by the client. * @param end - End time of the client's session. */ authenticate(t, u, s, c, o, h, C) { this.callOnConnection({ op: "auth", mac: t, client: u, dest: s, rand: c, t: o, level: h, end: C }); } /** * Sends the message to the transport. * If not connected, queues the message to send once reconnected. */ callOnConnection(t) { this.isConnected ? this.transport?.send(t) : this.once("connection", () => { this.transport?.send(t); }); } /** * Send a set_level request to the server. * * @param level - Status level (none, error, warning, info). * @param [id] - Operation ID to change status level on. */ setStatusLevel(t, u) { const s = { op: "set_level", level: t, id: u }; this.callOnConnection(s); } /** * Retrieve a list of action servers in ROS as an array of string. * * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getActionServers(t, u = console.error) { const s = new re({ ros: this, name: "rosapi/action_servers", serviceType: "rosapi/GetActionServers" }), c = {}; s.callService( c, function(o) { t(o.action_servers); }, function(o) { u(o); } ); } /** * Retrieve a list of topics in ROS as an array. * * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getTopics(t, u = console.error) { const s = new re({ ros: this, name: "rosapi/topics", serviceType: "rosapi/Topics" }), c = {}; s.callService( c, function(o) { t(o); }, function(o) { u(o); } ); } /** * Retrieve a list of topics in ROS as an array of a specific type. * * @param topicType - The topic type to find. * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getTopicsForType(t, u, s = console.error) { const c = new re({ ros: this, name: "rosapi/topics_for_type", serviceType: "rosapi/TopicsForType" }), o = { type: t }; c.callService( o, function(h) { u(h.topics); }, function(h) { s(h); } ); } /** * Retrieve a list of active service names in ROS. * * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getServices(t, u = console.error) { const s = new re({ ros: this, name: "rosapi/services", serviceType: "rosapi/Services" }), c = {}; s.callService( c, function(o) { t(o.services); }, function(o) { u(o); } ); } /** * Retrieve a list of services in ROS as an array as specific type. * * @param serviceType - The service type to find. * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getServicesForType(t, u, s = console.error) { const c = new re({ ros: this, name: "rosapi/services_for_type", serviceType: "rosapi/ServicesForType" }), o = { type: t }; c.callService( o, function(h) { u(h.services); }, function(h) { s(h); } ); } /** * Retrieve the details of a ROS service request. * * @param type - The type of the service. * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getServiceRequestDetails(t, u, s = console.error) { const c = new re({ ros: this, name: "rosapi/service_request_details", serviceType: "rosapi/ServiceRequestDetails" }), o = { type: t }; c.callService( o, function(h) { u(h); }, function(h) { s(h); } ); } /** * Retrieve the details of a ROS service response. * * @param type - The type of the service. * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getServiceResponseDetails(t, u, s = console.error) { const c = new re({ ros: this, name: "rosapi/service_response_details", serviceType: "rosapi/ServiceResponseDetails" }), o = { type: t }; c.callService( o, function(h) { u(h); }, function(h) { s(h); } ); } /** * Retrieve a list of active node names in ROS. * * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getNodes(t, u = console.error) { const s = new re({ ros: this, name: "rosapi/nodes", serviceType: "rosapi/Nodes" }), c = {}; s.callService( c, function(o) { t(o.nodes); }, function(o) { u(o); } ); } /** * Retrieve a list of subscribed topics, publishing topics and services of a specific node. * * @param node - Name of the node. */ getNodeDetails(t, u, s = console.error) { new re({ ros: this, name: "rosapi/node_details", serviceType: "rosapi/NodeDetails" }).callService({ node: t }, u, s); } /** * Retrieve a list of parameter names from the ROS Parameter Server. * * @param callback - Function with the following params: * @param failedCallback - The callback function when the service call failed with params: */ getParams(t, u = console.error) { const s = new re({ ros: this, name: "rosapi/get_param_names", serviceType: "rosapi/GetParamNames" }), c = {}; s.callService( c, function(o) { t(o.names); }, function(o) { u(o); } ); } /** * Retrieve the type of a ROS topic. * * @param topic - Name of the topic. * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getTopicType(t, u, s = console.error) { const c = new re({ ros: this, name: "rosapi/topic_type", serviceType: "rosapi/TopicType" }), o = { topic: t }; c.callService( o, function(h) { u(h.type); }, function(h) { s(h); } ); } /** * Retrieve the type of a ROS service. * * @param service - Name of the service. * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getServiceType(t, u, s = console.error) { const c = new re({ ros: this, name: "rosapi/service_type", serviceType: "rosapi/ServiceType" }), o = { service: t }; c.callService( o, function(h) { u(h.type); }, function(h) { s(h); } ); } /** * Retrieve the details of a ROS message. * * @param message - The name of the message type. * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getMessageDetails(t, u, s = console.error) { const c = new re({ ros: this, name: "rosapi/message_details", serviceType: "rosapi/MessageDetails" }), o = { type: t }; c.callService( o, function(h) { u(h.typedefs); }, function(h) { s(h); } ); } /** * Decode a typedef array into a dictionary like `rosmsg show foo/bar`. * * @param defs - Array of type_def dictionary. */ decodeTypeDefs(t) { const u = (s, c) => { const o = {}; for (let h = 0; h < s.fieldnames.length; h++) { const C = s.fieldarraylen[h], f = s.fieldnames[h], g = s.fieldtypes[h]; if (f === void 0 || g === void 0) throw new Error( "Received mismatched type definition vector lengths!" ); if (!g.includes("/")) C === -1 ? o[f] = g : o[f] = [g]; else { let D; for (const p of c) if (p.type === g) { D = p; break; } if (D) { const p = u(D, c); C === -1 ? o[f] = p : o[f] = [p]; } else this.emit("error", `Cannot find ${g} in decodeTypeDefs`); } } return o; }; return t[0] ? u(t[0], t) : {}; } /** * @callback getTopicsAndRawTypesCallback * @param {Object} result - The result object with the following params: * @param {string[]} result.topics - Array of topic names. * @param {string[]} result.types - Array of message type names. * @param {string[]} result.typedefs_full_text - Array of full definitions of message types, similar to `gendeps --cat`. */ /** * @callback getTopicsAndRawTypesFailedCallback * @param {string} error - The error message reported by ROS. */ /** * Retrieve a list of topics and their associated type definitions. * * @param callback - Function with the following params: * @param [failedCallback] - The callback function when the service call failed with params: */ getTopicsAndRawTypes(t, u = console.error) { const s = new re({ ros: this, name: "rosapi/topics_and_raw_types", serviceType: "rosapi/TopicsAndRawTypes" }), c = {}; s.callService( c, function(o) { t(o); }, function(o) { u(o); } ); } Topic(t) { return new fe({ ros: this, ...t }); } Param(t) { return new Kt({ ros: this, ...t }); } Service(t) { return new re({ ros: this, ...t }); } TFClient(t) { return new tr({ ros: this, ...t }); } ActionClient(t) { return new qt({ ros: this, ...t }); } SimpleActionServer(t) { return new rr({ ros: this, ...t }); } } var Be = /* @__PURE__ */ ((n) => (n[n.STATUS_UNKNOWN = 0] = "STATUS_UNKNOWN", n[n.STATUS_ACCEPTED = 1] = "STATUS_ACCEPTED", n[n.STATUS_EXECUTING = 2] = "STATUS_EXECUTING", n[n.STATUS_CANCELING = 3] = "STATUS_CANCELING", n[n.STATUS_SUCCEEDED = 4] = "STATUS_SUCCEEDED", n[n.STATUS_CANCELED = 5] = "STATUS_CANCELED", n[n.STATUS_ABORTED = 6] = "STATUS_ABORTED", n))(Be || {}); class Nt extends Error { constructor(t, u) { super(`${ir(t)}${u ? `: ${u}` : ""}`), this.name = "GoalError"; } } function ir(n) { switch (n) { case Be.STATUS_CANCELED: return "Action was canceled"; case Be.STATUS_ABORTED: return "Action was aborted"; case Be.STATUS_CANCELING: return "Action is canceling"; case Be.STATUS_UNKNOWN: return "Action status unknown"; default: return `Action failed with status ${String(n)}`; } } class nr { /** * @param options * @param options.ros - The ROSLIB.Ros connection handle. * @param options.name - The action name, like '/fibonacci'. * @param options.actionType - The action type, like 'example_interfaces/Fibonacci'. */ constructor({ ros: t, name: u, actionType: s }) { this.isAdvertised = !1, this.#e = null, this.#t = null, this.ros = t, this.name = u, this.actionType = s; } #e; #t; /** * Sends an action goal. Returns the feedback in the feedback callback while the action is running * and the result in the result callback when the action is completed. * Does nothing if this action is currently advertised. * * @param goal - The action goal to send. * @param resultCallback - The callback function when the action is completed. * @param [feedbackCallback] - The callback function when the action publishes feedback. * @param [failedCallback] - The callback function when the action failed. */ sendGoal(t, u, s, c = console.error) { if (this.isAdvertised) return; const o = `send_action_goal:${this.name}:${nt()}`; return this.ros.on(o, (h) => { if (Ut(h)) { const C = h.status; h.result ? C !== Be.STATUS_SUCCEEDED ? c( String(new Nt(C, JSON.stringify(h.values))) ) : u(h.values) : c(String(new Nt(C, h.values))); } else kt(h) && s?.(h.values); }), this.ros.callOnConnection({ op: "send_action_goal", id: o, action: this.name, action_type: this.actionType, args: t, feedback: !0 }), o; } /** * Cancels an action goal. * * @param id - The ID of the action goal to cancel. */ cancelGoal(t) { this.ros.callOnConnection({ op: "cancel_action_goal", id: t, action: this.name }); } /** * Cancels all action goals. */ cancelAllGoals() { this.ros.callOnConnection({ op: "call_service", service: `${this.name}/_action/cancel_goal`, args: {} }); } /** * Advertise the action. This turns the Action object from a client * into a server. The callback will be called with every goal sent to this action. * * @param actionCallback - This works similarly to the callback for a C++ action. * @param cancelCallback - A callback function to execute when the action is canceled. */ advertise(t, u) { this.isAdvertised || typeof t != "function" || (this.#e = t, this.#t = u, this.ros.on(this.name, (s) => { if (Lt(s)) this.#r(s); else throw new Error( "Received unrelated message on Action server event stream!" ); }), this.ros.callOnConnection({ op: "advertise_action", type: this.actionType, action: this.name }), this.isAdvertised = !0); } /** * Unadvertise a previously advertised action. */ unadvertise() { this.isAdvertised && (this.ros.callOnConnection({ op: "unadvertise_action", action: this.name }), this.isAdvertised = !1); } /** * Helper function that executes an action by calling the provided * action callback with the auto-generated ID as a user-accessible input. * Should not be called manually. * * @param rosbridgeRequest - The rosbridge request containing the action goal to send and its ID. * @param rosbridgeRequest.id - The ID of the action goal. * @param rosbridgeRequest.args - The arguments of the action goal. */ #r(t) { const u = t.id; if (typeof u == "string" && this.ros.on(u, (s) => { Pt(s) && this.#t && this.#t(u); }), this.#e) if (t.args) this.#e(t.args, u); else throw new Error( "Received Action goal with no arguments! This should never happen, because rosbridge should fill in blanks!" ); } /** * Helper function to send action feedback inside an action handler. * * @param id - The action goal ID. * @param feedback - The feedback to send. */ sendFeedback(t, u) { this.ros.callOnConnection({ op: "action_feedback", id: t, action: this.name, values: u }); } /** * Helper function to set an action as succeeded. * * @param id - The action goal ID. * @param result - The result to set. */ setSucceeded(t, u) { this.ros.callOnConnection({ op: "action_result", id: t, action: this.name, values: u, status: Be.STATUS_SUCCEEDED, result: !0 }); } /** * Helper function to set an action as canceled. * * @param id - The action goal ID. * @param result - The result to set. */ setCanceled(t, u) { this.ros.callOnConnection({ op: "action_result", id: t, action: this.name, values: u, status: Be.STATUS_CANCELED, result: !0 }); } /** * Helper function to set an action as failed. * * @param id - The action goal ID. */ setFailed(t) { this.ros.callOnConnection({ op: "action_result", id: t, action: this.name, status: Be.STATUS_ABORTED, result: !1 }); } } const sr = new TextDecoder(); function ar(n) { const t = Uint8Array.from(atob(n), (s) => s.charCodeAt(0)), u = or(t); try { return JSON.parse(sr.decode(u.data)); } catch (s) { throw new Error("Error parsing PNG JSON contents", { cause: s }); } } function or(n) { try { return Wt(n); } catch (t) { throw new Error("Error decoding PNG buffer", { cause: t }); } } class Pr extends ke { /** * Buffer Map for incoming message fragments. */ #e = /* @__PURE__ */ new Map(); /** * Decodes a raw message received from the transport * and emits it as a RosbridgeMessage over the "message" event. * If an error occurs, it is emitted