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,33 @@
1
+ ---
2
+ title: Code Examples
3
+ sort_info: 10
4
+ --- pipeline:tags,markdown
5
+ Notations
6
+ ---------
7
+ In the following pages, the example code is meant to be run either in a Ruby
8
+ shell or in a normal Unix shell. The following notations are used for these code
9
+ samples:
10
+
11
+ * "$" represents the prompt of the Unix shell. Code that is after it is
12
+ therefore supposed to be written by you.
13
+ * ">>" represents the Ruby shell prompt. Code that is after ">>" is therefore
14
+ supposed to be written by you
15
+ * '#' is a comment marker we use to display some information about the example
16
+ code
17
+ * '=>' is the result of the previously entered command. It is omitted in the
18
+ examples if that information is not useful for the purpose of the example
19
+ itself.
20
+ * lines that start with nothing are lines that are displayed by the Ruby code
21
+
22
+ Setting up the Ruby shell
23
+ -------------------------
24
+ Moreover, before typing in the Roby shell, you will need to prepare it a bit. Do
25
+ the following:
26
+
27
+ $ irb
28
+ {coderay:: ruby}
29
+ >> require 'roby/standalone'
30
+ >> include Roby
31
+ >> plan = Roby.plan
32
+ {coderay}
33
+
@@ -0,0 +1,69 @@
1
+ ---
2
+ title: Don't repeat yourself !
3
+ sort_info: 800
4
+ --- pipeline:tags,markdown,blocks
5
+
6
+ Unlike most (all ?) other supervision system, Roby is *not* a domain-specific
7
+ language (DSL). Instead, it uses the facilities offered by the Ruby programming
8
+ language to _look like_ a DSL.
9
+
10
+ The main consequence, and the reason why this design decision has been made, is
11
+ that in part of the Roby applications one can use programmatic ways to avoid
12
+ **repeating oneself**.
13
+
14
+ Let's take one simple example: in [the tasks page](tasks.html), we defined the
15
+ MyTask task model that way:
16
+
17
+ {coderay:: ruby}
18
+ class MyTask < Roby::Task
19
+ event :start do
20
+ puts "start event called"
21
+ emit :start
22
+ end
23
+ event :controlable do
24
+ puts "controlable event called"
25
+ emit :controlable
26
+ end
27
+ event :contingent
28
+
29
+ on(:start) { puts "start event emitted" }
30
+ on(:controlable) { puts "controlable event emitted" }
31
+ on(:contingent) { puts "contingent event emitted" }
32
+ on(:failed) { puts "failed event emitted" }
33
+ on(:stop) { puts "stop event emitted" }
34
+
35
+ event :finished, :terminal => true
36
+ on(:finished) { puts "finished event emitted" }
37
+ end
38
+ {coderay}
39
+
40
+ Full of repetitions ... Now, one could have written, instead:
41
+
42
+ {coderay:: ruby}
43
+ class MyTask < Roby::Task
44
+ event :start do
45
+ puts "start event called"
46
+ emit :start
47
+ end
48
+ event :controlable do
49
+ puts "controlable event called"
50
+ emit :controlable
51
+ end
52
+ event :finished, :terminal => true
53
+
54
+ each_event do |ev|
55
+ on(ev.symbol) { puts "#{ev.symbol} event emitted" }
56
+ end
57
+ end
58
+ {coderay}
59
+
60
+ This way:
61
+ * if we want to display more information, changing one line does the trick
62
+ * if a new event is added, it gets displayed automatically
63
+
64
+
65
+
66
+ **Don't forget !**: every piece of text you write in a Roby application is Ruby
67
+ code, so you have the means to avoid ugly repetitions.
68
+ {.warning}
69
+
@@ -0,0 +1,443 @@
1
+ ---
2
+ title: Error handling
3
+ sort_info: 700
4
+ --- pipeline:tags,markdown,blocks
5
+
6
+ One thing about robotics, and in particular plan execution, is that Murphy's
7
+ rule applies quite well. This is due to a few things. Among them, the first is
8
+ that the models planning uses (and therefore the plans it builds) are (i) too
9
+ simple to completely reflect the reality, (ii) badly parametrized and (iii)
10
+ represent dynamic agents, which can themselves be able to take decisions. So, in
11
+ essence, the rule of thumb is that a plan will fail during its execution.
12
+
13
+ Because Roby represents and executes all the activities of a given system, the
14
+ representation of errors becomes a very powerful thing: it is quite easy, when
15
+ an error appears somewhere to actually determine what are its consequences.
16
+
17
+ What this tutorial will show is:
18
+ * how parts of the error conditions are encoded in the task structure.
19
+ * how exceptions that come from the code itself (like NoMethodError ...) are
20
+ handled.
21
+
22
+ Failed dependencies
23
+ -------------------
24
+ The hierarchy relation, because it defines a dependency, also obviously defines
25
+ the situations where that dependency has failed. To describe this, the relation
26
+ options allow to define a set of _desirable_ and a set of _forbidden_ events.
27
+ The first category defines what the parent needs (task A desires the success
28
+ event of task B to be emitted). The second category defines what the parent is
29
+ incompatible with (task A will fail if task's B failed event is emitted).
30
+ Obviously, there is a problem if:
31
+ * none of the desirable events can be emitted ever
32
+ * one of the forbidden events is emitted
33
+
34
+ In both cases, a specific {rdoc_class: ChildFailedError} is produced. This error
35
+ describes what happened ("the dependency relation between tasks A and B failed
36
+ because the event 'failed' was emitted) and who is the culprit (in the
37
+ ChildFailedError, the child).
38
+
39
+ Let's see an example of such an error. We'll cheat a bit and make our
40
+ ComputePath task fail. Edit tasks/compute\_path.rb and add an error at the beginning of the implementation block. Make it so that it looks like the following:
41
+
42
+ {coderay:: ruby}
43
+ implementation do
44
+ raise "implementation failed !"
45
+ path = [start_point]
46
+ {coderay}
47
+
48
+ Now, start the controller in one console and the roby shell in another and:
49
+
50
+ localhost:48902 > planned_move! :x => 10, :y => 20
51
+ !Roby::ChildFailedError
52
+ !at [345013:53:51.812/109] in the failed event of ComputePath:0x7f6ff6a8f0a8
53
+ !implementation failed ! (RuntimeError)
54
+ ! ./tasks/compute_path.rb:19,
55
+ ! /home/joyeux/dev/roby/lib/roby/thread_task.rb:63:in `value',
56
+ ! /home/joyeux/dev/roby/lib/roby/thread_task.rb:63:in the polling handler,
57
+ ! /usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require',
58
+ ! /usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `require',
59
+ ! scripts/run:3
60
+ !
61
+ !The failed relation is
62
+ ! MoveTo:0x7f6ff6a8f198
63
+ ! owners: Roby::Distributed
64
+ ! arguments: {:goal=>Vector3D(x=10.000000,y=20.000000,z=0.000000)}
65
+ ! depends_on ComputePath:0x7f6ff6a8f0a8
66
+ ! owners: Roby::Distributed
67
+ ! arguments: {:path_task=>
68
+ ! MoveTo:0x7f6ff6a8f198
69
+ ! owners: Roby::Distributed
70
+ ! arguments: {:goal=>Vector3D(x=10.000000,y=20.000000,z=0.000000)},
71
+ ! :goal=>Vector3D(x=10.000000,y=20.000000,z=0.000000)}
72
+ !
73
+ !The following tasks have been killed:
74
+ ! ComputePath:0x7f6ff6a8f0a8
75
+ ! MoveTo:0x7f6ff6a8f198
76
+ !
77
+ !task MoveTo{goal => Vector3D(x=10.000000,y=20.000000,z=0.000000)}:0x7f6ff6a8f198[] failed
78
+
79
+ What information is there ?
80
+ * we do have a ChildFailedError
81
+ * the source is the emission at 345013:53:51.812/109 of the 'failed' event of
82
+ ComputePath. Roby::ThreadTask will automatically emit _failed_ if the
83
+ implementation block raises an exception (what we did). In that case, the
84
+ context of _failed_ is the exception object itself.
85
+ * because the exception object is available, it is displayed, including
86
+ its backtrace.
87
+
88
+ !implementation failed ! (RuntimeError)
89
+ ! ./tasks/compute_path.rb:19,
90
+ ! /home/joyeux/dev/roby/lib/roby/thread_task.rb:63:in `value',
91
+ ! /home/joyeux/dev/roby/lib/roby/thread_task.rb:63:in the polling handler,
92
+ ! /usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `gem_original_require',
93
+ ! /usr/lib/ruby/1.8/rubygems/custom_require.rb:31:in `require',
94
+ ! scripts/run:3
95
+
96
+ * the two tasks involved in the failed relation are displayed as well. MoveTo is the parent, ComputePath is the child.
97
+ * finally, because nothing was done to repair the error, both the failed task
98
+ _and its parents_ are **killed and removed from the plan**. This is because
99
+ the parent tasks may not behave correctly given that one of their dependencies
100
+ is not behaving properly. So, as a safety measure, we kill them. This is done by
101
+ the garbage collection mechanism we presented along with the plan display.
102
+ * the last line, announcing that the MoveTo task failed is not part of the
103
+ exception message, but of the interactive shell that tells us that one of our
104
+ missions has failed.
105
+
106
+ Coding mistakes
107
+ ---------------
108
+ Roby tries very hard to separate the _framework_ code from the _user_
109
+ code, so that coding mistakes in user code can be dealt with in a safe manner.
110
+
111
+ The _user code_ is the part of the code which is tied to events and tasks:
112
+ event commands, event handlers, polling blocks. For those, it is actually
113
+ possible to generate a plan error, as for instance for the child failed error,
114
+ and to handle the error at the plan level.
115
+
116
+ The _framework code_ is the more problematic part: if an error appears here, it
117
+ means that there is really a bug in the execution engine itself (and therefore
118
+ that we can't rely on it). In that case, Roby tries to hang up as cleanly as
119
+ possible by killing all tasks that are being executed.
120
+
121
+ Let's try one code error. Add the following event handler in the definition of
122
+ MoveTo in tasks/move\_to.rb
123
+
124
+ {coderay:: ruby}
125
+ on :start do
126
+ raise "the start handler failed !"
127
+ end
128
+ {coderay}
129
+
130
+ Start (or restart) the controller and launch a planned\_move! action in
131
+ the shell. The following should happen:
132
+
133
+ !Roby::EventHandlerError: user code raised an exception at [336641:28:04.607/23] in the start event of MoveTo:0x2b4330b4fae8
134
+ !
135
+ !
136
+ !the start handler failed ! (RuntimeError)
137
+ !./tasks/move_to.rb:10:in event handler for 'start',
138
+ ! /home/joyeux/system/rubygems/lib/rubygems/custom_require.rb:27:in `gem_original_require',
139
+ ! /home/joyeux/system/rubygems/lib/rubygems/custom_require.rb:27:in `require',
140
+ ! scripts/run:3
141
+ !
142
+ !The following tasks have been killed:
143
+ ! MoveTo:0x2b4330b4fae8
144
+
145
+ Now, what happens during execution: how Roby does react to that error ? What we
146
+ can see in the relation display is the following three successive steps. **Don't
147
+ forget to uncheck _View/Hide finalized_**.
148
+
149
+ ![](log_replay/hierarchy_error_1.png)
150
+ {.fullfigure}
151
+
152
+ **Starting point**: the MoveTo is started and so is the ComputePath task, as it
153
+ is an event handler that fails (i.e. the error appears _after_ the event is
154
+ emitted).
155
+ {.figurecaption}
156
+
157
+ ![](log_replay/hierarchy_error_2.png)
158
+ {.fullfigure}
159
+
160
+ **Next cycle**: MoveTo is killed because of the error in the previous cycle.
161
+ ComputePath took only one cycle to complete, so ExecutePath is started as well.
162
+ {.figurecaption}
163
+
164
+ ![](log_replay/hierarchy_error_3.png)
165
+ {.fullfigure}
166
+
167
+ **Killing useless tasks**: the now useless ExecutePath is killed as well.
168
+ {.figurecaption}
169
+
170
+ From Roby's point of view, the event has already happened when the event
171
+ handlers get called. Therefore, the event propagation should go on even though
172
+ one of the event handlers failed, and therefore ComputePath is started.
173
+
174
+ However, since an error occured and has not been handled, the MoveTo task cannot
175
+ keep running and is stopped at the next cycle. The tasks that are now useless
176
+ are also stopped (in this particular execution, ComputePath took only one cycle
177
+ to execute and therefore only ExecutePath is stopped).
178
+
179
+ For event commands, all depends on where the exception actually appears. If
180
+ 'emit' has already been called, then the event will be emitted and propagated.
181
+ Otherwise, it counts as a cancelling of the event command -- which is an error
182
+ itself.
183
+
184
+ Handling errors
185
+ ---------------
186
+ This is more an advanced subject, but we'll give you an overview of the means
187
+ for error handling that Roby offers anyway. The basic principles are as follows:
188
+ * the plan structure should be sane. We saw what it means in the case of the
189
+ hierarchy relation, but other relations also define what is a "nominal" plan
190
+ structure. An error is raised if the current plan does not match that nominal
191
+ structure.
192
+ * tasks for which coding errors have been detected must be terminated.
193
+ * anything that depends on a failed task must be terminated.
194
+
195
+ During the application's execution, the event propagation, error detection and
196
+ garbage detection are three distinct steps:
197
+
198
+ ![](roby_cycle_overview.png)
199
+
200
+ So, if a broken plan structure is repaired in the event propagation stage, then
201
+ no errors will ever be emitted. Indeed, the error detection and handling stage
202
+ will not see the problem. **Repairing errors in event handlers** is a first mean of
203
+ error handling.
204
+
205
+ The error handling stage is actually split into two sub-stages:
206
+
207
+ * first, specific code blocks are called for each error found in the plan (these
208
+ code blocks are called _exception handlers_)
209
+ * then, the structure is checked again and the errors that remain lead to
210
+ garbage collection.
211
+
212
+ **Exception handlers** are the second mean of error handling.
213
+
214
+ Finally, sometime, recovering from an error requires complex actions (or
215
+ decision-making) that do not fit in one execution cycle. For those situations,
216
+ Roby allows to mark some tasks as being _plan repairs_: these tasks' job is to
217
+ repair specific errors. **Plan repairs** are the third mean of error handling.
218
+
219
+ Repairing during events propagation
220
+ -----------------------------------
221
+
222
+ If a child fails, for instance because of a spurious problem, it would have been
223
+ possible to actually restart the failing child directly in the event handler of
224
+ _failed_ and replace the failed task through this new one. This is as simple as:
225
+
226
+ {coderay:: ruby}
227
+ on(:failed) do
228
+ plan.respawn(self)
229
+ end
230
+ {coderay}
231
+
232
+ Let's try it. Add the following to the definition of ExecutePath to simulate an
233
+ error:
234
+
235
+ {coderay:: ruby}
236
+ attr_accessor :should_pass
237
+ event :start do
238
+ if !should_pass
239
+ forward :start, self, :failed, :delay => 0.2
240
+ end
241
+ emit :start
242
+ end
243
+
244
+ on :failed do
245
+ if !should_pass
246
+ Robot.info "respawning ..."
247
+ new_task = plan.respawn(self)
248
+ new_task.should_pass = true
249
+ end
250
+ end
251
+ {coderay}
252
+
253
+ In the first pass, #should\_pass is false and therefore the _delayed forwarding
254
+ relation_ is set up between start and failed. It means that _failed_ will be
255
+ emitted 0.2 seconds after _start_ (thus simulating a failing task).
256
+
257
+ In the _failed_ handler, a new ExecutePath task is re-created with the same
258
+ arguments using Plan#respawn. On this task, #should\_pass is set to true so that
259
+ the execution continues normally.
260
+
261
+ Note that doing such a thing on the failed event is a bad idea, as failed is
262
+ emitted when the task gets interrupted. You would, in general, do that on a more
263
+ specific error event (i.e. an event that is forwarded to _failed_).
264
+
265
+ TODO: figure.
266
+
267
+ Asynchronous repairs
268
+ --------------------
269
+
270
+ In Roby's plans, asynchronous repairs are represented as _plan repairs_. Plan
271
+ repairs are tasks which are associated with a task's event. When the task's
272
+ event is the source of a failure, the task is activated and should repair that
273
+ particular error.
274
+
275
+ To define plan repairs, a ErrorHandling relation exists. This relation defines
276
+ the set of possible plan repairs for a given task and event. To define a
277
+ specific repair, one uses something like:
278
+
279
+ {coderay:: ruby}
280
+ task.event(error_event_name).handle_with(my_repair_task)
281
+ {coderay}
282
+
283
+ Let's try it in our application. What we will do is the following:
284
+ * add a _blocked_ fault event to the model of ExecutePath, and make the poll
285
+ block of ExecutePath emit _blocked_ randomly.
286
+ * have a repair task wait 2 seconds and either (randomly) respawn the path
287
+ execution after those two seconds, or emit _failed_.
288
+
289
+ The first point is straightforward: just change tasks/execute\_path.rb so that
290
+ the bottom of it looks like the following code. Changed lines are 4, 5, 10 and
291
+ 11.
292
+
293
+ {coderay:: {lang: ruby, line_numbers: true}}
294
+ if @waypoint_index == path_task.path.size
295
+ emit :success
296
+ elsif rand < 0.05
297
+ emit :blocked
298
+ end
299
+ Robot.info "moved to #{current_waypoint}"
300
+ end
301
+
302
+ event :blocked
303
+ forward :blocked => :failed
304
+ end
305
+ {coderay}
306
+
307
+ A new RepairTask model has to be added. Open tasks/repair\_task.rb and add the
308
+ following:
309
+
310
+ {coderay:: ruby}
311
+ class RepairTask < Roby::Task
312
+ terminates
313
+
314
+ event :start do
315
+ Robot.info "repair will succeed in 2 seconds"
316
+ forward_to :start, self, :success, :delay => 2
317
+ emit :start
318
+ end
319
+
320
+ on :success do
321
+ current = failed_task.current_waypoint
322
+ execute = plan.respawn(failed_task)
323
+ repair = plan.recreate(self)
324
+
325
+ # Get the path object, and remove from the points that have already been
326
+ # done
327
+ path = execute.path_task.path
328
+ while !path.empty? && path[0] != current
329
+ path.shift
330
+ end
331
+ end
332
+ end
333
+ {coderay}
334
+
335
+ Finally, the repair handler must be defined added to the plan. Edit the
336
+ planned\_move method in <tt>planners/goForward/main.rb</tt> and add the
337
+ following line to it:
338
+
339
+ {coderay:: ruby}
340
+ execute.event(:blocked).handle_with(RepairTask.new)
341
+ {coderay}
342
+
343
+ Let's run it as usual and see what happens ... In the Roby shell, do
344
+
345
+ >> planned_move! :x => 10, :y => 20
346
+
347
+ Then, take a look at the application output
348
+
349
+ 24 points between Vector3D(x=0.000000,y=0.000000,z=0.000000) and Vector3D(x=10.000000,y=20.000000,z=0.000000)
350
+ moved to Vector3D(x=0.447214,y=0.894427,z=0.000000)
351
+ moved to Vector3D(x=0.894427,y=1.788854,z=0.000000)
352
+ repair will succeed in 2 seconds
353
+ moved to Vector3D(x=1.341641,y=2.683282,z=0.000000)
354
+ moved to Vector3D(x=1.788854,y=3.577709,z=0.000000)
355
+ moved to Vector3D(x=2.236068,y=4.472136,z=0.000000)
356
+ moved to Vector3D(x=2.683282,y=5.366563,z=0.000000)
357
+ moved to Vector3D(x=3.130495,y=6.260990,z=0.000000)
358
+ moved to Vector3D(x=3.577709,y=7.155418,z=0.000000)
359
+ moved to Vector3D(x=4.024922,y=8.049845,z=0.000000)
360
+ moved to Vector3D(x=4.472136,y=8.944272,z=0.000000)
361
+ moved to Vector3D(x=4.919350,y=9.838699,z=0.000000)
362
+ moved to Vector3D(x=5.366563,y=10.733126,z=0.000000)
363
+ moved to Vector3D(x=5.813777,y=11.627553,z=0.000000)
364
+ moved to Vector3D(x=6.260990,y=12.521981,z=0.000000)
365
+ moved to Vector3D(x=6.708204,y=13.416408,z=0.000000)
366
+ moved to Vector3D(x=7.155418,y=14.310835,z=0.000000)
367
+ moved to Vector3D(x=7.602631,y=15.205262,z=0.000000)
368
+ moved to Vector3D(x=8.049845,y=16.099689,z=0.000000)
369
+ repair will succeed in 2 seconds
370
+ moved to Vector3D(x=8.497058,y=16.994117,z=0.000000)
371
+ moved to Vector3D(x=8.944272,y=17.888544,z=0.000000)
372
+ moved to Vector3D(x=9.391486,y=18.782971,z=0.000000)
373
+ repair will succeed in 2 seconds
374
+ moved to Vector3D(x=9.838699,y=19.677398,z=0.000000)
375
+ moved to Vector3D(x=10.000000,y=20.000000,z=0.000000)
376
+ moved to
377
+
378
+ Each time the task failed, the repair task was started and repaired the plan.
379
+ Note that the task also adds a copy of itself to the plan. Let's look at it in
380
+ more details in the plan display (note that this time, you need to display the
381
+ ErrorHandling relation as well).
382
+
383
+ ![](log_replay/plan_repair_1.png)
384
+ {.fullfigure}
385
+
386
+ **Nominal execution**: the plan repair (in blue) is not executed yet and the ExecutePath is running
387
+ {.figurecaption}
388
+
389
+ ![](log_replay/plan_repair_2.png)
390
+ {.fullfigure}
391
+
392
+ **ExecutePath fails**: the plan repair is queued for starting, and will actually start in the next cycle
393
+ {.figurecaption}
394
+
395
+ ![](log_replay/plan_repair_3.png)
396
+ {.fullfigure}
397
+
398
+ **Repair started**
399
+ {.figurecaption}
400
+
401
+ ![](log_replay/plan_repair_4.png)
402
+ {.fullfigure}
403
+
404
+ **Repair successful**: a new ExecutePath task is added with a new repair. The
405
+ new task's start event is queued, and will therefore start at the beginning of
406
+ the next cycle.
407
+ {.figurecaption}
408
+
409
+ A simple real-world example is
410
+ [here](http://roby.rubyforge.org/videos/rflex_repaired.avi) In this video, the
411
+ microcontroller which drives the robot's motors sometime gives spurious
412
+ <tt>BRAKES_ON</tt> messages. Our problem is that the Roby controller must
413
+ determine if the message is spurious, or if brakes are actually set by the means
414
+ of an emergency switch for instance. To do that, the plan waits a few seconds
415
+ and tests the <tt>BRAKES_ON</tt> state of the robot. If the brakes are reported
416
+ as off, then the robot can start moving again. Otherwise, the error was a
417
+ rightful one and should be handled by other means.
418
+
419
+ Another, more complex example is the "P3d repaired" video presented
420
+ [here](http://roby.rubyforge.org/videos/p3d_repaired.avi)
421
+
422
+ Exception propagation
423
+ ---------------------
424
+ This is the third error handling paradigm available in Roby. It is akin to
425
+ classical exception propagation. This mean of error handling is more advanced,
426
+ and therefore is not presented in detail here.
427
+
428
+ Unhandled errors
429
+ ----------------
430
+ Once the exception propagation phase is finished, the plan analysis (i.e.
431
+ constraint verification) is re-ran once to verify that exception handlers do
432
+ have repaired the errors. If errors are still found, they cannot be handled
433
+ anymore.
434
+
435
+ This set of errors, and the errors that have not been handled before, determine
436
+ a set of tasks that can be dangerous for the whole system. The garbage
437
+ collection kicks in and will take the necessary actions to remove these tasks
438
+ from the plan. Indeed, it is necessary to kill all tasks which were actually
439
+ depending on the faulty activities: all tasks that are parents of the faulty
440
+ tasks in any relation are forcefully garbage collected. In the exception
441
+ propagation example above, all tasks which have a number will be killed and
442
+ remove from the plan.
443
+