roby 0.7
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/.gitignore +29 -0
- data/History.txt +4 -0
- data/License-fr.txt +519 -0
- data/License.txt +515 -0
- data/Manifest.txt +245 -0
- data/NOTES +4 -0
- data/README.txt +163 -0
- data/Rakefile +161 -0
- data/TODO.txt +146 -0
- data/app/README.txt +24 -0
- data/app/Rakefile +8 -0
- data/app/config/ROBOT.rb +5 -0
- data/app/config/app.yml +91 -0
- data/app/config/init.rb +7 -0
- data/app/config/roby.yml +3 -0
- data/app/controllers/.gitattributes +0 -0
- data/app/controllers/ROBOT.rb +2 -0
- data/app/data/.gitattributes +0 -0
- data/app/planners/ROBOT/main.rb +6 -0
- data/app/planners/main.rb +5 -0
- data/app/scripts/distributed +3 -0
- data/app/scripts/generate/bookmarks +3 -0
- data/app/scripts/replay +3 -0
- data/app/scripts/results +3 -0
- data/app/scripts/run +3 -0
- data/app/scripts/server +3 -0
- data/app/scripts/shell +3 -0
- data/app/scripts/test +3 -0
- data/app/tasks/.gitattributes +0 -0
- data/app/tasks/ROBOT/.gitattributes +0 -0
- data/bin/roby +210 -0
- data/bin/roby-log +168 -0
- data/bin/roby-shell +25 -0
- data/doc/images/event_generalization.png +0 -0
- data/doc/images/exception_propagation_1.png +0 -0
- data/doc/images/exception_propagation_2.png +0 -0
- data/doc/images/exception_propagation_3.png +0 -0
- data/doc/images/exception_propagation_4.png +0 -0
- data/doc/images/exception_propagation_5.png +0 -0
- data/doc/images/replay_handler_error.png +0 -0
- data/doc/images/replay_handler_error_0.png +0 -0
- data/doc/images/replay_handler_error_1.png +0 -0
- data/doc/images/roby_cycle_overview.png +0 -0
- data/doc/images/roby_replay_02.png +0 -0
- data/doc/images/roby_replay_03.png +0 -0
- data/doc/images/roby_replay_04.png +0 -0
- data/doc/images/roby_replay_event_representation.png +0 -0
- data/doc/images/roby_replay_first_state.png +0 -0
- data/doc/images/roby_replay_relations.png +0 -0
- data/doc/images/roby_replay_startup.png +0 -0
- data/doc/images/task_event_generalization.png +0 -0
- data/doc/papers.rdoc +11 -0
- data/doc/styles/allison.css +314 -0
- data/doc/styles/allison.js +316 -0
- data/doc/styles/allison.rb +276 -0
- data/doc/styles/jamis.rb +593 -0
- data/doc/tutorials/01-GettingStarted.rdoc +86 -0
- data/doc/tutorials/02-GoForward.rdoc +220 -0
- data/doc/tutorials/03-PlannedPath.rdoc +268 -0
- data/doc/tutorials/04-EventPropagation.rdoc +236 -0
- data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
- data/doc/tutorials/06-Overview.rdoc +40 -0
- data/doc/videos.rdoc +69 -0
- data/ext/droby/dump.cc +175 -0
- data/ext/droby/extconf.rb +3 -0
- data/ext/graph/algorithm.cc +746 -0
- data/ext/graph/extconf.rb +7 -0
- data/ext/graph/graph.cc +529 -0
- data/ext/graph/graph.hh +183 -0
- data/ext/graph/iterator_sequence.hh +102 -0
- data/ext/graph/undirected_dfs.hh +226 -0
- data/ext/graph/undirected_graph.hh +421 -0
- data/lib/roby.rb +41 -0
- data/lib/roby/app.rb +870 -0
- data/lib/roby/app/rake.rb +56 -0
- data/lib/roby/app/run.rb +14 -0
- data/lib/roby/app/scripts/distributed.rb +13 -0
- data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
- data/lib/roby/app/scripts/replay.rb +31 -0
- data/lib/roby/app/scripts/results.rb +15 -0
- data/lib/roby/app/scripts/run.rb +26 -0
- data/lib/roby/app/scripts/server.rb +18 -0
- data/lib/roby/app/scripts/shell.rb +88 -0
- data/lib/roby/app/scripts/test.rb +40 -0
- data/lib/roby/basic_object.rb +151 -0
- data/lib/roby/config.rb +5 -0
- data/lib/roby/control.rb +747 -0
- data/lib/roby/decision_control.rb +17 -0
- data/lib/roby/distributed.rb +32 -0
- data/lib/roby/distributed/base.rb +440 -0
- data/lib/roby/distributed/communication.rb +871 -0
- data/lib/roby/distributed/connection_space.rb +592 -0
- data/lib/roby/distributed/distributed_object.rb +206 -0
- data/lib/roby/distributed/drb.rb +62 -0
- data/lib/roby/distributed/notifications.rb +539 -0
- data/lib/roby/distributed/peer.rb +550 -0
- data/lib/roby/distributed/protocol.rb +529 -0
- data/lib/roby/distributed/proxy.rb +343 -0
- data/lib/roby/distributed/subscription.rb +311 -0
- data/lib/roby/distributed/transaction.rb +498 -0
- data/lib/roby/event.rb +897 -0
- data/lib/roby/exceptions.rb +234 -0
- data/lib/roby/executives/simple.rb +30 -0
- data/lib/roby/graph.rb +166 -0
- data/lib/roby/interface.rb +390 -0
- data/lib/roby/log.rb +3 -0
- data/lib/roby/log/chronicle.rb +303 -0
- data/lib/roby/log/console.rb +72 -0
- data/lib/roby/log/data_stream.rb +197 -0
- data/lib/roby/log/dot.rb +279 -0
- data/lib/roby/log/event_stream.rb +151 -0
- data/lib/roby/log/file.rb +340 -0
- data/lib/roby/log/gui/basic_display.ui +83 -0
- data/lib/roby/log/gui/chronicle.rb +26 -0
- data/lib/roby/log/gui/chronicle_view.rb +40 -0
- data/lib/roby/log/gui/chronicle_view.ui +70 -0
- data/lib/roby/log/gui/data_displays.rb +172 -0
- data/lib/roby/log/gui/data_displays.ui +155 -0
- data/lib/roby/log/gui/notifications.rb +26 -0
- data/lib/roby/log/gui/relations.rb +248 -0
- data/lib/roby/log/gui/relations.ui +123 -0
- data/lib/roby/log/gui/relations_view.rb +185 -0
- data/lib/roby/log/gui/relations_view.ui +149 -0
- data/lib/roby/log/gui/replay.rb +327 -0
- data/lib/roby/log/gui/replay_controls.rb +200 -0
- data/lib/roby/log/gui/replay_controls.ui +259 -0
- data/lib/roby/log/gui/runtime.rb +130 -0
- data/lib/roby/log/hooks.rb +185 -0
- data/lib/roby/log/logger.rb +202 -0
- data/lib/roby/log/notifications.rb +244 -0
- data/lib/roby/log/plan_rebuilder.rb +470 -0
- data/lib/roby/log/relations.rb +1056 -0
- data/lib/roby/log/server.rb +550 -0
- data/lib/roby/log/sqlite.rb +47 -0
- data/lib/roby/log/timings.rb +164 -0
- data/lib/roby/plan-object.rb +247 -0
- data/lib/roby/plan.rb +762 -0
- data/lib/roby/planning.rb +13 -0
- data/lib/roby/planning/loops.rb +302 -0
- data/lib/roby/planning/model.rb +906 -0
- data/lib/roby/planning/task.rb +151 -0
- data/lib/roby/propagation.rb +562 -0
- data/lib/roby/query.rb +619 -0
- data/lib/roby/relations.rb +583 -0
- data/lib/roby/relations/conflicts.rb +70 -0
- data/lib/roby/relations/ensured.rb +20 -0
- data/lib/roby/relations/error_handling.rb +23 -0
- data/lib/roby/relations/events.rb +9 -0
- data/lib/roby/relations/executed_by.rb +193 -0
- data/lib/roby/relations/hierarchy.rb +239 -0
- data/lib/roby/relations/influence.rb +10 -0
- data/lib/roby/relations/planned_by.rb +63 -0
- data/lib/roby/robot.rb +7 -0
- data/lib/roby/standard_errors.rb +218 -0
- data/lib/roby/state.rb +5 -0
- data/lib/roby/state/events.rb +221 -0
- data/lib/roby/state/information.rb +55 -0
- data/lib/roby/state/pos.rb +110 -0
- data/lib/roby/state/shapes.rb +32 -0
- data/lib/roby/state/state.rb +353 -0
- data/lib/roby/support.rb +92 -0
- data/lib/roby/task-operations.rb +182 -0
- data/lib/roby/task.rb +1618 -0
- data/lib/roby/test/common.rb +399 -0
- data/lib/roby/test/distributed.rb +214 -0
- data/lib/roby/test/tasks/empty_task.rb +9 -0
- data/lib/roby/test/tasks/goto.rb +36 -0
- data/lib/roby/test/tasks/simple_task.rb +23 -0
- data/lib/roby/test/testcase.rb +519 -0
- data/lib/roby/test/tools.rb +160 -0
- data/lib/roby/thread_task.rb +87 -0
- data/lib/roby/transactions.rb +462 -0
- data/lib/roby/transactions/proxy.rb +292 -0
- data/lib/roby/transactions/updates.rb +139 -0
- data/plugins/fault_injection/History.txt +4 -0
- data/plugins/fault_injection/README.txt +37 -0
- data/plugins/fault_injection/Rakefile +18 -0
- data/plugins/fault_injection/TODO.txt +0 -0
- data/plugins/fault_injection/app.rb +52 -0
- data/plugins/fault_injection/fault_injection.rb +89 -0
- data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
- data/plugins/subsystems/README.txt +40 -0
- data/plugins/subsystems/Rakefile +18 -0
- data/plugins/subsystems/app.rb +171 -0
- data/plugins/subsystems/test/app/README +24 -0
- data/plugins/subsystems/test/app/Rakefile +8 -0
- data/plugins/subsystems/test/app/config/app.yml +71 -0
- data/plugins/subsystems/test/app/config/init.rb +9 -0
- data/plugins/subsystems/test/app/config/roby.yml +3 -0
- data/plugins/subsystems/test/app/planners/main.rb +20 -0
- data/plugins/subsystems/test/app/scripts/distributed +3 -0
- data/plugins/subsystems/test/app/scripts/replay +3 -0
- data/plugins/subsystems/test/app/scripts/results +3 -0
- data/plugins/subsystems/test/app/scripts/run +3 -0
- data/plugins/subsystems/test/app/scripts/server +3 -0
- data/plugins/subsystems/test/app/scripts/shell +3 -0
- data/plugins/subsystems/test/app/scripts/test +3 -0
- data/plugins/subsystems/test/app/tasks/services.rb +15 -0
- data/plugins/subsystems/test/test_subsystems.rb +71 -0
- data/test/distributed/test_communication.rb +178 -0
- data/test/distributed/test_connection.rb +282 -0
- data/test/distributed/test_execution.rb +373 -0
- data/test/distributed/test_mixed_plan.rb +341 -0
- data/test/distributed/test_plan_notifications.rb +238 -0
- data/test/distributed/test_protocol.rb +516 -0
- data/test/distributed/test_query.rb +102 -0
- data/test/distributed/test_remote_plan.rb +491 -0
- data/test/distributed/test_transaction.rb +463 -0
- data/test/mockups/tasks.rb +27 -0
- data/test/planning/test_loops.rb +380 -0
- data/test/planning/test_model.rb +427 -0
- data/test/planning/test_task.rb +106 -0
- data/test/relations/test_conflicts.rb +42 -0
- data/test/relations/test_ensured.rb +38 -0
- data/test/relations/test_executed_by.rb +149 -0
- data/test/relations/test_hierarchy.rb +158 -0
- data/test/relations/test_planned_by.rb +54 -0
- data/test/suite_core.rb +24 -0
- data/test/suite_distributed.rb +9 -0
- data/test/suite_planning.rb +3 -0
- data/test/suite_relations.rb +8 -0
- data/test/test_bgl.rb +508 -0
- data/test/test_control.rb +399 -0
- data/test/test_event.rb +894 -0
- data/test/test_exceptions.rb +592 -0
- data/test/test_interface.rb +37 -0
- data/test/test_log.rb +114 -0
- data/test/test_log_server.rb +132 -0
- data/test/test_plan.rb +584 -0
- data/test/test_propagation.rb +210 -0
- data/test/test_query.rb +266 -0
- data/test/test_relations.rb +180 -0
- data/test/test_state.rb +414 -0
- data/test/test_support.rb +16 -0
- data/test/test_task.rb +938 -0
- data/test/test_testcase.rb +122 -0
- data/test/test_thread_task.rb +73 -0
- data/test/test_transactions.rb +569 -0
- data/test/test_transactions_proxy.rb +198 -0
- metadata +570 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
rdoc_base_dir = Dir.pwd
|
|
2
|
+
if ENV['ROBY_ROOT_DIR']
|
|
3
|
+
$LOAD_PATH << File.join(ENV['ROBY_ROOT_DIR'], 'lib')
|
|
4
|
+
rdoc_base_dir = ENV['ROBY_ROOT_DIR']
|
|
5
|
+
end
|
|
6
|
+
require 'rake/rdoctask'
|
|
7
|
+
require 'roby/app/rake'
|
|
8
|
+
|
|
9
|
+
Rake::RDocTask.new("docs") do |rdoc|
|
|
10
|
+
rdoc.options << "--inline-source" << "--accessor" << "attribute" << "--accessor" << "attr_predicate"
|
|
11
|
+
rdoc.rdoc_dir = "#{rdoc_base_dir}/doc/rdoc/plugins/fault_injection"
|
|
12
|
+
rdoc.title = "Fault injection plugin for Roby"
|
|
13
|
+
rdoc.template = Roby::Rake.rdoc_template
|
|
14
|
+
rdoc.options << '--main' << 'README.txt'
|
|
15
|
+
rdoc.rdoc_files.include('README.txt', 'TODO.txt', 'History.txt')
|
|
16
|
+
rdoc.rdoc_files.include('fault_injection.rb', 'app.rb')
|
|
17
|
+
end
|
|
18
|
+
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
|
|
2
|
+
module Roby
|
|
3
|
+
# Fault injection plugin for Roby
|
|
4
|
+
#
|
|
5
|
+
# Roby::FaultInjection::Application#fault_models defines a set of
|
|
6
|
+
# probabilistic models of emission for task events. It maps task model
|
|
7
|
+
# events to emission models, and during execution will randomly emit
|
|
8
|
+
# events.
|
|
9
|
+
#
|
|
10
|
+
# Fault injection applies only on tasks which are running and interruptible.
|
|
11
|
+
module FaultInjection
|
|
12
|
+
# This module gets included in Roby::Application when the plugin is activated
|
|
13
|
+
module Application
|
|
14
|
+
# The set of fault models defined for this application. More specifically,
|
|
15
|
+
# the emission model for an event +ev+ of a task model +model+ is given by
|
|
16
|
+
#
|
|
17
|
+
# emission_model = Roby.app.fault_models[model][ev]
|
|
18
|
+
#
|
|
19
|
+
# where +ev+ is a symbol.
|
|
20
|
+
attribute(:fault_models) do
|
|
21
|
+
Hash.new { |h, k| h[k] = Hash.new }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# call-seq:
|
|
25
|
+
# Roby.app.fault_model task_model, emission_model, ev1, ev2, ...
|
|
26
|
+
#
|
|
27
|
+
# Defines +emission_model+ as the emission model for the listed
|
|
28
|
+
# events of task +task_model+. +ev1+, +ev2+ are symbols.
|
|
29
|
+
#
|
|
30
|
+
# Emission models are objects which must respond to #fault?(task).
|
|
31
|
+
# If this predicate returns true for the running task +task+, then
|
|
32
|
+
# the emission of the tested event will be simulated. The emission
|
|
33
|
+
# models are tested every one second.
|
|
34
|
+
#
|
|
35
|
+
# See Roby::FaultInjection::Rate for an example.
|
|
36
|
+
def add_fault_model(task_model, *args)
|
|
37
|
+
fault_model = args.pop
|
|
38
|
+
args.each do |ev|
|
|
39
|
+
fault_models[task_model][ev.to_sym] = fault_model
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
Application.register_plugin('fault_injection', Roby::FaultInjection::Application) do
|
|
46
|
+
require 'fault_injection'
|
|
47
|
+
Roby.every(1) do
|
|
48
|
+
FaultInjection.apply(Roby.app.fault_models)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Roby
|
|
2
|
+
class Task
|
|
3
|
+
# Returns for how many seconds this task is running. Returns nil if
|
|
4
|
+
# the task is not running.
|
|
5
|
+
def lifetime
|
|
6
|
+
if running?
|
|
7
|
+
Time.now - history.first.time
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module FaultInjection
|
|
13
|
+
extend Logger::Hierarchy
|
|
14
|
+
extend Logger::Forward
|
|
15
|
+
|
|
16
|
+
# This fault model is based on a constant fault rate, defined upon the
|
|
17
|
+
# mean-time-to-failure of the task.
|
|
18
|
+
#
|
|
19
|
+
# In this model, the probability of having a fault for task t at any
|
|
20
|
+
# given instant is
|
|
21
|
+
#
|
|
22
|
+
# p(fault) = max * (1.0 - exp(- task.lifetime / mttf))
|
|
23
|
+
#
|
|
24
|
+
class Rate
|
|
25
|
+
attr_reader :mttf, :max
|
|
26
|
+
def initialize(mttf, max = 1.0)
|
|
27
|
+
@mttf, @max = mttf, max
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def fault?(task)
|
|
31
|
+
f = max * (1.0 - Math.exp(- task.lifetime / mttf))
|
|
32
|
+
rand <= f
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Apply the given fault models to the main Roby plan
|
|
37
|
+
def self.apply(fault_models)
|
|
38
|
+
injected_faults = Array.new
|
|
39
|
+
|
|
40
|
+
for model, faults in fault_models
|
|
41
|
+
for ev, p in faults
|
|
42
|
+
Roby.plan.find_tasks(model).
|
|
43
|
+
running.not_finishing.
|
|
44
|
+
interruptible.each do |task|
|
|
45
|
+
if p.fault?(task)
|
|
46
|
+
FaultInjection.info "injecting fault #{ev} on #{task}"
|
|
47
|
+
injected_faults << [task, ev, task.inject_fault(ev)]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
injected_faults
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class Task
|
|
58
|
+
# Makes the plan behave as if this task had failed by emitting the
|
|
59
|
+
# event +event+ with context +context+.
|
|
60
|
+
#
|
|
61
|
+
# The task must be running and interruptible, and +event+ must be
|
|
62
|
+
# terminal. The default implementation creates a duplicate of this task
|
|
63
|
+
# by using #dup and forwards the +stop+ event of the old task to
|
|
64
|
+
# +event+ on the new one.
|
|
65
|
+
#
|
|
66
|
+
# It returns a new task which replaced this one in the plan. This is
|
|
67
|
+
# the task on which +event+ will actually be emitted.
|
|
68
|
+
def inject_fault(event, context = nil)
|
|
69
|
+
if !running?
|
|
70
|
+
raise ArgumentError, "this task is not running"
|
|
71
|
+
elsif finishing?
|
|
72
|
+
raise ArgumentError, "this task is being stopped"
|
|
73
|
+
elsif !interruptible?
|
|
74
|
+
raise ArgumentError, "fault injection works only on interruptible tasks"
|
|
75
|
+
elsif !event(event).terminal?
|
|
76
|
+
raise ArgumentError, "the injected fault must be a terminal event"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
new_task = dup
|
|
80
|
+
old_task = self
|
|
81
|
+
|
|
82
|
+
plan.replace_task(old_task, new_task)
|
|
83
|
+
old_task.event(:stop).filter(context).forward new_task.event(event)
|
|
84
|
+
|
|
85
|
+
new_task
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path( '..', File.dirname(__FILE__))
|
|
2
|
+
require 'test/unit'
|
|
3
|
+
require 'roby'
|
|
4
|
+
require 'roby/test/common'
|
|
5
|
+
require 'roby/test/tasks/simple_task'
|
|
6
|
+
require 'fault_injection'
|
|
7
|
+
|
|
8
|
+
class TC_FaultInjection < Test::Unit::TestCase
|
|
9
|
+
include Roby::Test
|
|
10
|
+
|
|
11
|
+
def test_inject_fault
|
|
12
|
+
Roby.control.run :detach => true
|
|
13
|
+
|
|
14
|
+
task = nil
|
|
15
|
+
Roby.execute do
|
|
16
|
+
model = Class.new(Roby::Test::SimpleTask) do
|
|
17
|
+
event :specialized_fault
|
|
18
|
+
forward :specialized_fault => :failed
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
plan.permanent(task = model.new)
|
|
22
|
+
assert_raises(ArgumentError) { task.inject_fault(:start) }
|
|
23
|
+
|
|
24
|
+
task.start!
|
|
25
|
+
end
|
|
26
|
+
Roby.wait_one_cycle
|
|
27
|
+
|
|
28
|
+
fake_task = nil
|
|
29
|
+
Roby.execute do
|
|
30
|
+
assert_raises(ArgumentError) { task.inject_fault(:updated_data) }
|
|
31
|
+
assert_nothing_raised { task.inject_fault(:specialized_fault) }
|
|
32
|
+
assert_equal(2, plan.known_tasks.size)
|
|
33
|
+
fake_task = plan.known_tasks.find { |t| t != task }
|
|
34
|
+
end
|
|
35
|
+
Roby.wait_one_cycle
|
|
36
|
+
|
|
37
|
+
assert(fake_task.finished?)
|
|
38
|
+
assert(fake_task.event(:specialized_fault).happened?)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def test_apply
|
|
42
|
+
Roby.control.run :detach => true
|
|
43
|
+
|
|
44
|
+
model = Class.new(Roby::Test::SimpleTask) do
|
|
45
|
+
event :specialized_fault
|
|
46
|
+
forward :specialized_fault => :failed
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
fault_models = Hash.new { |h, k| h[k] = Hash.new }
|
|
50
|
+
fault_models[model][:specialized_fault] = FaultInjection::Rate.new(0.01, 1.0)
|
|
51
|
+
fault_models[Roby::Test::SimpleTask][:stop] = FaultInjection::Rate.new(1_000_000, 1.0)
|
|
52
|
+
|
|
53
|
+
simple, specialized = nil
|
|
54
|
+
Roby.execute do
|
|
55
|
+
plan.permanent(simple = Roby::Test::SimpleTask.new)
|
|
56
|
+
plan.permanent(specialized = model.new)
|
|
57
|
+
simple.start!
|
|
58
|
+
specialized.start!
|
|
59
|
+
end
|
|
60
|
+
Roby.wait_one_cycle
|
|
61
|
+
|
|
62
|
+
sleep(0.5)
|
|
63
|
+
fake_specialized = nil
|
|
64
|
+
Roby.execute do
|
|
65
|
+
result = Roby::FaultInjection.apply(fault_models)
|
|
66
|
+
assert_equal(1, result.size)
|
|
67
|
+
fake_specialized = result.first.last
|
|
68
|
+
end
|
|
69
|
+
Roby.wait_one_cycle
|
|
70
|
+
|
|
71
|
+
assert(simple.running?)
|
|
72
|
+
assert(specialized.finished?)
|
|
73
|
+
assert(fake_specialized.finished?)
|
|
74
|
+
assert(fake_specialized.event(:specialized_fault).happened?)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def test_task_lifetime
|
|
78
|
+
plan.discover(task = Roby::Test::SimpleTask.new)
|
|
79
|
+
task.start!
|
|
80
|
+
sleep(0.5)
|
|
81
|
+
assert(task.lifetime > 0.5)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
|
|
2
|
+
http://rubyforge.org/projects/roby
|
|
3
|
+
|
|
4
|
+
= Subsystems plugin for Roby
|
|
5
|
+
|
|
6
|
+
This is a plugin for the Roby robotic control framework. It allows to generate
|
|
7
|
+
an initial plan in which a set of common subsystems (sensors, localization,
|
|
8
|
+
...) are already initialized, easing later the writing and management of the
|
|
9
|
+
planning methods for other, more dynamical actions.
|
|
10
|
+
|
|
11
|
+
== Usage
|
|
12
|
+
|
|
13
|
+
A set of services can be defined on <tt>State.services</tt> like this:
|
|
14
|
+
|
|
15
|
+
State.services.localization = 'pom'
|
|
16
|
+
State.services.camera = 'cam'
|
|
17
|
+
|
|
18
|
+
Then, at controller initialization and before the controller file is actually
|
|
19
|
+
loaded, the corresponding planning methods are called to generate a plan in which those
|
|
20
|
+
subsystems are present:
|
|
21
|
+
|
|
22
|
+
planner.localization(:id => 'pom')
|
|
23
|
+
planner.camera(:id => 'cam')
|
|
24
|
+
|
|
25
|
+
The returned tasks are also started, in order. The controller file gets loaded
|
|
26
|
+
only when the subsystems are ready (i.e. their start events are emitted).
|
|
27
|
+
|
|
28
|
+
== Enabling the plugin
|
|
29
|
+
|
|
30
|
+
The plugin must be enabled in the configuration files (i.e. either in
|
|
31
|
+
<tt>config/init.rb</tt> or in one of the robot-specific files) through a call
|
|
32
|
+
to Roby.app.using:
|
|
33
|
+
|
|
34
|
+
Roby.app.using 'subsystems'
|
|
35
|
+
|
|
36
|
+
== License:
|
|
37
|
+
|
|
38
|
+
This work is licensed under the CeCiLL-B license, which is a free software
|
|
39
|
+
license similar to the BSD license. See License.txt for details.
|
|
40
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
rdoc_base_dir = Dir.pwd
|
|
2
|
+
if ENV['ROBY_ROOT_DIR']
|
|
3
|
+
$LOAD_PATH << File.join(ENV['ROBY_ROOT_DIR'], 'lib')
|
|
4
|
+
rdoc_base_dir = ENV['ROBY_ROOT_DIR']
|
|
5
|
+
end
|
|
6
|
+
require 'rake/rdoctask'
|
|
7
|
+
require 'roby/app/rake'
|
|
8
|
+
|
|
9
|
+
Rake::RDocTask.new("docs") do |rdoc|
|
|
10
|
+
rdoc.options << "--inline-source" << "--accessor" << "attribute" << "--accessor" << "attr_predicate"
|
|
11
|
+
rdoc.rdoc_dir = "#{rdoc_base_dir}/doc/rdoc/plugins/subsystems"
|
|
12
|
+
rdoc.title = "Subsystems plugin for Roby"
|
|
13
|
+
rdoc.template = Roby::Rake.rdoc_template
|
|
14
|
+
rdoc.options << '--main' << 'README.txt'
|
|
15
|
+
rdoc.rdoc_files.include('README.txt')
|
|
16
|
+
rdoc.rdoc_files.include('app.rb')
|
|
17
|
+
end
|
|
18
|
+
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
|
|
2
|
+
module Roby
|
|
3
|
+
# Subsystem plugin for Roby
|
|
4
|
+
#
|
|
5
|
+
# This plugin manages a set of services that should be available at all
|
|
6
|
+
# times (for instance: localization). For now, it makes Roby start them at
|
|
7
|
+
# initialization time, allowing to consider that they are always available
|
|
8
|
+
# during plan generation afterwards.
|
|
9
|
+
#
|
|
10
|
+
# == Subsystems at initialization
|
|
11
|
+
# The set of subsystems that should be considered is to be set through the State.services
|
|
12
|
+
# configuration object:
|
|
13
|
+
#
|
|
14
|
+
# State.services do |sys|
|
|
15
|
+
# sys.localization = 'pom'
|
|
16
|
+
# sys.laser_ranges = 'sick'
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# The subsystem plugin will then use <tt>MainPlanner#subsystem_name(:id =>
|
|
20
|
+
# 'subsystem_selected')</tt> to generate the subsystems' plans. In the
|
|
21
|
+
# example above, the initial plan will be made using
|
|
22
|
+
#
|
|
23
|
+
# planner.localization(:id => 'pom')
|
|
24
|
+
# planner.laser_ranges(:id => 'sick')
|
|
25
|
+
#
|
|
26
|
+
# The resulting plan is then started one subsystem after the other
|
|
27
|
+
# <b>before</b> the controller is loaded
|
|
28
|
+
#
|
|
29
|
+
# == Subsystems and state update
|
|
30
|
+
#
|
|
31
|
+
# If the main tasks representing the subsystem -- the one returned by the
|
|
32
|
+
# planning methods, respond to update_<sysname>(state), that method is
|
|
33
|
+
# called, provided that the task is running, at every cycle with the
|
|
34
|
+
# system's state representation as an argument.
|
|
35
|
+
#
|
|
36
|
+
# For instance, the Localization task which represents the localization
|
|
37
|
+
# subsystems can have a #update_localization method which takes an
|
|
38
|
+
# argument. This method would update at each cycle the system's position in
|
|
39
|
+
# the state representation.
|
|
40
|
+
module Subsystems
|
|
41
|
+
module Application
|
|
42
|
+
def self.setup_main_planner
|
|
43
|
+
MainPlanner.class_eval do
|
|
44
|
+
Roby::State.services.each_member do |name, value|
|
|
45
|
+
filter(name) do |options, method|
|
|
46
|
+
options[:id] || method.id.to_s == value.to_s
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# This method generates the initial plan built upon the set of
|
|
53
|
+
# required subsystems.
|
|
54
|
+
def self.initialize_plan
|
|
55
|
+
setup_main_planner
|
|
56
|
+
plan = Transaction.new(Roby.plan)
|
|
57
|
+
|
|
58
|
+
# Event which is emitted when all subsystems are properly
|
|
59
|
+
# initialized
|
|
60
|
+
ready = AndGenerator.new
|
|
61
|
+
|
|
62
|
+
# Create the one task for each subsystem. +ready+ is an event
|
|
63
|
+
# which is fired when all subsystems are properly initialized
|
|
64
|
+
planner = MainPlanner.new(plan)
|
|
65
|
+
|
|
66
|
+
tasks = Array.new
|
|
67
|
+
task_objects = ValueSet.new
|
|
68
|
+
State.services.each_member do |name, value|
|
|
69
|
+
next if name == 'tasks'
|
|
70
|
+
new_task = begin
|
|
71
|
+
planner.send(name)
|
|
72
|
+
rescue Planning::NotFound => e
|
|
73
|
+
raise RuntimeError, e.full_message
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
State.services.tasks.send("#{name}=", new_task)
|
|
77
|
+
|
|
78
|
+
plan.permanent(new_task)
|
|
79
|
+
started_with = if new_task.has_event?(:ready) then :ready
|
|
80
|
+
else :start
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
ready << new_task.event(started_with)
|
|
84
|
+
|
|
85
|
+
new_task.on(started_with) do
|
|
86
|
+
Robot.info "#{name} subsystem started (#{value})"
|
|
87
|
+
end
|
|
88
|
+
new_task.on(:stop) do
|
|
89
|
+
Robot.info "#{name} subsystem stopped (#{value})"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
tasks << [name, value, new_task]
|
|
93
|
+
task_objects << new_task
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# We make sure each subsystem is started only when all the
|
|
97
|
+
# other subsystems it depends on are started and ready.
|
|
98
|
+
# +start_with+ is the event which should be called to start
|
|
99
|
+
# everything
|
|
100
|
+
starting_point = EventGenerator.new(true)
|
|
101
|
+
tasks.each do |name, type, task|
|
|
102
|
+
children = task.generated_subgraph(TaskStructure::Hierarchy) & task_objects
|
|
103
|
+
children.delete(task)
|
|
104
|
+
|
|
105
|
+
ev = if children.empty?
|
|
106
|
+
starting_point
|
|
107
|
+
else
|
|
108
|
+
children.inject(AndGenerator.new) do |ev, child|
|
|
109
|
+
started_with = if child.has_event?(:ready) then :ready
|
|
110
|
+
else :start
|
|
111
|
+
end
|
|
112
|
+
ev << child.event(started_with)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
ev.on { Robot.info "starting subsystem #{name} (#{type})" }
|
|
117
|
+
ev.on task.event(:start)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
Roby.execute do
|
|
121
|
+
plan.commit_transaction
|
|
122
|
+
end
|
|
123
|
+
[starting_point, ready]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Hook to generate the initial plan and start subsystems when the
|
|
127
|
+
# application starts.
|
|
128
|
+
def self.run(config, &block)
|
|
129
|
+
unless Roby::State.services? && !Roby::State.services.empty?
|
|
130
|
+
Robot.info "No subsystems defined"
|
|
131
|
+
return yield
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
Robot.info "Starting subsystems ..."
|
|
135
|
+
|
|
136
|
+
starting_point, ready = initialize_plan
|
|
137
|
+
# Start the deepest tasks. The signalling order will do the rest.
|
|
138
|
+
# The 'ready' event is emitted when all the subsystem tasks are
|
|
139
|
+
Roby.wait_until(ready) do
|
|
140
|
+
Roby.execute do
|
|
141
|
+
starting_point.call
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
yield
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
Application.register_plugin('subsystems', Roby::Subsystems::Application) do
|
|
151
|
+
# Call the #update_ methods for the subsystems tasks which define it.
|
|
152
|
+
Roby.each_cycle do
|
|
153
|
+
srv = Roby::State.services
|
|
154
|
+
srv.each_member do |name, value|
|
|
155
|
+
task = srv.tasks.send(name)
|
|
156
|
+
next unless task.running?
|
|
157
|
+
if task.has_event?(:ready)
|
|
158
|
+
next unless task.event(:ready).happened?
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
if task.respond_to?("update_#{name}")
|
|
162
|
+
begin
|
|
163
|
+
task.send("update_#{name}", State)
|
|
164
|
+
rescue Exception => e
|
|
165
|
+
Roby.warn "update_#{name} failed on #{task} with #{e}"
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|