Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags. |
Implementing an Action Layer
Description: C++ WalkthroughTutorial Level:
Next Tutorial: Other use cases of the MDM package
The following example shows how a simple MDM Action Layer can be implemented. The present Action Layer interprets the high-level actions Patrol, Assistance Response, Trespassing Response and Emergency Response. The former of these is itself a POMDP (this is an example of hierarchical control), while the remaining actions are carried out by finite-state controllers defined using the SMACH package. Please note that MDM is not affiliated with or dependent on SMACH -- we use it here because we find it to be a very nice package to implement relatively simple behaviors, but any other framework could be used instead (or none at all, you can implement your own behaviors if you'd like).
1 #include <boost/bind.hpp>
2
3 #include <ros/ros.h>
4 #include <std_srvs/Empty.h>
5
6 #include <markov_decision_making/ActionLayer.h>
7
8 using namespace ros;
9 using namespace markov_decision_making;
10
11 class Actions
12 {
13 public:
14 Actions () :
15 patrol_stop_client_
16 (nh_.serviceClient<std_srvs::Empty>("patrol_POMDP/stop")),
17 patrol_reset_client_
18 (nh_.serviceClient<std_srvs::Empty>("patrol_POMDP/reset")),
19 assistance_SMACH_client_
20 (nh_.serviceClient<std_srvs::Empty>("assistance_SMACH/act")),
21 trespassing_SMACH_client_
22 (nh_.serviceClient<std_srvs::Empty>("trespassing_SMACH/act")),
23 emergency_SMACH_client_
24 (nh_.serviceClient<std_srvs::Empty>("emergency_SMACH/act")){}
25
26 void patrolPOMDP() {
27 std_srvs::Empty e;
28 patrol_reset_client_.call (e);
29 }
30
31 void assistanceSMACH() {
32 std_srvs::Empty e;
33 patrol_stop_client_.call (e);
34 assistance_SMACH_client_.call (e);
35 }
36
37 void trespassingSMACH() {
38 std_srvs::Empty e;
39 patrol_stop_client_.call (e);
40 trespassing_SMACH_client_.call (e);
41 }
42
43 void emergencySMACH() {
44 std_srvs::Empty e;
45 patrol_stop_client_.call (e);
46 emergency_SMACH_client_.call (e);
47 }
48 private:
49 NodeHandle nh_;
50 ServiceClient patrol_stop_client_;
51 ServiceClient patrol_reset_client_;
52 ServiceClient assistance_SMACH_client_;
53 ServiceClient trespassing_SMACH_client_;
54 ServiceClient emergency_SMACH_client_;
55 };
56
57 int main (int argc, char** argv)
58 {
59 init (argc, argv, "action_layer_example");
60
61 Actions am;
62 ActionLayer al;
63 //Patrol Action
64 al.addAction (boost::bind (&Actions::patrolPOMDP, &am));
65 //Assistance Response Action
66 al.addAction (boost::bind (&Actions::assistanceSMACH, &am));
67 //Trespassing Response Action
68 al.addAction (boost::bind (&Actions::trespassingSMACH, &am));
69 //Emergency Response Action
70 al.addAction (boost::bind (&Actions::emergencySMACH, &am));
71
72 spin();
73
74 return 0;
75 }
The most important aspect of this example is how actions are bound to functions in the action layer:
1 Actions am;
2 ActionLayer al;
3 //Patrol Action
4 al.addAction (boost::bind (&Actions::patrolPOMDP, &am));
5 //Assistance Response Action
6 al.addAction (boost::bind (&Actions::assistanceSMACH, &am));
7 //Trespassing Response Action
8 al.addAction (boost::bind (&Actions::trespassingSMACH, &am));
9 //Emergency Response Action
10 al.addAction (boost::bind (&Actions::emergencySMACH, &am));
This will create an Action Layer with four associated actions, where each of them is implemented by a method in the auxiliary Actions class. Note that the latter class is not a part of MDM -- it is simply a design choice, a generic way of containing all action implementations in the same object. The addAction(.) function accepts boost::function pointers (to functions with no arguments / return values), so using bind on the methods of our auxiliary class directly returns the desired type. Those methods will be immediately called when their respective action is received through the ~/action topic. For example, receiving action 0 will trigger the Actions::patrolPOMDP() method.
In this example, all action-bound functions hand over the control of the agent to other modules through ROS service calls. This is also a design choice -- using services lets the Action Layer know when and if its client modules receive a request for execution, so it allows for a more secure control of the program. The execution on the client side is outside of the scope of the Action Layer (and of this example). Each SMACH finite-state controller is triggered by calling an <>\act service in its respective namespace, which can advertised by a SMACH Service State. The execution of a lower-level MDP/POMDP can also be controlled via service requests: Control Layers automatically advertise services to stop,start or reset their execution (the latter essentially stops and starts the controller from its initial conditions). In this particular implementation, the lower-level POMDP (patrol_POMDP) is controlled by calling its stop/reset services.
The downside to the service-based approach is that a client which should not be running in a given context may also need an explicit request to stop its execution. This is true, in particular, for the lower-level MDPs/POMDPs.