(!) 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.

在状态间传递用户数据

Description: 这个教程将教会你如何从一个状态(机)传递数据到下一个状态(机)。

Tutorial Level: BEGINNER

Next Tutorial: 创建一个分级状态机

指定用户数据

一个状态可能需要一些输入数据来完成它的工作,或者它可能有一些输出数据,它想要提供给其他状态。状态的输入和输出数据称为userdata。当你构造一个状态时,你可以指定它需要/提供的用户数据字段的名称。

   1   class Foo(smach.State):
   2      def __init__(self, outcomes=['outcome1', 'outcome2'],
   3                         input_keys=['foo_input'],
   4                         output_keys=['foo_output'])
   5 
   6      def execute(self, userdata):
   7         # Do something with userdata
   8         if userdata.foo_input == 1:
   9             return 'outcome1'
  10         else:
  11             userdata.foo_output = 3
  12             return 'outcome2'
  • input_keys列表列出了一个状态需要运行的所有输入。一个状态声明它期望在用户数据中包含这些字段。execute方法提供了一个userdata 结构体的副本。状态可以从它在input_keys列表中枚举所有userdata字段中读取,但它不能写入任何这些字段。

  • output_keys列表列出了一个状态提供的所有输出。在output_keys列表中,状态可以写入所有枚举的用户数据结构体里的字段。

/!\ 注意: 通过input_keys从userdata获得的对象被包装为不变性,因此一个状态不能调用这些对象上的方法。如果需要一个可变的输入对象,则必须在input_keys和output_keys中指定相同的密钥。如果你没有传递对象,或者不需要调用方法或修改它们,那么应该在input_keys和output_keys中使用惟一名称,以避免混淆和潜在的错误。

user_data_single.png

状态的接口由其结果、输入键和输出键来定义。

连接用户数据

当向状态机添加状态时,还需要连接用户数据字段,以便允许状态之间传递数据。例如,如果状态FOO生成'foo_output',而状态BAR需要'bar_input',那么你可以使用名称映射将这两个用户数据端口连接在一起:

   1   sm_top = smach.StateMachine(outcomes=['outcome4','outcome5'],
   2                           input_keys=['sm_input'],
   3                           output_keys=['sm_output'])
   4   with sm_top:
   5      smach.StateMachine.add('FOO', Foo(),
   6                             transitions={'outcome1':'BAR',
   7                                          'outcome2':'outcome4'},
   8                             remapping={'foo_input':'sm_input',
   9                                        'foo_output':'sm_data'})
  10      smach.StateMachine.add('BAR', Bar(),
  11                             transitions={'outcome2':'FOO'},
  12                             remapping={'bar_input':'sm_data',
  13                                        'bar_output1':'sm_output'})

重新映射字段将状态的in/output_key映射到状态机的userdata字段。所以当你重新映射'x':'y'的时候:

  • x 需要是状态的input_key或output_key。
  • y 将自动成为状态机的用户数据的一部分。

/!\ 注意,当你的状态中使用的用户数据名称与状态机使用的用户数据名称相同时,重新映射是不需要的。然而,remapping使连接非常明确,因此建议总是指定重新映射,甚至"remapping={'a':'a'}"。

状态间传递数据

我们可以使用重新映射机制将数据从状态FOO传递到状态BAR。为了实现这一点,我们需要在添加FOO时重新映射,并在添加BAR时重新映射:

  • FOO: remapping={'foo_output':'sm_user_data'}
  • BAR: remapping={'bar_input':'sm_user_data'}

在状态机和状态间传递数据

我们还可以使用映射机制将数据从状态BAR传递到包含BAR的状态机。如果'sm_output'是状态机的输出键:

  • BAR: remapping={'bar_output':'sm_output'}

或者,相反的,我们可以将数据从状态机传递到状态FOO。如果“sm_input”是状态机的输入键:

  • FOO: remapping={'foo_input':'sm_input'}

user_data.png

示例

你可以在executive_smach_tutorials包中找到一个完整的可运行的示例。

https://raw.githubusercontent.com/eacousineau/executive_smach_tutorials/hydro-devel/smach_tutorials/examples/user_data2.py

   1 #!/usr/bin/env python
   2 
   3 import roslib; roslib.load_manifest('smach_tutorials')
   4 import rospy
   5 import smach
   6 import smach_ros
   7 
   8 # define state Foo
   9 class Foo(smach.State):
  10     def __init__(self):
  11         smach.State.__init__(self, 
  12                              outcomes=['outcome1','outcome2'],
  13                              input_keys=['foo_counter_in'],
  14                              output_keys=['foo_counter_out'])
  15 
  16     def execute(self, userdata):
  17         rospy.loginfo('Executing state FOO')
  18         if userdata.foo_counter_in < 3:
  19             userdata.foo_counter_out = userdata.foo_counter_in + 1
  20             return 'outcome1'
  21         else:
  22             return 'outcome2'
  23 
  24 
  25 # define state Bar
  26 class Bar(smach.State):
  27     def __init__(self):
  28         smach.State.__init__(self, 
  29                              outcomes=['outcome1'],
  30                              input_keys=['bar_counter_in'])
  31         
  32     def execute(self, userdata):
  33         rospy.loginfo('Executing state BAR')
  34         rospy.loginfo('Counter = %f'%userdata.bar_counter_in)        
  35         return 'outcome1'
  36         
  37 
  38 
  39 
  40 
  41 def main():
  42     rospy.init_node('smach_example_state_machine')
  43 
  44     # Create a SMACH state machine
  45     sm = smach.StateMachine(outcomes=['outcome4'])
  46     sm.userdata.sm_counter = 0
  47 
  48     # Open the container
  49     with sm:
  50         # Add states to the container
  51         smach.StateMachine.add('FOO', Foo(), 
  52                                transitions={'outcome1':'BAR', 
  53                                             'outcome2':'outcome4'},
  54                                remapping={'foo_counter_in':'sm_counter', 
  55                                           'foo_counter_out':'sm_counter'})
  56         smach.StateMachine.add('BAR', Bar(), 
  57                                transitions={'outcome1':'FOO'},
  58                                remapping={'bar_counter_in':'sm_counter'})
  59 
  60 
  61     # Execute SMACH plan
  62     outcome = sm.execute()
  63 
  64 
  65 if __name__ == '__main__':
  66     main()

运行示例:

 $ roscd smach_tutorials
 $ ./examples/user_data2.py

Wiki: cn/smach/Tutorials/User Data (last edited 2018-03-10 03:25:25 by Playfish)