Author: openrobots@laas.fr

# tcl-genomix

openrobots@laas.fr

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 output 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

The interactive help on port reading procedures describes the output dictionary in details:

    >>> demo::Mobile -h
    Usage:
             demo::Mobile [-c|-connect] [-d|-disconnect] [-h] [--]

    Data dictionary:
    struct Mobile
    | double Mobile.position
    | double Mobile.speed

Optionally, the port reading procedure can be connected or disconnected from the component port by passing the corresponding `-c` or `-d` argument. It can be useful for a client to disconnect from a port that it is not using actively. This can save bandwidth and resources, depending on the underlying middleware.

Reading port data implicitly connects to the port if not already connected. The connect option is thus mostly present for symmetry with disconnect. It can still be used to check for a port existence without reading data, in particular for multiple out ports.

## 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

## Publishing input ports

Each input port of the component has a corresponding procedure in the component namespace, named after the input port name. These procedures can be used to create ports compatible with the component’s input ports. For each newly created port, a corresponding new procedure is dynamically created. It allows to publish contents to the new port. The component can then be configured to read from these ports by using the standard `connect_port` service.

For instance, one `Reference` input port of the `demo` component can be created with the `demo::Reference` procedure by giving it the name of the new port to create. In this example, the port is named `desired`:

    % demo::Reference desired
    demo::Reference::desired

The returned value is the new procedure name that can be used to publish data to the port.

If this port creation procedure is invoked multiple times from the same client with the same port name, it will always return the same port handle function and refer to the same port. This is done so that multiple shells using the same client (i.e. in the same genomixd instance) can acess this port. However, asking for a port name that already exists and was not created by the same client will be denied.

To publish data to the port, the procedure can be invoked with the new data as argument:

    % demo::Reference::desired -h
    Usage:
             demo::Reference::desired [-h] [-p|-port] [-r|-read] [-c|-close]
                     [-f|-flat] [--] ?data?

    Input data:
    struct Reference
    | double Reference.position
    | enum Reference.speed

Data can either be scanned interactively, passed as an ordered flat list or as a single TCL dictionary argument:

    % # interactive scanning
    % demo::Reference::desired
    struct Reference
    | double Reference.position > 0.5
    | enum Reference.speed > ::demo::SLOW
    %
    % # ordered flat list
    % demo::Reference::desired 0.5 ::demo::SLOW
    %
    % # TCL dictionary
    % demo::Reference::desired {Reference {position 0.5 speed ::demo::SLOW}}

The last published port data can also be retrieved by passing the `-data` argument:

    % set data [demo::Reference::desired -data]
    Reference {position 0.5 speed ::demo::SLOW}
    % set p [dict get $data Reference position]
    0.5

Finally, the component input ports can be connected to these new ports by using `connect_port` and giving it the new port name:

    % demo::connect_port {local Reference remote desired}

In scripts, the port publishing procedure can be put in a variable so that it can be passed around without hardcoding the port name. The name of the port can be retrieved from the port publishing procedure with the `-name` argument. The above examples can thus be rewriten as:

    % set ref [demo::Reference desired]
    demo::Reference::desired
    % $ref 0.5 ::demo::SLOW
    % demo::connect_port Reference [$ref -name]

Finaly, those ports can be closed at any time with the `-close` argument. They are also automatically closed as soon as the client is unloaded from genomix (i.e. not when the client shell is closed, only when genomixd is closed or the `unload` command is invoked).

    % demo::Reference::desired -close