Subsections

9. Advanced features


9.1 Attribute alarms


Each Tango attribute two several alarms. These alarms are :

9.1.1 The level alarms


This alarm is defined for all Tango attribute read type and for numerical data type. The action of this alarm depend on the attribute value when it is read : If the attribute is a spectrum or an image, then the alarm is set if any one of the attribute value satisfies the above criterium. By default, these four parameters are not defined and no check will be done.
The following figure is a drawing of attribute quality factor and device state values function of the the attribute value.
Figure: Level alarm
\includegraphics[scale=0.5]{advanced/alarm}



If the min_warning and max_warning parameters are not set, the attribute quality factor will simply change between Tango::ATTR_ALARM and Tango::ATTR_VALID function of the attribute value.

9.1.2 The Read Different than Set (RDS) alarm


This alarm is defined only for attribute of the Tango::READ_WRITE and Tango::READ_WITH_WRITE read/write type and for numerical data type. When the attribute is read (or when the device state is requested), if the difference between its read value and the last written value is something more than or equal to an authorized delta and if at least a certain amount of milli seconds occurs since the last write operation, the attribute quality factor will be set to Tango::ATTR_ALARM and if the device state is Tango::ON, it is switched to Tango::ALARM. If the attribute is a spectrum or an image, then the alarm is set if any one of the attribute value's satisfies the above criterium. This alarm configuration is done with two attribute configuration parameters called delta_val and delta_t. By default, these two parameters are not defined and no check will be done.

9.2 Device polling


9.2.1 Introduction


Each tango device server automatically have a separate polling thread pool. Polling a device means periodically executing command on a device (or reading device attribute) and storing the results (or the thrown exception) in a polling buffer. The aim of this polling is threefold : Speeding-up response time is achieved because the command_inout CORBA operation is able to get its data from the polling buffer or from the a real access to the device. For ``slow'' device, getting the data from the buffer is much faster than accessing the device. Returning a first-level command output history (or attribute value history) to a client is possible due to the polling buffer which is managed as a circular buffer. The history is the contents of this circular buffer. Obviously, the history depth is limited to the depth of the circular buffer. The polling is also the data source for the event system because detecting an event means being able to regularly read the data, memorize it and declaring that it is an event after some comparison with older values.

9.2.2 Configuring the polling system


9.2.2.1 Configuring what has to be polled and how


It is possible to configure the polling in order to poll : Configuring the polling is done by sending command to the device server administration device automatically implemented in every device server process. Seven commands are dedicated to this feature. These commands are
AddObjPolling
It add a new object (command or attribute) to the list of object(s) to be polled. It is also with this command that the polling period is specified.
RemObjPolling
To remove one object (command or attribute) from the polled object(s) list
UpdObjPollingPeriod
Change one object polling period
StartPolling
Starts polling for the whole process
StopPolling
Stops polling for the whole process
PolledDevice
Allow a client to know which device are polled
DevPollStatus
Allow a client to precisely knows the polling status for a device
All the necessary parameters for the polling configuration are stored in the Tango database. Therefore, the polling configuration is not lost after a device server process stop and restart (or after a device server process crash!!).
It is also possible to automatically poll a command (or an attribute) without sending command to the device server administration device. This request some coding (a method call) in the device server software during the command or attribute creation. In this case, for every devices supporting this command or this attribute, polling configuration will be automatically updated in the database and the polling will start automatically at each device server process startup. It is possible to stop this behavior on a device basis by sending a RemObjPolling command to the device server administration device. The following piece of code shows how the source code should be written.

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

     1  
     2  void DevTestClass::command_factory()
     3  {
     4  ...
     5      command_list.push_back(new IOStartPoll(IOStartPoll,
     6                                              Tango::DEV_VOID,
     7                                              Tango::DEV_LONG,
     8                                              Void,
     9                                              Constant number));
    10      command_list.back()->set_polling_period(400);
    11  ...
    12  }
    13  
    14  
    15  void DevTestClass::attribute_factory(vector<Tango::Attr *> &att_list)
    16  {
    17  ...
    18      att_list.push_back(new Tango::Attr(String_attr,
    19                                          Tango::DEV_STRING,
    20                                          Tango::READ));
    21      att_list.back()->set_polling_period(250);
    22  ...
    23  }

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

A polling period of 400 mS is set for the command called ``IOStartPoll'' at line 10 with the set_polling_period method of the Command class. Therefore, for a device of this class, the polling thread will start polling its IOStartPoll command at process start-up except if a RemObjPolling indicating this device and the IOStartPoll command has already been received by the device server administration device. This is exactly the same behavior for attribute. The polling period for attribute called ``String_attr'' is defined at line 20.
Configuring the polling means defining device attribute/command polling period. The polling period has to be chosen with care. If reading an attribute needs 200 mS, there is no point to poll this attribute with a polling period equal or even below 200 mS. You should also take into account that some free time has to be foreseen for external request(s) on the device. On average, for one attribute needing X mS as reading time, define a polling period which is equal to 1.4 X (280 mS for our example of one attribute needing 200 mS as reading time). In case the polling tuning is given to external user, Tango provides a way to define polling period minimun threshold. This is done using device properties. These properties are named min_poll_period, cmd_min_poll_period and attr_min_poll_period. The property min_poll_period (mS) defined a minimun polling period for the device. The property cmd_min_poll_period allows the definition of a minimun polling period for a specific device command. The property attr_min_poll_period allows the definition of a minimun polling period for one device attribute. In case these properties are defined, it is not possible to poll the device command/attribute with a polling period below those defined by these properties. See Appendix A on device parameter to get a precise syntax description for these properties.
The Jive[21] tool also allows a graphical device polling configuration.

