module Roby::Interface

High-level command and control of a Roby controller

The {Interface} module provides a high-level control interface to a running Roby controller. It is the basis for all remote Roby UIs such as the Syskit IDE or the Roby shell. The following documentation aims at giving a bird eye's view of the module's structure

Jobs

The high-level construct used in the Roby interface is the job. Jobs are representation of the high-level goals that a user gave to the system. A task represents a job if:

In case a job task is a planning task, the job itself will be represented by the job's planned task. Across the job-related APIs, one will see that jobs are therefore associated with two tasks: the task or placeholder task, and the job task itself.

The interface APIs provide ways to track the progress of jobs. Each job transition is represented by a Interface::JOB_* constant (e.g. {Interface::JOB_READY}), and notifications are sent to remote endpoints about the current state and progress of jobs.

Synchronous Client/Server API

A Roby application will in most cases create an {Interface::Interface} object, which is the endpoint for all interface-related matters. A client/server mechanism allows to access the app's interface. {Interface::Server} provides the server-side and {Interface::Client} the client-side. Both classes are independent of the communication channel used. The communication is based on marshalling and demarshalling of an array that represents a method name and arguments on the {Interface::Interface} class. The marshalling/demarshalling and the exact packet format is left to the channel class given to Client and Server at construction time (see below)

