Skip to content

state_machines

Matías Pavez edited this page May 8, 2017 · 1 revision

State Machines

Para la creación de máquinas de estado existen dos executives en ros: smach y teer, ambos tienen sus ventajas y desventajas. En bender se está utilizando smach.

Las máquinas de estados de smach deben ser programadas en python. . . Al menos nos ahorramos la parte de la compilación.


Documentación

  • smach wiki: Documentación básica sobre conceptos, estados y contenedores utilizados
  • smach wiki tutorials: Tutoriales para todos los niveles de smach. Ojo que son escritos por los diseñadores del package, sino que por usuarios, entonces no capturan todo lo que se puede hacer con smach, ni la mejor forma de hacerlo.
  • smach_ros pkg documentation: Documentación de objetos y funciones del package smach_ros.
  • smach pkg documentation: Documentación de objetos y funciones del package smach.

Instalación

  • sudo apt-get install ros-fuerte-executive-smach
  • sudo apt-get install ros-fuerte-executive-smach-visualization

Herramientas

Eclipse

La idea es usar algún IDE que provea soporte para python y ROS, con funciones como autocomplete y búsqueda de librerias. Simplififica mucho la vida.

Además de la instalación es necesario realizar la configuración para trabajar con python (explicada en el mismo link). Antes de poder ocupar cualquier package en eclipse o ante cambios en las dependencias, es necesario ejecutar make eclipse-project desde el directorio del package.

Smach Introspection Server

Es una interfaz provista por el package smach_viewer que permite monitorear las máquinas de estado de smach, estados y variables, por medio de una interfaz gráfica amigable. También permite setear 'a mano' el estado inicial de una máquina, lo que puede ser útil para debbugeo/programación.

