module Syskit::RobyApp::Plugin
This gets mixed in Roby::Application when the orocos plugin is loaded. It adds the configuration facilities needed to plug-in orogen projects in Roby.
When in development mode, it will emit the following UI events:
- syskit_orogen_config_changed
-
files under config/orogen/ have been modified, and this affects loaded models. In addition, a text notification is sent to inform a shell user
Constants
- OroGenLocation
Attributes
True if this application should not try to contact other machines/servers
Public Class Methods
# File lib/syskit/roby_app/plugin.rb, line 436 def self.auto_require_models(app) # Load the data services and task models prefixes = ['services', 'devices', 'compositions', 'profiles'] if Roby.app.backward_compatible_naming? prefixes << 'blueprints' end if Syskit.conf.ignore_missing_orogen_projects_during_load? ignored_exceptions = [OroGen::NotFound] end prefixes.each do |prefix_name| app.load_all_model_files_in( prefix_name, ignored_exceptions: ignored_exceptions) end # Also require all the available oroGen projects app.default_loader.each_available_project_name do |name| app.using_task_library name end if app.auto_load_all? || app.auto_load_all_task_libraries? app.auto_load_all_task_libraries end end
Hook called by the main application to undo what {.require_models} and {.setup} have done
# File lib/syskit/roby_app/plugin.rb, line 154 def self.cleanup(app) if app.development_mode? app.syskit_remove_configuration_changes_listener end disconnect_all_process_servers stop_local_process_server end
Called by the main Roby application to clear all before redoing a setup
# File lib/syskit/roby_app/plugin.rb, line 431 def self.clear_config(app) Syskit.conf.clear Syskit.conf.deployments.clear end
# File lib/syskit/roby_app/plugin.rb, line 719 def self.clear_models(app) OroGen.clear app.loaded_orogen_projects.clear app.default_loader.clear # We need to explicitly call Orocos.clear even though it looks # like clearing the process servers would be sufficient # # The reason is that #cleanup only disconnects from the process # servers, and is called before #clear_models. However, for # "fake" process servers, syskit also assumes that reconnecting # to a "new" local process server will have cleared all cached # values. This won't work as the process server will not be # cleared in addition of being disconnected # # I (sylvain) chose to not clear on disconnection as it sounds # too much like a very bad side-effect to me. Simply explicitly # clear the local registries here Orocos.clear # This needs to be cleared here and not in # Component.clear_model. The main reason is that we need to # clear them on every component model class, # including the models that are marked as permanent Syskit::Component.proxy_task_models.clear Syskit::Component.each_submodel do |sub| sub.proxy_task_models.clear end # require_models is where the deployments get loaded, so # un-define them here Syskit.conf.clear_deployments end
# File lib/syskit/roby_app/plugin.rb, line 539 def self.connect_to_local_process_server(app) if !@server_pid raise Orocos::RemoteProcesses::Client::StartupFailed, "#connect_to_local_process_server got called but no process server is being started" end # Wait for the server to be ready client = nil while !client client = begin Orocos::RemoteProcesses::Client.new('localhost', @server_port) rescue Errno::ECONNREFUSED sleep 0.1 is_running = begin !::Process.waitpid(@server_pid, ::Process::WNOHANG) rescue Errno::ESRCH false end if !is_running raise Orocos::RemoteProcesses::Client::StartupFailed, "the local process server failed to start" end nil end end # Verify that the server is actually ours (i.e. check that there # was not one that was still running) if client.server_pid != @server_pid raise Orocos::RemoteProcesses::Client::StartupFailed, "failed to start the local process server. It seems that there is one still running as PID #{client.server_pid} (was expecting #{@server_pid})" end # Do *not* manage the log directory for that one ... Syskit.conf.register_process_server('localhost', client, app.log_dir) client end
# File lib/syskit/roby_app/plugin.rb, line 697 def self.disable_engine_in_roby(roby_engine, *handlers) if @handler_ids handlers.each do |h| roby_engine.remove_propagation_handler(@handler_ids.delete(h)) end begin yield ensure all_handlers = roby_engine_propagation_handlers handlers.each do |h| @handler_ids[h] = roby_engine.add_propagation_handler(all_handlers[h][1], &all_handlers[h][0]) end end else yield end end
Disconnects from all process servers
# File lib/syskit/roby_app/plugin.rb, line 590 def self.disconnect_all_process_servers process_servers = Syskit.conf.each_process_server_config.map(&:name) process_servers.each do |name| next if name =~ /-sim$/ ps = Syskit.conf.remove_process_server(name) ps.client.disconnect end end
# File lib/syskit/roby_app/plugin.rb, line 774 def self.enable ::Robot.include Syskit::RobyApp::RobotExtension ::Roby.conf.syskit = Syskit.conf OroGen.load_orogen_plugins('syskit') Roby.app.filter_out_patterns << Regexp.new(Regexp.quote(OroGen::OROGEN_LIB_DIR)) Roby.app.filter_out_patterns << Regexp.new(Regexp.quote(Orocos::OROCOSRB_LIB_DIR)) Roby.app.filter_out_patterns << Regexp.new(Regexp.quote(Typelib::TYPELIB_LIB_DIR)) Roby.app.filter_out_patterns << Regexp.new(Regexp.quote(Syskit::SYSKIT_LIB_DIR)) toplevel_object.extend LoadToplevelMethods end
# File lib/syskit/roby_app/plugin.rb, line 235 def self.finalize_model_loading(app) if toplevel_object.respond_to?(:global_profile) app.app_module::Actions::Main.use_profile toplevel_object.global_profile end end
# File lib/syskit/roby_app/plugin.rb, line 810 def self.fixed_size_type?(type) !type.contains?(Typelib::ContainerType) end
# File lib/syskit/roby_app/plugin.rb, line 814 def self.globally_sized_type?(type) sizes = Orocos.max_sizes_for(type) !sizes.empty? && OroGen::Spec::Port.compute_max_marshalling_size(type, sizes) end
# File lib/syskit/roby_app/plugin.rb, line 535 def self.has_local_process_server? @server_pid end
Hook called by the main application in Application#load_base_config
# File lib/syskit/roby_app/plugin.rb, line 35 def self.load_base_config(app) options = app.options conf = Syskit.conf if options = options['syskit'] conf.prefix = options['prefix'] conf.exclude_from_prefixing.concat(options['exclude_from_prefixing'] || []) conf.sd_domain = options['sd_domain'] conf.publish_on_sd.concat(options['publish_on_sd'] || []) end if app.testing? require 'syskit/test' end Orocos.disable_sigchld_handler = true end
# File lib/syskit/roby_app/plugin.rb, line 461 def self.load_default_models(app) ['services.rb', 'devices.rb', 'compositions.rb', 'profiles.rb'].each do |root_file| if path = app.find_file('models', root_file, path: [app.app_dir], order: :specific_first) require path end end end
# File lib/syskit/roby_app/plugin.rb, line 664 def self.plug_engine_in_roby(roby_engine) handler_ids = Hash.new roby_engine_propagation_handlers.each do |name, (m, options)| handler_ids[name] = roby_engine.add_propagation_handler(options, &m) end handler_ids end
# File lib/syskit/roby_app/plugin.rb, line 679 def self.plug_handler_in_roby(roby_engine, *handlers) handlers.each do |handler_name| m, options = roby_engine_propagation_handlers.fetch(handler_name) next if @handler_ids.has_key?(handler_name) @handler_ids[handler_name] = roby_engine.add_propagation_handler(options, &m) end end
Hook called by the main application to prepare for execution
# File lib/syskit/roby_app/plugin.rb, line 164 def self.prepare(app) @handler_ids = plug_engine_in_roby(app.execution_engine) end
Hook called by the main application in Application#setup after the main setup hooks have been called
# File lib/syskit/roby_app/plugin.rb, line 132 def self.require_models(app) setup_loaders(app) app.extra_required_task_libraries.each do |name| app.using_task_library name end app.extra_required_typekits.each do |name| app.import_types_from name end if !app.permanent_requirements.empty? toplevel_object.extend SingleFileDSL app.execution_engine.once do app.permanent_requirements.each do |req| app.plan.add_mission_task(req.as_plan) end end end end
# File lib/syskit/roby_app/plugin.rb, line 651 def self.roby_engine_propagation_handlers handlers = Hash.new handlers[:update_deployment_states] = [ Runtime.method(:update_deployment_states), type: :external_events, description: 'syskit:update_deployment_states'] handlers[:update_task_states] = [ Runtime.method(:update_task_states), type: :external_events, description: 'syskit:update_task_states'] handlers[:connection_management] = [ Runtime::ConnectionManagement.method(:update), type: :propagation, late: true, description: 'syskit:connection_management_update'] handlers[:apply_requirement_modifications] = [ Runtime.method(:apply_requirement_modifications), type: :propagation, late: true, description: 'syskit:apply_requirement_modifications'] handlers end
# File lib/syskit/roby_app/plugin.rb, line 715 def self.root_models [Syskit::Component, Syskit::Actions::Profile] end
Hook called by the main application at the beginning of Application#setup
# File lib/syskit/roby_app/plugin.rb, line 53 def self.setup(app) # We have our own loader, avoid clashing Orocos.default_loader.export_types = false # But, for the time being, default_loader might be equal to # Orocos.default_loader, so reset the export_types flag to the # desired value app.default_loader.export_types = Syskit.conf.export_types? # This is a HACK. We should be able to specify it differently if app.testing? && app.auto_load_models? app.auto_load_all_task_libraries = true end if app.development_mode? require 'listen' app.syskit_listen_to_configuration_changes end if app.testing? Syskit.conf.logs.disable_conf_logging Syskit.conf.logs.disable_port_logging end unless Syskit.conf.only_load_models? Syskit.conf.logs.create_configuration_log( File.join(app.log_dir, 'properties')) Syskit.conf.register_process_server('ruby_tasks', Orocos::RubyTasks::ProcessManager.new(app.default_loader), app.log_dir, host_id: 'syskit') Syskit.conf.register_process_server( 'unmanaged_tasks', UnmanagedTasksManager.new, app.log_dir) Syskit.conf.register_process_server( 'ros', Orocos::ROS::ProcessManager.new(app.ros_loader), app.log_dir) end if Orocos.orocos_logfile ENV['ORO_LOGFILE'] = Orocos.orocos_logfile else ENV['ORO_LOGFILE'] = File.join(app.log_dir, "orocos.orocosrb-#{::Process.pid}.txt") end if Syskit.conf.only_load_models? Orocos.load if Orocos::ROS.available? Orocos::ROS.load end else # Change to the log dir so that the IOR file created by # the CORBA bindings ends up there Dir.chdir(app.log_dir) do Orocos.initialize if Orocos::ROS.enabled? Orocos::ROS.initialize Orocos::ROS.roscore_start(:wait => true) end end end start_local_process_server = !Syskit.conf.only_load_models? && !Syskit.conf.disables_local_process_server? && !(app.single? && app.simulation?) if start_local_process_server start_local_process_server(redirect: Syskit.conf.redirect_local_process_server?) connect_to_local_process_server(app) else fake_client = Configuration::ModelOnlyServer.new(app.default_loader) Syskit.conf.register_process_server('localhost', fake_client, app.log_dir, host_id: 'syskit') end rtt_core_model = app.default_loader.task_model_from_name("RTT::TaskContext") Syskit::TaskContext.define_from_orogen(rtt_core_model, register: true) end
# File lib/syskit/roby_app/plugin.rb, line 319 def self.setup_loaders(app) all_files = app.find_files_in_dirs("models", "ROBOT", "pack", "orogen", :all => app.auto_load_all?, :order => :specific_first, :pattern => /\.orogen$/) all_files.reverse.each do |path| name = File.basename(path, ".orogen") app.orogen_pack_loader.register_orogen_file path, name end all_files = app.find_files_in_dirs("models", "ROBOT", "pack", "orogen", :all => app.auto_load_all?, :order => :specific_first, :pattern => /\.typelist$/) all_files.reverse.each do |path| name = File.basename(path, ".typelist") dir = File.dirname(path) app.orogen_pack_loader.register_typekit dir, name end app.ros_loader.search_path. concat(Roby.app.find_dirs('models', 'ROBOT', 'orogen', 'ros', :all => app.auto_load_all?, :order => :specific_first)) app.ros_loader.packs. concat(Roby.app.find_dirs('models', 'ROBOT', 'pack', 'ros', :all => true, :order => :specific_last)) end
# File lib/syskit/roby_app/plugin.rb, line 833 def self.setup_rest_interface(app, rest_api) require 'syskit/roby_app/rest_api' rest_api.mount REST_API => '/syskit' end
Hook called by the main application to undo what {.prepare} did
# File lib/syskit/roby_app/plugin.rb, line 169 def self.shutdown(app) remaining = Orocos.each_process.to_a if !remaining.empty? Syskit.warn "killing remaining Orocos processes: #{remaining.map(&:name).join(", ")}" Orocos::Process.kill(remaining) end if @handler_ids unplug_engine_from_roby(@handler_ids.values, app.execution_engine) @handler_ids = nil end end
Start a process server on the local machine, and register it in Syskit.process_servers under the 'localhost' name
# File lib/syskit/roby_app/plugin.rb, line 511 def self.start_local_process_server(port = 0, redirect: true) if Syskit.conf.process_servers['localhost'] raise ArgumentError, "there is already a process server called 'localhost' running" end if !File.exists?(Roby.app.log_dir) FileUtils.mkdir_p(Roby.app.log_dir) end tcp_server = TCPServer.new('127.0.0.1', 0) spawn_options = Hash[tcp_server => tcp_server, chdir: Roby.app.log_dir, pgroup: true] if redirect spawn_options[:err] = :out spawn_options[:out] = File.join(Roby.app.log_dir, 'local_process_server.txt') end @server_pid = Kernel.spawn \ 'syskit', 'process_server', "--fd=#{tcp_server.fileno}", "--log-dir=#{Roby.app.log_dir}", "--debug", spawn_options @server_port = tcp_server.local_address.ip_port tcp_server.close nil end
Stop the process server started by ::start_local_process_server if one is running
# File lib/syskit/roby_app/plugin.rb, line 578 def self.stop_local_process_server return if !has_local_process_server? ::Process.kill('INT', @server_pid) begin ::Process.waitpid(@server_pid) @server_pid = nil rescue Errno::ESRCH end end
# File lib/syskit/roby_app/plugin.rb, line 672 def self.unplug_engine_from_roby(handler_ids = @handler_ids, roby_engine) handler_ids.delete_if do |handler_id| roby_engine.remove_propagation_handler(handler_id) true end end
# File lib/syskit/roby_app/plugin.rb, line 687 def self.unplug_handler_from_roby(roby_engine, *handlers) if @handler_ids handlers.each do |h| if h_id = @handler_ids.delete(h) roby_engine.remove_propagation_handler(h_id) end end end end
# File lib/syskit/roby_app/plugin.rb, line 819 def self.validate_all_port_types_have_fixed_size(only_warn: false, ignore: []) with_global_size = Set.new Syskit::Component.each_submodel do |component_m| next if component_m.abstract? component_m.each_input_port do |p| validate_port_has_fixed_size(p, with_global_size, only_warn: only_warn, ignore: ignore) end component_m.each_output_port do |p| validate_port_has_fixed_size(p, with_global_size, only_warn: only_warn, ignore: ignore) end end end
# File lib/syskit/roby_app/plugin.rb, line 788 def self.validate_port_has_fixed_size(port, with_global_size, only_warn: false, ignore: []) return if with_global_size.include?(port.type) if fixed_size_type?(port.type) || globally_sized_type?(port.type) with_global_size << port.type return end port = port.to_component_port if ignore.include?(port.type) return elsif size = port.max_marshalling_size size else msg = "marshalled size of port #{port} cannot be inferred" if only_warn ::Robot.warn msg else raise VariableSizedType, msg end end end
Public Instance Methods
Loads all available oroGen projects
# File lib/syskit/roby_app/plugin.rb, line 312 def auto_load_all_task_libraries self.auto_load_all_task_libraries = true default_loader.each_available_project_name do |name| using_task_library(name) end end
# File lib/syskit/roby_app/plugin.rb, line 494 def autodiscover_tests_in?(path) if File.basename(path) == 'orogen' search_path.each do |base_path| if File.join(base_path, 'test', 'orogen') == path return false end end end if defined? super super else true end end
# File lib/syskit/roby_app/plugin.rb, line 182 def default_loader if !@default_loader @default_loader = Orocos.default_loader default_loader.on_project_load do |project| project_define_from_orogen(project) end orogen_pack_loader ros_loader end @default_loader end
# File lib/syskit/roby_app/plugin.rb, line 206 def default_orogen_project @default_orogen_project ||= OroGen::Spec::Project.new(default_loader) end
# File lib/syskit/roby_app/plugin.rb, line 194 def default_pkgconfig_loader Orocos.default_pkgconfig_loader end
Loads the oroGen deployment model for the given name and returns the corresponding syskit model
@option options [String] :on the name of the process server this
deployment should be on. It is used for loading as well, i.e. the model for the deployment will be loaded from that process server
# File lib/syskit/roby_app/plugin.rb, line 636 def deployment_define_from_orogen(deployer) if Deployment.has_model_for?(deployer) Deployment.find_model_by_orogen(deployer) else Deployment.define_from_orogen(deployer, :register => true) end end
Loads the required typekit model by its name
# File lib/syskit/roby_app/plugin.rb, line 470 def import_types_from(typekit_name) default_loader.typekit_model_from_name(typekit_name) end
Load the component extension file associated with this an oroGen project
@param [String] name the orogen project name @return [String,nil] either a file that got required, or nil if
none was
# File lib/syskit/roby_app/plugin.rb, line 290 def load_component_extension(name) # If we are loading under Roby, get the plugins for the orogen # project return if !Syskit.conf.load_component_extensions? file = find_file('models', 'orogen', "#{name}.rb", order: :specific_first) || find_file('tasks', 'orogen', "#{name}.rb", order: :specific_first) || find_file('tasks', 'components', "#{name}.rb", order: :specific_first) return unless file Roby::Application.info "loading task extension #{file}" if require(file) file end end
@deprecated use {using_task_library} instead
# File lib/syskit/roby_app/plugin.rb, line 490 def load_orogen_project(name, options = Hash.new) using_task_library(name, options) end
Returns true if the given orogen project has already been loaded by load_orogen_project
# File lib/syskit/roby_app/plugin.rb, line 243 def loaded_orogen_project?(name); loaded_orogen_projects.has_key?(name) end
# File lib/syskit/roby_app/plugin.rb, line 198 def orogen_pack_loader @orogen_pack_loader ||= OroGen::Loaders::Files.new(default_loader) end
Registers all objects contained in a given oroGen project
@param [OroGen::Spec::Project] orogen the oroGen project that
should be added to the Syskit side
# File lib/syskit/roby_app/plugin.rb, line 251 def project_define_from_orogen(orogen) return if loaded_orogen_projects.has_key?(orogen.name) Syskit.info "loading oroGen project #{orogen.name}" tasks = orogen.self_tasks.each_value.map do |task_def| syskit_model = if !TaskContext.has_model_for?(task_def) Syskit::TaskContext.define_from_orogen(task_def, register: true) else Syskit::TaskContext.model_for(task_def) end syskit_model.configuration_manager.reload syskit_model end if file = load_component_extension(orogen.name) tasks.each do |t| t.definition_location = [OroGenLocation.new(file, 1, nil)] t.extension_file = file end end orogen end
# File lib/syskit/roby_app/plugin.rb, line 202 def ros_loader @ros_loader ||= OroGen::ROS::Loader.new(default_loader) end
Verifies whether the configuration on disk and the configurations currently loaded in the running deployments are identical
# File lib/syskit/roby_app/plugin.rb, line 362 def syskit_has_pending_configuration_changes? TaskContext.each_submodel do |model| return true if model.configuration_manager.changed_on_disk? end false end
# File lib/syskit/roby_app/plugin.rb, line 341 def syskit_listen_to_configuration_changes dirs = find_dirs('config', 'orogen', 'ROBOT', all: true, order: :specific_last) return if dirs.empty? @conf_listener = Listen.to(*dirs) do |modified, added, removed| if syskit_has_pending_configuration_changes? notify 'syskit', 'INFO', 'oroGen configuration files changed on disk. In the shell, reload with #reload_config and reconfigure affected running components with #redeploy' ui_event 'syskit_orogen_config_changed' end end @conf_listener.start end
Returns the names of the TaskContext that will need to be reconfigured on the next deployment (either redeploy or transition)
@return [(Array,Array)] the list of all task names for tasks whose
configuration has changed, and the subset of these tasks that are currently running
# File lib/syskit/roby_app/plugin.rb, line 414 def syskit_pending_reloaded_configurations pending_reconfigurations = Array.new pending_running_reconfigurations = Array.new plan.find_tasks(Deployment).each do |deployment_task| pending = deployment_task.pending_reconfigurations deployment_task.each_executed_task do |t| if pending.include?(t.orocos_name) pending_running_reconfigurations << t.orocos_name end end pending_reconfigurations.concat(pending) end return pending_reconfigurations, pending_running_reconfigurations end
Reloads the configuration files
This only modifies the configuration loaded internally, but does not change the configuration of existing task contexts. The new configuration will only be applied after the next deployment
@return [(Array,Array)] the names of the components whose configuration
changed, and the subset of those that are currently running
@see #syskit_pending_reloaded_configurations
# File lib/syskit/roby_app/plugin.rb, line 379 def syskit_reload_config needs_reconfiguration = [] running_needs_reconfiguration = [] TaskContext.each_submodel do |model| next if !model.concrete_model? changed_sections = model.configuration_manager.reload plan.find_tasks(Deployment).each do |deployment_task| deployment_task.mark_changed_configuration_as_not_reusable( model => changed_sections).each do |orocos_name| needs_reconfiguration << orocos_name deployment_task.each_executed_task do |t| if t.orocos_name == orocos_name running_needs_reconfiguration << orocos_name end end notify 'syskit', 'INFO', "task #{orocos_name} needs reconfiguration" end end end if !running_needs_reconfiguration.empty? notify 'syskit', 'INFO', "#{running_needs_reconfiguration.size} running tasks configuration changed. In the shell, use 'redeploy' to trigger reconfiguration." end ui_event 'syskit_orogen_config_reloaded', needs_reconfiguration, running_needs_reconfiguration needs_reconfiguration end
# File lib/syskit/roby_app/plugin.rb, line 354 def syskit_remove_configuration_changes_listener if @conf_listener @conf_listener.stop end end
Start all deployments
@param [String,nil] on the name of the process server on which
deployments should be started. If nil, all servers are considered
# File lib/syskit/roby_app/plugin.rb, line 617 def syskit_start_all_deployments(on: nil, except_on: "unmanaged_tasks") existing_deployments = plan.find_tasks(Syskit::Deployment). not_finished. find_all { |d| d.reusable? }. map(&:process_name).to_set Syskit.conf.each_configured_deployment(on: on, except_on: except_on) do |configured_deployment| next if existing_deployments.include?(configured_deployment.process_name) plan.add_permanent_task(configured_deployment.new) end end
# File lib/syskit/roby_app/plugin.rb, line 278 def syskit_utility_component?(task_context) if defined?(OroGen::Logger::Logger) task_context.kind_of?(OroGen::Logger::Logger) end end
Loads the oroGen deployment model for the given name and returns the corresponding syskit model
# File lib/syskit/roby_app/plugin.rb, line 601 def using_deployment(name, options = Hash.new) options = Kernel.validate_options options, :loader => default_loader deployer = options[:loader].deployment_model_from_name(name) deployment_define_from_orogen(deployer) end
Loads the oroGen deployment model based on a ROS launcher file
# File lib/syskit/roby_app/plugin.rb, line 608 def using_ros_launcher(name, options = Hash.new) options = Kernel.validate_options options, :loader => ros_loader using_deployment(name, options) end
Loads the required ROS package
# File lib/syskit/roby_app/plugin.rb, line 484 def using_ros_package(name, options = Hash.new) options = Kernel.validate_options options, :loader => ros_loader using_task_library(name, options) end
Load the specified oroGen project and register the task contexts and deployments they contain.
@return [OroGen::Spec::Project]
# File lib/syskit/roby_app/plugin.rb, line 478 def using_task_library(name, options = Hash.new) options = Kernel.validate_options options, :loader => default_loader options[:loader].project_model_from_name(name) end