Subsections

4. Writing a TANGO client using TANGO APIs


4.1 Introduction


TANGO devices and database are implemented using the TANGO device server model. To access them the user has the CORBA interface e.g. command_inout(), write_attributes() etc. defined by the idl file. These methods are very low-level and assume a good working knowledge of CORBA. In order to simplify this access, high-level api in C++ and Java have been implemented which hides all CORBA aspects of TANGO. In addition the api hides details like how to connect to a device via the database, how to reconnect after a device has been restarted, how to correctly pack and unpack attributes and so on by implementing these in a manner transparent to the user. The api provides a unified error handling for all TANGO and CORBA errors. Unlike the CORBA C++ bindings the TANGO api supports native C++ data types e.g. strings and vectors.
This chapter describes how to use these API's. It is not a reference guide. See chapter [*] for the C++ API details or chapter [*] for a Java API reference guide.

4.2 Getting Started


Refer to the chapter Getting Started for an example on getting start with the C++ or Java api.

4.3 Basic Philosophy


The basic philosophy is to have high level classes to deal with Tango devices. To communicate with Tango device, uses the DeviceProxy class. To send/receive data to/from Tango device, uses the DeviceData or DeviceAttribute classes. To communicate with a group of devices, use the Group class. If you are interested only in some attributes provided by a Tango device, uses the AttributeProxy class. Even if the Tango database is implemented as any other devices (and therefore accessible with one instance of a DeviceProxy class), specific high level classes have been developped to query it. Uses the Database, DbDevice, DbClass, DbServer or DbData classes when interfacing the Tango database. Callback for asynchronous requests or events are implemented via a CallBack class. An utility class called ApiUtil is also available.

4.4 Data types


The definition of the basic data type you can transfert using Tango is:



Tango type name C++ equivalent type
DevBoolean boolean
DevShort short
DevLong int (always 32 bits data)
DevLong64 long long on 32 bits chip or long on 64 bits chip
  always 64 bits data
DevFloat float
DevDouble double
DevString char *
DevEncoded structure with 2 fields: a string and an array of unsigned char
DevUChar unsigned char
DevUShort unsigned short
DevULong unsigned int (always 32 bits data)
DevULong64 unsigned long long on 32 bits chip or unsigned long on 64 bits chip
  always 64 bits data
DevState Tango specific data type





Using commands, you are able to transfert all these data types, array of these basic types and two other Tango specific data types called DevVarLongStringArray and DevVarDoubleStringArray. See chapter [*] to get details about them. You are also able to create attributes using any of these basic data types to transfer data between clients and servers.


4.5 Request model


For the most important API remote calls (command_inout, read_attribute(s) and write_attribute(s)), Tango supports two kind of requests which are the synchronous model and the asynchronous model. Synchronous model means that the client wait (and is blocked) for the server to send an answer. Asynchronous model means that the client does not wait for the server to send an answer. The client sends the request and immediately returns allowing the CPU to do anything else (like updating a graphical user interface). Within Tango, there are two ways to retrieve the server answer when using asynchronous model. They are:
  1. The polling mode
  2. The callback mode
In polling mode, the client executes a specific call to check if the answer is arrived. If this is not the case, an exception is thrown. If the reply is there, it is returned to the caller and if the reply was an exception, it is re-thrown. There are two calls to check if the reply is arrived: In callback model, the caller must supply a callback method which will be executed when the command returns. They are two sub-modes:
  1. The pull callback mode
  2. The push callback mode
In the pull callback mode, the callback is triggered if the server answer is arrived when the client decide it by calling a synchronization method (The client pull-out the answer). In push mode, the callback is executed as soon as the reply arrives in a separate thread (The server pushes the answer to the client).

4.5.1 Synchronous model


Synchronous access to Tango device are provided using the DeviceProxy or AttributeProxy class. For the DeviceProxy class, the main synchronous call methods are : For commands, data are send/received to/from device using the DeviceData class. For attributes, data are send/received to/from device attribute using the DeviceAttribute class.
In some cases, only attributes provided by a Tango device are interesting for the application. You can use the AttributeProxy class. Its main synchronous methods are : Data are transmitted using the DeviceAttribute class.

4.5.2 Asynchronous model


Asynchronous access to Tango device are provided using DeviceProxy or AttributeProxy, CallBack and ApiUtil classes methods. The main asynchronous call methods and used classes are : For commands, data are send/received to/from device using the DeviceData class. For attributes, data are send/received to/from device attribute using the DeviceAttribute class. It is also possible to generate asynchronous request(s) using the AttributeProxy class following the same schema than above. Methods to use are :


4.6 Events


4.6.1 Introduction


Events are a critical part of any distributed control system. Their aim is to provide a communication mechanism which is fast and efficient.
The standard CORBA communication paradigm is a synchronous or asynchronous two-way call. In this paradigm the call is initiated by the client who contacts the server. The server handles the client's request and sends the answer to the client or throws an exception which the client catches. This paradigm involves two calls to receive a single answer and requires the client to be active in initiating the request. If the client has a permanent interest in a value he is obliged to poll the server for an update in a value every time. This is not efficient in terms of network bandwidth nor in terms of client programming.
For clients who are permanently interested in values the event-driven communication paradigm is a more efficient and natural way of programming. In this paradigm the client registers her interest once in an event (value). After that the server informs the client every time the event has occurred. This paradigm avoids the client polling, frees it for doing other things, is fast and makes efficient use of the network.
The rest of this chapter explains how the TANGO events are implemented and the application programmer's interface.

4.6.2 Event definition


TANGO events represent an alternative channel for reading TANGO device attributes. Device attributes values are sent to all subscribed clients when an event occurs. Events can be an attribute value change, a change in the data quality or a periodically send event. The clients continue receiving events as long as they stay subscribed. Most of the time, the device server polling thread detects the event and then pushes the device attribute value to all clients. Nevertheless, in some cases, the delay introduced by the polling thread in the event propagation is detrimental. For such cases, some API calls directly push the event. Until TANGO release 8, the omniNotify implementation of the CORBA Notification service was used to dispatch events. Starting with TANGO 8, this CORBA Notification service has been replaced by the ZMQ library which implements a Publish/Subscribe communication model well adapted to TANGO events communication.

4.6.3 Event types


The following five event types have been implemented in TANGO :
  1. change - an event is triggered and the attribute value is sent when the attribute value changes significantly. The exact meaning of significant is device attribute dependent. For analog and digital values this is a delta fixed per attribute, for string values this is any non-zero change i.e. if the new attribute value is not equal to the previous attribute value. The delta can either be specified as a relative or absolute change. The delta is the same for all clients unless a filter is specified (see below). To easily write applications using the change event, it is also triggered in the following case :
    1. When a spectrum or image attribute size changes.
    2. At event subscription time
    3. When the polling thread receives an exception during attribute reading
    4. When the polling thread detects that the attribute quality factor has changed.
    5. The first good reading of the attribute after the polling thread has received exception when trying to read the attribute
    6. The first time the polling thread detects that the attribute quality factor has changed from INVALID to something else
    7. When a change event is pushed manually from the device server code. (DeviceImpl::push_change_event()).
    8. By the methods Attribute::set_quality() and Attribute::set_value_date_quality() if a client has subscribed to the change event on the attribute. This has been implemented for cases where the delay introduced by the polling thread in the event propagation is not authorized.
  2. periodic - an event is sent at a fixed periodic interval. The frequency of this event is determined by the event_period property of the attribute and the polling frequency. The polling frequency determines the highest frequency at which the attribute is read. The event_period determines the highest frequency at which the periodic event is sent. Note if the event_period is not an integral number of the polling period there will be a beating of the two frequencies4.1. Clients can reduce the frequency at which they receive periodic events by specifying a filter on the periodic event counter.
  3. archive - an event is sent if one of the archiving conditions is satisfied. Archiving conditions are defined via properties in the database. These can be a mixture of delta_change and periodic. Archive events can be send from the polling thread or can be manually pushed from the device server code (DeviceImpl::push_archive_event()).
  4. attribute configuration - an event is sent if the attribute configuration is changed.
  5. data ready - This event is sent when coded by the device server programmer who uses a specific method of one of the Tango device server class to fire the event (DeviceImpl::push_data_ready_event()). The rule of this event is to inform a client that it is now possible to read an attribute. This could be useful in case of attribute with many data.
  6. user - The criteria and configuration of these user events are managed by the device server programmer who uses a specific method of one of the Tango device server class to fire the event (DeviceImpl::push_event()).
