module Orocos::RobyPlugin

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.

Constants

ActualDataFlow
BasicObject
COLOR_PALETTE

A set of colors to be used in graphiz graphs

Cmp

Namespace for all defined composition models

ComBus
DataService
Dev

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.

Device
Flows
RequiredDataFlow
Srv

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.

Attributes

buffer_size_margin[R]

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%)

process_servers[R]

The set of known process servers.

It maps the server name to the Orocos::ProcessServer instance

current_color[R]

Used by the to_dot* methods for color allocation

Public Class Methods

allocate_color() click to toggle source

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
apply_requirement_modifications(plan) click to toggle source
# 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
buffer_size_margin=(value) click to toggle source

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
constant_search_path() click to toggle source

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
create_orogen_interface(name = nil, &block) click to toggle source

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_orogen_interfaces(target, interfaces, port_mappings = Hash.new) click to toggle source

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
orogen_project_module(name) click to toggle source

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
placeholder_model_for(name, models) click to toggle source
# 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
require_task(name_or_model) click to toggle source
# File lib/orocos/roby/selection_tasks.rb, line 173
def self.require_task(name_or_model)
    SingleRequirementTask.subplan(name_or_model)
end
requirement_from_name(name) click to toggle source
# File lib/orocos/roby/selection_tasks.rb, line 177
def self.requirement_from_name(name)
    Roby.app.orocos_engine.resolve_name(name)
end
update_connection_policy(old, new) click to toggle source
# 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

Public Instance Methods

all_inputs_connected?() click to toggle source

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
clear_relations() click to toggle source
Calls superclass method
# File lib/orocos/roby/connection_graphs.rb, line 184
def clear_relations
    Flows::DataFlow.remove(self)
    super
end
connect_or_forward_ports(target_task, mappings) click to toggle source

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_ports(target_task, mappings) click to toggle source

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
connected?(port_name) click to toggle source

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
connected_to?(port_name, other_task, other_port) click to toggle source

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
disconnect_port(port_name) click to toggle source
# 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
disconnect_ports(target_task, mappings) click to toggle source
# 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
each_input_connection { |source_task, source_port_name, sink_port_name, policy| ...} click to toggle source

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
each_concrete_output_connection(required_port = nil) { |source_port, sink_port, sink_task, this_policy| ... } click to toggle source
# 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
each_input_connection { |source_task, source_port_name, sink_port_name, policy| ...} click to toggle source

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
each_output_connection { |source_port_name, sink_port_name, sink_port, policy| ...} click to toggle source

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
ensure_has_input_port(name) click to toggle source

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
ensure_has_output_port(name) click to toggle source

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
finalized!(timestamp = nil) click to toggle source
Calls superclass method
# 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_ports(target_task, mappings) click to toggle source

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
has_concrete_input_connection?(required_port) click to toggle source
# 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
has_concrete_output_connection?(required_port) click to toggle source
# 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
may_have_input_port?(name) click to toggle source
# 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
may_have_output_port?(name) click to toggle source
# 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