roby 0.7.3 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +7 -5
- data/Manifest.txt +91 -16
- data/README.txt +24 -24
- data/Rakefile +92 -64
- data/app/config/app.yml +42 -43
- data/app/config/init.rb +26 -0
- data/benchmark/alloc_misc.rb +123 -0
- data/benchmark/discovery_latency.rb +67 -0
- data/benchmark/garbage_collection.rb +48 -0
- data/benchmark/genom.rb +31 -0
- data/benchmark/transactions.rb +62 -0
- data/bin/roby +1 -1
- data/bin/roby-log +16 -6
- data/doc/guide/.gitignore +2 -0
- data/doc/guide/config.yaml +34 -0
- data/doc/guide/ext/init.rb +14 -0
- data/doc/guide/ext/previous_next.rb +40 -0
- data/doc/guide/ext/rdoc_links.rb +33 -0
- data/doc/guide/index.rdoc +16 -0
- data/doc/guide/overview.rdoc +62 -0
- data/doc/guide/plan_modifications.rdoc +67 -0
- data/doc/guide/src/abstraction/achieve_with.page +8 -0
- data/doc/guide/src/abstraction/forwarding.page +8 -0
- data/doc/guide/src/abstraction/hierarchy.page +19 -0
- data/doc/guide/src/abstraction/index.page +28 -0
- data/doc/guide/src/abstraction/task_models.page +13 -0
- data/doc/guide/src/basics.template +6 -0
- data/doc/guide/src/basics/app.page +139 -0
- data/doc/guide/src/basics/code_examples.page +33 -0
- data/doc/guide/src/basics/dry.page +69 -0
- data/doc/guide/src/basics/errors.page +443 -0
- data/doc/guide/src/basics/events.page +179 -0
- data/doc/guide/src/basics/hierarchy.page +275 -0
- data/doc/guide/src/basics/index.page +11 -0
- data/doc/guide/src/basics/log_replay/goForward_1.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_2.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_3.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_4.png +0 -0
- data/doc/guide/src/basics/log_replay/goForward_5.png +0 -0
- data/doc/guide/src/basics/log_replay/hierarchy_error_1.png +0 -0
- data/doc/guide/src/basics/log_replay/hierarchy_error_2.png +0 -0
- data/doc/guide/src/basics/log_replay/hierarchy_error_3.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_1.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_2.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_3.png +0 -0
- data/doc/guide/src/basics/log_replay/plan_repair_4.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_log_main_window.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_log_relation_window.png +0 -0
- data/doc/guide/src/basics/log_replay/roby_replay_event_representation.png +0 -0
- data/doc/guide/src/basics/plan_objects.page +71 -0
- data/doc/guide/src/basics/relations_display.page +203 -0
- data/doc/guide/src/basics/roby_cycle_overview.png +0 -0
- data/doc/guide/src/basics/shell.page +102 -0
- data/doc/guide/src/basics/summary.page +32 -0
- data/doc/guide/src/basics/tasks.page +357 -0
- data/doc/guide/src/basics_shell_header.txt +16 -0
- data/doc/guide/src/cycle/cycle-overview.png +0 -0
- data/doc/guide/src/cycle/cycle-overview.svg +208 -0
- data/doc/guide/src/cycle/error_handling.page +168 -0
- data/doc/guide/src/cycle/error_instantaneous_repair.png +0 -0
- data/doc/guide/src/cycle/error_instantaneous_repair.svg +1224 -0
- data/doc/guide/src/cycle/garbage_collection.page +10 -0
- data/doc/guide/src/cycle/index.page +23 -0
- data/doc/guide/src/cycle/propagation.page +154 -0
- data/doc/guide/src/cycle/propagation_diamond.png +0 -0
- data/doc/guide/src/cycle/propagation_diamond.svg +1279 -0
- data/doc/guide/src/default.css +319 -0
- data/doc/guide/src/default.template +74 -0
- data/doc/guide/src/htmldoc.metainfo +20 -0
- data/doc/guide/src/htmldoc.virtual +18 -0
- data/doc/guide/src/images/bodybg.png +0 -0
- data/doc/guide/src/images/contbg.png +0 -0
- data/doc/guide/src/images/footerbg.png +0 -0
- data/doc/guide/src/images/gradient1.png +0 -0
- data/doc/guide/src/images/gradient2.png +0 -0
- data/doc/guide/src/index.page +7 -0
- data/doc/guide/src/introduction/index.page +29 -0
- data/doc/guide/src/introduction/install.page +133 -0
- data/doc/{papers.rdoc → guide/src/introduction/publications.page} +5 -2
- data/doc/{videos.rdoc → guide/src/introduction/videos.page} +4 -2
- data/doc/guide/src/plugins/fault_tolerance.page +44 -0
- data/doc/guide/src/plugins/index.page +11 -0
- data/doc/guide/src/plugins/subsystems.page +45 -0
- data/doc/guide/src/relations/dependency.page +89 -0
- data/doc/guide/src/relations/index.page +12 -0
- data/doc/misc/update_github +24 -0
- data/doc/tutorials/02-GoForward.rdoc +3 -3
- data/ext/graph/graph.cc +46 -0
- data/lib/roby.rb +57 -22
- data/lib/roby/app.rb +132 -112
- data/lib/roby/app/plugins/rake.rb +21 -0
- data/lib/roby/app/rake.rb +0 -7
- data/lib/roby/app/run.rb +1 -1
- data/lib/roby/app/scripts/distributed.rb +1 -2
- data/lib/roby/app/scripts/generate/bookmarks.rb +1 -1
- data/lib/roby/app/scripts/results.rb +2 -1
- data/lib/roby/app/scripts/run.rb +6 -2
- data/lib/roby/app/scripts/shell.rb +11 -11
- data/lib/roby/config.rb +1 -1
- data/lib/roby/decision_control.rb +62 -3
- data/lib/roby/distributed.rb +4 -0
- data/lib/roby/distributed/base.rb +8 -0
- data/lib/roby/distributed/communication.rb +12 -8
- data/lib/roby/distributed/connection_space.rb +61 -44
- data/lib/roby/distributed/distributed_object.rb +1 -1
- data/lib/roby/distributed/notifications.rb +22 -30
- data/lib/roby/distributed/peer.rb +13 -8
- data/lib/roby/distributed/proxy.rb +5 -5
- data/lib/roby/distributed/subscription.rb +4 -4
- data/lib/roby/distributed/transaction.rb +3 -3
- data/lib/roby/event.rb +176 -110
- data/lib/roby/exceptions.rb +12 -4
- data/lib/roby/execution_engine.rb +1604 -0
- data/lib/roby/external_process_task.rb +225 -0
- data/lib/roby/graph.rb +0 -6
- data/lib/roby/interface.rb +221 -137
- data/lib/roby/log/console.rb +5 -3
- data/lib/roby/log/data_stream.rb +94 -16
- data/lib/roby/log/dot.rb +8 -8
- data/lib/roby/log/event_stream.rb +13 -3
- data/lib/roby/log/file.rb +43 -18
- data/lib/roby/log/gui/basic_display_ui.rb +89 -0
- data/lib/roby/log/gui/chronicle_view_ui.rb +90 -0
- data/lib/roby/log/gui/data_displays.rb +4 -5
- data/lib/roby/log/gui/data_displays_ui.rb +146 -0
- data/lib/roby/log/gui/relations.rb +18 -18
- data/lib/roby/log/gui/relations_ui.rb +120 -0
- data/lib/roby/log/gui/relations_view_ui.rb +144 -0
- data/lib/roby/log/gui/replay.rb +41 -13
- data/lib/roby/log/gui/replay_controls.rb +3 -0
- data/lib/roby/log/gui/replay_controls.ui +133 -110
- data/lib/roby/log/gui/replay_controls_ui.rb +249 -0
- data/lib/roby/log/hooks.rb +19 -18
- data/lib/roby/log/logger.rb +7 -6
- data/lib/roby/log/notifications.rb +4 -4
- data/lib/roby/log/plan_rebuilder.rb +20 -22
- data/lib/roby/log/relations.rb +44 -16
- data/lib/roby/log/server.rb +1 -4
- data/lib/roby/log/timings.rb +88 -19
- data/lib/roby/plan-object.rb +135 -11
- data/lib/roby/plan.rb +408 -224
- data/lib/roby/planning/loops.rb +32 -25
- data/lib/roby/planning/model.rb +157 -51
- data/lib/roby/planning/task.rb +47 -20
- data/lib/roby/query.rb +128 -92
- data/lib/roby/relations.rb +254 -136
- data/lib/roby/relations/conflicts.rb +6 -9
- data/lib/roby/relations/dependency.rb +358 -0
- data/lib/roby/relations/ensured.rb +0 -1
- data/lib/roby/relations/error_handling.rb +0 -1
- data/lib/roby/relations/events.rb +0 -2
- data/lib/roby/relations/executed_by.rb +26 -11
- data/lib/roby/relations/planned_by.rb +14 -14
- data/lib/roby/robot.rb +46 -0
- data/lib/roby/schedulers/basic.rb +34 -0
- data/lib/roby/standalone.rb +4 -0
- data/lib/roby/standard_errors.rb +21 -15
- data/lib/roby/state/events.rb +5 -4
- data/lib/roby/support.rb +107 -6
- data/lib/roby/task-operations.rb +23 -19
- data/lib/roby/task.rb +522 -148
- data/lib/roby/task_index.rb +80 -0
- data/lib/roby/test/common.rb +283 -44
- data/lib/roby/test/distributed.rb +53 -37
- data/lib/roby/test/testcase.rb +9 -204
- data/lib/roby/test/tools.rb +3 -3
- data/lib/roby/transactions.rb +154 -111
- data/lib/roby/transactions/proxy.rb +40 -7
- data/manifest.xml +20 -0
- data/plugins/fault_injection/README.txt +0 -3
- data/plugins/fault_injection/Rakefile +2 -8
- data/plugins/fault_injection/app.rb +1 -1
- data/plugins/fault_injection/fault_injection.rb +3 -3
- data/plugins/fault_injection/test/test_fault_injection.rb +19 -25
- data/plugins/subsystems/README.txt +0 -3
- data/plugins/subsystems/Rakefile +2 -7
- data/plugins/subsystems/app.rb +27 -16
- data/plugins/subsystems/test/app/config/init.rb +3 -0
- data/plugins/subsystems/test/app/planners/main.rb +1 -1
- data/plugins/subsystems/test/app/tasks/services.rb +1 -1
- data/plugins/subsystems/test/test_subsystems.rb +23 -16
- data/test/distributed/test_communication.rb +32 -15
- data/test/distributed/test_connection.rb +28 -26
- data/test/distributed/test_execution.rb +59 -54
- data/test/distributed/test_mixed_plan.rb +34 -34
- data/test/distributed/test_plan_notifications.rb +26 -26
- data/test/distributed/test_protocol.rb +57 -48
- data/test/distributed/test_query.rb +11 -7
- data/test/distributed/test_remote_plan.rb +71 -71
- data/test/distributed/test_transaction.rb +50 -47
- data/test/mockups/external_process +28 -0
- data/test/planning/test_loops.rb +163 -119
- data/test/planning/test_model.rb +3 -3
- data/test/planning/test_task.rb +27 -7
- data/test/relations/test_conflicts.rb +3 -3
- data/test/relations/test_dependency.rb +324 -0
- data/test/relations/test_ensured.rb +2 -2
- data/test/relations/test_executed_by.rb +94 -19
- data/test/relations/test_planned_by.rb +11 -9
- data/test/suite_core.rb +6 -3
- data/test/suite_distributed.rb +1 -0
- data/test/suite_planning.rb +1 -0
- data/test/suite_relations.rb +2 -2
- data/test/tasks/test_external_process.rb +126 -0
- data/test/{test_thread_task.rb → tasks/test_thread_task.rb} +17 -20
- data/test/test_bgl.rb +21 -1
- data/test/test_event.rb +229 -155
- data/test/test_exceptions.rb +79 -80
- data/test/test_execution_engine.rb +987 -0
- data/test/test_gui.rb +1 -1
- data/test/test_interface.rb +11 -5
- data/test/test_log.rb +18 -7
- data/test/test_log_server.rb +1 -0
- data/test/test_plan.rb +229 -395
- data/test/test_query.rb +193 -35
- data/test/test_relations.rb +88 -8
- data/test/test_state.rb +55 -37
- data/test/test_support.rb +1 -1
- data/test/test_task.rb +371 -218
- data/test/test_testcase.rb +32 -16
- data/test/test_transactions.rb +211 -170
- data/test/test_transactions_proxy.rb +37 -19
- metadata +169 -71
- data/.gitignore +0 -29
- data/doc/styles/allison.css +0 -314
- data/doc/styles/allison.js +0 -316
- data/doc/styles/allison.rb +0 -276
- data/doc/styles/jamis.rb +0 -593
- data/lib/roby/control.rb +0 -746
- data/lib/roby/executives/simple.rb +0 -30
- data/lib/roby/propagation.rb +0 -562
- data/lib/roby/relations/hierarchy.rb +0 -239
- data/lib/roby/transactions/updates.rb +0 -139
- data/test/relations/test_hierarchy.rb +0 -158
- data/test/test_control.rb +0 -399
- data/test/test_propagation.rb +0 -210
@@ -0,0 +1,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
|
+
|