Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/actionlib/ActionClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Ros from "../core/Ros.js";
import Topic from "../core/Topic.js";
import { EventEmitter } from "eventemitter3";
import { actionlib_msgs } from "../types/actionlib_msgs.js";
import Goal from "./Goal.js";

/**
* An actionlib action client.
Expand All @@ -22,8 +23,10 @@ export default class ActionClient<
TGoal = unknown,
TFeedback = unknown,
TResult = unknown,
> extends EventEmitter {
goals = {};
> extends EventEmitter<{
timeout: void;
}> {
goals: Record<string, Goal<TGoal>> = {};
/** flag to check if a status has been received */
receivedStatus = false;
ros: Ros;
Expand Down
7 changes: 6 additions & 1 deletion src/actionlib/ActionListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ export default class ActionListener<
TGoal,
TFeedback,
TResult,
> extends EventEmitter {
> extends EventEmitter<{
status: actionlib_msgs.GoalStatus;
feedback: [TFeedback];
result: [TResult];
goal: [TGoal];
}> {
ros: Ros;
serverName: string;
actionName: string;
Expand Down
23 changes: 16 additions & 7 deletions src/actionlib/Goal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,24 @@ import { actionlib_msgs } from "../types/actionlib_msgs";
* Emits the following events:
* * 'timeout' - If a timeout occurred while sending a goal.
*/
export default class Goal<T> extends EventEmitter {
export default class Goal<
TGoal,
TFeedback = unknown,
TResult = unknown,
> extends EventEmitter<{
timeout: void;
status: actionlib_msgs.GoalStatus;
feedback: [TFeedback];
result: [TResult];
}> {
isFinished = false;
status = undefined;
result = undefined;
feedback = undefined;
result?: TResult = undefined;
feedback?: TFeedback = undefined;
// Create a random ID
goalID = "goal_" + Math.random() + "_" + new Date().getTime();
actionClient: ActionClient<T>;
goalMessage: { goal: T; goal_id: actionlib_msgs.GoalID };
actionClient: ActionClient<TGoal, TFeedback, TResult>;
goalMessage: { goal: TGoal; goal_id: actionlib_msgs.GoalID };
/**
* @param options
* @param options.actionClient - The ROSLIB.ActionClient to use with this goal.
Expand All @@ -31,8 +40,8 @@ export default class Goal<T> extends EventEmitter {
actionClient,
goalMessage,
}: {
actionClient: ActionClient<T>;
goalMessage: T;
actionClient: ActionClient<TGoal, TFeedback, TResult>;
goalMessage: TGoal;
}) {
super();
this.actionClient = actionClient;
Expand Down
5 changes: 4 additions & 1 deletion src/actionlib/SimpleActionServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export default class SimpleActionServer<
TGoal = unknown,
TFeedback = unknown,
TResult = unknown,
> extends EventEmitter {
> extends EventEmitter<{
goal: [TGoal];
cancel: void;
}> {
// needed for handling preemption prompted by a new goal being received
currentGoal: { goal: TGoal; goal_id: actionlib_msgs.GoalID } | null = null; // currently tracked goal
nextGoal: { goal: TGoal; goal_id: actionlib_msgs.GoalID } | null = null; // the one this'll be preempting
Expand Down
27 changes: 8 additions & 19 deletions src/core/Action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* @author Sebastian Castro - [email protected]
*/

import { EventEmitter } from "eventemitter3";
import { GoalStatus } from "./GoalStatus.ts";
import {
isRosbridgeActionFeedbackMessage,
Expand All @@ -14,13 +13,12 @@ import Ros from "./Ros.js";

/**
* A ROS 2 action client.
* @template TGoal, TFeedback, TResult
*/
export default class Action<
TGoal = unknown,
TFeedback = unknown,
TResult = unknown,
> extends EventEmitter {
> {
isAdvertised = false;
#actionCallback: ((goal: TGoal, id: string) => void) | null = null;
#cancelCallback: ((id: string) => void) | null = null;
Expand All @@ -42,7 +40,6 @@ export default class Action<
name: string;
actionType: string;
}) {
super();
this.ros = ros;
this.name = name;
this.actionType = actionType;
Expand Down Expand Up @@ -73,22 +70,14 @@ export default class Action<

if (resultCallback || failedCallback) {
this.ros.on(actionGoalId, function (message) {
if (message.result !== undefined && message.result === false) {
if (typeof failedCallback === "function") {
failedCallback(message.values);
if (isRosbridgeActionResultMessage<TResult>(message)) {
if (!message.result) {
failedCallback?.(message.values ?? "");
} else {
resultCallback?.(message.values);
}
} else if (
isRosbridgeActionFeedbackMessage(message) &&
typeof feedbackCallback === "function"
) {
// @ts-expect-error -- can't do generic type guards in this file until it's migrated to typescript
feedbackCallback(message.values);
} else if (
isRosbridgeActionResultMessage(message) &&
typeof resultCallback === "function"
) {
// @ts-expect-error -- can't do generic type guards in this file until it's migrated to typescript
resultCallback(message.values);
} else if (isRosbridgeActionFeedbackMessage<TFeedback>(message)) {
feedbackCallback?.(message.values);
}
});
}
Expand Down
13 changes: 10 additions & 3 deletions src/core/Ros.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,14 @@ import { rosapi } from "../types/rosapi.ts";
* * &#60;topicName&#62; - A message came from rosbridge with the given topic name.
* * &#60;serviceID&#62; - A service response came from rosbridge with the given ID.
*/
export default class Ros extends EventEmitter {
export default class Ros extends EventEmitter<
{
error: [string];
connection: [Event];
close: [Event];
// Any dynamically-named event should correspond to a rosbridge protocol message
} & Record<string, [RosbridgeMessage]>
> {
/** @type {import('./SocketAdapter.js').default | null} */
socket: import("./SocketAdapter.js").default | null = null;
idCounter = 0;
Expand Down Expand Up @@ -131,7 +138,7 @@ export default class Ros extends EventEmitter {
this.emit("close", event);
},
onError: (event) => {
this.emit("error", event);
this.emit("error", String(event));
},
onMessage: (message) => {
this.#handleMessage(message);
Expand All @@ -146,7 +153,7 @@ export default class Ros extends EventEmitter {
*/
#handleMessage(message: RosbridgeMessage) {
if (isRosbridgePublishMessage(message)) {
this.emit(message.topic, message.msg);
this.emit(message.topic, message);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly curious about this one -- was it a bug?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a bug as much as a weird layer of indirection - you can see in Topic that it was (internally, not exposed to users) behaving slightly differently than the other classes. It was handling just the inner msg member instead of the whole message from rosbridge. By moving the place where .msg was being accessed, the API became consistent.

} else if (isRosbridgeServiceResponseMessage(message)) {
if (message.id) {
this.emit(message.id, message);
Expand Down
11 changes: 6 additions & 5 deletions src/core/Service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { EventEmitter } from "eventemitter3";
import {
isRosbridgeServiceResponseMessage,
RosbridgeCallServiceMessage,
RosbridgeServiceResponseMessage,
} from "../types/protocol.ts";
Expand Down Expand Up @@ -79,12 +80,12 @@ export default class Service<TRequest, TResponse> extends EventEmitter {

if (callback || failedCallback) {
this.ros.once(serviceCallId, function (message) {
if (message.result !== undefined && message.result === false) {
if (typeof failedCallback === "function") {
failedCallback(message.values);
if (isRosbridgeServiceResponseMessage<TResponse>(message)) {
if (!message.result) {
failedCallback?.(message.values ?? "");
} else {
callback?.(message.values);
}
} else if (typeof callback === "function") {
callback(message.values);
}
});
}
Expand Down
12 changes: 9 additions & 3 deletions src/core/Topic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Service from "./Service.js";
import Ros from "./Ros.js";
import {
RosbridgeAdvertiseMessage,
RosbridgePublishMessage,
RosbridgeSubscribeMessage,
} from "../types/protocol.ts";
import { rosapi } from "../types/rosapi.ts";
Expand All @@ -19,7 +20,12 @@ import { rosapi } from "../types/rosapi.ts";
* * 'warning' - If there are any warning during the Topic creation.
* * 'message' - The message data from rosbridge.
*/
export default class Topic<T> extends EventEmitter {
export default class Topic<T> extends EventEmitter<{
message: [T];
warning: [string];
unsubscribe: void;
unadvertise: void;
}> {
waitForReconnect: boolean | undefined = undefined;
reconnectFunc: (() => void) | undefined = undefined;
isAdvertised = false;
Expand Down Expand Up @@ -125,8 +131,8 @@ export default class Topic<T> extends EventEmitter {
}
}

#messageCallback = (data: T) => {
this.emit("message", data);
#messageCallback = (data: RosbridgePublishMessage<T>) => {
this.emit("message", data.msg);
};
/**
* Every time a message is published for the given topic, the callback
Expand Down
5 changes: 1 addition & 4 deletions src/tf/BaseTFClient.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import Transform from "../math/Transform.js";
import { EventEmitter } from "eventemitter3";
import Ros from "../core/Ros.js";
import { tf2_msgs } from "../types/tf2_msgs.js";

/**
* Base class for TF Clients that provides common functionality.
*/
export default class BaseTFClient extends EventEmitter {
export default class BaseTFClient {
frameInfos: Record<
string,
{ transform?: Transform; cbs: ((tf: Transform) => void)[] }
Expand Down Expand Up @@ -55,8 +54,6 @@ export default class BaseTFClient extends EventEmitter {
topicTimeout?: number;
serverName?: string;
}) {
super();

this.ros = ros;
this.fixedFrame = fixedFrame;
this.angularThres = angularThres;
Expand Down
29 changes: 21 additions & 8 deletions src/types/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,9 @@ export type RosbridgeServiceResponseMessage<TValues = undefined> =
| FailedRosbridgeServiceResponseMessage
| SuccessfulRosbridgeServiceResponseMessage<TValues>;

export function isRosbridgeServiceResponseMessage(
export function isRosbridgeServiceResponseMessage<T>(
message: RosbridgeMessage,
): message is RosbridgeServiceResponseMessage {
): message is RosbridgeServiceResponseMessage<T> {
return message.op === "service_response";
}

Expand Down Expand Up @@ -260,19 +260,32 @@ export function isRosbridgeActionFeedbackMessage<TFeedback = unknown>(
return message.op === "action_feedback";
}

export interface RosbridgeActionResultMessage<TResultValues = unknown>
extends RosbridgeMessage {
interface RosbridgeActionResultMessageBase extends RosbridgeMessage {
op: "action_result";
id: string;
action: string;
values: TResultValues;
status: number;
result: boolean;
}

export function isRosbridgeActionResultMessage(
interface FailedRosbridgeActionResultMessage
extends RosbridgeActionResultMessageBase {
result: false;
values?: string;
}

interface SuccessfulRosbridgeActionResultMessage<TResultValues = unknown>
extends RosbridgeActionResultMessageBase {
values: TResultValues;
result: true;
}

export type RosbridgeActionResultMessage<TResultValues = unknown> =
| FailedRosbridgeActionResultMessage
| SuccessfulRosbridgeActionResultMessage<TResultValues>;

export function isRosbridgeActionResultMessage<TResultValues = unknown>(
message: RosbridgeMessage,
): message is RosbridgeActionResultMessage {
): message is RosbridgeActionResultMessage<TResultValues> {
return message.op === "action_result";
}

Expand Down