9.2.2.2 Configuring the polling threads pool


Starting with Tango release 7, a Tango device server process may have several polling threads managed as a pool. For instance, this could be usefull in case of devices within the same device server process but accessed by different hardware channel when one of the channel is not responding (Thus generating long timeout and de-synchronising the polling thread). By default, the polling threads pool size is set to 1 and all the polled object(s) are managed by the same thread (idem polling system in Tango releases older than release 7) . The configuration of the polling thread pool is done using two properties associated to the device server administration device. These properties are named: The granularity of the polling threads pool tuning is the device. You cannot ask the polling threads pool to have thread number 1 in charge of attribute att1 of device dev1 and thread number 2 to be in charge of att2 of the same device dev1.
When you require a new object (command or attribute) to be polled, two main cases may arrive:
  1. Some polled object(s) belonging to the device are already polled by one of the polling threads in the pool: There is no new thread created. The object is simply added to the list of objects to be polled for the existing thread
  2. There is no thread already created for the device. We have two sub-cases:
    1. The number of polling threads is less than the polling_threads_pool_size: A new thread is created and started to poll the object (command or attribute)
    2. The number of polling threads is already equal to the polling_threads_pool_size: The software search for the thread with the smallest number of polled objects and add the new polled object to this thread
Each time the polling threads pool configuration is changed, it is written in the database using the polling_threads_pool_conf property. If the behaviour previously described does not fulfill your needs, it is possible to update the polling_threads_pool_conf property in a graphical way using the Tango Astor [19] tool or manually using the Jive tool [21]. These changes will be taken into account at the next device server process start-up. At start-up, the polling threads pool will allways be configured as required by the polling_threads_pool_conf property. The syntax used for this property is described in the Reference part of the Appendix [*]. The following window dump is the Astor tool window which allows polling threads pool management.
Image ThreadsManagement

In this example, the polling threads pool size to set to 9 but only 4 polling threads are running. Thread 1 is in charge of all polled objects related to device pv/thread-pool/test-1 and pv/thread-pool/test-2. Thread 2 is in charge of all polled objects related to device pv/thread-pool/test-3. Thread 3 is in charge of all polled objects related to device pv/thread-pool/test-5 anf finally, thread 4 is in charge of all polled objects for devices pv/thread-pool/test-4, pv/thread-pool/test-6 and pv/thread-pool/test-7.
It's also possible to define the polling threads pool size programmatically in the main function of a device server process using the Util::set_polling_threads_pool_size() method before the call to the Util::server_init() method

9.2.3 Reading data from the polling buffer


For a polled command or a polled attribute, a client has three possibilities to get command result or attribute value (or the thrown exception) : The choice is done during the command_inout CORBA operation by positioning one of the operation parameter. When reading data from the polling buffer, several error cases are possible

9.2.4 Retrieving command/attribute result history


The polling thread stores the command result or attribute value in circular buffers. It is possible to retrieve an history of the command result (or attribute value) from these polling buffers. Obviously the history is limited by the depth of the circular buffer. For commands, a CORBA operation called command_inout_history_2 allows this retrieval. The client specifies the command name and the record number he want to retrieve. For each record, the call returns the date when the command was executed, the command result or the exception stack in case of the command failed when it was executed by the polling thread. In such a case, the exception stack is sent as a structure member and not as an exception. The same thing is available for attribute. The CORBA operation name is read_attribute_history_2. For these two calls, there is no check done between the call date and the record date in contrary of the call to retrieve the last command result (or attribute value).

9.2.5 Externally triggered polling (only for C++ device server)


Sometimes, rather than polling a command or an attribute regulary with a fixed period, it is more interesting to manually decides when the polling must occurs. The Tango polling system also supports this kind of usage. This is called externally triggered polling. To define one attribute (or command) as externally triggered, simply set its polling period to 0. This can be done with the device server administration device AddObjPolling or UpdObjPollingPeriod command. Once in this mode, the attribute (or command) polling is triggered with the trigger_cmd_polling() method (or trigger_attr_polling() method) of the Util class. The following piece of code shows how this method could be used for one externally triggered command.

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

     1  .....
     2  
     3  string ext_polled_cmd(MyCmd);
     4  Tango::DeviceImpl *device = .....;
     5  
     6  Tango::Util *tg = Tango::Util::instance();
     7  
     8  tg->trigger_cmd_polling(device,ext_polled_cmd);
     9  
    10  .....

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

line 3 : The externally polled command name
line 4 : The device object
line 8 : Trigger polling of command MyCmd

