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 Timer = require('./timer.js');
  17. const Publisher = require('./publisher.js');
  18. const Subscription = require('./subscription.js');
  19. const Client = require('./client.js');
  20. const Service = require('./service.js');
  21. const QoS = require('./qos.js');
  22. const debug = require('debug')('rclnodejs:node');
  23. const loader = require('./interface_loader.js');
  24. const Context = require('./context.js');
  25. const GuardCondition = require('./guard_condition.js');
  26. /**
  27. * @class - Class representing a Node in ROS
  28. * @hideconstructor
  29. */
  30. class Node {
  31. init(name, namespace) {
  32. this._publishers = [];
  33. this._subscriptions = [];
  34. this._clients = [];
  35. this._services = [];
  36. this._timers = [];
  37. this._guards = [];
  38. this._name = name;
  39. if (namespace.length === 0) {
  40. namespace = '/';
  41. } else if (!namespace.startsWith('/')) {
  42. namespace = '/' + namespace;
  43. }
  44. this._namespace = namespace;
  45. this.spinning = false;
  46. }
  47. execute(handles) {
  48. let timersReady = this._timers.filter((timer) => handles.indexOf(timer.handle) !== -1);
  49. let guardsReady = this._guards.filter((guard) => handles.indexOf(guard.handle) !== -1);
  50. let subscriptionsReady = this._subscriptions.filter((subscription) =>
  51. handles.indexOf(subscription.handle) !== -1);
  52. let clientsReady = this._clients.filter((client) => handles.indexOf(client.handle) !== -1);
  53. let servicesReady = this._services.filter((service) => handles.indexOf(service.handle) !== -1);
  54. timersReady.forEach((timer) => {
  55. if (timer.isReady()) {
  56. rclnodejs.callTimer(timer.handle);
  57. timer.callback();
  58. }
  59. });
  60. subscriptionsReady.forEach((subscription) => {
  61. let Message = subscription.typeClass;
  62. let msg = new Message();
  63. let success = rclnodejs.rclTake(subscription.handle, msg.toRawROS());
  64. if (success) {
  65. subscription.processResponse(msg.refObject);
  66. }
  67. Message.destoryRawROS(msg);
  68. });
  69. guardsReady.forEach((guard) => {
  70. guard.callback();
  71. });
  72. clientsReady.forEach((client) => {
  73. let Response = client.typeClass.Response;
  74. let response = new Response();
  75. let success = rclnodejs.rclTakeResponse(client.handle, client.sequenceNumber, response.toRawROS());
  76. if (success) {
  77. client.processResponse(response.refObject);
  78. }
  79. Response.destoryRawROS(response);
  80. });
  81. servicesReady.forEach((service) => {
  82. let Request = service.typeClass.Request;
  83. let request = new Request();
  84. let header = rclnodejs.rclTakeRequest(service.handle, this.handle, request.toRawROS());
  85. if (header) {
  86. service.processRequest(header, request.refObject);
  87. }
  88. Request.destoryRawROS(request);
  89. });
  90. }
  91. startSpinning(context, timeout) {
  92. this.start(context, timeout);
  93. this.spinning = true;
  94. }
  95. stopSpinning() {
  96. this.stop();
  97. this.spinning = false;
  98. }
  99. _removeEntityFromArray(entity, array) {
  100. let index = array.indexOf(entity);
  101. if (index > -1) {
  102. array.splice(index, 1);
  103. }
  104. }
  105. _destroyEntity(entity, array, syncHandles = true) {
  106. this._removeEntityFromArray(entity, array);
  107. if (syncHandles) {
  108. this.syncHandles();
  109. }
  110. entity.handle.release();
  111. }
  112. _validateOptions(options) {
  113. if (options !== undefined &&
  114. (options === null || typeof options !== 'object')) {
  115. throw new TypeError('Invalid argument of options');
  116. }
  117. if (options === undefined) {
  118. options = {enableTypedArray: true, qos: QoS.profileDefault};
  119. return options;
  120. }
  121. if (options.enableTypedArray === undefined) {
  122. options = Object.assign(options, {enableTypedArray: true});
  123. }
  124. if (options.qos === undefined) {
  125. options = Object.assign(options, {qos: QoS.profileDefault});
  126. }
  127. return options;
  128. }
  129. /**
  130. * Create a Timer.
  131. * @param {number} period - The number representing period in millisecond.
  132. * @param {function} callback - The callback to be called when timeout.
  133. * @param {Context} context - The context, default is Context.defaultContext().
  134. * @return {Timer} - An instance of Timer.
  135. */
  136. createTimer(period, callback, context = Context.defaultContext()) {
  137. if (typeof (period) !== 'number' || typeof (callback) !== 'function') {
  138. throw new TypeError('Invalid argument');
  139. }
  140. // The period unit is millisecond in JavaScript side. When being passed to the
  141. // C++ side, the value will be converted to nanosecond, which goes into a uint64_t
  142. // with maxmium value of 2^64-1. So the maxmium is UINT64_MAX in ns, that's 0x10c6f7a0b5ed in ms.
  143. const MAX_TIMER_PERIOD_IN_MILLISECOND = 0x10c6f7a0b5ed;
  144. if (period > 0x10c6f7a0b5ed || period < 0) {
  145. throw new RangeError('Parameter must be between ' + 0 + ' and ' + MAX_TIMER_PERIOD_IN_MILLISECOND);
  146. }
  147. let timerHandle = rclnodejs.createTimer(period, context.handle());
  148. let timer = new Timer(timerHandle, period, callback);
  149. debug('Finish creating timer, period = %d.', period);
  150. this._timers.push(timer);
  151. this.syncHandles();
  152. return timer;
  153. }
  154. /**
  155. * Create a Publisher.
  156. * @param {function|string|object} typeClass - The ROS message class,
  157. OR a string representing the message class, e.g. 'std_msgs/msg/String',
  158. OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
  159. * @param {string} topic - The name of the topic.
  160. * @param {object} options - The options argument used to parameterize the publisher.
  161. * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
  162. * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the publisher, default: QoS.profileDefault.
  163. * @return {Publisher} - An instance of Publisher.
  164. */
  165. createPublisher(typeClass, topic, options) {
  166. if (typeof (typeClass) === 'string' || typeof (typeClass) === 'object') {
  167. typeClass = loader.loadInterface(typeClass);
  168. }
  169. options = this._validateOptions(options);
  170. if (typeof (typeClass) !== 'function' || typeof (topic) !== 'string') {
  171. throw new TypeError('Invalid argument');
  172. }
  173. let publisher = Publisher.createPublisher(this.handle, typeClass, topic, options);
  174. debug('Finish creating publisher, topic = %s.', topic);
  175. this._publishers.push(publisher);
  176. return publisher;
  177. }
  178. /**
  179. * This callback is called when a message is published
  180. * @callback SubscriptionCallback
  181. * @param {Object} message - The message published
  182. * @see [Node.createSubscription]{@link Node#createSubscription}
  183. * @see [Node.createPublisher]{@link Node#createPublisher}
  184. * @see {@link Publisher}
  185. * @see {@link Subscription}
  186. */
  187. /**
  188. * Create a Subscription.
  189. * @param {function|string|object} typeClass - The ROS message class,
  190. OR a string representing the message class, e.g. 'std_msgs/msg/String',
  191. OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
  192. * @param {string} topic - The name of the topic.
  193. * @param {object} options - The options argument used to parameterize the subscription.
  194. * @param {boolean} options.enableTypedArray - The topic will use TypedArray if necessary, default: true.
  195. * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the subscription, default: QoS.profileDefault.
  196. * @param {SubscriptionCallback} callback - The callback to be call when receiving the topic subscribed.
  197. * @return {Subscription} - An instance of Subscription.
  198. * @see {@link SubscriptionCallback}
  199. */
  200. createSubscription(typeClass, topic, options, callback) {
  201. if (typeof (typeClass) === 'string' || typeof (typeClass) === 'object') {
  202. typeClass = loader.loadInterface(typeClass);
  203. }
  204. if (typeof options === 'function') {
  205. callback = options;
  206. options = undefined;
  207. }
  208. options = this._validateOptions(options);
  209. if (typeof (typeClass) !== 'function' || typeof (topic) !== 'string' || typeof (callback) !== 'function') {
  210. throw new TypeError('Invalid argument');
  211. }
  212. let subscription = Subscription.createSubscription(this.handle, typeClass, topic, options, callback);
  213. debug('Finish creating subscription, topic = %s.', topic);
  214. this._subscriptions.push(subscription);
  215. this.syncHandles();
  216. return subscription;
  217. }
  218. /**
  219. * Create a Client.
  220. * @param {function|string|object} typeClass - The ROS message class,
  221. OR a string representing the message class, e.g. 'std_msgs/msg/String',
  222. OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
  223. * @param {string} serviceName - The service name to request.
  224. * @param {object} options - The options argument used to parameterize the client.
  225. * @param {boolean} options.enableTypedArray - The response will use TypedArray if necessary, default: true.
  226. * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the client, default: QoS.profileDefault.
  227. * @return {Client} - An instance of Client.
  228. */
  229. createClient(typeClass, serviceName, options) {
  230. if (typeof (typeClass) === 'string' || typeof (typeClass) === 'object') {
  231. typeClass = loader.loadInterface(typeClass);
  232. }
  233. options = this._validateOptions(options);
  234. if (typeof (typeClass) !== 'function' || typeof (serviceName) !== 'string') {
  235. throw new TypeError('Invalid argument');
  236. }
  237. let client = Client.createClient(this.handle, serviceName, typeClass, options);
  238. debug('Finish creating client, service = %s.', serviceName);
  239. this._clients.push(client);
  240. this.syncHandles();
  241. return client;
  242. }
  243. /**
  244. * This callback is called when a request is sent to service
  245. * @callback RequestCallback
  246. * @param {Object} request - The request sent to the service
  247. * @param {Response} response - The response to client.
  248. Use [response.send()]{@link Response#send} to send response object to client
  249. * @return {undefined}
  250. * @see [Node.createService]{@link Node#createService}
  251. * @see [Client.sendRequest]{@link Client#sendRequest}
  252. * @see {@link Client}
  253. * @see {@link Service}
  254. * @see {@link Response#send}
  255. */
  256. /**
  257. * Create a Service.
  258. * @param {function|string|object} typeClass - The ROS message class,
  259. OR a string representing the message class, e.g. 'std_msgs/msg/String',
  260. OR an object representing the message class, e.g. {package: 'std_msgs', type: 'msg', name: 'String'}
  261. * @param {string} serviceName - The service name to offer.
  262. * @param {object} options - The options argument used to parameterize the service.
  263. * @param {boolean} options.enableTypedArray - The request will use TypedArray if necessary, default: true.
  264. * @param {QoS} options.qos - ROS Middleware "quality of service" settings for the service, default: QoS.profileDefault.
  265. * @param {RequestCallback} callback - The callback to be called when receiving request.
  266. * @return {Service} - An instance of Service.
  267. * @see {@link RequestCallback}
  268. */
  269. createService(typeClass, serviceName, options, callback) {
  270. if (typeof (typeClass) === 'string' || typeof (typeClass) === 'object') {
  271. typeClass = loader.loadInterface(typeClass);
  272. }
  273. if (typeof options === 'function') {
  274. callback = options;
  275. options = undefined;
  276. }
  277. options = this._validateOptions(options);
  278. if (typeof (typeClass) !== 'function' || typeof (serviceName) !== 'string' || typeof (callback) !== 'function') {
  279. throw new TypeError('Invalid argument');
  280. }
  281. let service = Service.createService(this.handle, serviceName, typeClass, options, callback);
  282. debug('Finish creating service, service = %s.', serviceName);
  283. this._services.push(service);
  284. this.syncHandles();
  285. return service;
  286. }
  287. /**
  288. * Create a guard condition.
  289. * @param {Function} callback - The callback to be called when the guard condition is triggered.
  290. * @return {GuardCondition} - An instance of GuardCondition.
  291. */
  292. createGuardCondition(callback) {
  293. if (typeof (callback) !== 'function') {
  294. throw new TypeError('Invalid argument');
  295. }
  296. let guard = GuardCondition.createGuardCondition(callback, this.context);
  297. debug('Finish creating guard condition');
  298. this._guards.push(guard);
  299. this.syncHandles();
  300. return guard;
  301. }
  302. /**
  303. * Destroy all resource allocated by this node, including
  304. * <code>Timer</code>s/<code>Publisher</code>s/<code>Subscription</code>s
  305. * /<code>Client</code>s/<code>Service</code>s
  306. * @return {undefined}
  307. */
  308. destroy() {
  309. if (this.spinning) {
  310. this.stopSpinning();
  311. }
  312. this.handle.release();
  313. this._timers = [];
  314. this._publishers = [];
  315. this._subscriptions = [];
  316. this._clients = [];
  317. this._services = [];
  318. this._guards = [];
  319. }
  320. /**
  321. * Destroy a Publisher.
  322. * @param {Publisher} publisher - The Publisher to be destroyed.
  323. * @return {undefined}
  324. */
  325. destroyPublisher(publisher) {
  326. if (!(publisher instanceof Publisher)) {
  327. throw new TypeError('Invalid argument');
  328. }
  329. this._destroyEntity(publisher, this._publishers, false);
  330. }
  331. /**
  332. * Destroy a Subscription.
  333. * @param {Subscription} subscription - The Subscription to be destroyed.
  334. * @return {undefined}
  335. */
  336. destroySubscription(subscription) {
  337. if (!(subscription instanceof Subscription)) {
  338. throw new TypeError('Invalid argument');
  339. }
  340. this._destroyEntity(subscription, this._subscriptions);
  341. }
  342. /**
  343. * Destroy a Client.
  344. * @param {Client} client - The Client to be destroyed.
  345. * @return {undefined}
  346. */
  347. destroyClient(client) {
  348. if (!(client instanceof Client)) {
  349. throw new TypeError('Invalid argument');
  350. }
  351. this._destroyEntity(client, this._clients);
  352. }
  353. /**
  354. * Destroy a Service.
  355. * @param {Service} service - The Service to be destroyed.
  356. * @return {undefined}
  357. */
  358. destroyService(service) {
  359. if (!(service instanceof Service)) {
  360. throw new TypeError('Invalid argument');
  361. }
  362. this._destroyEntity(service, this._services);
  363. }
  364. /**
  365. * Destroy a Timer.
  366. * @param {Timer} timer - The Timer to be destroyed.
  367. * @return {undefined}
  368. */
  369. destroyTimer(timer) {
  370. if (!(timer instanceof Timer)) {
  371. throw new TypeError('Invalid argument');
  372. }
  373. this._destroyEntity(timer, this._timers);
  374. }
  375. /**
  376. * Destroy a guard condition.
  377. * @param {GuardCondition} guard - The guard condition to be destroyed.
  378. * @return {undefined}
  379. */
  380. destroyGuardCondition(guard) {
  381. if (!(guard instanceof GuardCondition)) {
  382. throw new TypeError('Invalid argument');
  383. }
  384. this._destroyEntity(guard, this._guards);
  385. }
  386. /* Get the name of the node.
  387. * @return {string}
  388. */
  389. name() {
  390. return rclnodejs.getNodeName(this.handle);
  391. }
  392. /* Get the namespace of the node.
  393. * @return {string}
  394. */
  395. namespace() {
  396. return rclnodejs.getNamespace(this.handle);
  397. }
  398. /**
  399. * Get the list of published topics discovered by the provided node for the remote node name.
  400. * @param {string} nodeName - The name of the node.
  401. * @param {string} namespace - The name of the namespace.
  402. * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
  403. * @return {array} - An array of the names and types.
  404. */
  405. getPublisherNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
  406. return rclnodejs.getPublisherNamesAndTypesByNode(this.handle, nodeName, namespace, noDemangle);
  407. }
  408. /**
  409. * Get the list of published topics discovered by the provided node for the remote node name.
  410. * @param {string} nodeName - The name of the node.
  411. * @param {string} namespace - The name of the namespace.
  412. * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
  413. * @return {array} - An array of the names and types.
  414. */
  415. getSubscriptionNamesAndTypesByNode(nodeName, namespace, noDemangle = false) {
  416. return rclnodejs.getSubscriptionNamesAndTypesByNode(this.handle, nodeName, namespace, noDemangle);
  417. }
  418. /**
  419. * Get the list of service topics discovered by the provided node for the remote node name.
  420. * @param {string} nodeName - The name of the node.
  421. * @param {string} namespace - The name of the namespace.
  422. * @return {array} - An array of the names and types.
  423. */
  424. getServiceNamesAndTypesByNode(nodeName, namespace) {
  425. return rclnodejs.getServiceNamesAndTypesByNode(this.handle, nodeName, namespace);
  426. }
  427. /**
  428. * Get the list of topics discovered by the provided node.
  429. * @param {boolean} noDemangle - If true topic names and types returned will not be demangled, default: false.
  430. * @return {array} - An array of the names and types.
  431. */
  432. getTopicNamesAndTypes(noDemangle = false) {
  433. return rclnodejs.getTopicNamesAndTypes(this.handle, noDemangle);
  434. }
  435. /**
  436. * Get the list of services discovered by the provided node.
  437. * @return {array} - An array of the names and types.
  438. */
  439. getServiceNamesAndTypes() {
  440. return rclnodejs.getServiceNamesAndTypes(this.handle);
  441. }
  442. /**
  443. * Get the list of nodes discovered by the provided node.
  444. * @return {array} - An array of the names.
  445. */
  446. getNodeNames() {
  447. return this.getNodeNamesAndNamespaces().map(item => item.name);
  448. }
  449. /**
  450. * Get the list of nodes and their namespaces discovered by the provided node.
  451. * @return {array} - An array of the names and namespaces.
  452. */
  453. getNodeNamesAndNamespaces() {
  454. return rclnodejs.getNodeNames(this.handle);
  455. }
  456. /**
  457. * Return the number of publishers on a given topic.
  458. * @param {string} topic - The name of the topic.
  459. * @returns {number} - Number of publishers on the given topic.
  460. */
  461. countPublishers(topic) {
  462. let expandedTopic = rclnodejs.expandTopicName(topic, this._name, this._namespace);
  463. rclnodejs.validateTopicName(expandedTopic);
  464. return rclnodejs.countPublishers(this.handle, expandedTopic);
  465. }
  466. /**
  467. * Return the number of subscribers on a given topic.
  468. * @param {string} topic - The name of the topic.
  469. * @returns {number} - Number of subscribers on the given topic.
  470. */
  471. countSubscribers(topic) {
  472. let expandedTopic = rclnodejs.expandTopicName(topic, this._name, this._namespace);
  473. rclnodejs.validateTopicName(expandedTopic);
  474. return rclnodejs.countSubscribers(this.handle, expandedTopic);
  475. }
  476. }
  477. module.exports = Node;