The first three above events are automatically generated by the TANGO library or fired by the user code. Even number 4 is only automatically sent by the library and the last two are fired only by the user code.

4.6.4 Event filtering (Removed in Tango release 8 and above)


Please, note that this feature is available only for Tango releases older than Tango 8. The CORBA Notification Service allows event filtering. This means that a client can ask the Notification Service to send the event only if some filter is evaluated to true. Within the Tango control system, some pre-defined fields can be used as filter. These fields depend on the event type.



Event type Filterable field name Filterable field value type
  delta_change_rel Relative change (in %) since last event double
delta_change_abs Absolute change since last event double
change quality Is set to 1 when the attribute quality factor has double
  changed, otherwise it is 0  
forced_event Is set to 1 when the event was fired on exception double
  or a quality factor set to invalid  
periodic counter Incremented each time the event is sent long
delta_change_rel Relative change (in %) since last event double
delta_change_abs Absolute change since last event double
  quality Is set to 1 when the attribute quality factor has double
    changed, otherwise it is 0  
archive   Incremented each time the event is sent  
counter for periodic reason. Set to -1 if event long
  sent for change reason  
forced_event Is set to 1 when the event was fired on exception double
  or a quality factor set to invalid  
  delta_event Number of milli-seconds since previous event double





Filter are defined as a string following a grammar defined by CORBA. It is defined in [18]. The following example shows you the most common use of these filters in the Tango world : For user events, the filter field name(s) and their value are defined by the device server programmer.

4.6.5 Application Programmer's Interface


How to setup and use the TANGO events ? The interfaces described here are intended as user friendly interfaces to the underlying CORBA calls. The interface is modeled after the asynchronous command_inout() interface so as to maintain coherency. The event system supports push callback model as well as the pull callback model.
The two event reception modes are: The event reception buffer in the pull callback model, is implemented as a round robin buffer. The client can choose the size when subscribing for the event. This way the client can set-up different ways to receive events.

4.6.5.1 Configuring events


The attribute configuration set is used to configure under what conditions events are generated. A set of standard attribute properties (part of the standard attribute configuration) are read from the database at device startup time and used to configure the event engine. If there are no properties defined then default values specified in the code are used.

4.6.5.1.1 change


The attribute properties and their default values for the change event are :
  1. rel_change - a property of maximum 2 values. It specifies the positive and negative relative change of the attribute value w.r.t. the value of the previous change event which will trigger the event. If the attribute is a spectrum or an image then a change event is generated if any one of the attribute value's satisfies the above criterium. If only one property is specified then it is used for the positive and negative change. If no property is specified, no events are generated.
  2. abs_change - a property of maximum 2 values.It specifies the positive and negative absolute change of the attribute value w.r.t the value of the previous change event which will trigger the event. If the attribute is a spectrum or an image then a change event is generated if any one of the attribute value's satisfies the above criterium. If only one property is specified then it is used for the positive and negative change. If no properties are specified then the relative change is used.

4.6.5.1.2 periodic


The attribute properties and their default values for the periodic event are :
  1. event_period - the minimum time between events (in milliseconds). If no property is specified then a default value of 1 second is used.

4.6.5.1.3 archive


The attribute properties and their default values for the archive event are :
  1. archive_rel_change - a property of maximum 2 values which specifies the positive and negative relative change w.r.t. the previous attribute value which will trigger the event. If the attribute is a spectrum or an image then an archive event is generated if any one of the attribute value's satisfies the above criterium. If only one property is specified then it is used for the positive and negative change. If no properties are specified then no events are generate.
  2. archive_abs_change - a property of maximum 2 values which specifies the positive and negative absolute change w.r.t the previous attribute value which will trigger the event. If the attribute is a spectrum or an image then an archive event is generated if any one of the attribute value's satisfies the above criterium. If only one property is specified then it is used for the positive and negative change. If no properties are specified then the relative change is used.
  3. archive_period - the minimum time between archive events (in milliseconds). If no property is specified, no periodic archiving events are send.

4.6.5.2 C++ Clients


This is the interface for clients who want to receive events. The main action of the client is to subscribe and unsubscribe to events. Once the client has subscribed to one or more events the events are received in a separate thread by the client.
Two reception modes are possible: The mode to be used has to be chosen when subscribing for the event.

4.6.5.2.1 Subscribing to events


The client call to subscribe to an event is named DeviceProxy::subscribe_event() . During the event subscription the client has to choose the event reception mode to use.
Push model:
int DeviceProxy::subscribe_event( 
             const string &attribute, 
             Tango::EventType event, 
             Tango::CallBack *callback,
             bool stateless = false);
The client implements a callback method which is triggered when the event is received. Note that this callback method will be executed by a thread started by the underlying ORB. This thread is not the application main thread. For Tango releases before 8, a similar call with one extra parameter for event filtering is also available.
Pull model:
int DeviceProxy::subscribe_event( 
             const string &attribute, 
             Tango::EventType event, 
             int event_queue_size,
             bool stateless = false);
The client chooses the size of the round robin event reception buffer. Arriving events will be buffered until the client uses DeviceProxy::get_events() to extract the event data. For Tango releases before 8, a similar call with one extra parameter for event filtering is also available.
On top of the user filter defined by the filters parameter, basic filtering is done based on the reason specified and the event type. For example when reading the state and the reason specified is change the event will be fired only when the state changes. Events consist of an attribute name and the event reason. A standard set of reasons are implemented by the system, additional device specific reasons can be implemented by device servers programmers.
The stateless flag = false indicates that the event subscription will only succeed when the given attribute is known and available in the Tango system. Setting stateless = true will make the subscription succeed, even if an attribute of this name was never known. The real event subscription will happen when the given attribute will be available in the Tango system.
Note that in this model, the callback method will be executed by the thread doing the DeviceProxy::get_events() call.

4.6.5.2.2 The CallBack class


In C++, the client has to implement a class inheriting from the Tango CallBack class and pass this to the DeviceProxy::subscribe_event() method. The CallBack class is the same class as the one proposed for the TANGO asynchronous call. This is as follows for events :
class MyCallback : public Tango::CallBack
{
   .
   .
   .
   virtual push_event(Tango::EventData *);
   virtual push_event(Tango::AttrConfEventData *);
   virtual push_event(Tango::DataReadyEventData *);
}
where EventData is defined as follows :
class EventData 
{
   DeviceProxy *device;
   string &attr_name;
   string &event;
   DeviceAttribute *attr_value;
   bool err;
   DevErrorList &errors;
}
AttrConfEventData is defined as follows :
class AttrConfEventData 
{
   DeviceProxy *device;
   string &attr_name;
   string &event;
   AttributeInfoEx*attr_conf;
   bool err;
   DevErrorList &errors;
}
and DataReadyEventData is defined as follows :
class DataReadyEventData 
{
   DeviceProxy *device;
   string &attr_name;
   string &event;
   int attr_data_type;
   int ctr;
   bool err;
   DevErrorList &errors;
}
In push model, there are some cases (same callback used for events coming from different devices hosted in device server process running on different hosts) where the callback method could be executed concurently by different threads started by the ORB. The user has to code his callback method in a thread safe manner.

