RCL - RoboCLuedo  v1.0
Francesco Ganci - S4143910 - Experimental Robotics Lab - Assignment 1
robocluedo_main.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 
3 """! @file robocluedo_main.py
4 
5 <div><b>ROS Node Name</b>
6  <ul><li>robocluedo_main</li></ul></div>
7 @brief The main FSM of RCL
8 
9 @authors Francesco Ganci (S4143910)
10 @version v1.0
11 
12 <b>Description:</b> <br>
13 <p>
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>
17 
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.
21 </p>
22 
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>
26 
27 <b>Subscribers:</b> <br>
28 <ul>
29  <li>
30  <i>/hint</i> : Hint.msg <br>
31  hint from the oracle <br><br>
32  </li>
33 </ul>
34 
35 <b>Clients:</b> <br>
36 <ul>
37  <li>
38  <i>/go_to</i> : GoTo.srv <br>
39  see the service \ref GoToCallback <br><br>
40  </li>
41  <li>
42  <i>/random_room</i> : RandomRoom.srv <br>
43  see the service \ref ChooseRoomRandom <br><br>
44  </li>
45  <li>
46  <i>/check_solution</i> : checkSolution.srv <br>
47  see the service \ref checkSolutionCallback <br><br>
48  </li>
49  <li>
50  <i>/cluedo_armor/add_hint</i> : AddHint.srv <br>
51  see the service \ref ServiceAddHint <br><br>
52  </li>
53  <li>
54  <i>/cluedo_armor/find_consistent_h</i> : FindConsistentHypotheses.srv <br>
55  see the service \ref ServiceFindConsistentHypotheses <br><br>
56  </li>
57  <li>
58  <i>/cluedo_armor/wrong_hypothesis</i> : DiscardHypothesis.srv <br>
59  see the service \ref DiscardHypothesis <br><br>
60  </li>
61  <li>
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>
64  </li>
65 </ul>
66 
67 """
68 
69 import rospy
70 import smach, smach_ros
71 import random
72 
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
81 
82 
83 
84 # movement controller
85 
86 client_name_go_to = "/go_to"
87 
88 client_go_to = None
89 
90 # random target generator
91 
92 client_name_random_target = "/random_room"
93 
94 client_random_target = None
95 
96 # oracle channels
97 
98 subscriber_name_hint = "/hint"
99 
100 subscriber_hint = None
101 
102 client_name_check_solution = "/check_solution"
103 
104 client_check_solution = None
105 
106 # cluedo_ARMOR interface clients
107 
108 client_name_add_hint = "/cluedo_armor/add_hint"
109 
110 client_add_hint = None
111 
112 client_name_find_consistent_hyp = "/cluedo_armor/find_consistent_h"
113 
114 client_find_consistent_hyp = None
115 
116 client_name_wrong_hyp = "/cluedo_armor/wrong_hypothesis"
117 
118 client_wrong_hyp = None
119 
120 client_name_backup = "/cluedo_armor/backup"
121 
122 client_backup = None
123 
124 # global reference to the state machine
125 
126 robot_sm = None;
127 
128 
129 hint_received = False
130 
131 hint = None
132 
133 
134 target = ""
135 
136 actual_position = ""
137 
138 
139 charge_ready = False
140 
142 charge = None
143 
144 hypothesis_tag = ""
145 
146 
147 
148 def listen_for_hints( data ):
149  """!
150  @brief listen for a hint from the oracle, then buffer it
151 
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 .
154 
155  @param data : Hint.msg the hint received from the Oracle
156 
157  @todo the singlevar buffer should become a list buffer
158  """
159  global hint, hint_received
160 
161  hint_received = True
162  hint = data
163 
164 
165 
166 class robocluedo_random_target( smach.State ):
167  """!
168  @brief implementation of the state <i>random_target</i>.
169 
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
174  state. <br>
175 
176  This is also the <b>starting state</b> of the FSM.
177 
178  <div>
179  <b>FSM Evolution:</b>
180  <ul>
181  <li>
182  <u>move_robot</u> <i>to</i> robocluedo_moving <br>
183  after the requet, the robot has a target to reach. <br><br>
184  </li>
185  </ul>
186  </div>
187  """
188 
189  def __init__( self ):
190  smach.State.__init__( self, outcomes=['move_robot'] )
191 
192  def execute( self, d ):
193  global client_random_target
194  global actual_position, target
195 
196  # ask for a random room
197  tg = client_random_target( )
198 
199  # set the string to consume, then go to 'robocluedo_moving'
200  target = tg.room
201  if( actual_position == "" ):
202  actual_position = tg.room
203 
204  rospy.loginfo( "[state:%s] actual position: %s | random target: %s", "random_target", actual_position, target )
205 
206  return 'move_robot'
207 
208 
209 
210 class robocluedo_moving( smach.State ):
211  """!
212  @brief implementation of the state <i>moving</i>.
213 
214  The robot uses the robocluedo_movement_controller.cpp in order to move
215  from the actual position to the target position.
216 
217  <div>
218  <b>FSM Evolution:</b>
219  <ul>
220  <li>
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>
223  </li>
224  <li>
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>
227  </li>
228  </ul>
229  </div>
230  """
231 
232  def __init__( self ):
233  smach.State.__init__( self,
234  outcomes=['target_reached_no_charge', 'target_reached_ready_for_charge'] )
235 
236  def execute( self, d ):
237  global client_go_to
238  global actual_position, target
239  global charge_ready
240 
241  # reach the target
242  client_go_to( target )
243  actual_position = target
244  target = ""
245 
246  rospy.loginfo( "[state:%s] reached position: %s ", "moving", actual_position )
247 
248  if charge_ready:
249  return 'target_reached_ready_for_charge'
250  else:
251  return 'target_reached_no_charge'
252 
253 
254 
255 class robocluedo_listening_for_hints( smach.State ):
256  """!
257  @brief implementation of the state <i>listening_for_hints</i>.
258 
259  Simple checking: if there are messages in the buffer, update the
260  ontology. Otherwise, go further.
261 
262  <div>
263  <b>FSM Evolution:</b>
264  <ul>
265  <li>
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>
268  </li>
269  <li>
270  <u>no_hints</u> <i>to</i> robocluedo_reasoning <br>
271  the buffer is empty. <br><br>
272  </li>
273  </ul>
274  </div>
275  """
276 
277  def __init__( self ):
278  smach.State.__init__( self, outcomes=['received_a_hint', 'no_hints'] )
279 
280  def execute( self, d ):
281  global hint_received
282 
283  if hint_received:
284  rospy.loginfo( "[state:listening_for_hints] received a hint" )
285  return 'received_a_hint'
286  else:
287  rospy.loginfo( "[state:listening_for_hints] no new hints" )
288  return 'no_hints'
289 
290 
291 
292 class robocluedo_update_ontology( smach.State ):
293  """!
294  @brief implementation of the state <i>update_ontology</i>.
295 
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>
298 
299  Before going in the next state, the ontology is exported into a file.
300 
301  <div>
302  <b>FSM Evolution:</b>
303  <ul>
304  <li>
305  <u>done</u> <i>to</i> robocluedo_reasoning <br>
306  ontology update done <br><br>
307  </li>
308  </ul>
309  </div>
310 
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.
312  """
313 
314  def __init__( self ):
315  smach.State.__init__( self, outcomes=['done'] )
316 
317  def execute( self, d ):
318  global client_add_hint, client_backup
319  global hint, hint_received
320 
321  rospy.loginfo( "[state:update_ontology] received hint: HP%d(%s:%s)", hint.HintID, hint.HintType, hint.HintContent )
322 
323  # add the hint to the ontology
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 )
330 
331  # clear the buffer
332  hint_received = False
333  hint = None
334 
335  # make a backup of the ontology
336  rospy.loginfo( "[state:update_ontology] writing backup of the ontology..." )
337  client_backup( )
338  rospy.loginfo( "[state:update_ontology] ontology saved" )
339 
340  return 'done'
341 
342 
343 
344 class robocluedo_reasoning( smach.State ):
345  """!
346  @brief implementation of the state <i>reasoning</i>.
347 
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
351  retrives it.
352 
353  <div>
354  <b>FSM Evolution:</b>
355  <ul>
356  <li>
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>
359  </li>
360  <li>
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>
363  </li>
364  <li>
365  <u>no_consistent_hp</u> <i>to</i> robocluedo_random_target <br>
366  there are no COMPLETE hypotheses yet <br><br>
367  </li>
368  </ul>
369  </div>
370  """
371 
372  def __init__( self ):
373  smach.State.__init__( self, outcomes=['ready_for_charge', 'not_in_the_right_place', 'no_consistent_hp'] )
374 
375  def execute( self, d ):
376  global client_find_consistent_hyp
377  global charge_ready, charge
378  global actual_position, target
379 
380  # check for any consistent hypothesis
381  hplist = ( client_find_consistent_hyp( ) ).hyp
382  if ( len( hplist ) == 0 ):
383  return 'no_consistent_hp'
384 
385  rospy.loginfo( "[state:reasoning] complete hypotheses found: %d", len( hplist ) )
386  for h in hplist:
387  rospy.loginfo( "[state:reasoning] %s(who:%s, where:%s, what:%s)", h.tag, h.who, h.where, h.what )
388 
389  # choose random one among the complete hypotheses
390  to_evaluate = random.choice( hplist )
391 
392  rospy.loginfo( "[state:reasoning] selected hyp. with tag: %s | actual position: %s", to_evaluate.tag, actual_position )
393 
394  # store the hypothesis
395  charge_ready = True
396  charge = to_evaluate
397 
398  # choose the outcome
399  if ( actual_position == charge.where ):
400  return 'ready_for_charge'
401  else:
402  target = charge.where
403  return 'not_in_the_right_place'
404 
405 
406 
407 class robocluedo_charge( smach.State ):
408  """!
409  @brief implementation of the state <i>charge</i>.
410 
411  The system is ready to make a charge.
412 
413  <div>
414  <b>FSM Evolution:</b>
415  <ul>
416  <li>
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>
419  </li>
420  <li>
421  <u>wrong_hp</u> <i>to</i> robocluedo_listening_for_hints <br>
422  the solution is not correct. <br><br>
423  </li>
424  </ul>
425  </div>
426  """
427 
428  def __init__( self ):
429  smach.State.__init__( self, outcomes=['case_solved', 'wrong_hp'] )
430 
431  def execute( self, d ):
432  global client_check_solution, client_wrong_hyp
433  global charge, charge_ready
434 
435  # send the charge to the oracle
436  req = CheckSolutionRequest( )
437  req.Who = charge.who
438  req.What = charge.what
439  req.Where = charge.where
440 
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 )
443 
444  # success?
445  if solution.MysterySolved:
446  rospy.loginfo( "[state:charge] mystery solved!" )
447  return 'case_solved'
448 
449  # explore again
450  rospy.loginfo( "[state:charge] WRONG hypothesis with tag: %s", charge.tag )
451  client_wrong_hyp( charge.tag )
452  charge_ready = False
453  charge = None
454 
455  rospy.loginfo( "[state:charge] exploring again..." )
456  return 'wrong_hp'
457 
458 
459 
460 # create the state machine
462  """!
463  @brief define the state machine of the robot before starting
464  """
465  global robot_sm
466 
467  robot_sm = smach.StateMachine( outcomes=[ 'mystery_solved' ] )
468 
469  with robot_sm:
470  smach.StateMachine.add( 'robocluedo_random_target',
472  transitions={'move_robot':'robocluedo_moving'} )
473 
474  smach.StateMachine.add( 'robocluedo_moving',
475  robocluedo_moving( ),
476  transitions={'target_reached_no_charge':'robocluedo_listening_for_hints',
477  'target_reached_ready_for_charge':'robocluedo_charge'} )
478 
479  smach.StateMachine.add( 'robocluedo_listening_for_hints',
481  transitions={ 'received_a_hint':'robocluedo_update_ontology',
482  'no_hints':'robocluedo_reasoning' } )
483 
484  smach.StateMachine.add( 'robocluedo_update_ontology',
486  transitions={ 'done':'robocluedo_reasoning' } )
487 
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' } )
493 
494  smach.StateMachine.add( 'robocluedo_charge',
495  robocluedo_charge( ),
496  transitions={ 'case_solved':'mystery_solved',
497  'wrong_hp':'robocluedo_listening_for_hints' } )
498 
499 
500 
501 
502 # message on shutdown
503 
504 def on_shut( ):
505  rospy.loginfo( "[robocluedo_main] closing..." )
506 
507 
508 
509 
510 if __name__ == "__main__":
511  rospy.init_node( "robocluedo_main" )
512  rospy.on_shutdown( on_shut )
513 
514 
515  # movement controller
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" )
520 
521 
522  # random target generator
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" )
527 
528 
529  # oracle
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" )
533 
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" )
538 
539 
540  # aRMOR interfaces
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" )
545 
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" )
550 
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" )
555 
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" )
560 
561 
562  # create the state machine
564 
565 
566  sis = smach_ros.IntrospectionServer('server_name', robot_sm, '/SM_ROOT')
567  sis.start()
568 
569  # start the system
570  rospy.loginfo( "[robocluedo_main] starting the FSM..." )
571  robot_sm.execute( )
572  rospy.loginfo( "[robocluedo_main] elementary, Whatson. " )
573 
574  # rospy.spin()
575  sis.stop()
robocluedo_main.robocluedo_listening_for_hints.__init__
def __init__(self)
Definition: robocluedo_main.py:277
robocluedo_main.robocluedo_update_ontology.execute
def execute(self, d)
Definition: robocluedo_main.py:317
robocluedo_main.robocluedo_reasoning.__init__
def __init__(self)
Definition: robocluedo_main.py:372
robocluedo_main.robocluedo_random_target.__init__
def __init__(self)
Definition: robocluedo_main.py:189
robocluedo_main.robocluedo_listening_for_hints
implementation of the state listening_for_hints.
Definition: robocluedo_main.py:255
robocluedo_main.robocluedo_charge.execute
def execute(self, d)
Definition: robocluedo_main.py:431
robocluedo_main.robocluedo_reasoning.execute
def execute(self, d)
Definition: robocluedo_main.py:375
robocluedo_main.robocluedo_random_target.execute
def execute(self, d)
Definition: robocluedo_main.py:192
robocluedo_main.robocluedo_moving.__init__
def __init__(self)
Definition: robocluedo_main.py:232
robocluedo_main.listen_for_hints
def listen_for_hints(data)
listen for a hint from the oracle, then buffer it
Definition: robocluedo_main.py:148
robocluedo_main.robocluedo_reasoning
implementation of the state reasoning.
Definition: robocluedo_main.py:344
robocluedo_main.robocluedo_moving.execute
def execute(self, d)
Definition: robocluedo_main.py:236
robocluedo_main.robocluedo_update_ontology
implementation of the state update_ontology.
Definition: robocluedo_main.py:292
robocluedo_main.robocluedo_update_ontology.__init__
def __init__(self)
Definition: robocluedo_main.py:314
robocluedo_main.create_state_machine
def create_state_machine()
define the state machine of the robot before starting
Definition: robocluedo_main.py:461
robocluedo_main.robocluedo_moving
implementation of the state moving.
Definition: robocluedo_main.py:210
robocluedo_main.robocluedo_charge
implementation of the state charge.
Definition: robocluedo_main.py:407
robocluedo_main.robocluedo_listening_for_hints.execute
def execute(self, d)
Definition: robocluedo_main.py:280
robocluedo_main.robocluedo_random_target
implementation of the state random_target.
Definition: robocluedo_main.py:166
robocluedo_main.robocluedo_charge.__init__
def __init__(self)
Definition: robocluedo_main.py:428