9.2.6 Filling polling buffer (only for C++ device server)


Some hardware to be interfaced already returned an array of pair value, timestamp. In order to be read with the command_inout_history or read_attribute_history calls, this array has to be transferred in the attribute or command polling buffer. This is possible only for attribute or command configured in the externally triggered polling mode. Once in externally triggered polling mode, the attribute (or command) polling buffer is filled with the fill_cmd_polling_buffer() method (or fill_attr_polling_buffer() method) of the Util class. For command, the user uses a template class called TimedCmdData for each element of the command history. Each element is stored in a stack in one instance of a template class called CmdHistoryStack. This object is one of the argument of the fill_cmd_polling_buffer() method. Obviously, the stack depth cannot be larger than the polling buffer depth. See [*] to learn how the polling buffer depth is defined. The same way is used for attribute with the TimedAttrData and AttrHistoryStack template classes. These classes are documented in [8]. The following piece of code fills the polling buffer for a command called MyCmd which is already in externally triggered mode. It returns a DevVarLongArray data type with three elements. This example is not really something you will find in a real hardware interface. It is only to demonstrate the fill_cmd_polling_buffer() method usage. Error management has also been removed.

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

     1  ....
     2  
     3  Tango::DevVarLongArray dvla_array[4];
     4          
     5  for(int i = 0;i < 4;i++)
     6  {
     7      dvla_array[i].length(3);
     8      dvla_array[i][0] = 10 + i;
     9      dvla_array[i][1] = 11 + i;
    10      dvla_array[i][2] = 12 + i;
    11  }
    12  
    13  Tango::CmdHistoryStack<DevVarLongArray> chs;
    14  chs.length(4);
    15  
    16  for (int k = 0;k < 4;k++)
    17  {
    18      time_t when = time(NULL);
    19  
    20      Tango::TimedCmdData<DevVarLongArray> tcd(&dvla_array[k],when);
    21      chs.push(tcd);
    22  }
    23  
    24  Tango::Util *tg = Tango::Util::instance();
    25  string cmd_name(MyCmd);
    26  DeviceImpl *dev = ....;
    27  
    28  tg->fill_cmd_polling_buffer(dev,cmd_name,chs);
    29  
    30  .....

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

Line 3-11 : Simulate data coming from hardware
Line 13-14 : Create one instance of the CmdHistoryStack class and reserve space for one history of 4 elements
Line 16-17 : A loop on each history element
Line 18 : Get date (hardware simulation)
Line 20 : Create one instance of the TimedCmdData class with data and date
Line 21 : Store this command history element in the history stack. The element order will be the insertion order whatever the element date is.
Line 28 : Fill command polling buffer
After one execution of this code, a command_inout_history() call will return one history with 4 elements. The first array element of the oldest history record will have the value 10. The first array element of the newest history record will have the value 13. A command_inout() call with the data source parameter set to CACHE will return the newest history record (ie an array with values 13,14 and 15). A command_inout() call with the data source parameter set to DEVICE will return what is coded is the command method. If you execute this code a second time, a command_inout_history() call will return an history of 8 elements.
The next example fills the polling buffer for an attribute called MyAttr which is already in externally triggered mode. It is a scalar attribute of the DevString data type. This example is not really something you will find in a real hardware interface. It is only to demonstrate the fill_attr_polling_buffer() method usage with memory management issue. Error management has also been removed.

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

     1  ....
     2  
     3  AttrHistoryStack<DevString> ahs;
     4  ahs.length(3);
     5  
     6  for (int k = 0;k < 3;k++)
     7  {
     8      time_t when = time(NULL);
     9  
    10      DevString *ptr = new DevString [1];
    11      ptr = CORBA::string_dup(Attr history data);
    12  
    13      TimedAttrData<DevString> tad(ptr,Tango::ATTR_VALID,true,when);
    14      ahs.push(tad);
    15  }
    16  
    17  Tango::Util *tg = Tango::Util::instance();
    18  string attr_name(MyAttr);
    19  DeviceImpl *dev = ....;
    20  
    21  tg->fill_attr_polling_buffer(dev,attr_name,ahs);
    22  
    23  .....
  

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

Line 3-4 : Create one instance of the AttrHistoryStack class and reserve space for an history with 3 elements
Line 6-7 : A loop on each history element
Line 8 : Get date (hardware simulation)
Line 10-11 : Create a string. Note that the DevString object is created on the heap
Line 13 : Create one instance of the TimedAttrData class with data and date requesting the memory to be released.
Line 14 : Store this attribute history element in the history stack. The element order will be the insertion order whatever the element date is.
Line 21 : Fill command polling buffer
It is not necessary to return the memory allocated at line 10. The fill_attr_polling_buffer() method will do it for you.

9.2.7 Setting and tuning the polling in a Tango class


Even if the polling is normally set and tuned with external tool like Jive, it is possible to set it directly into the code of a Tango class. A set of methods belonging to the DeviceImpl class allows the user to deal with polling. These methods are: The following code snippet is just an exmaple of how these methods could be used. They are documented in [24]

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

     [language=C++]advanced/poll_in_ds.cpp.lines

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