4.6.5.2.3 Unsubscribing from an event


Unsubscribe a client from receiving the event specified by event_id is done by calling the DeviceProxy::unsubscribe_event() method :
void DeviceProxy::unsubscribe_event(int event_id);

4.6.5.2.4 Extract buffered event data


When the pull model was chosen during the event subscription, the received event data can be extracted with DeviceProxy::get_events(). Two possibilities are available for data extraction. Either a callback method can be executed for every event in the buffer when using
int DeviceProxy::get_events( 
             int event_id, 
             CallBack *cb);
Or all the event data can be directly extracted as EventDataList, AttrConfEventDataList or DataReadyEventDataList when using
int DeviceProxy::get_events( 
             int event_id, 
             EventDataList &event_list);
int DeviceProxy::get_events( 
             int event_id, 
             AttrConfEventDataList &event_list);
int DeviceProxy::get_events( 
             int event_id, 
             DataReadyEventDataList &event_list);
The event data lists are vectors of EventData, AttrConfEventData or DataReadyEventData pointers with special destructor and clean-up methods to ease the memory handling.
class EventDataList:public vector<EventData *>
class AttrConfEventDataList:public vector<AttrConfEventData *>
class DataReadyEventDataList:public vector<DataReadyEventDataList *>

4.6.5.2.5 Example


Here is a typical code example of a client to register and receive events. First, you have to define a callback method as follows:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

class DoubleEventCallBack : public Tango::CallBack 
{
   void push_event(Tango::EventData*);
}; 
 
void DoubleEventCallBack::push_event(Tango::EventData *myevent)
{
    Tango::DevVarDoubleArray *double_value;
    try
    {
        cout << DoubleEventCallBack::push_event(): called attribute  
             << myevent->attr_name
             <<  event 
             << myevent->event 
             <<  (err=
             << myevent->err
             << ) << endl;
 
         if (!myevent->err)
         {
             myevent->attr_value >> double_value;
             cout << double value 
                  << (*double_value)[0]
                  << endl;
             delete double_value;
         }
    }
    catch (...)
    {
         cout << DoubleEventCallBack::push_event(): could not extract data !\n;
    }
}

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Then the main code must subscribe to the event and choose the push or the pull model for event reception.
Push model:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

DoubleEventCallBack *double_callback = new DoubleEventCallBack; 
      
Tango::DeviceProxy *mydevice = new Tango::DeviceProxy(my/device/1);
 
int event_id;
const string attr_name(current);
event_id = mydevice->subscribe_event(attr_name, 
                         Tango::CHANGE_EVENT,
                         double_callback);
cout << event_client() id =  << event_id << endl;
// The callback methods are executed by the Tango event reception thread.
// The main thread is not concerned of event reception.
// Whatch out with synchronisation and data access in a multi threaded environment!
sleep(1000); // wait for events
 
event_test->unsubscribe_event(event_id);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Pull model:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

DoubleEventCallBack *double_callback = new DoubleEventCallBack;
int event_queue_size = 100; // keep the last 100 events
      
Tango::DeviceProxy *mydevice = new Tango::DeviceProxy(my/device/1);
 
int event_id;
const string attr_name(current);
event_id = mydevice->subscribe_event(attr_name, 
                         Tango::CHANGE_EVENT,
                         event_queue_size);
cout << event_client() id =  << event_id << endl;
// Check every 3 seconds whether new events have arrived and trigger the callback method 
// for the new events.
for (int i=0; i < 100; i++)
{
    sleep (3); 
    
    // Read the stored event data from the queue and call the callback method for every event.
    mydevice->get_events(event_id, double_callback);
}
 
event_test->unsubscribe_event(event_id);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

4.6.5.3 Java Clients


This is the interface for java clients who want to receive events. There are two ways to receive events using the TANGO java API :
  1. Using Callback.
  2. Using Java listener
Using callback, is very similar to a C++ clients. Using listener is more in the Java philosophy.

4.6.5.3.1 Using CallBack


In Java when using callback, the client has to implement a class inheriting from the Tango CallBack class and pass this to the DeviceProxy.subscribe_event() method. The CallBack class is the same class as the one proposed for the TANGO asynchronous call. This is as follows for events :
class MyCallback extends CallBack
{
   .
   .
   .
   public void push_event(EventData evt)
   {
     
   }
}
where EventData is similar to the C++ EventData class. To subscribe to an event, use the DeviceProxy.subscribe_event() method. To unsubscribe from an event, use the DeviceProxy.unsubscribe_event() method.


4.6.5.3.2 Using listeners


The Tango API defined four Java interfaces called All these interfaces defined one method respectively called change(), periodic(), qualityChange() and archive() which will be called when the event is received. The user must write a class implementing the interface for which he (she) want to receive event.
To install or remove a listener, use the TangoEventsAdapter class which has methods to install/remove listeners for the four different types of listener. This TangoEventAdapter class is created from the Tango device name.


4.6.5.3.2.1 Example

Here is a typical example of what a client will need to do to register for and receive events. First, you have to define a class implementing an interface as follows:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

class DoubleEventListener implements ITangoPeriodicListener
{
   public void periodic(TangoPeriodicEvent event)
   {
      DeviceAttribute attr = event.getValue();
      double[] double_value;
      try
      {
         double_value =   attr.extractDoubleArray();
         System.out.println( double value  + double_value[0]);
      }
      catch (Exception e)
      {                    
         System.out.println(
DoubleEventListener.periodic() : could not extract data!);
      }
   }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

The main code looks like (suppose the device generating event is called my/event/tester and the attribute name is double_event) :

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

DoubleEventListener listener = new DoubleEventListener();
 
TangoEventsAdapter adapter = new TangoEventsAdapter(my/event/tester) ;
 
String[] filters = new String[0];
adapter.addTangoPeriodicListener(listener,double_event,filters); 

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}




4.7 Group


A Tango Group provides the user with a single point of control for a collection of devices. By analogy, one could see a Tango Group as a proxy for a collection of devices. For instance, the Tango Group API supplies a command_inout() method to execute the same command on all the elements of a group.
A Tango Group is also a hierarchical object. In other words, it is possible to build a group of both groups and individual devices. This feature allows creating logical views of the control system - each view representing a hierarchical family of devices or a sub-system.
In this chapter, we will use the term hierarchy to refer to a group and its sub-groups. The term Group designates to the local set of devices attached to a specific Group.

4.7.1 Getting started with Tango group


The quickest way of getting started is to study an example...
Imagine we are vacuum engineers who need to monitor and control hundreds of gauges distributed over the 16 cells of a large-scale instrument. Each cell contains several penning and pirani gauges. It also contains one strange gauge. Our main requirement is to be able to control the whole set of gauges, a family of gauges located into a particular cell (e.g. all the penning gauges of the 6th cell) or a single gauge (e.g. the strange gauge of the 7th cell). Using a Tango Group, such features are quite straightforward to obtain.
Reading the description of the problem, the device hierarchy becomes obvious. Our gauges group will have the following structure:
-> gauges
  |  -> cell-01
  |     |-> inst-c01/vac-gauge/strange 
  |     |-> penning 
  |     |   |-> inst-c01/vac-gauge/penning-01 
  |     |   |-> inst-c01/vac-gauge/penning-02 
  |     |   |- ... 
  |     |   |-> inst-c01/vac-gauge/penning-xx 
  |     |-> pirani 
  |         |-> inst-c01/vac-gauge/pirani-01
  |         |-> ... 
  |         |-> inst-c01/vac-gauge/pirani-xx 
  |  -> cell-02
  |     |-> inst-c02/vac-gauge/strange 
  |     |-> penning 
  |     |   |-> inst-c02/vac-gauge/penning-01 
  |     |   |-> ... 
  |     | 
  |     |-> pirani 
  |     |   |-> ... 
  |  -> cell-03 
  |     |-> ... 
  |         | -> ... 
