class Orocos::RobyPlugin::TaskContext

In the orocos/rtt, a task context is what is usually called a component.

Subclasses of TaskContext represent these components in Roby plans, an TaskContext instances may be associated with a Deployment task, that represent the underlying deployment process. The link between a task context and its deployment is usually represented by an executed_by relation.

The task configuration step is managed as follows:

Constants

D_DIFFERENT_MACHINES

Value returned by #distance_to when the tasks are in different processes localized on different machines

D_MAX

Maximum distance value

D_SAME_MACHINE

Value returned by #distance_to when the tasks are in different processes, but on the same machine

D_SAME_PROCESS

Value returned by #distance_to when the tasks are in the same process

STATE_READER_BUFFER_SIZE

Attributes

name[RW]
orogen_spec[RW]

The Orocos::Generation::TaskContext that represents this deployed task context.

state_events[RW]

A state_name => event_name mapping that maps the component’s state names to the event names that should be emitted when it enters a new state.

dynamics[RW]
last_orogen_state[R]

The last state before we went to #orogen_state

orogen_spec[RW]

The Orocos::Generation::TaskDeployment instance that describes the underlying task

orogen_state[R]

The current state for the orogen task. It is a symbol that represents the state name (i.e. :RUNTIME_ERROR, :RUNNING, …)

orogen_task[RW]

The Orocos::TaskContext instance that gives us access to the remote task context. Note that it is set only when the task is started.

task_dynamics[R]

The PortDynamics object that describes the dynamics of the task itself.

The sample_size attribute on this object is ignored. Only the triggers are of any use

Public Class Methods

config_type_from_properties(register = true) click to toggle source

Creates a Ruby class which represents the set of properties that the task context has. The returned class will initialize its members to the default values declared in the oroGen files

# File lib/orocos/roby/task_context.rb, line 141
def self.config_type_from_properties(register = true)
    if @config_type
        return @config_type
    end

    default_values = Hash.new
    task_model = self

    config = Class.new do
        class << self
            attr_accessor :name
        end
        @name = "#{task_model.name}::ConfigType"

        attr_reader :property_names

        task_model.orogen_spec.each_property do |p|
            property_type = Orocos.typelib_type_for(p.type)
          singleton_class.class_eval do
                attr_reader p.name
            end
            instance_variable_set "@#{p.name}", property_type

            default_values[p.name] =
                if p.default_value
                    Typelib.from_ruby(p.default_value, property_type)
                else
                    value = property_type.new
                    value.zero!
                    value
                end

            if property_type < Typelib::CompoundType || property_type < Typelib::ArrayType
                attr_accessor p.name
            else
                define_method(p.name) do
                    Typelib.to_ruby(instance_variable_get("@#{p.name}"))
                end
                define_method("#{p.name}=") do |value|
                    value = Typelib.from_ruby(value, property_type)
                    instance_variable_set("@#{p.name}", value)
                end
            end
        end

        define_method(:initialize) do
            default_values.each do |name, value|
                instance_variable_set("@#{name}", value.dup)
            end
            @property_names = default_values.keys
        end

        class_eval <<-EOD
        def each
            property_names.each do |name|
                yield(name, send(name))
            end
        end
        EOD
    end
    if register && !self.constants.include?(:Config)
        self.const_set(:Config, config)
    end
    @config_type = config
end
configured() click to toggle source

A name => [orogen_model, current_conf] mapping that says if the task named ‘name’ is configured

orogen_model is the model for name and current_conf an array of configuration sections as expected by #conf. It represents the last configuration applied on name

# File lib/orocos/roby/task_context.rb, line 46
def configured; @@configured end
define_from_orogen(task_spec, system_model) click to toggle source

Creates a subclass of TaskContext that represents the given task specification. The class is registered as Roby::Orogen::ProjectName::ClassName.

