roby 0.7.3 → 0.8.0
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/History.txt +7 -5
- data/Manifest.txt +91 -16
- data/README.txt +24 -24
- data/Rakefile +92 -64
- data/app/config/app.yml +42 -43
- data/app/config/init.rb +26 -0
- data/benchmark/alloc_misc.rb +123 -0
- data/benchmark/discovery_latency.rb +67 -0
- data/benchmark/garbage_collection.rb +48 -0
- data/benchmark/genom.rb +31 -0
- data/benchmark/transactions.rb +62 -0
- data/bin/roby +1 -1
- data/bin/roby-log +16 -6
- data/doc/guide/.gitignore +2 -0
- data/doc/guide/config.yaml +34 -0
- data/doc/guide/ext/init.rb +14 -0
- data/doc/guide/ext/previous_next.rb +40 -0
- data/doc/guide/ext/rdoc_links.rb +33 -0
- data/doc/guide/index.rdoc +16 -0
- data/doc/guide/overview.rdoc +62 -0
- data/doc/guide/plan_modifications.rdoc +67 -0
- data/doc/guide/src/abstraction/achieve_with.page +8 -0
- data/doc/guide/src/abstraction/forwarding.page +8 -0
- data/doc/guide/src/abstraction/hierarchy.page +19 -0
- data/doc/guide/src/abstraction/index.page +28 -0
- data/doc/guide/src/abstraction/task_models.page +13 -0
- data/doc/guide/src/basics.template +6 -0
- data/doc/guide/src/basics/app.page +139 -0
- data/doc/guide/src/basics/code_examples.page +33 -0
- data/doc/guide/src/basics/dry.page +69 -0
- data/doc/guide/src/basics/errors.page +443 -0
- data/doc/guide/src/basics/events.page +179 -0
- data/doc/guide/src/basics/hierarchy.page +275 -0
- data/doc/guide/src/basics/index.page +11 -0
- data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_4.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
- data/doc/guide/src/basics/log_replay/hierarchy_error_1.png +0 -0
- data/doc/guide/src/basics/log_replay/hierarchy_error_2.png +0 -0
- data/doc/guide/src/basics/log_replay/hierarchy_error_3.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_1.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_2.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_3.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_log_relation_window.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_replay_event_representation.png +0 -0
- data/doc/guide/src/basics/plan_objects.page +71 -0
- data/doc/guide/src/basics/relations_display.page +203 -0
- data/doc/guide/src/basics/roby_cycle_overview.png +0 -0
- data/doc/guide/src/basics/shell.page +102 -0
- data/doc/guide/src/basics/summary.page +32 -0
- data/doc/guide/src/basics/tasks.page +357 -0
- data/doc/guide/src/basics_shell_header.txt +16 -0
- data/doc/guide/src/cycle/cycle-overview.png +0 -0
- data/doc/guide/src/cycle/cycle-overview.svg +208 -0
- data/doc/guide/src/cycle/error_handling.page +168 -0
- data/doc/guide/src/cycle/error_instantaneous_repair.png +0 -0
- data/doc/guide/src/cycle/error_instantaneous_repair.svg +1224 -0
- data/doc/guide/src/cycle/garbage_collection.page +10 -0
- data/doc/guide/src/cycle/index.page +23 -0
- data/doc/guide/src/cycle/propagation.page +154 -0
- data/doc/guide/src/cycle/propagation_diamond.png +0 -0
- data/doc/guide/src/cycle/propagation_diamond.svg +1279 -0
- data/doc/guide/src/default.css +319 -0
- data/doc/guide/src/default.template +74 -0
- data/doc/guide/src/htmldoc.metainfo +20 -0
- data/doc/guide/src/htmldoc.virtual +18 -0
- data/doc/guide/src/images/bodybg.png +0 -0
- data/doc/guide/src/images/contbg.png +0 -0
- data/doc/guide/src/images/footerbg.png +0 -0
- data/doc/guide/src/images/gradient1.png +0 -0
- data/doc/guide/src/images/gradient2.png +0 -0
- data/doc/guide/src/index.page +7 -0
- data/doc/guide/src/introduction/index.page +29 -0
- data/doc/guide/src/introduction/install.page +133 -0
- data/doc/{papers.rdoc → guide/src/introduction/publications.page} +5 -2
- data/doc/{videos.rdoc → guide/src/introduction/videos.page} +4 -2
- data/doc/guide/src/plugins/fault_tolerance.page +44 -0
- data/doc/guide/src/plugins/index.page +11 -0
- data/doc/guide/src/plugins/subsystems.page +45 -0
- data/doc/guide/src/relations/dependency.page +89 -0
- data/doc/guide/src/relations/index.page +12 -0
- data/doc/misc/update_github +24 -0
- data/doc/tutorials/02-GoForward.rdoc +3 -3
- data/ext/graph/graph.cc +46 -0
- data/lib/roby.rb +57 -22
- data/lib/roby/app.rb +132 -112
- data/lib/roby/app/plugins/rake.rb +21 -0
- data/lib/roby/app/rake.rb +0 -7
- data/lib/roby/app/run.rb +1 -1
- data/lib/roby/app/scripts/distributed.rb +1 -2
- data/lib/roby/app/scripts/generate/bookmarks.rb +1 -1
- data/lib/roby/app/scripts/results.rb +2 -1
- data/lib/roby/app/scripts/run.rb +6 -2
- data/lib/roby/app/scripts/shell.rb +11 -11
- data/lib/roby/config.rb +1 -1
- data/lib/roby/decision_control.rb +62 -3
- data/lib/roby/distributed.rb +4 -0
- data/lib/roby/distributed/base.rb +8 -0
- data/lib/roby/distributed/communication.rb +12 -8
- data/lib/roby/distributed/connection_space.rb +61 -44
- data/lib/roby/distributed/distributed_object.rb +1 -1
- data/lib/roby/distributed/notifications.rb +22 -30
- data/lib/roby/distributed/peer.rb +13 -8
- data/lib/roby/distributed/proxy.rb +5 -5
- data/lib/roby/distributed/subscription.rb +4 -4
- data/lib/roby/distributed/transaction.rb +3 -3
- data/lib/roby/event.rb +176 -110
- data/lib/roby/exceptions.rb +12 -4
- data/lib/roby/execution_engine.rb +1604 -0
- data/lib/roby/external_process_task.rb +225 -0
- data/lib/roby/graph.rb +0 -6
- data/lib/roby/interface.rb +221 -137
- data/lib/roby/log/console.rb +5 -3
- data/lib/roby/log/data_stream.rb +94 -16
- data/lib/roby/log/dot.rb +8 -8
- data/lib/roby/log/event_stream.rb +13 -3
- data/lib/roby/log/file.rb +43 -18
- data/lib/roby/log/gui/basic_display_ui.rb +89 -0
- data/lib/roby/log/gui/chronicle_view_ui.rb +90 -0
- data/lib/roby/log/gui/data_displays.rb +4 -5
- data/lib/roby/log/gui/data_displays_ui.rb +146 -0
- data/lib/roby/log/gui/relations.rb +18 -18
- data/lib/roby/log/gui/relations_ui.rb +120 -0
- data/lib/roby/log/gui/relations_view_ui.rb +144 -0
- data/lib/roby/log/gui/replay.rb +41 -13
- data/lib/roby/log/gui/replay_controls.rb +3 -0
- data/lib/roby/log/gui/replay_controls.ui +133 -110
- data/lib/roby/log/gui/replay_controls_ui.rb +249 -0
- data/lib/roby/log/hooks.rb +19 -18
- data/lib/roby/log/logger.rb +7 -6
- data/lib/roby/log/notifications.rb +4 -4
- data/lib/roby/log/plan_rebuilder.rb +20 -22
- data/lib/roby/log/relations.rb +44 -16
- data/lib/roby/log/server.rb +1 -4
- data/lib/roby/log/timings.rb +88 -19
- data/lib/roby/plan-object.rb +135 -11
- data/lib/roby/plan.rb +408 -224
- data/lib/roby/planning/loops.rb +32 -25
- data/lib/roby/planning/model.rb +157 -51
- data/lib/roby/planning/task.rb +47 -20
- data/lib/roby/query.rb +128 -92
- data/lib/roby/relations.rb +254 -136
- data/lib/roby/relations/conflicts.rb +6 -9
- data/lib/roby/relations/dependency.rb +358 -0
- data/lib/roby/relations/ensured.rb +0 -1
- data/lib/roby/relations/error_handling.rb +0 -1
- data/lib/roby/relations/events.rb +0 -2
- data/lib/roby/relations/executed_by.rb +26 -11
- data/lib/roby/relations/planned_by.rb +14 -14
- data/lib/roby/robot.rb +46 -0
- data/lib/roby/schedulers/basic.rb +34 -0
- data/lib/roby/standalone.rb +4 -0
- data/lib/roby/standard_errors.rb +21 -15
- data/lib/roby/state/events.rb +5 -4
- data/lib/roby/support.rb +107 -6
- data/lib/roby/task-operations.rb +23 -19
- data/lib/roby/task.rb +522 -148
- data/lib/roby/task_index.rb +80 -0
- data/lib/roby/test/common.rb +283 -44
- data/lib/roby/test/distributed.rb +53 -37
- data/lib/roby/test/testcase.rb +9 -204
- data/lib/roby/test/tools.rb +3 -3
- data/lib/roby/transactions.rb +154 -111
- data/lib/roby/transactions/proxy.rb +40 -7
- data/manifest.xml +20 -0
- data/plugins/fault_injection/README.txt +0 -3
- data/plugins/fault_injection/Rakefile +2 -8
- data/plugins/fault_injection/app.rb +1 -1
- data/plugins/fault_injection/fault_injection.rb +3 -3
- data/plugins/fault_injection/test/test_fault_injection.rb +19 -25
- data/plugins/subsystems/README.txt +0 -3
- data/plugins/subsystems/Rakefile +2 -7
- data/plugins/subsystems/app.rb +27 -16
- data/plugins/subsystems/test/app/config/init.rb +3 -0
- data/plugins/subsystems/test/app/planners/main.rb +1 -1
- data/plugins/subsystems/test/app/tasks/services.rb +1 -1
- data/plugins/subsystems/test/test_subsystems.rb +23 -16
- data/test/distributed/test_communication.rb +32 -15
- data/test/distributed/test_connection.rb +28 -26
- data/test/distributed/test_execution.rb +59 -54
- data/test/distributed/test_mixed_plan.rb +34 -34
- data/test/distributed/test_plan_notifications.rb +26 -26
- data/test/distributed/test_protocol.rb +57 -48
- data/test/distributed/test_query.rb +11 -7
- data/test/distributed/test_remote_plan.rb +71 -71
- data/test/distributed/test_transaction.rb +50 -47
- data/test/mockups/external_process +28 -0
- data/test/planning/test_loops.rb +163 -119
- data/test/planning/test_model.rb +3 -3
- data/test/planning/test_task.rb +27 -7
- data/test/relations/test_conflicts.rb +3 -3
- data/test/relations/test_dependency.rb +324 -0
- data/test/relations/test_ensured.rb +2 -2
- data/test/relations/test_executed_by.rb +94 -19
- data/test/relations/test_planned_by.rb +11 -9
- data/test/suite_core.rb +6 -3
- data/test/suite_distributed.rb +1 -0
- data/test/suite_planning.rb +1 -0
- data/test/suite_relations.rb +2 -2
- data/test/tasks/test_external_process.rb +126 -0
- data/test/{test_thread_task.rb → tasks/test_thread_task.rb} +17 -20
- data/test/test_bgl.rb +21 -1
- data/test/test_event.rb +229 -155
- data/test/test_exceptions.rb +79 -80
- data/test/test_execution_engine.rb +987 -0
- data/test/test_gui.rb +1 -1
- data/test/test_interface.rb +11 -5
- data/test/test_log.rb +18 -7
- data/test/test_log_server.rb +1 -0
- data/test/test_plan.rb +229 -395
- data/test/test_query.rb +193 -35
- data/test/test_relations.rb +88 -8
- data/test/test_state.rb +55 -37
- data/test/test_support.rb +1 -1
- data/test/test_task.rb +371 -218
- data/test/test_testcase.rb +32 -16
- data/test/test_transactions.rb +211 -170
- data/test/test_transactions_proxy.rb +37 -19
- metadata +169 -71
- data/.gitignore +0 -29
- data/doc/styles/allison.css +0 -314
- data/doc/styles/allison.js +0 -316
- data/doc/styles/allison.rb +0 -276
- data/doc/styles/jamis.rb +0 -593
- data/lib/roby/control.rb +0 -746
- data/lib/roby/executives/simple.rb +0 -30
- data/lib/roby/propagation.rb +0 -562
- data/lib/roby/relations/hierarchy.rb +0 -239
- data/lib/roby/transactions/updates.rb +0 -139
- data/test/relations/test_hierarchy.rb +0 -158
- data/test/test_control.rb +0 -399
- data/test/test_propagation.rb +0 -210
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Events
|
|
3
|
+
sort_info: 100
|
|
4
|
+
--- name:content pipeline:tags,markdown,blocks
|
|
5
|
+
|
|
6
|
+
This page will present the basic tools that allow to define and manipulate
|
|
7
|
+
events. In {link: {path: /basics/plan_objects.html, attr:
|
|
8
|
+
{:link_text: the overview of plan objects}}}, we saw that
|
|
9
|
+
events are two sided objects: one the one hand, they represent the
|
|
10
|
+
situations the system is in (event _emission_). On the other hand, they
|
|
11
|
+
represent the commands that the system accepts (event _commands_).
|
|
12
|
+
We'll see in this page how to associate a command with an event (event
|
|
13
|
+
command), how to call code when an event is emitted (event handlers), how to
|
|
14
|
+
call it and how to emit it.
|
|
15
|
+
|
|
16
|
+
{include_file: {filename: src/basics_shell_header.txt, escape_html: false}}
|
|
17
|
+
|
|
18
|
+
The basics
|
|
19
|
+
----------
|
|
20
|
+
|
|
21
|
+
In the plan, events are represented through the {rdoc_class: EventGenerator}
|
|
22
|
+
class. With the following code, you will create an event that has no command (it
|
|
23
|
+
is called a _contingent_ event, or a _non-controllable_ event):
|
|
24
|
+
|
|
25
|
+
{coderay:: ruby}
|
|
26
|
+
>> ev1 = Roby::EventGenerator.new
|
|
27
|
+
{coderay}
|
|
28
|
+
|
|
29
|
+
Then, before using it, you need to include it in a plan, so do
|
|
30
|
+
{coderay:: ruby}
|
|
31
|
+
>> plan.add(ev1)
|
|
32
|
+
{coderay}
|
|
33
|
+
|
|
34
|
+
Finally, to be able to see the event's emission, we would display some text
|
|
35
|
+
using an _event handler_: a piece of code that is executed when the event is
|
|
36
|
+
emitted.
|
|
37
|
+
|
|
38
|
+
{coderay:: ruby}
|
|
39
|
+
>> ev1.on { |e| puts "ev1 emitted with context=#{e.context.inspect}" }
|
|
40
|
+
{coderay}
|
|
41
|
+
|
|
42
|
+
Let's try to emit it. The event _emission_ says "the event happened just now".
|
|
43
|
+
{coderay:: ruby}
|
|
44
|
+
>> ev1.emit
|
|
45
|
+
ev1 emitted with context=nil
|
|
46
|
+
{coderay}
|
|
47
|
+
|
|
48
|
+
And if you would like to associate data with the event (what is called the event
|
|
49
|
+
context), you would do
|
|
50
|
+
|
|
51
|
+
{coderay:: ruby}
|
|
52
|
+
>> ev1.emit(10)
|
|
53
|
+
ev1 emitted with context=[10]
|
|
54
|
+
{coderay}
|
|
55
|
+
|
|
56
|
+
Let's now create a second event
|
|
57
|
+
|
|
58
|
+
{coderay:: ruby}
|
|
59
|
+
>> ev2 = Roby::EventGenerator.new do |argument|
|
|
60
|
+
?> puts "ev2 called with argument=#{argument.inspect}"
|
|
61
|
+
?> ev2.emit(argument.first + 1)
|
|
62
|
+
>> end
|
|
63
|
+
>> plan.add(ev2)
|
|
64
|
+
>> ev2.on { |ev| puts "ev2 emitted with context=#{ev.context.inspect}" }
|
|
65
|
+
{coderay}
|
|
66
|
+
|
|
67
|
+
This second event is _controllable_. It has a block of code associated to it
|
|
68
|
+
(the event command) whose purpose it to make sure that the event will _happen_
|
|
69
|
+
(be emitted). As you can see above, a command accepts an argument. In this
|
|
70
|
+
example, the emission is done by calling emit directly. More complex
|
|
71
|
+
(asynchronous) schemes can also be built, but in general they would be
|
|
72
|
+
represented by tasks (that we will see later on).
|
|
73
|
+
|
|
74
|
+
Let's try our controllable event
|
|
75
|
+
{coderay:: ruby}
|
|
76
|
+
>> ev2.call(10)
|
|
77
|
+
ev2 called with argument=[10]
|
|
78
|
+
ev2 emitted with context=[11]
|
|
79
|
+
{coderay}
|
|
80
|
+
|
|
81
|
+
Reacting to events
|
|
82
|
+
------------------
|
|
83
|
+
The whole point of having a *plan* is to be able to describe _reactions_: i.e.
|
|
84
|
+
what the system should do when something happens. The basic tool to do that,
|
|
85
|
+
is to create a _signal_ between two events:
|
|
86
|
+
|
|
87
|
+
{coderay:: ruby}
|
|
88
|
+
>> ev1.signals ev2
|
|
89
|
+
{coderay}
|
|
90
|
+
|
|
91
|
+
Try it:
|
|
92
|
+
|
|
93
|
+
{coderay:: ruby}
|
|
94
|
+
>> ev1.emit(10)
|
|
95
|
+
ev1 emitted with context=10
|
|
96
|
+
ev2 called with argument=10
|
|
97
|
+
ev2 emitted with context=11
|
|
98
|
+
{coderay}
|
|
99
|
+
|
|
100
|
+
You can therefore see that
|
|
101
|
+
|
|
102
|
+
* because of the signal, the _emission_ of the first event caused the second
|
|
103
|
+
event's command to be called. The signal therefore means "when ev1 happens,
|
|
104
|
+
call ev2"
|
|
105
|
+
* the signalling transforms the event's context into the target command's
|
|
106
|
+
argument.
|
|
107
|
+
|
|
108
|
+
Composing events
|
|
109
|
+
----------------
|
|
110
|
+
|
|
111
|
+
Another important capability built around events is the ability to _compose_ them. Two
|
|
112
|
+
basic operators exist.
|
|
113
|
+
|
|
114
|
+
For instance, in the following snippet, "and\_ev" is emitted when _both_ ev1
|
|
115
|
+
__and__ ev2 have been emitted and "or\_ev" is emitted as soon as _one of_ ev1
|
|
116
|
+
__or__ ev2 have been emitted.
|
|
117
|
+
|
|
118
|
+
{coderay:: ruby}
|
|
119
|
+
>> plan.add(ev1 = EventGenerator.new)
|
|
120
|
+
>> plan.add(ev2 = EventGenerator.new)
|
|
121
|
+
>> and_ev = ev1 & ev2
|
|
122
|
+
>> or_ev = ev1 | ev2
|
|
123
|
+
>> and_ev.on { puts "AND" }
|
|
124
|
+
>> or_ev.on { puts "OR" }
|
|
125
|
+
>> ev1.emit
|
|
126
|
+
OR
|
|
127
|
+
>> ev2.emit
|
|
128
|
+
AND
|
|
129
|
+
{coderay}
|
|
130
|
+
|
|
131
|
+
You did not have to add the and\_ev and or\_ev events to the plan. This is because
|
|
132
|
+
they are linked to ev1 and ev2, so Roby added them to the plan automatically.
|
|
133
|
+
{.info}
|
|
134
|
+
|
|
135
|
+
You can see that the "OR" event is emitted only once. If you want it to be
|
|
136
|
+
emitted every time one of its sources are, you would do:
|
|
137
|
+
{coderay:: ruby}
|
|
138
|
+
>> or_ev = ev1 | ev2
|
|
139
|
+
>> or_ev.on { puts "OR" }
|
|
140
|
+
>> or_ev.on { or_ev.reset }
|
|
141
|
+
>> ev2.emit
|
|
142
|
+
OR
|
|
143
|
+
>> ev2.emit
|
|
144
|
+
OR
|
|
145
|
+
>> ev1.emit
|
|
146
|
+
OR
|
|
147
|
+
{coderay}
|
|
148
|
+
|
|
149
|
+
In the same way, the "AND" event is emitted only once. You can also use #reset
|
|
150
|
+
to make it emit again.
|
|
151
|
+
|
|
152
|
+
Common errors when manipulating events
|
|
153
|
+
---------------------------------------
|
|
154
|
+
|
|
155
|
+
A common error is to try to manipulate an event which is not included in a
|
|
156
|
+
plan (objects are included in plans through the #add call). In that case,
|
|
157
|
+
the event is not executable and you will get the following error:
|
|
158
|
+
|
|
159
|
+
{coderay:: ruby}
|
|
160
|
+
>> ev = EventGenerator.new { }
|
|
161
|
+
>> ev.call
|
|
162
|
+
Roby::EventNotExecutable: #call called on #<Roby::EventGenerator:0x484aa3c0> which is a non-executable event
|
|
163
|
+
>> ev.emit
|
|
164
|
+
Roby::EventNotExecutable: #emit called on #<Roby::EventGenerator:0x484aa3c0> which is a non-executable event
|
|
165
|
+
{coderay}
|
|
166
|
+
|
|
167
|
+
Another common error is to try to call (or signal) and event that is non
|
|
168
|
+
controllable. In that case, Roby raises a {rdoc_class: EventNotControlable}
|
|
169
|
+
exception.
|
|
170
|
+
|
|
171
|
+
{coderay:: ruby}
|
|
172
|
+
>> plan.add(ev = EventGenerator.new)
|
|
173
|
+
>> ev.call
|
|
174
|
+
Roby::EventNotControlable: #call called on a non-controllable event
|
|
175
|
+
>> source = EventGenerator.new
|
|
176
|
+
>> source.signal(ev)
|
|
177
|
+
Roby::EventNotControlable: trying to establish a signal from #<Roby::EventGenerator:0x484a77f8> to #<Roby::EventGenerator:0x484aab88> which is not controllable
|
|
178
|
+
{coderay}
|
|
179
|
+
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Task Hierarchies
|
|
3
|
+
sort_info: 500
|
|
4
|
+
--- pipeline:tags,markdown
|
|
5
|
+
|
|
6
|
+
What building blocks do we have until now ?
|
|
7
|
+
|
|
8
|
+
* events, signals and forwarding: a way to represent how the execution should
|
|
9
|
+
proceed. This is **the execution flow**.
|
|
10
|
+
* tasks: how events can be aggregated into a representation of long-running
|
|
11
|
+
processes
|
|
12
|
+
|
|
13
|
+
The goal of a plan-based system such as Roby is to allow aggregating the tasks
|
|
14
|
+
to represent both *what* the system is doing and *how* it is doing it. To do
|
|
15
|
+
this, Roby offers different **task relations**: these relations are a
|
|
16
|
+
representation of the purpose of each tasks. For instance, the *hierarchy*
|
|
17
|
+
relation represents a dependency, i.e. saying "a given task purpose is to allow
|
|
18
|
+
the successful execution of another task". Another example is the "planned\_by"
|
|
19
|
+
relation. This other relation represents that a given task takes care of
|
|
20
|
+
generating the plan needed to perform an action.
|
|
21
|
+
|
|
22
|
+
This page presents the most important of them: the hierarchy relation.
|
|
23
|
+
|
|
24
|
+
{include_file: {filename: src/basics_shell_header.txt, escape_html: false}}
|
|
25
|
+
|
|
26
|
+
Semantics of task hierarchies
|
|
27
|
+
-----------------------------
|
|
28
|
+
The goal of that relation is to represent the hard dependencies between tasks.
|
|
29
|
+
I.e. to say "task A needs results from task B to perform its duty" or "task A
|
|
30
|
+
needs task B to run to perform its duty". The idea behind such a relation is to
|
|
31
|
+
be able to create a "library" of tasks that perform simple actions (usually
|
|
32
|
+
implemented in a functional layer external to Roby itself), and then to be able
|
|
33
|
+
**aggregate** them into more complex actions simply by building plans.
|
|
34
|
+
|
|
35
|
+
What is the purpose of this ? Why not just run things "like this" ?
|
|
36
|
+
|
|
37
|
+
The goal is to be able to detect errors in execution. What a dependency relation
|
|
38
|
+
gives is not only a semantic (why things are what they are), but also that "if
|
|
39
|
+
the child fails, then something is wrong". The support of error detection and
|
|
40
|
+
reparation is the central issue in supervision, and Roby offers advanced tools
|
|
41
|
+
for it.
|
|
42
|
+
|
|
43
|
+
Adding dependencies between tasks
|
|
44
|
+
---------------------------------
|
|
45
|
+
|
|
46
|
+
The code in that section is not meant to be tried out, but to support the
|
|
47
|
+
explanations. A proper controller, that uses these principles, is shown in the
|
|
48
|
+
following sections.
|
|
49
|
+
{.warning}
|
|
50
|
+
|
|
51
|
+
Let's assume our robot's functional layer offers two services:
|
|
52
|
+
* it has a way to compute a path from A to B (path planning)
|
|
53
|
+
* it has a way to execute that path once its computed
|
|
54
|
+
|
|
55
|
+
What we want here is to build the plan that represents a _movement_ from the
|
|
56
|
+
current robot's position to a goal position. That aggregate action will
|
|
57
|
+
naturally be represented by a MoveTo task that takes one 'goal' argument. In
|
|
58
|
+
Roby, it would look like this:
|
|
59
|
+
|
|
60
|
+
{coderay:: ruby}
|
|
61
|
+
class MoveTo < Roby::Task
|
|
62
|
+
argument :goal
|
|
63
|
+
end
|
|
64
|
+
{coderay}
|
|
65
|
+
|
|
66
|
+
So, now, we would have two options:
|
|
67
|
+
* either call the functional layer directly from the MoveTo task
|
|
68
|
+
* or integrate both services independently (in two different tasks) and then
|
|
69
|
+
aggregate their functionality through the plan.
|
|
70
|
+
|
|
71
|
+
In Roby, we would usually use the second method, as it promotes reusability. The
|
|
72
|
+
plan would therefore have to represent the following:
|
|
73
|
+
|
|
74
|
+
To do a MoveTo from point A to point B
|
|
75
|
+
=> first the robot must successfully compute its
|
|
76
|
+
path between those two points
|
|
77
|
+
=> then it must successfully execute that path
|
|
78
|
+
|
|
79
|
+
Assuming that we have the ComputePath and ExecutePath task models to represent
|
|
80
|
+
our functional layer's services, and that the target point is represented by the
|
|
81
|
+
variable 'p', this can be translated as a plan into:
|
|
82
|
+
|
|
83
|
+
The successful execution of MoveTo(:goal => p) depends on
|
|
84
|
+
the successful execution of ComputePath(:goal => p)
|
|
85
|
+
followed by the successful execution of ExecutePath
|
|
86
|
+
|
|
87
|
+
Finally, that plan would be generated with the following code.
|
|
88
|
+
|
|
89
|
+
{coderay:: ruby}
|
|
90
|
+
move = MoveTo.new :goal => a
|
|
91
|
+
compute = ComputePath.new :goal => a
|
|
92
|
+
execute = ExecutePath.new
|
|
93
|
+
|
|
94
|
+
# The movement depends on the successful execution of both the computation and
|
|
95
|
+
# execution of the path
|
|
96
|
+
move.depends_on compute
|
|
97
|
+
move.depends_on execute
|
|
98
|
+
# Execution should start when computation has finished successfully
|
|
99
|
+
compute.signal :success, execute, :start
|
|
100
|
+
# Computation should start when the movement starts
|
|
101
|
+
move.signal :start, compute, :start
|
|
102
|
+
# The movement has successfully finished when the execution has successfully
|
|
103
|
+
# finished
|
|
104
|
+
execute.forward :success, move, :success
|
|
105
|
+
{coderay}
|
|
106
|
+
|
|
107
|
+
Obviously, the ComputePath/ExecutePath combination is a **sequence**. Rephrasing
|
|
108
|
+
again, we could describe this plan with:
|
|
109
|
+
The MoveTo task is a sequence of ComputePath followed by ExecutePath
|
|
110
|
+
|
|
111
|
+
and could be written
|
|
112
|
+
{coderay:: ruby}
|
|
113
|
+
move = MoveTo.new :goal => a
|
|
114
|
+
compute = ComputePath.new :goal => a
|
|
115
|
+
execute = ExecutePath.new
|
|
116
|
+
(compute + execute).to_task(move)
|
|
117
|
+
move
|
|
118
|
+
{coderay}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
A controller using task hierarchies
|
|
122
|
+
-----------------------------------
|
|
123
|
+
|
|
124
|
+
What do we need to get a proper controller implementing this ?
|
|
125
|
+
* first we need to define the three task models described above
|
|
126
|
+
* then we need to define the planning method that will create the necessary
|
|
127
|
+
plan.
|
|
128
|
+
|
|
129
|
+
The only thing that still needs to be defined is how to transfer the path from
|
|
130
|
+
the ComputePath task to the ExecutePath task. In general, such an endeavour is
|
|
131
|
+
done in Roby through the attributes on task objects. Here, we will simply define
|
|
132
|
+
a 'path' attribute on the MoveTo class. Given that the actual actions are
|
|
133
|
+
performed by ComputePath and ExecutePath, the definition of the MoveTo task is
|
|
134
|
+
actually quite simple:
|
|
135
|
+
|
|
136
|
+
{coderay:: ruby}
|
|
137
|
+
class MoveTo < Roby::Task
|
|
138
|
+
terminates
|
|
139
|
+
|
|
140
|
+
# The movement goal
|
|
141
|
+
argument :goal
|
|
142
|
+
# The generated path
|
|
143
|
+
attr_accessor :path
|
|
144
|
+
end
|
|
145
|
+
{coderay}
|
|
146
|
+
|
|
147
|
+
The implementation of the ComputePath task is a bit more complex. First, on a
|
|
148
|
+
model point of view, it will require the 'goal' argument again, and also a
|
|
149
|
+
'path\_task' argument which is the task holding the path data when computed.
|
|
150
|
+
|
|
151
|
+
On the implementation side, we will use a standard task, the {rdoc_class:
|
|
152
|
+
ThreadTask}. This task represents in Roby's plan a computation that is done in a
|
|
153
|
+
separate thread. To use this task, one simply needs to define an
|
|
154
|
+
"implementation" block (see below). This block is ran in a separate thread by
|
|
155
|
+
ThreadTask and, upon successful execution of the thread, the result value is
|
|
156
|
+
saved in the tasks's 'result' attribute (and success is emitted). The
|
|
157
|
+
added value is that if the thread fails by raising an exception, the "failed"
|
|
158
|
+
event is simply emitted with the exception as context. Now, open
|
|
159
|
+
tasks/compute\_path.rb and add the following code to it:
|
|
160
|
+
|
|
161
|
+
{coderay:: ruby}
|
|
162
|
+
require 'roby/thread_task'
|
|
163
|
+
class ComputePath < Roby::ThreadTask
|
|
164
|
+
# Where we should store the path when computed
|
|
165
|
+
argument :path_task
|
|
166
|
+
# The movement goal
|
|
167
|
+
argument :goal
|
|
168
|
+
|
|
169
|
+
# The robot position at which we started planning the path
|
|
170
|
+
attr_reader :start_point
|
|
171
|
+
|
|
172
|
+
# Initialize start_point and call ThreadTask's start command
|
|
173
|
+
event :start do |context|
|
|
174
|
+
@start_point = State.pos.dup
|
|
175
|
+
super
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Implementation of the computation thread
|
|
179
|
+
implementation do
|
|
180
|
+
path = [start_point]
|
|
181
|
+
max_speed = 1
|
|
182
|
+
while goal.distance(path.last) > max_speed
|
|
183
|
+
u = goal - path.last
|
|
184
|
+
u /= u.length / max_speed
|
|
185
|
+
path << path.last + u
|
|
186
|
+
end
|
|
187
|
+
path << goal
|
|
188
|
+
|
|
189
|
+
Robot.info "#{path.size} points between #{start_point} and #{goal}"
|
|
190
|
+
path
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
on :success do |ev|
|
|
194
|
+
path_task.path = result
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
{coderay}
|
|
198
|
+
|
|
199
|
+
Finally, ExecutePath takes the path generated and follows it. In the same way than
|
|
200
|
+
done with ComputePath, a "path\_task" argument represents the task that holds the
|
|
201
|
+
path (which is everything the task needs). So, edit tasks/execute\_path.rb and
|
|
202
|
+
add the following:
|
|
203
|
+
|
|
204
|
+
{coderay:: ruby}
|
|
205
|
+
class ExecutePath < Roby::Task
|
|
206
|
+
terminates
|
|
207
|
+
|
|
208
|
+
# The task holding the path data
|
|
209
|
+
argument :path_task
|
|
210
|
+
|
|
211
|
+
# The current waypoint
|
|
212
|
+
def current_waypoint; path_task.task[@waypoint_index] end
|
|
213
|
+
|
|
214
|
+
poll do
|
|
215
|
+
@waypoint_index ||= 0
|
|
216
|
+
State.pos = current_waypoint
|
|
217
|
+
@waypoint_index += 1
|
|
218
|
+
if @waypoint_index == path_task.data.size
|
|
219
|
+
emit :success
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
Robot.info "moved to #{current_waypoint}"
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
{coderay}
|
|
226
|
+
|
|
227
|
+
There are two things left to do: properly initializing the position and adding
|
|
228
|
+
the planning method. For the first point, we will use the Pos::Vector3D class
|
|
229
|
+
that Roby provides (a simple x,y,z tuple). Edit controllers/goForward.rb and
|
|
230
|
+
add:
|
|
231
|
+
|
|
232
|
+
{coderay:: ruby}
|
|
233
|
+
State.pos = Pos::Vector3D.new
|
|
234
|
+
{coderay}
|
|
235
|
+
|
|
236
|
+
Finally, edit planners/goForward/main.rb and add the following method:
|
|
237
|
+
|
|
238
|
+
{coderay:: ruby}
|
|
239
|
+
method(:planned_move) do
|
|
240
|
+
goal = Pos::Vector3D.new(*arguments.values_at(:x, :y))
|
|
241
|
+
move = MoveTo.new :goal => goal
|
|
242
|
+
compute = ComputePath.new :goal => goal, :path_task => move
|
|
243
|
+
execute = ExecutePath.new :path_task => move
|
|
244
|
+
(compute + execute).to_task(move)
|
|
245
|
+
move
|
|
246
|
+
end
|
|
247
|
+
{coderay}
|
|
248
|
+
|
|
249
|
+
Trying it out
|
|
250
|
+
-------------
|
|
251
|
+
|
|
252
|
+
In one Unix shell, do
|
|
253
|
+
|
|
254
|
+
$ scripts/run goForward
|
|
255
|
+
344919:32:59.172 (Roby) GC.enable does not accept an argument. GC will not be controlled by Roby
|
|
256
|
+
344919:32:59.204 (goForward) loaded Roby 0.7.90 on ruby 1.8.7 (2008-08-11 patchlevel 72) [x86_64-linux]
|
|
257
|
+
344919:32:59.272 (goForward) loading controller file /home/joyeux/dev/first_app/controllers/goForward.rb
|
|
258
|
+
344919:32:59.273 (goForward) done initialization
|
|
259
|
+
0
|
|
260
|
+
0
|
|
261
|
+
|
|
262
|
+
Then, **in another one**, start the Roby shell
|
|
263
|
+
|
|
264
|
+
$ scripts/shell
|
|
265
|
+
localhost:48902 >
|
|
266
|
+
|
|
267
|
+
And do
|
|
268
|
+
|
|
269
|
+
{coderay:: ruby}
|
|
270
|
+
localhost:48902 > task = planned_move! :x => 10, :y => 20
|
|
271
|
+
=> MoveTo{goal => Vector3D(x=10.000000,y=20.000000,z=0.000000)}:0x7fd0ec1b1a38[]
|
|
272
|
+
localhost:48902 >
|
|
273
|
+
!task MoveTo{goal => Vector3D(x=10.000000,y=20.000000,z=0.000000)}:0x7fd0ec1b1a38[] finished successfully
|
|
274
|
+
{coderay}
|
|
275
|
+
|