In the C++, such a hierarchy can be build as follows (basic version):

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- step0: create the root group 
Tango::Group *gauges = new Tango::Group(gauges);
 
//- step1: create a group for the n-th cell
Tango::Group *cell = new Tango::Group(cell-01);
 
//- step2: make the cell a sub-group of the root group 
gauges->add(cell);
 
//- step3: create a penning group 
Tango::Group *gauge_family = new Tango::Group(penning);
 
//- step4: add all penning gauges located into the cell (note the wildcard)
gauge_family->add(inst-c01/vac-gauge/penning*);
 
//- step5: add the penning gauges to the cell
cell->add(gauge_family);
 
//- step6: create a pirani group 
gauge_family = new Tango::Group(pirani);
 
//- step7: add all pirani gauges located into the cell (note the wildcard)
gauge_family->add(inst-c01/vac-gauge/pirani*);
 
//- step8: add the pirani gauges to the cell
cell->add(gauge_family);
 
//- step9: add the strange gauge to the cell
cell->add(inst-c01/vac-gauge/strange);
 
//- repeat step 1 to 9 for the remaining cells
cell = new Tango::Group(cell-02);
...

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Here is the Java version:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

import fr.esrf.TangoApi.Group;
 
//- step0: create the root group Group
gauges = new Group(gauges);
 
//- step1: create a group for the n-th cell
Group cell = new Group(cell-01);
 
//- step2: make the cell a sub-group of the root group
gauges.add(cell);
 
//- step3: create a penning group
Group gauge_family = new Group(penning);
 
//- step4: add all penning gauges located into the cell (note the wildcard)
gauge_family.add(inst-c01/vac-gauge/penning*);
 
//- step5: add the penning gauges to the cell
cell.add(gauge_family);
 
//- step6: create a pirani group
gauge_family = new Group(pirani);
 
//- step7: add all pirani gauges located into the cell (note the wildcard)
gauge_family.add(inst-c01/vac-gauge/pirani*);
 
//- step8: add the pirani gauges to the cell cell.add(gauge_family);
 
//- step9: add the strange gauge to the cell
cell.add(inst-c01/vac-gauge/strange);
 
//- repeat step 1 to 9 for the remaining cells
cell = new Group(cell-02);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Important note: There is no particular order to create the hierarchy. However, the insertion order of the devices is conserved throughout the lifecycle of the Group and cannot be changed. That way, the Group implementation can guarantee the order in which results are returned (see below).
Keeping a reference to the root group is enough to manage the whole hierarchy (i.e. there no need to keep trace of the sub-groups or individual devices). The Group interface provides methods to retrieve a sub-group or an individual device.
Be aware that a C++ group allways gets the ownership of its children and deletes them when it is itself deleted. Therefore, never try to delete a Group (respectively a DeviceProxy) returned by a call to Tango::Group::get_group() (respectively to Tango::Group::get_device()). Use the Tango::Group::remove() method instead (see the Tango Group class API documentation for details).
We can now perform any action on any element of our gauges group. For instance, let's ping the whole hierarchy to be sure that all devices are alive.

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- ping the whole hierarchy 
if (gauges->ping() == true)
{
    std::cout << all devices alive << std::endl;
}
else
{
    std::cout << at least one dead/busy/locked/... device << std::endl;
}

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}


4.7.2 Forward or not forward?


Since a Tango Group is a hierarchical object, any action performed on a group can be forwarded to its sub-groups. Most of the methods in the Group interface have a so-called forward option controlling this propagation. When set to false, the action is only performed on the local set of devices. Otherwise, the action is also forwarded to the sub-groups, in other words, propagated along the hierarchy. In C++ , the forward option defaults to true (thanks to the C++ default argument value). There is no such mechanism in Java and the forward option must be systematically specified.

4.7.3 Executing a command


As a proxy for a collection of devices, the Tango Group provides an interface similar to the DeviceProxy's. For the execution of a command, the Group interface contains several implementations of the command_inout method. Both synchronous and asynchronous forms are supported.


4.7.3.1 Obtaining command results


Command results are returned using a Tango::GroupCmdReplyList. This is nothing but a vector containing a Tango::GroupCmdReply for each device in the group. The Tango::GroupCmdReply contains the actual data (i.e. the Tango::DeviceData). By inheritance, it may also contain any error occurred during the execution of the command (in which case the data is invalid).
We previously indicated that the Tango Group implementation guarantees that the command results are returned in the order in which its elements were attached to the group. For instance, if g1 is a group containing three devices attached in the following order:
g1->add(my/device/01);
g1->add(my/device/03);
g1->add(my/device/02);
the results of
Tango::GroupCmdReplyList crl = g1->command_inout(Status);
will be organized as follows:
crl[0] contains the status of my/device/01
crl[1] contains the status of my/device/03
crl[2] contains the status of my/device/02
Things get more complicated if sub-groups are added between devices.
g2->add(my/device/04);
g2->add(my/device/05);
 
g4->add(my/device/08);
g4->add(my/device/09);
 
g3->add(my/device/06);
g3->add(g4);
g3->add(my/device/07);
 
g1->add(my/device/01);
g1->add(g2);
g1->add(my/device/03);
g1->add(g3);
g1->add(my/device/02);
The result order in the Tango::GroupCmdReplyList depends on the value of the forward option. If set to true, the results will be organized as follows:
Tango::GroupCmdReplyList crl = g1->command_inout(Status, true);
crl[0] contains the status of my/device/01 which belongs to g1
crl[1] contains the status of my/device/04 which belongs to g1.g2
crl[2] contains the status of my/device/05 which belongs to g1.g2
crl[3] contains the status of my/device/03 which belongs to g1
crl[4] contains the status of my/device/06 which belongs to g1.g3
crl[5] contains the status of my/device/08 which belongs to g1.g3.g4
crl[6] contains the status of my/device/09 which belongs to g1.g3.g
crl[7] contains the status of my/device/07 which belongs to g1.g3
crl[8] contains the status of my/device/02 which belongs to g1
If the forward option is set to false, the results are:
Tango::GroupCmdReplyList crl = g1->command_inout(Status, false); 
crl[0] contains the status of my/device/01 which belongs to g
crl[1] contains the status of my/device/03 which belongs to g1
crl[2] contains the status of my/device/02 which belongs to g1
The Tango::GroupCmdReply contains some public members allowing the identification of both the device (Tango::GroupCmdReply::dev_name) and the command (Tango::GroupCmdReply::obj_name). It means that, depending of your application, you can associate a response with its source using its position in the response list or using the Tango::GroupCmdReply::dev_name member.


4.7.3.2 Case 1: a command, no argument


As an example, we execute the Status command on the whole hierarchy synchronously.
Tango::GroupCmdReplyList crl = gauges->command_inout(Status);
As a first step in the results processing, it could be interesting to check value returned by the has_failed() method of the GroupCmdReplyList. If it is set to true, it means that at least one error occurred during the execution of the command (i.e. at least one device gave error).

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

if (crl.has_failed())
{
    cout << at least one error occurred << endl;
}
else
{
    cout << no error  << endl;
}

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