# File lib/orocos/roby/task_context.rb, line 827
def self.define_from_orogen(task_spec, system_model)
    superclass = task_spec.superclass
    if !(supermodel = Roby.app.orocos_tasks[superclass.name])
        supermodel = define_from_orogen(superclass, system_model)
    end
    klass = system_model.
        task_context(task_spec.name, :child_of => supermodel)

    klass.instance_variable_set :@orogen_spec, task_spec
    
    # Define specific events for the extended states (if there is any)
    state_events = { :EXCEPTION => :exception, :FATAL_ERROR => :fatal_error, :RUNTIME_ERROR => :runtime_error }
    task_spec.states.each do |name, type|
        event_name = name.snakecase.downcase.to_sym
        klass.event event_name
        if type == :fatal
            klass.forward event_name => :fatal_error
        elsif type == :exception
            klass.forward event_name => :exception
        elsif type == :error
            klass.forward event_name => :runtime_error
        end

        state_events[name.to_sym] = event_name
    end
    if supermodel && supermodel.state_events
        state_events = state_events.merge(supermodel.state_events)
    end

    klass.instance_variable_set :@state_events, state_events
    if task_spec.name
        Roby.app.orocos_tasks[task_spec.name] = klass
    end
    klass
end
driver_for(model, arguments = Hash.new, &block) click to toggle source

Declares that this task context model can be used as a driver for the device model.

It will create the corresponding device model if it does not already exist, and return it. See the documentation of Component.data_service for the description of arguments

# File lib/orocos/roby/task_context.rb, line 755
def self.driver_for(model, arguments = Hash.new, &block)
    if model.respond_to?(:to_str)
        service_options, model_options = Kernel.filter_options arguments, Component::DATA_SERVICE_ARGUMENTS
        model = system_model.query_or_create_service_model(
            model, DeviceModel, model_options, &block)
    else
        service_options = arguments
    end

    model = Model.validate_service_model(model, system_model, Device)
    if !model.config_type
        model.config_type = config_type_from_properties
    end
    dserv = provides(model, service_options)
    argument "#{dserv.name}_name"
    dserv
end
each_event_port(&block) click to toggle source
# File lib/orocos/roby/task_context.rb, line 92
def each_event_port(&block)
    orogen_spec.each_event_port(&block)
end
needs_reconfiguration() click to toggle source

A set of names that says if the task named ‘name’ should be reconfigured the next time

# File lib/orocos/roby/task_context.rb, line 50
def needs_reconfiguration; @@needs_reconfiguration end
new(arguments = Hash.new) click to toggle source
Calls superclass method Orocos::RobyPlugin::Component.new
# File lib/orocos/roby/task_context.rb, line 125
def initialize(arguments = Hash.new)
    super

    @allow_automatic_setup = true

    # All tasks start with executable? and setup? set to false
    #
    # Then, the engine will call setup, which will do what it should
    @setup = false
    @required_host = nil
    self.executable = false
end
require_dynamic_service(service_model, options) click to toggle source
# File lib/orocos/roby/task_context.rb, line 863
def self.require_dynamic_service(service_model, options)
    # Verify that there are dynamic ports in orogen_spec that match
    # the ports in service_model.orogen_spec
    service_model.each_input_port do |p|
        if !has_dynamic_input_port?(p.name, p.type)
            raise ArgumentError, "there are no dynamic input ports declared in #{short_name} that match #{p.name}:#{p.type_name}"
        end
    end
    service_model.each_output_port do |p|
        if !has_dynamic_output_port?(p.name, p.type)
            raise ArgumentError, "there are no dynamic output ports declared in #{short_name} that match #{p.name}:#{p.type_name}"
        end
    end
    
    # Unlike #data_service, we need to add the service's interface
    # to our own
    RobyPlugin.merge_orogen_interfaces(orogen_spec, [service_model.orogen_spec])

    # Then we can add the service
    provides(service_model, options)
end
specialize(name) click to toggle source

Creates a private specialization of the current model

# File lib/orocos/roby/task_context.rb, line 71
def specialize(name)
    if self == TaskContext
        raise "#specialize should not be used to create a specialization of TaskContext. Use only on \"real\" task context models"
    end
    klass = new_submodel
    klass.private_specialization = true
    klass.private_model
    klass.name = name
    # The oroGen spec name should be the same, as we need that
    # for logging. Note that the layer itself does not care about the
    # name
    klass.orogen_spec  = RobyPlugin.create_orogen_interface(self.name)
    klass.state_events = state_events.dup
    RobyPlugin.merge_orogen_interfaces(klass.orogen_spec, [orogen_spec])
    klass
