Rock

the Robot Construction Kit

Fault Response Tables

The result of this tutorial can be found in bundles/tutorials if you followed the instructions at the bottom of this page. While the tip of the master branch contains the accumulated result of all the tutorials, you can get the specific result of this one by checking out the fault_response_tables tag with the following git command: git checkout fault_response_tables

In the previous two tutorials, we first saw how to combine Syskit subsystem definitions into state machines to build more complex, temporally coordinated, behaviours. Then, we saw how one can monitor data streams to trigger the state machine transitions.

In both these tutorials, the event that was causing the behaviour switch was nominal: it was expected that, at a certain point in time, the movement behaviour would go over the fence and that we should be able to bring it back.

However, for quite a lot of events, it can become pretty cumbersome to think this way. Indeed, what if we wanted to make sure that any behaviour is fenced ? We could of course define a ‘fenced’ operator that would take any action and transform it into a ‘fenced’ action, but what if we also want to watch for battery, for localization quality, for water ingress when underwater, … It simply does not scale

For such usage, Syskit offers a different mechanism: the fault response table. Fault response tables define reactions to faults, with the possibility – at the end of the reaction – to restart whatever action was being performed.

This tutorial will guide you through the process of defining such faults using data monitors, defining the corresponding reaction, and use them in state machines and/or action interfaces.

Data Monitoring Tables

The first step, when you want to use data monitors in combination with fault response tables, is to create a data monitoring table. This data monitoring table is a separate list of named monitors that the fault response table will be able to use as fault sources. There are other types of fault generators that the fault response can use, but we won’t detail them in this tutorial (look instead in the documentation ).

Let’s create the fencing data monitoring table. Edit models/actions/fence_monitor.rb and add

module Tutorials
  # Define a list of data monitors
  class FenceMonitors < Syskit::Coordination::DataMonitoringTable
    # This table can be applied only on tasks of this type
    root Base::PositionSrv
    # It requires one argument
    argument :fence_size, :default => 5

    # This is almost a copy/paste of the previous monitor. The
    # differences are that arguments do not need to be provided
    # anymore (they are listed at the table level) and we
    # raise_exception instead of emit. The port is also
    # referred to directly as we attach to the PositionSrv directly
    monitor('fence_crossed', position_samples_port).
      trigger_on do |pose|
        pose.position.x.abs > fence_size ||
            pose.position.y.abs > fence_size
      end.
      raise_exception # We want this monitor to generate a fault
  end
end

Fault Response Tables

So, we now have a data monitoring table that is able to check that a given position is within the fence. We now need to define what to do when it happens.

This is done with a fault response table. Fault response tables are action interfaces, so that you can define the response actions.

Let’s start a new one in models/actions/fence.rb

require 'models/actions/fence_monitor'
module Tutorials
  # Define a fault response table to move back to origin when the
  # system crosses the virtual fence
  class Fence < Roby::Coordination::FaultResponseTable
    # Define the threshold for reached-origin
    argument :origin_reached_threshold, :default => 10
    # Define the fence size. Since it has a default value
    # in the FenceMonitors table, we don't have to pass it
    argument :fence_size, :default => 5
    # Import the definitions from the profile
    use_profile Tutorials::RocksWithTransformer
    # Declare our data monitors. The table's fence_size
    # argument is set from the fence_size argument of this
    # table
    use_data_monitoring_table FenceMonitors,
      :fence_size => fence_size

    # While not strictly necessary, it is a good policy to create
    # separate actions for the response steps. The fault response
    # handlers themselves should really be small
    describe('move back towards origin').
      required_arg('threshold', 'threshold in meters')
    action_script 'move_to_origin' do
      # Define our go-to-origin task in the same way than in
      # the action state machine. Simply replace #state with #task
      origin = task to_origin_def.use(rock1_dev)
      origin.monitor(
          'done',
          origin.rock_child.pose_samples_port,
          :threshold => threshold).
        trigger_on do |pose|
          pose.position.x.abs < threshold &&
            pose.position.y.abs < threshold
        end.
        emit origin.success_event

      # Start it and wait for it to end
      execute origin
      # We're done
      emit success_event
    end

    # Fault handler for faults generated by
    # the fence_crossed monitor.
    on_fault fence_crossed_monitor do
      # Go back to the origin (finally !)
      execute task(move_to_origin(:threshold => origin_reached_threshold))
      # And restart whatever we were doing
      try_again
    end
  end
end

Activating Tables

Now that our tables are defined, we need to activate them, i.e. tell Syskit that they need to be used in some context. This can be done globally (always active), but can also be done in the context of some actions. We’ll go for this solution here

Let’s create a new fenced random movement action using fault tables

describe('random motion within a delimited area').
  optional_arg('fence_size', 'size in meters of the fence around the origin', 3).
  optional_arg('threshold', 'size in meters we need to be from the origin to consider that we have reached it', 1)
action_state_machine 'fenced_random_move_with_faults' do
  use_fault_response_table Tutorials::Fence,
    :origin_reached_threshold => threshold,
    :fence_size => fence_size

  # And move randomly ... "forever"
  random = state random_def
  start random
end

and don’t forget to add the

require 'models/actions/fence'

at the top of the file

Summary

That’s the last tutorial … for now.

After all these, you should be able to start integrating pretty complex systems. Let’s make a summary of all you have seen here:

  • how to model composition of components, and then how to use dependency injection to adapt these generic compositions for a particular situation and/or system
  • how to organize the various models in the file structure
  • the place of devices
  • how to use Syskit to configure components that use Rock’s transformer, as well as make sure that the networks are consistent from a geometric frame point of view
  • and, finally, how to sequence behaviours in a temporal manner