In Java, we should write:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

import fr.esrf.TangoApi.Group;
GroupCmdReplyList crl = gauges.command_inout(Status,true);
if (crl.has_failed())
{
   System.out.println(at least one error occurred);
}
else
{
   System.out.println(no error);
}

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Now, we have to process each individual response in the list.

4.7.3.3 A few words on error handling and data extraction


Depending of the application and/or the developer's programming habits, each individual error can be handle by the C++ (or Java) exception mechanism or using the dedicated has_failed() method. The GroupReply class - which is the mother class of both GroupCmdReply and GroupAttrReply - contains a static method to enable (or disable) exceptions called enable_exception(). By default, exceptions are disabled (in both Java and C++). The following example is proposed with both exceptions enable and disable.
In C++, data can be extracted directly from an individual reply. The GroupCmdReply interface contains a template operator >> allowing the extraction of any supported Tango type (in fact the actual data extraction is delegated to DeviceData::operator >>). One dedicated extract method is also provided in order to extract DevVarLongStringArray and DevVarDoubleStringArray types to std::vectors.
Error and data handling C++ example:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//----------------------------
//- synch. group command example with exception enabled
//----------------------------
//- enable exceptions and save current mode
bool last_mode = GroupReply::enable_exception(true);
//- process each response in the list ...
for (int r = 0; r < crl.size(); r++)
{
//- enter a try/catch block
   try
   {
//- try to extract the data from the r-th reply
//- suppose data contains a double
       double ans;
       crl[r] >> ans;
       cout << crl[r].dev_name()
            << ::
            << crl[r].obj_name()
            <<  returned 
            << ans
            << endl;
    }
    catch (const DevFailed& df)
    {
//- DevFailed caught while trying to extract the data from reply
      for (int err = 0; err < df.errors.length(); err++)
      {
           cout << error:  << df.errors[err].desc.in() << endl;
      }
//- alternatively, one can use crl[r].get_err_stack() see below
    }
    catch (...)
    {
       cout << unknown exception caught;
    }
}
//- restore last exception mode (if needed)
GroupReply::enable_exception(last_mode);
//- Clear the response list (if reused later in the code)
crl.reset();
 
//----------------------------
//- synch. group command example with exception disabled
//----------------------------
//- disable exceptions and save current mode bool
last_mode = GroupReply::enable_exception(false);
//- process each response in the list ...
for (int r = 0; r < crl.size(); r++)
{
//- did the r-th device give error?
    if (crl[r].has_failed() == true)
    {
//- printout error description
       cout << an error occurred while executing 
            << crl[r].obj_name()
            <<  on  
            << crl[r].dev_name() << endl;
//- dump error stack
       const DevErrorList& el = crl[r].get_err_stack();
       for (int err = 0; err < el.size(); err++)
       {
           cout << el[err].desc.in();
       }
    }
    else
    {
//- no error (suppose data contains a double)
       double ans;
       bool result = crl[r] >> ans;
       if (result == false)
       {
           cout << could not extract double from 
                << crl[r].dev_name()
                <<  reply
                << endl;
       }
       else
       {
           cout << crl[r].dev_name()
                << ::
                << crl[r].obj_name()
                <<  returned 
                << ans
                << endl;
       }
    }
}
//- restore last exception mode (if needed)
GroupReply::enable_exception(last_mode);
//- Clear the response list (if reused later in the code)
crl.reset();

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Error and data handling Java example:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//----------------------------
//- synch. group command example with exception enabled
//----------------------------
//- enable exceptions and save current mode
boolean last_mode = GroupReply.enable_exception(true);
//- process each response in the list ...
Iterator it = crl.iterator();
//- try to extract the data from the each reply
//- suppose data contains a double
double ans;
while (it.hasNext())
{
//- cast from Object to GroupCmdreply
GroupCmdreply     cr = (GroupCmdreply)it.next();
//- enter a try/catch block
    try
    {
//- extract value from data (may throw DevFailed)
       ans = get_data().extractDouble();
//- verbose
       System.out.println(cr.dev_name()
                        + ::
                        + cr.obj_name()
                        +  returned 
                        + ans);
    }
    catch (DevFailed d)
    {
//- DevFailed caught while trying to extract the data from reply
       for (int err = 0; err < d.errors.length; err++)
       {
          System.out.println(error:  + d.errors[err].desc);
       }
//- alternatively, one can use cr.get_err_stack() see below
    }
    catch (Exception e)
    {
       System.out.println(unknown exception caught);
    }
}
//- restore last exception mode (if needed)
GroupReply.enable_exception(last_mode);
 
 
//----------------------------
//- synch. group command example with exception disabled
//----------------------------
//- disable exceptions and save current mode
boolean last_mode = GroupReply.enable_exception(false);
//- process each response in the list ...
Iterator it = crl.iterator();
//- try to extract the data from the each reply
while (it.hasNext())
{
//- cast from Object to GroupCmdreply
    GroupCmdreply cr = (GroupCmdreply)it.next();
//- did the device give error?
    if (cr.has_failed() == true)
    {
//- printout error description
       System.out.println(an error occurred while executing 
               + cr.obj_name()
               +  on  
               + cr.dev_name());
//- dump error stack
       DevError[] de = cr.get_err_stack();
       for (int err = 0; err < de.length; err++)
       {
          System.out.println(error:  + de[err].desc);
       }
    }
    else
    {
//- no error (suppose data contains a double)
       double ans = cr.get_data().extractDouble();
//- verbose
       System.out.println(cr.dev_name()
                          + ::
                          + cr.obj_name() 
                          +  returned  
                          + ans);
    }
}
//- restore last exception mode (if needed)
GroupReply.enable_exception(last_mode);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Now execute the same command asynchronously. C++ example:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//----------------------------
//- asynch. group command example (C++ example)
//----------------------------
long request_id = gauges->command_inout_asynch(Status);
//- do some work
do_some_work();
 
 
//- get results
crl = gauges->command_inout_reply(request_id);
//- process responses as previously describe in the synch. implementation
for (int r = 0; r < crl.size(); r++)
{
//- data processing and error handling goes here
//- copy/paste code from previous example
. . .
}
//- clear the response list (if reused later in the code)
crl.reset();

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

The same asynchronous example in Java:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//----------------------------
//- asynch. group command example (Java example)
//----------------------------
int request_id = gauges.command_inout_asynch(Status,false,true);
//- do some work
do_some_work();
 
 
//- get results
crl = gauges.command_inout_reply(request_id);
//- process responses as previously describe in the synch. implementation
Iterator it = crl.iterator();
//- try to extract the data from the each reply
while (it.hasNext())
{
//- data processing and error handling goes here
//- copy/paste code from previous example
. . .
}

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}


4.7.3.4 Case 2: a command, one argument


Here, we give an example in which the same input argument is applied to all devices in the group (or its sub-groups).
In C++:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- the argument value
double d = 0.1;
//- insert it into the TANGO generic container for command: DeviceData
Tango::DeviceData dd;
dd << d;
//- execute the command: Dev_Void SetDummyFactor (Dev_Double)
Tango::GroupCmdReplyList crl = gauges->command_inout(SetDummyFactor, dd);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