end
to_s() click to toggle source
# File lib/orocos/roby/task_context.rb, line 52
def to_s
    services = each_data_service.map do |name, srv|
            "#{name}[#{srv.model.short_name}]"
    end.join(", ")
    if private_specialization?
        "#<specialized from #{superclass.name} services: #{services}>"
    else
        "#<#{name} services: #{services}>"
    end
end
worstcase_processing_time(value) click to toggle source
# File lib/orocos/roby/task_context.rb, line 88
def worstcase_processing_time(value)
    orogen_spec.worstcase_processing_time(value)
end

Public Instance Methods

apply_configuration(config_type) click to toggle source

Applies the values stored in config_type to the task properties.

It is assumed that config_type responds to each, and that the provided each method yields (name, value) pairs. These pairs are then used to call component.name=value to set the values on the component

# File lib/orocos/roby/task_context.rb, line 814
def apply_configuration(config_type)
    config_type.each do |name, value|
        if orogen_task.has_property?(name)
            orogen_task.send("#{name}=", value)
        else
            Robot.warn "ignoring field #{name} in configuration of #{orogen_name} (#{model.orogen_name})"
        end
    end
end
configure() click to toggle source

Default implementation of the configure method.

This default implementation takes its configuration from State.config.task_name, where task_name is the CORBA task name (i.e. the global name of the task).

It then sets the task properties using the values found there

Calls superclass method Orocos::RobyPlugin::Component#configure
# File lib/orocos/roby/task_context.rb, line 780
def configure
    super if defined? super

    # First, set configuration from the configuration files
    # Note: it can only set properties
    conf = self.conf || ['default']
    if Orocos.conf.apply(orogen_task, conf, true)
        Robot.info "applied configuration #{conf} to #{orogen_task.name}"
    end

    # Then set configuration stored in Conf.orocos
    if Roby::Conf.orocos.send("#{orogen_name}?")
        config = Roby::Conf.orocos.send(orogen_name)
        apply_configuration(config)
    end

    # Then set per-device configuration options
    if respond_to?(:each_device)
        each_master_device do |_, device|
            if device.configuration
                apply_configuration(device.configuration)
            elsif device.configuration_block
                device.configuration_block.call(orogen_task)
            end
        end
    end
end
create_state_reader() click to toggle source
# File lib/orocos/roby/task_context.rb, line 420
def create_state_reader
    @state_reader = orogen_task.state_reader(:type => :buffer, :size => STATE_READER_BUFFER_SIZE, :init => true, :transport => Orocos::TRANSPORT_CORBA)
end
distance_to(other) click to toggle source

Returns a value that represents how the two task contexts are far from each other. The possible return values are:

nil

one or both of the tasks are not deployed

D_SAME_PROCESS

both tasks are in the same process

D_SAME_MACHINE

both tasks are in different processes, but on the same machine

D_DIFFERENT_MACHINES

both tasks are in different processes localized on different machines

# File lib/orocos/roby/task_context.rb, line 250
def distance_to(other)
    return if !execution_agent || !other.execution_agent

    if execution_agent == other.execution_agent # same process
        D_SAME_PROCESS
    elsif execution_agent.machine == other.execution_agent.machine # same machine
        D_SAME_MACHINE
    else
        D_DIFFERENT_MACHINES
    end
end
each_input_port(&block) click to toggle source
# File lib/orocos/roby/task_context.rb, line 355
def each_input_port(&block)
    orogen_task.each_input_port(&block)
end
each_output_port(&block) click to toggle source
# File lib/orocos/roby/task_context.rb, line 359
def each_output_port(&block)
    orogen_task.each_output_port(&block)
end
exception_event() click to toggle source

Returns the exception error event object for this task. This event gets emitted whenever the component goes into an exception state.

# File lib/orocos/roby/task_context.rb, line 713
event :exception
fatal_error_event() click to toggle source

Returns the fatal error event object for this task. This event gets emitted whenever the component goes into a fatal error state.

This leads to the component emitting both :failed and :stop