Uso del introspection server: Viewing State Machines (*Basta agregar 4 líneas de código (: *)


Outcomes

Para la definición de outcomes, se ocuparán como norma, al menos los siguientes outcomes,

  • succeeded: Finalización ok
  • aborted: Aborto debido a fallo (por ejemplo: una excepción)
  • preempted: Aborto debido a orden superior (por ejemplo: stop button, fin de estado paralelo, etc.)

de ser necesario, se puede utilizar cualquier otro outcome que se desee. Tal norma es importante para simplificar la creación de máquinas de estados y poder implementar cosas como el stop button.

Ejemplo: Outcomes obligatiorios + outcomes extras

#!/usr/bin/env python
...
class Foo(smach.State):
    def __init__(self):
        smach.State.__init__(self, outcomes=['succeeded','aborted','preempted','my_state_outcome_1'])

    def execute(self, userdata):
        ...
        return 'succeeded'

if __name__ == '__main__':
    ...
    sm = smach.StateMachine(outcomes=['succeeded','aborted','preempted','my_sm_outcome_1'])
    ...

Preempt, request_preempt() & preempt_requested()

Es importante especificar como se manejan los casos de preempt en bender. Se ocupa el sistema de preempt para los casos de aborto del estado, debido a una orden superior (de mayor peso). Un preempt típicamente no sucede espontaneamente, por una condición interna del estado, sino que debe haber una señal que gatille esa acción, por ejemplo:

  • Contenedores de Concurrencia: señales de preempt enviada debido a término de estado hermano.
  • Stop button: El stop button es un caso particular de concurrencia, en donde se envían señales de preempt a todos los estados actuales.
  • Padre que reciba señal de preempt (típicamente) reenvía la señal a sus hijos en ejecución actuales.
  • otros . . .

Ya que un preempt es un outcome gatillado, smach provee un mecanismo de chequeo de tal señal: preempt_requested() y otro de notificación a sus hijos request_preempt(). En éste tutorial se explica el funcionamiento de tales funciones.

Ante un preempt es posible que la máquina quede ''pausada'' y posteriormente sea retomada en el mismo estado, por lo que junto con el chequeo de la señal, se debe hacer almacenamiento de variables importantes para retomar la ejecución posteriormente.

Si no se hace el chequeo del preempt, el estado trabajará ininterrumpidamente hasta su término, lo que puede ser peligroso en estados que involucren movimientos del robot. Por lo tanto, es necesario implementar tal mecanismo y ejecutar los procedimientos necesarios para detener el estado (por ejemplo, con señales de stop a actuadores).


macros

Las macros son StateMachines que implementan una funcionalidad en específico y existen para ser anidadas en una StateMachine de mayor nivel. El uso de macros permite reutilizar el código y ayuda con el control de bugs, pues todos los cambios necesarios se hacen en un mismo lugar.

__init__.py

Tal como está explicado acá y en la guia de python, es importante la creación de archivos __init__.py (típicamente vacios) para poder hacer includes con namespaces y evitar overloads de nombres de archivos .py entre cada package.

Para el caso de las macros, se recomienda que todos los imports de macros se realicen utilizando un namespace. Por ejemplo, se presenta la siguiente configuración utilizada en bender:

  • bender_macros/
    • src/
      • bender_macros/
        • __init__.py
        • nav/
          • __init__.py
          • GoToPose.py
          • GoToPlace.py
        • vision/
          • __init__.py
          • . . .
        • my_macro.py

con lo que un import de una macro de navegación quedaría como:

from bender_macros.nav import GoToPose.py

main() vs. getInstance()

Para simplificar el testeo y anidación de macros, se promueve el uso de las funciones main() y getInstance() como en el siguiente ejemplo:

  • Macro Example: /bender_macros/src/bender_macros/my_folder/MyMacro.py
#!/usr/bin/env python
...

def getInstance():
    
    # Machine creation
    sm = smach.StateMachine(outcomes = ['succeeded','aborted','preempted'])
    ...
    
    # Fill machine
    with sm:
        smach.StateMachine.add('...', ... )
        ...
    ...
    # return machine
    return sm
 
if __name__ == '__main__':

    rospy.init_node('my_node')

    # create machine
    sm = getInstance()

    # introspection server
    sis = smach_ros.IntrospectionServer('my_machine', sm, '/MY_MACHINE_SM')
    sis.start()
    outcome = sm.execute()
    sis.stop()

  • Macro Usage: /bender_behaviors/src/bender_behaviors/my_folder/MyBehavior.py
#!/usr/bin/env python

# Import macro!
from bender_macros.my_folder import MyMacro
...

def getInstance():
    ...
    
    # Fill machine
    with sm:

        # use macro
        smach.StateMachine.add('MY_MACRO', MyMacro.getInstance(),... )
        ...
    ...
 

main(): Permite testear la macro sin tener que correr todo el behavior. Además, acá se agrega el introspection server, para que sólo sea cargado una vez en todo el proceso. Cuando la máquina debe tener un input, es posible dar valores dummy:

# main
if __name__ == '__main__':

    ...
    sm = getInstance()

    # generate dummy userdata
    dummy_ud = smach.UserData()
    dummy_ud.my_sm_place = 'living_room'
    dummy_ud.my_sm_number = 3216.03584021

    ...
    # execute with dummy data
    outcome = sm.execute(dummy_ud)
 

getInstance(): Utilizado para hacer la anidación de macros. Retorna el objeto de tipo smach.state_machine. En caso de desear que un mismo archivo .py retorne más macros, queda a gusto del programador la implementación de funciones similares (i.e, myGetInstance1(), ...). Si se desea que la macro retornada sea creada según ciertos parámetros, siéntase libre de hacer modificaciones como getInstance(var1,var2,...)

State SETUP

Al trabajar en máquinas de estados de alto nivel (macros) es altamente probable que se requiera que cierto servicio esté activado o que se hayan cargado ciertas configuraciones para poder operar normalmente. Por lo anterior, se recomienda la creación de un(os) estado(s) SETUP encargado de realizar los procedimientos necesarios. En el fondo, se recomienda asumir que nadie realizo todas las preparaciones que necesitamos.


Más sobre: Monitor State

La información básica sobre el Monitor State puede ser encontrada en los tutoriales o en la documentación.

Se debe definir un callback para atender el tópico seleccionado: def monitor_cb(ud,msg). Tal callback debe retornar True ó False:

  • monitor_cb - return 'False': Termina la ejecución del monitor con outcome 'invalid'
  • monitor_cb - return 'True': Continua atendiendo mensajes ('valid'). Sin outcome.

Más sobre: Concurrence Container

out_cb

kjsañdkcjñsadklcj

child_term_cb

sadcascasdc

monitor_cb

ascsadcsadcasd