rclnodejs 1.6.0 → 1.8.0

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.
Files changed (84) hide show
  1. package/binding.gyp +2 -0
  2. package/index.js +152 -0
  3. package/lib/action/client.js +109 -10
  4. package/lib/action/deferred.js +8 -2
  5. package/lib/action/server.js +10 -1
  6. package/lib/action/uuid.js +4 -1
  7. package/lib/client.js +218 -4
  8. package/lib/clock.js +182 -1
  9. package/lib/clock_change.js +49 -0
  10. package/lib/clock_event.js +88 -0
  11. package/lib/context.js +12 -2
  12. package/lib/duration.js +37 -12
  13. package/lib/errors.js +621 -0
  14. package/lib/event_handler.js +21 -4
  15. package/lib/interface_loader.js +52 -12
  16. package/lib/lifecycle.js +8 -2
  17. package/lib/logging.js +90 -3
  18. package/lib/message_introspector.js +123 -0
  19. package/lib/message_serialization.js +10 -2
  20. package/lib/message_validation.js +512 -0
  21. package/lib/native_loader.js +9 -4
  22. package/lib/node.js +403 -50
  23. package/lib/node_options.js +40 -1
  24. package/lib/observable_subscription.js +105 -0
  25. package/lib/parameter.js +172 -35
  26. package/lib/parameter_client.js +506 -0
  27. package/lib/parameter_watcher.js +309 -0
  28. package/lib/publisher.js +56 -1
  29. package/lib/qos.js +79 -5
  30. package/lib/rate.js +6 -1
  31. package/lib/serialization.js +7 -2
  32. package/lib/subscription.js +8 -0
  33. package/lib/time.js +136 -21
  34. package/lib/time_source.js +13 -4
  35. package/lib/timer.js +42 -0
  36. package/lib/utils.js +27 -1
  37. package/lib/validator.js +74 -19
  38. package/package.json +4 -2
  39. package/prebuilds/linux-arm64/humble-jammy-arm64-rclnodejs.node +0 -0
  40. package/prebuilds/linux-arm64/jazzy-noble-arm64-rclnodejs.node +0 -0
  41. package/prebuilds/linux-arm64/kilted-noble-arm64-rclnodejs.node +0 -0
  42. package/prebuilds/linux-x64/humble-jammy-x64-rclnodejs.node +0 -0
  43. package/prebuilds/linux-x64/jazzy-noble-x64-rclnodejs.node +0 -0
  44. package/prebuilds/linux-x64/kilted-noble-x64-rclnodejs.node +0 -0
  45. package/rosidl_gen/message_translator.js +0 -61
  46. package/scripts/config.js +1 -0
  47. package/src/addon.cpp +2 -0
  48. package/src/clock_event.cpp +268 -0
  49. package/src/clock_event.hpp +62 -0
  50. package/src/macros.h +2 -4
  51. package/src/rcl_action_server_bindings.cpp +21 -3
  52. package/src/rcl_bindings.cpp +59 -0
  53. package/src/rcl_context_bindings.cpp +5 -0
  54. package/src/rcl_graph_bindings.cpp +73 -0
  55. package/src/rcl_logging_bindings.cpp +158 -0
  56. package/src/rcl_node_bindings.cpp +14 -2
  57. package/src/rcl_publisher_bindings.cpp +12 -0
  58. package/src/rcl_service_bindings.cpp +7 -6
  59. package/src/rcl_subscription_bindings.cpp +51 -14
  60. package/src/rcl_time_point_bindings.cpp +135 -0
  61. package/src/rcl_timer_bindings.cpp +140 -0
  62. package/src/rcl_utilities.cpp +103 -2
  63. package/src/rcl_utilities.h +7 -1
  64. package/types/action_client.d.ts +27 -2
  65. package/types/base.d.ts +6 -0
  66. package/types/client.d.ts +65 -1
  67. package/types/clock.d.ts +86 -0
  68. package/types/clock_change.d.ts +27 -0
  69. package/types/clock_event.d.ts +51 -0
  70. package/types/errors.d.ts +496 -0
  71. package/types/index.d.ts +10 -0
  72. package/types/logging.d.ts +32 -0
  73. package/types/message_introspector.d.ts +75 -0
  74. package/types/message_validation.d.ts +183 -0
  75. package/types/node.d.ts +107 -0
  76. package/types/node_options.d.ts +13 -0
  77. package/types/observable_subscription.d.ts +39 -0
  78. package/types/parameter_client.d.ts +252 -0
  79. package/types/parameter_watcher.d.ts +104 -0
  80. package/types/publisher.d.ts +28 -1
  81. package/types/qos.d.ts +18 -0
  82. package/types/subscription.d.ts +6 -0
  83. package/types/timer.d.ts +18 -0
  84. package/types/validator.d.ts +86 -0
