simple_action_server.py
Go to the documentation of this file.
00001 #! /usr/bin/env python
00002 # Copyright (c) 2009, Willow Garage, Inc.
00003 # All rights reserved.
00004 #
00005 # Redistribution and use in source and binary forms, with or without
00006 # modification, are permitted provided that the following conditions are met:
00007 #
00008 #     * Redistributions of source code must retain the above copyright
00009 #       notice, this list of conditions and the following disclaimer.
00010 #     * Redistributions in binary form must reproduce the above copyright
00011 #       notice, this list of conditions and the following disclaimer in the
00012 #       documentation and/or other materials provided with the distribution.
00013 #     * Neither the name of the Willow Garage, Inc. nor the names of its
00014 #       contributors may be used to endorse or promote products derived from
00015 #       this software without specific prior written permission.
00016 #
00017 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00018 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00019 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00020 # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
00021 # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
00022 # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
00023 # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
00024 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
00025 # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
00026 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00027 # POSSIBILITY OF SUCH DAMAGE.
00028 #
00029 # Author: Alexander Sorokin.
00030 # Based on C++ simple_action_server.h by Eitan Marder-Eppstein
00031 
00032 import rospy
00033 
00034 import threading
00035 import traceback
00036 
00037 from actionlib_msgs.msg import *
00038 
00039 from actionlib import ActionServer
00040 from actionlib.server_goal_handle import ServerGoalHandle;
00041 
00042 def nop_cb(goal_handle):
00043     pass
00044 
00045 
00046 ## @class SimpleActionServer
00047 ## @brief The SimpleActionServer
00048 ## implements a singe goal policy on top of the ActionServer class. The
00049 ## specification of the policy is as follows: only one goal can have an
00050 ## active status at a time, new goals preempt previous goals based on the
00051 ## stamp in their GoalID field (later goals preempt earlier ones), an
00052 ## explicit preempt goal preempts all goals with timestamps that are less
00053 ## than or equal to the stamp associated with the preempt, accepting a new
00054 ## goal implies successful preemption of any old goal and the status of the
00055 ## old goal will be change automatically to reflect this.
00056 class SimpleActionServer:
00057     ## @brief Constructor for a SimpleActionServer
00058     ## @param name A name for the action server
00059     ## @param execute_cb Optional callback that gets called in a separate thread whenever
00060     ## a new goal is received, allowing users to have blocking callbacks.
00061     ## Adding an execute callback also deactivates the goalCallback.
00062     ## @param  auto_start A boolean value that tells the ActionServer wheteher or not to start publishing as soon as it comes up. THIS SHOULD ALWAYS BE SET TO FALSE TO AVOID RACE CONDITIONS and start() should be called after construction of the server.
00063     def __init__(self, name, ActionSpec, execute_cb = None, auto_start = True):
00064 
00065         self.new_goal = False
00066         self.preempt_request = False
00067         self.new_goal_preempt_request = False
00068 
00069         self.execute_callback = execute_cb;
00070         self.goal_callback = None;
00071         self.preempt_callback = None;
00072 
00073         self.need_to_terminate = False
00074         self.terminate_mutex = threading.RLock();
00075         self.lock = threading.RLock();
00076 
00077         self.execute_condition = threading.Condition(self.lock);
00078 
00079         self.current_goal = ServerGoalHandle();
00080         self.next_goal = ServerGoalHandle();
00081 
00082         if self.execute_callback:
00083             self.execute_thread = threading.Thread(None, self.executeLoop);
00084             self.execute_thread.start();
00085         else:
00086             self.execute_thread = None
00087 
00088         #create the action server
00089         self.action_server = ActionServer(name, ActionSpec, self.internal_goal_callback,self.internal_preempt_callback,auto_start);
00090 
00091 
00092     def __del__(self):
00093         if hasattr(self, 'execute_callback') and self.execute_callback:
00094             with self.terminate_mutex:
00095                 self.need_to_terminate = True;
00096 
00097             assert(self.execute_thread);
00098             self.execute_thread.join();
00099 
00100 
00101     ## @brief Accepts a new goal when one is available The status of this
00102     ## goal is set to active upon acceptance, and the status of any
00103     ## previously active goal is set to preempted. Preempts received for the
00104     ## new goal between checking if isNewGoalAvailable or invokation of a
00105     ## goal callback and the acceptNewGoal call will not trigger a preempt
00106     ## callback.  This means, isPreemptReqauested should be called after
00107     ## accepting the goal even for callback-based implementations to make
00108     ## sure the new goal does not have a pending preempt request.
00109     ## @return A shared_ptr to the new goal.
00110     def accept_new_goal(self):
00111         with self.lock:
00112             if not self.new_goal or not self.next_goal.get_goal():
00113                 rospy.logerr("Attempting to accept the next goal when a new goal is not available");
00114                 return None;
00115 
00116             #check if we need to send a preempted message for the goal that we're currently pursuing
00117             if self.is_active() and self.current_goal.get_goal() and self.current_goal != self.next_goal:
00118                 self.current_goal.set_canceled(None, "This goal was canceled because another goal was received by the simple action server");
00119 
00120             rospy.logdebug("Accepting a new goal");
00121 
00122             #accept the next goal
00123             self.current_goal = self.next_goal;
00124             self.new_goal = False;
00125 
00126             #set preempt to request to equal the preempt state of the new goal
00127             self.preempt_request = self.new_goal_preempt_request;
00128             self.new_goal_preempt_request = False;
00129 
00130             #set the status of the current goal to be active
00131             self.current_goal.set_accepted("This goal has been accepted by the simple action server");
00132 
00133             return self.current_goal.get_goal();
00134 
00135 
00136     ## @brief Allows  polling implementations to query about the availability of a new goal
00137     ## @return True if a new goal is available, false otherwise
00138     def is_new_goal_available(self):
00139         return self.new_goal;
00140 
00141 
00142     ## @brief Allows  polling implementations to query about preempt requests
00143     ## @return True if a preempt is requested, false otherwise
00144     def is_preempt_requested(self):
00145         return self.preempt_request;
00146 
00147     ## @brief Allows  polling implementations to query about the status of the current goal
00148     ## @return True if a goal is active, false otherwise
00149     def is_active(self):
00150        if not self.current_goal.get_goal():
00151            return False;
00152 
00153        status = self.current_goal.get_goal_status().status;
00154        return status == actionlib_msgs.msg.GoalStatus.ACTIVE or status == actionlib_msgs.msg.GoalStatus.PREEMPTING;
00155 
00156     ## @brief Sets the status of the active goal to succeeded
00157     ## @param  result An optional result to send back to any clients of the goal
00158     def set_succeeded(self,result=None, text=""):
00159       with self.lock:
00160           if not result:
00161               result=self.get_default_result();
00162           self.current_goal.set_succeeded(result, text);
00163 
00164     ## @brief Sets the status of the active goal to aborted
00165     ## @param  result An optional result to send back to any clients of the goal
00166     def set_aborted(self, result = None, text=""):
00167         with self.lock:
00168             if not result:
00169                 result=self.get_default_result();
00170             self.current_goal.set_aborted(result, text);
00171 
00172     ## @brief Publishes feedback for a given goal
00173     ## @param  feedback Shared pointer to the feedback to publish
00174     def publish_feedback(self,feedback):
00175         self.current_goal.publish_feedback(feedback);
00176 
00177 
00178     def get_default_result(self):
00179         return self.action_server.ActionResultType();
00180 
00181     ## @brief Sets the status of the active goal to preempted
00182     ## @param  result An optional result to send back to any clients of the goal
00183     def set_preempted(self,result=None, text=""):
00184         if not result:
00185             result=self.get_default_result();
00186         with self.lock:
00187             rospy.logdebug("Setting the current goal as canceled");
00188             self.current_goal.set_canceled(result, text);
00189 
00190     ## @brief Allows users to register a callback to be invoked when a new goal is available
00191     ## @param cb The callback to be invoked
00192     def register_goal_callback(self,cb):
00193         if self.execute_callback:
00194             rospy.logwarn("Cannot call SimpleActionServer.register_goal_callback() because an executeCallback exists. Not going to register it.");
00195         else:
00196             self.goal_callback = cb;
00197 
00198     ## @brief Allows users to register a callback to be invoked when a new preempt request is available
00199     ## @param cb The callback to be invoked
00200     def register_preempt_callback(self, cb):
00201         self.preempt_callback = cb;
00202 
00203 
00204     ## @brief Explicitly start the action server, used it auto_start is set to false
00205     def start(self):
00206         self.action_server.start();
00207 
00208 
00209     ## @brief Callback for when the ActionServer receives a new goal and passes it on
00210     def internal_goal_callback(self, goal):
00211           self.execute_condition.acquire();
00212 
00213           try:
00214               rospy.logdebug("A new goal %shas been recieved by the single goal action server",goal.get_goal_id().id);
00215 
00216               #check that the timestamp is past that of the current goal and the next goal
00217               if((not self.current_goal.get_goal() or goal.get_goal_id().stamp >= self.current_goal.get_goal_id().stamp)
00218                  and (not self.next_goal.get_goal() or goal.get_goal_id().stamp >= self.next_goal.get_goal_id().stamp)):
00219                   #if next_goal has not been accepted already... its going to get bumped, but we need to let the client know we're preempting
00220                   if(self.next_goal.get_goal() and (not self.current_goal.get_goal() or self.next_goal != self.current_goal)):
00221                       self.next_goal.set_canceled(None, "This goal was canceled because another goal was received by the simple action server");
00222 
00223                   self.next_goal = goal;
00224                   self.new_goal = True;
00225                   self.new_goal_preempt_request = False;
00226 
00227                   #if the server is active, we'll want to call the preempt callback for the current goal
00228                   if(self.is_active()):
00229                       self.preempt_request = True;
00230                       #if the user has registered a preempt callback, we'll call it now
00231                       if(self.preempt_callback):
00232                           self.preempt_callback();
00233 
00234                   #if the user has defined a goal callback, we'll call it now
00235                   if self.goal_callback:
00236                       self.goal_callback();
00237 
00238                   #Trigger runLoop to call execute()
00239                   self.execute_condition.notify();
00240                   self.execute_condition.release();
00241               else:
00242                   #the goal requested has already been preempted by a different goal, so we're not going to execute it
00243                   goal.set_canceled(None, "This goal was canceled because another goal was received by the simple action server");
00244                   self.execute_condition.release();
00245           except Exception, e:
00246               rospy.logerr("SimpleActionServer.internal_goal_callback - exception %s",str(e))
00247               self.execute_condition.release();
00248 
00249     ## @brief Callback for when the ActionServer receives a new preempt and passes it on
00250     def internal_preempt_callback(self,preempt):
00251           with self.lock:
00252               rospy.logdebug("A preempt has been received by the SimpleActionServer");
00253 
00254               #if the preempt is for the current goal, then we'll set the preemptRequest flag and call the user's preempt callback
00255               if(preempt == self.current_goal):
00256                   rospy.logdebug("Setting preempt_request bit for the current goal to TRUE and invoking callback");
00257                   self.preempt_request = True;
00258 
00259                   #if the user has registered a preempt callback, we'll call it now
00260                   if(self.preempt_callback):
00261                       self.preempt_callback();
00262               #if the preempt applies to the next goal, we'll set the preempt bit for that
00263               elif(preempt == self.next_goal):
00264                   rospy.logdebug("Setting preempt request bit for the next goal to TRUE");
00265                   self.new_goal_preempt_request = True;
00266 
00267     ## @brief Called from a separate thread to call blocking execute calls
00268     def executeLoop(self):
00269           loop_duration = rospy.Duration.from_sec(.1);
00270 
00271           while (not rospy.is_shutdown()):
00272               rospy.logdebug("SAS: execute");
00273 
00274               with self.terminate_mutex:
00275                   if (self.need_to_terminate):
00276                       break;
00277 
00278               shall_run=False;
00279               with self.lock:
00280                   if (self.is_active()):
00281                       rospy.logerr("Should never reach this code with an active goal");
00282                       return
00283                   elif (self.is_new_goal_available()):
00284                       goal = self.accept_new_goal();
00285                       if not self.execute_callback:
00286                           rospy.logerr("execute_callback_ must exist. This is a bug in SimpleActionServer");
00287                           return
00288                       shall_run=True
00289 
00290               if shall_run:
00291                   try:
00292                       self.execute_callback(goal)
00293 
00294                       if self.is_active():
00295                           rospy.logwarn("Your executeCallback did not set the goal to a terminal status.  " +
00296                                         "This is a bug in your ActionServer implementation. Fix your code!  "+
00297                                         "For now, the ActionServer will set this goal to aborted");
00298                           self.set_aborted(None, "No terminal state was set.");
00299                   except Exception, ex:
00300                       rospy.logerr("Exception in your execute callback: %s\n%s", str(ex),
00301                                    traceback.format_exc())
00302                       self.set_aborted(None, "Exception in execute callback: %s" % str(ex))
00303 
00304               with self.execute_condition:
00305                   self.execute_condition.wait(loop_duration.to_sec());
00306 
00307 
00308 


actionlib
Author(s): Eitan Marder-Eppstein, Vijay Pradeep
autogenerated on Thu Jan 2 2014 11:03:49