3 """! @file robocluedo_main.py
5 <div><b>ROS Node Name</b>
6 <ul><li>robocluedo_main</li></ul></div>
7 @brief The main FSM of RCL
9 @authors Francesco Ganci (S4143910)
12 <b>Description:</b> <br>
14 The main part of this architecture is the FSM implemented in this node,
15 which uses the ROS framework <i>smach</i>. See the diagrams to understand
16 how this node works. <br>
18 this node has been implemented thinking on the maximum flexibility: the
19 way it works can be easily extended or integrated with other components
20 with small, or also absent, changes.
23 <b>UML component</b><br>
24 (See \ref robocluedo_arch the overal architecture, for further informations)<br>
25 <img src="UML_components_robocluedo_main.png" /><br>
27 <b>Subscribers:</b> <br>
30 <i>/hint</i> : Hint.msg <br>
31 hint from the oracle <br><br>
38 <i>/go_to</i> : GoTo.srv <br>
39 see the service \ref GoToCallback <br><br>
42 <i>/random_room</i> : RandomRoom.srv <br>
43 see the service \ref ChooseRoomRandom <br><br>
46 <i>/check_solution</i> : checkSolution.srv <br>
47 see the service \ref checkSolutionCallback <br><br>
50 <i>/cluedo_armor/add_hint</i> : AddHint.srv <br>
51 see the service \ref ServiceAddHint <br><br>
54 <i>/cluedo_armor/find_consistent_h</i> : FindConsistentHypotheses.srv <br>
55 see the service \ref ServiceFindConsistentHypotheses <br><br>
58 <i>/cluedo_armor/wrong_hypothesis</i> : DiscardHypothesis.srv <br>
59 see the service \ref DiscardHypothesis <br><br>
62 <i>/cluedo_armor/backup</i> : <a href="http://docs.ros.org/en/api/std_srvs/html/srv/Trigger.html">std_srvs::Trigger</a> <br>
63 see the service \ref ServiceBackupOntology <br><br>
70 import smach, smach_ros
73 from robocluedo_msgs.srv
import GoTo, GoToRequest, GoToResponse
74 from robocluedo_msgs.msg
import Hint
75 from robocluedo_msgs.srv
import CheckSolution, CheckSolutionRequest, CheckSolutionResponse
76 from robocluedo_msgs.srv
import AddHint, AddHintRequest, AddHintResponse
77 from robocluedo_msgs.srv
import FindConsistentHypotheses, FindConsistentHypothesesRequest, FindConsistentHypothesesResponse
78 from robocluedo_msgs.srv
import DiscardHypothesis, DiscardHypothesisRequest, DiscardHypothesisResponse
79 from robocluedo_msgs.srv
import RandomRoom, RandomRoomRequest, RandomRoomResponse
80 from std_srvs.srv
import Trigger, TriggerRequest, TriggerResponse
86 client_name_go_to =
"/go_to"
92 client_name_random_target =
"/random_room"
94 client_random_target =
None
98 subscriber_name_hint =
"/hint"
100 subscriber_hint =
None
102 client_name_check_solution =
"/check_solution"
104 client_check_solution =
None
108 client_name_add_hint =
"/cluedo_armor/add_hint"
110 client_add_hint =
None
112 client_name_find_consistent_hyp =
"/cluedo_armor/find_consistent_h"
114 client_find_consistent_hyp =
None
116 client_name_wrong_hyp =
"/cluedo_armor/wrong_hypothesis"
118 client_wrong_hyp =
None
120 client_name_backup =
"/cluedo_armor/backup"
129 hint_received =
False
150 @brief listen for a hint from the oracle, then buffer it
152 This callback receives the message, and puts it in a single-variable
153 buffer, ready to be consumed in the state robocluedo_update_ontology .
155 @param data : Hint.msg the hint received from the Oracle
157 @todo the singlevar buffer should become a list buffer
159 global hint, hint_received
168 @brief implementation of the state <i>random_target</i>.
170 During this state, the robot asks for a random target to the node
171 robocluedo_random_room.cpp . The state is reachable only when there
172 are no consistent hypotheses at the end of the phase
173 robocluedo_reasoning . The position to reach is stored for the next
176 This is also the <b>starting state</b> of the FSM.
179 <b>FSM Evolution:</b>
182 <u>move_robot</u> <i>to</i> robocluedo_moving <br>
183 after the requet, the robot has a target to reach. <br><br>
190 smach.State.__init__( self, outcomes=[
'move_robot'] )
193 global client_random_target
194 global actual_position, target
197 tg = client_random_target( )
201 if( actual_position ==
"" ):
202 actual_position = tg.room
204 rospy.loginfo(
"[state:%s] actual position: %s | random target: %s",
"random_target", actual_position, target )
212 @brief implementation of the state <i>moving</i>.
214 The robot uses the robocluedo_movement_controller.cpp in order to move
215 from the actual position to the target position.
218 <b>FSM Evolution:</b>
221 <u>target_reached_no_charge</u> <i>to</i> robocluedo_listening_for_hints <br>
222 the robot still has not a COMPLETE hypothesis to send to the Oracle <br><br>
225 <u>target_reached_ready_for_charge</u> <i>to</i> robocluedo_charge <br>
226 the robot is in the right place to send a COMPLETE hypothesis to the Oracle <br><br>
233 smach.State.__init__( self,
234 outcomes=[
'target_reached_no_charge',
'target_reached_ready_for_charge'] )
238 global actual_position, target
242 client_go_to( target )
243 actual_position = target
246 rospy.loginfo(
"[state:%s] reached position: %s ",
"moving", actual_position )
249 return 'target_reached_ready_for_charge'
251 return 'target_reached_no_charge'
257 @brief implementation of the state <i>listening_for_hints</i>.
259 Simple checking: if there are messages in the buffer, update the
260 ontology. Otherwise, go further.
263 <b>FSM Evolution:</b>
266 <u>received_a_hint</u> <i>to</i> robocluedo_update_ontology <br>
267 the Oracle sent a hint, the buffer is full<br><br>
270 <u>no_hints</u> <i>to</i> robocluedo_reasoning <br>
271 the buffer is empty. <br><br>
278 smach.State.__init__( self, outcomes=[
'received_a_hint',
'no_hints'] )
284 rospy.loginfo(
"[state:listening_for_hints] received a hint" )
285 return 'received_a_hint'
287 rospy.loginfo(
"[state:listening_for_hints] no new hints" )
294 @brief implementation of the state <i>update_ontology</i>.
296 During this state, the system loads the hint from the Oracle into the
297 ontology, using the services from robocluedo_armor_interface.cpp . <br>
299 Before going in the next state, the ontology is exported into a file.
302 <b>FSM Evolution:</b>
305 <u>done</u> <i>to</i> robocluedo_reasoning <br>
306 ontology update done <br><br>
311 @todo exporting the ontology at each update could be too much expensive in terms of performances. There should be a setting (using parameter server) which lets the user to select when to make a backup of the ontology on file.
315 smach.State.__init__( self, outcomes=[
'done'] )
318 global client_add_hint, client_backup
319 global hint, hint_received
321 rospy.loginfo(
"[state:update_ontology] received hint: HP%d(%s:%s)", hint.HintID, hint.HintType, hint.HintContent )
324 hintreq = AddHintRequest( )
325 hintreq.hypID = hint.HintID
326 hintreq.property = hint.HintType
327 hintreq.Aelem =
"HP" + str(hint.HintID)
328 hintreq.Belem = hint.HintContent
329 client_add_hint( hintreq )
332 hint_received =
False
336 rospy.loginfo(
"[state:update_ontology] writing backup of the ontology..." )
338 rospy.loginfo(
"[state:update_ontology] ontology saved" )
346 @brief implementation of the state <i>reasoning</i>.
348 Here the system searches for any COMPLETE hypothesis using the services
349 provided by robocluedo_armor_interface.cpp . If the robot is not in the
350 right place to make the charge, it stores the charge, move, and then
354 <b>FSM Evolution:</b>
357 <u>ready_for_charge</u> <i>to</i> robocluedo_charge <br>
358 the robot found a COMPLETE hypothesis and is in the place of the hypothesis <br><br>
361 <u>not_in_the_right_place</u> <i>to</i> robocluedo_moving <br>
362 the robot found a COMPLETE hypothesis but is not in the right place to send the charge <br><br>
365 <u>no_consistent_hp</u> <i>to</i> robocluedo_random_target <br>
366 there are no COMPLETE hypotheses yet <br><br>
373 smach.State.__init__( self, outcomes=[
'ready_for_charge',
'not_in_the_right_place',
'no_consistent_hp'] )
376 global client_find_consistent_hyp
377 global charge_ready, charge
378 global actual_position, target
381 hplist = ( client_find_consistent_hyp( ) ).hyp
382 if ( len( hplist ) == 0 ):
383 return 'no_consistent_hp'
385 rospy.loginfo(
"[state:reasoning] complete hypotheses found: %d", len( hplist ) )
387 rospy.loginfo(
"[state:reasoning] %s(who:%s, where:%s, what:%s)", h.tag, h.who, h.where, h.what )
390 to_evaluate = random.choice( hplist )
392 rospy.loginfo(
"[state:reasoning] selected hyp. with tag: %s | actual position: %s", to_evaluate.tag, actual_position )
399 if ( actual_position == charge.where ):
400 return 'ready_for_charge'
402 target = charge.where
403 return 'not_in_the_right_place'
409 @brief implementation of the state <i>charge</i>.
411 The system is ready to make a charge.
414 <b>FSM Evolution:</b>
417 <u>case_solved</u> <i>to</i> <b>END STATE</b> <br>
418 the oracle replies with a positive result: case solved. <br><br>
421 <u>wrong_hp</u> <i>to</i> robocluedo_listening_for_hints <br>
422 the solution is not correct. <br><br>
429 smach.State.__init__( self, outcomes=[
'case_solved',
'wrong_hp'] )
432 global client_check_solution, client_wrong_hyp
433 global charge, charge_ready
436 req = CheckSolutionRequest( )
438 req.What = charge.what
439 req.Where = charge.where
441 rospy.loginfo(
"[state:charge] asking to the oracle: (who:%s, where:%s, what:%s)", charge.who, charge.where, charge.what )
442 solution = client_check_solution( req )
445 if solution.MysterySolved:
446 rospy.loginfo(
"[state:charge] mystery solved!" )
450 rospy.loginfo(
"[state:charge] WRONG hypothesis with tag: %s", charge.tag )
451 client_wrong_hyp( charge.tag )
455 rospy.loginfo(
"[state:charge] exploring again..." )
463 @brief define the state machine of the robot before starting
467 robot_sm = smach.StateMachine( outcomes=[
'mystery_solved' ] )
470 smach.StateMachine.add(
'robocluedo_random_target',
472 transitions={
'move_robot':
'robocluedo_moving'} )
474 smach.StateMachine.add(
'robocluedo_moving',
476 transitions={
'target_reached_no_charge':
'robocluedo_listening_for_hints',
477 'target_reached_ready_for_charge':
'robocluedo_charge'} )
479 smach.StateMachine.add(
'robocluedo_listening_for_hints',
481 transitions={
'received_a_hint':
'robocluedo_update_ontology',
482 'no_hints':
'robocluedo_reasoning' } )
484 smach.StateMachine.add(
'robocluedo_update_ontology',
486 transitions={
'done':
'robocluedo_reasoning' } )
488 smach.StateMachine.add(
'robocluedo_reasoning',
490 transitions={
'ready_for_charge':
'robocluedo_charge',
491 'not_in_the_right_place':
'robocluedo_moving',
492 'no_consistent_hp':
'robocluedo_random_target' } )
494 smach.StateMachine.add(
'robocluedo_charge',
496 transitions={
'case_solved':
'mystery_solved',
497 'wrong_hp':
'robocluedo_listening_for_hints' } )
505 rospy.loginfo(
"[robocluedo_main] closing..." )
510 if __name__ ==
"__main__":
511 rospy.init_node(
"robocluedo_main" )
512 rospy.on_shutdown( on_shut )
516 rospy.loginfo(
"[robocluedo_main] calling service %s ...", client_name_go_to )
517 rospy.wait_for_service( client_name_go_to )
518 client_go_to = rospy.ServiceProxy( client_name_go_to, GoTo )
519 rospy.loginfo(
"[robocluedo_main] OK" )
523 rospy.loginfo(
"[robocluedo_main] calling service %s ...", client_name_random_target )
524 rospy.wait_for_service( client_name_random_target )
525 client_random_target = rospy.ServiceProxy( client_name_random_target, RandomRoom )
526 rospy.loginfo(
"[robocluedo_main] OK" )
530 rospy.loginfo(
"[robocluedo_main] subscribing to %s ...", subscriber_name_hint )
531 subscriber_hint = rospy.Subscriber( subscriber_name_hint, Hint, listen_for_hints )
532 rospy.loginfo(
"[robocluedo_main] OK" )
534 rospy.loginfo(
"[robocluedo_main] calling service %s ...", client_name_check_solution )
535 rospy.wait_for_service( client_name_check_solution )
536 client_check_solution = rospy.ServiceProxy( client_name_check_solution, CheckSolution )
537 rospy.loginfo(
"[robocluedo_main] OK" )
541 rospy.loginfo(
"[robocluedo_main] calling service %s ...", client_name_add_hint )
542 rospy.wait_for_service( client_name_add_hint )
543 client_add_hint = rospy.ServiceProxy( client_name_add_hint, AddHint )
544 rospy.loginfo(
"[robocluedo_main] OK" )
546 rospy.loginfo(
"[robocluedo_main] calling service %s ...", client_name_find_consistent_hyp )
547 rospy.wait_for_service( client_name_find_consistent_hyp )
548 client_find_consistent_hyp = rospy.ServiceProxy( client_name_find_consistent_hyp, FindConsistentHypotheses )
549 rospy.loginfo(
"[robocluedo_main] OK" )
551 rospy.loginfo(
"[robocluedo_main] calling service %s ...", client_name_wrong_hyp )
552 rospy.wait_for_service( client_name_wrong_hyp )
553 client_wrong_hyp = rospy.ServiceProxy( client_name_wrong_hyp, DiscardHypothesis )
554 rospy.loginfo(
"[robocluedo_main] OK" )
556 rospy.loginfo(
"[%s] asking for service [%s] ...",
"robocluedo_main", client_name_backup )
557 rospy.wait_for_service( client_name_backup )
558 client_backup = rospy.ServiceProxy( client_name_backup, Trigger )
559 rospy.loginfo(
"[%s] OK!",
"robocluedo_main" )
566 sis = smach_ros.IntrospectionServer(
'server_name', robot_sm,
'/SM_ROOT')
570 rospy.loginfo(
"[robocluedo_main] starting the FSM..." )
572 rospy.loginfo(
"[robocluedo_main] elementary, Whatson. " )