Skip to content

Commit 343c121

Browse files
authored
Statically type EventEmitter events (#1023)
This rooted out another few bits of weirdness.
1 parent 9f56947 commit 343c121

File tree

10 files changed

+86
-53
lines changed

10 files changed

+86
-53
lines changed

src/actionlib/ActionClient.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Ros from "../core/Ros.js";
77
import Topic from "../core/Topic.js";
88
import { EventEmitter } from "eventemitter3";
99
import { actionlib_msgs } from "../types/actionlib_msgs.js";
10+
import Goal from "./Goal.js";
1011

1112
/**
1213
* An actionlib action client.
@@ -22,8 +23,10 @@ export default class ActionClient<
2223
TGoal = unknown,
2324
TFeedback = unknown,
2425
TResult = unknown,
25-
> extends EventEmitter {
26-
goals = {};
26+
> extends EventEmitter<{
27+
timeout: void;
28+
}> {
29+
goals: Record<string, Goal<TGoal>> = {};
2730
/** flag to check if a status has been received */
2831
receivedStatus = false;
2932
ros: Ros;

src/actionlib/ActionListener.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ export default class ActionListener<
2323
TGoal,
2424
TFeedback,
2525
TResult,
26-
> extends EventEmitter {
26+
> extends EventEmitter<{
27+
status: actionlib_msgs.GoalStatus;
28+
feedback: [TFeedback];
29+
result: [TResult];
30+
goal: [TGoal];
31+
}> {
2732
ros: Ros;
2833
serverName: string;
2934
actionName: string;

src/actionlib/Goal.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,24 @@ import { actionlib_msgs } from "../types/actionlib_msgs";
1313
* Emits the following events:
1414
* * 'timeout' - If a timeout occurred while sending a goal.
1515
*/
16-
export default class Goal<T> extends EventEmitter {
16+
export default class Goal<
17+
TGoal,
18+
TFeedback = unknown,
19+
TResult = unknown,
20+
> extends EventEmitter<{
21+
timeout: void;
22+
status: actionlib_msgs.GoalStatus;
23+
feedback: [TFeedback];
24+
result: [TResult];
25+
}> {
1726
isFinished = false;
1827
status = undefined;
19-
result = undefined;
20-
feedback = undefined;
28+
result?: TResult = undefined;
29+
feedback?: TFeedback = undefined;
2130
// Create a random ID
2231
goalID = "goal_" + Math.random() + "_" + new Date().getTime();
23-
actionClient: ActionClient<T>;
24-
goalMessage: { goal: T; goal_id: actionlib_msgs.GoalID };
32+
actionClient: ActionClient<TGoal, TFeedback, TResult>;
33+
goalMessage: { goal: TGoal; goal_id: actionlib_msgs.GoalID };
2534
/**
2635
* @param options
2736
* @param options.actionClient - The ROSLIB.ActionClient to use with this goal.
@@ -31,8 +40,8 @@ export default class Goal<T> extends EventEmitter {
3140
actionClient,
3241
goalMessage,
3342
}: {
34-
actionClient: ActionClient<T>;
35-
goalMessage: T;
43+
actionClient: ActionClient<TGoal, TFeedback, TResult>;
44+
goalMessage: TGoal;
3645
}) {
3746
super();
3847
this.actionClient = actionClient;

src/actionlib/SimpleActionServer.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ export default class SimpleActionServer<
1919
TGoal = unknown,
2020
TFeedback = unknown,
2121
TResult = unknown,
22-
> extends EventEmitter {
22+
> extends EventEmitter<{
23+
goal: [TGoal];
24+
cancel: void;
25+
}> {
2326
// needed for handling preemption prompted by a new goal being received
2427
currentGoal: { goal: TGoal; goal_id: actionlib_msgs.GoalID } | null = null; // currently tracked goal
2528
nextGoal: { goal: TGoal; goal_id: actionlib_msgs.GoalID } | null = null; // the one this'll be preempting

src/core/Action.ts

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
* @author Sebastian Castro - sebastian.castro@picknik.ai
44
*/
55

6-
import { EventEmitter } from "eventemitter3";
76
import { GoalStatus } from "./GoalStatus.ts";
87
import {
98
isRosbridgeActionFeedbackMessage,
@@ -14,13 +13,12 @@ import Ros from "./Ros.js";
1413

1514
/**
1615
* A ROS 2 action client.
17-
* @template TGoal, TFeedback, TResult
1816
*/
1917
export default class Action<
2018
TGoal = unknown,
2119
TFeedback = unknown,
2220
TResult = unknown,
23-
> extends EventEmitter {
21+
> {
2422
isAdvertised = false;
2523
#actionCallback: ((goal: TGoal, id: string) => void) | null = null;
2624
#cancelCallback: ((id: string) => void) | null = null;
@@ -42,7 +40,6 @@ export default class Action<
4240
name: string;
4341
actionType: string;
4442
}) {
45-
super();
4643
this.ros = ros;
4744
this.name = name;
4845
this.actionType = actionType;
@@ -73,22 +70,14 @@ export default class Action<
7370

7471
if (resultCallback || failedCallback) {
7572
this.ros.on(actionGoalId, function (message) {
76-
if (message.result !== undefined && message.result === false) {
77-
if (typeof failedCallback === "function") {
78-
failedCallback(message.values);
73+
if (isRosbridgeActionResultMessage<TResult>(message)) {
74+
if (!message.result) {
75+
failedCallback?.(message.values ?? "");
76+
} else {
77+
resultCallback?.(message.values);
7978
}
80-
} else if (
81-
isRosbridgeActionFeedbackMessage(message) &&
82-
typeof feedbackCallback === "function"
83-
) {
84-
// @ts-expect-error -- can't do generic type guards in this file until it's migrated to typescript
85-
feedbackCallback(message.values);
86-
} else if (
87-
isRosbridgeActionResultMessage(message) &&
88-
typeof resultCallback === "function"
89-
) {
90-
// @ts-expect-error -- can't do generic type guards in this file until it's migrated to typescript
91-
resultCallback(message.values);
79+
} else if (isRosbridgeActionFeedbackMessage<TFeedback>(message)) {
80+
feedbackCallback?.(message.values);
9281
}
9382
});
9483
}

src/core/Ros.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,14 @@ import { rosapi } from "../types/rosapi.ts";
3535
* * &#60;topicName&#62; - A message came from rosbridge with the given topic name.
3636
* * &#60;serviceID&#62; - A service response came from rosbridge with the given ID.
3737
*/
38-
export default class Ros extends EventEmitter {
38+
export default class Ros extends EventEmitter<
39+
{
40+
error: [string];
41+
connection: [Event];
42+
close: [Event];
43+
// Any dynamically-named event should correspond to a rosbridge protocol message
44+
} & Record<string, [RosbridgeMessage]>
45+
> {
3946
/** @type {import('./SocketAdapter.js').default | null} */
4047
socket: import("./SocketAdapter.js").default | null = null;
4148
idCounter = 0;
@@ -131,7 +138,7 @@ export default class Ros extends EventEmitter {
131138
this.emit("close", event);
132139
},
133140
onError: (event) => {
134-
this.emit("error", event);
141+
this.emit("error", String(event));
135142
},
136143
onMessage: (message) => {
137144
this.#handleMessage(message);
@@ -146,7 +153,7 @@ export default class Ros extends EventEmitter {
146153
*/
147154
#handleMessage(message: RosbridgeMessage) {
148155
if (isRosbridgePublishMessage(message)) {
149-
this.emit(message.topic, message.msg);
156+
this.emit(message.topic, message);
150157
} else if (isRosbridgeServiceResponseMessage(message)) {
151158
if (message.id) {
152159
this.emit(message.id, message);

src/core/Service.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { EventEmitter } from "eventemitter3";
77
import {
8+
isRosbridgeServiceResponseMessage,
89
RosbridgeCallServiceMessage,
910
RosbridgeServiceResponseMessage,
1011
} from "../types/protocol.ts";
@@ -79,12 +80,12 @@ export default class Service<TRequest, TResponse> extends EventEmitter {
7980

8081
if (callback || failedCallback) {
8182
this.ros.once(serviceCallId, function (message) {
82-
if (message.result !== undefined && message.result === false) {
83-
if (typeof failedCallback === "function") {
84-
failedCallback(message.values);
83+
if (isRosbridgeServiceResponseMessage<TResponse>(message)) {
84+
if (!message.result) {
85+
failedCallback?.(message.values ?? "");
86+
} else {
87+
callback?.(message.values);
8588
}
86-
} else if (typeof callback === "function") {
87-
callback(message.values);
8889
}
8990
});
9091
}

src/core/Topic.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import Service from "./Service.js";
88
import Ros from "./Ros.js";
99
import {
1010
RosbridgeAdvertiseMessage,
11+
RosbridgePublishMessage,
1112
RosbridgeSubscribeMessage,
1213
} from "../types/protocol.ts";
1314
import { rosapi } from "../types/rosapi.ts";
@@ -19,7 +20,12 @@ import { rosapi } from "../types/rosapi.ts";
1920
* * 'warning' - If there are any warning during the Topic creation.
2021
* * 'message' - The message data from rosbridge.
2122
*/
22-
export default class Topic<T> extends EventEmitter {
23+
export default class Topic<T> extends EventEmitter<{
24+
message: [T];
25+
warning: [string];
26+
unsubscribe: void;
27+
unadvertise: void;
28+
}> {
2329
waitForReconnect: boolean | undefined = undefined;
2430
reconnectFunc: (() => void) | undefined = undefined;
2531
isAdvertised = false;
@@ -125,8 +131,8 @@ export default class Topic<T> extends EventEmitter {
125131
}
126132
}
127133

128-
#messageCallback = (data: T) => {
129-
this.emit("message", data);
134+
#messageCallback = (data: RosbridgePublishMessage<T>) => {
135+
this.emit("message", data.msg);
130136
};
131137
/**
132138
* Every time a message is published for the given topic, the callback

src/tf/BaseTFClient.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import Transform from "../math/Transform.js";
2-
import { EventEmitter } from "eventemitter3";
32
import Ros from "../core/Ros.js";
43
import { tf2_msgs } from "../types/tf2_msgs.js";
54

65
/**
76
* Base class for TF Clients that provides common functionality.
87
*/
9-
export default class BaseTFClient extends EventEmitter {
8+
export default class BaseTFClient {
109
frameInfos: Record<
1110
string,
1211
{ transform?: Transform; cbs: ((tf: Transform) => void)[] }
@@ -55,8 +54,6 @@ export default class BaseTFClient extends EventEmitter {
5554
topicTimeout?: number;
5655
serverName?: string;
5756
}) {
58-
super();
59-
6057
this.ros = ros;
6158
this.fixedFrame = fixedFrame;
6259
this.angularThres = angularThres;

src/types/protocol.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,9 +187,9 @@ export type RosbridgeServiceResponseMessage<TValues = undefined> =
187187
| FailedRosbridgeServiceResponseMessage
188188
| SuccessfulRosbridgeServiceResponseMessage<TValues>;
189189

190-
export function isRosbridgeServiceResponseMessage(
190+
export function isRosbridgeServiceResponseMessage<T>(
191191
message: RosbridgeMessage,
192-
): message is RosbridgeServiceResponseMessage {
192+
): message is RosbridgeServiceResponseMessage<T> {
193193
return message.op === "service_response";
194194
}
195195

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

263-
export interface RosbridgeActionResultMessage<TResultValues = unknown>
264-
extends RosbridgeMessage {
263+
interface RosbridgeActionResultMessageBase extends RosbridgeMessage {
265264
op: "action_result";
266265
id: string;
267266
action: string;
268-
values: TResultValues;
269267
status: number;
270-
result: boolean;
271268
}
272269

273-
export function isRosbridgeActionResultMessage(
270+
interface FailedRosbridgeActionResultMessage
271+
extends RosbridgeActionResultMessageBase {
272+
result: false;
273+
values?: string;
274+
}
275+
276+
interface SuccessfulRosbridgeActionResultMessage<TResultValues = unknown>
277+
extends RosbridgeActionResultMessageBase {
278+
values: TResultValues;
279+
result: true;
280+
}
281+
282+
export type RosbridgeActionResultMessage<TResultValues = unknown> =
283+
| FailedRosbridgeActionResultMessage
284+
| SuccessfulRosbridgeActionResultMessage<TResultValues>;
285+
286+
export function isRosbridgeActionResultMessage<TResultValues = unknown>(
274287
message: RosbridgeMessage,
275-
): message is RosbridgeActionResultMessage {
288+
): message is RosbridgeActionResultMessage<TResultValues> {
276289
return message.op === "action_result";
277290
}
278291

0 commit comments

Comments
 (0)