roby 0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|