Project

General

Profile

Tutorial using the 'demo' componment

Launch the 'demo' component, a genomix server and MATLAB

First, you need to run the components you wish to control and the genomix server. For example, to use the 'demo-ros' compiled for the ROS middleware, run the following commands:

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

Then, start MATLAB and make sure the matlab-genomix installation directory is in your userpath.

% matlab &
>> userpath('<prefix>/lib/matlab')

(where <prefix> is the path configured during the installation step).

Connect MATLAB to the genomix server

From MATLAB, connect to the running genomix server. By default, the connection is made on localhost, port 8080, but this can be overriden by passing some extra arguments:

>> client = genomix.client('example.com:80'); % connection to example.com:80
>> client = genomix.client;                   % connection to localhost:8080

client =

  client with no properties.

On success, these commands return a client handle.

Load a remote software component

Use the client handle returned by a successful connection to load new components in MATLAB. Components may have specific load options. You can check the available options before loading a given component with the extra -h argument:

>> client.load('demo', '-h')

ans =

Usage:
	 client.load('demo', [-h], [client options])

Client options:
  -i|--name                  instance name
  -t|--topic_timeout         timeout subsciptions after that many seconds

For instance, loading the 'demo' component without specific options:

>> demo = client.load('demo')

demo =

  component with properties:

        genom_state: [function_handle]
            Monitor: [function_handle]
               Stop: [function_handle]
           GetSpeed: [function_handle]
     abort_activity: [function_handle]
       MoveDistance: [function_handle]
           SetSpeed: [function_handle]
             Mobile: [function_handle]
    connect_service: [function_handle]
       connect_port: [function_handle]
               kill: [function_handle]
       GotoPosition: [function_handle]

On success, a handle on the demo component is returned. Each property in the returned handle is a service or port of the component.

In case of error, it might be that the client cannot be found automatically by genomix. In this case, 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, where * denotes the middleware used, so the following would be required if the demo ros client was installed to the /home/jdoe prefix:

>> demo = client.load('/home/jdoe/lib/genom/ros/plugins/demo.so');

Setting a load path

It can be inconvenient to specify the load path for each component. The rpath client handle command allows to specify a list of paths where genomix will search in order.

If the demo ros client was for instance installed in the /home/jdoe prefix, the following can be used:

>> client.rpath('/home/jdoe/lib/genom/ros/plugins')

ans =

    '/home/jdoe/lib/genom/ros/plugins'

>> demo = client.load('demo');

You need to set the rpath only once for all load requests done with a specific client instance, but you can issue multiple rpath commands in order to specify a list of paths to be searched in order.

Invoke a service

The component handle returned after a successful load is used to invoke remote services. Some services may require input arguments. If you don’t pass any, there are prompted interactively:

>> demo.GotoPosition();
 double posRef: Goto position in m (0) > 1.0

Here, the posRef input argument was asked because GotoPosition() was invoked without any argument.

You can also check the required input arguments by using the -h option:

>> demo.GotoPosition('-h')

ans =

Usage:
	 demo.GotoPosition [-a|-ack] [-t|-timeout secs] [-f|-flat]
		[-args] [-h] [--] input [callback]

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

Finally, you can pass required argument as a flat list, or using a MATLAB struct with appropriate fields:

>> demo.GotoPosition(-1.0);
>> s = struct();
>> s.posRef = 1.0;
>> demo.GotoPosition(s);

Retrieve services output

A request handle is returned for each invoked service. This handle can be used to query the service result after completion:

>> r = demo.GetSpeed();
>> r.status

ans =

done
>> r.result

ans =

    speedRef: '::demo::SLOW'

A result is valid only if the request has the status done. If the service invocation was not successful, the request has the status error and the exception property of the request handle is filled with details:

>> r = demo.GotoPosition(2.0)

r =

  request with properties:

       status: 'error'
       result: []
    exception: [1x1 struct]

>> r.exception

ans =

        ex: '::demo::TOO_FAR_AWAY'
    detail: [1x1 struct]

>> r.exception.detail

ans =

    overshoot: 1

Call services asynchronously

All services can be invoked without blocking. This is especially useful for long lasting actions, where you do not want your MATLAB program to be blocked waiting for the completion of the service.

The first way to invoke a service asynchronously is to use the -a option of the service invocation:

>> r = demo.GotoPosition('-a', 1.0)

r =

  request with properties:

       status: 'sent'
       result: []
    exception: []

Here the request handle r is returned immediately. It has the status sent initially, meaning that the request was successfully sent but has not completed yet. Later on, it is possible to wait for completion with the wait method of the request handle, with or without a timeout:

>> r.wait(0.1) % wait at most 100ms
Error using genomix.request/wait (line 125)
timeout
>> r.wait % wait until completion or error
>> r

r =

  request with properties:

       status: 'done'
       result: [1x1 struct]
    exception: []

Or you can also do some polling:

>> while strcmp(r.status, 'sent') pause(0.1); end

The second way to invoke a service asynchronously is to pass a callback as the last argument of a the service invocation:

>> r = demo.GotoPosition(0.0, @(r) disp(['callback with status: ' r.status]))
callback with status: sent

r =

  request with properties:

       status: 'sent'
       result: []
    exception: []

The callback function is invoked each time the request status is updated sent. In the example above it is invoked when the status changes to sent after a successful invocation.

Callback events are processed implicitly each time a 'genomix' object is accessed (e.g. checking the status of any ongoing request, or calling any service of any component, etc.). 'genomix' also provides an explicit update method in client handles for checking pending events, that can be invoked anywhere in your code:

>> client.update
callback with status: done

In this example, the callback registered in the previous request invocation was triggered because the asynchronous request had completed at the time the update method was invoked.

Read component ports

In addition to functions for calling services, it is also possible to read the data ports of components. For instance, the demo component has a port called Mobile:

>> p = demo.Mobile()

p =

    Mobile: [1x1 struct]

>> p.Mobile

ans =

    position: 0
       speed: 0

Ports are always read synchronously.

Publish input ports

Each input port of the component has a corresponding procedure in the component handle, 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 function handle 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:

>> desired = demo.Reference('desired')

desired =

  function_handle with value:

    @(varargin)genomix.client.pubid(port,varargin{:})

The returned value is the new function handle 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 function handle can be invoked with the new data as argument:

>> desired('-h')

ans =

    'Usage:
         Reference::-h [-d|-data] [-c|-close] [-n|-name] [-h]
                [-f|-flat] [--] data

     Input dictionary:
     struct Reference
     | double Reference.position
     | enum Reference.speed'

Data can either be scanned interactively, passed as an ordered flat list or as a single MATLAB struct with appropriate fields:

>> % interactive scanning
>> desired()
 struct Reference
|  double Reference.position > 0.5
|  enum Reference.speed > '::demo::SLOW'
>>
>> % ordered flat list
>> desired(0.5, '::demo::SLOW')
>>
>> % MATLAB struct
>> s = struct();
>> s.Reference.position = 0.5;
>> s.Reference.speed = '::demo::SLOW';
>> desired(s)

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

>> d = desired('-data')

d =

  struct with fields:

    Reference: [1×1 struct]

>> d.Reference

ans =

  struct with fields:

    position: 0.5000
       speed: '::demo::SLOW'

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('Reference', 'desired');

The name of the port can be retrieved with the -name argument:

>> desired('-name')
ans =

    'desired'

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).

>> desired('-close')

End the session

Where you are done, cleaning up things is done in a straightforward way by deleting the objects you no longer need:

>> delete(demo)
>> delete(client)

Deleting the client closes the connection to the genomix server.