package/lib/node.js CHANGED
@@ -32,12 +32,20 @@ const {
32
32
  ParameterDescriptor,
33
33
  } = require('./parameter.js');
34
34
  const { isValidSerializationMode } = require('./message_serialization.js');
35
+ const {
36
+ TypeValidationError,
37
+ RangeValidationError,
38
+ ValidationError,
39
+ } = require('./errors.js');
35
40
  const ParameterService = require('./parameter_service.js');
41
+ const ParameterClient = require('./parameter_client.js');
42
+ const ParameterWatcher = require('./parameter_watcher.js');
36
43
  const Publisher = require('./publisher.js');
37
44
  const QoS = require('./qos.js');
38
45
  const Rates = require('./rate.js');
39
46
  const Service = require('./service.js');
40
47
  const Subscription = require('./subscription.js');
48
+ const ObservableSubscription = require('./observable_subscription.js');
41
49
  const TimeSource = require('./time_source.js');
42
50
  const Timer = require('./timer.js');
43
51
  const TypeDescriptionService = require('./type_description_service.js');
@@ -74,8 +82,11 @@ class Node extends rclnodejs.ShadowNode {
74
82
  ) {
75
83
  super();
76
84
 
77
- if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
78
- throw new TypeError('Invalid argument.');
85
+ if (typeof nodeName !== 'string') {
86
+ throw new TypeValidationError('nodeName', nodeName, 'string');
87
+ }
88
+ if (typeof namespace !== 'string') {
89
+ throw new TypeValidationError('namespace', namespace, 'string');
79
90
  }
80
91
 
81
92
  this._init(nodeName, namespace, options, context, args, useGlobalArguments);
@@ -92,7 +103,8 @@ class Node extends rclnodejs.ShadowNode {
92
103
  namespace,
93
104
  context.handle,
94
105
  args,
95
- useGlobalArguments
106
+ useGlobalArguments,
107
+ options.rosoutQos
96
108
  );
97
109
  Object.defineProperty(this, 'handle', {
98
110
  configurable: false,
@@ -111,6 +123,8 @@ class Node extends rclnodejs.ShadowNode {
111
123
  this._events = [];
112
124
  this._actionClients = [];
113
125
  this._actionServers = [];
126
+ this._parameterClients = [];
127
+ this._parameterWatchers = [];
114
128
  this._rateTimerServer = null;
115
129
  this._parameterDescriptors = new Map();
116
130
  this._parameters = new Map();
@@ -120,6 +134,11 @@ class Node extends rclnodejs.ShadowNode {
120
134
  this._setParametersCallbacks = [];
121
135
  this._logger = new Logging(rclnodejs.getNodeLoggerName(this.handle));
122
136
  this._spinning = false;
137
+ this._enableRosout = options.enableRosout;
138
+
139
+ if (this._enableRosout) {
140
+ rclnodejs.initRosoutPublisherForNode(this.handle);
141
+ }
123
142
 
124
143
  this._parameterEventPublisher = this.createPublisher(
125
144
  PARAMETER_EVENT_MSG_TYPE,
@@ -133,8 +152,13 @@ class Node extends rclnodejs.ShadowNode {
133
152
  if (options.parameterOverrides.length > 0) {
134
153
  for (const parameter of options.parameterOverrides) {
135
154
  if ((!parameter) instanceof Parameter) {
136
- throw new TypeError(
137
- 'Parameter-override must be an instance of Parameter.'
155
+ throw new TypeValidationError(
156
+ 'parameterOverride',
157
+ parameter,
158
+ 'Parameter instance',
159
+ {
160
+ nodeName: name,
161
+ }
138
162
  );
139
163
  }
140
164
  this._parameterOverrides.set(parameter.name, parameter);
@@ -514,7 +538,9 @@ class Node extends rclnodejs.ShadowNode {
514
538
  options !== undefined &&
515
539
  (options === null || typeof options !== 'object')
516
540
  ) {
517
- throw new TypeError('Invalid argument of options');
541
+ throw new TypeValidationError('options', options, 'object', {
542
+ nodeName: this.name(),
543
+ });
518
544
  }
519
545
 
520
546
  if (options === undefined) {
@@ -536,8 +562,15 @@ class Node extends rclnodejs.ShadowNode {
536
562
  if (options.serializationMode === undefined) {
537
563
  options = Object.assign(options, { serializationMode: 'default' });
538
564
  } else if (!isValidSerializationMode(options.serializationMode)) {
539
- throw new TypeError(
540
- `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`
565
+ throw new ValidationError(
566
+ `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`,
567
+ {
568
+ code: 'INVALID_SERIALIZATION_MODE',
569
+ argumentName: 'serializationMode',
570
+ providedValue: options.serializationMode,
571
+ expectedType: "'default' | 'plain' | 'json'",
572
+ nodeName: this.name(),
573
+ }
541
574
  );
542
575
  }
543
576
 
@@ -558,8 +591,15 @@ class Node extends rclnodejs.ShadowNode {
558
591
  clock = arguments[3];
559
592
  }
560
593
 
561
- if (typeof period !== 'bigint' || typeof callback !== 'function') {
562
- throw new TypeError('Invalid argument');
594
+ if (typeof period !== 'bigint') {
595
+ throw new TypeValidationError('period', period, 'bigint', {
596
+ nodeName: this.name(),
597
+ });
598
+ }
599
+ if (typeof callback !== 'function') {
600
+ throw new TypeValidationError('callback', callback, 'function', {
601
+ nodeName: this.name(),
602
+ });
563
603
  }
564
604
 
565
605
  const timerClock = clock || this._clock;
@@ -584,13 +624,20 @@ class Node extends rclnodejs.ShadowNode {
584
624
  */
585
625
  async createRate(hz = 1) {
586
626
  if (typeof hz !== 'number') {
587
- throw new TypeError('Invalid argument');
627
+ throw new TypeValidationError('hz', hz, 'number', {
628
+ nodeName: this.name(),
629
+ });
588
630
  }
589
631
 
590
632
  const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
591
633
  if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
592
- throw new RangeError(
593
- `Hz must be between 0.0 and ${MAX_RATE_HZ_IN_MILLISECOND}`
634
+ throw new RangeValidationError(
635
+ 'hz',
636
+ hz,
637
+ `0.0 < hz <= ${MAX_RATE_HZ_IN_MILLISECOND}`,
638
+ {
639
+ nodeName: this.name(),
640
+ }
594
641
  );
595
642
  }
596
643
 
@@ -635,12 +682,32 @@ class Node extends rclnodejs.ShadowNode {
635
682
  }
636
683
  options = this._validateOptions(options);
637
684
 
685
+ if (typeof typeClass !== 'function') {
686
+ throw new TypeValidationError('typeClass', typeClass, 'function', {
687
+ nodeName: this.name(),
688
+ entityType: 'publisher',
689
+ });
690
+ }
691
+ if (typeof topic !== 'string') {
692
+ throw new TypeValidationError('topic', topic, 'string', {
693
+ nodeName: this.name(),
694
+ entityType: 'publisher',
695
+ });
696
+ }
638
697
  if (
639
- typeof typeClass !== 'function' ||
640
- typeof topic !== 'string' ||
641
- (eventCallbacks && !(eventCallbacks instanceof PublisherEventCallbacks))
698
+ eventCallbacks &&
699
+ !(eventCallbacks instanceof PublisherEventCallbacks)
642
700
  ) {
643
- throw new TypeError('Invalid argument');
701
+ throw new TypeValidationError(
702
+ 'eventCallbacks',
703
+ eventCallbacks,
704
+ 'PublisherEventCallbacks',
705
+ {
706
+ nodeName: this.name(),
707
+ entityType: 'publisher',
708
+ entityName: topic,
709
+ }
710
+ );
644
711
  }
645
712
 
646
713
  let publisher = publisherClass.createPublisher(
@@ -704,14 +771,39 @@ class Node extends rclnodejs.ShadowNode {
704
771
  }
705
772
  options = this._validateOptions(options);
706
773
 
774
+ if (typeof typeClass !== 'function') {
775
+ throw new TypeValidationError('typeClass', typeClass, 'function', {
776
+ nodeName: this.name(),
777
+ entityType: 'subscription',
778
+ });
779
+ }
780
+ if (typeof topic !== 'string') {
781
+ throw new TypeValidationError('topic', topic, 'string', {
782
+ nodeName: this.name(),
783
+ entityType: 'subscription',
784
+ });
785
+ }
786
+ if (typeof callback !== 'function') {
787
+ throw new TypeValidationError('callback', callback, 'function', {
788
+ nodeName: this.name(),
789
+ entityType: 'subscription',
790
+ entityName: topic,
791
+ });
792
+ }
707
793
  if (
708
- typeof typeClass !== 'function' ||
709
- typeof topic !== 'string' ||
710
- typeof callback !== 'function' ||
711
- (eventCallbacks &&
712
- !(eventCallbacks instanceof SubscriptionEventCallbacks))
794
+ eventCallbacks &&
795
+ !(eventCallbacks instanceof SubscriptionEventCallbacks)
713
796
  ) {
714
- throw new TypeError('Invalid argument');
797
+ throw new TypeValidationError(
798
+ 'eventCallbacks',
799
+ eventCallbacks,
800
+ 'SubscriptionEventCallbacks',
801
+ {
802
+ nodeName: this.name(),
803
+ entityType: 'subscription',
804
+ entityName: topic,
805
+ }
806
+ );
715
807
  }
716
808
 
717
809
  let subscription = Subscription.createSubscription(
@@ -729,6 +821,42 @@ class Node extends rclnodejs.ShadowNode {
729
821
  return subscription;
730
822
  }
731
823
 
824
+ /**
825
+ * Create a Subscription that returns an RxJS Observable.
826
+ * This allows using reactive programming patterns with ROS 2 messages.
827
+ *
828
+ * @param {function|string|object} typeClass - The ROS message class,
829
+ * OR a string representing the message class, e.g. 'std_msgs/msg/String',
830
+ * OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
831
+ * @param {string} topic - The name of the topic.
832
+ * @param {object} [options] - The options argument used to parameterize the subscription.
833
+ * @param {boolean} [options.enableTypedArray=true] - The topic will use TypedArray if necessary.
834
+ * @param {QoS} [options.qos=QoS.profileDefault] - ROS Middleware "quality of service" settings.
835
+ * @param {boolean} [options.isRaw=false] - The topic is serialized when true.
836
+ * @param {string} [options.serializationMode='default'] - Controls message serialization format.
837
+ * @param {object} [options.contentFilter] - The content-filter (if supported by RMW).
838
+ * @param {SubscriptionEventCallbacks} [eventCallbacks] - The event callbacks for the subscription.
839
+ * @return {ObservableSubscription} - An ObservableSubscription with an RxJS Observable.
840
+ */
841
+ createObservableSubscription(typeClass, topic, options, eventCallbacks) {
842
+ let observableSubscription = null;
843
+
844
+ const subscription = this.createSubscription(
845
+ typeClass,
846
+ topic,
847
+ options,
848
+ (message) => {
849
+ if (observableSubscription) {
850
+ observableSubscription._emit(message);
851
+ }
852
+ },
853
+ eventCallbacks
854
+ );
855
+
856
+ observableSubscription = new ObservableSubscription(subscription);
857
+ return observableSubscription;
858
+ }
859
+
732
860
  /**
733
861
  * Create a Client.
734
862
  * @param {function|string|object} typeClass - The ROS message class,
@@ -746,8 +874,17 @@ class Node extends rclnodejs.ShadowNode {
746
874
  }
747
875
  options = this._validateOptions(options);
748
876
 
749
- if (typeof typeClass !== 'function' || typeof serviceName !== 'string') {
750
- throw new TypeError('Invalid argument');
877
+ if (typeof typeClass !== 'function') {
878
+ throw new TypeValidationError('typeClass', typeClass, 'function', {
879
+ nodeName: this.name(),
880
+ entityType: 'client',
881
+ });
882
+ }
883
+ if (typeof serviceName !== 'string') {
884
+ throw new TypeValidationError('serviceName', serviceName, 'string', {
885
+ nodeName: this.name(),
886
+ entityType: 'client',
887
+ });
751
888
  }
752
889
 
753
890
  let client = Client.createClient(
@@ -801,12 +938,24 @@ class Node extends rclnodejs.ShadowNode {
801
938
  }
802
939
  options = this._validateOptions(options);
803
940
 
804
- if (
805
- typeof typeClass !== 'function' ||
806
- typeof serviceName !== 'string' ||
807
- typeof callback !== 'function'
808
- ) {
809
- throw new TypeError('Invalid argument');
941
+ if (typeof typeClass !== 'function') {
942
+ throw new TypeValidationError('typeClass', typeClass, 'function', {
943
+ nodeName: this.name(),
944
+ entityType: 'service',
945
+ });
946
+ }
947
+ if (typeof serviceName !== 'string') {
948
+ throw new TypeValidationError('serviceName', serviceName, 'string', {
949
+ nodeName: this.name(),
950
+ entityType: 'service',
951
+ });
952
+ }
953
+ if (typeof callback !== 'function') {
954
+ throw new TypeValidationError('callback', callback, 'function', {
955
+ nodeName: this.name(),
956
+ entityType: 'service',
957
+ entityName: serviceName,
958
+ });
810
959
  }
811
960
 
812
961
  let service = Service.createService(
@@ -823,6 +972,52 @@ class Node extends rclnodejs.ShadowNode {
823
972
  return service;
824
973
  }
825
974
 
975
+ /**
976
+ * Create a ParameterClient for accessing parameters on a remote node.
977
+ * @param {string} remoteNodeName - The name of the remote node whose parameters to access.
978
+ * @param {object} [options] - Options for parameter client.
979
+ * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
980
+ * @return {ParameterClient} - An instance of ParameterClient.
981
+ */
982
+ createParameterClient(remoteNodeName, options = {}) {
983
+ if (typeof remoteNodeName !== 'string' || remoteNodeName.trim() === '') {
984
+ throw new TypeError('Remote node name must be a non-empty string');
985
+ }
986
+
987
+ const parameterClient = new ParameterClient(this, remoteNodeName, options);
988
+ debug(
989
+ 'Finish creating parameter client for remote node = %s.',
990
+ remoteNodeName
991
+ );
992
+ this._parameterClients.push(parameterClient);
993
+
994
+ return parameterClient;
995
+ }
996
+
997
+ /**
998
+ * Create a ParameterWatcher for watching parameter changes on a remote node.
999
+ * @param {string} remoteNodeName - The name of the remote node whose parameters to watch.
1000
+ * @param {string[]} parameterNames - Array of parameter names to watch.
1001
+ * @param {object} [options] - Options for parameter watcher.
1002
+ * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
1003
+ * @return {ParameterWatcher} - An instance of ParameterWatcher.
1004
+ */
1005
+ createParameterWatcher(remoteNodeName, parameterNames, options = {}) {
1006
+ const watcher = new ParameterWatcher(
1007
+ this,
1008
+ remoteNodeName,
1009
+ parameterNames,
1010
+ options
1011
+ );
1012
+ debug(
1013
+ 'Finish creating parameter watcher for remote node = %s.',
1014
+ remoteNodeName
1015
+ );
1016
+ this._parameterWatchers.push(watcher);
1017
+
1018
+ return watcher;
1019
+ }
1020
+
826
1021
  /**
827
1022
  * Create a guard condition.
828
1023
  * @param {Function} callback - The callback to be called when the guard condition is triggered.
@@ -830,7 +1025,10 @@ class Node extends rclnodejs.ShadowNode {
830
1025
  */
831
1026
  createGuardCondition(callback) {
832
1027
  if (typeof callback !== 'function') {
833
- throw new TypeError('Invalid argument');
1028
+ throw new TypeValidationError('callback', callback, 'function', {
1029
+ nodeName: this.name(),
1030
+ entityType: 'guard_condition',
1031
+ });
834
1032
  }
835
1033
 
836
1034
  let guard = GuardCondition.createGuardCondition(callback, this.context);
@@ -856,8 +1054,16 @@ class Node extends rclnodejs.ShadowNode {
856
1054
  this._actionClients.forEach((actionClient) => actionClient.destroy());
857
1055
  this._actionServers.forEach((actionServer) => actionServer.destroy());
858
1056
 
1057
+ this._parameterClients.forEach((paramClient) => paramClient.destroy());
1058
+ this._parameterWatchers.forEach((watcher) => watcher.destroy());
1059
+
859
1060
  this.context.onNodeDestroyed(this);
860
1061
 
1062
+ if (this._enableRosout) {
1063
+ rclnodejs.finiRosoutPublisherForNode(this.handle);
1064
+ this._enableRosout = false;
1065
+ }
1066
+
861
1067
  this.handle.release();
862
1068
  this._clock = null;
863
1069
  this._timers = [];
@@ -868,6 +1074,8 @@ class Node extends rclnodejs.ShadowNode {
868
1074
  this._guards = [];
869
1075
  this._actionClients = [];
870
1076
  this._actionServers = [];
1077
+ this._parameterClients = [];
1078
+ this._parameterWatchers = [];
871
1079
 
872
1080
  if (this._rateTimerServer) {
873
1081
  this._rateTimerServer.shutdown();
@@ -882,7 +1090,14 @@ class Node extends rclnodejs.ShadowNode {
882
1090
  */
883
1091
  destroyPublisher(publisher) {
884
1092
  if (!(publisher instanceof Publisher)) {
885
- throw new TypeError('Invalid argument');
1093
+ throw new TypeValidationError(
1094
+ 'publisher',
1095
+ publisher,
1096
+ 'Publisher instance',
1097
+ {
1098
+ nodeName: this.name(),
1099
+ }
1100
+ );
886
1101
  }
887
1102
  if (publisher.events) {
888
1103
  publisher.events.forEach((event) => {
@@ -900,7 +1115,14 @@ class Node extends rclnodejs.ShadowNode {
900
1115
  */
901
1116
  destroySubscription(subscription) {
902
1117
  if (!(subscription instanceof Subscription)) {
903
- throw new TypeError('Invalid argument');
1118
+ throw new TypeValidationError(
1119
+ 'subscription',
1120
+ subscription,
1121
+ 'Subscription instance',
1122
+ {
1123
+ nodeName: this.name(),
1124
+ }
1125
+ );
904
1126
  }
905
1127
  if (subscription.events) {
906
1128
  subscription.events.forEach((event) => {
@@ -919,7 +1141,9 @@ class Node extends rclnodejs.ShadowNode {
919
1141
  */
920
1142
  destroyClient(client) {
921
1143
  if (!(client instanceof Client)) {
922
- throw new TypeError('Invalid argument');
1144
+ throw new TypeValidationError('client', client, 'Client instance', {
1145
+ nodeName: this.name(),
1146
+ });
923
1147
  }
924
1148
  this._destroyEntity(client, this._clients);
925
1149
  }
@@ -931,11 +1155,39 @@ class Node extends rclnodejs.ShadowNode {
931
1155
  */
932
1156
  destroyService(service) {
933
1157
  if (!(service instanceof Service)) {
934
- throw new TypeError('Invalid argument');
1158
+ throw new TypeValidationError('service', service, 'Service instance', {
1159
+ nodeName: this.name(),
1160
+ });
935
1161
  }
936
1162
  this._destroyEntity(service, this._services);
937
1163
  }
938
1164
 
1165
+ /**
1166
+ * Destroy a ParameterClient.
1167
+ * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
1168
+ * @return {undefined}
1169
+ */
1170
+ destroyParameterClient(parameterClient) {
1171
+ if (!(parameterClient instanceof ParameterClient)) {
1172
+ throw new TypeError('Invalid argument');
1173
+ }
1174
+ this._removeEntityFromArray(parameterClient, this._parameterClients);
1175
+ parameterClient.destroy();
1176
+ }
1177
+
1178
+ /**
1179
+ * Destroy a ParameterWatcher.
1180
+ * @param {ParameterWatcher} watcher - The ParameterWatcher to be destroyed.
1181
+ * @return {undefined}
1182
+ */
1183
+ destroyParameterWatcher(watcher) {
1184
+ if (!(watcher instanceof ParameterWatcher)) {
1185
+ throw new TypeError('Invalid argument');
1186
+ }
1187
+ this._removeEntityFromArray(watcher, this._parameterWatchers);
1188
+ watcher.destroy();
1189
+ }
1190
+
939
1191
  /**
940
1192
  * Destroy a Timer.
941
1193
  * @param {Timer} timer - The Timer to be destroyed.
@@ -943,7 +1195,9 @@ class Node extends rclnodejs.ShadowNode {
943
1195
  */
944
1196
  destroyTimer(timer) {
945
1197
  if (!(timer instanceof Timer)) {
946
- throw new TypeError('Invalid argument');
1198
+ throw new TypeValidationError('timer', timer, 'Timer instance', {
1199
+ nodeName: this.name(),
1200
+ });
947
1201
  }
948
1202
  this._destroyEntity(timer, this._timers);
949
1203
  }
@@ -955,7 +1209,9 @@ class Node extends rclnodejs.ShadowNode {
955
1209
  */
956
1210
  destroyGuardCondition(guard) {
957
1211
  if (!(guard instanceof GuardCondition)) {
958
- throw new TypeError('Invalid argument');
1212
+ throw new TypeValidationError('guard', guard, 'GuardCondition instance', {
1213
+ nodeName: this.name(),
1214
+ });
959
1215
  }
960
1216
  this._destroyEntity(guard, this._guards);
961
1217
  }
@@ -1002,7 +1258,7 @@ class Node extends rclnodejs.ShadowNode {
1002
1258
 
1003
1259
  /**
1004
1260
  * Get the current time using the node's clock.
1005
- * @returns {Time} - The current time.
1261
+ * @returns {Timer} - The current time.
1006
1262
  */
1007
1263
  now() {
1008
1264
  return this.getClock().now();
@@ -1141,6 +1397,74 @@ class Node extends rclnodejs.ShadowNode {
1141
1397
  );
1142
1398
  }
1143
1399
 
1400
+ /**
1401
+ * Return a list of clients on a given service.
1402
+ *
1403
+ * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1404
+ * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1405
+ *
1406
+ * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1407
+ * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1408
+ * apps). When the `no_mangle` parameter is `false`, the provided `service` should
1409
+ * follow ROS service name conventions.
1410
+ *
1411
+ * `service` may be a relative, private, or fully qualified service name.
1412
+ * A relative or private service will be expanded using this node's namespace and name.
1413
+ * The queried `service` is not remapped.
1414
+ *
1415
+ * @param {string} service - The service on which to find the clients.
1416
+ * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1417
+ * name, otherwise it should be a valid ROS service name. Defaults to `false`.
1418
+ * @returns {Array} - list of clients
1419
+ */
1420
+ getClientsInfoByService(service, noDemangle = false) {
1421
+ if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
1422
+ console.warn(
1423
+ 'getClientsInfoByService is not supported by this version of ROS 2'
1424
+ );
1425
+ return null;
1426
+ }
1427
+ return rclnodejs.getClientsInfoByService(
1428
+ this.handle,
1429
+ this._getValidatedServiceName(service, noDemangle),
1430
+ noDemangle
1431
+ );
1432
+ }
1433
+
1434
+ /**
1435
+ * Return a list of servers on a given service.
1436
+ *
1437
+ * The returned parameter is a list of ServiceEndpointInfo objects, where each will contain
1438
+ * the node name, node namespace, service type, service endpoint's GID, and its QoS profile.
1439
+ *
1440
+ * When the `no_mangle` parameter is `true`, the provided `service` should be a valid
1441
+ * service name for the middleware (useful when combining ROS with native middleware (e.g. DDS)
1442
+ * apps). When the `no_mangle` parameter is `false`, the provided `service` should
1443
+ * follow ROS service name conventions.
1444
+ *
1445
+ * `service` may be a relative, private, or fully qualified service name.
1446
+ * A relative or private service will be expanded using this node's namespace and name.
1447
+ * The queried `service` is not remapped.
1448
+ *
1449
+ * @param {string} service - The service on which to find the servers.
1450
+ * @param {boolean} [noDemangle=false] - If `true`, `service` needs to be a valid middleware service
1451
+ * name, otherwise it should be a valid ROS service name. Defaults to `false`.
1452
+ * @returns {Array} - list of servers
1453
+ */
1454
+ getServersInfoByService(service, noDemangle = false) {
1455
+ if (DistroUtils.getDistroId() < DistroUtils.DistroId.ROLLING) {
1456
+ console.warn(
1457
+ 'getServersInfoByService is not supported by this version of ROS 2'
1458
+ );
1459
+ return null;
1460
+ }
1461
+ return rclnodejs.getServersInfoByService(
1462
+ this.handle,
1463
+ this._getValidatedServiceName(service, noDemangle),
1464
+ noDemangle
1465
+ );
1466
+ }
1467
+
1144
1468
  /**
1145
1469
  * Get the list of nodes discovered by the provided node.
1146
1470
  * @return {Array<string>} - An array of the names.
@@ -1247,14 +1571,14 @@ class Node extends rclnodejs.ShadowNode {
1247
1571
  *
1248
1572
  * @param {Parameter} parameter - Parameter to declare.
1249
1573
  * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1250
- * @param {boolean} [ignoreOveride] - When true disregard any parameter-override that may be present.
1574
+ * @param {boolean} [ignoreOverride] - When true disregard any parameter-override that may be present.
1251
1575
  * @return {Parameter} - The newly declared parameter.
1252
1576
  */
1253
- declareParameter(parameter, descriptor, ignoreOveride = false) {
1577
+ declareParameter(parameter, descriptor, ignoreOverride = false) {
1254
1578
  const parameters = this.declareParameters(
1255
1579
  [parameter],
1256
1580
  descriptor ? [descriptor] : [],
1257
- ignoreOveride
1581
+ ignoreOverride
1258
1582
  );
1259
1583
  return parameters.length == 1 ? parameters[0] : null;
1260
1584
  }
@@ -1295,16 +1619,25 @@ class Node extends rclnodejs.ShadowNode {
1295
1619
  */
1296
1620
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
1297
1621
  if (!Array.isArray(parameters)) {
1298
- throw new TypeError('Invalid parameter: expected array of Parameter');
1622
+ throw new TypeValidationError('parameters', parameters, 'Array', {
1623
+ nodeName: this.name(),
1624
+ });
1299
1625
  }
1300
1626
  if (!Array.isArray(descriptors)) {
1301
- throw new TypeError(
1302
- 'Invalid parameters: expected array of ParameterDescriptor'
1303
- );
1627
+ throw new TypeValidationError('descriptors', descriptors, 'Array', {
1628
+ nodeName: this.name(),
1629
+ });
1304
1630
  }
1305
1631
  if (descriptors.length > 0 && parameters.length !== descriptors.length) {
1306
- throw new TypeError(
1307
- 'Each parameter must have a cooresponding ParameterDescriptor'
1632
+ throw new ValidationError(
1633
+ 'Each parameter must have a corresponding ParameterDescriptor',
1634
+ {
1635
+ code: 'PARAMETER_DESCRIPTOR_MISMATCH',
1636
+ argumentName: 'descriptors',
1637
+ providedValue: descriptors.length,
1638
+ expectedType: `Array with length ${parameters.length}`,
1639
+ nodeName: this.name(),
1640
+ }
1308
1641
  );
1309
1642
  }
1310
1643
 
@@ -1744,7 +2077,9 @@ class Node extends rclnodejs.ShadowNode {
1744
2077
  */
1745
2078
  resolveTopicName(topicName, onlyExpand = false) {
1746
2079
  if (typeof topicName !== 'string') {
1747
- throw new TypeError('Invalid argument: expected string');
2080
+ throw new TypeValidationError('topicName', topicName, 'string', {
2081
+ nodeName: this.name(),
2082
+ });
1748
2083
  }
1749
2084
  return rclnodejs.resolveName(
1750
2085
  this.handle,
@@ -1762,7 +2097,9 @@ class Node extends rclnodejs.ShadowNode {
1762
2097
  */
1763
2098
  resolveServiceName(service, onlyExpand = false) {
1764
2099
  if (typeof service !== 'string') {
1765
- throw new TypeError('Invalid argument: expected string');
2100
+ throw new TypeValidationError('service', service, 'string', {
2101
+ nodeName: this.name(),
2102
+ });
1766
2103
  }
1767
2104
  return rclnodejs.resolveName(
1768
2105
  this.handle,
@@ -1921,6 +2258,22 @@ class Node extends rclnodejs.ShadowNode {
1921
2258
  validateFullTopicName(fqTopicName);
1922
2259
  return rclnodejs.remapTopicName(this.handle, fqTopicName);
1923
2260
  }
2261
+
2262
+ _getValidatedServiceName(serviceName, noDemangle) {
2263
+ if (typeof serviceName !== 'string') {
2264
+ throw new TypeValidationError('serviceName', serviceName, 'string', {
2265
+ nodeName: this.name(),
2266
+ });
2267
+ }
2268
+
2269
+ if (noDemangle) {
2270
+ return serviceName;
2271
+ }
2272
+
2273
+ const resolvedServiceName = this.resolveServiceName(serviceName);
2274
+ rclnodejs.validateTopicName(resolvedServiceName);
2275
+ return resolvedServiceName;
2276
+ }
1924
2277
  }
1925
2278
 
1926
2279
  /**