In Java:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- the argument value
double d = 0.1;
//- insert it into the TANGO generic container for command: DeviceData
DeviceData dd = new DeviceData();
dd.insert(d);
//- execute the command: Dev_Void SetDummyFactor (Dev_Double)
GroupCmdReplyList crl = gauges.command_inout(SetDummyFactor, dd, false, true);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Since the SetDummyFactor command does not return any value, the individual replies (i.e. the GroupCmdReply) do not contain any data. However, we have to check their has_failed() method returned value to be sure that the command completed successfully on each device (acknowledgement). Note that in such a case, exceptions are useless since we never try to extract data from the replies.
In C++ we should have something like:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- no need to process the results if no error occurred (Dev_Void command)
if (crl.has_failed())
{
//- at least one error occurred
    for (int r = 0; r < crl.size(); r++)
    {
//- handle errors here (see previous C++ examples)
    }
}
//- clear the response list (if reused later in the code)
crl.reset();

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

While in Java

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- no need to process the results if no error occurred (Dev_Void command)
if (crl.has_failed())
{
//- at least one error occurred
   for (int r = 0; r < crl.length; r++)
   {
//- handle errors here (see previous Java examples)
   }
}

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

See case 1 for an example of asynchronous command.


4.7.3.5 Case 3: a command, several arguments


Here, we give an example in which a specific input argument is applied to each device in the hierarchy. In order to use this form of command_inout, the user must have an a priori and perfect knowledge of the devices order in the hierarchy. In such a case, command arguments are passed in an array (with one entry for each device in the hierarchy).
The C++ implementation provides a template method which accepts a std::vector of C++ type for command argument. This allows passing any kind of data using a single method.
Since templates are not (already) supported in Java, the implementation is somewhat different and an array of DeviceData is used to pass the specific arguments.
In both cases (C++ and Java), the size of this vector must equal the number of device in the hierarchy (respectively the number of device in the group) if the forward option is set to true (respectively set to false). Otherwise, an exception is thrown.
The first item in the vector is applied to the first device in the hierarchy, the second to the second device in the hierarchy, and so on...That's why the user must have a perfect knowledge of the devices order in the hierarchy.
Assuming that gauges are ordered by name, the SetDummyFactor command can be executed on group cell-01 (and its sub-groups) as follows:
Remember, cell-01 has the following internal structure:
-> gauges
   | -> cell-01
   |    |-> inst-c01/vac-gauge/strange
   |    |-> penning
   |    |   |-> inst-c01/vac-gauge/penning-01
   |    |   |-> inst-c01/vac-gauge/penning-02
   |    |   |-> ...
   |    |   |-> inst-c01/vac-gauge/penning-xx
   |    |-> pirani
   |        |-> inst-c01/vac-gauge/pirani-01
   |        |-> ...
   |        |-> inst-c01/vac-gauge/pirani-xx
Passing a specific argument to each device in C++:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- get a reference to the target group
Tango::Group *g = gauges->get_group(cell-01);
//- get number of device in the hierarchy (starting at cell-01)
long n_dev = g->get_size(true);
//- Build argin list
std::vector<double> argins(n_dev);
//- argument for inst-c01/vac-gauge/strange
argins[0] = 0.0;
//- argument for inst-c01/vac-gauge/penning-01
argins[1] = 0.1;
//- argument for inst-c01/vac-gauge/penning-02
argins[2] = 0.2;
//- argument for remaining devices in cell-01.penning
. . .
//- argument for devices in cell-01.pirani
. . .
//- the reply list
Tango::GroupCmdReplyList crl;
//- enter a try/catch block (see below)
try
{
//- execute the command
    crl = g->command_inout(SetDummyFactor, argins, true);
    if (crl.has_failed())
    {
//- error handling goes here (see case 1)
    }
}
catch (const DevFailed& df)
{
//- see below
}
crl.reset();

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

If we want to execute the command locally on cell-01 (i.e. not on its sub-groups), we should write the following C++ code:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- get a reference to the target group
Tango::Group *g = gauges->get_group(cell-01);
//- get number of device in the group (starting at cell-01)
long n_dev = g->get_size(false);
//- Build argin list
std::vector<double> argins(n_dev);
//- argins for inst-c01/vac-gauge/penning-01
argins[0] = 0.1;
//- argins for inst-c01/vac-gauge/penning-02
argins[1] = 0.2;
//- argins for remaining devices in cell-01.penning
. . .
//- the reply list
Tango::GroupCmdReplyList crl;
//- enter a try/catch block (see below)
try
{
//- execute the command
    crl = g->command_inout(SetDummyFactor, argins, false);
    if (crl.has_failed())
    {
//- error handling goes here (see case 1)
    }
}
catch (const DevFailed& df)
{
//- see below
}
crl.reset();

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Passing a specific argument to each device in Java:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- get a reference to the target group
Group g = gauges.get_group(cell-01);
//- get pre-build arguments list for the whole hierarchy (starting@cell-01)
DeviceData[] argins = g.get_command_specific_argument_list(true);
//- argument for inst-c01/vac-gauge/strange
argins[0].insert(0.0);
//- argument for inst-c01/vac-gauge/penning-01
argins[1].insert(0.1);
//- argument for inst-c01/vac-gauge/penning-02
argins[2].insert(0.2);
//- argument for remaining devices in cell-01.penning
. . .
//- argument for devices in cell-01.pirani
. . .
//- the reply list GroupCmdReplyList
crl = null;
//- enter a try/catch block (see below)
try
{
//- execute the command
    crl = g.command_inout(SetDummyFactor, argins, false, true);
    if (crl.has_failed())
    {
//- error handling goes here (see case 1)
    }
}
catch (DevFailed d)
{
//- see below
}

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Note: if we want to execute the command locally on cell-01 (i.e. not on its sub-groups), we should write the following code:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- get a reference to the target group
Group g = gauges.get_group(cell-01);
//- get pre-build arguments list for the group (starting@cell-01)
DeviceData[] argins = g.get_command_specific_argument_list(false);
//- argins for inst-c01/vac-gauge/penning-01
argins[0].insert(0.1);
//- argins for inst-c01/vac-gauge/penning-02
argins[1].insert(0.2);
//- argins for remaining devices in cell-01.penning
. . .
//- the reply list 
GroupCmdReplyList crl;
//- enter a try/catch block (see below)
try
{
//- execute the command
    crl = g.command_inout(SetDummyFactor, argins, false, false);
    if (crl.has_failed())
    {
//- error handling goes here (see case 1)
    }
}
catch (DevFailed d)
{
//- see below
}

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

This form of command_inout (the one that accepts an array of value as its input argument), may throw an exception before executing the command if the number of elements in the input array does not match the number of individual devices in the group or in the hierarchy (depending on the forward option).
Java developers should use the Group.get_command_specific_argument_list helper method (see previous example). It guarantees than the returned array has the right number of elements. This array may be kept and reused as far as the group size is not changed (i.e. no add or remove of elements).
An asynchronous version of this method is also available. See case 1 for an example of asynchronous command.


4.7.4 Reading attribute(s)


In order to read attribute(s), the Group interface contains several implementations of the read_attribute() and read_attributes() methods. Both synchronous and asynchronous forms are supported. Reading several attributes is very similar to reading a single attribute. Simply replace the std::string used for attribute name by a vector of std::string with one element for each attribute name. In case of read_attributes() call, the order of attribute value returned in the GroupAttrReplyList is all attributes for first element in the group followed by all attributes for the second group element and so on.


4.7.4.1 Obtaining attribute values


Attribute values are returned using a GroupAttrReplyList. This is nothing but an array containing a GroupAttrReply for each device in the group. The GroupAttrReply contains the actual data (i.e. the DeviceAttribute). By inheritance, it may also contain any error occurred during the execution of the command (in which case the data is invalid).
Here again, the Tango Group implementation guarantees that the attribute values are returned in the order in which its elements were attached to the group. See Obtaining command results for details.
The GroupAttrReply contains some public methods allowing the identification of both the device (GroupAttrReply::dev_name) and the attribute (GroupAttrReply::obj_name). It means that, depending of your application, you can associate a response with its source using its position in the response list or using the Tango::GroupAttrReply::dev_name member.

