rclnodejs 1.6.0 → 1.7.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.
package/lib/node.js CHANGED
@@ -32,7 +32,14 @@ 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');
@@ -74,8 +81,11 @@ class Node extends rclnodejs.ShadowNode {
74
81
  ) {
75
82
  super();
76
83
 
77
- if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
78
- throw new TypeError('Invalid argument.');
84
+ if (typeof nodeName !== 'string') {
85
+ throw new TypeValidationError('nodeName', nodeName, 'string');
86
+ }
87
+ if (typeof namespace !== 'string') {
88
+ throw new TypeValidationError('namespace', namespace, 'string');
79
89
  }
80
90
 
81
91
  this._init(nodeName, namespace, options, context, args, useGlobalArguments);
@@ -111,6 +121,8 @@ class Node extends rclnodejs.ShadowNode {
111
121
  this._events = [];
112
122
  this._actionClients = [];
113
123
  this._actionServers = [];
124
+ this._parameterClients = [];
125
+ this._parameterWatchers = [];
114
126
  this._rateTimerServer = null;
115
127
  this._parameterDescriptors = new Map();
116
128
  this._parameters = new Map();
@@ -133,8 +145,13 @@ class Node extends rclnodejs.ShadowNode {
133
145
  if (options.parameterOverrides.length > 0) {
134
146
  for (const parameter of options.parameterOverrides) {
135
147
  if ((!parameter) instanceof Parameter) {
136
- throw new TypeError(
137
- 'Parameter-override must be an instance of Parameter.'
148
+ throw new TypeValidationError(
149
+ 'parameterOverride',
150
+ parameter,
151
+ 'Parameter instance',
152
+ {
153
+ nodeName: name,
154
+ }
138
155
  );
139
156
  }
140
157
  this._parameterOverrides.set(parameter.name, parameter);
@@ -514,7 +531,9 @@ class Node extends rclnodejs.ShadowNode {
514
531
  options !== undefined &&
515
532
  (options === null || typeof options !== 'object')
516
533
  ) {
517
- throw new TypeError('Invalid argument of options');
534
+ throw new TypeValidationError('options', options, 'object', {
535
+ nodeName: this.name(),
536
+ });
518
537
  }
519
538
 
520
539
  if (options === undefined) {
@@ -536,8 +555,15 @@ class Node extends rclnodejs.ShadowNode {
536
555
  if (options.serializationMode === undefined) {
537
556
  options = Object.assign(options, { serializationMode: 'default' });
538
557
  } else if (!isValidSerializationMode(options.serializationMode)) {
539
- throw new TypeError(
540
- `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`
558
+ throw new ValidationError(
559
+ `Invalid serializationMode: ${options.serializationMode}. Valid modes are: 'default', 'plain', 'json'`,
560
+ {
561
+ code: 'INVALID_SERIALIZATION_MODE',
562
+ argumentName: 'serializationMode',
563
+ providedValue: options.serializationMode,
564
+ expectedType: "'default' | 'plain' | 'json'",
565
+ nodeName: this.name(),
566
+ }
541
567
  );
542
568
  }
543
569
 
@@ -558,8 +584,15 @@ class Node extends rclnodejs.ShadowNode {
558
584
  clock = arguments[3];
559
585
  }
560
586
 
561
- if (typeof period !== 'bigint' || typeof callback !== 'function') {
562
- throw new TypeError('Invalid argument');
587
+ if (typeof period !== 'bigint') {
588
+ throw new TypeValidationError('period', period, 'bigint', {
589
+ nodeName: this.name(),
590
+ });
591
+ }
592
+ if (typeof callback !== 'function') {
593
+ throw new TypeValidationError('callback', callback, 'function', {
594
+ nodeName: this.name(),
595
+ });
563
596
  }
564
597
 
565
598
  const timerClock = clock || this._clock;
@@ -584,13 +617,20 @@ class Node extends rclnodejs.ShadowNode {
584
617
  */
585
618
  async createRate(hz = 1) {
586
619
  if (typeof hz !== 'number') {
587
- throw new TypeError('Invalid argument');
620
+ throw new TypeValidationError('hz', hz, 'number', {
621
+ nodeName: this.name(),
622
+ });
588
623
  }
589
624
 
590
625
  const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
591
626
  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}`
627
+ throw new RangeValidationError(
628
+ 'hz',
629
+ hz,
630
+ `0.0 < hz <= ${MAX_RATE_HZ_IN_MILLISECOND}`,
631
+ {
632
+ nodeName: this.name(),
633
+ }
594
634
  );
595
635
  }
596
636
 
@@ -635,12 +675,32 @@ class Node extends rclnodejs.ShadowNode {
635
675
  }
636
676
  options = this._validateOptions(options);
637
677
 
678
+ if (typeof typeClass !== 'function') {
679
+ throw new TypeValidationError('typeClass', typeClass, 'function', {
680
+ nodeName: this.name(),
681
+ entityType: 'publisher',
682
+ });
683
+ }
684
+ if (typeof topic !== 'string') {
685
+ throw new TypeValidationError('topic', topic, 'string', {
686
+ nodeName: this.name(),
687
+ entityType: 'publisher',
688
+ });
689
+ }
638
690
  if (
639
- typeof typeClass !== 'function' ||
640
- typeof topic !== 'string' ||
641
- (eventCallbacks && !(eventCallbacks instanceof PublisherEventCallbacks))
691
+ eventCallbacks &&
692
+ !(eventCallbacks instanceof PublisherEventCallbacks)
642
693
  ) {
643
- throw new TypeError('Invalid argument');
694
+ throw new TypeValidationError(
695
+ 'eventCallbacks',
696
+ eventCallbacks,
697
+ 'PublisherEventCallbacks',
698
+ {
699
+ nodeName: this.name(),
700
+ entityType: 'publisher',
701
+ entityName: topic,
702
+ }
703
+ );
644
704
  }
645
705
 
646
706
  let publisher = publisherClass.createPublisher(
@@ -704,14 +764,39 @@ class Node extends rclnodejs.ShadowNode {
704
764
  }
705
765
  options = this._validateOptions(options);
706
766
 
767
+ if (typeof typeClass !== 'function') {
768
+ throw new TypeValidationError('typeClass', typeClass, 'function', {
769
+ nodeName: this.name(),
770
+ entityType: 'subscription',
771
+ });
772
+ }
773
+ if (typeof topic !== 'string') {
774
+ throw new TypeValidationError('topic', topic, 'string', {
775
+ nodeName: this.name(),
776
+ entityType: 'subscription',
777
+ });
778
+ }
779
+ if (typeof callback !== 'function') {
780
+ throw new TypeValidationError('callback', callback, 'function', {
781
+ nodeName: this.name(),
782
+ entityType: 'subscription',
783
+ entityName: topic,
784
+ });
785
+ }
707
786
  if (
708
- typeof typeClass !== 'function' ||
709
- typeof topic !== 'string' ||
710
- typeof callback !== 'function' ||
711
- (eventCallbacks &&
712
- !(eventCallbacks instanceof SubscriptionEventCallbacks))
787
+ eventCallbacks &&
788
+ !(eventCallbacks instanceof SubscriptionEventCallbacks)
713
789
  ) {
714
- throw new TypeError('Invalid argument');
790
+ throw new TypeValidationError(
791
+ 'eventCallbacks',
792
+ eventCallbacks,
793
+ 'SubscriptionEventCallbacks',
794
+ {
795
+ nodeName: this.name(),
796
+ entityType: 'subscription',
797
+ entityName: topic,
798
+ }
799
+ );
715
800
  }
716
801
 
717
802
  let subscription = Subscription.createSubscription(
@@ -746,8 +831,17 @@ class Node extends rclnodejs.ShadowNode {
746
831
  }
747
832
  options = this._validateOptions(options);
748
833
 
749
- if (typeof typeClass !== 'function' || typeof serviceName !== 'string') {
750
- throw new TypeError('Invalid argument');
834
+ if (typeof typeClass !== 'function') {
835
+ throw new TypeValidationError('typeClass', typeClass, 'function', {
836
+ nodeName: this.name(),
837
+ entityType: 'client',
838
+ });
839
+ }
840
+ if (typeof serviceName !== 'string') {
841
+ throw new TypeValidationError('serviceName', serviceName, 'string', {
842
+ nodeName: this.name(),
843
+ entityType: 'client',
844
+ });
751
845
  }
752
846
 
753
847
  let client = Client.createClient(
@@ -801,12 +895,24 @@ class Node extends rclnodejs.ShadowNode {
801
895
  }
802
896
  options = this._validateOptions(options);
803
897
 
804
- if (
805
- typeof typeClass !== 'function' ||
806
- typeof serviceName !== 'string' ||
807
- typeof callback !== 'function'
808
- ) {
809
- throw new TypeError('Invalid argument');
898
+ if (typeof typeClass !== 'function') {
899
+ throw new TypeValidationError('typeClass', typeClass, 'function', {
900
+ nodeName: this.name(),
901
+ entityType: 'service',
902
+ });
903
+ }
904
+ if (typeof serviceName !== 'string') {
905
+ throw new TypeValidationError('serviceName', serviceName, 'string', {
906
+ nodeName: this.name(),
907
+ entityType: 'service',
908
+ });
909
+ }
910
+ if (typeof callback !== 'function') {
911
+ throw new TypeValidationError('callback', callback, 'function', {
912
+ nodeName: this.name(),
913
+ entityType: 'service',
914
+ entityName: serviceName,
915
+ });
810
916
  }
811
917
 
812
918
  let service = Service.createService(
@@ -823,6 +929,52 @@ class Node extends rclnodejs.ShadowNode {
823
929
  return service;
824
930
  }
825
931
 
932
+ /**
933
+ * Create a ParameterClient for accessing parameters on a remote node.
934
+ * @param {string} remoteNodeName - The name of the remote node whose parameters to access.
935
+ * @param {object} [options] - Options for parameter client.
936
+ * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
937
+ * @return {ParameterClient} - An instance of ParameterClient.
938
+ */
939
+ createParameterClient(remoteNodeName, options = {}) {
940
+ if (typeof remoteNodeName !== 'string' || remoteNodeName.trim() === '') {
941
+ throw new TypeError('Remote node name must be a non-empty string');
942
+ }
943
+
944
+ const parameterClient = new ParameterClient(this, remoteNodeName, options);
945
+ debug(
946
+ 'Finish creating parameter client for remote node = %s.',
947
+ remoteNodeName
948
+ );
949
+ this._parameterClients.push(parameterClient);
950
+
951
+ return parameterClient;
952
+ }
953
+
954
+ /**
955
+ * Create a ParameterWatcher for watching parameter changes on a remote node.
956
+ * @param {string} remoteNodeName - The name of the remote node whose parameters to watch.
957
+ * @param {string[]} parameterNames - Array of parameter names to watch.
958
+ * @param {object} [options] - Options for parameter watcher.
959
+ * @param {number} [options.timeout=5000] - Default timeout in milliseconds for service calls.
960
+ * @return {ParameterWatcher} - An instance of ParameterWatcher.
961
+ */
962
+ createParameterWatcher(remoteNodeName, parameterNames, options = {}) {
963
+ const watcher = new ParameterWatcher(
964
+ this,
965
+ remoteNodeName,
966
+ parameterNames,
967
+ options
968
+ );
969
+ debug(
970
+ 'Finish creating parameter watcher for remote node = %s.',
971
+ remoteNodeName
972
+ );
973
+ this._parameterWatchers.push(watcher);
974
+
975
+ return watcher;
976
+ }
977
+
826
978
  /**
827
979
  * Create a guard condition.
828
980
  * @param {Function} callback - The callback to be called when the guard condition is triggered.
@@ -830,7 +982,10 @@ class Node extends rclnodejs.ShadowNode {
830
982
  */
831
983
  createGuardCondition(callback) {
832
984
  if (typeof callback !== 'function') {
833
- throw new TypeError('Invalid argument');
985
+ throw new TypeValidationError('callback', callback, 'function', {
986
+ nodeName: this.name(),
987
+ entityType: 'guard_condition',
988
+ });
834
989
  }
835
990
 
836
991
  let guard = GuardCondition.createGuardCondition(callback, this.context);
@@ -856,6 +1011,9 @@ class Node extends rclnodejs.ShadowNode {
856
1011
  this._actionClients.forEach((actionClient) => actionClient.destroy());
857
1012
  this._actionServers.forEach((actionServer) => actionServer.destroy());
858
1013
 
1014
+ this._parameterClients.forEach((paramClient) => paramClient.destroy());
1015
+ this._parameterWatchers.forEach((watcher) => watcher.destroy());
1016
+
859
1017
  this.context.onNodeDestroyed(this);
860
1018
 
861
1019
  this.handle.release();
@@ -868,6 +1026,8 @@ class Node extends rclnodejs.ShadowNode {
868
1026
  this._guards = [];
869
1027
  this._actionClients = [];
870
1028
  this._actionServers = [];
1029
+ this._parameterClients = [];
1030
+ this._parameterWatchers = [];
871
1031
 
872
1032
  if (this._rateTimerServer) {
873
1033
  this._rateTimerServer.shutdown();
@@ -882,7 +1042,14 @@ class Node extends rclnodejs.ShadowNode {
882
1042
  */
883
1043
  destroyPublisher(publisher) {
884
1044
  if (!(publisher instanceof Publisher)) {
885
- throw new TypeError('Invalid argument');
1045
+ throw new TypeValidationError(
1046
+ 'publisher',
1047
+ publisher,
1048
+ 'Publisher instance',
1049
+ {
1050
+ nodeName: this.name(),
1051
+ }
1052
+ );
886
1053
  }
887
1054
  if (publisher.events) {
888
1055
  publisher.events.forEach((event) => {
@@ -900,7 +1067,14 @@ class Node extends rclnodejs.ShadowNode {
900
1067
  */
901
1068
  destroySubscription(subscription) {
902
1069
  if (!(subscription instanceof Subscription)) {
903
- throw new TypeError('Invalid argument');
1070
+ throw new TypeValidationError(
1071
+ 'subscription',
1072
+ subscription,
1073
+ 'Subscription instance',
1074
+ {
1075
+ nodeName: this.name(),
1076
+ }
1077
+ );
904
1078
  }
905
1079
  if (subscription.events) {
906
1080
  subscription.events.forEach((event) => {
@@ -919,7 +1093,9 @@ class Node extends rclnodejs.ShadowNode {
919
1093
  */
920
1094
  destroyClient(client) {
921
1095
  if (!(client instanceof Client)) {
922
- throw new TypeError('Invalid argument');
1096
+ throw new TypeValidationError('client', client, 'Client instance', {
1097
+ nodeName: this.name(),
1098
+ });
923
1099
  }
924
1100
  this._destroyEntity(client, this._clients);
925
1101
  }
@@ -931,11 +1107,39 @@ class Node extends rclnodejs.ShadowNode {
931
1107
  */
932
1108
  destroyService(service) {
933
1109
  if (!(service instanceof Service)) {
934
- throw new TypeError('Invalid argument');
1110
+ throw new TypeValidationError('service', service, 'Service instance', {
1111
+ nodeName: this.name(),
1112
+ });
935
1113
  }
936
1114
  this._destroyEntity(service, this._services);
937
1115
  }
938
1116
 
1117
+ /**
1118
+ * Destroy a ParameterClient.
1119
+ * @param {ParameterClient} parameterClient - The ParameterClient to be destroyed.
1120
+ * @return {undefined}
1121
+ */
1122
+ destroyParameterClient(parameterClient) {
1123
+ if (!(parameterClient instanceof ParameterClient)) {
1124
+ throw new TypeError('Invalid argument');
1125
+ }
1126
+ this._removeEntityFromArray(parameterClient, this._parameterClients);
1127
+ parameterClient.destroy();
1128
+ }
1129
+
1130
+ /**
1131
+ * Destroy a ParameterWatcher.
1132
+ * @param {ParameterWatcher} watcher - The ParameterWatcher to be destroyed.
1133
+ * @return {undefined}
1134
+ */
1135
+ destroyParameterWatcher(watcher) {
1136
+ if (!(watcher instanceof ParameterWatcher)) {
1137
+ throw new TypeError('Invalid argument');
1138
+ }
1139
+ this._removeEntityFromArray(watcher, this._parameterWatchers);
1140
+ watcher.destroy();
1141
+ }
1142
+
939
1143
  /**
940
1144
  * Destroy a Timer.
941
1145
  * @param {Timer} timer - The Timer to be destroyed.
@@ -943,7 +1147,9 @@ class Node extends rclnodejs.ShadowNode {
943
1147
  */
944
1148
  destroyTimer(timer) {
945
1149
  if (!(timer instanceof Timer)) {
946
- throw new TypeError('Invalid argument');
1150
+ throw new TypeValidationError('timer', timer, 'Timer instance', {
1151
+ nodeName: this.name(),
1152
+ });
947
1153
  }
948
1154
  this._destroyEntity(timer, this._timers);
949
1155
  }
@@ -955,7 +1161,9 @@ class Node extends rclnodejs.ShadowNode {
955
1161
  */
956
1162
  destroyGuardCondition(guard) {
957
1163
  if (!(guard instanceof GuardCondition)) {
958
- throw new TypeError('Invalid argument');
1164
+ throw new TypeValidationError('guard', guard, 'GuardCondition instance', {
1165
+ nodeName: this.name(),
1166
+ });
959
1167
  }
960
1168
  this._destroyEntity(guard, this._guards);
961
1169
  }
@@ -1002,7 +1210,7 @@ class Node extends rclnodejs.ShadowNode {
1002
1210
 
1003
1211
  /**
1004
1212
  * Get the current time using the node's clock.
1005
- * @returns {Time} - The current time.
1213
+ * @returns {Timer} - The current time.
1006
1214
  */
1007
1215
  now() {
1008
1216
  return this.getClock().now();
@@ -1247,14 +1455,14 @@ class Node extends rclnodejs.ShadowNode {
1247
1455
  *
1248
1456
  * @param {Parameter} parameter - Parameter to declare.
1249
1457
  * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
1250
- * @param {boolean} [ignoreOveride] - When true disregard any parameter-override that may be present.
1458
+ * @param {boolean} [ignoreOverride] - When true disregard any parameter-override that may be present.
1251
1459
  * @return {Parameter} - The newly declared parameter.
1252
1460
  */
1253
- declareParameter(parameter, descriptor, ignoreOveride = false) {
1461
+ declareParameter(parameter, descriptor, ignoreOverride = false) {
1254
1462
  const parameters = this.declareParameters(
1255
1463
  [parameter],
1256
1464
  descriptor ? [descriptor] : [],
1257
- ignoreOveride
1465
+ ignoreOverride
1258
1466
  );
1259
1467
  return parameters.length == 1 ? parameters[0] : null;
1260
1468
  }
@@ -1295,16 +1503,25 @@ class Node extends rclnodejs.ShadowNode {
1295
1503
  */
1296
1504
  declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
1297
1505
  if (!Array.isArray(parameters)) {
1298
- throw new TypeError('Invalid parameter: expected array of Parameter');
1506
+ throw new TypeValidationError('parameters', parameters, 'Array', {
1507
+ nodeName: this.name(),
1508
+ });
1299
1509
  }
1300
1510
  if (!Array.isArray(descriptors)) {
1301
- throw new TypeError(
1302
- 'Invalid parameters: expected array of ParameterDescriptor'
1303
- );
1511
+ throw new TypeValidationError('descriptors', descriptors, 'Array', {
1512
+ nodeName: this.name(),
1513
+ });
1304
1514
  }
1305
1515
  if (descriptors.length > 0 && parameters.length !== descriptors.length) {
1306
- throw new TypeError(
1307
- 'Each parameter must have a cooresponding ParameterDescriptor'
1516
+ throw new ValidationError(
1517
+ 'Each parameter must have a corresponding ParameterDescriptor',
1518
+ {
1519
+ code: 'PARAMETER_DESCRIPTOR_MISMATCH',
1520
+ argumentName: 'descriptors',
1521
+ providedValue: descriptors.length,
1522
+ expectedType: `Array with length ${parameters.length}`,
1523
+ nodeName: this.name(),
1524
+ }
1308
1525
  );
1309
1526
  }
1310
1527
 
@@ -1744,7 +1961,9 @@ class Node extends rclnodejs.ShadowNode {
1744
1961
  */
1745
1962
  resolveTopicName(topicName, onlyExpand = false) {
1746
1963
  if (typeof topicName !== 'string') {
1747
- throw new TypeError('Invalid argument: expected string');
1964
+ throw new TypeValidationError('topicName', topicName, 'string', {
1965
+ nodeName: this.name(),
1966
+ });
1748
1967
  }
1749
1968
  return rclnodejs.resolveName(
1750
1969
  this.handle,
@@ -1762,7 +1981,9 @@ class Node extends rclnodejs.ShadowNode {
1762
1981
  */
1763
1982
  resolveServiceName(service, onlyExpand = false) {
1764
1983
  if (typeof service !== 'string') {
1765
- throw new TypeError('Invalid argument: expected string');
1984
+ throw new TypeValidationError('service', service, 'string', {
1985
+ nodeName: this.name(),
1986
+ });
1766
1987
  }
1767
1988
  return rclnodejs.resolveName(
1768
1989
  this.handle,