roby 0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +29 -0
- data/History.txt +4 -0
- data/License-fr.txt +519 -0
- data/License.txt +515 -0
- data/Manifest.txt +245 -0
- data/NOTES +4 -0
- data/README.txt +163 -0
- data/Rakefile +161 -0
- data/TODO.txt +146 -0
- data/app/README.txt +24 -0
- data/app/Rakefile +8 -0
- data/app/config/ROBOT.rb +5 -0
- data/app/config/app.yml +91 -0
- data/app/config/init.rb +7 -0
- data/app/config/roby.yml +3 -0
- data/app/controllers/.gitattributes +0 -0
- data/app/controllers/ROBOT.rb +2 -0
- data/app/data/.gitattributes +0 -0
- data/app/planners/ROBOT/main.rb +6 -0
- data/app/planners/main.rb +5 -0
- data/app/scripts/distributed +3 -0
- data/app/scripts/generate/bookmarks +3 -0
- data/app/scripts/replay +3 -0
- data/app/scripts/results +3 -0
- data/app/scripts/run +3 -0
- data/app/scripts/server +3 -0
- data/app/scripts/shell +3 -0
- data/app/scripts/test +3 -0
- data/app/tasks/.gitattributes +0 -0
- data/app/tasks/ROBOT/.gitattributes +0 -0
- data/bin/roby +210 -0
- data/bin/roby-log +168 -0
- data/bin/roby-shell +25 -0
- data/doc/images/event_generalization.png +0 -0
- data/doc/images/exception_propagation_1.png +0 -0
- data/doc/images/exception_propagation_2.png +0 -0
- data/doc/images/exception_propagation_3.png +0 -0
- data/doc/images/exception_propagation_4.png +0 -0
- data/doc/images/exception_propagation_5.png +0 -0
- data/doc/images/replay_handler_error.png +0 -0
- data/doc/images/replay_handler_error_0.png +0 -0
- data/doc/images/replay_handler_error_1.png +0 -0
- data/doc/images/roby_cycle_overview.png +0 -0
- data/doc/images/roby_replay_02.png +0 -0
- data/doc/images/roby_replay_03.png +0 -0
- data/doc/images/roby_replay_04.png +0 -0
- data/doc/images/roby_replay_event_representation.png +0 -0
- data/doc/images/roby_replay_first_state.png +0 -0
- data/doc/images/roby_replay_relations.png +0 -0
- data/doc/images/roby_replay_startup.png +0 -0
- data/doc/images/task_event_generalization.png +0 -0
- data/doc/papers.rdoc +11 -0
- data/doc/styles/allison.css +314 -0
- data/doc/styles/allison.js +316 -0
- data/doc/styles/allison.rb +276 -0
- data/doc/styles/jamis.rb +593 -0
- data/doc/tutorials/01-GettingStarted.rdoc +86 -0
- data/doc/tutorials/02-GoForward.rdoc +220 -0
- data/doc/tutorials/03-PlannedPath.rdoc +268 -0
- data/doc/tutorials/04-EventPropagation.rdoc +236 -0
- data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
- data/doc/tutorials/06-Overview.rdoc +40 -0
- data/doc/videos.rdoc +69 -0
- data/ext/droby/dump.cc +175 -0
- data/ext/droby/extconf.rb +3 -0
- data/ext/graph/algorithm.cc +746 -0
- data/ext/graph/extconf.rb +7 -0
- data/ext/graph/graph.cc +529 -0
- data/ext/graph/graph.hh +183 -0
- data/ext/graph/iterator_sequence.hh +102 -0
- data/ext/graph/undirected_dfs.hh +226 -0
- data/ext/graph/undirected_graph.hh +421 -0
- data/lib/roby.rb +41 -0
- data/lib/roby/app.rb +870 -0
- data/lib/roby/app/rake.rb +56 -0
- data/lib/roby/app/run.rb +14 -0
- data/lib/roby/app/scripts/distributed.rb +13 -0
- data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
- data/lib/roby/app/scripts/replay.rb +31 -0
- data/lib/roby/app/scripts/results.rb +15 -0
- data/lib/roby/app/scripts/run.rb +26 -0
- data/lib/roby/app/scripts/server.rb +18 -0
- data/lib/roby/app/scripts/shell.rb +88 -0
- data/lib/roby/app/scripts/test.rb +40 -0
- data/lib/roby/basic_object.rb +151 -0
- data/lib/roby/config.rb +5 -0
- data/lib/roby/control.rb +747 -0
- data/lib/roby/decision_control.rb +17 -0
- data/lib/roby/distributed.rb +32 -0
- data/lib/roby/distributed/base.rb +440 -0
- data/lib/roby/distributed/communication.rb +871 -0
- data/lib/roby/distributed/connection_space.rb +592 -0
- data/lib/roby/distributed/distributed_object.rb +206 -0
- data/lib/roby/distributed/drb.rb +62 -0
- data/lib/roby/distributed/notifications.rb +539 -0
- data/lib/roby/distributed/peer.rb +550 -0
- data/lib/roby/distributed/protocol.rb +529 -0
- data/lib/roby/distributed/proxy.rb +343 -0
- data/lib/roby/distributed/subscription.rb +311 -0
- data/lib/roby/distributed/transaction.rb +498 -0
- data/lib/roby/event.rb +897 -0
- data/lib/roby/exceptions.rb +234 -0
- data/lib/roby/executives/simple.rb +30 -0
- data/lib/roby/graph.rb +166 -0
- data/lib/roby/interface.rb +390 -0
- data/lib/roby/log.rb +3 -0
- data/lib/roby/log/chronicle.rb +303 -0
- data/lib/roby/log/console.rb +72 -0
- data/lib/roby/log/data_stream.rb +197 -0
- data/lib/roby/log/dot.rb +279 -0
- data/lib/roby/log/event_stream.rb +151 -0
- data/lib/roby/log/file.rb +340 -0
- data/lib/roby/log/gui/basic_display.ui +83 -0
- data/lib/roby/log/gui/chronicle.rb +26 -0
- data/lib/roby/log/gui/chronicle_view.rb +40 -0
- data/lib/roby/log/gui/chronicle_view.ui +70 -0
- data/lib/roby/log/gui/data_displays.rb +172 -0
- data/lib/roby/log/gui/data_displays.ui +155 -0
- data/lib/roby/log/gui/notifications.rb +26 -0
- data/lib/roby/log/gui/relations.rb +248 -0
- data/lib/roby/log/gui/relations.ui +123 -0
- data/lib/roby/log/gui/relations_view.rb +185 -0
- data/lib/roby/log/gui/relations_view.ui +149 -0
- data/lib/roby/log/gui/replay.rb +327 -0
- data/lib/roby/log/gui/replay_controls.rb +200 -0
- data/lib/roby/log/gui/replay_controls.ui +259 -0
- data/lib/roby/log/gui/runtime.rb +130 -0
- data/lib/roby/log/hooks.rb +185 -0
- data/lib/roby/log/logger.rb +202 -0
- data/lib/roby/log/notifications.rb +244 -0
- data/lib/roby/log/plan_rebuilder.rb +470 -0
- data/lib/roby/log/relations.rb +1056 -0
- data/lib/roby/log/server.rb +550 -0
- data/lib/roby/log/sqlite.rb +47 -0
- data/lib/roby/log/timings.rb +164 -0
- data/lib/roby/plan-object.rb +247 -0
- data/lib/roby/plan.rb +762 -0
- data/lib/roby/planning.rb +13 -0
- data/lib/roby/planning/loops.rb +302 -0
- data/lib/roby/planning/model.rb +906 -0
- data/lib/roby/planning/task.rb +151 -0
- data/lib/roby/propagation.rb +562 -0
- data/lib/roby/query.rb +619 -0
- data/lib/roby/relations.rb +583 -0
- data/lib/roby/relations/conflicts.rb +70 -0
- data/lib/roby/relations/ensured.rb +20 -0
- data/lib/roby/relations/error_handling.rb +23 -0
- data/lib/roby/relations/events.rb +9 -0
- data/lib/roby/relations/executed_by.rb +193 -0
- data/lib/roby/relations/hierarchy.rb +239 -0
- data/lib/roby/relations/influence.rb +10 -0
- data/lib/roby/relations/planned_by.rb +63 -0
- data/lib/roby/robot.rb +7 -0
- data/lib/roby/standard_errors.rb +218 -0
- data/lib/roby/state.rb +5 -0
- data/lib/roby/state/events.rb +221 -0
- data/lib/roby/state/information.rb +55 -0
- data/lib/roby/state/pos.rb +110 -0
- data/lib/roby/state/shapes.rb +32 -0
- data/lib/roby/state/state.rb +353 -0
- data/lib/roby/support.rb +92 -0
- data/lib/roby/task-operations.rb +182 -0
- data/lib/roby/task.rb +1618 -0
- data/lib/roby/test/common.rb +399 -0
- data/lib/roby/test/distributed.rb +214 -0
- data/lib/roby/test/tasks/empty_task.rb +9 -0
- data/lib/roby/test/tasks/goto.rb +36 -0
- data/lib/roby/test/tasks/simple_task.rb +23 -0
- data/lib/roby/test/testcase.rb +519 -0
- data/lib/roby/test/tools.rb +160 -0
- data/lib/roby/thread_task.rb +87 -0
- data/lib/roby/transactions.rb +462 -0
- data/lib/roby/transactions/proxy.rb +292 -0
- data/lib/roby/transactions/updates.rb +139 -0
- data/plugins/fault_injection/History.txt +4 -0
- data/plugins/fault_injection/README.txt +37 -0
- data/plugins/fault_injection/Rakefile +18 -0
- data/plugins/fault_injection/TODO.txt +0 -0
- data/plugins/fault_injection/app.rb +52 -0
- data/plugins/fault_injection/fault_injection.rb +89 -0
- data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
- data/plugins/subsystems/README.txt +40 -0
- data/plugins/subsystems/Rakefile +18 -0
- data/plugins/subsystems/app.rb +171 -0
- data/plugins/subsystems/test/app/README +24 -0
- data/plugins/subsystems/test/app/Rakefile +8 -0
- data/plugins/subsystems/test/app/config/app.yml +71 -0
- data/plugins/subsystems/test/app/config/init.rb +9 -0
- data/plugins/subsystems/test/app/config/roby.yml +3 -0
- data/plugins/subsystems/test/app/planners/main.rb +20 -0
- data/plugins/subsystems/test/app/scripts/distributed +3 -0
- data/plugins/subsystems/test/app/scripts/replay +3 -0
- data/plugins/subsystems/test/app/scripts/results +3 -0
- data/plugins/subsystems/test/app/scripts/run +3 -0
- data/plugins/subsystems/test/app/scripts/server +3 -0
- data/plugins/subsystems/test/app/scripts/shell +3 -0
- data/plugins/subsystems/test/app/scripts/test +3 -0
- data/plugins/subsystems/test/app/tasks/services.rb +15 -0
- data/plugins/subsystems/test/test_subsystems.rb +71 -0
- data/test/distributed/test_communication.rb +178 -0
- data/test/distributed/test_connection.rb +282 -0
- data/test/distributed/test_execution.rb +373 -0
- data/test/distributed/test_mixed_plan.rb +341 -0
- data/test/distributed/test_plan_notifications.rb +238 -0
- data/test/distributed/test_protocol.rb +516 -0
- data/test/distributed/test_query.rb +102 -0
- data/test/distributed/test_remote_plan.rb +491 -0
- data/test/distributed/test_transaction.rb +463 -0
- data/test/mockups/tasks.rb +27 -0
- data/test/planning/test_loops.rb +380 -0
- data/test/planning/test_model.rb +427 -0
- data/test/planning/test_task.rb +106 -0
- data/test/relations/test_conflicts.rb +42 -0
- data/test/relations/test_ensured.rb +38 -0
- data/test/relations/test_executed_by.rb +149 -0
- data/test/relations/test_hierarchy.rb +158 -0
- data/test/relations/test_planned_by.rb +54 -0
- data/test/suite_core.rb +24 -0
- data/test/suite_distributed.rb +9 -0
- data/test/suite_planning.rb +3 -0
- data/test/suite_relations.rb +8 -0
- data/test/test_bgl.rb +508 -0
- data/test/test_control.rb +399 -0
- data/test/test_event.rb +894 -0
- data/test/test_exceptions.rb +592 -0
- data/test/test_interface.rb +37 -0
- data/test/test_log.rb +114 -0
- data/test/test_log_server.rb +132 -0
- data/test/test_plan.rb +584 -0
- data/test/test_propagation.rb +210 -0
- data/test/test_query.rb +266 -0
- data/test/test_relations.rb +180 -0
- data/test/test_state.rb +414 -0
- data/test/test_support.rb +16 -0
- data/test/test_task.rb +938 -0
- data/test/test_testcase.rb +122 -0
- data/test/test_thread_task.rb +73 -0
- data/test/test_transactions.rb +569 -0
- data/test/test_transactions_proxy.rb +198 -0
- metadata +570 -0
@@ -0,0 +1,236 @@
|
|
1
|
+
{Previous tutorial}[link:files/doc/tutorials/03-PlannedPath_rdoc.html]
|
2
|
+
{Next tutorial}[link:files/doc/tutorials/05-ErrorHandling_rdoc.html]
|
3
|
+
= Understanding event propagation
|
4
|
+
|
5
|
+
This tutorial will show you how to trace plan execution, and understand how the
|
6
|
+
plans you build are actually executed by the system. It will not go in great
|
7
|
+
details, but it should help you understand more advanced uses of Roby. For that,
|
8
|
+
we will base ourselves on the execution trace of the controller we built in the
|
9
|
+
previous tutorial.
|
10
|
+
|
11
|
+
Roby plan execution is based on a fixed-duration execution cycle which includes
|
12
|
+
three steps:
|
13
|
+
|
14
|
+
link:../../images/roby_cycle_overview.png
|
15
|
+
|
16
|
+
This tutorial focusses on the first process. It will show how to use the log and
|
17
|
+
display tools that are shipped with Roby, and how event propagation works. The
|
18
|
+
last section will also give you a glimpse of the purpose of the garbage
|
19
|
+
collection algorithm.
|
20
|
+
|
21
|
+
|
22
|
+
== Getting a log file
|
23
|
+
|
24
|
+
We will first get a log file of the plan execution of the PathPlan robot
|
25
|
+
(previous tutorial). Plan execution logs are expensive from a CPU point of
|
26
|
+
view, so they are disabled by default. Enable them back by editing
|
27
|
+
<tt>config/app.yml</tt> and uncomment <tt>events: true</tt> around line 23.
|
28
|
+
|
29
|
+
Now, run again the controller
|
30
|
+
|
31
|
+
# scripts/run PathPlan
|
32
|
+
|
33
|
+
and in the shell, do
|
34
|
+
|
35
|
+
>> move_to! :x => 10, :y => 10
|
36
|
+
=> MoveTo{goal => Vector3D(x=10.000000,y=10.000000,z=0.000000)}:0x4840c8d8[]
|
37
|
+
>>
|
38
|
+
!task MoveTo{goal => Vector3D(x=10.000000,y=10.000000,z=0.000000)}:0x4840c8d8[] finished successfully
|
39
|
+
|
40
|
+
Now, let's save the log files for further analysis (otherwise, one could
|
41
|
+
destroy them by restarting the controller).
|
42
|
+
|
43
|
+
# scripts/result tut04
|
44
|
+
moving /home/doudou/dev/roby-tutorials/log to /home/doudou/dev/roby-tutorials/results/20080502-tut04
|
45
|
+
|
46
|
+
<tt>scripts/result</tt> copies all files in <tt>log/</tt> into a subdirectory
|
47
|
+
of the result dir (by default APP_DIR/results, but can be changed in
|
48
|
+
<tt>config/app.yml</tt>). The target directory name is generated following a pattern of
|
49
|
+
<tt><current date>-<name provided on command line></tt>.
|
50
|
+
|
51
|
+
== Displaying the log file
|
52
|
+
Now, let's go in the directory where the results are (see <tt>scripts/results</tt> output). If you look into it,
|
53
|
+
two PathPlan files are present: <tt>PathPlan-events.log</tt> and
|
54
|
+
<tt>PathPlan-index.log</tt>. The first one includes a trace of everything that
|
55
|
+
happens in the Roby controller which has been traced. The second one can
|
56
|
+
actually be generated from data in the first one. It is simply used to speed up
|
57
|
+
operations.
|
58
|
+
|
59
|
+
The data in the event log can be used to display the plan operations in a GUI.
|
60
|
+
For that, you need to have installed {Ruby/Qt4}[http://korundum.rubyforge.org], as
|
61
|
+
the GUI is written using Qt and Ruby.
|
62
|
+
|
63
|
+
To start it, simply do the following in the directory of the log files:
|
64
|
+
|
65
|
+
# roby-log replay PathPlan
|
66
|
+
|
67
|
+
The following window should appear:
|
68
|
+
|
69
|
+
link:../../images/roby_replay_startup.png
|
70
|
+
|
71
|
+
This window is separated in three:
|
72
|
+
* the toplevel part is the list of data sources defined for this project. It is
|
73
|
+
for instance possible to have a synchronized display of the logs of two
|
74
|
+
different Roby controllers -- for multi-robot setup.
|
75
|
+
* the second part is the set of displays defined. More about that later.
|
76
|
+
* the third part is the replay controls: play, fast forward, position, ...
|
77
|
+
|
78
|
+
Note that you can either print the display, or export it as a SVG file for
|
79
|
+
further editing. See the +View+ menu.
|
80
|
+
|
81
|
+
Right now, we will be looking at the plan structure and execution trace. The
|
82
|
+
+Relations+ display is designed for that. Let's add one by clicking on the
|
83
|
+
+Add+ button just next to the display type combo. The configuration options
|
84
|
+
appear (including the data source associated with the display), and a new
|
85
|
+
window:
|
86
|
+
|
87
|
+
link:../../images/roby_replay_relations.png
|
88
|
+
|
89
|
+
This display will show two things: the task structure (i.e. how tasks are
|
90
|
+
related to each other) and the event propagation (i.e. how events call and/or
|
91
|
+
emit each other). The set of task relations to display has to be selected on
|
92
|
+
the configuration part of the relation display, including the colors for each
|
93
|
+
displayed relation. For our purposes, we only need the +Hierarchy+ (it is the
|
94
|
+
actual name of the +realized_by+ relation) and the +PlannedBy+ relations.
|
95
|
+
|
96
|
+
<b>Very important note</b> your own display may not look exactly like the ones
|
97
|
+
displayed here. Some of the features showed here (like threaded planning) are
|
98
|
+
asynchronous and as such the exact displays depend on the execution timing. Note
|
99
|
+
that, even though it is the case, the robot _behaviour_ remains unchanged.
|
100
|
+
|
101
|
+
== Startup of the <tt>move_to!</tt> action
|
102
|
+
|
103
|
+
Let's get to the first task-related events. Click on the 'Step' button until
|
104
|
+
something appears on the display. It should look like the next image:
|
105
|
+
|
106
|
+
link:../../images/roby_replay_first_state.png
|
107
|
+
|
108
|
+
The displays shows two plans (black boxes). The left one is the plan as it is
|
109
|
+
being executed. The right one is called a _transaction_ and allows to build a
|
110
|
+
new plan without interfering with the execution. Transactions are presented in
|
111
|
+
the sixth tutorial. The task description includes the task model and the task
|
112
|
+
owners (which is only useful in multi-robot setup). The <tt>Task labels</tt>
|
113
|
+
menu allows to customize that.
|
114
|
+
|
115
|
+
The left part is a representation of the plan built when the <tt>move_to!</tt>
|
116
|
+
command is entered in the shell. It consists of a generic task (Roby::Task)
|
117
|
+
which is +planned_by+ a Roby::PlanningTask. This is how Roby handles action
|
118
|
+
requests from the shell: (i) it searches a planner defined for that robot with
|
119
|
+
the specific action and (ii) generates the plan representing the planning
|
120
|
+
process.
|
121
|
+
|
122
|
+
Once that initial plan has been built, the Roby::PlanningTask task has been
|
123
|
+
started. The various cases of event propagation are represented in different
|
124
|
+
ways, based on wether or not the event is controlable or contingent, or if it is
|
125
|
+
called and/or emitted.
|
126
|
+
|
127
|
+
link:../../images/roby_replay_event_representation.png
|
128
|
+
|
129
|
+
A note about propagation representation: it would be useless to represent all
|
130
|
+
the event propagation from the beginning of the execution to the current point.
|
131
|
+
The display therefore represents only the propagations that have taken place
|
132
|
+
<i>since the last display point</i>. It means that, if you go forward 10
|
133
|
+
seconds, it will display 10 seconds worth of propagation. In our case, we
|
134
|
+
displayed only the first execution cycle and we see that, in this cycle, the
|
135
|
+
planning task +start+ event has been called and emitted.
|
136
|
+
|
137
|
+
== The MoveTo plan
|
138
|
+
|
139
|
+
Advance again using 'Step' until the display looks like this:
|
140
|
+
|
141
|
+
link:../../images/roby_replay_02.png
|
142
|
+
|
143
|
+
The MoveTo action has been planned and the executed plan is modified to reflect
|
144
|
+
that. The MoveTo action itself is then started, and that is propagated to the
|
145
|
+
ComputePath 'start' event through the signalling relation our plan method has
|
146
|
+
defined betweem both.
|
147
|
+
|
148
|
+
Next execution step gives us the following:
|
149
|
+
|
150
|
+
link:../../images/roby_replay_03.png
|
151
|
+
|
152
|
+
PlannedPath emitted its +success+ event. We see here that the emission of the
|
153
|
+
+success+ event of that task does not mean 'the plan modification has just took
|
154
|
+
place' but instead that 'it has taken place some time earlier'.
|
155
|
+
|
156
|
+
The ComputePath task has also finished generating the path. That has two
|
157
|
+
consequences: the internal data of MoveTo is changed (hence the 'internal_data'
|
158
|
+
emission) and TrackPath is started. Here, the dotted lines between the events
|
159
|
+
represent a forwarding relation between the two events, while the plain lines
|
160
|
+
represent signal relations.
|
161
|
+
|
162
|
+
If we go back to the code, we see that nowhere a forwarding relation has been
|
163
|
+
set up between the +success+ event of ComputePath and the +internal_data+ of
|
164
|
+
MoveTo. +internal_data+ is actually automatically emitted by Roby::Task#data=,
|
165
|
+
which is called in ComputePath's event handler of +success+ we installed.
|
166
|
+
|
167
|
+
on :success do |ev|
|
168
|
+
parents.find { true }.data = result
|
169
|
+
end
|
170
|
+
|
171
|
+
The interpretation of Roby in that the causality chain is so that the emission
|
172
|
+
of +success+ is the cause of the emission of +success+, and as such counts for
|
173
|
+
a forward. The same would have happened if the event handler would have called
|
174
|
+
an event command.
|
175
|
+
|
176
|
+
Finally, light grey here represents tasks that have finished with the +success+
|
177
|
+
event. Tasks whose +failed+ event has been emitted are represented in red.
|
178
|
+
|
179
|
+
== To finish: the garbage collection process
|
180
|
+
|
181
|
+
Now, uncheck the box <tt>View/Hide finalized</tt> in the menu. If you go a few
|
182
|
+
cycles further, you should then get the following:
|
183
|
+
|
184
|
+
link:../../images/roby_replay_04.png
|
185
|
+
|
186
|
+
Here, TrackPath has finished its execution with success and MoveTo is therefore
|
187
|
+
finished as well -- per the forwarding relation between those two events. Note
|
188
|
+
that the tasks are now in a dark grey instead than a light one.
|
189
|
+
|
190
|
+
The mission of the robot, MoveTo, is therefore finished. From the plan
|
191
|
+
management point of view, it makes keeping a reference to it useless. In the
|
192
|
+
same way, the tasks that were in the plan for the purpose of fullfilling that
|
193
|
+
mission are rendered useless as well and can also be removed. The process which
|
194
|
+
removes those tasks is called the <i>garbage collection process</i> and runs at
|
195
|
+
the end of the execution cycle (Roby::Plan#garbage_collect).
|
196
|
+
|
197
|
+
The general idea is to kill and remove from the plan the tasks that are not
|
198
|
+
useful for the achievement of the robot's missions. The "important" tasks for
|
199
|
+
the robot are defined by two sets:
|
200
|
+
1. a set of missions (Roby::Plan#missions)
|
201
|
+
2. a set of "permanent" tasks that should not be considered by Roby's GC
|
202
|
+
mechanism (Roby::Plan#keepalive).
|
203
|
+
|
204
|
+
Then, the task relations are used to determine what are the tasks, in the plan,
|
205
|
+
which are actually useful for those "important" tasks
|
206
|
+
(Roby::Plan#useful_tasks). This is based on the convention that if a
|
207
|
+
<tt>a=>b</tt> relation exists, then +b+ is useful for +a+.
|
208
|
+
|
209
|
+
The remaining tasks, i.e. the tasks that are not useful, are killed (if needed)
|
210
|
+
and removed from the plan. When it is done, the task is said to be finalized
|
211
|
+
(hence the need to not hide this kind of task in the View menu).
|
212
|
+
|
213
|
+
== Some insight in the internal event propagation process
|
214
|
+
|
215
|
+
The whole propagation process tries to make life as easy as possible in cases
|
216
|
+
where multiple events are propagated towards the same source. To make that happen,
|
217
|
+
event propagation is done in a bunch of gathering/propagation pairs:
|
218
|
+
* the most suitable propagation step (forward and/or signal) is selected
|
219
|
+
* This step is performed. The associated user code is called (i.e. event
|
220
|
+
command in case of a signal and event handler in case of a forward). Any call
|
221
|
+
to command and/or emission which is performed in this user code is delayed in
|
222
|
+
the set of pending propagation steps, to be considered for propagation in the
|
223
|
+
global propagation loop.
|
224
|
+
|
225
|
+
See my PhD thesis for more details (links are available in README.txt).
|
226
|
+
|
227
|
+
= Next tutorial
|
228
|
+
|
229
|
+
Now that we have seen what happens in the nominal case, the {next
|
230
|
+
tutorial}[link:files/doc/tutorials/05-ErrorHandling_rdoc.html] will introduce
|
231
|
+
the second part of the execution cycle: the error representation and handling,
|
232
|
+
which is -- admittedly -- the most important part of this kind of plan-based
|
233
|
+
system.
|
234
|
+
---
|
235
|
+
vim: tw=80 et
|
236
|
+
|
@@ -0,0 +1,319 @@
|
|
1
|
+
{Previous tutorial}[link:files/doc/tutorials/04-EventPropagation_rdoc.html]
|
2
|
+
{Next tutorial}[link:files/doc/tutorials/06-Overview_rdoc.html]
|
3
|
+
= Representing and handling errors
|
4
|
+
|
5
|
+
One thing about robotics, and in particular plan execution, is that Murphy's
|
6
|
+
rule applies quite well. This is due to a few things. Among them, the first is
|
7
|
+
that the models planning uses (and therefore the plans it builds) are (i) too
|
8
|
+
simple to completely reflect the reality, (ii) badly parametrized and (iii)
|
9
|
+
represent dynamic agents, which can themselves be able to take decisions. So, in
|
10
|
+
essence, the rule of thumb is that a plan will fail during its execution.
|
11
|
+
|
12
|
+
Because Roby represents and executes all the activities of a given system, the
|
13
|
+
representation of errors becomes a very powerful thing: it is quite easy, when
|
14
|
+
an error appears somewhere to actually determine what are its consequences.
|
15
|
+
|
16
|
+
What this tutorial will show is:
|
17
|
+
* how parts of the error conditions are encoded in the task structure.
|
18
|
+
* how exceptions that come from the code itself (like NoMethodError ...) are
|
19
|
+
handled.
|
20
|
+
|
21
|
+
== Where do errors come from ?
|
22
|
+
=== Task structure as a constraint representation
|
23
|
+
|
24
|
+
Some (not all) task relations also define a set of constraints on the plan
|
25
|
+
execution. For instance, the +realized_by+ relation defines a set of
|
26
|
+
_desirable_ and a set of _forbidden_ events (the +success+ and +failure+
|
27
|
+
options of TaskStructure#realized_by). If none of the desirable events are
|
28
|
+
reachable (i.e. none will be emitted +ever+, see
|
29
|
+
Roby::EventGenerator#unreachable?), or if one of the forbidden events is
|
30
|
+
emitted, a ChildFailedError error is generated.
|
31
|
+
|
32
|
+
For instance, if we look at the first tutorial, we had an error provoked because
|
33
|
+
the +failed+ event of ComputePath has been emitted, while ComputePath was a
|
34
|
+
child of MoveTo:
|
35
|
+
$ scripts/shell
|
36
|
+
>> move_to! :x => 10, :y => 10
|
37
|
+
=> MoveTo{goal => Vector3D(x=10.000000,y=10.000000,z=0.000000)}:0x48350370[]
|
38
|
+
>>
|
39
|
+
!Roby::ChildFailedError
|
40
|
+
!at [336040:01:45.419/186] in the failed event of ComputePath:0x483502e0
|
41
|
+
!block not supplied (ArgumentError)
|
42
|
+
! /home/doudou/dev/roby/lib/roby/thread_task.rb:51:in `instance_eval',
|
43
|
+
! /home/doudou/dev/roby/lib/roby/thread_task.rb:61:in `value',
|
44
|
+
! /home/doudou/dev/roby/lib/roby/thread_task.rb:61:in the polling handler,
|
45
|
+
! /home/doudou/system/powerpc-linux/ruby-1.8.6/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require',
|
46
|
+
! /home/doudou/system/powerpc-linux/ruby-1.8.6/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require',
|
47
|
+
! scripts/run:3
|
48
|
+
!
|
49
|
+
!The failed relation is
|
50
|
+
! MoveTo:0x48350370
|
51
|
+
! owners: Roby::Distributed
|
52
|
+
! arguments: {:goal=>Vector3D(x=10.000000,y=10.000000,z=0.000000)}
|
53
|
+
! realized_by ComputePath:0x483502e0
|
54
|
+
! owners: Roby::Distributed
|
55
|
+
! arguments: {:max_speed=>1.0,
|
56
|
+
! :goal=>Vector3D(x=10.000000,y=10.000000,z=0.000000)}
|
57
|
+
!The following tasks have been killed:
|
58
|
+
! ComputePath:0x483502e0
|
59
|
+
! MoveTo:0x48350370
|
60
|
+
|
61
|
+
In the case of the PlannedBy relation that we saw in the previous tutorial, the
|
62
|
+
error is that no plan can be found. A PlanningFailedError is generated in that
|
63
|
+
case.
|
64
|
+
|
65
|
+
Those two types of error have in common that it is possible to associate the
|
66
|
+
error with one of the plan objects (event or task). They are <i>localized
|
67
|
+
errors</i> and are subclasses of Roby::LocalizedError. The nice aspect of that
|
68
|
+
is that it is possible to assess what is their impact on the plan execution. It
|
69
|
+
is therefore possible to handle the error at the plan level and continue
|
70
|
+
executing what can be executed.
|
71
|
+
|
72
|
+
=== Errors generated by the code itself
|
73
|
+
|
74
|
+
In that case, the problem is not to have plan-specific errors anymore. It is to
|
75
|
+
handle errors that appear because of bugs in the code itself. Roby is
|
76
|
+
implemented in a way where the code is split into two parts:
|
77
|
+
|
78
|
+
The <i>framework code</i> is the really problematic one. It means that there is
|
79
|
+
really a bug in the execution engine itself. In that case, Roby tries to hang up
|
80
|
+
as cleanly as possible by killing all tasks that are being executed.
|
81
|
+
|
82
|
+
The <i>user code</i> is the part of the code which is tied to events and tasks:
|
83
|
+
event commands, event handlers, polling blocks. For those, it is actually
|
84
|
+
possible to generate a Roby::LocalizedError as in the previous case and to
|
85
|
+
handle the error at the plan level. Failed command tasks generate a
|
86
|
+
Roby::CommandFailedError, failed event handlers a Roby::EventHandlerError.
|
87
|
+
Polling blocks actually emit +failed+ with the poller exception as context (see
|
88
|
+
the above error message).
|
89
|
+
|
90
|
+
Let's try. Add the following event handler in the definition of MoveTo
|
91
|
+
(<tt>tasks/move_to.rb</tt>).
|
92
|
+
|
93
|
+
on :start do
|
94
|
+
raise
|
95
|
+
end
|
96
|
+
|
97
|
+
Start (or restart) the controller and launch a <tt>move_to!</tt> action in the
|
98
|
+
shell. The following should happen:
|
99
|
+
|
100
|
+
!Roby::EventHandlerError: user code raised an exception at [336641:28:04.607/23] in the start event of MoveTo:0x2b4330b4fae8
|
101
|
+
!
|
102
|
+
!
|
103
|
+
! (RuntimeError)
|
104
|
+
!./tasks/move_to.rb:10:in event handler for 'start',
|
105
|
+
! /home/joyeux/system/rubygems/lib/rubygems/custom_require.rb:27:in `gem_original_require',
|
106
|
+
! /home/joyeux/system/rubygems/lib/rubygems/custom_require.rb:27:in `require',
|
107
|
+
! scripts/run:3
|
108
|
+
!The following tasks have been killed:
|
109
|
+
! MoveTo:0x2b4330b4fae8
|
110
|
+
|
111
|
+
An equivalent thing would happen with a task-level event handler (i.e. one
|
112
|
+
defined on the task object instead of the task model). Remove the model-level
|
113
|
+
handler we just added and try adding the following to the planning method in
|
114
|
+
<tt>planners/PathPlan/main.rb</tt>. Execute, and see the result !
|
115
|
+
|
116
|
+
move.on :start do
|
117
|
+
raise
|
118
|
+
end
|
119
|
+
|
120
|
+
Now, what happens during execution: how Roby does react to that error ? What we
|
121
|
+
can see in the relation display is the following two successive steps (don't
|
122
|
+
forget to uncheck <tt>View/Hide finalized</tt>).
|
123
|
+
|
124
|
+
link:../../images/replay_handler_error_0.png
|
125
|
+
link:../../images/replay_handler_error_1.png
|
126
|
+
|
127
|
+
From Roby point of view, the event has already happened when the event handlers
|
128
|
+
are called. Therefore, the event propagation should go on (the temporal
|
129
|
+
structure is well-formed). However, an error occured and has not been handled,
|
130
|
+
so the MoveTo task cannot be kept running. That is the job of the garbage
|
131
|
+
collection process, which queues the 'stop' event, to be executed during the
|
132
|
+
next cycle. The MoveTo task is therefore stopped at the next cycle, and the
|
133
|
+
tasks that are now useless are also stopped.
|
134
|
+
|
135
|
+
For event commands, all depends on where the exception actually appears. If
|
136
|
+
'emit' has already been called, then the event will be emitted and propagated.
|
137
|
+
Otherwise, it counts as a cancelling of the event command.
|
138
|
+
|
139
|
+
== Handling errors
|
140
|
+
|
141
|
+
Now that we have seen how errors are detected and represented, we can tackle
|
142
|
+
the problem of handling them. There are three ways to do that in Roby, that we
|
143
|
+
will present right away.
|
144
|
+
|
145
|
+
As we saw in the fifth tutorial, the forwarding relation represents an event
|
146
|
+
generalization (the target represents a superset of the situations represented
|
147
|
+
by the source), allowing to represent <i>fault modes</i>, i.e. specific fault
|
148
|
+
situations that are classified through the forwarding relation (see figure
|
149
|
+
below). The target of the forwarding relations being, of course, the +failed+
|
150
|
+
event. This is used during error handling to generalize the event handlers: an
|
151
|
+
event handler which applies to a given erroneous situation also applies to all
|
152
|
+
the situations that are subsets of it.
|
153
|
+
|
154
|
+
link:../../images/task_event_generalization.png
|
155
|
+
|
156
|
+
*Example*: the +blocked+ event is a particular fault mode during the movement.
|
157
|
+
More complex forwarding network would allow to represent the relationships
|
158
|
+
between the different type of faults recognized by the system.
|
159
|
+
|
160
|
+
=== Repairing during events propagation
|
161
|
+
|
162
|
+
If a child fails, for instance because of a spurious problem, it would have been
|
163
|
+
possible to actually restart the failing child directly in the event handler of
|
164
|
+
'failed' and replace the failed task through this new one. This is as simple as:
|
165
|
+
|
166
|
+
on(:failed) do
|
167
|
+
plan.respawn(self)
|
168
|
+
end
|
169
|
+
|
170
|
+
Let's try it. Add the following to the definition of +TrackPath+ to simulate an
|
171
|
+
error:
|
172
|
+
attr_accessor :should_pass
|
173
|
+
event :start do
|
174
|
+
if !should_pass
|
175
|
+
forward :start, self, :failed, :delay => 0.2
|
176
|
+
end
|
177
|
+
emit :start
|
178
|
+
end
|
179
|
+
|
180
|
+
What the event command does is schedule a delayed forwarding of 0.2 seconds if
|
181
|
+
#should_pass is false (the default). +failed+ will therefore be emitted 0.2
|
182
|
+
seconds after the path tracking has been started, if +should_pass+ is false.
|
183
|
+
|
184
|
+
Then, the error handler itself:
|
185
|
+
|
186
|
+
on :failed do
|
187
|
+
if !should_pass
|
188
|
+
Robot.info "respawning ..."
|
189
|
+
new_task = plan.respawn(self)
|
190
|
+
new_task.should_pass = true
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
This handler replaces the failed TrackPath with a copy of itself and schedules
|
195
|
+
it for starting. Then, we set @should_pass to true to avoid having further
|
196
|
+
errors. Look at the relation display to see how it worked. Note that doing such
|
197
|
+
a thing on the +failed+ event is a bad idea, as +failed+ is emitted when the
|
198
|
+
task gets interrupted.
|
199
|
+
|
200
|
+
The next figure is an example of how it works on a real robot. As a workaround
|
201
|
+
of a spurious error in the +TrackSpeedStart+ task, known to be harmless, an
|
202
|
+
event handler is defined on this task model, which restarts the task online.
|
203
|
+
|
204
|
+
link:../../images/repair_event_propagation.png
|
205
|
+
|
206
|
+
=== Asynchronous repairs
|
207
|
+
|
208
|
+
Sometime, repairing the plan needs a few actions. While those actions are
|
209
|
+
performed, we do not actually know yet if the plan *can* be repaired or not,
|
210
|
+
only that necessary measures are taken to assess it and/or repair it.
|
211
|
+
|
212
|
+
In Roby's plans, asynchronous repairs are represented as <i>plan repairs</i>
|
213
|
+
(Roby::Plan#add_repair). Plan repairs are tasks which are associated with a
|
214
|
+
task's event. While the plan repair is running, errors whose failure point is
|
215
|
+
the associated event are simply ignored by the system. Once the task finished,
|
216
|
+
normal error detection and handling resumes.
|
217
|
+
|
218
|
+
To automate the process of installing plan repairs, a ErrorHandling relation
|
219
|
+
exists, which defines the set of possible plan repairs for a given task and
|
220
|
+
event. Roby::TaskEventGenerator#handle_with allows to easily add a new plan
|
221
|
+
repair by associated the receiving task event with the (pending) task.
|
222
|
+
|
223
|
+
Here is a simple example: http://roby.rubyforge.org/videos/rflex_repaired.avi.
|
224
|
+
In this video, the microcontroller which drives the robot's motors can give us
|
225
|
+
spurious <tt>BRAKES_ON</tt> messages. Our problem is that the Roby controller
|
226
|
+
must determine if the message is spurious, or if brakes are actually set by the
|
227
|
+
means of an emergency switch for instance. To do that, an error handling is set
|
228
|
+
up, which wait for a few seconds and tests the <tt>BRAKES_ON</tt> state of the
|
229
|
+
robot. If the brakes are reported as off, then the robot can start moving again.
|
230
|
+
Otherwise, the error was a rightful one and should be handled by other means.
|
231
|
+
|
232
|
+
Let's simulate the same kind of problem in the PathPlan controller. What we will
|
233
|
+
do is the following:
|
234
|
+
* add a 'blocked' fault event to the model of TrackPath, and make the 'poll'
|
235
|
+
event of TrackPath emit 'blocked' randomly.
|
236
|
+
* have a 'repair' task wait 2 seconds and either (randomly) respawn the path
|
237
|
+
tracking after those two seconds, or emit +failed+.
|
238
|
+
|
239
|
+
The first point is done by adding the following to the definition of TrackPath:
|
240
|
+
event :blocked
|
241
|
+
forward :blocked => :failed
|
242
|
+
|
243
|
+
and then those three lines to the polling block:
|
244
|
+
if rand < 0.05
|
245
|
+
emit :blocked
|
246
|
+
end
|
247
|
+
|
248
|
+
A new RepairTask model has to be added. Open <tt>tasks/repair_task.rb</tt> and
|
249
|
+
add the following
|
250
|
+
class RepairTask < Roby::Task
|
251
|
+
terminates
|
252
|
+
|
253
|
+
event :start do
|
254
|
+
Robot.info "repair will succeed in 2 seconds"
|
255
|
+
forward :start, self, :success, :delay => 2
|
256
|
+
emit :start
|
257
|
+
end
|
258
|
+
|
259
|
+
on :success do
|
260
|
+
plan.respawn(failed_task)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
Finally, the repair handler must be defined added to the plan. Edit the +move_to+ method in <tt>planners/PathPlan/main.rb</tt> and add the following line before the last line of the method:
|
265
|
+
track.event(:blocked).handle_with(RepairTask.new)
|
266
|
+
|
267
|
+
Run as usual and see what happens ...
|
268
|
+
|
269
|
+
Another, more complex example is the "P3d repaired" video presented
|
270
|
+
{here}[files/doc/videos_rdoc.html]
|
271
|
+
|
272
|
+
=== Exception propagation
|
273
|
+
|
274
|
+
This is the third error handling paradigm available in Roby. It is akin to
|
275
|
+
classical exception propagation:
|
276
|
+
* task models can define per-type exception handlers using Roby::Task#on_exception
|
277
|
+
* when an error occurs and is not handled by a plan repair, the error is
|
278
|
+
propagated up in the +realized_by+ relation, searching for a matching
|
279
|
+
exception handler.
|
280
|
+
* if an exception handler is found, it is called with the error. If the
|
281
|
+
exception handlers raises, or if it calls #pass_exception, the propagation is
|
282
|
+
resumed. Otherwise, the system stops propagating the exception. In addition
|
283
|
+
to following the +realized_by+ relation, the +planned_by+ relation is used
|
284
|
+
to check if planning activities can repair the error as well (see example below).
|
285
|
+
* if no handler accepted the error, it is passed to a global error handler defined
|
286
|
+
by Roby.on_exception.
|
287
|
+
|
288
|
+
link:../../images/exception_propagation_5.png
|
289
|
+
|
290
|
+
== Unhandled errors
|
291
|
+
|
292
|
+
Once the exception propagation phase is finished, the plan analysis (i.e.
|
293
|
+
constraint verification) is re-ran once to verify that exception handlers do
|
294
|
+
have repaired the errors. If errors are still found, they cannot be handled
|
295
|
+
anymore.
|
296
|
+
|
297
|
+
This set of errors, and the errors that have not been handled before, determine
|
298
|
+
a set of tasks that can be dangerous for the whole system. The garbage
|
299
|
+
collection kicks in and will take the necessary actions to remove these tasks
|
300
|
+
from the plan. Once necessity is to kill all tasks which were actually
|
301
|
+
depending on the faulty activities: all tasks that are parents of the faulty
|
302
|
+
tasks in any relation are forcefully garbage collected. In the exception
|
303
|
+
propagation example above, all tasks which have a number will be killed and
|
304
|
+
remove from the plan.
|
305
|
+
|
306
|
+
= Next tutorial
|
307
|
+
|
308
|
+
This tutorial presented you with one of the two most singular features of Roby:
|
309
|
+
an extensive way to represent and handle errors. Among them, the error handling
|
310
|
+
relation is the most powerful, as it allows to represent error handling
|
311
|
+
directly in the plan and would for instance work in multi-robot context, even
|
312
|
+
without communication between the two robots.
|
313
|
+
|
314
|
+
{The next tutorial} is not really a tutorial. It is an overview of important
|
315
|
+
Roby features that these tutorials did not cover. Again, my PhD thesis should
|
316
|
+
still be considered one of the most central design document which allow to
|
317
|
+
understand the system.
|
318
|
+
|
319
|
+
|