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:



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

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)


118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/roby/coordination/models/action_state_machine.rb', line 118

def capture(state, event = nil, &block)
    unless 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
            ->(ev) { ev.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:



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/roby/coordination/models/action_state_machine.rb', line 163

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



144
145
146
# File 'lib/roby/coordination/models/action_state_machine.rb', line 144

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)


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

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



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/roby/coordination/models/action_state_machine.rb', line 185

def parse(&block)
    super

    unless 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
    unless unreachable.empty?
        raise UnreachableStateUsed.new(unreachable),
              "#{unreachable.size} states are used in transitions "\
              "but are actually not reachable"
    end
end

#parse_namesObject



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

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

#start(state) ⇒ Object

Declares the starting state



91
92
93
94
95
96
97
98
# File 'lib/roby/coordination/models/action_state_machine.rb', line 91

def start(state)
    parse_names
    if @starting_state
        raise ArgumentError, "this state machine already has a starting "\
            "state, use #depends_on to run more than one task at startup"
    end
    @starting_state = validate_task(state)
end

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



100
101
102
103
104
# File 'lib/roby/coordination/models/action_state_machine.rb', line 100

def state(object, task_model = Roby::Task, as: nil)
    state = task(object, task_model)
    state.name = as
    state
end

#to_sObject



235
236
237
# File 'lib/roby/coordination/models/action_state_machine.rb', line 235

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)


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

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



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

inherited_attribute(:transition, :transitions) { [] }

#validate_task(object) ⇒ Object



148
149
150
151
152
153
154
155
156
157
# File 'lib/roby/coordination/models/action_state_machine.rb', line 148

def validate_task(object)
    unless 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