9.3 Threading


When used with C++, Tango used omniORB as underlying ORB. This CORBA implementation is a threaded implementation and therefore a C++ Tango device server or client are multi-threaded processes.

9.3.1 C++ device server process


A classical Tango device server without any connected clients has eight threads. These threads are : On top of these eight threads, you have to add the thread(s) used by the polling threads pool. This number depends on the polling thread pool configuration and could be between 0 (no polling at all) and the maximun number of threads in the pool.
A new thread is started for each connected client. Device server are mostly used to interface hardware which most of the time does not support multi-threaded access. Therefore, all remote calls executed from a client are serialized within the device server code by using mutual exclusion. See chapter [*] on which serialization model are available. In order to limit thread number, the underlying ORB (omniORB) is configured to shutdown threads dedicated to client if the connection is inactive for more than 3 minutes. To also limit thread number, the ORB is configured to create one thread per connection up to 55 threads. When this level is reached, the threading model is automatically switch to a thread pool model with up to 100 threads. If the number of threads decrease down to 50, the threading model will return to thread per connection model.
If you are using event, the event system for its internal heartbeat system periodically (every 200 seconds) sends a command to the device server administration device. As explained above, a thread is created to execute these command. The omniORB scavanger will terminate this thread before the next event system heartbeat command arrives. For example, if you have a device server with three connected clients using only event, the process thread number will permanently change between 8 and 11 threads.
In summary, the number of threads in a device server process can be evaluated with the following formula:
8 + k + m
k is the number of polling threads used from the polling threads pool and m is the number of threads used for connected clients.


9.3.1.1 Serialization model within a device server


Four serialization models are available within a device server. These models protect all requests coming from the network but also requests coming from the polling thread. These models are:
  1. Serialization by device. All access to the same device are serialized. As an example, let's take a device server implementing one class of device with two instances (dev1 and dev2). Two clients are connected to these devices (client1 and client2). Client2 will not be able to access dev1 if client1 is using it. Nevertheless, client2 is able to access dev2 while client1 access dev1 (There is one mutual exclusion object by device)
  2. Serialization by class. With non multi-threaded legacy software, the preceding scenario could generate problem. In this mode of serialization, client2 is not able to access dev2 while client1 access dev1 because dev2 and dev1 are instances of the same class (There is one mutual exclusion object by class)
  3. Serialization by process. This is one step further than the previous case. In this mode, only one client can access any device embedded within the device server at a time. There is only one mutual exclusion object for the whole process)
  4. No serialization. This is an exotic kind of serialization and should be used with extreme care only with device which are fully thread safe. In this model, most of the device access are not serialized at all. Due to Tango internal structure, the get_attribute_config, set_attribute_config, read_attributes and write_attributes CORBA calls are still protected. Reading the device state and status via commands or via CORBA attribute is also protected.
By default, every Tango device server is in serialization by device mode. A method of the Tango::Util class allows to change this default behavior.

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

     1  #include <tango.h>
     2  
     3  int main(int argc,char *argv[])
     4  {
     5  
     6      try
     7      {
     8          
     9          Tango::Util *tg = Tango::Util::init(argc,argv);
    10  
    11          tg->set_serial_model(Tango::BY_CLASS);
    12  
    13          tg->server_init();
    14  
    15          cout << Ready to accept request << endl;
    16          tg->server_run();
    17      }
    18      catch (bad_alloc)
    19      {
    20           cout << Can't allocate memory!!! << endl;
    21           cout << Exiting << endl;
    22      }
    23      catch (CORBA::Exception &e)
    24      {
    25           Tango::Except::print_exception(e);
    26                  
    27           cout << Received a CORBA::Exception << endl;
    28           cout << Exiting << endl;
    29      }
    30          
    31      return(0);
    32  }

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

The serialization model is set at line 11 before the server is initialized and the infinite loop is started. See [8] for all details on the methods to set/get serialization model.

9.3.1.2 Attribute Serialization model


Even with the serialization model described previously, in case of attributes carrying a large number of data and several clients reading this attribute, a device attribute serialization has to be followed. Without this level of serialization, for attribute using a shared buffer, a thread scheduling may happens while the device server process is in the CORBA layer transferring the attribute data on the network. Three serialization models are available for attribute serialization. The default is well adapted to nearly all cases. Nevertheless, if the user code manages several attributes data buffer or if it manages its own buffer protection by one way or another, it could be interesting to tune this serialization level. The available models are:
  1. Serialization by kernel. This is the default case. The kernel is managing the serialization
  2. Serialization by user. The user code is in charge of the serialization. This serialization is done by the use of a omni_mutex object. An omni_mutex is an object provided by the omniORB package. It is the user responsability to lock this mutex when appropriate and to give this mutex to the Tango kernel before leaving the attribute read method
  3. No serialization.
By default, every Tango device attribute is in serialization by kernel. Methods of the Tango::Attribute class allow to change the attribute serialization behavior and to give the user omni_mutex object to the kernel.

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