4.7.4.2 A few words on error handling and data extraction


Here again, depending of the application and/or the developer's programming habits, each individual error can be handle by the C++ exception mechanism or using the dedicated has_failed() method. The GroupReply class - which is the mother class of both GroupCmdReply and GroupAttrReply - contains a static method to enable (or disable) exceptions called enable_exception(). By default, exceptions are disabled (in both Java and C++). The following example is proposed with both exceptions enable and disable.
In C++, data can be extracted directly from an individual reply. The GroupAttrReply interface contains a template operator>> allowing the extraction of any supported Tango type (in fact the actual data extraction is delegated to DeviceAttribute::operator>>).
Reading an attribute is very similar to executing a command.
Reading an attribute in C++:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//---------------------------------
//- synch. read vacuum attribute on each device in the hierarchy
//- with exceptions enabled - C++ example
//---------------------------------
//- enable exceptions and save current mode
bool last_mode = GroupReply::enable_exception(true);
//- read attribute
Tango::GroupAttrReplyList arl = gauges->read_attribute(vacuum);
//- for each response in the list ...
for (int r = 0; r < arl.size(); r++)
{
//- enter a try/catch block
   try
   {
//- try to extract the data from the r-th reply
//- suppose data contains a double
      double ans;
      arl[r] >> ans;
      cout << arl[r].dev_name()
           << ::
           << arl[r].obj_name()
           <<  value is 
           << ans << endl;
   }
   catch (const DevFailed& df)
   {
//- DevFailed caught while trying to extract the data from reply
      for (int err = 0; err < df.errors.length(); err++)
      {
         cout << error:  << df.errors[err].desc.in() << endl;
      }
//- alternatively, one can use arl[r].get_err_stack() see below
   }
   catch (...)
   {
      cout << unknown exception caught;
   }
}
//- restore last exception mode (if needed)
GroupReply::enable_exception(last_mode);
//- clear the reply list (if reused later in the code)
arl.reset();

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Reading an attribute in Java:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//---------------------------------
//- synch. read vacuum attribute on each device in the hierarchy
//- with exceptions enabled - Java example
//---------------------------------
//- enable exceptions and save current mode
boolean last_mode = GroupReply.enable_exception(true);
//- read attribute
GroupAttrReplyList arl = gauges.read_attribute(vacuum,true);
//- try to extract the data from the each reply
//- suppose data contains a double
double ans;
while (it.hasNext())
{
//- cast from Object to GroupAttrReply
    GroupAttrReply ar = (GroupAttrreply)it.next();
//- enter a try/catch block
    try
    {
//- extract value from data (may throw DevFailed)
ans = get_data().extractDouble();
//- verbose
       System.out.println(cr.dev_name()
                        + ::
                        + cr.obj_name()
                        +  returned 
                        + ans);
    }
    catch (DevFailed d)
    {
//- DevFailed caught while trying to extract the data from reply
        for (int err = 0; err < d.errors.length; err++)
        {
           System.out.println(error:  + d.errors[err].desc);
        }
//- alternatively, one can use cr.get_err_stack() see below
    }
    catch (Exception e)
    {
        System.out.println(unknown exception caught);
    }
}
//- restore last exception mode (if needed)
GroupReply.enable_exception(last_mode);

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

In C++, an asynchronous version of the previous example could be:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- read the attribute asynchronously
long request_id = gauges->read_attribute_asynch(vacuum);
//- do some work
do_some_work();
 
 
//- get results
Tango::GroupAttrReplyList arl = gauges->read_attribute_reply(request_id);
//- process replies as previously described in the synch. implementation
for (int r = 0; r < arl.size(); r++)
{
//- data processing and/or error handling goes here
...
}
//- clear the reply list (if reused later in the code)
arl.reset();

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

The same asynchronous example in Java:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- read the attribute asynchronously
int request_id = gauges.read_attribute_asynch(vacuum,true);
//- do some work
do_some_work();
 
 
//- get results
GroupAttrReplyList arl = gauges.read_attribute_reply(request_id);
Iterator it = arl.iterator();
//- try to extract the data from the each reply
while (it.hasNext())
{
//- data processing and error handling goes here
//- copy/paste code from previous example
. . .
}

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}


4.7.5 Writing an attribute


The Group interface contains several implementations of the write_attribute() method. Both synchronous and asynchronous forms are supported. However, writing more than one attribute at a time is not supported.


4.7.5.1 Obtaining acknowledgement


Acknowledgements are returned using a GroupReplyList. This is nothing but an array containing a GroupReply for each device in the group. The GroupReply may contain any error occurred during the execution of the command. The return value of the has_failed() method indicates whether an error occurred or not. If this flag is set to true, the GroupReply::get_err_stack() method gives error details.
Here again, the Tango Group implementation guarantees that the attribute values are returned in the order in which its elements were attached to the group. See Obtaining command results for details.
The GroupReply contains some public members allowing the identification of both the device (GroupReply::dev_name) and the attribute (GroupReply::obj_name). It means that, depending of your application, you can associate a response with its source using its position in the response list or using the GroupReply::dev_name member.


4.7.5.2 Case 1: one value for all devices


Here, we give an example in which the same attribute value is written on all devices in the group (or its sub-groups). Exceptions are supposed to be disabled.
Writing an attribute in C++:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//---------------------------------
//- synch. write dummy attribute on each device in the hierarchy
//---------------------------------
//- assume each device support a dummy writable attribute
//- insert the value to be written into a generic container
Tango::DeviceAttribute value(std::string(dummy), 3.14159);
//- write the attribute
Tango::GroupReplyList rl = gauges->write_attribute(value);
//- any error?
if (rl.has_failed() == false)
{
    cout << no error << endl;
}
else
{
    cout << at least one error occurred << endl;
//- for each response in the list ...
    for (int r = 0; r < rl.size(); r++)
    {
//- did the r-th device give error?
       if (rl[r].has_failed() == true)
       {
//- printout error description
           cout << an error occurred while reading  
                << rl[r].obj_name()
                <<  on 
                << rl[r].dev_name()
                << endl;
//- dump error stack
           const DevErrorList& el = rl[r].get_err_stack();
           for (int err = 0; err < el.size(); err++)
           {
              cout << el[err].desc.in();
           }
        }
     }
}
//- clear the reply list (if reused later in the code)
rl.reset();

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Writing an attribute in Java:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//---------------------------------
//- synch. write dummy attribute on each device in the hierarchy
//---------------------------------
//- assume each device support a dummy writable attribute
//- insert the value to be written into a generic container
DeviceAttribute value = new DeviceAttribute(dummy), 3.14159);
//- write the attribute
GroupReplyList rl = gauges.write_attribute(value,true);
//- any error?
if (rl.has_failed() == false)
{
    System.out.println(no error);
}
else
{
    System.out.println(at least one error occurred);
//- for each response in the list ... 
    Iterator it = rl.iterator();
    while (it.hasNext())
    {
//- cast from Object to GroupReply
        GroupReply gr = (GroupReply)it.next();
//- did the r-th device give error?
        if (gr.has_failed())
        {
//- printout error description
            System.out.println(an error occurred while reading  
                  + gr.obj_name()
                  +  on  
                  + gr.dev_name());
//- dump error stack
            DevError[] el = gr.get_err_stack();
            for (int err = 0; err < el.length; err++)
            {
                System.out.println(el[err].desc);
            }
        }
    }
 }

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Here is a C++ asynchronous version:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- insert the value to be written into a generic container
Tango::DeviceAttribute value(std::string(dummy), 3.14159);
//- write the attribute asynchronously
long request_id = gauges.write_attribute_asynch(value);
//- do some work
do_some_work();
 
 
//- get results
Tango::GroupReplyList rl = gauges->write_attribute_reply(request_id);
//- process replies as previously describe in the synch. implementation ...

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

