Source: tf/TFClient.js

  1. /**
  2. * @fileOverview
  3. * @author David Gossow - dgossow@willowgarage.com
  4. */
  5. var ActionClient = require('../actionlib/ActionClient');
  6. var Goal = require('../actionlib/Goal');
  7. var Service = require('../core/Service.js');
  8. var ServiceRequest = require('../core/ServiceRequest.js');
  9. var Topic = require('../core/Topic.js');
  10. var Transform = require('../math/Transform');
  11. /**
  12. * A TF Client that listens to TFs from tf2_web_republisher.
  13. *
  14. * @constructor
  15. * @param {Object} options
  16. * @param {Ros} options.ros - The ROSLIB.Ros connection handle.
  17. * @param {string} [options.fixedFrame=base_link] - The fixed frame.
  18. * @param {number} [options.angularThres=2.0] - The angular threshold for the TF republisher.
  19. * @param {number} [options.transThres=0.01] - The translation threshold for the TF republisher.
  20. * @param {number} [options.rate=10.0] - The rate for the TF republisher.
  21. * @param {number} [options.updateDelay=50] - The time (in ms) to wait after a new subscription
  22. * to update the TF republisher's list of TFs.
  23. * @param {number} [options.topicTimeout=2.0] - The timeout parameter for the TF republisher.
  24. * @param {string} [options.serverName=/tf2_web_republisher] - The name of the tf2_web_republisher server.
  25. * @param {string} [options.repubServiceName=/republish_tfs] - The name of the republish_tfs service (non groovy compatibility mode only).
  26. */
  27. function TFClient(options) {
  28. options = options || {};
  29. this.ros = options.ros;
  30. this.fixedFrame = options.fixedFrame || 'base_link';
  31. this.angularThres = options.angularThres || 2.0;
  32. this.transThres = options.transThres || 0.01;
  33. this.rate = options.rate || 10.0;
  34. this.updateDelay = options.updateDelay || 50;
  35. var seconds = options.topicTimeout || 2.0;
  36. var secs = Math.floor(seconds);
  37. var nsecs = Math.floor((seconds - secs) * 1000000000);
  38. this.topicTimeout = {
  39. secs: secs,
  40. nsecs: nsecs
  41. };
  42. this.serverName = options.serverName || '/tf2_web_republisher';
  43. this.repubServiceName = options.repubServiceName || '/republish_tfs';
  44. this.currentGoal = false;
  45. this.currentTopic = false;
  46. this.frameInfos = {};
  47. this.republisherUpdateRequested = false;
  48. this._subscribeCB = null;
  49. this._isDisposed = false;
  50. // Create an Action Client
  51. this.actionClient = new ActionClient({
  52. ros : options.ros,
  53. serverName : this.serverName,
  54. actionName : 'tf2_web_republisher/TFSubscriptionAction',
  55. omitStatus : true,
  56. omitResult : true
  57. });
  58. // Create a Service Client
  59. this.serviceClient = new Service({
  60. ros: options.ros,
  61. name: this.repubServiceName,
  62. serviceType: 'tf2_web_republisher/RepublishTFs'
  63. });
  64. }
  65. /**
  66. * Process the incoming TF message and send them out using the callback
  67. * functions.
  68. *
  69. * @param {Object} tf - The TF message from the server.
  70. */
  71. TFClient.prototype.processTFArray = function(tf) {
  72. var that = this;
  73. tf.transforms.forEach(function(transform) {
  74. var frameID = transform.child_frame_id;
  75. if (frameID[0] === '/')
  76. {
  77. frameID = frameID.substring(1);
  78. }
  79. var info = this.frameInfos[frameID];
  80. if (info) {
  81. info.transform = new Transform({
  82. translation : transform.transform.translation,
  83. rotation : transform.transform.rotation
  84. });
  85. info.cbs.forEach(function(cb) {
  86. cb(info.transform);
  87. });
  88. }
  89. }, this);
  90. };
  91. /**
  92. * Create and send a new goal (or service request) to the tf2_web_republisher
  93. * based on the current list of TFs.
  94. */
  95. TFClient.prototype.updateGoal = function() {
  96. var goalMessage = {
  97. source_frames : Object.keys(this.frameInfos),
  98. target_frame : this.fixedFrame,
  99. angular_thres : this.angularThres,
  100. trans_thres : this.transThres,
  101. rate : this.rate
  102. };
  103. // if we're running in groovy compatibility mode (the default)
  104. // then use the action interface to tf2_web_republisher
  105. if(this.ros.groovyCompatibility) {
  106. if (this.currentGoal) {
  107. this.currentGoal.cancel();
  108. }
  109. this.currentGoal = new Goal({
  110. actionClient : this.actionClient,
  111. goalMessage : goalMessage
  112. });
  113. this.currentGoal.on('feedback', this.processTFArray.bind(this));
  114. this.currentGoal.send();
  115. }
  116. else {
  117. // otherwise, use the service interface
  118. // The service interface has the same parameters as the action,
  119. // plus the timeout
  120. goalMessage.timeout = this.topicTimeout;
  121. var request = new ServiceRequest(goalMessage);
  122. this.serviceClient.callService(request, this.processResponse.bind(this));
  123. }
  124. this.republisherUpdateRequested = false;
  125. };
  126. /**
  127. * Process the service response and subscribe to the tf republisher
  128. * topic.
  129. *
  130. * @param {Object} response - The service response containing the topic name.
  131. */
  132. TFClient.prototype.processResponse = function(response) {
  133. // Do not setup a topic subscription if already disposed. Prevents a race condition where
  134. // The dispose() function is called before the service call receives a response.
  135. if (this._isDisposed) {
  136. return;
  137. }
  138. // if we subscribed to a topic before, unsubscribe so
  139. // the republisher stops publishing it
  140. if (this.currentTopic) {
  141. this.currentTopic.unsubscribe(this._subscribeCB);
  142. }
  143. this.currentTopic = new Topic({
  144. ros: this.ros,
  145. name: response.topic_name,
  146. messageType: 'tf2_web_republisher/TFArray'
  147. });
  148. this._subscribeCB = this.processTFArray.bind(this);
  149. this.currentTopic.subscribe(this._subscribeCB);
  150. };
  151. /**
  152. * Subscribe to the given TF frame.
  153. *
  154. * @param {string} frameID - The TF frame to subscribe to.
  155. * @param {function} callback - Function with the following params:
  156. * @param {Transform} callback.transform - The transform data.
  157. */
  158. TFClient.prototype.subscribe = function(frameID, callback) {
  159. // remove leading slash, if it's there
  160. if (frameID[0] === '/')
  161. {
  162. frameID = frameID.substring(1);
  163. }
  164. // if there is no callback registered for the given frame, create empty callback list
  165. if (!this.frameInfos[frameID]) {
  166. this.frameInfos[frameID] = {
  167. cbs: []
  168. };
  169. if (!this.republisherUpdateRequested) {
  170. setTimeout(this.updateGoal.bind(this), this.updateDelay);
  171. this.republisherUpdateRequested = true;
  172. }
  173. }
  174. // if we already have a transform, callback immediately
  175. else if (this.frameInfos[frameID].transform) {
  176. callback(this.frameInfos[frameID].transform);
  177. }
  178. this.frameInfos[frameID].cbs.push(callback);
  179. };
  180. /**
  181. * Unsubscribe from the given TF frame.
  182. *
  183. * @param {string} frameID - The TF frame to unsubscribe from.
  184. * @param {function} callback - The callback function to remove.
  185. */
  186. TFClient.prototype.unsubscribe = function(frameID, callback) {
  187. // remove leading slash, if it's there
  188. if (frameID[0] === '/')
  189. {
  190. frameID = frameID.substring(1);
  191. }
  192. var info = this.frameInfos[frameID];
  193. for (var cbs = info && info.cbs || [], idx = cbs.length; idx--;) {
  194. if (cbs[idx] === callback) {
  195. cbs.splice(idx, 1);
  196. }
  197. }
  198. if (!callback || cbs.length === 0) {
  199. delete this.frameInfos[frameID];
  200. }
  201. };
  202. /**
  203. * Unsubscribe and unadvertise all topics associated with this TFClient.
  204. */
  205. TFClient.prototype.dispose = function() {
  206. this._isDisposed = true;
  207. this.actionClient.dispose();
  208. if (this.currentTopic) {
  209. this.currentTopic.unsubscribe(this._subscribeCB);
  210. }
  211. };
  212. module.exports = TFClient;