roslib
Version:
The standard ROS Javascript Library
1,623 lines • 220 kB
JavaScript
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