The same asynchronous example in Java:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- insert the value to be written into a generic container
DeviceAttribute value = new DeviceAttribute(dummy, 3.14159);
//- write the attribute asynchronously
int request_id = gauges.write_attribute_asynch(value,true);
//- do some work
do_some_work();
 
 
//- get results
GroupReplyList rl = gauges.write_attribute_reply(request_id, 0);
//- process replies as previously describe in the synch. implementation ...

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}


4.7.5.3 Case 2: a specific value per device


Here, we give an example in which a specific attribute value is applied to each device in the hierarchy. In order to use this form of write_attribute(), the user must have an a priori and perfect knowledge of the devices order in the hierarchy.
The C++ implementation provides a template method which accepts a std::vector of C++ type for command argument. This allows passing any kind of data using a single method.
Since templates are not (already) supported in Java, the implementation is somewhat different and an array of DeviceAttribute is used to pass the specific arguments.
In both cases (C++ and Java), the size of this vector must equal the number of device in the hierarchy (respectively the number of device in the group) if the forward option is set to true (respectively set to false). Otherwise, an exception is thrown.
The first item in the vector is applied to the first device in the group, the second to the second device in the group, and so on...That's why the user must have a perfect knowledge of the devices order in the group.
Assuming that gauges are ordered by name, the dummy attribute can be written as follows on group cell-01 (and its sub-groups) as follows:
Remember, cell-01 has the following internal structure:
-> gauges 
    | -> cell-01
    |     |-> inst-c01/vac-gauge/strange
    |     |-> penning
    |     |    |-> inst-c01/vac-gauge/penning-01
    |     |    |-> inst-c01/vac-gauge/penning-02
    |     |    |-> ...
    |     |    |-> inst-c01/vac-gauge/penning-xx
    |     |-> pirani
    |          |-> inst-c01/vac-gauge/pirani-01
    |          |-> ...
    |          |-> inst-c01/vac-gauge/pirani-xx
C++ version:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- get a reference to the target group
Tango::Group *g = gauges->get_group(cell-01);
//- get number of device in the hierarchy (starting at cell-01)
long n_dev = g->get_size(true);
//- Build value list
std::vector<double> values(n_dev);
//- value for inst-c01/vac-gauge/strange
values[0] = 3.14159;
//- value for inst-c01/vac-gauge/penning-01
values[1] = 2 * 3.14159;
//- value for inst-c01/vac-gauge/penning-02
values[2] = 3 * 3.14159;
//- value for remaining devices in cell-01.penning
. . .
//- value for devices in cell-01.pirani
. . .
//- the reply list
Tango::GroupReplyList rl;
//- enter a try/catch block (see below)
try
{
//- write the dummy attribute
    rl = g->write_attribute(dummy, values, true);
    if (rl.has_failed())
    {
//- error handling (see previous cases)
    }
}
catch (const DevFailed& df)
{
//- see below
}
rl.reset();

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Here is a Java version:

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- get a reference to the target group
Group g = gauges.get_group(cell-01);
//- get pre-build arguments list for the whole hierarchy (starting@cell-01)
DeviceAttribute[] values = g.get_attribute_specific_value_list(true);
//- value for inst-c01/vac-gauge/strange
values[0] = 3.14159;
//- value for inst-c01/vac-gauge/penning-01
values[1] = 2 * 3.14159;
//- value for inst-c01/vac-gauge/penning-02
values[2] = 3 * 3.14159;
//- value for remaining devices in cell-01.penning
. . 
//- value for devices in cell-01.pirani
. . .
//- the reply list
GroupReplyList rl;
try
{
//- write the dummy attribute
rl = g.write_attribute(dummy, values, true);
   if (rl.has_failed())
   {
//- error handling (see previous cases)
   }
}
catch (DevFailed d)
{
//- see below
}

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

Note: if we want to execute the command locally on cell-01 (i.e. not on its sub-groups), we should write the following code (example is only proposed for C++ - Java port is straightforward):

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

//- get a reference to the target group
Tango::Group *g = gauges->get_group(cell-01);
//- get number of device in the group
long n_dev = g->get_size(false);
//- Build value list
std::vector<double> values(n_dev);
//- value for inst-c01/vac-gauge/penning-01
values[0] = 2 * 3.14159;
//- value for inst-c01/vac-gauge/penning-02
values[1] = 3 * 3.14159;
//- value for remaining devices in cell-01.penning
. . .
//- the reply list
Tango::GroupReplyList rl;
//- enter a try/catch block (see below)
try
{
//- write the dummy attribute
   rl = g->write_attribute(dummy, values, false);
   if (rl.has_failed())
   {
//- error handling (see previous cases)
   }
}
catch (const DevFailed& df)
{
//- see below
}
rl.reset();

\begin{picture}(0,0)\thicklines
\put(0,0){\line(1,0){400}}
\end{picture}

This form of write_attribute() (the one that accepts an array of value as its input argument), may throw an exception before executing the command if the number of elements in the input array does not match the number of individual devices in the group or in the hierarchy (depending on the forward option).
Java developers should use the Group.get_attribute_specific_value_list helper method (see previous example). It guarantees than the returned array has the right number of elements. This array may be kept and reused as far as the group size is not changed (i.e. no add or remove of elements).
An asynchronous version of this method is also available.


4.8 Device locking


Starting with Tango release 7 (and device inheriting from Device_4Impl), device locking is supported. For instance, this feature could be used by an application doing a scan on a synchrotron beam line. In such a case, you want to move an actuator then read a sensor, move the actuator again, read the sensor...You don't want the actuator to be moved by another client while the application is doing the scan. If the application doing the scan locks the actuator device, it will be sure that this device is reserved for the application doing the scan and other client will not be able to move it until the scan application un-locks this actuator.
A locked device is protected against: Other clients trying to do one of these calls on a locked device will get a DevFailed exception. In case of application with locked device crashed, the lock will be automatically release after a defined interval. The API provides a set of methods for application code to lock/unlock device. These methods are: These methods are precisely described in the API reference chapters.


4.9 Reconnection and exception


The Tango API automatically manages re-connection between client and server in case of communication error during a network access between a client and a server. By default, when a communication error occurs, an exception is returned to the caller and the connection is internally marked as bad. On the next try to contact the device, the API will try to re-build the network connection. With the set_transparency_reconnection() method of the DeviceProxy class, it is even possible not to have any exception thrown in case of communication error. The API will try to re-build the network connection as soon as it is detected as bad. This is the default mode. See [*] for more details on this subject.


4.10 Thread safety


Starting with Tango 7.2, some classes of the C++ API has been made thread safe. These classes are: This means that it is possible to share between threads a pointer to a DeviceProxy instance. It is safe to execute a call on this DeviceProxy instance while another thread is also doing a call to the same DeviceProxy instance. Obviously, this also means that it is possible to create thread local DeviceProxy instances and to execute method calls on these instances. Nevertheless, data local to a DeviceProxy instance like its timeout are not managed on a per thread basis. For a DeviceProxy instance shared between two threads, if thread 1 changes the instance timeout, thread 2 will also see this change.

4.11 Compiling and linking a Tango client


Compiling and linking a Tango client is similar to compiling and linking a Tango device server. Please, refer to chapter Compiling, Linking and executing a Tango device server process ([*]) to get all the details.

Image 0066-reduc



Emmanuel Taurel 2013-06-27