Source: lib/node.js

  1. // Copyright (c) 2017 Intel Corporation. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. 'use strict';
  15. const rclnodejs = require('bindings')('rclnodejs');
  16. const ActionInterfaces = require('./action/interfaces.js');
  17. const Client = require('./client.js');
  18. const Clock = require('./clock.js');
  19. const Context = require('./context.js');
  20. const debug = require('debug')('rclnodejs:node');
  21. const GuardCondition = require('./guard_condition.js');
  22. const loader = require('./interface_loader.js');
  23. const Logging = require('./logging.js');
  24. const NodeOptions = require('./node_options.js');
  25. const {
  26. ParameterType,
  27. Parameter,
  28. ParameterDescriptor,
  29. } = require('./parameter.js');
  30. const ParameterService = require('./parameter_service.js');
  31. const Publisher = require('./publisher.js');
  32. const QoS = require('./qos.js');
  33. const Rates = require('./rate.js');
  34. const Service = require('./service.js');
  35. const Subscription = require('./subscription.js');
  36. const TimeSource = require('./time_source.js');
  37. const Timer = require('./timer.js');
  38. const Entity = require('./entity.js');
  39. // Parameter event publisher constants
  40. const PARAMETER_EVENT_MSG_TYPE = 'rcl_interfaces/msg/ParameterEvent';
  41. const PARAMETER_EVENT_TOPIC = 'parameter_events';
  42. /**
  43. * @class - Class representing a Node in ROS
  44. */
  45. class Node extends rclnodejs.ShadowNode {
  46. /**
  47. * Create a ROS2Node.
  48. * model using the {@link https://github.com/ros2/rcl/tree/master/rcl_lifecycle|ros2 client library (rcl) lifecyle api}.
  49. * @param {string} nodeName - The name used to register in ROS.
  50. * @param {string} [namespace=''] - The namespace used in ROS.
  51. * @param {Context} [context=Context.defaultContext()] - The context to create the node in.
  52. * @param {NodeOptions} [options=NodeOptions.defaultOptions] - The options to configure the new node behavior.
  53. * @throws {Error} If the given context is not registered.
  54. */
  55. constructor(
  56. nodeName,
  57. namespace = '',
  58. context = Context.defaultContext(),
  59. options = NodeOptions.defaultOptions
  60. ) {
  61. super();
  62. if (typeof nodeName !== 'string' || typeof namespace !== 'string') {
  63. throw new TypeError('Invalid argument.');
  64. }
  65. this._init(nodeName, namespace, options, context);
  66. debug(
  67. 'Finish initializing node, name = %s and namespace = %s.',
  68. nodeName,
  69. namespace
  70. );
  71. }
  72. _init(name, namespace, options, context) {
  73. this.handle = rclnodejs.createNode(name, namespace, context.handle);
  74. Object.defineProperty(this, 'handle', {
  75. configurable: false,
  76. writable: false,
  77. }); // make read-only
  78. this._context = context;
  79. this.context.onNodeCreated(this);
  80. this._publishers = [];
  81. this._subscriptions = [];
  82. this._clients = [];
  83. this._services = [];
  84. this._timers = [];
  85. this._guards = [];
  86. this._actionClients = [];
  87. this._actionServers = [];
  88. this._rateTimerServer = null;
  89. this._parameterDescriptors = new Map();
  90. this._parameters = new Map();
  91. this._parameterService = null;
  92. this._parameterEventPublisher = null;
  93. this._setParametersCallbacks = [];
  94. this._logger = new Logging(rclnodejs.getNodeLoggerName(this.handle));
  95. this._spinning = false;
  96. this._parameterEventPublisher = this.createPublisher(
  97. PARAMETER_EVENT_MSG_TYPE,
  98. PARAMETER_EVENT_TOPIC
  99. );
  100. // initialize _parameterOverrides from parameters defined on the commandline
  101. this._parameterOverrides = this._getNativeParameterOverrides();
  102. // override cli parameterOverrides with those specified in options
  103. if (options.parameterOverrides.length > 0) {
  104. for (const parameter of options.parameterOverrides) {
  105. if ((!parameter) instanceof Parameter) {
  106. throw new TypeError(
  107. 'Parameter-override must be an instance of Parameter.'
  108. );
  109. }
  110. this._parameterOverrides.set(parameter.name, parameter);
  111. }
  112. }
  113. // initialize _parameters from parameterOverrides
  114. if (options.automaticallyDeclareParametersFromOverrides) {
  115. for (const parameter of this._parameterOverrides.values()) {
  116. parameter.validate();
  117. const descriptor = ParameterDescriptor.fromParameter(parameter);
  118. this._parameters.set(parameter.name, parameter);
  119. this._parameterDescriptors.set(parameter.name, descriptor);
  120. }
  121. }
  122. // Clock that has support for ROS time.
  123. // Note: parameter overrides and parameter event publisher need to be ready at this point
  124. // to be able to declare 'use_sim_time' if it was not declared yet.
  125. this._clock = new Clock.ROSClock();
  126. this._timeSource = new TimeSource(this);
  127. this._timeSource.attachClock(this._clock);
  128. if (options.startParameterServices) {
  129. this._parameterService = new ParameterService(this);
  130. this._parameterService.start();
  131. }
  132. }
  133. execute(handles) {
  134. let timersReady = this._timers.filter((timer) =>
  135. handles.includes(timer.handle)
  136. );
  137. let guardsReady = this._guards.filter((guard) =>
  138. handles.includes(guard.handle)
  139. );
  140. let subscriptionsReady = this._subscriptions.filter((subscription) =>
  141. handles.includes(subscription.handle)
  142. );
  143. let clientsReady = this._clients.filter((client) =>
  144. handles.includes(client.handle)
  145. );
  146. let servicesReady = this._services.filter((service) =>
  147. handles.includes(service.handle)
  148. );
  149. let actionClientsReady = this._actionClients.filter((actionClient) =>
  150. handles.includes(actionClient.handle)
  151. );
  152. let actionServersReady = this._actionServers.filter((actionServer) =>
  153. handles.includes(actionServer.handle)
  154. );
  155. timersReady.forEach((timer) => {
  156. if (timer.isReady()) {
  157. rclnodejs.callTimer(timer.handle);
  158. timer.callback();
  159. }
  160. });
  161. for (const subscription of subscriptionsReady) {
  162. if (subscription.isDestroyed()) continue;
  163. if (subscription.isRaw) {
  164. let rawMessage = rclnodejs.rclTakeRaw(subscription.handle);
  165. if (rawMessage) {
  166. subscription.processResponse(rawMessage);
  167. }
  168. continue;
  169. }
  170. this._runWithMessageType(
  171. subscription.typeClass,
  172. (message, deserialize) => {
  173. let success = rclnodejs.rclTake(subscription.handle, message);
  174. if (success) {
  175. subscription.processResponse(deserialize());
  176. }
  177. }
  178. );
  179. }
  180. for (const guard of guardsReady) {
  181. if (guard.isDestroyed()) continue;
  182. guard.callback();
  183. }
  184. for (const client of clientsReady) {
  185. if (client.isDestroyed()) continue;
  186. this._runWithMessageType(
  187. client.typeClass.Response,
  188. (message, deserialize) => {
  189. let sequenceNumber = rclnodejs.rclTakeResponse(
  190. client.handle,
  191. message
  192. );
  193. if (sequenceNumber !== undefined) {
  194. client.processResponse(sequenceNumber, deserialize());
  195. }
  196. }
  197. );
  198. }
  199. for (const service of servicesReady) {
  200. if (service.isDestroyed()) continue;
  201. this._runWithMessageType(
  202. service.typeClass.Request,
  203. (message, deserialize) => {
  204. let header = rclnodejs.rclTakeRequest(
  205. service.handle,
  206. this.handle,
  207. message
  208. );
  209. if (header) {
  210. service.processRequest(header, deserialize());
  211. }
  212. }
  213. );
  214. }
  215. for (const actionClient of actionClientsReady) {
  216. if (actionClient.isDestroyed()) continue;
  217. const properties = actionClient.handle.properties;
  218. if (properties.isGoalResponseReady) {
  219. this._runWithMessageType(
  220. actionClient.typeClass.impl.SendGoalService.Response,
  221. (message, deserialize) => {
  222. let sequence = rclnodejs.actionTakeGoalResponse(
  223. actionClient.handle,
  224. message
  225. );
  226. if (sequence != undefined) {
  227. actionClient.processGoalResponse(sequence, deserialize());
  228. }
  229. }
  230. );
  231. }
  232. if (properties.isCancelResponseReady) {
  233. this._runWithMessageType(
  234. actionClient.typeClass.impl.CancelGoal.Response,
  235. (message, deserialize) => {
  236. let sequence = rclnodejs.actionTakeCancelResponse(
  237. actionClient.handle,
  238. message
  239. );
  240. if (sequence != undefined) {
  241. actionClient.processCancelResponse(sequence, deserialize());
  242. }
  243. }
  244. );
  245. }
  246. if (properties.isResultResponseReady) {
  247. this._runWithMessageType(
  248. actionClient.typeClass.impl.GetResultService.Response,
  249. (message, deserialize) => {
  250. let sequence = rclnodejs.actionTakeResultResponse(
  251. actionClient.handle,
  252. message
  253. );
  254. if (sequence != undefined) {
  255. actionClient.processResultResponse(sequence, deserialize());
  256. }
  257. }
  258. );
  259. }
  260. if (properties.isFeedbackReady) {
  261. this._runWithMessageType(
  262. actionClient.typeClass.impl.FeedbackMessage,
  263. (message, deserialize) => {
  264. let success = rclnodejs.actionTakeFeedback(
  265. actionClient.handle,
  266. message
  267. );
  268. if (success) {
  269. actionClient.processFeedbackMessage(deserialize());
  270. }
  271. }
  272. );
  273. }
  274. if (properties.isStatusReady) {
  275. this._runWithMessageType(
  276. actionClient.typeClass.impl.GoalStatusArray,
  277. (message, deserialize) => {
  278. let success = rclnodejs.actionTakeStatus(
  279. actionClient.handle,
  280. message
  281. );
  282. if (success) {
  283. actionClient.processStatusMessage(deserialize());
  284. }
  285. }
  286. );
  287. }
  288. }
  289. for (const actionServer of actionServersReady) {
  290. if (actionServer.isDestroyed()) continue;
  291. const properties = actionServer.handle.properties;
  292. if (properties.isGoalRequestReady) {
  293. this._runWithMessageType(
  294. actionServer.typeClass.impl.SendGoalService.Request,
  295. (message, deserialize) => {
  296. const result = rclnodejs.actionTakeGoalRequest(
  297. actionServer.handle,
  298. message
  299. );
  300. if (result) {
  301. actionServer.processGoalRequest(result, deserialize());
  302. }
  303. }
  304. );
  305. }
  306. if (properties.isCancelRequestReady) {
  307. this._runWithMessageType(
  308. actionServer.typeClass.impl.CancelGoal.Request,
  309. (message, deserialize) => {
  310. const result = rclnodejs.actionTakeCancelRequest(
  311. actionServer.handle,
  312. message
  313. );
  314. if (result) {
  315. actionServer.processCancelRequest(result, deserialize());
  316. }
  317. }
  318. );
  319. }
  320. if (properties.isResultRequestReady) {
  321. this._runWithMessageType(
  322. actionServer.typeClass.impl.GetResultService.Request,
  323. (message, deserialize) => {
  324. const result = rclnodejs.actionTakeResultRequest(
  325. actionServer.handle,
  326. message
  327. );
  328. if (result) {
  329. actionServer.processResultRequest(result, deserialize());
  330. }
  331. }
  332. );
  333. }
  334. if (properties.isGoalExpired) {
  335. let GoalInfoArray = ActionInterfaces.GoalInfo.ArrayType;
  336. let message = new GoalInfoArray(actionServer._goalHandles.size);
  337. let count = rclnodejs.actionExpireGoals(
  338. actionServer.handle,
  339. actionServer._goalHandles.size,
  340. message._refArray.buffer
  341. );
  342. if (count > 0) {
  343. actionServer.processGoalExpired(message, count);
  344. }
  345. GoalInfoArray.freeArray(message);
  346. }
  347. }
  348. // At this point it is safe to clear the cache of any
  349. // destroyed entity references
  350. Entity._gcHandles();
  351. }
  352. /**
  353. * Determine if this node is spinning.
  354. * @returns {boolean} - true when spinning; otherwise returns false.
  355. */
  356. get spinning() {
  357. return this._spinning;
  358. }
  359. /**
  360. * Trigger the event loop to continuously check for and route.
  361. * incoming events.
  362. * @param {Node} node - The node to be spun up.
  363. * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
  364. * @throws {Error} If the node is already spinning.
  365. * @return {undefined}
  366. */
  367. spin(timeout = 10) {
  368. if (this.spinning) {
  369. throw new Error('The node is already spinning.');
  370. }
  371. this.start(this.context.handle, timeout);
  372. this._spinning = true;
  373. }
  374. /**
  375. * Use spin().
  376. * @deprecated, since 0.18.0
  377. */
  378. startSpinning(timeout) {
  379. this.spin(timeout);
  380. }
  381. /**
  382. * Terminate spinning - no further events will be received.
  383. * @returns {undefined}
  384. */
  385. stop() {
  386. super.stop();
  387. this._spinning = false;
  388. }
  389. /**
  390. * Terminate spinning - no further events will be received.
  391. * @returns {undefined}
  392. * @deprecated since 0.18.0, Use stop().
  393. */
  394. stopSpinning() {
  395. super.stop();
  396. this._spinning = false;
  397. }
  398. /**
  399. * Spin the node and trigger the event loop to check for one incoming event. Thereafter the node
  400. * will not received additional events until running additional calls to spin() or spinOnce().
  401. * @param {Node} node - The node to be spun.
  402. * @param {number} [timeout=10] - Timeout to wait in milliseconds. Block forever if negative. Don't wait if 0.
  403. * @throws {Error} If the node is already spinning.
  404. * @return {undefined}
  405. */
  406. spinOnce(timeout = 10) {
  407. if (this.spinning) {
  408. throw new Error('The node is already spinning.');
  409. }
  410. super.spinOnce(this.context.handle, timeout);
  411. }
  412. _removeEntityFromArray(entity, array) {
  413. let index = array.indexOf(entity);
  414. if (index > -1) {
  415. array.splice(index, 1);
  416. }
  417. }
  418. _destroyEntity(entity, array, syncHandles = true) {
  419. if (entity['isDestroyed'] && entity.isDestroyed()) return;
  420. this._removeEntityFromArray(entity, array);
  421. if (syncHandles) {
  422. this.syncHandles();
  423. }
  424. if (entity['_destroy']) {
  425. entity._destroy();
  426. } else {
  427. // guards and timers
  428. entity.handle.release();
  429. }
  430. }
  431. _validateOptions(options) {
  432. if (
  433. options !== undefined &&
  434. (options === null || typeof options !== 'object')
  435. ) {
  436. throw new TypeError('Invalid argument of options');
  437. }
  438. if (options === undefined) {
  439. return Node.getDefaultOptions();
  440. }
  441. if (options.enableTypedArray === undefined) {
  442. options = Object.assign(options, { enableTypedArray: true });
  443. }
  444. if (options.qos === undefined) {
  445. options = Object.assign(options, { qos: QoS.profileDefault });
  446. }
  447. if (options.isRaw === undefined) {
  448. options = Object.assign(options, { isRaw: false });
  449. }
  450. return options;
  451. }
  452. /**
  453. * Create a Timer.
  454. * @param {bigint} period - The number representing period in nanoseconds.
  455. * @param {function} callback - The callback to be called when timeout.
  456. * @param {Clock} [clock] - The clock which the timer gets time from.
  457. * @return {Timer} - An instance of Timer.
  458. */
  459. createTimer(period, callback, clock = null) {
  460. if (arguments.length === 3 && !(arguments[2] instanceof Clock)) {
  461. clock = null;
  462. } else if (arguments.length === 4) {
  463. clock = arguments[3];
  464. }
  465. if (typeof period !== 'bigint' || typeof callback !== 'function') {
  466. throw new TypeError('Invalid argument');
  467. }
  468. const timerClock = clock || this._clock;
  469. let timerHandle = rclnodejs.createTimer(
  470. timerClock.handle,
  471. this.context.handle,
  472. period
  473. );
  474. let timer = new Timer(timerHandle, period, callback);
  475. debug('Finish creating timer, period = %d.', period);
  476. this._timers.push(timer);
  477. this.syncHandles();
  478. return timer;
  479. }
  480. /**
  481. * Create a Rate.
  482. *
  483. * @param {number} hz - The frequency of the rate timer; default is 1 hz.
  484. * @returns {Promise<Rate>} - Promise resolving to new instance of Rate.
  485. */
  486. async createRate(hz = 1) {
  487. if (typeof hz !== 'number') {
  488. throw new TypeError('Invalid argument');
  489. }
  490. const MAX_RATE_HZ_IN_MILLISECOND = 1000.0;
  491. if (hz <= 0.0 || hz > MAX_RATE_HZ_IN_MILLISECOND) {
  492. throw new RangeError(
  493. `Hz must be between 0.0 and ${MAX_RATE_HZ_IN_MILLISECOND}`
  494. );
  495. }
  496. // lazy initialize rateTimerServer
  497. if (!this._rateTimerServer) {
  498. this._rateTimerServer = new Rates.RateTimerServer(this);
  499. await this._rateTimerServer.init();
  500. }
  501. const period = Math.round(1000 / hz);
  502. const timer = this._rateTimerServer.createTimer(BigInt(period) * 1000000n);
  503. const rate = new Rates.Rate(hz, timer);
  504. return rate;
  505. }
  506. /**
  507. * Create a Publisher.
  508. * @param {function|string|object} typeClass - The ROS message class,
  509. OR a string representing the message class, e.g. 'std_msgs/msg/String',
  510. OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
  511. * @param {string} topic - The name of the topic.
  512. * @param {object} options - The options argument used to parameterize the publisher.
  513. * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
  514. * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
  515. * @return {Publisher} - An instance of Publisher.
  516. */
  517. createPublisher(typeClass, topic, options) {
  518. return this._createPublisher(typeClass, topic, options, Publisher);
  519. }
  520. _createPublisher(typeClass, topic, options, publisherClass) {
  521. if (typeof typeClass === 'string' || typeof typeClass === 'object') {
  522. typeClass = loader.loadInterface(typeClass);
  523. }
  524. options = this._validateOptions(options);
  525. if (typeof typeClass !== 'function' || typeof topic !== 'string') {
  526. throw new TypeError('Invalid argument');
  527. }
  528. let publisher = publisherClass.createPublisher(
  529. this.handle,
  530. typeClass,
  531. topic,
  532. options
  533. );
  534. debug('Finish creating publisher, topic = %s.', topic);
  535. this._publishers.push(publisher);
  536. return publisher;
  537. }
  538. /**
  539. * This callback is called when a message is published
  540. * @callback SubscriptionCallback
  541. * @param {Object} message - The message published
  542. * @see [Node.createSubscription]{@link Node#createSubscription}
  543. * @see [Node.createPublisher]{@link Node#createPublisher}
  544. * @see {@link Publisher}
  545. * @see {@link Subscription}
  546. */
  547. /**
  548. * Create a Subscription with optional content-filtering.
  549. * @param {function|string|object} typeClass - The ROS message class,
  550. OR a string representing the message class, e.g. 'std_msgs/msg/String',
  551. OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
  552. * @param {string} topic - The name of the topic.
  553. * @param {object} options - The options argument used to parameterize the subscription.
  554. * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
  555. * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
  556. * @param {boolean} options.isRaw - The topic is serialized when true, default: false.
  557. * @param {object} [options.contentFilter=undefined] - The content-filter, default: undefined.
  558. * Confirm that your RMW supports content-filtered topics before use.
  559. * @param {string} options.contentFilter.expression - Specifies the criteria to select the data samples of
  560. * interest. It is similar to the WHERE part of an SQL clause.
  561. * @param {string[]} [options.contentFilter.parameters=undefined] - Array of strings that give values to
  562. * the ‘parameters’ (i.e., "%n" tokens) in the filter_expression. The number of supplied parameters must
  563. * fit with the requested values in the filter_expression (i.e., the number of %n tokens). default: undefined.
  564. * @param {SubscriptionCallback} callback - The callback to be call when receiving the topic subscribed. The topic will be an instance of null-terminated Buffer when options.isRaw is true.
  565. * @return {Subscription} - An instance of Subscription.
  566. * @throws {ERROR} - May throw an RMW error if content-filter is malformed.
  567. * @see {@link SubscriptionCallback}
  568. * @see {@link https://www.omg.org/spec/DDS/1.4/PDF|Content-filter details at DDS 1.4 specification, Annex B}
  569. */
  570. createSubscription(typeClass, topic, options, callback) {
  571. if (typeof typeClass === 'string' || typeof typeClass === 'object') {
  572. typeClass = loader.loadInterface(typeClass);
  573. }
  574. if (typeof options === 'function') {
  575. callback = options;
  576. options = undefined;
  577. }
  578. options = this._validateOptions(options);
  579. if (
  580. typeof typeClass !== 'function' ||
  581. typeof topic !== 'string' ||
  582. typeof callback !== 'function'
  583. ) {
  584. throw new TypeError('Invalid argument');
  585. }
  586. let subscription = Subscription.createSubscription(
  587. this.handle,
  588. typeClass,
  589. topic,
  590. options,
  591. callback
  592. );
  593. debug('Finish creating subscription, topic = %s.', topic);
  594. this._subscriptions.push(subscription);
  595. this.syncHandles();
  596. return subscription;
  597. }
  598. /**
  599. * Create a Client.
  600. * @param {function|string|object} typeClass - The ROS message class,
  601. OR a string representing the message class, e.g. 'std_msgs/msg/String',
  602. OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
  603. * @param {string} serviceName - The service name to request.
  604. * @param {object} options - The options argument used to parameterize the client.
  605. * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
  606. * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
  607. * @return {Client} - An instance of Client.
  608. */
  609. createClient(typeClass, serviceName, options) {
  610. if (typeof typeClass === 'string' || typeof typeClass === 'object') {
  611. typeClass = loader.loadInterface(typeClass);
  612. }
  613. options = this._validateOptions(options);
  614. if (typeof typeClass !== 'function' || typeof serviceName !== 'string') {
  615. throw new TypeError('Invalid argument');
  616. }
  617. let client = Client.createClient(
  618. this.handle,
  619. serviceName,
  620. typeClass,
  621. options
  622. );
  623. debug('Finish creating client, service = %s.', serviceName);
  624. this._clients.push(client);
  625. this.syncHandles();
  626. return client;
  627. }
  628. /**
  629. * This callback is called when a request is sent to service
  630. * @callback RequestCallback
  631. * @param {Object} request - The request sent to the service
  632. * @param {Response} response - The response to client.
  633. Use [response.send()]{@link Response#send} to send response object to client
  634. * @return {undefined}
  635. * @see [Node.createService]{@link Node#createService}
  636. * @see [Client.sendRequest]{@link Client#sendRequest}
  637. * @see {@link Client}
  638. * @see {@link Service}
  639. * @see {@link Response#send}
  640. */
  641. /**
  642. * Create a Service.
  643. * @param {function|string|object} typeClass - The ROS message class,
  644. OR a string representing the message class, e.g. 'std_msgs/msg/String',
  645. OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
  646. * @param {string} serviceName - The service name to offer.
  647. * @param {object} options - The options argument used to parameterize the service.
  648. * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
  649. * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
  650. * @param {RequestCallback} callback - The callback to be called when receiving request.
  651. * @return {Service} - An instance of Service.
  652. * @see {@link RequestCallback}
  653. */
  654. createService(typeClass, serviceName, options, callback) {
  655. if (typeof typeClass === 'string' || typeof typeClass === 'object') {
  656. typeClass = loader.loadInterface(typeClass);
  657. }
  658. if (typeof options === 'function') {
  659. callback = options;
  660. options = undefined;
  661. }
  662. options = this._validateOptions(options);
  663. if (
  664. typeof typeClass !== 'function' ||
  665. typeof serviceName !== 'string' ||
  666. typeof callback !== 'function'
  667. ) {
  668. throw new TypeError('Invalid argument');
  669. }
  670. let service = Service.createService(
  671. this.handle,
  672. serviceName,
  673. typeClass,
  674. options,
  675. callback
  676. );
  677. debug('Finish creating service, service = %s.', serviceName);
  678. this._services.push(service);
  679. this.syncHandles();
  680. return service;
  681. }
  682. /**
  683. * Create a guard condition.
  684. * @param {Function} callback - The callback to be called when the guard condition is triggered.
  685. * @return {GuardCondition} - An instance of GuardCondition.
  686. */
  687. createGuardCondition(callback) {
  688. if (typeof callback !== 'function') {
  689. throw new TypeError('Invalid argument');
  690. }
  691. let guard = GuardCondition.createGuardCondition(callback, this.context);
  692. debug('Finish creating guard condition');
  693. this._guards.push(guard);
  694. this.syncHandles();
  695. return guard;
  696. }
  697. /**
  698. * Destroy all resource allocated by this node, including
  699. * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
  700. * /<code>Client</code>s/<code>Service</code>s
  701. * @return {undefined}
  702. */
  703. destroy() {
  704. if (this.spinning) {
  705. this.stop();
  706. }
  707. // Action servers/clients require manual destruction due to circular reference with goal handles.
  708. this._actionClients.forEach((actionClient) => actionClient.destroy());
  709. this._actionServers.forEach((actionServer) => actionServer.destroy());
  710. this.context.onNodeDestroyed(this);
  711. this.handle.release();
  712. this._clock = null;
  713. this._timers = [];
  714. this._publishers = [];
  715. this._subscriptions = [];
  716. this._clients = [];
  717. this._services = [];
  718. this._guards = [];
  719. this._actionClients = [];
  720. this._actionServers = [];
  721. if (this._rateTimerServer) {
  722. this._rateTimerServer.shutdown();
  723. this._rateTimerServer = null;
  724. }
  725. }
  726. /**
  727. * Destroy a Publisher.
  728. * @param {Publisher} publisher - The Publisher to be destroyed.
  729. * @return {undefined}
  730. */
  731. destroyPublisher(publisher) {
  732. if (!(publisher instanceof Publisher)) {
  733. throw new TypeError('Invalid argument');
  734. }
  735. this._destroyEntity(publisher, this._publishers, false);
  736. }
  737. /**
  738. * Destroy a Subscription.
  739. * @param {Subscription} subscription - The Subscription to be destroyed.
  740. * @return {undefined}
  741. */
  742. destroySubscription(subscription) {
  743. if (!(subscription instanceof Subscription)) {
  744. throw new TypeError('Invalid argument');
  745. }
  746. this._destroyEntity(subscription, this._subscriptions);
  747. }
  748. /**
  749. * Destroy a Client.
  750. * @param {Client} client - The Client to be destroyed.
  751. * @return {undefined}
  752. */
  753. destroyClient(client) {
  754. if (!(client instanceof Client)) {
  755. throw new TypeError('Invalid argument');
  756. }
  757. this._destroyEntity(client, this._clients);
  758. }
  759. /**
  760. * Destroy a Service.
  761. * @param {Service} service - The Service to be destroyed.
  762. * @return {undefined}
  763. */
  764. destroyService(service) {
  765. if (!(service instanceof Service)) {
  766. throw new TypeError('Invalid argument');
  767. }
  768. this._destroyEntity(service, this._services);
  769. }
  770. /**
  771. * Destroy a Timer.
  772. * @param {Timer} timer - The Timer to be destroyed.
  773. * @return {undefined}
  774. */
  775. destroyTimer(timer) {
  776. if (!(timer instanceof Timer)) {
  777. throw new TypeError('Invalid argument');
  778. }
  779. this._destroyEntity(timer, this._timers);
  780. }
  781. /**
  782. * Destroy a guard condition.
  783. * @param {GuardCondition} guard - The guard condition to be destroyed.
  784. * @return {undefined}
  785. */
  786. destroyGuardCondition(guard) {
  787. if (!(guard instanceof GuardCondition)) {
  788. throw new TypeError('Invalid argument');
  789. }
  790. this._destroyEntity(guard, this._guards);
  791. }
  792. /**
  793. * Get the name of the node.
  794. * @return {string}
  795. */
  796. name() {
  797. return rclnodejs.getNodeName(this.handle);
  798. }
  799. /**
  800. * Get the namespace of the node.
  801. * @return {string}
  802. */
  803. namespace() {
  804. return rclnodejs.getNamespace(this.handle);
  805. }
  806. /**
  807. * Get the context in which this node was created.
  808. * @return {Context}
  809. */
  810. get context() {
  811. return this._context;
  812. }
  813. /**
  814. * Get the nodes logger.
  815. * @returns {Logger} - The logger for the node.
  816. */
  817. getLogger() {
  818. return this._logger;
  819. }
  820. /**
  821. * Get the clock used by the node.
  822. * @returns {Clock} - The nodes clock.
  823. */
  824. getClock() {
  825. return this._clock;
  826. }
  827. /**
  828. * Get the current time using the node's clock.
  829. * @returns {Time} - The current time.
  830. */
  831. now() {
  832. return this.getClock().now();
  833. }
  834. /**
  835. * Get the list of published topics discovered by the provided node for the remote node name.
  836. * @param {string} nodeName - The name of the node.
  837. * @param {string} namespace - The name of the namespace.
  838. * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
  839. * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
  840. */
  841. getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
  842. return rclnodejs.getPublisherNamesAndTypesByNode(
  843. this.handle,
  844. nodeName,
  845. namespace,
  846. noDemangle
  847. );
  848. }
  849. /**
  850. * Get the list of published topics discovered by the provided node for the remote node name.
  851. * @param {string} nodeName - The name of the node.
  852. * @param {string} namespace - The name of the namespace.
  853. * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
  854. * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
  855. */
  856. getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
  857. return rclnodejs.getSubscriptionNamesAndTypesByNode(
  858. this.handle,
  859. nodeName,
  860. namespace,
  861. noDemangle
  862. );
  863. }
  864. /**
  865. * Get the list of service topics discovered by the provided node for the remote node name.
  866. * @param {string} nodeName - The name of the node.
  867. * @param {string} namespace - The name of the namespace.
  868. * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
  869. */
  870. getServiceNamesAndTypesByNode(nodeName, namespace) {
  871. return rclnodejs.getServiceNamesAndTypesByNode(
  872. this.handle,
  873. nodeName,
  874. namespace
  875. );
  876. }
  877. /**
  878. * Get the list of topics discovered by the provided node.
  879. * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
  880. * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
  881. */
  882. getTopicNamesAndTypes(noDemangle = false) {
  883. return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
  884. }
  885. /**
  886. * Get the list of services discovered by the provided node.
  887. * @return {Array<{name: string, types: Array<string>}>} - An array of the names and types.
  888. */
  889. getServiceNamesAndTypes() {
  890. return rclnodejs.getServiceNamesAndTypes(this.handle);
  891. }
  892. /**
  893. * Get the list of nodes discovered by the provided node.
  894. * @return {Array<string>} - An array of the names.
  895. */
  896. getNodeNames() {
  897. return this.getNodeNamesAndNamespaces().map((item) => item.name);
  898. }
  899. /**
  900. * Get the list of nodes and their namespaces discovered by the provided node.
  901. * @return {Array<{name: string, namespace: string}>} An array of the names and namespaces.
  902. */
  903. getNodeNamesAndNamespaces() {
  904. return rclnodejs.getNodeNames(this.handle);
  905. }
  906. /**
  907. * Return the number of publishers on a given topic.
  908. * @param {string} topic - The name of the topic.
  909. * @returns {number} - Number of publishers on the given topic.
  910. */
  911. countPublishers(topic) {
  912. let expandedTopic = rclnodejs.expandTopicName(
  913. topic,
  914. this.name(),
  915. this.namespace()
  916. );
  917. rclnodejs.validateTopicName(expandedTopic);
  918. return rclnodejs.countPublishers(this.handle, expandedTopic);
  919. }
  920. /**
  921. * Return the number of subscribers on a given topic.
  922. * @param {string} topic - The name of the topic.
  923. * @returns {number} - Number of subscribers on the given topic.
  924. */
  925. countSubscribers(topic) {
  926. let expandedTopic = rclnodejs.expandTopicName(
  927. topic,
  928. this.name(),
  929. this.namespace()
  930. );
  931. rclnodejs.validateTopicName(expandedTopic);
  932. return rclnodejs.countSubscribers(this.handle, expandedTopic);
  933. }
  934. /**
  935. * Get the list of parameter-overrides found on the commandline and
  936. * in the NodeOptions.parameter_overrides property.
  937. *
  938. * @return {Array<Parameter>} - An array of Parameters.
  939. */
  940. getParameterOverrides() {
  941. return Array.from(this._parameterOverrides.values());
  942. }
  943. /**
  944. * Declare a parameter.
  945. *
  946. * Internally, register a parameter and it's descriptor.
  947. * If a parameter-override exists, it's value will replace that of the parameter
  948. * unless ignoreOverride is true.
  949. * If the descriptor is undefined, then a ParameterDescriptor will be inferred
  950. * from the parameter's state.
  951. *
  952. * If a parameter by the same name has already been declared then an Error is thrown.
  953. * A parameter must be undeclared before attempting to redeclare it.
  954. *
  955. * @param {Parameter} parameter - Parameter to declare.
  956. * @param {ParameterDescriptor} [descriptor] - Optional descriptor for parameter.
  957. * @param {boolean} [ignoreOveride] - When true disregard any parameter-override that may be present.
  958. * @return {Parameter} - The newly declared parameter.
  959. */
  960. declareParameter(parameter, descriptor, ignoreOveride = false) {
  961. const parameters = this.declareParameters(
  962. [parameter],
  963. descriptor ? [descriptor] : [],
  964. ignoreOveride
  965. );
  966. return parameters.length == 1 ? parameters[0] : null;
  967. }
  968. /**
  969. * Declare a list of parameters.
  970. *
  971. * Internally register parameters with their corresponding descriptor one by one
  972. * in the order they are provided. This is an atomic operation. If an error
  973. * occurs the process halts and no further parameters are declared.
  974. * Parameters that have already been processed are undeclared.
  975. *
  976. * While descriptors is an optional parameter, when provided there must be
  977. * a descriptor for each parameter; otherwise an Error is thrown.
  978. * If descriptors is not provided then a descriptor will be inferred
  979. * from each parameter's state.
  980. *
  981. * When a parameter-override is available, the parameter's value
  982. * will be replaced with that of the parameter-override unless ignoreOverrides
  983. * is true.
  984. *
  985. * If a parameter by the same name has already been declared then an Error is thrown.
  986. * A parameter must be undeclared before attempting to redeclare it.
  987. *
  988. * Prior to declaring the parameters each SetParameterEventCallback registered
  989. * using setOnParameterEventCallback() is called in succession with the parameters
  990. * list. Any SetParameterEventCallback that retuns does not return a successful
  991. * result will cause the entire operation to terminate with no changes to the
  992. * parameters. When all SetParameterEventCallbacks return successful then the
  993. * list of parameters is updated.
  994. *
  995. * @param {Parameter[]} parameters - The parameters to declare.
  996. * @param {ParameterDescriptor[]} [descriptors] - Optional descriptors,
  997. * a 1-1 correspondence with parameters.
  998. * @param {boolean} ignoreOverrides - When true, parameter-overrides are
  999. * not considered, i.e.,ignored.
  1000. * @return {Parameter[]} - The declared parameters.
  1001. */
  1002. declareParameters(parameters, descriptors = [], ignoreOverrides = false) {
  1003. if (!Array.isArray(parameters)) {
  1004. throw new TypeError('Invalid parameter: expected array of Parameter');
  1005. }
  1006. if (!Array.isArray(descriptors)) {
  1007. throw new TypeError(
  1008. 'Invalid parameters: expected array of ParameterDescriptor'
  1009. );
  1010. }
  1011. if (descriptors.length > 0 && parameters.length !== descriptors.length) {
  1012. throw new TypeError(
  1013. 'Each parameter must have a cooresponding ParameterDescriptor'
  1014. );
  1015. }
  1016. const declaredDescriptors = [];
  1017. const declaredParameters = [];
  1018. const declaredParameterCollisions = [];
  1019. for (let i = 0; i < parameters.length; i++) {
  1020. let parameter =
  1021. !ignoreOverrides && this._parameterOverrides.has(parameters[i].name)
  1022. ? this._parameterOverrides.get(parameters[i].name)
  1023. : parameters[i];
  1024. // stop processing parameters that have already been declared
  1025. if (this._parameters.has(parameter.name)) {
  1026. declaredParameterCollisions.push(parameter);
  1027. continue;
  1028. }
  1029. // create descriptor for parameter if not provided
  1030. let descriptor =
  1031. descriptors.length > 0
  1032. ? descriptors[i]
  1033. : ParameterDescriptor.fromParameter(parameter);
  1034. descriptor.validate();
  1035. declaredDescriptors.push(descriptor);
  1036. declaredParameters.push(parameter);
  1037. }
  1038. if (declaredParameterCollisions.length > 0) {
  1039. const errorMsg =
  1040. declaredParameterCollisions.length == 1
  1041. ? `Parameter(${declaredParameterCollisions[0]}) already declared.`
  1042. : `Multiple parameters already declared, e.g., Parameter(${declaredParameterCollisions[0]}).`;
  1043. throw new Error(errorMsg);
  1044. }
  1045. // register descriptor
  1046. for (const descriptor of declaredDescriptors) {
  1047. this._parameterDescriptors.set(descriptor.name, descriptor);
  1048. }
  1049. const result = this._setParametersAtomically(declaredParameters, true);
  1050. if (!result.successful) {
  1051. // unregister descriptors
  1052. for (const descriptor of declaredDescriptors) {
  1053. this._parameterDescriptors.delete(descriptor.name);
  1054. }
  1055. throw new Error(result.reason);
  1056. }
  1057. return this.getParameters(declaredParameters.map((param) => param.name));
  1058. }
  1059. /**
  1060. * Undeclare a parameter.
  1061. *
  1062. * Readonly parameters can not be undeclared or updated.
  1063. * @param {string} name - Name of parameter to undeclare.
  1064. * @return {undefined} -
  1065. */
  1066. undeclareParameter(name) {
  1067. if (!this.hasParameter(name)) return;
  1068. const descriptor = this.getParameterDescriptor(name);
  1069. if (descriptor.readOnly) {
  1070. throw new Error(
  1071. `${name} parameter is read-only and can not be undeclared`
  1072. );
  1073. }
  1074. this._parameters.delete(name);
  1075. this._parameterDescriptors.delete(name);
  1076. }
  1077. /**
  1078. * Determine if a parameter has been declared.
  1079. * @param {string} name - name of parameter
  1080. * @returns {boolean} - Return true if parameter is declared; false otherwise.
  1081. */
  1082. hasParameter(name) {
  1083. return this._parameters.has(name);
  1084. }
  1085. /**
  1086. * Get a declared parameter by name.
  1087. *
  1088. * If unable to locate a declared parameter then a
  1089. * parameter with type == PARAMETER_NOT_SET is returned.
  1090. *
  1091. * @param {string} name - The name of the parameter.
  1092. * @return {Parameter} - The parameter.
  1093. */
  1094. getParameter(name) {
  1095. return this.getParameters([name])[0];
  1096. }
  1097. /**
  1098. * Get a list of parameters.
  1099. *
  1100. * Find and return the declared parameters.
  1101. * If no names are provided return all declared parameters.
  1102. *
  1103. * If unable to locate a declared parameter then a
  1104. * parameter with type == PARAMETER_NOT_SET is returned in
  1105. * it's place.
  1106. *
  1107. * @param {string[]} [names] - The names of the declared parameters
  1108. * to find or null indicating to return all declared parameters.
  1109. * @return {Parameter[]} - The parameters.
  1110. */
  1111. getParameters(names = []) {
  1112. let params = [];
  1113. if (names.length == 0) {
  1114. // get all parameters
  1115. params = [...this._parameters.values()];
  1116. return params;
  1117. }
  1118. for (const name of names) {
  1119. const param = this.hasParameter(name)
  1120. ? this._parameters.get(name)
  1121. : new Parameter(name, ParameterType.PARAMETER_NOT_SET);
  1122. params.push(param);
  1123. }
  1124. return params;
  1125. }
  1126. /**
  1127. * Get the types of given parameters.
  1128. *
  1129. * Return the types of given parameters.
  1130. *
  1131. * @param {string[]} [names] - The names of the declared parameters.
  1132. * @return {Uint8Array} - The types.
  1133. */
  1134. getParameterTypes(names = []) {
  1135. let types = [];
  1136. for (const name of names) {
  1137. const descriptor = this._parameterDescriptors.get(name);
  1138. if (descriptor) {
  1139. types.push(descriptor.type);
  1140. }
  1141. }
  1142. return types;
  1143. }
  1144. /**
  1145. * Get the names of all declared parameters.
  1146. *
  1147. * @return {Array<string>} - The declared parameter names or empty array if
  1148. * no parameters have been declared.
  1149. */
  1150. getParameterNames() {
  1151. return this.getParameters().map((param) => param.name);
  1152. }
  1153. /**
  1154. * Determine if a parameter descriptor exists.
  1155. *
  1156. * @param {string} name - The name of a descriptor to for.
  1157. * @return {boolean} - True if a descriptor has been declared; otherwise false.
  1158. */
  1159. hasParameterDescriptor(name) {
  1160. return !!this.getParameterDescriptor(name);
  1161. }
  1162. /**
  1163. * Get a declared parameter descriptor by name.
  1164. *
  1165. * If unable to locate a declared parameter descriptor then a
  1166. * descriptor with type == PARAMETER_NOT_SET is returned.
  1167. *
  1168. * @param {string} name - The name of the parameter descriptor to find.
  1169. * @return {ParameterDescriptor} - The parameter descriptor.
  1170. */
  1171. getParameterDescriptor(name) {
  1172. return this.getParameterDescriptors([name])[0];
  1173. }
  1174. /**
  1175. * Find a list of declared ParameterDescriptors.
  1176. *
  1177. * If no names are provided return all declared descriptors.
  1178. *
  1179. * If unable to locate a declared descriptor then a
  1180. * descriptor with type == PARAMETER_NOT_SET is returned in
  1181. * it's place.
  1182. *
  1183. * @param {string[]} [names] - The names of the declared parameter
  1184. * descriptors to find or null indicating to return all declared descriptors.
  1185. * @return {ParameterDescriptor[]} - The parameter descriptors.
  1186. */
  1187. getParameterDescriptors(names = []) {
  1188. let descriptors = [];
  1189. if (names.length == 0) {
  1190. // get all parameters
  1191. descriptors = [...this._parameterDescriptors.values()];
  1192. return descriptors;
  1193. }
  1194. for (const name of names) {
  1195. let descriptor = this._parameterDescriptors.get(name);
  1196. if (!descriptor) {
  1197. descriptor = new ParameterDescriptor(
  1198. name,
  1199. ParameterType.PARAMETER_NOT_SET
  1200. );
  1201. }
  1202. descriptors.push(descriptor);
  1203. }
  1204. return descriptors;
  1205. }
  1206. /**
  1207. * Replace a declared parameter.
  1208. *
  1209. * The parameter being replaced must be a declared parameter who's descriptor
  1210. * is not readOnly; otherwise an Error is thrown.
  1211. *
  1212. * @param {Parameter} parameter - The new parameter.
  1213. * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - The result of the operation.
  1214. */
  1215. setParameter(parameter) {
  1216. const results = this.setParameters([parameter]);
  1217. return results[0];
  1218. }
  1219. /**
  1220. * Replace a list of declared parameters.
  1221. *
  1222. * Declared parameters are replaced in the order they are provided and
  1223. * a ParameterEvent is published for each individual parameter change.
  1224. *
  1225. * Prior to setting the parameters each SetParameterEventCallback registered
  1226. * using setOnParameterEventCallback() is called in succession with the parameters
  1227. * list. Any SetParameterEventCallback that retuns does not return a successful
  1228. * result will cause the entire operation to terminate with no changes to the
  1229. * parameters. When all SetParameterEventCallbacks return successful then the
  1230. * list of parameters is updated.
  1231. *
  1232. * If an error occurs, the process is stopped and returned. Parameters
  1233. * set before an error remain unchanged.
  1234. *
  1235. * @param {Parameter[]} parameters - The parameters to set.
  1236. * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult[]} - A list of SetParameterResult, one for each parameter that was set.
  1237. */
  1238. setParameters(parameters = []) {
  1239. return parameters.map((parameter) =>
  1240. this.setParametersAtomically([parameter])
  1241. );
  1242. }
  1243. /**
  1244. * Repalce a list of declared parameters atomically.
  1245. *
  1246. * Declared parameters are replaced in the order they are provided.
  1247. * A single ParameterEvent is published collectively for all changed
  1248. * parameters.
  1249. *
  1250. * Prior to setting the parameters each SetParameterEventCallback registered
  1251. * using setOnParameterEventCallback() is called in succession with the parameters
  1252. * list. Any SetParameterEventCallback that retuns does not return a successful
  1253. * result will cause the entire operation to terminate with no changes to the
  1254. * parameters. When all SetParameterEventCallbacks return successful then the
  1255. * list of parameters is updated.d
  1256. *
  1257. * If an error occurs, the process stops immediately. All parameters updated to
  1258. * the point of the error are reverted to their previous state.
  1259. *
  1260. * @param {Parameter[]} parameters - The parameters to set.
  1261. * @return {rclnodejs.rcl_interfaces.msg.SetParameterResult} - describes the result of setting 1 or more parameters.
  1262. */
  1263. setParametersAtomically(parameters = []) {
  1264. return this._setParametersAtomically(parameters);
  1265. }
  1266. /**
  1267. * Internal method for updating parameters atomically.
  1268. *
  1269. * Prior to setting the parameters each SetParameterEventCallback registered
  1270. * using setOnParameterEventCallback() is called in succession with the parameters
  1271. * list. Any SetParameterEventCallback that retuns does not return a successful
  1272. * result will cause the entire operation to terminate with no changes to the
  1273. * parameters. When all SetParameterEventCallbacks return successful then the
  1274. * list of parameters is updated.
  1275. *
  1276. * @param {Paramerter[]} parameters - The parameters to update.
  1277. * @param {boolean} declareParameterMode - When true parameters are being declared;
  1278. * otherwise they are being changed.
  1279. * @return {SetParameterResult} - A single collective result.
  1280. */
  1281. _setParametersAtomically(parameters = [], declareParameterMode = false) {
  1282. let result = this._validateParameters(parameters, declareParameterMode);
  1283. if (!result.successful) {
  1284. return result;
  1285. }
  1286. // give all SetParametersCallbacks a chance to veto this change
  1287. for (const callback of this._setParametersCallbacks) {
  1288. result = callback(parameters);
  1289. if (!result.successful) {
  1290. // a callback has vetoed a parameter change
  1291. return result;
  1292. }
  1293. }
  1294. // collectively track updates to parameters for use
  1295. // when publishing a ParameterEvent
  1296. const newParameters = [];
  1297. const changedParameters = [];
  1298. const deletedParameters = [];
  1299. for (const parameter of parameters) {
  1300. if (parameter.type == ParameterType.PARAMETER_NOT_SET) {
  1301. this.undeclareParameter(parameter.name);
  1302. deletedParameters.push(parameter);
  1303. } else {
  1304. this._parameters.set(parameter.name, parameter);
  1305. if (declareParameterMode) {
  1306. newParameters.push(parameter);
  1307. } else {
  1308. changedParameters.push(parameter);
  1309. }
  1310. }
  1311. }
  1312. // create ParameterEvent
  1313. const parameterEvent = new (loader.loadInterface(
  1314. PARAMETER_EVENT_MSG_TYPE
  1315. ))();
  1316. const { seconds, nanoseconds } = this._clock.now().secondsAndNanoseconds;
  1317. parameterEvent.stamp = {
  1318. sec: Number(seconds),
  1319. nanosec: Number(nanoseconds),
  1320. };
  1321. parameterEvent.node =
  1322. this.namespace() === '/'
  1323. ? this.namespace() + this.name()
  1324. : this.namespace() + '/' + this.name();
  1325. if (newParameters.length > 0) {
  1326. parameterEvent['new_parameters'] = newParameters.map((parameter) =>
  1327. parameter.toParameterMessage()
  1328. );
  1329. }
  1330. if (changedParameters.length > 0) {
  1331. parameterEvent['changed_parameters'] = changedParameters.map(
  1332. (parameter) => parameter.toParameterMessage()
  1333. );
  1334. }
  1335. if (deletedParameters.length > 0) {
  1336. parameterEvent['deleted_parameters'] = deletedParameters.map(
  1337. (parameter) => parameter.toParameterMessage()
  1338. );
  1339. }
  1340. // publish ParameterEvent
  1341. this._parameterEventPublisher.publish(parameterEvent);
  1342. return {
  1343. successful: true,
  1344. reason: '',
  1345. };
  1346. }
  1347. /**
  1348. * This callback is called when declaring a parameter or setting a parameter.
  1349. * The callback is provided a list of parameters and returns a SetParameterResult
  1350. * to indicate approval or veto of the operation.
  1351. *
  1352. * @callback SetParametersCallback
  1353. * @param {Parameter[]} parameters - The message published
  1354. * @returns {rcl_interfaces.msg.SetParameterResult} -
  1355. *
  1356. * @see [Node.addOnSetParametersCallback]{@link Node#addOnSetParametersCallback}
  1357. * @see [Node.removeOnSetParametersCallback]{@link Node#removeOnSetParametersCallback}
  1358. */
  1359. /**
  1360. * Add a callback to the front of the list of callbacks invoked for parameter declaration
  1361. * and setting. No checks are made for duplicate callbacks.
  1362. *
  1363. * @param {SetParametersCallback} callback - The callback to add.
  1364. * @returns {undefined}
  1365. */
  1366. addOnSetParametersCallback(callback) {
  1367. this._setParametersCallbacks.unshift(callback);
  1368. }
  1369. /**
  1370. * Remove a callback from the list of SetParametersCallbacks.
  1371. * If the callback is not found the process is a nop.
  1372. *
  1373. * @param {SetParametersCallback} callback - The callback to be removed
  1374. * @returns {undefined}
  1375. */
  1376. removeOnSetParametersCallback(callback) {
  1377. const idx = this._setParametersCallbacks.indexOf(callback);
  1378. if (idx > -1) {
  1379. this._setParametersCallbacks.splice(idx, 1);
  1380. }
  1381. }
  1382. // returns on 1st error or result {successful, reason}
  1383. _validateParameters(parameters = [], declareParameterMode = false) {
  1384. for (const parameter of parameters) {
  1385. // detect invalid parameter
  1386. try {
  1387. parameter.validate();
  1388. } catch (e) {
  1389. return {
  1390. successful: false,
  1391. reason: `Invalid ${parameter.name}`,
  1392. };
  1393. }
  1394. // detect undeclared parameter
  1395. if (!this.hasParameterDescriptor(parameter.name)) {
  1396. return {
  1397. successful: false,
  1398. reason: `Parameter ${parameter.name} has not been declared`,
  1399. };
  1400. }
  1401. // detect readonly parameter that can not be updated
  1402. const descriptor = this.getParameterDescriptor(parameter.name);
  1403. if (!declareParameterMode && descriptor.readOnly) {
  1404. return {
  1405. successful: false,
  1406. reason: `Parameter ${parameter.name} is readonly`,
  1407. };
  1408. }
  1409. // validate parameter against descriptor if not an undeclare action
  1410. if (parameter.type != ParameterType.PARAMETER_NOT_SET) {
  1411. try {
  1412. descriptor.validateParameter(parameter);
  1413. } catch (e) {
  1414. return {
  1415. successful: false,
  1416. reason: `Parameter ${parameter.name} does not readonly`,
  1417. };
  1418. }
  1419. }
  1420. }
  1421. return {
  1422. successful: true,
  1423. reason: null,
  1424. };
  1425. }
  1426. // Get a Map(nodeName->Parameter[]) of CLI parameter args that
  1427. // apply to 'this' node, .e.g., -p mynode:foo:=bar -p hello:=world
  1428. _getNativeParameterOverrides() {
  1429. const overrides = new Map();
  1430. // Get native parameters from rcl context->global_arguments.
  1431. // rclnodejs returns an array of objects, 1 for each node e.g., -p my_node:foo:=bar,
  1432. // and a node named '/**' for global parameter rules,
  1433. // i.e., does not include a node identifier, e.g., -p color:=red
  1434. // {
  1435. // name: string // node name
  1436. // parameters[] = {
  1437. // name: string
  1438. // type: uint
  1439. // value: object
  1440. // }
  1441. const cliParamOverrideData = rclnodejs.getParameterOverrides(
  1442. this.context.handle
  1443. );
  1444. // convert native CLI parameterOverrides to Map<nodeName,Array<ParameterOverride>>
  1445. const cliParamOverrides = new Map();
  1446. if (cliParamOverrideData) {
  1447. for (let nodeParamData of cliParamOverrideData) {
  1448. const nodeName = nodeParamData.name;
  1449. const nodeParamOverrides = [];
  1450. for (let paramData of nodeParamData.parameters) {
  1451. const paramOverride = new Parameter(
  1452. paramData.name,
  1453. paramData.type,
  1454. paramData.value
  1455. );
  1456. nodeParamOverrides.push(paramOverride);
  1457. }
  1458. cliParamOverrides.set(nodeName, nodeParamOverrides);
  1459. }
  1460. }
  1461. // collect global CLI global parameters, name == /**
  1462. let paramOverrides = cliParamOverrides.get('/**'); // array of ParameterOverrides
  1463. if (paramOverrides) {
  1464. for (const parameter of paramOverrides) {
  1465. overrides.set(parameter.name, parameter);
  1466. }
  1467. }
  1468. // merge CLI node parameterOverrides with global parameterOverrides, replace existing
  1469. paramOverrides = cliParamOverrides.get(this.name()); // array of ParameterOverrides
  1470. if (paramOverrides) {
  1471. for (const parameter of paramOverrides) {
  1472. overrides.set(parameter.name, parameter);
  1473. }
  1474. }
  1475. return overrides;
  1476. }
  1477. /**
  1478. * Invokes the callback with a raw message of the given type. After the callback completes
  1479. * the message will be destroyed.
  1480. * @param {function} Type - Message type to create.
  1481. * @param {function} callback - Callback to invoke. First parameter will be the raw message,
  1482. * and the second is a function to retrieve the deserialized message.
  1483. * @returns {undefined}
  1484. */
  1485. _runWithMessageType(Type, callback) {
  1486. let message = new Type();
  1487. callback(message.toRawROS(), () => {
  1488. let result = new Type();
  1489. result.deserialize(message.refObject);
  1490. return result;
  1491. });
  1492. Type.destoryRawROS(message);
  1493. }
  1494. _addActionClient(actionClient) {
  1495. this._actionClients.push(actionClient);
  1496. this.syncHandles();
  1497. }
  1498. _addActionServer(actionServer) {
  1499. this._actionServers.push(actionServer);
  1500. this.syncHandles();
  1501. }
  1502. }
  1503. /**
  1504. * Create an Options instance initialized with default values.
  1505. * @returns {Options} - The new initialized instance.
  1506. * @static
  1507. * @example
  1508. * {
  1509. * enableTypedArray: true,
  1510. * isRaw: false,
  1511. * qos: QoS.profileDefault,
  1512. * contentFilter: undefined,
  1513. * }
  1514. */
  1515. Node.getDefaultOptions = function () {
  1516. return {
  1517. enableTypedArray: true,
  1518. isRaw: false,
  1519. qos: QoS.profileDefault,
  1520. contentFilter: undefined,
  1521. };
  1522. };
  1523. module.exports = Node;