1 void MyClass::init_device()
2 {
3    ...
4    ...
5    Tango::Attribute &att = dev_attr->get_attr_by_name(TheAttribute);
6    att.set_attr_serial_model(Tango::ATTR_BY_USER);
7    ....
8    ....

10 }
11 
12 
13 void MyClass::read_TheAttribute(Tango::Attribute &attr)
14 {
15    ....
16    ....
17    the_mutex.lock();
18    ....
19    // Fill the attribute buffer
20    ....
21    attr.set_value(buffer,....);
22    attr->set_user_attr_mutex(&the_mutex);
23 }
24 

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

The serialization model is set at line 6 in the init_device() method. The user omni_mutex is passed to the Tango kernel at line 22. This omni_mutex object is a device data member. See [8] for all details on the methods to set attribute serialization model.

9.3.2 C++ client process


Clients are also multi threaded processes. The underlying C++ ORB (omniORB) try to keep system resources to a minimum. To decrease process file descriptors usage, each connection to server is automatically closed if it is idle for more than 2 minutes and automatically re-opened when needed. A dedicated thread is spawned by the ORB to manage this automatic closing connection (the ORB scavenger thread).
Threrefore, a Tango client has two threads which are:
  1. The main thread
  2. The ORB scavanger thread
If the client is using the event system and as Tango is using the event push-push model, it has to be a server for receiving the events. This increases the number of threads. The client now has 6 threads which are:

9.4 Generating events in a device server


The server is at the origin of events. It will fire events as soon as they occur. Standard events (change, periodic and archive) are detected automatically in the polling thread and fired as soon as they are detected. The periodic events can only be handled by the polling thread. Change, Data ready and archive events can also be pushed from the device server code. To allow a client to subscribe to events of non polled attributes the server has to declare that events are pushed from the code. Three methods are available for this purpose:
Attr::set_change_event(bool implemented, bool detect = true);
Attr::set_archive_event(bool implemented, bool detect = true);
Attr::set_data_ready_event( bool implemented);
where implemented=true indicates that events are pushed manually from the code and detect=true (when used) triggers the verification of the same event properties as for events send by the polling thread. When setting detect=false, no value checking is done on the pushed value! The class DeviceImpl also supports the first two methods with an addictional parameter attr_name defining the attribute name.
To push events manually from the code a set of data type dependent methods can be used:
DeviceImpl::push_change_event (string attr_name, ....);
DeviceImpl::push_archive_event(string attr_name, ....);
For the data ready event, a DeviceImpl class method has to be used to push the event.
DeviceImpl::push_data_ready_event(string attr_name,Tango::DevLong ctr);
See the class documentation for all available interfaces.
For non-standard events a single call exists for pushing the data to the CORBA Notification Service (omniNotify). Clients who are subscribed to this event have to know what data type is in the DeviceAttribute and unpack it accordingly.
To push non-standard events, use the following api call is available to all device servers :
DeviceImpl::push_event( string attr_name,
             vector<string> &filterable_names,
             vector<double> &filterable_vals,
             Attribute &att)
where attr_name is the name of the attribute. Filterable_names and filterable_vals represent any filterable data which can be used by clients to filter on. Here is a typical example of what a server will need to do to send its own events. We are in the read method of the Sinusoide attribute. This attribute is readable as any other attribute but an event is sent if its value is positive when it is read. On top of that, this event is sent with one filterable field called value which is set to the attribute value.

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

1  void MyClass::read_Sinusoide(Tango::Attribute &attr)
2  {
3    ...
4       struct timeval tv;
5       gettimeofday(&tv, NULL);
6       sinusoide = 100 * sin( 2 * 3.14 * frequency * tv.tv_sec);
7  
8       if (sinusoide >= 0) 
9       {
10          vector<string> filterable_names;
11          vector<double> filterable_value;
12 
13          filterable_names.push_back(value);
14          filterable_value.push_back((double)sinusoide);
15 
16          push_event( attr.get_name(),
17                      filterable_names, filterable_value,
18                      &sinusoide);
19       }
20    ....
21    ....
22 
23 }

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

line 13-14 : The filter pair name/value is initialised
line 16-18 : The event is pushed

9.5 Using multicast protocol to transfer events


This feature is available starting with Tango 8.1. Transferring events using a multicast protocol means delivering the events to a group of clients simultaneously in a single transmission from the event source. Tango, through ZMQ, uses the OpenPGM multicating protocol. This is one implementation of the PGM protocol defined by the RFC 3208 (Reliable multicasting protocol). Nevertheless, the default event communication mode is unicast and propagating events via multicasting requires some specific configuration.

9.5.1 Configuring events to use multicast transport


Before using multicasting transport for event(s), you have to choose which address and port have to be used. In a IP V4 network, only a limited set of addresses are associated with multicasting. These are the IP V4 addresses between
224.0.1.0 and 238.255.255.255
Once the address is selected, you have to choose a port number. Together with the event name, these are the two minimum configuration informations which have to be provided to Tango to get multicast transport. This configuration is done using the MulticastEvent free property associated to the CtrlSystem object.

Image jive_simpl

In the above window dump of the Jive tool, the change event on the state attribute of the dev/test/11 device has to be transferred using multicasting with the address 226.20.21.22 and the port number 2222. The exact definition of this CtrlSystem/MulticastEvent property for one event propagated using multicast is

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

