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.
Files changed (236) hide show
  1. data/History.txt +7 -5
  2. data/Manifest.txt +91 -16
  3. data/README.txt +24 -24
  4. data/Rakefile +92 -64
  5. data/app/config/app.yml +42 -43
  6. data/app/config/init.rb +26 -0
  7. data/benchmark/alloc_misc.rb +123 -0
  8. data/benchmark/discovery_latency.rb +67 -0
  9. data/benchmark/garbage_collection.rb +48 -0
  10. data/benchmark/genom.rb +31 -0
  11. data/benchmark/transactions.rb +62 -0
  12. data/bin/roby +1 -1
  13. data/bin/roby-log +16 -6
  14. data/doc/guide/.gitignore +2 -0
  15. data/doc/guide/config.yaml +34 -0
  16. data/doc/guide/ext/init.rb +14 -0
  17. data/doc/guide/ext/previous_next.rb +40 -0
  18. data/doc/guide/ext/rdoc_links.rb +33 -0
  19. data/doc/guide/index.rdoc +16 -0
  20. data/doc/guide/overview.rdoc +62 -0
  21. data/doc/guide/plan_modifications.rdoc +67 -0
  22. data/doc/guide/src/abstraction/achieve_with.page +8 -0
  23. data/doc/guide/src/abstraction/forwarding.page +8 -0
  24. data/doc/guide/src/abstraction/hierarchy.page +19 -0
  25. data/doc/guide/src/abstraction/index.page +28 -0
  26. data/doc/guide/src/abstraction/task_models.page +13 -0
  27. data/doc/guide/src/basics.template +6 -0
  28. data/doc/guide/src/basics/app.page +139 -0
  29. data/doc/guide/src/basics/code_examples.page +33 -0
  30. data/doc/guide/src/basics/dry.page +69 -0
  31. data/doc/guide/src/basics/errors.page +443 -0
  32. data/doc/guide/src/basics/events.page +179 -0
  33. data/doc/guide/src/basics/hierarchy.page +275 -0
  34. data/doc/guide/src/basics/index.page +11 -0
  35. data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
  36. data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
  37. data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
  38. data/doc/guide/src/basics/log_replay/goForward_4.png +0 -0
  39. data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
  40. data/doc/guide/src/basics/log_replay/hierarchy_error_1.png +0 -0
  41. data/doc/guide/src/basics/log_replay/hierarchy_error_2.png +0 -0
  42. data/doc/guide/src/basics/log_replay/hierarchy_error_3.png +0 -0
  43. data/doc/guide/src/basics/log_replay/plan_repair_1.png +0 -0
  44. data/doc/guide/src/basics/log_replay/plan_repair_2.png +0 -0
  45. data/doc/guide/src/basics/log_replay/plan_repair_3.png +0 -0
  46. data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
  47. data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
  48. data/doc/guide/src/basics/log_replay/roby_log_relation_window.png +0 -0
  49. data/doc/guide/src/basics/log_replay/roby_replay_event_representation.png +0 -0
  50. data/doc/guide/src/basics/plan_objects.page +71 -0
  51. data/doc/guide/src/basics/relations_display.page +203 -0
  52. data/doc/guide/src/basics/roby_cycle_overview.png +0 -0
  53. data/doc/guide/src/basics/shell.page +102 -0
  54. data/doc/guide/src/basics/summary.page +32 -0
  55. data/doc/guide/src/basics/tasks.page +357 -0
  56. data/doc/guide/src/basics_shell_header.txt +16 -0
  57. data/doc/guide/src/cycle/cycle-overview.png +0 -0
  58. data/doc/guide/src/cycle/cycle-overview.svg +208 -0
  59. data/doc/guide/src/cycle/error_handling.page +168 -0
  60. data/doc/guide/src/cycle/error_instantaneous_repair.png +0 -0
  61. data/doc/guide/src/cycle/error_instantaneous_repair.svg +1224 -0
  62. data/doc/guide/src/cycle/garbage_collection.page +10 -0
  63. data/doc/guide/src/cycle/index.page +23 -0
  64. data/doc/guide/src/cycle/propagation.page +154 -0
  65. data/doc/guide/src/cycle/propagation_diamond.png +0 -0
  66. data/doc/guide/src/cycle/propagation_diamond.svg +1279 -0
  67. data/doc/guide/src/default.css +319 -0
  68. data/doc/guide/src/default.template +74 -0
  69. data/doc/guide/src/htmldoc.metainfo +20 -0
  70. data/doc/guide/src/htmldoc.virtual +18 -0
  71. data/doc/guide/src/images/bodybg.png +0 -0
  72. data/doc/guide/src/images/contbg.png +0 -0
  73. data/doc/guide/src/images/footerbg.png +0 -0
  74. data/doc/guide/src/images/gradient1.png +0 -0
  75. data/doc/guide/src/images/gradient2.png +0 -0
  76. data/doc/guide/src/index.page +7 -0
  77. data/doc/guide/src/introduction/index.page +29 -0
  78. data/doc/guide/src/introduction/install.page +133 -0
  79. data/doc/{papers.rdoc → guide/src/introduction/publications.page} +5 -2
  80. data/doc/{videos.rdoc → guide/src/introduction/videos.page} +4 -2
  81. data/doc/guide/src/plugins/fault_tolerance.page +44 -0
  82. data/doc/guide/src/plugins/index.page +11 -0
  83. data/doc/guide/src/plugins/subsystems.page +45 -0
  84. data/doc/guide/src/relations/dependency.page +89 -0
  85. data/doc/guide/src/relations/index.page +12 -0
  86. data/doc/misc/update_github +24 -0
  87. data/doc/tutorials/02-GoForward.rdoc +3 -3
  88. data/ext/graph/graph.cc +46 -0
  89. data/lib/roby.rb +57 -22
  90. data/lib/roby/app.rb +132 -112
  91. data/lib/roby/app/plugins/rake.rb +21 -0
  92. data/lib/roby/app/rake.rb +0 -7
  93. data/lib/roby/app/run.rb +1 -1
  94. data/lib/roby/app/scripts/distributed.rb +1 -2
  95. data/lib/roby/app/scripts/generate/bookmarks.rb +1 -1
  96. data/lib/roby/app/scripts/results.rb +2 -1
  97. data/lib/roby/app/scripts/run.rb +6 -2
  98. data/lib/roby/app/scripts/shell.rb +11 -11
  99. data/lib/roby/config.rb +1 -1
  100. data/lib/roby/decision_control.rb +62 -3
  101. data/lib/roby/distributed.rb +4 -0
  102. data/lib/roby/distributed/base.rb +8 -0
  103. data/lib/roby/distributed/communication.rb +12 -8
  104. data/lib/roby/distributed/connection_space.rb +61 -44
  105. data/lib/roby/distributed/distributed_object.rb +1 -1
  106. data/lib/roby/distributed/notifications.rb +22 -30
  107. data/lib/roby/distributed/peer.rb +13 -8
  108. data/lib/roby/distributed/proxy.rb +5 -5
  109. data/lib/roby/distributed/subscription.rb +4 -4
  110. data/lib/roby/distributed/transaction.rb +3 -3
  111. data/lib/roby/event.rb +176 -110
  112. data/lib/roby/exceptions.rb +12 -4
  113. data/lib/roby/execution_engine.rb +1604 -0
  114. data/lib/roby/external_process_task.rb +225 -0
  115. data/lib/roby/graph.rb +0 -6
  116. data/lib/roby/interface.rb +221 -137
  117. data/lib/roby/log/console.rb +5 -3
  118. data/lib/roby/log/data_stream.rb +94 -16
  119. data/lib/roby/log/dot.rb +8 -8
  120. data/lib/roby/log/event_stream.rb +13 -3
  121. data/lib/roby/log/file.rb +43 -18
  122. data/lib/roby/log/gui/basic_display_ui.rb +89 -0
  123. data/lib/roby/log/gui/chronicle_view_ui.rb +90 -0
  124. data/lib/roby/log/gui/data_displays.rb +4 -5
  125. data/lib/roby/log/gui/data_displays_ui.rb +146 -0
  126. data/lib/roby/log/gui/relations.rb +18 -18
  127. data/lib/roby/log/gui/relations_ui.rb +120 -0
  128. data/lib/roby/log/gui/relations_view_ui.rb +144 -0
  129. data/lib/roby/log/gui/replay.rb +41 -13
  130. data/lib/roby/log/gui/replay_controls.rb +3 -0
  131. data/lib/roby/log/gui/replay_controls.ui +133 -110
  132. data/lib/roby/log/gui/replay_controls_ui.rb +249 -0
  133. data/lib/roby/log/hooks.rb +19 -18
  134. data/lib/roby/log/logger.rb +7 -6
  135. data/lib/roby/log/notifications.rb +4 -4
  136. data/lib/roby/log/plan_rebuilder.rb +20 -22
  137. data/lib/roby/log/relations.rb +44 -16
  138. data/lib/roby/log/server.rb +1 -4
  139. data/lib/roby/log/timings.rb +88 -19
  140. data/lib/roby/plan-object.rb +135 -11
  141. data/lib/roby/plan.rb +408 -224
  142. data/lib/roby/planning/loops.rb +32 -25
  143. data/lib/roby/planning/model.rb +157 -51
  144. data/lib/roby/planning/task.rb +47 -20
  145. data/lib/roby/query.rb +128 -92
  146. data/lib/roby/relations.rb +254 -136
  147. data/lib/roby/relations/conflicts.rb +6 -9
  148. data/lib/roby/relations/dependency.rb +358 -0
  149. data/lib/roby/relations/ensured.rb +0 -1
  150. data/lib/roby/relations/error_handling.rb +0 -1
  151. data/lib/roby/relations/events.rb +0 -2
  152. data/lib/roby/relations/executed_by.rb +26 -11
  153. data/lib/roby/relations/planned_by.rb +14 -14
  154. data/lib/roby/robot.rb +46 -0
  155. data/lib/roby/schedulers/basic.rb +34 -0
  156. data/lib/roby/standalone.rb +4 -0
  157. data/lib/roby/standard_errors.rb +21 -15
  158. data/lib/roby/state/events.rb +5 -4
  159. data/lib/roby/support.rb +107 -6
  160. data/lib/roby/task-operations.rb +23 -19
  161. data/lib/roby/task.rb +522 -148
  162. data/lib/roby/task_index.rb +80 -0
  163. data/lib/roby/test/common.rb +283 -44
  164. data/lib/roby/test/distributed.rb +53 -37
  165. data/lib/roby/test/testcase.rb +9 -204
  166. data/lib/roby/test/tools.rb +3 -3
  167. data/lib/roby/transactions.rb +154 -111
  168. data/lib/roby/transactions/proxy.rb +40 -7
  169. data/manifest.xml +20 -0
  170. data/plugins/fault_injection/README.txt +0 -3
  171. data/plugins/fault_injection/Rakefile +2 -8
  172. data/plugins/fault_injection/app.rb +1 -1
  173. data/plugins/fault_injection/fault_injection.rb +3 -3
  174. data/plugins/fault_injection/test/test_fault_injection.rb +19 -25
  175. data/plugins/subsystems/README.txt +0 -3
  176. data/plugins/subsystems/Rakefile +2 -7
  177. data/plugins/subsystems/app.rb +27 -16
  178. data/plugins/subsystems/test/app/config/init.rb +3 -0
  179. data/plugins/subsystems/test/app/planners/main.rb +1 -1
  180. data/plugins/subsystems/test/app/tasks/services.rb +1 -1
  181. data/plugins/subsystems/test/test_subsystems.rb +23 -16
  182. data/test/distributed/test_communication.rb +32 -15
  183. data/test/distributed/test_connection.rb +28 -26
  184. data/test/distributed/test_execution.rb +59 -54
  185. data/test/distributed/test_mixed_plan.rb +34 -34
  186. data/test/distributed/test_plan_notifications.rb +26 -26
  187. data/test/distributed/test_protocol.rb +57 -48
  188. data/test/distributed/test_query.rb +11 -7
  189. data/test/distributed/test_remote_plan.rb +71 -71
  190. data/test/distributed/test_transaction.rb +50 -47
  191. data/test/mockups/external_process +28 -0
  192. data/test/planning/test_loops.rb +163 -119
  193. data/test/planning/test_model.rb +3 -3
  194. data/test/planning/test_task.rb +27 -7
  195. data/test/relations/test_conflicts.rb +3 -3
  196. data/test/relations/test_dependency.rb +324 -0
  197. data/test/relations/test_ensured.rb +2 -2
  198. data/test/relations/test_executed_by.rb +94 -19
  199. data/test/relations/test_planned_by.rb +11 -9
  200. data/test/suite_core.rb +6 -3
  201. data/test/suite_distributed.rb +1 -0
  202. data/test/suite_planning.rb +1 -0
  203. data/test/suite_relations.rb +2 -2
  204. data/test/tasks/test_external_process.rb +126 -0
  205. data/test/{test_thread_task.rb → tasks/test_thread_task.rb} +17 -20
  206. data/test/test_bgl.rb +21 -1
  207. data/test/test_event.rb +229 -155
  208. data/test/test_exceptions.rb +79 -80
  209. data/test/test_execution_engine.rb +987 -0
  210. data/test/test_gui.rb +1 -1
  211. data/test/test_interface.rb +11 -5
  212. data/test/test_log.rb +18 -7
  213. data/test/test_log_server.rb +1 -0
  214. data/test/test_plan.rb +229 -395
  215. data/test/test_query.rb +193 -35
  216. data/test/test_relations.rb +88 -8
  217. data/test/test_state.rb +55 -37
  218. data/test/test_support.rb +1 -1
  219. data/test/test_task.rb +371 -218
  220. data/test/test_testcase.rb +32 -16
  221. data/test/test_transactions.rb +211 -170
  222. data/test/test_transactions_proxy.rb +37 -19
  223. metadata +169 -71
  224. data/.gitignore +0 -29
  225. data/doc/styles/allison.css +0 -314
  226. data/doc/styles/allison.js +0 -316
  227. data/doc/styles/allison.rb +0 -276
  228. data/doc/styles/jamis.rb +0 -593
  229. data/lib/roby/control.rb +0 -746
  230. data/lib/roby/executives/simple.rb +0 -30
  231. data/lib/roby/propagation.rb +0 -562
  232. data/lib/roby/relations/hierarchy.rb +0 -239
  233. data/lib/roby/transactions/updates.rb +0 -139
  234. data/test/relations/test_hierarchy.rb +0 -158
  235. data/test/test_control.rb +0 -399
  236. 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
+