roslib-ts 0.1.1 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -59,13 +59,11 @@ class EventEmitter {
59
59
  }
60
60
 
61
61
  class Ros extends EventEmitter {
62
- // private reconnectDelay = 1000;
63
62
  constructor(options = {}) {
64
63
  super();
65
64
  this.socket = null;
66
65
  this._isConnected = false;
67
66
  this.idCounter = 0;
68
- this.reconnectTimer = null;
69
67
  this.options = options;
70
68
  if (options.url) {
71
69
  this.connect(options.url);
@@ -88,11 +86,6 @@ class Ros extends EventEmitter {
88
86
  this.socket.onopen = () => {
89
87
  this._isConnected = true;
90
88
  this.emit('connection');
91
- // 清除重连定时器
92
- if (this.reconnectTimer) {
93
- clearTimeout(this.reconnectTimer);
94
- this.reconnectTimer = null;
95
- }
96
89
  };
97
90
  this.socket.onclose = () => {
98
91
  this._isConnected = false;
@@ -115,10 +108,6 @@ class Ros extends EventEmitter {
115
108
  this.socket = null;
116
109
  }
117
110
  this._isConnected = false;
118
- if (this.reconnectTimer) {
119
- clearTimeout(this.reconnectTimer);
120
- this.reconnectTimer = null;
121
- }
122
111
  }
123
112
  handleMessage(data) {
124
113
  try {
@@ -164,11 +153,361 @@ class Ros extends EventEmitter {
164
153
  }
165
154
  }
166
155
 
156
+ /**
157
+ * 事件发射器,用于内部实现
158
+ */
159
+ /**
160
+ * 连接状态枚举
161
+ */
162
+ var EnhancedRosState;
163
+ (function (EnhancedRosState) {
164
+ /** 空闲/初始状态 */
165
+ EnhancedRosState["IDLE"] = "IDLE";
166
+ /** 正在连接 */
167
+ EnhancedRosState["CONNECTING"] = "CONNECTING";
168
+ /** 已连接 */
169
+ EnhancedRosState["CONNECTED"] = "CONNECTED";
170
+ /** 正在重连 */
171
+ EnhancedRosState["RECONNECTING"] = "RECONNECTING";
172
+ /** 已手动关闭 */
173
+ EnhancedRosState["CLOSED"] = "CLOSED";
174
+ /** 发生错误 */
175
+ EnhancedRosState["ERROR"] = "ERROR";
176
+ })(EnhancedRosState || (EnhancedRosState = {}));
177
+ /**
178
+ * 增强版 ROS 连接封装
179
+ * 支持自动重连、心跳保活、消息队列、状态管理
180
+ */
181
+ class EnhancedRos extends EventEmitter {
182
+ /**
183
+ * 构造函数
184
+ * @param options 配置项
185
+ */
186
+ constructor(options = {}) {
187
+ var _a, _b, _c;
188
+ super();
189
+ /** WebSocket 实例 */
190
+ this.socket = null;
191
+ /** 自增 ID 计数器,用于请求-响应匹配 */
192
+ this.idCounter = 0;
193
+ /** 当前连接状态 */
194
+ this._state = EnhancedRosState.IDLE;
195
+ /** 当前/最近一次连接地址 */
196
+ this.currentUrl = null;
197
+ /** 离线消息队列,连接成功后自动发送 */
198
+ this.messageQueue = [];
199
+ /** 重连定时器句柄 */
200
+ this.reconnectTimer = null;
201
+ /** 心跳定时器句柄 */
202
+ this.heartbeatTimer = null;
203
+ /** 最近一次收到服务端消息的时间戳 */
204
+ this.lastServerMessageAtMs = null;
205
+ /** 标记是否为用户主动关闭(影响重连策略) */
206
+ this.manualClose = false;
207
+ /** 连接代际,用于丢弃过期的重连任务 */
208
+ this.connectGeneration = 0;
209
+ this.options = options;
210
+ // 初始化重连退避参数
211
+ this.reconnectMinDelayMs = (_a = options.reconnect_min_delay) !== null && _a !== void 0 ? _a : 1000;
212
+ this.reconnectMaxDelayMs = (_b = options.reconnect_max_delay) !== null && _b !== void 0 ? _b : 30000;
213
+ this.reconnectDelayMs = this.reconnectMinDelayMs;
214
+ // 初始化心跳参数 开启的话12000ms,不开启0ms
215
+ this.heartbeatIntervalMs = (_c = options.heartbeat_interval_ms) !== null && _c !== void 0 ? _c : 0;
216
+ this.heartbeatFn = options.heartbeat_fn;
217
+ // 如果提供了 url,立即开始连接
218
+ if (options.url) {
219
+ this.connect(options.url);
220
+ }
221
+ }
222
+ /** 获取当前状态 */
223
+ get state() {
224
+ return this._state;
225
+ }
226
+ /** 是否已连接 */
227
+ get isConnected() {
228
+ return this._state === EnhancedRosState.CONNECTED;
229
+ }
230
+ /**
231
+ * 建立连接(如已连接相同地址则忽略)
232
+ * @param url WebSocket 地址,例如 ws://localhost:9090
233
+ */
234
+ connect(url) {
235
+ // 避免重复连接相同地址
236
+ if (this.currentUrl === url &&
237
+ (this._state === EnhancedRosState.CONNECTING || this._state === EnhancedRosState.CONNECTED)) {
238
+ return;
239
+ }
240
+ // 进入新一轮连接生命周期
241
+ this.connectGeneration += 1;
242
+ this.cleanupForConnect();
243
+ this.currentUrl = url;
244
+ this.manualClose = false;
245
+ this.setState(EnhancedRosState.IDLE);
246
+ this.setState(EnhancedRosState.CONNECTING);
247
+ this.openSocket(url);
248
+ }
249
+ /**
250
+ * 手动关闭连接(不会触发自动重连)
251
+ */
252
+ close() {
253
+ this.connectGeneration += 1;
254
+ this.manualClose = true;
255
+ this.clearReconnectTimer();
256
+ this.stopHeartbeat();
257
+ this.messageQueue = [];
258
+ this.setState(EnhancedRosState.CLOSED);
259
+ this.closeSocket();
260
+ }
261
+ /**
262
+ * 发送消息(离线时自动入队)
263
+ * @param message 任意 JSON 兼容对象
264
+ */
265
+ callOnConnection(message) {
266
+ if (this._state !== EnhancedRosState.CONNECTED || !this.socket || this.socket.readyState !== WebSocket.OPEN) {
267
+ this.messageQueue.push(message);
268
+ return;
269
+ }
270
+ this.send(message);
271
+ }
272
+ /**
273
+ * 获取下一个自增 ID(字符串形式)
274
+ */
275
+ getNextId() {
276
+ return (++this.idCounter).toString();
277
+ }
278
+ /* ===================== 私有方法 ===================== */
279
+ /** 状态变更并对外广播 */
280
+ setState(next) {
281
+ if (this._state === next)
282
+ return;
283
+ this._state = next;
284
+ this.emit('state', next);
285
+ }
286
+ /** 连接前清理资源 */
287
+ cleanupForConnect() {
288
+ this.clearReconnectTimer();
289
+ this.stopHeartbeat();
290
+ this.closeSocket();
291
+ this.messageQueue = [];
292
+ this.reconnectDelayMs = this.reconnectMinDelayMs;
293
+ this.lastServerMessageAtMs = null;
294
+ }
295
+ /** 清除重连定时器 */
296
+ clearReconnectTimer() {
297
+ if (!this.reconnectTimer)
298
+ return;
299
+ clearTimeout(this.reconnectTimer);
300
+ this.reconnectTimer = null;
301
+ }
302
+ /** 创建并绑定 WebSocket 事件 */
303
+ openSocket(url) {
304
+ var _a, _b;
305
+ try {
306
+ const WS = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.WebSocket) !== null && _b !== void 0 ? _b : WebSocket;
307
+ this.socket = new WS(url);
308
+ const generation = this.connectGeneration; // 捕获当前代际
309
+ this.socket.onopen = () => {
310
+ // 连接成功,重置退避
311
+ this.reconnectDelayMs = this.reconnectMinDelayMs;
312
+ this.lastServerMessageAtMs = Date.now();
313
+ this.setState(EnhancedRosState.CONNECTED);
314
+ this.emit('connection');
315
+ this.startHeartbeat();
316
+ this.flushQueue();
317
+ };
318
+ this.socket.onclose = () => {
319
+ if (generation !== this.connectGeneration)
320
+ return;
321
+ console.log('socket close');
322
+ this.stopHeartbeat();
323
+ this.socket = null;
324
+ this.emit('close');
325
+ if (this.manualClose) {
326
+ // 用户主动关闭,不再重连
327
+ this.setState(EnhancedRosState.CLOSED);
328
+ return;
329
+ }
330
+ // 异常断开,进入重连逻辑
331
+ this.setState(EnhancedRosState.RECONNECTING);
332
+ this.scheduleReconnect();
333
+ };
334
+ this.socket.onerror = (error) => {
335
+ this.emit('error', error);
336
+ if (!this.manualClose && this._state === EnhancedRosState.CONNECTING) {
337
+ // 连接阶段出错,准备重连
338
+ this.setState(EnhancedRosState.RECONNECTING);
339
+ }
340
+ };
341
+ this.socket.onmessage = (event) => {
342
+ this.lastServerMessageAtMs = Date.now();
343
+ this.handleMessage(event.data);
344
+ };
345
+ }
346
+ catch (error) {
347
+ this.emit('error', error);
348
+ this.setState(EnhancedRosState.ERROR);
349
+ }
350
+ }
351
+ /** 安全关闭 WebSocket */
352
+ closeSocket() {
353
+ if (!this.socket)
354
+ return;
355
+ try {
356
+ this.socket.close();
357
+ }
358
+ finally {
359
+ this.socket = null;
360
+ }
361
+ }
362
+ /** 调度下一次重连(退避策略,达到最大后固定) */
363
+ scheduleReconnect() {
364
+ if (!this.currentUrl) {
365
+ this.setState(EnhancedRosState.ERROR);
366
+ return;
367
+ }
368
+ const generation = this.connectGeneration;
369
+ const delayMs = this.reconnectDelayMs;
370
+ this.clearReconnectTimer();
371
+ this.reconnectTimer = setTimeout(() => {
372
+ if (this.manualClose)
373
+ return;
374
+ if (generation !== this.connectGeneration)
375
+ return; // 连接已过期
376
+ if (this._state !== EnhancedRosState.RECONNECTING)
377
+ return;
378
+ if (!this.currentUrl) {
379
+ this.setState(EnhancedRosState.ERROR);
380
+ return;
381
+ }
382
+ // 进入新一轮连接生命周期
383
+ // this.connectGeneration += 1;
384
+ // 不能 this.cleanupForConnect();
385
+ // 关闭定时器、清除socket在close时候已经处理; 连接上后自然会重置 this.reconnectDelayMs = this.reconnectMinDelayMs; this.lastServerMessageAtMs = null;
386
+ // 现在讨论是否要不要connectGeneration++ ,我认为是有必要的,每一次重连都是一个新的socket
387
+ // 不能 清空messageQueue,messageQueue还等着重连成功自动重发
388
+ this.connectGeneration += 1;
389
+ this.setState(EnhancedRosState.CONNECTING);
390
+ this.openSocket(this.currentUrl);
391
+ // 退避:下次等待时间翻倍,直到上限后固定
392
+ if (this.reconnectDelayMs < this.reconnectMaxDelayMs) {
393
+ this.reconnectDelayMs = Math.min(this.reconnectMaxDelayMs, this.reconnectDelayMs * 2);
394
+ }
395
+ // 已达上限,保持最大延迟不变,持续重试
396
+ }, delayMs);
397
+ }
398
+ /** 启动心跳定时器 */
399
+ startHeartbeat() {
400
+ this.stopHeartbeat();
401
+ if (this.heartbeatIntervalMs <= 0)
402
+ return;
403
+ this.heartbeatTimer = setInterval(() => {
404
+ if (this._state !== EnhancedRosState.CONNECTED)
405
+ return;
406
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN)
407
+ return;
408
+ const now = Date.now();
409
+ const last = this.lastServerMessageAtMs;
410
+ // 超过两倍心跳间隔未收到消息,认为连接失效
411
+ if (last && now - last > this.heartbeatIntervalMs * 2) {
412
+ this.setState(EnhancedRosState.RECONNECTING);
413
+ try {
414
+ this.closeSocket();
415
+ }
416
+ catch (_a) { }
417
+ // if (!this.reconnectTimer) {
418
+ // this.scheduleReconnect();
419
+ // }
420
+ return;
421
+ }
422
+ // 发送 ping 保活
423
+ if (this.heartbeatFn) {
424
+ this.heartbeatFn();
425
+ }
426
+ else {
427
+ /// 默认心跳:调用 /rosapi/get_time 服务
428
+ this.cast({ op: "call_service", id: this.getNextId(), service: "/rosapi/get_time", type: "rosapi/GetTime", args: {} });
429
+ }
430
+ }, this.heartbeatIntervalMs);
431
+ }
432
+ /** 停止心跳定时器 */
433
+ stopHeartbeat() {
434
+ if (!this.heartbeatTimer)
435
+ return;
436
+ clearInterval(this.heartbeatTimer);
437
+ this.heartbeatTimer = null;
438
+ }
439
+ /** 将离线队列全部发出 */
440
+ flushQueue() {
441
+ if (this._state !== EnhancedRosState.CONNECTED || !this.socket || this.socket.readyState !== WebSocket.OPEN) {
442
+ return;
443
+ }
444
+ if (this.messageQueue.length === 0)
445
+ return;
446
+ const pending = this.messageQueue;
447
+ this.messageQueue = [];
448
+ for (const msg of pending) {
449
+ this.send(msg);
450
+ }
451
+ }
452
+ /** 真正发送 JSON 字符串 */
453
+ send(message) {
454
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
455
+ // 兜底:万一状态不一致,重新入队
456
+ this.messageQueue.push(message);
457
+ return;
458
+ }
459
+ const messageStr = JSON.stringify(message);
460
+ this.socket.send(messageStr);
461
+ }
462
+ cast(message) {
463
+ if (!this.socket || this.socket.readyState !== WebSocket.OPEN) {
464
+ return;
465
+ }
466
+ const messageStr = JSON.stringify(message);
467
+ this.socket.send(messageStr);
468
+ }
469
+ /** 解析并分发服务端消息 */
470
+ handleMessage(data) {
471
+ try {
472
+ const message = JSON.parse(data);
473
+ if (message.op === 'publish') {
474
+ // 普通话题消息
475
+ this.emit(message.topic, message.msg);
476
+ }
477
+ else if (message.op === 'service_response') {
478
+ // 服务响应,用 id 匹配请求
479
+ this.emit(message.id, message);
480
+ }
481
+ else if (message.op === 'status') {
482
+ // 状态消息,可选带 id
483
+ if (message.id) {
484
+ this.emit('status:' + message.id, message);
485
+ }
486
+ else {
487
+ this.emit('status', message);
488
+ }
489
+ }
490
+ else if (message.op === 'service_request') {
491
+ // 服务请求(作为服务端角色时)
492
+ this.emit('service_request:' + message.service, message);
493
+ }
494
+ }
495
+ catch (error) {
496
+ console.error('Error parsing message:', error);
497
+ }
498
+ }
499
+ }
500
+
167
501
  class Topic extends EventEmitter {
168
502
  constructor(options) {
169
503
  super();
170
504
  this.isSubscribed = false;
171
505
  this.isAdvertised = false;
506
+ /** 内部状态处理:连接关闭时重置标志位 */
507
+ this._handleClose = () => {
508
+ this.isSubscribed = false;
509
+ this.isAdvertised = false;
510
+ };
172
511
  this.ros = options.ros;
173
512
  this.name = options.name;
174
513
  this.messageType = options.messageType;
@@ -177,72 +516,93 @@ class Topic extends EventEmitter {
177
516
  this.queue_size = options.queue_size;
178
517
  this.latch = options.latch;
179
518
  this.queue_length = options.queue_length;
519
+ // 预定义重连逻辑
520
+ this._reconnectHandler = () => {
521
+ if (this.isSubscribed) {
522
+ this._sendSubscribe();
523
+ }
524
+ if (this.isAdvertised) {
525
+ this._sendAdvertise();
526
+ }
527
+ };
180
528
  }
181
- subscribe(callback) {
182
- if (this.isSubscribed) {
183
- return;
184
- }
529
+ /** 发送底层的订阅协议包 */
530
+ _sendSubscribe() {
531
+ this.isSubscribed = true;
185
532
  const subscribeMessage = Object.assign(Object.assign(Object.assign({ op: 'subscribe', topic: this.name, type: this.messageType }, (this.compression && { compression: this.compression })), (this.throttle_rate && { throttle_rate: this.throttle_rate })), (this.queue_length && { queue_length: this.queue_length }));
186
533
  this.ros.callOnConnection(subscribeMessage);
187
- this.isSubscribed = true;
188
- // 监听来自 ROS 的消息
534
+ }
535
+ /** 发送底层的公告协议包 */
536
+ _sendAdvertise() {
537
+ this.isAdvertised = true;
538
+ const advertiseMessage = Object.assign(Object.assign({ op: 'advertise', topic: this.name, type: this.messageType }, (this.latch && { latch: this.latch })), (this.queue_size && { queue_size: this.queue_size }));
539
+ this.ros.callOnConnection(advertiseMessage);
540
+ }
541
+ /**
542
+ * 订阅话题
543
+ * @param callback 接收消息的回调函数
544
+ */
545
+ subscribe(callback) {
546
+ if (this.isSubscribed)
547
+ return;
548
+ // 1. 先尝试移除已有的监听,防止重复挂载
549
+ this.ros.off('connection', this._reconnectHandler);
550
+ this.ros.off('close', this._handleClose);
551
+ // 2. 挂载监听
552
+ this.ros.on('connection', this._reconnectHandler);
553
+ this.ros.on('close', this._handleClose);
554
+ // 3. 执行物理订阅
555
+ this._sendSubscribe();
556
+ // 4. 监听来自 ROS 的消息分发
189
557
  this.ros.on(this.name, (message) => {
190
558
  this.emit('message', message);
191
- if (callback) {
559
+ if (callback)
192
560
  callback(message);
193
- }
194
- });
195
- // 监听连接关闭事件,重新订阅
196
- this.ros.on('close', () => {
197
- this.isSubscribed = false;
198
- });
199
- this.ros.on('connection', () => {
200
- if (!this.isSubscribed) {
201
- this.subscribe(callback);
202
- }
203
561
  });
204
562
  }
563
+ /** 取消订阅 */
205
564
  unsubscribe() {
206
- if (!this.isSubscribed) {
565
+ if (!this.isSubscribed)
207
566
  return;
208
- }
209
567
  const unsubscribeMessage = {
210
568
  op: 'unsubscribe',
211
- topic: this.name
569
+ topic: this.name,
212
570
  };
213
571
  this.ros.callOnConnection(unsubscribeMessage);
214
572
  this.isSubscribed = false;
215
- // 移除事件监听器
573
+ // 彻底清理:移除重连监听和消息监听
216
574
  this.ros.off(this.name);
575
+ if (!this.isAdvertised) {
576
+ this.ros.off('connection', this._reconnectHandler);
577
+ this.ros.off('close', this._handleClose);
578
+ }
217
579
  }
580
+ /** 公告话题(作为发布者) */
218
581
  advertise() {
219
- if (this.isAdvertised) {
582
+ if (this.isAdvertised)
220
583
  return;
221
- }
222
- const advertiseMessage = Object.assign(Object.assign({ op: 'advertise', topic: this.name, type: this.messageType }, (this.latch && { latch: this.latch })), (this.queue_size && { queue_size: this.queue_size }));
223
- this.ros.callOnConnection(advertiseMessage);
224
- this.isAdvertised = true;
225
- // 监听连接关闭事件
226
- this.ros.on('close', () => {
227
- this.isAdvertised = false;
228
- });
229
- this.ros.on('connection', () => {
230
- if (!this.isAdvertised) {
231
- this.advertise();
232
- }
233
- });
584
+ this.ros.off('connection', this._reconnectHandler);
585
+ this.ros.on('connection', this._reconnectHandler);
586
+ this.ros.on('close', this._handleClose);
587
+ this._sendAdvertise();
234
588
  }
589
+ /** 取消公告 */
235
590
  unadvertise() {
236
- if (!this.isAdvertised) {
591
+ if (!this.isAdvertised)
237
592
  return;
238
- }
239
593
  const unadvertiseMessage = {
240
594
  op: 'unadvertise',
241
- topic: this.name
595
+ topic: this.name,
242
596
  };
243
597
  this.ros.callOnConnection(unadvertiseMessage);
244
598
  this.isAdvertised = false;
599
+ // 如果当前也没有订阅,则可以安全移除重连处理器
600
+ if (!this.isSubscribed) {
601
+ this.ros.off('connection', this._reconnectHandler);
602
+ this.ros.off('close', this._handleClose);
603
+ }
245
604
  }
605
+ /** 发布消息 */
246
606
  publish(message) {
247
607
  if (!this.isAdvertised) {
248
608
  this.advertise();
@@ -250,7 +610,7 @@ class Topic extends EventEmitter {
250
610
  const publishMessage = {
251
611
  op: 'publish',
252
612
  topic: this.name,
253
- msg: message
613
+ msg: message,
254
614
  };
255
615
  this.ros.callOnConnection(publishMessage);
256
616
  }
@@ -275,9 +635,21 @@ class Service extends EventEmitter {
275
635
  constructor(options) {
276
636
  super();
277
637
  this.isAdvertised = false;
638
+ /** 存储服务请求处理函数,便于卸载 */
639
+ this._currentServiceCallback = null;
640
+ /** 内部状态处理:连接关闭时重置标志位 */
641
+ this._handleClose = () => {
642
+ this.isAdvertised = false;
643
+ };
278
644
  this.ros = options.ros;
279
645
  this.name = options.name;
280
646
  this.serviceType = options.serviceType;
647
+ // 预定义重连恢复逻辑
648
+ this._reconnectHandler = () => {
649
+ if (this.isAdvertised && this._currentServiceCallback) {
650
+ this._sendAdvertise();
651
+ }
652
+ };
281
653
  }
282
654
  callService(request, callback, failedCallback) {
283
655
  return new Promise((resolve, reject) => {
@@ -318,18 +690,32 @@ class Service extends EventEmitter {
318
690
  this.ros.callOnConnection(serviceMessage);
319
691
  });
320
692
  }
321
- advertise(callback) {
322
- if (this.isAdvertised) {
323
- return;
324
- }
693
+ /** 发送底层的服务公告协议 */
694
+ _sendAdvertise() {
695
+ this.isAdvertised = true;
325
696
  const advertiseMessage = {
326
697
  op: 'advertise_service',
327
- service: this.name,
328
- type: this.serviceType
698
+ type: this.serviceType,
699
+ service: this.name
329
700
  };
330
701
  this.ros.callOnConnection(advertiseMessage);
331
- this.isAdvertised = true;
332
- // 监听服务请求
702
+ }
703
+ /**
704
+ * 公告服务(服务端模式)
705
+ * @param callback 处理请求并返回结果的回调
706
+ */
707
+ advertise(callback) {
708
+ if (this.isAdvertised)
709
+ return;
710
+ this._currentServiceCallback = callback;
711
+ // 1. 防御性卸载旧监听
712
+ this.ros.off('connection', this._reconnectHandler);
713
+ this.ros.off('close', this._handleClose);
714
+ this.ros.off('service_request:' + this.name);
715
+ // 2. 挂载生命周期监听
716
+ this.ros.on('connection', this._reconnectHandler);
717
+ this.ros.on('close', this._handleClose);
718
+ // 3. 监听服务请求
333
719
  this.ros.on('service_request:' + this.name, (message) => {
334
720
  const request = new ServiceRequest(message.args);
335
721
  const response = new ServiceResponse();
@@ -355,28 +741,26 @@ class Service extends EventEmitter {
355
741
  this.ros.callOnConnection(errorMessage);
356
742
  }
357
743
  });
358
- // 监听连接关闭事件
359
- this.ros.on('close', () => {
360
- this.isAdvertised = false;
361
- });
362
- this.ros.on('connection', () => {
363
- if (!this.isAdvertised) {
364
- this.advertise(callback);
365
- }
366
- });
744
+ // 4. 执行物理公告
745
+ this._sendAdvertise();
367
746
  }
747
+ /**
748
+ * 取消服务公告
749
+ */
368
750
  unadvertise() {
369
- if (!this.isAdvertised) {
751
+ if (!this.isAdvertised)
370
752
  return;
371
- }
372
753
  const unadvertiseMessage = {
373
754
  op: 'unadvertise_service',
374
755
  service: this.name
375
756
  };
376
757
  this.ros.callOnConnection(unadvertiseMessage);
377
758
  this.isAdvertised = false;
378
- // 移除事件监听器
759
+ this._currentServiceCallback = null;
760
+ // 彻底清理资源:移除所有相关监听
379
761
  this.ros.off('service_request:' + this.name);
762
+ this.ros.off('connection', this._reconnectHandler);
763
+ this.ros.off('close', this._handleClose);
380
764
  }
381
765
  }
382
766
 
@@ -446,5 +830,209 @@ class Param {
446
830
  }
447
831
  }
448
832
 
449
- export { EventEmitter, Param, Ros, Service, ServiceRequest, ServiceResponse, Topic };
833
+ class TopicManager {
834
+ constructor(ros) {
835
+ this.topics = new Map();
836
+ this.ros = ros;
837
+ }
838
+ subscribe(name, messageType, callback) {
839
+ if (!this.ros) {
840
+ throw new Error('ros instance is not initialized');
841
+ }
842
+ if (!this.ros.isConnected) {
843
+ console.warn(`ROS not connected, cannot subscribe to ${name}, ${name} in messageQueue when ros reconnected`);
844
+ }
845
+ // 已存在,添加回调即可
846
+ if (this.topics.has(name)) {
847
+ const managed = this.topics.get(name);
848
+ managed.callbacks.add(callback);
849
+ return;
850
+ }
851
+ // 创建新 topic
852
+ const topic = new Topic({ ros: this.ros, name, messageType });
853
+ const callbacks = new Set();
854
+ callbacks.add(callback);
855
+ topic.subscribe((msg) => {
856
+ callbacks.forEach((cb) => cb(msg));
857
+ });
858
+ this.topics.set(name, { topic, callbacks, messageType });
859
+ }
860
+ unsubscribe(name, callback) {
861
+ const managed = this.topics.get(name);
862
+ if (!managed)
863
+ return;
864
+ if (callback) {
865
+ managed.callbacks.delete(callback);
866
+ // 如果没有回调了,取消订阅
867
+ if (managed.callbacks.size === 0) {
868
+ managed.topic.unsubscribe();
869
+ this.topics.delete(name);
870
+ }
871
+ }
872
+ else {
873
+ // 取消所有订阅
874
+ managed.topic.unsubscribe();
875
+ this.topics.delete(name);
876
+ }
877
+ }
878
+ clearAll() {
879
+ this.topics.forEach((managed) => {
880
+ managed.topic.unsubscribe();
881
+ });
882
+ this.topics.clear();
883
+ }
884
+ resubscribeAll(ros) {
885
+ this.topics.forEach((managed, name) => {
886
+ const topic = new Topic({ ros, name, messageType: managed.messageType });
887
+ managed.topic = topic;
888
+ topic.subscribe((msg) => {
889
+ managed.callbacks.forEach((cb) => cb(msg));
890
+ });
891
+ });
892
+ }
893
+ publish(name, messageType, data) {
894
+ if (!this.ros) {
895
+ throw new Error('ros instance is not initialized');
896
+ }
897
+ if (!this.ros.isConnected) {
898
+ console.warn(`ROS not connected, cannot publish to ${name}, ${name} in messageQueue when ros reconnected`);
899
+ }
900
+ const chatter = new Topic({
901
+ ros: this.ros,
902
+ name,
903
+ messageType
904
+ });
905
+ chatter.publish({ data: data });
906
+ }
907
+ }
908
+ class ServiceManager {
909
+ constructor(ros, timeout = 10000) {
910
+ this.defaultTimeout = 10000; // 默认超时 10s
911
+ this.ros = ros;
912
+ this.defaultTimeout = timeout;
913
+ }
914
+ /**
915
+ * 调用服务(每次直接创建 Service 实例,带统一超时)
916
+ */
917
+ call(name, serviceType, request, timeout = this.defaultTimeout) {
918
+ return new Promise((resolve, reject) => {
919
+ if (!this.ros) {
920
+ return reject(new Error('ros instance is not initialized'));
921
+ }
922
+ if (!this.ros.isConnected) {
923
+ return reject(new Error(`ROS not connected, cannot call service ${name}`));
924
+ }
925
+ let timer = null;
926
+ const cleanup = () => {
927
+ if (timer) {
928
+ clearTimeout(timer);
929
+ timer = null;
930
+ }
931
+ };
932
+ timer = setTimeout(() => {
933
+ cleanup();
934
+ reject(new Error(`Service call ${name} timeout after ${timeout}ms`));
935
+ }, timeout);
936
+ try {
937
+ const service = new Service({
938
+ ros: this.ros,
939
+ name,
940
+ serviceType,
941
+ });
942
+ const serviceRequest = new ServiceRequest(request);
943
+ service.callService(serviceRequest, (result) => {
944
+ cleanup();
945
+ resolve(result);
946
+ }, (error) => {
947
+ cleanup();
948
+ reject(error);
949
+ });
950
+ }
951
+ catch (error) {
952
+ cleanup();
953
+ reject(error);
954
+ }
955
+ });
956
+ }
957
+ }
958
+ class ParamManager {
959
+ constructor(ros, timeout = 10000) {
960
+ this.defaultTimeout = 10000; // 默认超时 10s
961
+ this.ros = ros;
962
+ this.defaultTimeout = timeout;
963
+ }
964
+ /**
965
+ * 获取参数值
966
+ */
967
+ get(name, timeout = this.defaultTimeout) {
968
+ return new Promise((resolve, reject) => {
969
+ const ros = this.ros;
970
+ if (!this.ros) {
971
+ return reject(new Error('ros instance is not initialized'));
972
+ }
973
+ if (!this.ros.isConnected) {
974
+ return reject(new Error(`ROS not connected, cannot get param ${name}`));
975
+ }
976
+ const param = new Param({ ros, name });
977
+ const timer = setTimeout(() => {
978
+ reject(new Error(`Get param ${name} timeout`));
979
+ }, timeout);
980
+ param
981
+ .get((value) => {
982
+ clearTimeout(timer);
983
+ resolve(value);
984
+ })
985
+ .catch((error) => {
986
+ clearTimeout(timer);
987
+ reject(error);
988
+ });
989
+ });
990
+ }
991
+ /**
992
+ * 设置参数值
993
+ */
994
+ set(name, value) {
995
+ return new Promise((resolve, reject) => {
996
+ const ros = this.ros;
997
+ if (!this.ros) {
998
+ return reject(new Error('ros instance is not initialized'));
999
+ }
1000
+ if (!this.ros.isConnected) {
1001
+ return reject(new Error(`ROS not connected, cannot set param ${name}`));
1002
+ }
1003
+ const param = new Param({ ros, name });
1004
+ param
1005
+ .set(value, () => {
1006
+ resolve();
1007
+ })
1008
+ .catch((error) => {
1009
+ reject(error);
1010
+ });
1011
+ });
1012
+ }
1013
+ /**
1014
+ * 删除参数
1015
+ */
1016
+ delete(name) {
1017
+ return new Promise((resolve, reject) => {
1018
+ const ros = this.ros;
1019
+ if (!this.ros) {
1020
+ return reject(new Error('ros instance is not initialized'));
1021
+ }
1022
+ if (!this.ros.isConnected) {
1023
+ return reject(new Error(`ROS not connected, cannot delete param ${name}`));
1024
+ }
1025
+ const param = new Param({ ros, name });
1026
+ param
1027
+ .delete(() => {
1028
+ resolve();
1029
+ })
1030
+ .catch((error) => {
1031
+ reject(error);
1032
+ });
1033
+ });
1034
+ }
1035
+ }
1036
+
1037
+ export { EnhancedRos, EnhancedRosState, EventEmitter, Param, ParamManager, Ros, Service, ServiceManager, ServiceRequest, ServiceResponse, Topic, TopicManager };
450
1038
  //# sourceMappingURL=index.esm.js.map