Module: Roby::Coordination::Models::ActionStateMachine

Includes:
Actions
Included in:
ActionStateMachine
Defined in:
lib/roby/coordination/models/action_state_machine.rb

Overview

Definition of model-level functionality for action state machines

In an action state machine, each state is represented by a single Roby task. At the model level, they get represented by a Task object, and more specifically very often by a TaskFromAction object. One important bit to understand is that a given Task object represents always the same state (i.e. task0 == task1 if task0 and task1 represent the same state). It is for instance possible that task0 != task1 even if task0 and task1 are issued by the same action.

Transitions are stored as (Task,Event,Task) triplets, specifying the origin state, the triggering event and the target state. Event#task is often the same than the origin state, but not always (see below)

Note that because some states are subclasses of TaskWithDependencies, it is possible that some Task objects are not states, only tasks that are dependencies of other states created with TaskWithDependencies#depends_on or dependencies of the state machine itself created with Roby::Coordination::Models::Actions#depends_on. The events of these task objects can be used in transitions

Action state machine models are usually created through an action interface with Interface#action_state_machine. The state machine model can then be retrieved using Actions::Models::Action#coordination_model.

Examples:

creating an action state machine model

class Main < Roby::Actions::Interface
  action_state_machine 'example_action' do
    move  = state move(speed: 0.1)
    stand = state move(speed: 0)
    # This monitor triggers each time the system moves more than
    # 0.1 meters
    d_monitor = task monitor_movement_threshold(d: 0.1) 
    # This monitor triggers after 20 seconds
    t_monitor = task monitor_time_threshold(t: 20) 
    # Make the distance monitor run in the move state
    move.depends_on d_monitor
    # Make the time monitor run in the stand state
    stand.depends_on t_monitor
    start move
    transition move, d_monitor.success_event, stand
    transition stand, t_monitor.success_event, move
  end
end

retrieving a state machine model from an action

Main.find_action_by_name('example_action').coordination_model

Instance Attribute Summary collapse

Attributes included from Actions

#action_interface

Attributes included from Base

#name, #root

Instance Method Summary collapse

Methods included from Actions

#dependency, #depends_on, #event_active_in_state?, #forward, #from, #from_state, #method_missing, #rebind, #required_tasks_for, #respond_to_missing?, #root_event?, #setup_submodel

Methods included from Base

#find_event, #find_task_by_name, #method_missing, #respond_to_missing?, #setup_submodel, #task, #task_model, #use_fault_response_table, #used_fault_response_table, #validate_event, #validate_or_create_task

Methods included from Arguments

#argument, #validate_arguments

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Roby::Coordination::Models::Actions

Instance Attribute Details

#starting_stateTask (readonly)

The starting state

Returns:



59
60
61
# File 'lib/roby/coordination/models/action_state_machine.rb', line 59

def starting_state
  @starting_state
end

Instance Method Details

#capture(state, event = nil, &block) ⇒ Object

Capture the value of an event context

The value returned by #capture is meant to be used as arguments to other states. This is a mechanism to pass information from one state to the next.

Examples:

determine the current heading and servo on it

measure_heading = state(self.measure_heading)
start(measure_heading)
current_heading = capture(measure_heading.success_event)
transition measure_heading.success_event, keep_heading(heading: current_heading)


111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/roby/coordination/models/action_state_machine.rb', line 111

def capture(state, event = nil, &block)
    if !event
        state, event = state.task, state
    end

    if !toplevel_state?(state)
        raise ArgumentError, "#{state} is not a toplevel state, a capture's state must be toplevel"
    elsif !event_active_in_state?(event, state)
        raise ArgumentError, "#{event} is not an event that is active in state #{state}"
    end

    filter =
        if block
            lambda(&block)
        else
            lambda { |event| event.context.first }
        end

    capture = Capture.new(filter)
    captures[capture] = [state, event]
    capture
end

#compute_unreachable_statesArray<Task>

Computes the set of states that are used in the transitions but are actually not reachable

