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
data/lib/roby/app.rb
ADDED
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
require 'roby'
|
|
2
|
+
require 'roby/distributed'
|
|
3
|
+
require 'roby/planning'
|
|
4
|
+
require 'roby/log'
|
|
5
|
+
require 'roby/log/event_stream'
|
|
6
|
+
|
|
7
|
+
require 'roby/robot'
|
|
8
|
+
require 'yaml'
|
|
9
|
+
|
|
10
|
+
module Roby
|
|
11
|
+
# Returns the only one Application object
|
|
12
|
+
def self.app; Application.instance end
|
|
13
|
+
|
|
14
|
+
# = Roby Applications
|
|
15
|
+
#
|
|
16
|
+
# There is one and only one Application object, which holds mainly the
|
|
17
|
+
# system-wide configuration and takes care of file loading and system-wide
|
|
18
|
+
# setup (#setup). A Roby application can be started in multiple modes. The
|
|
19
|
+
# first and most important mode is the runtime mode
|
|
20
|
+
# (<tt>scripts/run</tt>). Other modes are the testing mode (#testing?
|
|
21
|
+
# returns true, entered through <tt>scripts/test</tt>) and the shell mode
|
|
22
|
+
# (#shell? returns true, entered through <tt>scripts/shell</tt>). Usually,
|
|
23
|
+
# user code does not have to take the modes into account, but it is
|
|
24
|
+
# sometime useful.
|
|
25
|
+
#
|
|
26
|
+
# Finally, in both testing and runtime mode, the code can be started in
|
|
27
|
+
# simulation or live setups (see #simulation?). Specific plugins can for
|
|
28
|
+
# instance start and set up a simulation system in simulation mode, and as
|
|
29
|
+
# well set up some simulation-specific configuration for the functional
|
|
30
|
+
# layer of the architecture.
|
|
31
|
+
#
|
|
32
|
+
# == Configuration files
|
|
33
|
+
#
|
|
34
|
+
# In all modes, a specific set of configuration files are loaded. The
|
|
35
|
+
# files that are actually loaded are defined by the robot name and type, as
|
|
36
|
+
# specified to #robot. The loaded files are, in order, the following:
|
|
37
|
+
# [config/app.yml]
|
|
38
|
+
# the application configuration as a YAML file. See the comments in that
|
|
39
|
+
# file for more details.
|
|
40
|
+
# [config/init.rb]
|
|
41
|
+
# Ruby code for the common configuration of all robots
|
|
42
|
+
# [config/ROBOT_NAME.rb or config/ROBOT_TYPE.rb]
|
|
43
|
+
# Ruby code for the configuration of either all robots of the same type,
|
|
44
|
+
# or a specific robot. It is one or the other. If a given robot needs to
|
|
45
|
+
# inherit the configuration of its type, explicitely require the
|
|
46
|
+
# ROBOT_TYPE.rb file in config/ROBOT_NAME.rb.
|
|
47
|
+
#
|
|
48
|
+
# == Runtime mode (<tt>scripts/run</tt>)
|
|
49
|
+
# Then, in runtime mode the robot controller
|
|
50
|
+
# <tt>controller/ROBOT_NAME.rb</tt> or <tt>controller/ROBOT_TYPE.rb</tt> is
|
|
51
|
+
# loaded. The same rules than for the configuration file
|
|
52
|
+
# <tt>config/ROBOT_NAME.rb</tt> apply.
|
|
53
|
+
#
|
|
54
|
+
# == Testing mode (<tt>scripts/test</tt>)
|
|
55
|
+
# This mode is used to run test suites in the +test+ directory. See
|
|
56
|
+
# Roby::Test::TestCase for a description of Roby-specific tests.
|
|
57
|
+
class Application
|
|
58
|
+
include Singleton
|
|
59
|
+
|
|
60
|
+
# The plain option hash saved in config/app.yml
|
|
61
|
+
attr_reader :options
|
|
62
|
+
|
|
63
|
+
# Logging options.
|
|
64
|
+
# events:: save a log of all events in the system. This log can be read using scripts/replay
|
|
65
|
+
# If this value is 'stats', only the data necessary for timing statistics is saved.
|
|
66
|
+
# levels:: a component => level hash of the minimum level of the messages that
|
|
67
|
+
# should be displayed on the console. The levels are DEBUG, INFO, WARN and FATAL.
|
|
68
|
+
# Roby: FATAL
|
|
69
|
+
# Roby::Distributed: INFO
|
|
70
|
+
# dir:: the log directory. Uses APP_DIR/log if not set
|
|
71
|
+
# filter_backtraces:: true if the framework code should be removed from the error backtraces
|
|
72
|
+
attr_reader :log
|
|
73
|
+
|
|
74
|
+
# A [name, dir, file, module] array of available plugins, where 'name'
|
|
75
|
+
# is the plugin name, 'dir' the directory in which it is installed,
|
|
76
|
+
# 'file' the file which should be required to load the plugin and
|
|
77
|
+
# 'module' the Application-compatible module for configuration of the
|
|
78
|
+
# plug-in
|
|
79
|
+
attr_reader :available_plugins
|
|
80
|
+
# An [name, module] array of the loaded plugins
|
|
81
|
+
attr_reader :plugins
|
|
82
|
+
|
|
83
|
+
# The discovery options in multi-robot mode
|
|
84
|
+
attr_reader :discovery
|
|
85
|
+
# The robot's dRoby options
|
|
86
|
+
# period:: the period of neighbour discovery
|
|
87
|
+
# max_errors:: disconnect from a peer if there is more than +max_errors+ consecutive errors
|
|
88
|
+
# detected
|
|
89
|
+
attr_reader :droby
|
|
90
|
+
|
|
91
|
+
# Configuration of the control loop
|
|
92
|
+
# abort_on_exception:: if the control loop should abort if an uncaught task or event exception is received. Defaults
|
|
93
|
+
# to false
|
|
94
|
+
# abort_on_application_exception:: if the control should abort if an uncaught application exception (not originating
|
|
95
|
+
# from a task or event) is caught. Defaults to true.
|
|
96
|
+
attr_reader :control
|
|
97
|
+
|
|
98
|
+
# An array of directories in which to search for plugins
|
|
99
|
+
attr_reader :plugin_dirs
|
|
100
|
+
|
|
101
|
+
# True if user interaction is disabled during tests
|
|
102
|
+
attr_predicate :automatic_testing?, true
|
|
103
|
+
|
|
104
|
+
# True if all logs should be kept after testing
|
|
105
|
+
attr_predicate :testing_keep_logs?, true
|
|
106
|
+
|
|
107
|
+
# True if all logs should be kept after testing
|
|
108
|
+
attr_predicate :testing_overwrites_logs?, true
|
|
109
|
+
|
|
110
|
+
# True if we should remove the framework code from the error backtraces
|
|
111
|
+
def filter_backtraces?; log['filter_backtraces'] end
|
|
112
|
+
def filter_backtraces=(value); log['filter_backtraces'] = value end
|
|
113
|
+
|
|
114
|
+
def initialize
|
|
115
|
+
@plugins = Array.new
|
|
116
|
+
@available_plugins = Array.new
|
|
117
|
+
@log = Hash['events' => 'stats', 'levels' => Hash.new, 'filter_backtraces' => true]
|
|
118
|
+
@discovery = Hash.new
|
|
119
|
+
@droby = Hash['period' => 0.5, 'max_errors' => 1]
|
|
120
|
+
@control = Hash[ 'abort_on_exception' => false,
|
|
121
|
+
'abort_on_application_exception' => true ]
|
|
122
|
+
|
|
123
|
+
@automatic_testing = true
|
|
124
|
+
@testing_keep_logs = false
|
|
125
|
+
|
|
126
|
+
@plugin_dirs = []
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Adds +dir+ in the list of directories searched for plugins
|
|
130
|
+
def plugin_dir(dir)
|
|
131
|
+
dir = File.expand_path(dir)
|
|
132
|
+
@plugin_dirs << dir
|
|
133
|
+
$LOAD_PATH.unshift File.expand_path(dir)
|
|
134
|
+
|
|
135
|
+
Dir.new(dir).each do |subdir|
|
|
136
|
+
subdir = File.join(dir, subdir)
|
|
137
|
+
next unless File.directory?(subdir)
|
|
138
|
+
appfile = File.join(subdir, "app.rb")
|
|
139
|
+
next unless File.file?(appfile)
|
|
140
|
+
|
|
141
|
+
begin
|
|
142
|
+
require appfile
|
|
143
|
+
rescue
|
|
144
|
+
Roby.warn "cannot load plugin in #{subdir}: #{$!.full_message}\n"
|
|
145
|
+
end
|
|
146
|
+
Roby.info "loaded plugin in #{subdir}"
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Returns true if +name+ is a loaded plugin
|
|
151
|
+
def loaded_plugin?(name)
|
|
152
|
+
plugins.any? { |plugname, _| plugname == name }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Returns the [name, dir, file, module] array definition of the plugin
|
|
156
|
+
# +name+, or nil if +name+ is not a known plugin
|
|
157
|
+
def plugin_definition(name)
|
|
158
|
+
available_plugins.find { |plugname, *_| plugname == name }
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# True if +name+ is a plugin known to us
|
|
162
|
+
def defined_plugin?(name)
|
|
163
|
+
available_plugins.any? { |plugname, *_| plugname == name }
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def each_plugin(on_available = false)
|
|
167
|
+
plugins = self.plugins
|
|
168
|
+
if on_available
|
|
169
|
+
plugins = available_plugins.map { |name, _, mod, _| [name, mod] }
|
|
170
|
+
end
|
|
171
|
+
plugins.each do |_, mod|
|
|
172
|
+
yield(mod)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Yields each extension modules that respond to +method+
|
|
177
|
+
def each_responding_plugin(method, on_available = false)
|
|
178
|
+
each_plugin do |mod|
|
|
179
|
+
yield(mod) if mod.respond_to?(method)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Call +method+ on each loaded extension module which define it, with
|
|
184
|
+
# arguments +args+
|
|
185
|
+
def call_plugins(method, *args)
|
|
186
|
+
each_responding_plugin(method) do |config_extension|
|
|
187
|
+
config_extension.send(method, *args)
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Load configuration from the given option hash
|
|
192
|
+
def load_yaml(options)
|
|
193
|
+
options = options.dup
|
|
194
|
+
|
|
195
|
+
if robot_name && (robot_config = options['robots'])
|
|
196
|
+
if robot_config = robot_config[robot_name]
|
|
197
|
+
robot_config.each do |section, values|
|
|
198
|
+
if options[section]
|
|
199
|
+
options[section].merge! values
|
|
200
|
+
else
|
|
201
|
+
options[section] = values
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
options.delete('robots')
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
@options = options
|
|
209
|
+
|
|
210
|
+
load_option_hashes(options, %w{log control discovery droby})
|
|
211
|
+
call_plugins(:load, self, options)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def load_option_hashes(options, names)
|
|
215
|
+
names.each do |optname|
|
|
216
|
+
if options[optname]
|
|
217
|
+
send(optname).merge! options[optname]
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Loads the plugins whose name are listed in +names+
|
|
223
|
+
def using(*names)
|
|
224
|
+
names.each do |name|
|
|
225
|
+
name = name.to_s
|
|
226
|
+
unless plugin = plugin_definition(name)
|
|
227
|
+
raise ArgumentError, "#{name} is not a known plugin (#{available_plugins.map { |n, *_| n }.join(", ")})"
|
|
228
|
+
end
|
|
229
|
+
name, dir, mod, init = *plugin
|
|
230
|
+
if plugins.find { |n, m| n == name && m == mod }
|
|
231
|
+
next
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
if init
|
|
235
|
+
begin
|
|
236
|
+
$LOAD_PATH.unshift dir
|
|
237
|
+
init.call
|
|
238
|
+
mod.reset(self) if mod.respond_to?(:reset)
|
|
239
|
+
rescue Exception => e
|
|
240
|
+
Roby.fatal "cannot load plugin #{name}: #{e.full_message}"
|
|
241
|
+
exit(1)
|
|
242
|
+
ensure
|
|
243
|
+
$LOAD_PATH.shift
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
plugins << [name, mod]
|
|
248
|
+
extend mod
|
|
249
|
+
# If +load+ has already been called, call it on the module
|
|
250
|
+
if mod.respond_to?(:load) && options
|
|
251
|
+
mod.load(self, options)
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def reset
|
|
257
|
+
if defined? State
|
|
258
|
+
State.clear
|
|
259
|
+
else
|
|
260
|
+
Roby.const_set(:State, StateSpace.new)
|
|
261
|
+
end
|
|
262
|
+
call_plugins(:reset, self)
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# The robot name
|
|
266
|
+
attr_reader :robot_name
|
|
267
|
+
# The robot type
|
|
268
|
+
attr_reader :robot_type
|
|
269
|
+
# Sets up the name and type of the robot. This can be called only once
|
|
270
|
+
# in a given Roby controller.
|
|
271
|
+
def robot(name, type = name)
|
|
272
|
+
if @robot_name
|
|
273
|
+
if name != @robot_name && type != @robot_type
|
|
274
|
+
raise ArgumentError, "the robot is already set to #{name}, of type #{type}"
|
|
275
|
+
end
|
|
276
|
+
return
|
|
277
|
+
end
|
|
278
|
+
@robot_name = name
|
|
279
|
+
@robot_type = type
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# The directory in which logs are to be saved
|
|
283
|
+
# Defaults to APP_DIR/log
|
|
284
|
+
def log_dir
|
|
285
|
+
File.expand_path(log['dir'] || 'log', APP_DIR)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# A path => File hash, to re-use the same file object for different
|
|
289
|
+
# logs
|
|
290
|
+
attribute(:log_files) { Hash.new }
|
|
291
|
+
|
|
292
|
+
# The directory in which results should be saved
|
|
293
|
+
# Defaults to APP_DIR/results
|
|
294
|
+
def results_dir
|
|
295
|
+
File.expand_path(log['results'] || 'results', APP_DIR)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Returns a unique directory name as a subdirectory of
|
|
299
|
+
# +base_dir+, based on +path_spec+. The generated name
|
|
300
|
+
# is of the form
|
|
301
|
+
# <base_dir>/a/b/c/YYYYMMDD-basename
|
|
302
|
+
# if <tt>path_spec = "a/b/c/basename"</tt>. A .<number> suffix
|
|
303
|
+
# is appended if the path already exists.
|
|
304
|
+
def self.unique_dirname(base_dir, path_spec)
|
|
305
|
+
if path_spec =~ /\/$/
|
|
306
|
+
basename = ""
|
|
307
|
+
dirname = path_spec
|
|
308
|
+
else
|
|
309
|
+
basename = File.basename(path_spec)
|
|
310
|
+
dirname = File.dirname(path_spec)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
date = Date.today
|
|
314
|
+
date = "%i%02i%02i" % [date.year, date.month, date.mday]
|
|
315
|
+
if basename && !basename.empty?
|
|
316
|
+
basename = date + "-" + basename
|
|
317
|
+
else
|
|
318
|
+
basename = date
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# Check if +basename+ already exists, and if it is the case add a
|
|
322
|
+
# .x suffix to it
|
|
323
|
+
full_path = File.expand_path(File.join(dirname, basename), base_dir)
|
|
324
|
+
base_dir = File.dirname(full_path)
|
|
325
|
+
|
|
326
|
+
unless File.exists?(base_dir)
|
|
327
|
+
FileUtils.mkdir_p(base_dir)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
final_path, i = full_path, 0
|
|
331
|
+
while File.exists?(final_path)
|
|
332
|
+
i += 1
|
|
333
|
+
final_path = full_path + ".#{i}"
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
final_path
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Sets up all the default loggers. It creates the logger for the Robot
|
|
340
|
+
# module (accessible through Robot.logger), and sets up log levels as
|
|
341
|
+
# specified in the <tt>config/app.yml</tt> file.
|
|
342
|
+
def setup_loggers
|
|
343
|
+
# Create the robot namespace
|
|
344
|
+
STDOUT.sync = true
|
|
345
|
+
Robot.logger = Logger.new(STDOUT)
|
|
346
|
+
Robot.logger.level = Logger::INFO
|
|
347
|
+
Robot.logger.formatter = Roby.logger.formatter
|
|
348
|
+
Robot.logger.progname = robot_name
|
|
349
|
+
|
|
350
|
+
# Set up log levels
|
|
351
|
+
log['levels'].each do |name, value|
|
|
352
|
+
name = name.camelize
|
|
353
|
+
if value =~ /^(\w+):(.+)$/
|
|
354
|
+
level, file = $1, $2
|
|
355
|
+
level = Logger.const_get(level)
|
|
356
|
+
file = file.gsub('ROBOT', robot_name) if robot_name
|
|
357
|
+
else
|
|
358
|
+
level = Logger.const_get(value)
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
new_logger = if file
|
|
362
|
+
path = File.expand_path(file, log_dir)
|
|
363
|
+
io = (log_files[path] ||= File.open(path, 'w'))
|
|
364
|
+
Logger.new(io)
|
|
365
|
+
else Logger.new(STDOUT)
|
|
366
|
+
end
|
|
367
|
+
new_logger.level = level
|
|
368
|
+
new_logger.formatter = Roby.logger.formatter
|
|
369
|
+
|
|
370
|
+
if (mod = name.constantize rescue nil)
|
|
371
|
+
if robot_name
|
|
372
|
+
new_logger.progname = "#{name} #{robot_name}"
|
|
373
|
+
else
|
|
374
|
+
new_logger.progname = name
|
|
375
|
+
end
|
|
376
|
+
mod.logger = new_logger
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def setup_dirs
|
|
382
|
+
Dir.mkdir(log_dir) unless File.exists?(log_dir)
|
|
383
|
+
if File.directory?(libdir = File.join(APP_DIR, 'lib'))
|
|
384
|
+
if !$LOAD_PATH.include?(libdir)
|
|
385
|
+
$LOAD_PATH.unshift File.join(APP_DIR, 'lib')
|
|
386
|
+
end
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
Roby::State.datadirs = []
|
|
390
|
+
datadir = File.join(APP_DIR, "data")
|
|
391
|
+
if File.directory?(datadir)
|
|
392
|
+
Roby::State.datadirs << datadir
|
|
393
|
+
end
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Loads the models, based on the given robot name and robot type
|
|
397
|
+
def require_models
|
|
398
|
+
# Require all common task models and the task models specific to
|
|
399
|
+
# this robot
|
|
400
|
+
require_dir(File.join(APP_DIR, 'tasks'))
|
|
401
|
+
require_robotdir(File.join(APP_DIR, 'tasks', 'ROBOT'))
|
|
402
|
+
|
|
403
|
+
# Load robot-specific configuration
|
|
404
|
+
planner_dir = File.join(APP_DIR, 'planners')
|
|
405
|
+
models_search = [planner_dir]
|
|
406
|
+
if robot_name
|
|
407
|
+
load_robotfile(File.join(APP_DIR, 'config', "ROBOT.rb"))
|
|
408
|
+
|
|
409
|
+
models_search << File.join(planner_dir, robot_name) << File.join(planner_dir, robot_type)
|
|
410
|
+
if !require_robotfile(File.join(APP_DIR, 'planners', 'ROBOT', 'main.rb'))
|
|
411
|
+
require File.join(APP_DIR, "planners", "main")
|
|
412
|
+
end
|
|
413
|
+
else
|
|
414
|
+
require File.join(APP_DIR, "planners", "main")
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Load the other planners
|
|
418
|
+
models_search.each do |base_dir|
|
|
419
|
+
next unless File.directory?(base_dir)
|
|
420
|
+
Dir.new(base_dir).each do |file|
|
|
421
|
+
if File.file?(file) && file =~ /\.rb$/ && file !~ 'main\.rb$'
|
|
422
|
+
require file
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def setup
|
|
429
|
+
reset
|
|
430
|
+
|
|
431
|
+
$LOAD_PATH.unshift(APP_DIR) unless $LOAD_PATH.include?(APP_DIR)
|
|
432
|
+
|
|
433
|
+
# Get the application-wide configuration
|
|
434
|
+
file = File.join(APP_DIR, 'config', 'app.yml')
|
|
435
|
+
file = YAML.load(File.open(file))
|
|
436
|
+
load_yaml(file)
|
|
437
|
+
if File.exists?(initfile = File.join(APP_DIR, 'config', 'init.rb'))
|
|
438
|
+
load initfile
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
setup_dirs
|
|
442
|
+
setup_loggers
|
|
443
|
+
|
|
444
|
+
# Import some constants directly at toplevel before loading the
|
|
445
|
+
# user-defined models
|
|
446
|
+
unless Object.const_defined?(:Application)
|
|
447
|
+
Object.const_set(:Application, Roby::Application)
|
|
448
|
+
Object.const_set(:State, Roby::State)
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
require_models
|
|
452
|
+
|
|
453
|
+
# MainPlanner is always included in the planner list
|
|
454
|
+
Roby.control.planners << MainPlanner
|
|
455
|
+
|
|
456
|
+
# Set up the loaded plugins
|
|
457
|
+
call_plugins(:setup, self)
|
|
458
|
+
|
|
459
|
+
# If we are in test mode, import the test extensions from plugins
|
|
460
|
+
if testing?
|
|
461
|
+
require 'roby/test/testcase'
|
|
462
|
+
each_plugin do |mod|
|
|
463
|
+
if mod.const_defined?(:Test)
|
|
464
|
+
Roby::Test::TestCase.include mod.const_get(:Test)
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
def run(&block)
|
|
471
|
+
# Set up dRoby, setting an Interface object as front server, for shell access
|
|
472
|
+
host = droby['host'] || ""
|
|
473
|
+
if host !~ /:\d+$/
|
|
474
|
+
host << ":#{Distributed::DEFAULT_DROBY_PORT}"
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
if single? || !robot_name
|
|
478
|
+
host =~ /:(\d+)$/
|
|
479
|
+
DRb.start_service "druby://:#{$1 || '0'}", Interface.new(Roby.control)
|
|
480
|
+
else
|
|
481
|
+
DRb.start_service "druby://#{host}", Interface.new(Roby.control)
|
|
482
|
+
droby_config = { :ring_discovery => !!discovery['ring'],
|
|
483
|
+
:name => robot_name,
|
|
484
|
+
:plan => Roby.plan,
|
|
485
|
+
:period => discovery['period'] || 0.5 }
|
|
486
|
+
|
|
487
|
+
if discovery['tuplespace']
|
|
488
|
+
droby_config[:discovery_tuplespace] = DRbObject.new_with_uri("druby://#{discovery['tuplespace']}")
|
|
489
|
+
end
|
|
490
|
+
Roby::Distributed.state = Roby::Distributed::ConnectionSpace.new(droby_config)
|
|
491
|
+
|
|
492
|
+
if discovery['ring']
|
|
493
|
+
Roby::Distributed.publish discovery['ring']
|
|
494
|
+
end
|
|
495
|
+
Roby::Control.every(discovery['period'] || 0.5) do
|
|
496
|
+
Roby::Distributed.state.start_neighbour_discovery
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
@robot_name ||= 'common'
|
|
501
|
+
@robot_type ||= 'common'
|
|
502
|
+
|
|
503
|
+
control_config = self.control
|
|
504
|
+
control = Roby.control
|
|
505
|
+
options = { :detach => true, :cycle => control_config['cycle'] || 0.1 }
|
|
506
|
+
|
|
507
|
+
# Add an executive if one is defined
|
|
508
|
+
if control_config['executive']
|
|
509
|
+
self.executive = control_config['executive']
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
if log['events']
|
|
513
|
+
require 'roby/log/file'
|
|
514
|
+
logfile = File.join(log_dir, robot_name)
|
|
515
|
+
logger = Roby::Log::FileLogger.new(logfile)
|
|
516
|
+
logger.stats_mode = log['events'] == 'stats'
|
|
517
|
+
Roby::Log.add_logger logger
|
|
518
|
+
end
|
|
519
|
+
control.abort_on_exception =
|
|
520
|
+
control_config['abort_on_exception']
|
|
521
|
+
control.abort_on_application_exception =
|
|
522
|
+
control_config['abort_on_application_exception']
|
|
523
|
+
control.run options
|
|
524
|
+
|
|
525
|
+
plugins = self.plugins.map { |_, mod| mod if mod.respond_to?(:run) }.compact
|
|
526
|
+
run_plugins(plugins, &block)
|
|
527
|
+
|
|
528
|
+
rescue Exception => e
|
|
529
|
+
if e.respond_to?(:pretty_print)
|
|
530
|
+
pp e
|
|
531
|
+
else
|
|
532
|
+
pp e.full_message
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
def run_plugins(mods, &block)
|
|
536
|
+
control = Roby.control
|
|
537
|
+
|
|
538
|
+
if mods.empty?
|
|
539
|
+
yield
|
|
540
|
+
control.join
|
|
541
|
+
else
|
|
542
|
+
mod = mods.shift
|
|
543
|
+
mod.run(self) do
|
|
544
|
+
run_plugins(mods, &block)
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
rescue Exception => e
|
|
549
|
+
if Roby.control.running?
|
|
550
|
+
control.quit
|
|
551
|
+
control.join
|
|
552
|
+
raise e, e.message, e.backtrace
|
|
553
|
+
else
|
|
554
|
+
raise
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
|
|
558
|
+
attr_reader :executive
|
|
559
|
+
|
|
560
|
+
def executive=(name)
|
|
561
|
+
if executive
|
|
562
|
+
Control.event_processing.delete(executive.method(:initial_events))
|
|
563
|
+
@executive = nil
|
|
564
|
+
end
|
|
565
|
+
return unless name
|
|
566
|
+
|
|
567
|
+
full_name = "roby/executives/#{name}"
|
|
568
|
+
require full_name
|
|
569
|
+
@executive = full_name.camelize.constantize.new
|
|
570
|
+
Control.event_processing << executive.method(:initial_events)
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def stop; call_plugins(:stop, self) end
|
|
574
|
+
|
|
575
|
+
DISCOVERY_TEMPLATE = [:droby, nil, nil]
|
|
576
|
+
|
|
577
|
+
# Starts services needed for distributed operations. These services are
|
|
578
|
+
# supposed to be started only once for a whole system
|
|
579
|
+
#
|
|
580
|
+
# If you have external servers to start for every robot, plug it into
|
|
581
|
+
# #start_server
|
|
582
|
+
def start_distributed
|
|
583
|
+
Thread.abort_on_exception = true
|
|
584
|
+
|
|
585
|
+
if !File.exists?(log_dir)
|
|
586
|
+
Dir.mkdir(log_dir)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
unless single? || !discovery['tuplespace']
|
|
590
|
+
ts = Rinda::TupleSpace.new
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
discovery['tuplespace'] =~ /(:\d+)$/
|
|
594
|
+
DRb.start_service "druby://#{$1}", ts
|
|
595
|
+
|
|
596
|
+
new_db = ts.notify('write', DISCOVERY_TEMPLATE)
|
|
597
|
+
take_db = ts.notify('take', DISCOVERY_TEMPLATE)
|
|
598
|
+
|
|
599
|
+
Thread.start do
|
|
600
|
+
new_db.each { |_, t| STDERR.puts "new host #{t[1]}" }
|
|
601
|
+
end
|
|
602
|
+
Thread.start do
|
|
603
|
+
take_db.each { |_, t| STDERR.puts "host #{t[1]} has disconnected" }
|
|
604
|
+
end
|
|
605
|
+
Roby.warn "Started service discovery on #{discovery['tuplespace']}"
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
call_plugins(:start_distributed, self)
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
# Stop services needed for distributed operations. See #start_distributed
|
|
612
|
+
def stop_distributed
|
|
613
|
+
DRb.stop_service
|
|
614
|
+
|
|
615
|
+
call_plugins(:stop_distributed, self)
|
|
616
|
+
rescue Interrupt
|
|
617
|
+
end
|
|
618
|
+
|
|
619
|
+
attr_reader :log_server
|
|
620
|
+
attr_reader :log_sources
|
|
621
|
+
|
|
622
|
+
# Start services that should exist for every robot in the system. Services that
|
|
623
|
+
# are needed only once for all robots should be started in #start_distributed
|
|
624
|
+
def start_server
|
|
625
|
+
Thread.abort_on_exception = true
|
|
626
|
+
|
|
627
|
+
# Start a log server if needed, and poll the log directory for new
|
|
628
|
+
# data sources
|
|
629
|
+
if log_server = (log.has_key?('server') ? log['server'] : true)
|
|
630
|
+
require 'roby/log/server'
|
|
631
|
+
port = if log_server.kind_of?(Hash) && log_server['port']
|
|
632
|
+
Integer(log_server['port'])
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
@log_server = Log::Server.new(port ||= Log::Server::RING_PORT)
|
|
636
|
+
Roby::Log::Server.info "log server published on port #{port}"
|
|
637
|
+
@log_streams = []
|
|
638
|
+
@log_streams_poll = Thread.new do
|
|
639
|
+
begin
|
|
640
|
+
loop do
|
|
641
|
+
Thread.exclusive do
|
|
642
|
+
known_streams = @log_server.streams
|
|
643
|
+
streams = data_streams
|
|
644
|
+
|
|
645
|
+
(streams - known_streams).each do |s|
|
|
646
|
+
Roby::Log::Server.info "new stream found #{s.name} [#{s.type}]"
|
|
647
|
+
s.open
|
|
648
|
+
@log_server.added_stream(s)
|
|
649
|
+
end
|
|
650
|
+
(known_streams - streams).each do |s|
|
|
651
|
+
Roby::Log::Server.info "end of stream #{s.name} [#{s.type}]"
|
|
652
|
+
s.close
|
|
653
|
+
@log_server.removed_stream(s)
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
sleep(5)
|
|
657
|
+
end
|
|
658
|
+
rescue Interrupt
|
|
659
|
+
rescue
|
|
660
|
+
Roby::Log::Server.fatal $!.full_message
|
|
661
|
+
end
|
|
662
|
+
end
|
|
663
|
+
end
|
|
664
|
+
|
|
665
|
+
call_plugins(:start_server, self)
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
# Stop server. See #start_server
|
|
669
|
+
def stop_server
|
|
670
|
+
if @log_server
|
|
671
|
+
@log_streams_poll.raise Interrupt, "quitting"
|
|
672
|
+
@log_streams_poll.join
|
|
673
|
+
|
|
674
|
+
@log_server.quit
|
|
675
|
+
@log_streams.clear
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
call_plugins(:stop_server, self)
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
# Require all files in +dirname+
|
|
682
|
+
def require_dir(dirname)
|
|
683
|
+
Dir.new(dirname).each do |file|
|
|
684
|
+
file = File.join(dirname, file)
|
|
685
|
+
file = file.gsub(/^#{Regexp.quote(APP_DIR)}\//, '')
|
|
686
|
+
require file if file =~ /\.rb$/ && File.file?(file)
|
|
687
|
+
end
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
# Require all files in the directories matching +pattern+. If +pattern+
|
|
691
|
+
# contains the word ROBOT, it is replaced by -- in order -- the robot
|
|
692
|
+
# name and then the robot type
|
|
693
|
+
def require_robotdir(pattern)
|
|
694
|
+
return unless robot_name && robot_type
|
|
695
|
+
|
|
696
|
+
[robot_name, robot_type].each do |name|
|
|
697
|
+
dirname = pattern.gsub(/ROBOT/, name)
|
|
698
|
+
require_dir(dirname) if File.directory?(dirname)
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
# Loads the first file found matching +pattern+
|
|
703
|
+
#
|
|
704
|
+
# See #require_robotfile
|
|
705
|
+
def load_robotfile(pattern)
|
|
706
|
+
require_robotfile(pattern, :load)
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
# Requires or loads (according to the value of +method+) the first file
|
|
710
|
+
# found matching +pattern+. +pattern+ can contain the word ROBOT, in
|
|
711
|
+
# which case the file is first checked against the robot name and then
|
|
712
|
+
# against the robot type
|
|
713
|
+
def require_robotfile(pattern, method = :require)
|
|
714
|
+
return unless robot_name && robot_type
|
|
715
|
+
|
|
716
|
+
robot_config = pattern.gsub(/ROBOT/, robot_name)
|
|
717
|
+
if File.file?(robot_config)
|
|
718
|
+
Kernel.send(method, robot_config)
|
|
719
|
+
true
|
|
720
|
+
else
|
|
721
|
+
robot_config = pattern.gsub(/ROBOT/, robot_type)
|
|
722
|
+
if File.file?(robot_config)
|
|
723
|
+
Kernel.send(method, robot_config)
|
|
724
|
+
true
|
|
725
|
+
else
|
|
726
|
+
false
|
|
727
|
+
end
|
|
728
|
+
end
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
attr_predicate :simulation?, true
|
|
732
|
+
def simulation; self.simulation = true end
|
|
733
|
+
|
|
734
|
+
attr_predicate :testing?, true
|
|
735
|
+
def testing; self.testing = true end
|
|
736
|
+
attr_predicate :shell?, true
|
|
737
|
+
def shell; self.shell = true end
|
|
738
|
+
def single?; @single || discovery.empty? end
|
|
739
|
+
def single; @single = true end
|
|
740
|
+
|
|
741
|
+
# Guesses the type of +filename+ if it is a source suitable for
|
|
742
|
+
# data display in this application
|
|
743
|
+
def data_streams_of(filenames)
|
|
744
|
+
if filenames.size == 1
|
|
745
|
+
path = filenames.first
|
|
746
|
+
path = if path =~ /-(events|timings)\.log$/
|
|
747
|
+
$`
|
|
748
|
+
elsif File.exists?("#{path}-events.log")
|
|
749
|
+
path
|
|
750
|
+
end
|
|
751
|
+
if path
|
|
752
|
+
return [Roby::Log::EventStream.new(path)]
|
|
753
|
+
end
|
|
754
|
+
end
|
|
755
|
+
|
|
756
|
+
each_responding_plugin(:data_streams_of, true) do |config|
|
|
757
|
+
if streams = config.data_streams_of(filenames)
|
|
758
|
+
return streams
|
|
759
|
+
end
|
|
760
|
+
end
|
|
761
|
+
nil
|
|
762
|
+
end
|
|
763
|
+
|
|
764
|
+
# Returns the list of data streams suitable for data display known
|
|
765
|
+
# to the application
|
|
766
|
+
def data_streams(log_dir = nil)
|
|
767
|
+
log_dir ||= self.log_dir
|
|
768
|
+
streams = []
|
|
769
|
+
Dir.glob(File.join(log_dir, '*-events.log*')).each do |file|
|
|
770
|
+
next unless file =~ /-events\.log$/
|
|
771
|
+
streams << Roby::Log::EventStream.new($`)
|
|
772
|
+
end
|
|
773
|
+
each_responding_plugin(:data_streams, true) do |config|
|
|
774
|
+
if s = config.data_streams(log_dir)
|
|
775
|
+
streams += s
|
|
776
|
+
end
|
|
777
|
+
end
|
|
778
|
+
streams
|
|
779
|
+
end
|
|
780
|
+
|
|
781
|
+
def self.find_data(name)
|
|
782
|
+
Roby::State.datadirs.each do |dir|
|
|
783
|
+
path = File.join(dir, name)
|
|
784
|
+
return path if File.exists?(path)
|
|
785
|
+
end
|
|
786
|
+
raise Errno::ENOENT, "no file #{name} found in #{Roby::State.datadirs.join(":")}"
|
|
787
|
+
end
|
|
788
|
+
|
|
789
|
+
def self.register_plugin(name, mod, &init)
|
|
790
|
+
caller(1)[0] =~ /^([^:]+):\d/
|
|
791
|
+
dir = File.expand_path(File.dirname($1))
|
|
792
|
+
Roby.app.available_plugins << [name, dir, mod, init]
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
@@reload_model_filter = []
|
|
796
|
+
# Add a filter to model reloading. A task or planner model is
|
|
797
|
+
# reinitialized only if all filter blocks return true for it
|
|
798
|
+
def self.filter_reloaded_models(&block)
|
|
799
|
+
@@reload_model_filter << block
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
def model?(model)
|
|
803
|
+
(model <= Roby::Task) || (model.kind_of?(Roby::TaskModelTag)) ||
|
|
804
|
+
(model <= Planning::Planner) || (model <= Planning::Library)
|
|
805
|
+
end
|
|
806
|
+
|
|
807
|
+
def reload_model?(model)
|
|
808
|
+
@@reload_model_filter.all? { |filter| filter[model] }
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
def app_file?(path)
|
|
812
|
+
(path =~ %r{(^|/)#{APP_DIR}(/|$)}) ||
|
|
813
|
+
((path[0] != ?/) && File.file?(File.join(APP_DIR, path)))
|
|
814
|
+
end
|
|
815
|
+
def framework_file?(path)
|
|
816
|
+
if path =~ /roby\/.*\.rb$/
|
|
817
|
+
true
|
|
818
|
+
else
|
|
819
|
+
Roby.app.plugins.any? do |name, _|
|
|
820
|
+
_, dir, _, _ = Roby.app.plugin_definition(name)
|
|
821
|
+
path =~ %r{(^|/)#{dir}(/|$)}
|
|
822
|
+
end
|
|
823
|
+
end
|
|
824
|
+
end
|
|
825
|
+
|
|
826
|
+
def reload
|
|
827
|
+
# Always reload this file first. This ensure that one can use #reload
|
|
828
|
+
# to fix the reload code itself
|
|
829
|
+
load __FILE__
|
|
830
|
+
|
|
831
|
+
# Clear all event definitions in task models that are filtered out by
|
|
832
|
+
# Application.filter_reloaded_models
|
|
833
|
+
ObjectSpace.each_object(Class) do |model|
|
|
834
|
+
next unless model?(model)
|
|
835
|
+
next unless reload_model?(model)
|
|
836
|
+
|
|
837
|
+
model.clear_model
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
# Remove what we want to reload from LOADED_FEATURES and use
|
|
841
|
+
# require. Do not use 'load' as the reload order should be the
|
|
842
|
+
# require order.
|
|
843
|
+
needs_reload = []
|
|
844
|
+
$LOADED_FEATURES.delete_if do |feature|
|
|
845
|
+
if framework_file?(feature) || app_file?(feature)
|
|
846
|
+
needs_reload << feature
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
needs_reload.each do |feature|
|
|
851
|
+
begin
|
|
852
|
+
require feature.gsub(/\.rb$/, '')
|
|
853
|
+
rescue Exception => e
|
|
854
|
+
STDERR.puts e.full_message
|
|
855
|
+
end
|
|
856
|
+
end
|
|
857
|
+
end
|
|
858
|
+
end
|
|
859
|
+
|
|
860
|
+
# Load the plugins 'main' files
|
|
861
|
+
Roby.app.plugin_dir File.join(ROBY_ROOT_DIR, 'plugins')
|
|
862
|
+
if plugin_path = ENV['ROBY_PLUGIN_PATH']
|
|
863
|
+
plugin_path.split(':').each do |dir|
|
|
864
|
+
if File.directory?(dir)
|
|
865
|
+
Roby.app.plugin_dir File.expand_path(dir)
|
|
866
|
+
end
|
|
867
|
+
end
|
|
868
|
+
end
|
|
869
|
+
end
|
|
870
|
+
|