The core of the method calls on {Interface::Client} are the calls available on {Interface::Interface}. Check the latter to figure out what you can do with the former. In addition, it supports starting actions (and jobs) using an action_name!(arguments) syntax. This is meant as syntactic sugar for use in interactive implementations, but one should use {Interface::Interface#start_job} when starting jobs programmatically.

In addition to the remote method calls, the Client API provides notifications pushed by the interface:

Asynchronous API

To connect to the client/server API, one has to have a remote Roby app to connect to. Moreoover, the API is really designed as a request/response scheme, which is not a very nice format to build UIs from.

For these, reasons, a higher level, event-based API has been built on top of the client/server functionality. The main entrypoint for this asynchronous API is {Interface::Async::Interface}. In addition to properly handling (re)connections, this API provides also a nicer interface to job tracking.

Jobs are represented by {Async::JobMonitor} objects, which track the job state and provide operations on them such as killing, dropping and restarting them as well as registering hooks to track their progress. One usually gets these job monitor objects by listening for new jobs using {Async::Interface#on_job}.

Note that in most cases, new job monitor objects are inactive (i.e. won't get notifications) until you explicitely call {Async::JobMonitor#start} on them. Whether this is the case or not is documented on each method that return or yield a job monitor object.

Asynchronous log stream API

In addition to the notifications provided by {Interface::Client}, one can use the Roby logging to build a complete representation of a plan. The {Interface::Async::Log} class gives easy-to-use access to such a rebuilt plan, along with the ability to disconnect and reconnect to a remote Roby app.

Event Loop Integration

{Interface::Interface} hooks itself in the app's main event loop, as does {Interface::TCPServer}. On the client side, processing is done in {Interface::Client#poll} which therefore needs to be called periodically within your app's main loop. In Qt, it usually means starting a timer

timer = Qt::Timer.new(self)
timer.connect(SIGNAL('timeout()')) do
    client.poll
end

Communication Channel

{Interface::DRobyChannel} provides a default implementation, using the DRoby marshalling/demarshalling for object-to-binary translation, WebSockets for framing and a subclass of IO as the underlying communication medium. The most common usage is to spawn a TCP server based on this channel with {Interface::TCPServer}, and connect to it from the client side with {Interface.connect_with_tcp_to}. A Roby application spawns such a server automatically by calling {Roby::Application#setup_shell_interface} if {Roby::Application#public_shell_interface?} is true.

Constants

DEFAULT_PORT
DEFAULT_REST_PORT
JOB_DROPPED

The job has been dropped, i.e. its mission status has been removed

JOB_FAILED

The job has failed

JOB_FINALIZED

The job has been finalized (i.e. removed from plan)

JOB_FINISHED

The job has finished

JOB_LOST

The job got replaced by a task that is not this job

JOB_MONITORED

Initial notification, when the interface starts monitoring a job

JOB_PLANNING

The job's planning task is running

JOB_PLANNING_FAILED

The job's planning task has failed

JOB_PLANNING_READY

The job's planning task is ready to be executed

JOB_READY

The job's main task is ready to be executed

JOB_REPLACED

The job placeholder task got replaced, and the replacement is managed under the same job

JOB_STARTED

The job is started

JOB_SUCCESS

The job has finished successfully

Public Class Methods

allocate_job_id() click to toggle source
# File lib/roby/interface/job.rb, line 20
def self.allocate_job_id
    @@job_id += 1
end
connect_with_tcp_to(host, port = DEFAULT_PORT, marshaller: DRoby::Marshal.new(auto_create_plans: true)) click to toggle source

Connect to a Roby controller interface at this host and port

@return [Client] the client object that gives access

# File lib/roby/interface/tcp.rb, line 146
def self.connect_with_tcp_to(host, port = DEFAULT_PORT,
        marshaller: DRoby::Marshal.new(auto_create_plans: true))
    require 'socket'
    socket = TCPSocket.new(host, port)
    addr = socket.addr(true)
    Client.new(DRobyChannel.new(socket, true,
        marshaller: DRoby::Marshal.new(auto_create_plans: true)),
        "#{addr[2]}:#{addr[1]}")

rescue Errno::ECONNREFUSED => e
    raise ConnectionError, "failed to connect to #{host}:#{port}: #{e.message}",
        e.backtrace
rescue SocketError => e
    raise e, "cannot connect to host '#{host}' port '#{port}': #{e.message}",
        e.backtrace
rescue ::Exception
    if socket && !socket.closed?
        socket.close
    end
    raise
end
error_state?(state) click to toggle source

Tests if the given state (one of the JOB_ constants) means that the job finished with error

# File lib/roby/interface/interface.rb, line 52
def self.error_state?(state)
    [JOB_PLANNING_FAILED, JOB_FAILED].include?(state)
end
finalized_state?(state) click to toggle source

Tests if the given state (one of the JOB_ constants) means that the job has been finalized (removed from plan)

# File lib/roby/interface/interface.rb, line 64
def self.finalized_state?(state)
    [JOB_FINALIZED].include?(state)
end
new(arguments = Hash.new) click to toggle source
Calls superclass method
# File lib/roby/interface/job.rb, line 38
def initialize(arguments = Hash.new)
    super
    if Conf.app.auto_allocate_job_ids? && !job_id
        allocate_job_id
    end
end
planning_finished_state?(state) click to toggle source

Whether the given state indicates that the job's planning is finished

# File lib/roby/interface/interface.rb, line 34
def self.planning_finished_state?(state)
    ![JOB_PLANNING_READY, JOB_PLANNING, JOB_FINALIZED].include?(state)
end
running_state?(state) click to toggle source

Tests if the given state (one of the JOB_ constants) means that the job is still running

# File lib/roby/interface/interface.rb, line 58
def self.running_state?(state)
    [JOB_STARTED].include?(state)
end
success_state?(state) click to toggle source

Tests if the given state (one of the JOB_ constants) means that the job finished successfully

# File lib/roby/interface/interface.rb, line 46
def self.success_state?(state)
    [JOB_SUCCESS].include?(state)
end
terminal_state?(state) click to toggle source

Tests if the given state (one of the JOB_ constants) is terminal, e.g. means that the job is finished

# File lib/roby/interface/interface.rb, line 40
def self.terminal_state?(state)
    [JOB_PLANNING_FAILED, JOB_FAILED, JOB_SUCCESS, JOB_FINISHED, JOB_FINALIZED].include?(state)
end

Public Instance Methods

allocate_job_id() click to toggle source

Automatically allocate a job ID

@return [Integer]

# File lib/roby/interface/job.rb, line 28
def allocate_job_id
    self.job_id ||= Job.allocate_job_id
end
job_name() click to toggle source

@return [String] the job name as should be displayed by the job

management API
# File lib/roby/interface/job.rb, line 34
def job_name
    to_s
end