1 CtrlSystem->MulticastEvent:   Multicast address,
2                               port number,
3                               [rate in Mbit/sec],
4                               [ivl in seconds],
5                               event name

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

Rate and Ivl are optional properties. In case several events have to be transferred using multicasting, simply extend the MulicastEvent property with the configuration parameters related to the other events. There is only one MultiCastEvent property per Tango control system. The underlying multicast protocol (PGM) is rate limited. This means that it limits its network bandwidth usage to a user defined value. The optional third configuration parameter is the maximum rate (in Mbit/sec) that the protocol will use to transfert this event. Because PGM is a reliable protocol, data has to be buffered for re-transmission in case a receiver signal some lost data. The optional forth configuration parameter specify the maximum amount of time (in seconds) that a receiver can be absent for a multicast group before unrecoverable data loss will occur. Exercise care when setting large recovery interval as the data needed for recovery will be held in memory. For example, a 60 seconds (1 minute) recovery interval at a data rate of 1 Gbit/sec requires a 7 GBytes in-memory buffer. Whan any of these two optional parameters are not set, the default value (defined in next sub-chapter) are used. Here is another example of events using multicasting configuration
Image jive_sophis
In this example, there are 5 events which are transmitted using multicasting:
  1. Event change for attribute state on device dev/test/11 which uses multicasting address 226.20.21.22 and port number 2222
  2. Event periodic for attribute state on device dev/test/10 which uses multicasting address 226.20.21.22 and port number 3333
  3. Event change for attribute ImaAttr on device et/ev/01 which uses multicasting address 226.30.31.32 and port number 4444. Note that this event uses a rate set to 40 Mbit/sec and a ivl set to 20 seconds.
  4. Event change for attribute event_change_tst on device dev/test/12 which uses multicasting address 226.20.21.22 and port number 2233
  5. Event archive for attribute event_change_tst on device dev/tomasz/3 which uses multicasting address 226.20.21.22 and port number 2234

9.5.2 Default multicast related properties


On top of the MulticastEvent property previously described, Tango supports three properties to defined default value for multicast transport tuning. These properties are:

9.6 Memorized attribute


It is possible to ask Tango to store in its database the last written value for attribute of the SCALAR data format and obviously only for READ_WRITE or READ_WITH_WRITE attribute. This is fully automatic. During device startup phase, for all device memorized attributes, the value written in the database is fetched and applied. A write_attribute call can be generated to apply the memorized value to the attribute or only the attribute set point can be initialised. The following piece of code shows how the source code should be written to set an attribute as memorized and to initialise only the attribute set point.

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

1 void DevTestClass::attribute_factory(vector<Tango::Attr *> &att_list)
2  {
3      ...
4      att_list.push_back(new String_attrAttr());
5      att_list.back()->set_memorized();
6      att_list.back()->set_memorized_init(false);
7      ...
8  }

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

Line 4 : The attribute to be memorized is created and inserted in the attribute vector.
Line 5 : The set_memorized() method of the attribute base class is called to define the attribute as memorized.
Line 6 : The set_memorized_init() method is called with the parameter false to define that only the set point should be initialsied.

9.7 Transfering images


Some optimized methods have been written to optimize image transfer between client and server using the attribute DevEncoded data type. All these methods have been merged in a class called EncodedAttribute. Within this class, you will find methods to: The following code snippets are examples of how these methods have to be used in a server and in a client. On the server side, creates an instance of the EncodedAttribute class within your object

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

1 class MyDevice::Tango::Device_4Impl
2  {
3      ...
4      Tango::EncodedAttribute jpeg;
5      ...
6  }

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

In the code of your device, use an encoding method of the EncodedAttribute class

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

1 void MyDevice::read_Encoded_attr_image(Tango::Attribute &att)
2 {
3      ....
4      jpeg.encode_jpeg_gray8(imageData,256,256,50.0);
5      att.set_value(&jpeg);
6 }

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

Line 4: Image encoding. The size of the image is 256 by 256. Each pixel is coded using 8 bits. The encoding quality is defined to 50 in a scale of 0 - 100. imageData is the pointer to the image data (pointer to unsigned char)
Line 5: Set the value of the attribute using a Attribute::set_value() method.
On the client side, the code is the following (without exception management)

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

1    ....
2    DeviceAttribute da;
3    EncodedAttribute att;
4    int width,height;
5    unsigned char *gray8;
6      
7    da = device.read_attribute(Encoded_attr_image);
8    att.decode_gray8(&da,&width,&height,&gray8);
9    ....
10   delete [] gray8;
11   ...

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

The attribute named Encoded_attr_image is read at line7. The image is decoded at line 8 in a 8 bits gray scale format. The image data are stored in the buffer pointed to by gray8. The memory allocated by the image decoding at line 8 is returned to the system at line 10.

9.8 Device server with user defined event loop