# File lib/orocos/roby/task_context.rb, line 723
event :fatal_error
find_input_port(name) click to toggle source
# File lib/orocos/roby/task_context.rb, line 305
def find_input_port(name)
    if !orogen_task
        raise ArgumentError, "#find_input_port called but we have no task handler yet"
    end
    port = orogen_task.port(name)
    return if port.kind_of?(Orocos::OutputPort)
    port

rescue Orocos::NotFound
end
find_output_port(name) click to toggle source
# File lib/orocos/roby/task_context.rb, line 323
def find_output_port(name)
    if !orogen_task
        raise ArgumentError, "#find_output_port called but we have no task handler yet"
    end
    port = orogen_task.port(name)
    return if port.kind_of?(Orocos::InputPort)
    port

rescue Orocos::NotFound
end
input_port(name) click to toggle source
# File lib/orocos/roby/task_context.rb, line 316
def input_port(name)
    if !(port = find_input_port(name))
        raise ArgumentError, "port #{name} is not an input port in #{self}"
    end
    port
end
input_port_model(name) click to toggle source
# File lib/orocos/roby/task_context.rb, line 341
def input_port_model(name)
    if !(p = orogen_task.input_port_model(name))
        raise ArgumentError, "there is no port #{name} on #{self}"
    end
    p
end
interrupt!() click to toggle source

Interrupts the execution of this task context

# File lib/orocos/roby/task_context.rb, line 662
event :interrupt do |context|
    Robot.info "interrupting #{name}"
    begin
        if !orogen_task # already killed
            emit :interrupt
            emit :aborted
        elsif execution_agent && !execution_agent.finishing?
            orogen_task.stop(false)
        end
    rescue Orocos::CORBA::ComError
        # We actually aborted
        emit :interrupt
        emit :aborted
    rescue Orocos::StateTransitionFailed
        # Use #rtt_state as it has no problem with asynchronous
        # communication, unlike the port-based state updates.
        state = orogen_task.rtt_state
        if state != :RUNNING
            Engine.debug { "in the interrupt event, StateTransitionFailed: task.state == #{state}" }
            # Nothing to do, the poll block will finalize the task
        else
            raise
        end
    end
end
is_setup!() click to toggle source

Announces that the task is indeed setup

This is meant for internal use. Don’t use it unless you know what you are doing

# File lib/orocos/roby/task_context.rb, line 492
def is_setup!
    @setup = true
    if all_inputs_connected?
        self.executable = nil
        Engine.debug { "#{self} is setup and all its inputs are connected, set executable to nil and executable? = #{executable?}" }
    else
        Engine.debug { "#{self} is setup but some of its inputs are not connected, keep executable = #{executable?}" }
    end
end
merge(merged_task) click to toggle source
Calls superclass method Orocos::RobyPlugin::Component#merge
# File lib/orocos/roby/task_context.rb, line 111
def merge(merged_task)
    super
    self.required_host ||= merged_task.required_host

    if merged_task.orogen_spec && !orogen_spec
        self.orogen_spec = merged_task.orogen_spec
    end

    if merged_task.orogen_task && !orogen_task
        self.orogen_task = merged_task.orogen_task
    end
    nil
end
minimal_period() click to toggle source

Returns the minimal period, i.e. the minimum amount of time between two triggers

# File lib/orocos/roby/task_context.rb, line 295
def minimal_period
    task_dynamics.minimal_period
end
needs_reconfiguration!() click to toggle source

Make sure that #configure will be called on this task before it gets started

See also #setup and #needs_reconfiguration?

# File lib/orocos/roby/task_context.rb, line 514
def needs_reconfiguration!
    if orogen_spec
        TaskContext.needs_reconfiguration << orocos_name
    end
end
needs_reconfiguration?() click to toggle source

If true, #configure must be called on this task before it is started. This flag is reset after #configure has been called

# File lib/orocos/roby/task_context.rb, line 504
def needs_reconfiguration?
    if orogen_spec
        TaskContext.needs_reconfiguration.include?(orocos_name)
    end
end
on_same_server?(task) click to toggle source

Returns true if self and task are on the same process server

# File lib/orocos/roby/task_context.rb, line 234
def on_same_server?(task)
    distance_to(task) != D_DIFFERENT_MACHINES
