Project

General

Profile

tcl-genomix

This documentation presents the TCL interface exposed by genom3 components through a genomix process. Throughout the documentation, a 'demo-ros' component is used as an example.

Setup

First, you need to run the components you wish to control as well as a genomix server (or a rosix server).

For example, to use the 'demo-ros' compiled for the ROS middleware, run the following commands:

$ roscore &       # the ROS communication daemon.
$ genomixd &      # A genomix server
$ demo-ros -b     # The 'demo' component compiled for ROS.

Then, start a tcl interpreter and require the genomix package:

$ tclsh
% package require genomix
1.6.1
%

If this raises an error, such as "can’t find package genomix", make sure the tcl-genomix package is installed and tune the TCLLIBPATH environment variable so that it contains the lib/tcl-genomix directory under the installation prefix of the tcl-genomix package.

Note For interactive use, prefer using the eltclsh interpreter, that offers command line edition, interactive completion, etc.

Connecting to a server and loading clients

genomix::connect

A connection to the genomix server must be established before anything else. To connect to a locally running server on the default 8080 port, simply use genomix::connect. You can optionally specify a remote host name and a port using the genomix::connect host:port syntax:

% genomix::connect                  ;# default connection to localhost:8080
genomix1
% genomix::connect example.com:8080 ;# explicit remote host name and port

The command returns a connection handle, identifying this particular connection. The handle must be stored in a variable for future use.

% set handle [genomix::connect]
genomix1

Multiple connections to different servers can be made simultaneously. The corresponding handles must be stored in different variables.

% set handle_host1 [genomix::connect host1.example.com:8080]
genomix2
% set handle_host2 [genomix::connect host2.example.com:8080]
genomix2

genomix::localhost

When both the client and components run on the same host, a genomixd server is not required. In this case, genomix::localhost can be used in place of genomix::connect.

% set handle [genomix::localhost]
genomix1

The handle can be used in exactly the same way as the one returned by genomix::connect.

$handle load

A connection handle is used to control one or several components. Each communication client for each component must be loaded first, using the load subcommand provided by the handle.

% $handle load demo
demo