Returns:



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/roby/coordination/models/action_state_machine.rb', line 150

def compute_unreachable_states
    queue = [starting_state].to_set
    transitions = self.each_transition.to_a.dup

    done_something = true
    while done_something
        done_something = false
        transitions.delete_if do |from, _, to|
            if queue.include?(from)
                queue << to
                done_something = true
            end
        end
    end

    transitions.map(&:first).to_set
end

#find_state_by_name(name) ⇒ Object

Returns the state for the given name, if found, nil otherwise

Returns:

  • Roby::Coordination::Models::TaskFromAction



137
# File 'lib/roby/coordination/models/action_state_machine.rb', line 137

def find_state_by_name(name) find_task_by_name("#{name}_state") end

#map_tasks(mapping) ⇒ Object

See Also:

  • Roby::Coordination::Models::ActionStateMachine.(Base(Base#map_task)


74
75
76
77
78
79
80
81
82
83
# File 'lib/roby/coordination/models/action_state_machine.rb', line 74

def map_tasks(mapping)
    super

    @starting_state = mapping[starting_state]
    @transitions = transitions.map do |state, event, new_state|
        [mapping[state],
         mapping[event.task].find_event(event.symbol),
         mapping[new_state]]
    end
end

#parse(&block) ⇒ Object

Overloaded from Actions to validate the state machine definition

Raises:

  • (UnreachableStateUsed)

    if some transitions are using events from states that cannot be reached from the start state



172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/roby/coordination/models/action_state_machine.rb', line 172

def parse(&block)
    super

    if !starting_state
        raise ArgumentError, "no starting state defined"
    end

    # Validate that all source states in transitions are reachable
    # from the start state
    unreachable = compute_unreachable_states
    if !unreachable.empty?
        raise UnreachableStateUsed.new(unreachable), "#{unreachable.size} states are used in transitions but are actually not reachable"
    end
end

#parse_namesObject



85
86
87
# File 'lib/roby/coordination/models/action_state_machine.rb', line 85

def parse_names
    super(Task => '_state', Capture => "")
end

#start(state) ⇒ Object

Declares the starting state



90
91
92
93
# File 'lib/roby/coordination/models/action_state_machine.rb', line 90

def start(state)
    parse_names
    @starting_state = validate_task(state)
end

#state(object, task_model = Roby::Task) ⇒ Object



95
96
97
# File 'lib/roby/coordination/models/action_state_machine.rb', line 95

def state(object, task_model = Roby::Task)
    task(object, task_model)
end

#to_sObject



216
217
218
# File 'lib/roby/coordination/models/action_state_machine.rb', line 216

def to_s
    "#{action_interface}.#{name}"
end

#toplevel_state?(state) ⇒ Boolean

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Raise if the given state is not a toplevel state

Returns:

  • (Boolean)


67
68
69
70
71
# File 'lib/roby/coordination/models/action_state_machine.rb', line 67

def toplevel_state?(state)
    root == state ||
        starting_state == state ||
        transitions.any? { |from_state, _, to_state| from_state == state || to_state == state }
end

#transition(state.my_event, new_state) ⇒ Object #transition(state, event, new_state) ⇒ Object

Declares a transition from a state to a new state, caused by an event

Overloads:

  • #transition(state.my_event, new_state) ⇒ Object

    declares that once the ‘my’ event on the given state is emitted, we should transition to new_state

  • #transition(state, event, new_state) ⇒ Object

    declares that, while in state ‘state’, transition to ‘new_state’ if the given event is emitted



64
# File 'lib/roby/coordination/models/action_state_machine.rb', line 64

inherited_attribute(:transition, :transitions) { Array.new }

#validate_task(object) ⇒ Object



139
140
141
142
143
144
# File 'lib/roby/coordination/models/action_state_machine.rb', line 139

def validate_task(object) if
    !object.kind_of?(Coordination::Models::Task)
        raise ArgumentError, "expected a state object, got #{object}. States need to be created from e.g. actions by calling #state before they can be used in the state machine"
    end
    object
end