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:
-
it provides the {Interface::Job} service
-
it has a non-nil {Interface::Job#job_id} argument
-
itself or its planned task is a mission
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:
-
{Interface::Client#pop_notification}: general log messages from {Application#notify}. By default, all log messages generated from {Robot} are forwarded this way
-
{Interface::Client#pop_job_progress}: job progress
-
{Interface::Client#pop_exception}: about exceptions
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
# File lib/roby/interface/job.rb, line 20 def self.allocate_job_id @@job_id += 1 end
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
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
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
# 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
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
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
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
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
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
@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