end
operation(name) click to toggle source
# File lib/orocos/roby/task_context.rb, line 363
def operation(name)
    orogen_task.operation(name)
end
orocos_name() click to toggle source

The global name of the Orocos task underlying this Roby task

# File lib/orocos/roby/task_context.rb, line 379
def orocos_name; orogen_spec.name end
orogen_name() click to toggle source

Returns the task name inside the deployment

When using CORBA, this is the CORBA name as well

# File lib/orocos/roby/task_context.rb, line 210
def orogen_name
    orogen_spec.name
end
output_port(name) click to toggle source
# File lib/orocos/roby/task_context.rb, line 334
def output_port(name)
    if !(port = find_output_port(name))
        raise ArgumentError, "port #{name} is not an output port in #{self}"
    end
    port
end
output_port_model(name) click to toggle source
# File lib/orocos/roby/task_context.rb, line 348
def output_port_model(name)
    if !(p = orogen_task.output_port_model(name))
        raise ArgumentError, "there is no port #{name} on #{self}"
    end
    p
end
property(name) click to toggle source
# File lib/orocos/roby/task_context.rb, line 367
def property(name)
    orogen_task.property(name)
end
read_current_state() click to toggle source
# File lib/orocos/roby/task_context.rb, line 386
def read_current_state
    while update_orogen_state
    end
    @orogen_state
end
ready_for_setup?() click to toggle source

Returns true if this component needs to be setup by calling the #setup method, or if it can be used as-is

# File lib/orocos/roby/task_context.rb, line 456
def ready_for_setup?
    # @allow_automatic_setup is being used to sequence the end of a
    # running task with the reconfiguration of the a new one.
    #
    # It MUST be kept here
    if !@allow_automatic_setup
        return false
    elsif !orogen_spec || !orogen_task
        return false
    end

    state = begin orogen_task.rtt_state
            rescue CORBA::ComError
                return false
            end

    return (state == :EXCEPTION || state == :STOPPED || state == :PRE_OPERATIONAL)
end
reusable?() click to toggle source
Calls superclass method Orocos::RobyPlugin::Component#reusable?
# File lib/orocos/roby/task_context.rb, line 520
def reusable?
    super && (!setup? || !needs_reconfiguration?)
end
running_event() click to toggle source

Returns the running event object for this task. This event gets emitted whenever the component goes into the Running state, either because it has just been started or because it left a runtime error state.

# File lib/orocos/roby/task_context.rb, line 697
event :running
runtime_error_event() click to toggle source

Returns the runtime error event object for this task. This event gets emitted whenever the component goes into a runtime error state.

# File lib/orocos/roby/task_context.rb, line 705
event :runtime_error
setup() click to toggle source

Called to configure the component

Calls superclass method Orocos::RobyPlugin::Component#setup
# File lib/orocos/roby/task_context.rb, line 525
def setup
    if !orogen_task
        raise InternalError, "#setup called but there is no orogen_task"
    end

    state = orogen_task.rtt_state

    if ![:EXCEPTION, :PRE_OPERATIONAL, :STOPPED].include?(state)
        raise InternalError, "wrong state in #setup for #{orogen_task}: got #{state}, but only EXCEPTION, PRE_OPERATIONAL and STOPPED are available"
    end

    needs_reconf = false
    if state == :EXCEPTION
        ::Robot.info "reconfiguring #{self}: the task was in exception state"
        orogen_task.reset_exception(false)
        state = orogen_task.rtt_state
        needs_reconf = true
    elsif state == :PRE_OPERATIONAL
        needs_reconf = true
    elsif needs_reconfiguration?
        ::Robot.info "reconfiguring #{self}: the task is marked as needing reconfiguration"
        needs_reconf = true
    else
        _, current_conf = TaskContext.configured[orocos_name]
        if !current_conf
            needs_reconf = true
        elsif current_conf != self.conf
            ::Robot.info "reconfiguring #{self}: configuration changed"
            needs_reconf = true
        end
    end

    if !needs_reconf
        Robot.info "#{self} was already configured"
        is_setup!
        return
    end
    if state == :STOPPED && orogen_task.model.needs_configuration?
        ::Robot.info "cleaning up #{self}"
        cleaned_up = true
        orogen_task.cleanup
    end

    ::Robot.info "setting up #{self}"

    self.conf ||= ['default']

    super

    if !Roby.app.orocos_engine.dry_run? && (cleaned_up || state == :PRE_OPERATIONAL)
        orogen_task.configure(false)
    end
    TaskContext.needs_reconfiguration.delete(orocos_name)
    TaskContext.configured[orocos_name] = [orogen_task.model, self.conf.dup]
