roby 0.7.3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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
+