tcl-genomix
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.
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
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
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
Once a request result is retrieved, the request handle itself becomes invalid and cannot be used anymore. |
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