end
setup?() click to toggle source

Returns true if the underlying Orocos task has been configured and can be started

The general protocol is:

if !setup? && ready_for_setup?
    setup
end
# File lib/orocos/roby/task_context.rb, line 484
def setup?
    @setup
end
start!() click to toggle source

Optionally configures and then start the component. The start event will be emitted when the it has successfully been configured and started.

# File lib/orocos/roby/task_context.rb, line 592
event :start do |context|
    # Create the state reader right now. Otherwise, we might not get
    # the state updates related to the task's startup
    if orogen_spec.context.extended_state_support?
        create_state_reader
    end

    # At this point, we should have already created all the dynamic
    # ports that are required ... check that
    each_concrete_output_connection do |source_port, _|
        if !orogen_task.has_port?(source_port)
            raise "#{orocos_name}(#{orogen_spec.name}) does not have a port named #{source_port}"
        end
    end
    each_concrete_input_connection do |_, _, sink_port, _|
        if !orogen_task.has_port?(sink_port)
            raise "#{orocos_name}(#{orogen_spec.name}) does not have a port named #{sink_port}"
        end
    end

    ::Robot.info "starting #{to_s} (#{orocos_name})"
    @last_orogen_state = nil
    orogen_task.start(false)
    emit :start
end
start_event() click to toggle source

Returns the start event object for this task

# File lib/orocos/roby/task_context.rb, line 586
            
state_event(name) click to toggle source

Returns the event name that maps to the given component state name

# File lib/orocos/roby/task_context.rb, line 107
def state_event(name)
    model.state_events[name]
end
stop!() click to toggle source

Interrupts the execution of this task context

# File lib/orocos/roby/task_context.rb, line 735
event :stop do |context|
    interrupt!
end
tid() click to toggle source

Returns the thread ID of the thread running this task

Beware, the thread might be on a remote machine !

# File lib/orocos/roby/task_context.rb, line 102
def tid
    orogen_task.tid
end
trigger_latency() click to toggle source

Maximum time between the task is sent a trigger signal and the time it is actually triggered

# File lib/orocos/roby/task_context.rb, line 301
def trigger_latency
    orogen_spec.worstcase_trigger_latency
end
update_input_port_dynamics(port_name) click to toggle source

Tries to update the port dynamics information for the input port port_name based on its inputs

Returns the new PortDynamics object if successful, and nil otherwise

# File lib/orocos/roby/dataflow_dynamics.rb, line 13
def update_input_port_dynamics(port_name)
    dynamics = []
    each_concrete_input_connection(port_name) do |source_task, source_port, sink_port|
        if dyn = source_task.port_dynamics[source_port]
            dynamics << dyn
        else
            return
        end
    end
    dyn = PortDynamics.new("#{name}.#{port_name}")
    dynamics.each { |d| dyn.merge(d) }
    port_dynamics[port_name] = dyn
end
validate_orogen_state_from_rtt_state() click to toggle source
# File lib/orocos/roby/task_context.rb, line 396
def validate_orogen_state_from_rtt_state
    orogen_state = orogen_state
    rtt_state    = orogen_task.rtt_state
    mismatch =
        case rtt_state
        when :RUNNING
            !orogen_task.runtime_state?(orogen_state)
        when :STOPPED
            orogen_state != :STOPPED
        when :RUNTIME_ERROR
            !orogen_task.error_state?(orogen_state)
        when :FATAL_ERROR
            !orogen_task.fatal_error_state?(orogen_state)
        when :EXCEPTION
            !orogen_task.exception_state?(orogen_state)
        end

    if mismatch
        Engine.warn "state mismatch on #{self} between state=#{orogen_state} and rtt_state=#{rtt_state}"
        @orogen_state = rtt_state
        handle_state_changes
    end
end