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 data_monitors tag with
git checkout data_monitors
To build the state machine in the previous tutorial, we manually created data monitoring compositions, whose job was to listen to certain data streams in the executed network in order to find out when the state machine should transition.
If you look at the implementation of these two monitors, they are basically the same:
- read a stream
- emit an event when a condition is met
This tutorial will show you how to define such monitors in a much more compact and reusable way, and how these ‘new’ monitors differ from the manually created ones. We will then see in the next tutorial how lists of such monitors can be defined in order to design fault detection and response tables
Defining Data Monitors
Data monitors can be used in multiple places. We will see here how to integrate them in an action state machine, in order to understand what they do. The reference documentation lists all the ways you can define tables and activate / deactivate them.
In action state machines, data monitors can be attached to states. One simply calls #monitor on the state. Let’s define a new state machine using this feature:
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_monitors' do
# The 'move randomly' state
random = state random_def
# Monitor that verifies that we don't go outside the fence. Emit the
# cross_fenced event on the state machine's task when it happens
#
# The hash at the end passes options from the state machine
# to the monitor. They are available as local variables
# inside
random.monitor(
'fence', # monitor name
random.rock_child.pose_samples_port, # data sources
:fence_size => fence_size). # arguments
trigger_on do |pose|
pos = pose.position
pos.x.abs > fence_size || pos.y.abs > fence_size
end.
# emit this event when the predicate is true
emit crossed_fence_event
# The move-to-origin state
origin = state to_origin_def.use(rock1_dev)
# Monitor that waits for us to be close enough to the center
origin.monitor(
'reached_center',
origin.rock_child.pose_samples_port,
:threshold => threshold).
trigger_on do |pose|
pos = pose.position
pos.x.abs < threshold && pos.y.abs < threshold
end.
# emit this event when the predicate is true
emit origin.success_event
# We start by moving randomly
start random
# We transition each time the rock passes the fence
transition random, crossed_fence_event, origin
# And transition back when we reach the origin
transition origin, origin.success_event, random
end
The only bit missing is the definition of the crossed_fence event. For the origin monitor, we reuse the success event of the origin state, but for the fence we have to define a new one.
We therefore have to create a new Roby task model that is going to be representing the action state machine, and add this event to it. When one does not create such a model explictly, Roby creates a new one that is named after the state machine’s name, i.e. in this case FencedRandomMoveWithMonitors.
Let’s now define one explicitly and then tell Roby that it should be used as the root model for the state machine.
in models/tasks/fenced_random_move.rb
module Tutorials
class FencedRandomMove < Roby::Task
terminates # Always add this unless you know what you are doing
event :crossed_fence
end
end
and in the action file:
describe('random motion within a delimited area').
returns(Tutorials::FencedRandomMove).
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)
The resulting behaviour can be started and tested with
syskit run -rtut-transformer fenced_random_move_with_monitors!
Summary
The data monitors as used in the state machines are compact ways to make the state machines transition based on information in the data. They allow to easily transform data triggers into Roby events.
The next tutorial will show you how they can also be used in conjunction with fault response tables to define fault detection / fault response systems