470 likes | 757 Views
Multi-Robot Systems with ROS Lesson 10. Teaching Assistant: Roi Yehoshua roiyeho@gmail.com. Agenda. TAO events Allocating sub-plans Defining custom Next protocols Wandering robot example. TAO Events. TAO defines an event distribution system using an EventQueue
E N D
Multi-Robot Systems with ROS Lesson 10 Teaching Assistant: RoiYehoshua roiyeho@gmail.com
Agenda • TAO events • Allocating sub-plans • Defining custom Next protocols • Wandering robot example (C)2014 Roi Yehoshua
TAO Events • TAO defines an event distribution system using an EventQueue • It’s used for sharing events inside TAO machines • It’s possible to insert external events to the system (from ROS or other custom source) • It is thread safe (C)2014 Roi Yehoshua
TAO Event • Each Event is a path containing: • all context names • when this event was created • an event short name at the end • Example: /ContextName/EventName • When you compare two events you can use regular expressions as the name of one event by writing @ at the beggining of the name. • Example: @/Con.*/Event[NT]...e (C)2014 Roi Yehoshua
Events Inside TAO • TAO_RAISE(/STOP) - raise global event • TAO_RAISE(STOP) - raise an event related to TAO context • TAO_RAISE(PLAN_NAME/STOP) - raise an event related to TAO PLAN context event • TAO_STOP_CONDITION(event==TAO_EVENT(STOP)) - check condition based on an occurred event • TAO_EVENTS_DROP - Clear current events queue (C)2014 Roi Yehoshua
Events Outside TAO • Defining an event: • Event e("/STOP") - global / without context • Event e("STOP",call_context) - related to call_context • Raising the event: • events->raiseEvent(e); (C)2014 Roi Yehoshua
Events Interface • Event waitEvent() - block function until a new event arrives • Event tryGetEvent(bool& success) - get a new event if exists (non-blocking call) • boolisTerminated() const - check if the event system is closed • void drop_all() - (C)2014 Roi Yehoshua
Events Interface (C)2014 Roi Yehoshua
RosEventQueue • A class derived from decision_making::EventQueue • RosEventQueue creates a connection between ROS (/decision_making/NODE_NAME/events topic) and the internal EventQueue • Must be created after ros::init and ros_decision_making_init (C)2014 Roi Yehoshua
Events Example • In the following example, we will add a support for a STOP event to our Incrementer plan • Copy TaskWithStopCondition.cpp to Events.cpp • In the TAO machine definition, add a clause to the STOP condition that checks if a STOP event has occurred (C)2014 Roi Yehoshua
TaoEvents.cpp TAO(Incrementer) { TAO_PLANS { Increment } TAO_START_PLAN(Increment); TAO_BGN { TAO_PLAN(Increment) { TAO_START_CONDITION(true); TAO_CALL_TASK(incrementTask); TAO_ALLOCATE_EMPTY TAO_STOP_CONDITION(WM.counter == 100 || event == TAO_EVENT("/STOP")); TAO_NEXT_EMPTY } } TAO_END } (C)2014 Roi Yehoshua
Sending Events • Once running, your model will be able to receive events over the decision_making/[NODE_NAME]/events topic • Events to the model can be sent using: • For example, to send a STOP event to the tao_events node, type: • $ rostopic pub decision_making/[NODE_NAME]/events std_msgs/String "EVENT_NAME" • rostopic pub decision_making/tao_events/events std_msgs/String "STOP" (C)2014 Roi Yehoshua
Sending Events Example (C)2014 Roi Yehoshua
Allocating SubPlans • We will now change the incrementer plan so it allocates two sub-plans: • Even – this subplan will increment the counter when it has an even value • Odd – this subplan will increment the counter when it has an odd value • Copy BasicPlanWithWM.cpp to AllocatingSubPlans.cpp (C)2014 Roi Yehoshua
AllocatingSubPlans.cpp (1) structWorldModel : publicCallContextParameters { int counter; booleven_plan_finished; boolodd_plan_finished; string str() const { stringstream s; s << "counter = " << counter; return s.str(); } }; #define WM TAO_CONTEXT.parameters<WorldModel>() (C)2014 Roi Yehoshua
AllocatingSubPlans.cpp (2) TAO_HEADER(Even) TAO_HEADER(Odd) TAO(Incrementer) { TAO_PLANS { Increment } TAO_START_PLAN(Increment); TAO_BGN { TAO_PLAN(Increment) { TAO_START_CONDITION(WM.counter < 100); WM.even_plan_finished = false; WM.odd_plan_finished = false; TAO_ALLOCATE(AllocFirstReady) { TAO_SUBPLAN(Even); TAO_SUBPLAN(Odd); } TAO_STOP_CONDITION(WM.even_plan_finished || WM.odd_plan_finished); TAO_NEXT(NextFirstReady) { TAO_NEXT_PLAN(Increment); } } } TAO_END } (C)2014 Roi Yehoshua
AllocatingSubPlans.cpp (3) TAO(Even) { TAO_PLANS { Even, } TAO_START_PLAN(Even); TAO_BGN { TAO_PLAN(Even) { TAO_START_CONDITION(WM.counter % 2 == 0); WM.counter++; cout << "Even: " << WM.str() << endl; boost::this_thread::sleep(boost::posix_time::milliseconds(100)); TAO_ALLOCATE_EMPTY TAO_STOP_CONDITION(true); TAO_NEXT_EMPTY WM.even_plan_finished = true; } } TAO_END } (C)2014 Roi Yehoshua
AllocatingSubPlans.cpp (4) TAO(Odd) { TAO_PLANS{ Odd, } TAO_START_PLAN(Odd); TAO_BGN { TAO_PLAN(Odd) { TAO_START_CONDITION(WM.counter % 2 == 1); WM.counter++; cout << "Odd: " << WM.str() << endl; boost::this_thread::sleep(boost::posix_time::milliseconds(100)); TAO_ALLOCATE_EMPTY TAO_STOP_CONDITION(true); TAO_NEXT_EMPTY WM.odd_plan_finished = true; } } TAO_END } (C)2014 Roi Yehoshua
SubPlans Behavior • The sub-plans start executing only after the parent plan reaches its stop condition (and not right after allocation) • If the parent’s stop condition is true before any of its sub-plans started running, then the TAO machine ends • If the parent’s stop condition is false and the parent’s plan has a next plan, then it is unpredictable whether the next plan or the sub-plan starts first • Once a plan starts executing, no other plan can execute at the same time (unless using tasks) • The entire TAO machine ends only when the parent plan and all its sub-plans are finished (C)2014 Roi Yehoshua
Allocating SubPlans Demo (C)2014 Roi Yehoshua
Decision Graph (C)2014 Roi Yehoshua
Wandering Robot Plan • Now we will write a TAO plan for a wandering robot • The wandering plan will be composed of two sub-plans: • Drive – makes the robot drive forward until an obstacle is detected • Turn – makes the robot turn until the way becomes clear • Create a new package called tao_wandering: • $ cd ~/dmw/src • $ catkin_create_pkgtao_wanderingroscppdecision_makingdecision_making_parserrandom_numberssensor_msgsstd_msgsgeometry_msgs (C)2014 Roi Yehoshua
Wandering.cpp (1) #include <iostream> #include <ros/ros.h> #include <random_numbers/random_numbers.h> #include <sensor_msgs/LaserScan.h> #include <geometry_msgs/Twist.h> #include <decision_making/TAO.h> #include <decision_making/TAOStdProtocols.h> #include <decision_making/ROSTask.h> #include <decision_making/DecisionMaking.h> using namespace std; using namespace decision_making; (C)2014 Roi Yehoshua
Wandering.cpp (2) /*** Constants ***/ #define MIN_SCAN_ANGLE_RAD -45.0/180*M_PI #define MAX_SCAN_ANGLE_RAD +45.0/180*M_PI #define MIN_DIST_TO_OBSTACLE 0.8 // in meters /*** Variables ***/ random_numbers::RandomNumberGenerator _randomizer; ros::Publisher _velocityPublisher; /*** World model ***/ structWorldModel : publicCallContextParameters { boolobstacleDetected; booldriveFinished; boolturnFinished; string str() const { stringstream s; s << "obstacleDetected = " << obstacleDetected; return s.str(); } }; #define WM TAO_CONTEXT.parameters<WorldModel>() (C)2014 Roi Yehoshua
Wandering.cpp (3) /*** TAO machine ***/ TAO_HEADER(Drive) TAO_HEADER(Turn) TAO(Wandering) { TAO_PLANS { Wandering } TAO_START_PLAN(Wandering); TAO_BGN { TAO_PLAN(Wandering) { TAO_START_CONDITION(true); WM.driveFinished = false; WM.turnFinished = false; TAO_ALLOCATE(AllocFirstReady) { TAO_SUBPLAN(Drive); TAO_SUBPLAN(Turn); } TAO_STOP_CONDITION(WM.driveFinished || WM.turnFinished); TAO_NEXT(NextFirstReady) { TAO_NEXT_PLAN(Wandering); } } } TAO_END } (C)2014 Roi Yehoshua
Wandering.cpp (4) TAO(Drive) { TAO_PLANS { Drive, } TAO_START_PLAN(Drive); TAO_BGN { TAO_PLAN(Drive) { TAO_START_CONDITION(!WM.obstacleDetected); TAO_CALL_TASK(driveTask); TAO_ALLOCATE_EMPTY TAO_STOP_CONDITION(WM.obstacleDetected); TAO_NEXT_EMPTY WM.driveFinished = true; } } TAO_END } (C)2014 Roi Yehoshua
Wandering.cpp (5) TAO(Turn) { TAO_PLANS{ Turn, } TAO_START_PLAN(Turn); TAO_BGN { TAO_PLAN(Turn) { TAO_START_CONDITION(WM.obstacleDetected); TAO_CALL_TASK(turnTask); TAO_ALLOCATE_EMPTY TAO_STOP_CONDITION(true); TAO_NEXT_EMPTY WM.turnFinished = true; } } TAO_END } (C)2014 Roi Yehoshua
Wandering.cpp (6) /*** Task implementations***/ TaskResultdriveTask(string name, constCallContext& context, EventQueue& eventQueue) { ROS_INFO("Driving..."); geometry_msgs::Twist forwardMessage; forwardMessage.linear.x = 1.0; // Preemptive wait while (!eventQueue.isTerminated()) { _velocityPublisher.publish(forwardMessage); boost::this_thread::sleep(boost::posix_time::milliseconds(100.0)); } ROS_INFO("Obstacle detected!"); returnTaskResult::SUCCESS(); } (C)2014 Roi Yehoshua
Wandering.cpp (7) TaskResultturnTask(string name, constCallContext& context, EventQueue& eventQueue) { ROS_INFO("Turning..."); boolturnLeft = _randomizer.uniformInteger(0, 1); geometry_msgs::Twist turnMessage; turnMessage.angular.z = 2 * (turnLeft ? 1 : -1); inttimeToTurnMs = _randomizer.uniformInteger(2000, 4000); intturnLoops = timeToTurnMs / 100; for (inti = 0; i < turnLoops; i++) { _velocityPublisher.publish(turnMessage); boost::this_thread::sleep(boost::posix_time::milliseconds(100.0)); } returnTaskResult::SUCCESS(); } (C)2014 Roi Yehoshua
Wandering.cpp (8) /***ROS Subscriptions***/ voidonLaserScanMessage(constsensor_msgs::LaserScan::PtrlaserScanMessage, CallContext* context) { boolobstacleFound = false; intminIndex = ceil((MIN_SCAN_ANGLE_RAD - laserScanMessage->angle_min) / laserScanMessage->angle_increment); intmaxIndex = floor((MAX_SCAN_ANGLE_RAD - laserScanMessage->angle_min) / laserScanMessage->angle_increment); for (inti = minIndex; i <= maxIndex; i++) { if (laserScanMessage->ranges[i] < MIN_DIST_TO_OBSTACLE) { obstacleFound = true; } } context->parameters<WorldModel>().obstacleDetected = obstacleFound; } (C)2014 Roi Yehoshua
Wandering.cpp (9) int main(intargc, char **argv) { ros::init(argc, argv, "wandering_node"); ros::NodeHandlenh; ros_decision_making_init(argc, argv); // ROS spinner for topic subscriptions ros::AsyncSpinner spinner(1); spinner.start(); // Tasks registration LocalTasks::registrate("driveTask", driveTask); LocalTasks::registrate("turnTask", turnTask); RosEventQueueeventQueue; CallContext context; context.createParameters(newWorldModel()); (C)2014 Roi Yehoshua
Wandering.cpp (10) // CallContext must define a team teamwork::SharedMemory db; teamwork::Teammates teammates; teamwork::Team main_team = teamwork::createMainTeam(db, "main", teammates); context.team(TAO_CURRENT_TEAM_NAME, main_team.ptr()); // Subscription for the laser topic and velocity publisher creation ros::Subscriber laserSubscriber = nh.subscribe<void>("base_scan", 1, boost::function<void(constsensor_msgs::LaserScan::Ptr)>(boost::bind(onLaserScanMessage, _1, &context))); _velocityPublisher = nh.advertise<geometry_msgs::Twist>("cmd_vel", 100); eventQueue.async_spin(); ROS_INFO("Starting wandering machine..."); TaoWandering(&context, &eventQueue); eventQueue.close(); ROS_INFO("TAO finished."); return 0; } (C)2014 Roi Yehoshua
Wandering Launch File • Copy worlds directory from ~/catkin_ws/src/gazebo_navigation_multi package • Create a launch directory in the tao_wandering package • Add the following wandering.launch file (C)2014 Roi Yehoshua
Wandering Launch File <launch> <master auto="start"/> <param name="/use_sim_time" value="true"/> <!-- start gazebo --> <include file="$(find gazebo_ros)/launch/empty_world.launch"> <arg name="world_name" value="$(find tao_wandering)/worlds/willowgarage.world" /> </include> <param name="robot_description" command="$(find xacro)/xacro.py $(find lizi_description)/urdf/lizi.urdf"/> <node name="spawn_urdf" pkg="gazebo_ros" type="spawn_model" args="-x 34 -y 18 -z 0 -Y 1.57 -urdf -paramrobot_description -model lizi1" output="screen"/> <node name="wandering" pkg="tao_wandering" type="wandering_node" output="screen" /> </launch> (C)2014 Roi Yehoshua
Wandering Demo (C)2014 Roi Yehoshua
Custom Next Protocol • To define your own next protocol: • Create a class that inherits from decision_making::ProtocolNext • Implement the pure virtual function decide() • Call setDecision at the end of the function with the chosen plan ID (C)2014 Roi Yehoshua
Custom Next Protocol • In the following example, we will decompose the Turn plan into TurnLeft and TurnRightsubplans • We will create a custom RandomNext protocol that will randomly choose the next plan from its set of candidate plans • We will use this protocol to make the Turn plan randomly choose between TurnLeft and TurnRight as its continuation plan (C)2014 Roi Yehoshua
RandomNext Class classNextRandom: publicdecision_making::ProtocolNext { public: NextRandom(int& res, decision_making::CallContext* call_context, decision_making::EventQueue* events):ProtocolNext(res, call_context, events){} bool decide(){ vector<int> ready_index; for(size_ti = 0; i < options.size(); i++) if (options[i].isReady) ready_index.push_back(i); if (ready_index.size() == 0) returnfalse; inti = _randomizer.uniformInteger(0, ready_index.size() - 1); returnsetDecision(options[ready_index[i]].id); } }; (C)2014 Roi Yehoshua
Turn Plan (1) TAO(Turn) { TAO_PLANS{ Turn, TurnLeft, TurnRight } TAO_START_PLAN(Turn); TAO_BGN { TAO_PLAN(Turn) { TAO_START_CONDITION(WM.obstacleDetected); TAO_ALLOCATE_EMPTY TAO_STOP_CONDITION(true); TAO_NEXT(NextRandom) { TAO_NEXT_PLAN(TurnLeft); TAO_NEXT_PLAN(TurnRight); } } (C)2014 Roi Yehoshua
Turn Plan (2) TAO_PLAN(TurnLeft) { TAO_START_CONDITION(true); TAO_ALLOCATE_EMPTY; WM.turnLeft = false; TAO_CALL_TASK(turnTask); TAO_STOP_CONDITION(true); TAO_NEXT_EMPTY; WM.turnFinished = true; } TAO_PLAN(TurnRight) { TAO_START_CONDITION(true); TAO_ALLOCATE_EMPTY; WM.turnLeft = true; TAO_CALL_TASK(turnTask); TAO_STOP_CONDITION(true); TAO_NEXT_EMPTY; WM.turnFinished = true; } } TAO_END } (C)2014 Roi Yehoshua
World Model Changes • Add a boolean variable to the world model indicating which turn direction was chosen: structWorldModel : publicCallContextParameters { boolobstacleDetected; booldriveFinished; boolturnFinished; boolturnLeft; string str() const { stringstream s; s << "obstacleDetected = " << obstacleDetected; return s.str(); } }; #define WM TAO_CONTEXT.parameters<WorldModel>() (C)2014 Roi Yehoshua
Turn Task • Make the following changes to the turnTask callback: TaskResultturnTask(string name, constCallContext& context, EventQueue& eventQueue) { if (context.parameters<WorldModel>().turnLeft) ROS_INFO("Turning left..."); else ROS_INFO("Turning right..."); geometry_msgs::Twist turnMessage; turnMessage.angular.z = 2 * (context.parameters<WorldModel>().turnLeft ? 1 : -1); inttimeToTurnMs = _randomizer.uniformInteger(2000, 4000); intturnLoops = timeToTurnMs / 100; for (inti = 0; i < turnLoops; i++) { _velocityPublisher.publish(turnMessage); boost::this_thread::sleep(boost::posix_time::milliseconds(100.0)); } returnTaskResult::SUCCESS(); } (C)2014 Roi Yehoshua
Wandering Demo (C)2014 Roi Yehoshua
Homework (not for submission) • Create a custom next protocol for the turn plan that will choose between the following 3 plans: • Turn random when there is an obstacle on the front • Turn left when there is an obstacle on the right side • Turn right when there is an obstacle on the left side • Add a support for pausing/resuming the robot by sending an appropriate event (C)2014 Roi Yehoshua