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

|
|
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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
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
|
+

|
|
384
|
+
{.fullfigure}
|
|
385
|
+
|
|
386
|
+
**Nominal execution**: the plan repair (in blue) is not executed yet and the ExecutePath is running
|
|
387
|
+
{.figurecaption}
|
|
388
|
+
|
|
389
|
+

|
|
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
|
+

|
|
396
|
+
{.fullfigure}
|
|
397
|
+
|
|
398
|
+
**Repair started**
|
|
399
|
+
{.figurecaption}
|
|
400
|
+
|
|
401
|
+

|
|
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
|
+
|