class Roby::Interface::Client

The client-side object that allows to access an interface (e.g. a Roby app) from another process than the Roby controller

Constants

Job

Attributes

actions[R]

@return [Array<Roby::Actions::Model::Action>] set of known actions

commands[R]

@return [Hash] the set of available commands

cycle_index[R]

@return [Integer] index of the last processed cycle

cycle_start_time[R]

@return [Time] time of the last processed cycle

exception_queue[R]

@return [Array<Integer,Array>] list of existing exceptions. The

integer is an ID that can be used to refer to the exception.
It is always growing and will never collide with a notification ID
io[R]

@return [DRobyChannel] the IO to the server

job_progress_queue[R]

@return [Array<Integer,Array>] list of existing job progress

information. The integer is an ID that can be used to refer to the
job progress information.  It is always growing and will never
collide with a job progress and exception ID
notification_queue[R]

@return [Array<Integer,Array>] list of existing notifications. The

integer is an ID that can be used to refer to the notification.
It is always growing and will never collide with an exception ID
pending_async_calls[R]

@return [Array<Hash>] list of the pending async calls

ui_event_queue[R]

@return [Array<Integer,Array>] list of queued UI events. The

integer is an ID that can be used to refer to the exception.
It is always growing and will never collide with a notification ID

Public Class Methods

new(io, id) click to toggle source

Create a client endpoint to a Roby interface [Server]

@param [DRobyChannel] io a channel to the server @param [String] id a unique identifier for this client