If the client cannot be found automatically by genomix, you need to specify the load path. This can be done either by using the 'rpath' handle subcommand (see next section) or by specifying the full path to the client. genom3 clients are found in the lib/genom/*/plugins directory.

% $handle load /home/jdoe/lib/genom/ros/plugins/demo.so ;# full path
demo

By default, the client is loaded in a TCL namespace with the same name as the component. The namespace can be changed by appending as with the desired name to the load command. This is useful to control multiple instances of same component on different hosts:

host1$ demo-ros  ;# runs a demo-ros instance on host1
host2$ demo-ros  ;# runs a demo-ros instance on host2
% $handle_host1 load demo as foo ;# control the demo-ros on host1
foo
% $handle_host2 load demo as bar ;# control the demo-ros on host2
bar

Also by default, the client connects to an instance of component with the same name as the component. With the -i option, the instance name can be changed. This is useful to control multiple instances of the same component on the same host:

host$ demo-ros -i foo  ;# runs a demo-ros instance named foo
host$ demo-ros -i bar  ;# runs a demo-ros instance named bar
tcl% $handle load demo -i foo  ;# control the foo demo-ros instance
foo
tcl% $handle load demo -i bar  ;# control the bar demo-ros instance
bar

$handle rpath

An ordered list of load paths can be specified for a connection using the rpath handle subcommand:

% $handle rpath /home/jdoe/lib/genom/ros/plugins
% $handle rpath /home/jdoe/lib/genom/pocolibs/plugins
% $handle load demo

The paths are searched in order and the first match wins.

Reading ports

Each output port of the component can be read using a dedicated procedure found in the component namespace. For instance, the Mobile output port of the demo component can be read with the demo::Mobile procedure:

% demo::Mobile
Mobile {position 0 speed 0}

The returned value is a TCL dictionary, so that individual members of the port can be easily used in a script:

% set data [demo::Mobile]
Mobile {position 0 speed 0.125}
% set p [dict get $data Mobile position]
0
% set v [dict get $data Mobile speed]
0.125

Invoking services

As for ports, components services are available as dedicated procedures in the component namespace. Services can be invoked synchronously or asynchronously, with an optional callback. An interactive help is available with the -h argument:

% demo::GotoPosition -h
Usage:
         demo::GotoPosition [-id var] [-s|-send] [-a|-ack] [-o|-oneway]
                  [-t|-timeout milli] [-f|-flat] [-args] [-h]
                  [--] input [& ?script?]

Input dictionary:
double posRef: Goto position in m (0)

Synchronous call

In its simplest form, the service is invoked synchronously and the result (i.e. the output data structure) is returned after completion:

% set result [demo::GetSpeed]
speedRef ::demo::SLOW

The result is a TCL dictionary that can be used with regular TCL dictionary commands:

% dict get $result speedRef
::demo::SLOW

If the service has arguments, they can either be scanned interactively, passed as an ordered flat list or as a single TCL dictionary argument:

% demo::GotoPosition              ;# interactive scanning
double posRef: Goto position in m (0) > 1
% demo::GotoPosition 1.0          ;# ordered flat list
% demo::GotoPosition {posRef 1.0} ;# TCL dictionary

A timeout, in milliseconds, can be specified with -t. If the timeout is reached before the service completes, the procedure will raise the timeout error:

% demo::GotoPosition -t 100 {posRef 1.0} ;# 100ms timeout
timeout
Note In order to pass service arguments that start with a leading dash -, such as a negative number, you have to use the -- argument separator to prevent TCL from interpreting the argument as an option. For instance, you can use demo::GotoPosition — -1.0 to go to position -1.0.

If the service raises an exception, the result is a TCL dictionary containing two keys: the exception type associated with the key ex and the detail (if any) associated with the key detail. A regular TCL error is also raised. It can be caught using the native TCL error handling routines:

% demo::GotoPosition 2.0
ex ::demo::TOO_FAR_AWAY detail {overshoot 1}
% if {[catch {demo::GotoPosition 2.0} exception]} {
puts "error! [dict get $exception ex]"
}
error! ::demo::TOO_FAR_AWAY

Asynchronous call

In order to make more complex scripts, all services can be invoked asynchronously. The available options are the following:

  • -send: just send the request, do not wait for any acknowledgement.

  • -ack or final &: send the request, wait for acknowledgement and return.

The most useful form of asynchronous call is to use the & notation that waits for acknowledgment, by the component, that the service has successfully started:

% set request [demo::GotoPosition {posRef 1.0} &]
demo::7

The returned object is a request handle that can be used to track the status of the service. See the $request subcommand section for how to use it.

$request subcommand

The request handle returned by asynchronous calls is used to track the status of a running service:

% $request -h
usage: demo::7 [-t|-timeout ?milli?] [-h] [--] result|wait ?status?|status|abort

Arguments between square brackets '[]' are optional. Words enclosed in question marks '?' needs to be replaced by an appropriate value.

service status

The status of the running service can be queried with status or a particular status can be waited for with wait:

% $request status
sent
% $request wait done

The valid statuses are:

  • sent: the service is still running

  • done: the service has completed sucessfuly

  • error: an exception was raised and the service is stopped

service result

When a service is in the done or error state, the result (i.e. the output data structure) returned by the service (if any) can be retrieved with result. Note that if the service has not terminated yet, the call will block.

% set $request [demo::GetSpeed &]
% $request status
done
% $request result
speedRef ::demo::SLOW

If the service raised an exception, the returned data will indicate this by raising a TCL error when $request result is invoked. In addition, the error message will be a TCL dictionary with two keys: ex containing the exception type and detail containing the exception detail (if any).

% set request [::demo::GotoPosition 2.0 &]
demo::1
% $request result
ex ::demo::TOO_FAR_AWAY detail {overshoot 1}

These error cases can be handled by TCL constructs dealing with errors, such as the catch TCL command:

% set request [::demo::GotoPosition 2.0 &]
% if {[catch {$request result} exception]} {
puts "error! [dict get $exception ex]"
}
error! ::demo::TOO_FAR_AWAY
Note Once a request result is retrieved, the request handle itself becomes invalid and cannot be used anymore.

Aborting a running service

Finally, a running service can be aborted with abort.

% set $request [demo::MoveDistance 1.0 &]
% $request abort

The result can then be queried in the usual way, using $request result.

Asynchronous callback

When using the & notation to invoke a service asynchronously, a callback script can be specified after the final &. The script will be invoked each time the status of the request changes (i.e. when it is sent, done or raises an exception). The script is invoked with a single argument that is the request handle.

% proc report { request } { puts "report: [$request status]" }
% demo::GotoPosition -s {posRef 1.0} & report
demo::7
report: sent
report: done

Oneway call

The oneway call (option -o) should normally not be used: it sends a request blindly, without confirmation and does not track the status of the request. This is useful only for performance reasons, when a service must be invoked at a high frequency (e.g. several hundreds of time per second).

% demo::GotoPosition -o {posRef 1.0} ;# fire and forget