Sometimes, it could be usefull to write your own process event handling loop. For instance, this feature can be used in a device server process where the ORB is only one of several components that must perform event handling. A device server with a graphical user interface must allow the GUI to handle windowing events in addition to allowing the ORB to handle incoming requests. These types of device server therefore perform non-blocking event handling. They turn the main thread of control over each of the vvarious event-handling sub-systems while not allowing any of them to block for significants period of time. The Tango::Util class has a method called server_set_event_loop() to deal with such a case. This method has only one argument which is a function pointer. This function does not receive any argument and returns a boolean. If this boolean is true, the device server process exits. The device server core will call this function in a loop without any sleeping time between the call. It is the user responsability to implement in this function some kind of sleeping mechanism in order not to make this loop too CPU consuming. The code of this function is executed by the device server main thread. The following piece of code is an example of how you can use this feature.

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

     1  
     2  bool my_event_loop()
     3  {
     4     bool ret;
     5  
     6     some_sleeping_time();
     7  
     8     ret = handle_gui_events();
     9  
    10     return ret;
    11  }
    12  
    13  int main(int argc,char *argv[])
    14  {
    15     Tango::Util *tg;
    16     try
    17     {
    18        // Initialise the device server
    19        //--------------------
    20        tg = Tango::Util::init(argc,argv);
    21  
    22        tg->set_polling_threads_pool_size(5);
    23  
    24        // Create the device server singleton 
    25        //        which will create everything
    26        //--------------------
    27        tg->server_init(false);
    28  
    29        tg->server_set_event_loop(my_event_loop);
    30  
    31        // Run the endless loop
    32        //--------------------
    33        cout << Ready to accept request << endl;
    34        tg->server_run();
    35     }
    36     catch (bad_alloc)
    37     {
    38     ...

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

The device server main event loop is set at line 29 before the call to the Util::server_run() method. The function used as server loop is defined between lines 2 and 11.


9.9 Device server using file as database


For device servers not able to access the Tango database (most of the time due to network route or security reason), it is possible to start them using file instead of a real database. This is done via the device server
-file=<file name>
command line option. In this case, are handled using the specified file instead of the Tango database. The file is an ASCII file and follows a well-defined syntax with predefined keywords. The simplest way to generate the file for a specific device server is to use the Jive application. See [21] to get Jive documentation. The Tango database is not only used to store device configuration parameters, it is also used to store device network access parameter (the CORBA IOR). To allow an application to connect to a device hosted by a device server using file instead of database, you need to start it on a pre-defined port, and you must use one of the underlying ORB option called endPoint like
myserver myinstance_name -file=/tmp/MyServerFile -ORBendPoint giop:tcp::<port number>
to start your device server. The device name passed to the client application must also be modified in order to refect the non-database usage. See [*] to learn about Tango device name syntax. Nevertheless, using this Tango feature prevents some other features to be used :


9.10 Device server without database


In some very specific cases (Running a device server within a lab during hardware development...), it could be very useful to have a device server able to run even if there is no database in the control system. Obviously, running a Tango device server without a database means loosing Tango features. The lost features are : To run a device server without a database, the -nodb command line option must be used. One problem when running a device server without the database is to pass device name(s) to the device server. Within Tango, it is possible to define these device names at two different levels :
  1. At the command line with the -dlist option: In case of device server with several device pattern implementation, the device name list given at command line is only for the last device pattern created in the class_factory() method. In the device name list, the device name separator is the comma character.
  2. At the device pattern implementation level: In the class inherited from the Tango::DeviceClass class via the re-definition of a well defined method called device_name_factory()
If none of these two possibilities is used, the tango core classes defined one default device name for each device pattern implementation. This default device name is NoName. Device definition at the command line has the highest priority.

9.10.1 Example of device server started without database usage


Without database, you need to start a Tango device server on a pre-defined port, and you must use one of the underlying ORB option called endPoint like
myserver myinstance_name -ORBendPoint giop:tcp::<port number> -nodb -dlist a/b/c

The following is two examples of starting a device server not using the database when the device_name_factory() method is not re-defined. When the device_name_factory() method is re-defined within the StepperMotorClass class.

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

     1  void StepperMotorClass::device_name_factory(vector<string> &list)
     2  {
     3      list.push_back(sr/cav-tuner/1);
     4      list.push_back(sr/cav-tuner/2);
     5  }

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

9.10.1.1 Java device server without the database


It is also possible to start a Java device server without the database using exactly the principle described in the above lines. Nevertheless, a java device server process retrieves its list of device pattern implementation from the database! Therefore, a add_class() method is defined in the java Util class and the main method must be updated.

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

     1  package StepperMotor
     2  
     3  import java.util.*;
     4  import org.omg.CORBA.*;
     5  import fr.esrf.Tango.*;
     6  import fr.esrf.TangoDs.*;
     7  
     8  public class StepperMotor extends DeviceImpl implements TangoConst
     9  {
    10      public static void main(String[] argv)
    11      {
    12          try
    13          {
    14                  
    15              Util tg = Util.init(argv,StepperMotor);
    16                          
    17              tg.add_class(StepperMotor);
    18              tg.server_init();
    19                          
    20              System.out.println(Ready to accept request);
    21  
    22              tg.server_run();
    23          }
    24          catch (OutOfMemoryError ex)
    25          {
    26              System.err.println(Can't allocate memory !!!!);
    27              System.err.println(Exiting);
    28          }
    29          catch (UserException ex)
    30          {
    31              Except.print_exception(ex);
    32                          
    33              System.err.println(Received a CORBA user exception);
    34              System.err.println(Exiting);
    35          }
    36          catch (SystemException ex)
    37          {
    38              Except.print_exception(ex);
    39                          
    40              System.err.println(Received a CORBA system exception);
    41              System.err.println(Exiting);
    42          }
    43                  
    44          System.exit(-1);                
    45          
    46      }       
    47  }

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

The add_class() method is used at line 17 before the device pattern(s) implementation initialization.

9.10.1.2 Start a java device server without database


Without database, you need to start a Tango device server on a pre-defined port, and you must use one of the underlying ORB option OAPort like
java -DOAPort=<port number> myserver myinstance_name -nodb -dlist id11/motor/1,id11/motor/2

9.10.2 Connecting client to device within a device server started without database


In this case, the host and port on which the device server is running are part of the device name. If the device name is a/b/c, the host is mycomputer and the port 1234, the device name to be used by client is
mycomputer:1234/a/b/c#dbase=no

See appendix [*] for all details about Tango object naming.

9.11 Multiple database servers within a Tango control system


Tango uses MySQL as database and allows access to this database via a specific Tango device server. It is possible for the same Tango control system to have several Tango database servers. The host name and port number of the database server is known via the TANGO_HOST environment variable. If you want to start several database servers in order to prevent server crash, use the following TANGO_HOST syntax

TANGO_HOST=<host_1>:<port_1>,<host_2>:<port_2>,<host_3>:<port_3>

All calls to the database server will automatically switch to a running servers in the given list if the one used dies.


9.12 The Tango controlled access system


9.12.1 User rights definition


Within the Tango controlled system, you give rights to a user. User is the name of the user used to log-in the computer where the application trying to access a device is running. Two kind of users are defined:
  1. Users with defined rights
  2. Users without any rights defined in the controlled system. These users will have the rights associated with the pseudo-user called All Users
The controlled system manages two kind of rights: The rights given to a user is the check result splitted in two levels:
  1. At the host level: You define from which hosts the user may have write access to the control system by specifying the host name. If the request comes from a host which is not defined, the right will be Read access. If nothing is defined at this level for the user, the rights of the All Users user will be used. It is also possible to specify the host by its IP address. You can define a host family using wide-card in the IP address (eg. 160.103.11.* meaning any host with IP address starting with 160.103.11). Only IP V4 is supported.
  2. At the device level: You define on which device(s) request are allowed using device name. Device family can be used using widecard in device name like domin/family/*
Therefore, the controlled system is doing the following checks when a client try to access a device: Then, when the client tries to access the device, the following algorithm is used: All these checks are done during the DeviceProxy instance constructor except those related to the device class allowed commands which are checked during the command_inout call.
To simplify the rights management, give the All Users user host access right to all hosts (*.*.*.*) and read access to all devices (*/*/*). With such a set-up for this user, each new user without any rights defined in the controlled access will have only Read Access to all devices on the control system but from any hosts. Then, on request, gives Write Access to specific user on specific host (or family) and on specific device (or family).
The rights managements are done using the Tango Astor[19] tool which has some graphical windows allowing to grant/revoke user rights and to define device class allowed commands set. The following window dump shows this Astor window.
\includegraphics{advanced/control}

In this example, the user taurel has Write Access to the device sr/d-ct/1 and to all devices belonging to the domain fe but only from the host pcantares He has read access to all other devices but always only from the host pcantares. The user verdier has write access to the device sys/dev/01 from any host on the network 160.103.5 and Read Access to all the remaining devices from the same network. All the other users has only Read Access but from any host.

9.12.2 Running a Tango control system with the controlled access


All the users rights are stored in two tables of the Tango database. A dedicated device server called TangoAccessControl access these tables without using the classical Tango database server. This TangoAccessControl device server must be configured with only one device. The property Services belonging to the free object CtrlSystem is used to run a Tango control system with its controlled access. This property is an array of string with each string describing the service(s) running in the control system. For controlled access, the service name is AccessControl. The service instance name has to be defined as tango. The device name associated with this service must be the name of the TangoAccessControl server device. For instance, if the TangoAccessControl device server device is named sys/access_control/1, one element of the Services property of the CtrlSystem object has to be set to
AccessControl/tango:sys/access_control/1

If the service is defined but without a valid device name corresponding to the TangoAccessControl device server, all users from any host will have write access (simulating a Tango control system without controlled access). Note that this device server connects to the MySQL database and therefore may need the MySQL connection related environment variables MYSQL_USER and MYSQL_PASSWORD described in [*]
Even if a controlled access system is running, it is possible to by-pass it if, in the environment of the client application, the environment variable SUPER_TANGO is defined to true. If for one reason or another, the controlled access server is defined but not accessible, the device right checked at that time will be Read Access.



Image AT97-65-size



Emmanuel Taurel 2013-06-27