(e.g. host:port of the local endpoint when using TCP). It is
passed to the server through {Server#handshake}

@see Interface.connect_with_tcp_to

# File lib/roby/interface/client.rb, line 45
def initialize(io, id)
    @pending_async_calls = Array.new
    @io = io
    @message_id = 0
    @notification_queue = Array.new
    @job_progress_queue = Array.new
    @exception_queue = Array.new
    @ui_event_queue = Array.new

    @actions, @commands = call([], :handshake, id)
end

Public Instance Methods

allocate_message_id() click to toggle source

@api private

Allocation of unique IDs for notification messages

# File lib/roby/interface/client.rb, line 186
def allocate_message_id
    @message_id += 1
end
async_call(path, m, *args, &block) click to toggle source

@api private

Asynchronously call a method on the interface or on one of the interface's subcommands

@param [Array<String>] path path to the subcommand. Empty means on

the interface object itself.

@param [Symbol] m command or action name. Actions are always

formatted as action_name!

@param [Object] args the command or action arguments @return [Object] an Object associated with the call @see async_call_pending?

# File lib/roby/interface/client.rb, line 328
def async_call(path, m, *args, &block)
    raise RuntimeError, "no callback block given" unless block_given?
    if m.to_s =~ /(.*)!$/
        action_name = $1
        if find_action_by_name(action_name)
            path = []
            m = :start_job
            args = [action_name, *args]
        else raise NoSuchAction, "there is no action called #{action_name} on #{self}"
        end
    end
    io.write_packet([path, m, *args])
    pending_async_calls << { block: block, path: path, m: m, args: args }
    pending_async_calls.last.freeze
end
async_call_pending?(a_call) click to toggle source

@api private

Whether the async call is still pending @param [Object] call the Object associated with the call @return [Boolean] true if the async call is pending,

false otherwise
# File lib/roby/interface/client.rb, line 350
def async_call_pending?(a_call)
    pending_async_calls.any? { |item| item.equal?(a_call) }
end
call(path, m, *args) click to toggle source

@api private

Call a method on the interface or on one of the interface's subcommands

@param [Array<String>] path path to the subcommand. Empty means on

the interface object itself.

@param [Symbol] m command or action name. Actions are always

formatted as action_name!

@param [Object] args the command or action arguments @return [Object] the command result, or – in the case of an

action -- the job ID for the newly created action
# File lib/roby/interface/client.rb, line 306
def call(path, m, *args)
    if m.to_s =~ /(.*)!$/
        action_name = $1
        start_job(action_name, *args)
    else
        io.write_packet([path, m, *args])
        result, _ = poll(1)
        result
    end
end
close() click to toggle source

Close the communication channel

# File lib/roby/interface/client.rb, line 63
def close
    io.close
end
closed?() click to toggle source

Whether the communication channel to the server is closed

# File lib/roby/interface/client.rb, line 58
def closed?
    io.closed?
end
create_batch() click to toggle source

Create a batch context

Messages sent to the returned object are validated as much as possible and gathered in a list. Call {#process_batch} to send all the gathered calls at once to the remote server

@return [BatchContext]

# File lib/roby/interface/client.rb, line 520
def create_batch
    BatchContext.new(self)
end
each_job() { |job| ... } click to toggle source

Enumerate the current jobs

# File lib/roby/interface/client.rb, line 497
def each_job
    return enum_for(__method__) if !block_given?
    jobs.each do |job_id, (job_state, placeholder_task, job_task)|
        yield(Job.new(job_id, job_state, placeholder_task, job_task))
    end
end
find_action_by_name(name) click to toggle source

Find an action by its name

This is a local operation using the information gathered at connection time

@param [String] name the name of the action to look for @return [Actions::Models::Action,nil]

# File lib/roby/interface/client.rb, line 84
def find_action_by_name(name)
    actions.find { |act| act.name == name }
end
find_all_actions_matching(matcher) click to toggle source

Finds all actions whose name matches a pattern

@param [#===] matcher the matching object (usually a Regexp or

String)

@return [Array<Actions::Models::Action>]

# File lib/roby/interface/client.rb, line 93
def find_all_actions_matching(matcher)
    actions.find_all { |act| matcher === act.name }
end
find_all_jobs_by_action_name(action_name) click to toggle source

Find all the jobs that match the given action name

@return [Array<Job>]

# File lib/roby/interface/client.rb, line 507
def find_all_jobs_by_action_name(action_name)
    each_job.find_all do |j|
        j.action_model.name == action_name
    end
end
find_subcommand_by_name(name) click to toggle source
# File lib/roby/interface/client.rb, line 539
def find_subcommand_by_name(name)
    commands[name]
end
has_action?(name) click to toggle source

Tests whether the interface has an action with that name

# File lib/roby/interface/client.rb, line 73
def has_action?(name)
    !!find_action_by_name(name)
end
has_exceptions?() click to toggle source

Whether some exception notifications have been queued

# File lib/roby/interface/client.rb, line 265
def has_exceptions?
    !exception_queue.empty?
end
has_job_progress?() click to toggle source

Whether some job progress information is currently queued

# File lib/roby/interface/client.rb, line 201
def has_job_progress?
    !job_progress_queue.empty?
end
has_notifications?() click to toggle source

Whether some generic notifications have been queued

# File lib/roby/interface/client.rb, line 222
def has_notifications?
    !notification_queue.empty?
end
has_ui_event?() click to toggle source

Whether some UI events have been queued

# File lib/roby/interface/client.rb, line 243
def has_ui_event?
    !ui_event_queue.empty?
end
method_missing(m, *args) click to toggle source
# File lib/roby/interface/client.rb, line 543
def method_missing(m, *args)
    if sub = find_subcommand_by_name(m.to_s)
        SubcommandClient.new(self, m.to_s, sub.description, sub.commands)
    else
        call([], m, *args)
    end
end
poll(expected_count = 0) click to toggle source

Polls for new data on the IO channel

@return [Object] a call reply @raise [ComError] if the link seem to be broken @raise [ProtocolError] if some errors happened when validating the

protocol
# File lib/roby/interface/client.rb, line 159
def poll(expected_count = 0)
    result = nil
    timeout = if expected_count > 0 then nil
              else 0
              end

    has_cycle_end = false
    while packet = io.read_packet(timeout)
        has_cycle_end = process_packet(*packet) do |reply_value|
            if result
                raise ProtocolError, "got more than one sync reply in a single poll call"
            end
            result = reply_value
            expected_count -= 1
        end

        if expected_count <= 0
            break if has_cycle_end
            timeout = 0
        end
    end
    return result, has_cycle_end
end
pop_exception() click to toggle source

Remove and return the oldest exception notification

@return [(Integer,Array)] a unique and monotonically-increasing

message ID and the generic notification information as specified
by (Interface#on_exception)
# File lib/roby/interface/client.rb, line 274
def pop_exception
    exception_queue.shift
end
pop_job_progress() click to toggle source

Remove and return the oldest job information message

@return [(Integer,Array)] a unique and monotonically-increasing

message ID and the arguments to job progress as specified on
{Interface#on_job_notification}.
# File lib/roby/interface/client.rb, line 210
def pop_job_progress
    job_progress_queue.shift
end
pop_notification() click to toggle source

Remove and return the oldest generic notification message

@return [(Integer,Array)] a unique and monotonically-increasing

message ID and the generic notification information as specified
by (Application#notify)
# File lib/roby/interface/client.rb, line 231
def pop_notification
    notification_queue.shift
end
pop_ui_event() click to toggle source

Remove the oldest UI event and return it

# File lib/roby/interface/client.rb, line 248
def pop_ui_event
    ui_event_queue.shift
end
process_batch(batch) click to toggle source

Send all commands gathered in a batch for processing on the remote server

@param [BatchContext] batch @return [Array] the return values of each of the calls gathered in

the batch
# File lib/roby/interface/client.rb, line 530
def process_batch(batch)
    ret = call([], :process_batch, batch.__calls)
    BatchContext::Return.from_calls_and_return(batch.__calls, ret)
end
process_packet(m, *args) { |first| ... } click to toggle source

@api private

Process a message as received on {#io}

@return [Boolean] whether the message was a cycle_end message

# File lib/roby/interface/client.rb, line 102
def process_packet(m, *args)
    if m == :cycle_end
        @cycle_index, @cycle_start_time = *args
        return true
    end

    if m == :bad_call
        if !pending_async_calls.empty?
            process_pending_async_call(args.first, nil)
        else
            e = args.first
            raise e, e.message, (e.backtrace + caller)
        end
    elsif m == :reply
        if !pending_async_calls.empty?
            process_pending_async_call(nil, args.first)
        else
            yield args.first
        end
    elsif m == :job_progress
        queue_job_progress(*args)
    elsif m == :notification
        queue_notification(*args)
    elsif m == :ui_event
        queue_ui_event(*args)
    elsif m == :exception
        queue_exception(*args)
    else
        raise ProtocolError, "unexpected reply from #{io}: #{m} (#{args.map(&:to_s).join(",")})"
    end
    false
end
process_pending_async_call(error, result) click to toggle source

@api private

Remove and call the block of a pending async call

# File lib/roby/interface/client.rb, line 148
def process_pending_async_call(error, result)
    current_call = pending_async_calls.shift
    current_call[:block].call(error, result)
end
queue_exception(kind, error, tasks, job_ids) click to toggle source

@api private

Push an exception notification to {#exception_queue}

It can be retrieved with {#pop_exception}

See the yield parameters of {Interface#on_exception} for the overall argument format.

# File lib/roby/interface/client.rb, line 260
def queue_exception(kind, error, tasks, job_ids)
    exception_queue.push [allocate_message_id, [kind, error, tasks, job_ids]]
end
queue_job_progress(kind, job_id, job_name, *args) click to toggle source

@api private

Push a job notification to {#job_progress_queue}

See the yield parameters of {Interface#on_job_notification} for the overall argument format.

# File lib/roby/interface/client.rb, line 196
def queue_job_progress(kind, job_id, job_name, *args)
    job_progress_queue.push [allocate_message_id, [kind, job_id, job_name, *args]]
end
queue_notification(source, level, message) click to toggle source

@api private

Push a generic notification to {#notification_queue}

# File lib/roby/interface/client.rb, line 217
def queue_notification(source, level, message)
    notification_queue.push [allocate_message_id, [source, level, message]]
end
queue_ui_event(event_name, *args) click to toggle source

@api private

Push a UI event to {#ui_event_queue}

# File lib/roby/interface/client.rb, line 238
def queue_ui_event(event_name, *args)
    ui_event_queue.push [allocate_message_id, [event_name, *args]]
end
reload_actions() click to toggle source
# File lib/roby/interface/client.rb, line 535
def reload_actions
    @actions = call([], :reload_actions)
end
start_job(action_name, **arguments) click to toggle source

Start the given job within the batch

@param [Symbol] action_name the action name @param [Hash<Symbol,Object>] arguments the action arguments

@raise [NoSuchAction] if the requested action does not exist

# File lib/roby/interface/client.rb, line 287
def start_job(action_name, **arguments)
    if find_action_by_name(action_name)
        call([], :start_job, action_name, arguments)
    else raise NoSuchAction, "there is no action called #{action_name} on #{self}"
    end
end
to_io() click to toggle source

The underlying IO object

# File lib/roby/interface/client.rb, line 68
def to_io
    io.to_io
end
wait(timeout: nil) click to toggle source

Wait until there is data to process on the IO channel

@param [Numeric,nil] timeout a timeout after which the method

will return. Use nil for no timeout

@return [Boolean] falsy if the timeout was reached, true

otherwise
# File lib/roby/interface/client.rb, line 141
def wait(timeout: nil)
    io.read_wait(timeout: timeout)
end