Roby is a plan management component, i.e. a supervision framework that is based on the concept of plans.
See doudou.github.com/roby for more information.
This module includes both the Roby bindings, i.e. what allows to represent Orocos task contexts and deployment processes in Roby, and a model-based system configuration environment.
A set of colors to be used in graphiz graphs
Namespace for all defined composition models
Namespace in which device models are stored.
When a device is declared with
device 'a_name_in_snake_case'
The plugin creates a Orocos::RobyPlugin::Devices::ANameInSnakeCase instance of the DataServiceModel class. This instance then gets included in every task context model that provides the service.
A Orocos::Generation::TaskContext instance is used to represent the service interface. This instance is available through the ‘interface’ attribute of the DataServiceModel instance.
Namespace in which data services are stored.
When a service is declared with
data_service 'a_name_in_snake_case'
The plugin creates a Orocos::RobyPlugin::DataServices::ANameInSnakeCase instance of the DataServiceModel class. This instance then gets included in every task context model and device model that provides the service.
A Orocos::Generation::TaskContext instance is used to represent the service interface. This instance is available through the ‘interface’ attribute of the DataServiceModel instance.
Margin that should be added to the computed buffer sizes. It is a ratio of the optimal buffer size
I.e. if a connection requires 5 buffers and that value is 0.1, then the actual buffer size will be 6 (it is rounded upwards).
If the buffer size is 400, then 440 will be used in the end
The default is 0.1 (10%)
The set of known process servers.
It maps the server name to the Orocos::ProcessServer instance
Used by the to_dot* methods for color allocation
Returns a color from COLOR_PALETTE, rotating each time the method is called. It is used by the to_dot* methods.
# File lib/orocos/roby/engine.rb, line 22 def self.allocate_color @current_color = (@current_color + 1) % COLOR_PALETTE.size COLOR_PALETTE[@current_color] end
# File lib/orocos/roby/engine.rb, line 2308 def self.apply_requirement_modifications(plan) tasks = plan.find_tasks(RequirementModificationTask).running.to_a if Roby.app.orocos_engine.modified? # We assume that all requirement modification have been applied # by the RequirementModificationTask instances. They therefore # take the blame if something fails, and announce a success begin Roby.app.orocos_engine.resolve tasks.each do |t| t.emit :success end rescue Exception => e if tasks.empty? # No task to take the blame ... we'll have to shut down raise end tasks.each do |t| t.emit(:failed, e) end end end end
Sets the margin that should be added to the computed buffer sizes
See #buffer_size_margin for more explanations
# File lib/orocos/roby/dataflow_dynamics.rb, line 43 def buffer_size_margin=(value) value = Float(value) if value < 0 raise ArgumentError, "only positive values can be used as buffer_size_margin, got #{value}" end @buffer_size_margin = Float(value) end
Returns an array of modules. It is used as the search path for DSL parsing.
I.e. when someone uses a ClassName in a DSL, this constant will be searched following the order of modules returned by this method.
# File lib/orocos/roby/base.rb, line 49 def self.constant_search_path [Orocos::RobyPlugin] end
Creates a blank orogen interface and returns it
# File lib/orocos/roby/base.rb, line 40 def self.create_orogen_interface(name = nil, &block) Orocos.create_orogen_interface(name, &block) end
Merge the given orogen interfaces into one subclass
# File lib/orocos/roby/base.rb, line 27 def self.merge_orogen_interfaces(target, interfaces, port_mappings = Hash.new) interfaces.each do |i| target.implements i.name target.merge_ports_from(i, port_mappings) i.each_event_port do |port| target_name = port_mappings[port.name] || port.name target.port_driven target_name end end end
Returns the Project instance that represents the given orogen project.
# File lib/orocos/roby/base.rb, line 167 def self.orogen_project_module(name) const_name = name.camelcase(:upper) Orocos::RobyPlugin.define_or_reuse(const_name) do mod = Project.new mod.instance_variable_set :@orogen_spec, ::Roby.app.loaded_orogen_projects[name] mod end end
# File lib/orocos/roby/task_context.rb, line 936 def self.placeholder_model_for(name, models) # If all that is required is a proper task model, just return it if models.size == 1 && (models.find { true } <= Roby::Task) return models.find { true } end if task_model = models.find { |t| t < Roby::Task } model = task_model.specialize("placeholder_model_for_" + name.gsub(/[^\w]/, '_')) model.name = name model.abstract model.include ComponentModelProxy model.proxied_data_services = models.dup else model = DataServiceProxy.new_submodel(name, models) end orogen_spec = RobyPlugin.create_orogen_interface model.instance_variable_set(:@orogen_spec, orogen_spec) RobyPlugin.merge_orogen_interfaces(model.orogen_spec, models.map(&:orogen_spec)) models.each do |m| if m.kind_of?(DataServiceModel) model.provides m end end model end
# File lib/orocos/roby/selection_tasks.rb, line 173 def self.require_task(name_or_model) SingleRequirementTask.subplan(name_or_model) end
# File lib/orocos/roby/selection_tasks.rb, line 177 def self.requirement_from_name(name) Roby.app.orocos_engine.resolve_name(name) end
# File lib/orocos/roby/connection_graphs.rb, line 101 def self.update_connection_policy(old, new) old = old.dup new = new.dup if old.empty? return new elsif new.empty? return old end old_fallback = old.delete(:fallback_policy) new_fallback = new.delete(:fallback_policy) if old_fallback && new_fallback fallback = update_connection_policy(old_fallback, new_fallback) else fallback = old_fallback || new_fallback end old = Port.validate_policy(old) new = Port.validate_policy(new) type = old[:type] || new[:type] merged = old.merge(new) do |key, old_value, new_value| if old_value == new_value old_value elsif key == :type raise ArgumentError, "connection types mismatch: #{old_value} != #{new_value}" elsif key == :transport if old_value == 0 then new_value elsif new_value == 0 then old_value else raise ArgumentError, "policy mismatch for transport: #{old_value} != #{new_value}" end else raise ArgumentError, "policy mismatch for #{key}: #{old_value} != #{new_value}" end end if fallback merged[:fallback_policy] = fallback end merged end
Returns true if all the declared connections to the inputs of
task have been applied. A given module won’t be started until
it is the case.
If the only_static flag is set to true, only ports that
require static connections will be considered
# File lib/orocos/roby/connection_graphs.rb, line 533 def all_inputs_connected? each_concrete_input_connection do |source_task, source_port, sink_port, policy| # Our source may not be initialized at all if !source_task.orogen_task return false end return false if !ActualDataFlow.linked?(source_task.orogen_task, orogen_task) mappings = source_task.orogen_task[orogen_task, ActualDataFlow] return false if !mappings.has_key?([source_port, sink_port]) end true end
# File lib/orocos/roby/connection_graphs.rb, line 184 def clear_relations Flows::DataFlow.remove(self) super end
Calls either #connect_ports or #forward_ports, depending on its arguments
It calls #forward_ports only if one of [target_task, self] is a composition and the other is part of this composition. Otherwise, calls #connect_ports
# File lib/orocos/roby/connection_graphs.rb, line 354 def connect_or_forward_ports(target_task, mappings) if !kind_of?(Composition) && !target_task.kind_of?(Composition) return connect_ports(target_task, mappings) end connections = Hash.new forwards = Hash.new mappings.each do |(out_port_name, in_port_name), policy| source_has_output = may_have_output_port?(out_port_name) target_has_input = target_task.may_have_input_port?(in_port_name) if source_has_output && target_has_input connections[[out_port_name, in_port_name]] = policy next end if kind_of?(Composition) source_has_input = may_have_input_port?(out_port_name) if source_has_input forwards[[out_port_name, in_port_name]] = policy next elsif !source_has_output raise ArgumentError, "#{out_port_name} is neither an output port nor an exported input of #{self}" end elsif !source_has_output raise ArgumentError, "#{out_port_name} is not an output port of #{self}" end if target_task.kind_of?(Composition) target_has_output = target_task.may_have_output_port?(in_port_name) if target_has_output forwards[[out_port_name, in_port_name]] = policy next elsif !target_has_input raise ArgumentError, "#{out_port_name} is neither an input port nor an exported output of #{self}" end elsif !target_has_input raise ArgumentError, "#{out_port_name} is not an input port of #{self}" end raise ArgumentError, "invalid connection #{self}.#{out_port_name} => #{target_task}.#{in_port_name}" end if !connections.empty? connect_ports(target_task, connections) end if !forwards.empty? forward_ports(target_task, forwards) end end
Connect a set of ports between self and
target_task.
mappings describes the connections. It is a hash of the form
[source_port_name, sink_port_name] => connection_policy
where source_port_name is a port of self and sink_port_name a
port of target_task
Raises ArgumentError if one of the ports do not exist.
# File lib/orocos/roby/connection_graphs.rb, line 275 def connect_ports(target_task, mappings) if target_task.as_plan != target_task port_mappings = target_task.model.port_mappings_for_task mappings = mappings.map_key do |(source, sink), _| mapped_sink = port_mappings[sink] if !mapped_sink raise ArgumentError, "#{sink} is not a port of #{target_task}" end [source, mapped_sink] end target_task = target_task.as_plan end mappings.each do |(out_port, in_port), options| ensure_has_output_port(out_port) target_task.ensure_has_input_port(in_port) end add_sink(target_task, mappings) end
Returns true if port_name is connected
# File lib/orocos/roby/connection_graphs.rb, line 235 def connected?(port_name) each_sink do |sink_task, mappings| if mappings.any? { |(from, to), _| from == port_name } return true end end each_source do |source_task| mappings = source_task[self, Flows::DataFlow] if mappings.any? { |(from, to), _| to == port_name } return true end end false end
Tests if port_name is connected to other_port on
other_task
# File lib/orocos/roby/connection_graphs.rb, line 251 def connected_to?(port_name, other_task, other_port) if Flows::DataFlow.linked?(self, other_task) self[other_task, Flows::DataFlow].each_key do |from, to| return true if from == port_name && to == other_port end end if Flows::DataFlow.linked?(other_task, self) other_task[self, Flows::DataFlow].each_key do |from, to| return true if from == other_port && to == port_name end end false end
# File lib/orocos/roby/connection_graphs.rb, line 330 def disconnect_port(port_name) if port_name.respond_to?(:name) port_name = port_name.name end each_source do |parent_task| current = parent_task[self, Flows::DataFlow] current.delete_if { |(from, to), pol| to == port_name } parent_task[self, Flows::DataFlow] = current end each_sink do |child_task| current = self[child_task, Flows::DataFlow] current.delete_if { |(from, to), pol| from == port_name } self[child_task, Flows::DataFlow] = current end Flows::DataFlow.modified_tasks << self << target_task end
# File lib/orocos/roby/connection_graphs.rb, line 296 def disconnect_ports(target_task, mappings) if target_task.as_plan != target_task port_mappings = target_task.model.port_mappings_for_task mappings = mappings.map do |source, sink| mapped_sink = port_mappings[sink] if !mapped_sink raise ArgumentError, "#{sink} is not a port of #{target_task}" end [source, mapped_sink] end target_task = target_task.as_plan end if !Flows::DataFlow.linked?(self, target_task) raise ArgumentError, "no such connections #{mappings} for #{self} => #{target_task}" end connections = self[target_task, Flows::DataFlow] result = Hash.new mappings.delete_if do |port_pair| if !port_pair.respond_to?(:to_ary) raise ArgumentError, "invalid connection description #{mappings.inspect}, expected a list of pairs of port names" end result[port_pair] = connections.delete(port_pair) end if !mappings.empty? raise ArgumentError, "no such connections #{mappings.map { |pair| "#{pair[0]} => #{pair[1]}" }.join(", ")} for #{self} => #{target_task}. Existing connections are: #{connections.map { |pair| "#{pair[0]} => #{pair[1]}" }.join(", ")}" end Flows::DataFlow.modified_tasks << self << target_task result end
Yield or enumerates the connections that exist towards the input ports of
sink_task. It does not include connections to composition
ports (i.e. exported ports): these connections are followed until a
concrete port (a port on an actual Orocos task
context) is found.
# File lib/orocos/roby/connection_graphs.rb, line 436 def each_concrete_input_connection(required_port = nil, &block) if !block_given? return enum_for(:each_concrete_input_connection, required_port) end each_input_connection(required_port) do |source_task, source_port, sink_port, policy| # Follow the forwardings while +sink_task+ is a composition if source_task.kind_of?(Composition) source_task.each_concrete_input_connection(source_port) do |source_task, source_port, _, connection_policy| begin this_policy = RobyPlugin.update_connection_policy(policy, connection_policy) rescue ArgumentError => e raise SpecError, "incompatible policies in input chain for #{self}:#{sink_port}: #{e.message}" end yield(source_task, source_port, sink_port, policy) end else yield(source_task, source_port, sink_port, policy) end end self end
# File lib/orocos/roby/connection_graphs.rb, line 465 def each_concrete_output_connection(required_port = nil) if !block_given? return enum_for(:each_concrete_output_connection, required_port) end each_output_connection(required_port) do |source_port, sink_port, sink_task, policy| # Follow the forwardings while +sink_task+ is a composition if sink_task.kind_of?(Composition) sink_task.each_concrete_output_connection(sink_port) do |_, sink_port, sink_task, connection_policy| begin this_policy = RobyPlugin.update_connection_policy(policy, connection_policy) rescue ArgumentError => e raise SpecError, "incompatible policies in output chain for #{self}:#{source_port}: #{e.message}" end policy_copy = this_policy.dup yield(source_port, sink_port, sink_task, this_policy) if policy_copy != this_policy connection_policy.clear connection_policy.merge!(this_policy) end end else yield(source_port, sink_port, sink_task, policy) end end self end
Yield or enumerates the connections that exist towards the input ports of
sink_task. It includes connections to composition ports (i.e.
exported ports).
# File lib/orocos/roby/connection_graphs.rb, line 410 def each_input_connection(required_port = nil) if !block_given? return enum_for(:each_input_connection) end each_source do |source_task| source_task[self, Flows::DataFlow].each do |(source_port, sink_port), policy| if required_port if sink_port == required_port yield(source_task, source_port, sink_port, policy) end else yield(source_task, source_port, sink_port, policy) end end end end
Yield or enumerates the connections that exist getting out of the ports of
source_task. It does not include connections to composition
ports (i.e. exported ports): these connections are followed until a
concrete port (a port on an actual Orocos task
context) is found.
If required_port is given, it must be a port name, and only
the connections going out of this port will be yield.
# File lib/orocos/roby/connection_graphs.rb, line 509 def each_output_connection(required_port = nil) if !block_given? return enum_for(:each_output_connection, required_port) end each_sink do |sink_task, connections| connections.each do |(source_port, sink_port), policy| if required_port if required_port == source_port yield(source_port, sink_port, sink_task, policy) end else yield(source_port, sink_port, sink_task, policy) end end end self end
Makes sure that self has an input port called
name. It will instanciate a dynamic port if needed.
Raises ArgumentError if no such port can ever exist on self
# File lib/orocos/roby/connection_graphs.rb, line 169 def ensure_has_input_port(name) if !model.find_input_port(name) if model.has_dynamic_input_port?(name) instanciate_dynamic_input(name) else raise ArgumentError, "#{self} has no input port called #{name}" end end end
Makes sure that self has an output port called
name. It will instanciate a dynamic port if needed.
Raises ArgumentError if no such port can ever exist on self
# File lib/orocos/roby/connection_graphs.rb, line 150 def ensure_has_output_port(name) if !model.find_output_port(name) if model.has_dynamic_output_port?(name) instanciate_dynamic_output(name) else raise ArgumentError, "#{self} has no output port called #{name}" end end end
# File lib/orocos/roby/connection_graphs.rb, line 547 def finalized!(timestamp = nil) plan = self.plan super # Do not remove if we are on a running plan. The connection # management code needs to look at these tasks to actually # disconnect them if !plan.executable? || !plan.engine Flows::DataFlow.modified_tasks.delete(self) end end
Forward an input port of a composition to one of its children, or an output port of a composition’s child to its parent composition.
mappings is a hash of the form
source_port_name => sink_port_name
If the self composition is the parent of
target_task, then source_port_name must be an input port of
self and sink_port_name an input port of
target_task.
If self is a child of the target_task
composition, then source_port_name must be an output port of
self and sink_port_name an output port of
target_task.
Raises ArgumentError if one of the specified ports do not exist, or if
target_task and self are not related in the
Dependency relation.
# File lib/orocos/roby/connection_graphs.rb, line 207 def forward_ports(target_task, mappings) if self.child_object?(target_task, Roby::TaskStructure::Dependency) if !fullfills?(Composition) raise ArgumentError, "#{self} is not a composition" end mappings.each do |(from, to), options| ensure_has_input_port(from) target_task.ensure_has_input_port(to) end elsif target_task.child_object?(self, Roby::TaskStructure::Dependency) if !target_task.fullfills?(Composition) raise ArgumentError, "#{self} is not a composition" end mappings.each do |(from, to), options| ensure_has_output_port(from) target_task.ensure_has_output_port(to) end else raise ArgumentError, "#{target_task} and #{self} are not related in the Dependency relation" end add_sink(target_task, mappings) end
# File lib/orocos/roby/connection_graphs.rb, line 460 def has_concrete_input_connection?(required_port) each_concrete_input_connection(required_port) { return true } false end
# File lib/orocos/roby/connection_graphs.rb, line 493 def has_concrete_output_connection?(required_port) each_concrete_output_connection(required_port) { return true } false end
# File lib/orocos/roby/connection_graphs.rb, line 179 def may_have_input_port?(name) model.find_input_port(name) || model.has_dynamic_input_port?(name) end
# File lib/orocos/roby/connection_graphs.rb, line 160 def may_have_output_port?(name) model.find_output_port(name) || model.has_dynamic_output_port?(name) end