Source: actionlib/SimpleActionServer.js

  1. /**
  2. * @fileOverview
  3. * @author Laura Lindzey - lindzey@gmail.com
  4. */
  5. var Topic = require('../core/Topic');
  6. var Message = require('../core/Message');
  7. var EventEmitter2 = require('eventemitter2').EventEmitter2;
  8. /**
  9. * An actionlib action server client.
  10. *
  11. * Emits the following events:
  12. * * 'goal' - Goal sent by action client.
  13. * * 'cancel' - Action client has canceled the request.
  14. *
  15. * @constructor
  16. * @param {Object} options
  17. * @param {Ros} options.ros - The ROSLIB.Ros connection handle.
  18. * @param {string} options.serverName - The action server name, like '/fibonacci'.
  19. * @param {string} options.actionName - The action message name, like 'actionlib_tutorials/FibonacciAction'.
  20. */
  21. function SimpleActionServer(options) {
  22. var that = this;
  23. options = options || {};
  24. this.ros = options.ros;
  25. this.serverName = options.serverName;
  26. this.actionName = options.actionName;
  27. // create and advertise publishers
  28. this.feedbackPublisher = new Topic({
  29. ros : this.ros,
  30. name : this.serverName + '/feedback',
  31. messageType : this.actionName + 'Feedback'
  32. });
  33. this.feedbackPublisher.advertise();
  34. var statusPublisher = new Topic({
  35. ros : this.ros,
  36. name : this.serverName + '/status',
  37. messageType : 'actionlib_msgs/GoalStatusArray'
  38. });
  39. statusPublisher.advertise();
  40. this.resultPublisher = new Topic({
  41. ros : this.ros,
  42. name : this.serverName + '/result',
  43. messageType : this.actionName + 'Result'
  44. });
  45. this.resultPublisher.advertise();
  46. // create and subscribe to listeners
  47. var goalListener = new Topic({
  48. ros : this.ros,
  49. name : this.serverName + '/goal',
  50. messageType : this.actionName + 'Goal'
  51. });
  52. var cancelListener = new Topic({
  53. ros : this.ros,
  54. name : this.serverName + '/cancel',
  55. messageType : 'actionlib_msgs/GoalID'
  56. });
  57. // Track the goals and their status in order to publish status...
  58. this.statusMessage = new Message({
  59. header : {
  60. stamp : {secs : 0, nsecs : 100},
  61. frame_id : ''
  62. },
  63. status_list : []
  64. });
  65. // needed for handling preemption prompted by a new goal being received
  66. this.currentGoal = null; // currently tracked goal
  67. this.nextGoal = null; // the one that'll be preempting
  68. goalListener.subscribe(function(goalMessage) {
  69. if(that.currentGoal) {
  70. that.nextGoal = goalMessage;
  71. // needs to happen AFTER rest is set up
  72. that.emit('cancel');
  73. } else {
  74. that.statusMessage.status_list = [{goal_id : goalMessage.goal_id, status : 1}];
  75. that.currentGoal = goalMessage;
  76. that.emit('goal', goalMessage.goal);
  77. }
  78. });
  79. // helper function to determine ordering of timestamps
  80. // returns t1 < t2
  81. var isEarlier = function(t1, t2) {
  82. if(t1.secs > t2.secs) {
  83. return false;
  84. } else if(t1.secs < t2.secs) {
  85. return true;
  86. } else if(t1.nsecs < t2.nsecs) {
  87. return true;
  88. } else {
  89. return false;
  90. }
  91. };
  92. // TODO: this may be more complicated than necessary, since I'm
  93. // not sure if the callbacks can ever wind up with a scenario
  94. // where we've been preempted by a next goal, it hasn't finished
  95. // processing, and then we get a cancel message
  96. cancelListener.subscribe(function(cancelMessage) {
  97. // cancel ALL goals if both empty
  98. if(cancelMessage.stamp.secs === 0 && cancelMessage.stamp.secs === 0 && cancelMessage.id === '') {
  99. that.nextGoal = null;
  100. if(that.currentGoal) {
  101. that.emit('cancel');
  102. }
  103. } else { // treat id and stamp independently
  104. if(that.currentGoal && cancelMessage.id === that.currentGoal.goal_id.id) {
  105. that.emit('cancel');
  106. } else if(that.nextGoal && cancelMessage.id === that.nextGoal.goal_id.id) {
  107. that.nextGoal = null;
  108. }
  109. if(that.nextGoal && isEarlier(that.nextGoal.goal_id.stamp,
  110. cancelMessage.stamp)) {
  111. that.nextGoal = null;
  112. }
  113. if(that.currentGoal && isEarlier(that.currentGoal.goal_id.stamp,
  114. cancelMessage.stamp)) {
  115. that.emit('cancel');
  116. }
  117. }
  118. });
  119. // publish status at pseudo-fixed rate; required for clients to know they've connected
  120. var statusInterval = setInterval( function() {
  121. var currentTime = new Date();
  122. var secs = Math.floor(currentTime.getTime()/1000);
  123. var nsecs = Math.round(1000000000*(currentTime.getTime()/1000-secs));
  124. that.statusMessage.header.stamp.secs = secs;
  125. that.statusMessage.header.stamp.nsecs = nsecs;
  126. statusPublisher.publish(that.statusMessage);
  127. }, 500); // publish every 500ms
  128. }
  129. SimpleActionServer.prototype.__proto__ = EventEmitter2.prototype;
  130. /**
  131. * Set action state to succeeded and return to client.
  132. *
  133. * @param {Object} result - The result to return to the client.
  134. */
  135. SimpleActionServer.prototype.setSucceeded = function(result) {
  136. var resultMessage = new Message({
  137. status : {goal_id : this.currentGoal.goal_id, status : 3},
  138. result : result
  139. });
  140. this.resultPublisher.publish(resultMessage);
  141. this.statusMessage.status_list = [];
  142. if(this.nextGoal) {
  143. this.currentGoal = this.nextGoal;
  144. this.nextGoal = null;
  145. this.emit('goal', this.currentGoal.goal);
  146. } else {
  147. this.currentGoal = null;
  148. }
  149. };
  150. /**
  151. * Set action state to aborted and return to client.
  152. *
  153. * @param {Object} result - The result to return to the client.
  154. */
  155. SimpleActionServer.prototype.setAborted = function(result) {
  156. var resultMessage = new Message({
  157. status : {goal_id : this.currentGoal.goal_id, status : 4},
  158. result : result
  159. });
  160. this.resultPublisher.publish(resultMessage);
  161. this.statusMessage.status_list = [];
  162. if(this.nextGoal) {
  163. this.currentGoal = this.nextGoal;
  164. this.nextGoal = null;
  165. this.emit('goal', this.currentGoal.goal);
  166. } else {
  167. this.currentGoal = null;
  168. }
  169. };
  170. /**
  171. * Send a feedback message.
  172. *
  173. * @param {Object} feedback - The feedback to send to the client.
  174. */
  175. SimpleActionServer.prototype.sendFeedback = function(feedback) {
  176. var feedbackMessage = new Message({
  177. status : {goal_id : this.currentGoal.goal_id, status : 1},
  178. feedback : feedback
  179. });
  180. this.feedbackPublisher.publish(feedbackMessage);
  181. };
  182. /**
  183. * Handle case where client requests preemption.
  184. */
  185. SimpleActionServer.prototype.setPreempted = function() {
  186. this.statusMessage.status_list = [];
  187. var resultMessage = new Message({
  188. status : {goal_id : this.currentGoal.goal_id, status : 2},
  189. });
  190. this.resultPublisher.publish(resultMessage);
  191. if(this.nextGoal) {
  192. this.currentGoal = this.nextGoal;
  193. this.nextGoal = null;
  194. this.emit('goal', this.currentGoal.goal);
  195. } else {
  196. this.currentGoal = null;
  197. }
  198. };
  199. module.exports = SimpleActionServer;