Device polling#

audience:developers lang:c++ lang:all

Introduction#

Each tango device server automatically has a separate polling thread pool. Polling a device means periodically executing command or reading attributes on a device and storing the results, or the thrown exception, in a polling buffer.

The aim of polling is threefold:

  • Speed-up response time for slow devices

  • Get a first-level history of device command output or attribute value

  • Be the data source for the Tango event system

Speeding-up response time is achieved because the command_inout or read_attribute CORBA operation is able to get its data from the polling buffer or from a real access to the device. For “slow” devices, 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. The polling is also the data source for the event system because detecting an event means being able to regularly read the data, store it and declaring that it is an event after some comparison with older values.

There are currently two polling algorithms available, the current one and the old one. See the reference section on polling properties to get details on this.

Configuring the polling system#

Configuring what has to be polled and how#

It is possible to configure the polling in order to poll:

  • Any command which does not need input parameter

  • Any attribute

Configuring the polling is done by sending a 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 adds 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

Changes one object polling period

StartPolling

Starts polling for the whole process

StopPolling

Stops polling for the whole process

PolledDevice

Allows a client to know which device are polled

DevPollStatus

Allows 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 admin device. This demands to add a method call in the device server source code while creating the command or attribute. In this case, for every device 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 admin device. The following piece of code shows how the source code should be written.

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

A polling period of 400 ms is set for the command called IOStartPoll at line 9 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 admin 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 the device attribute/command polling period. The polling period has to be choosen with care. If reading an attribute needs 200 ms, there is no point polling this attribute with a period equal to or below 200 ms. You should also take into account that some free time has to be foreseen for external requests 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 defines a minimum polling period in milliseconds 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 polling properties to get a precise syntax description for these properties.

Jive also allows a graphical device polling configuration.

Configuring the polling threads pool#

A Tango device server process may have several polling threads managed as a pool. For instance, this could be useful in case of devices within the same device server process but accessing to different hardware channels when one of the channels 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. The configuration of the polling thread pool is done using two properties associated to the admin device. These properties are named:

  • polling_threads_pool_size defining the maximun number of threads that you can have in the pool

  • polling_threads_pool_conf defining which threads in the pool manages which device

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 arise:

  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 Astor or manually using Jive. 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 polling properties reference part.

The following screenshot is the Astor tool window which allows polling threads pool management.

../_images/ThreadsManagement.png

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

Choosing polling algorithm#

Starting with Tango 9, you can choose between two different polling algorithm:

  • The polling as it was in Tango since it has been introduced. This means:

    • For one device, always poll attribute one at a time even if the polling period is the same (use of read_attribute instead of read_attributes)

    • Do not allow the polling thread to be late: If it is the case (because at the end of polling object 1, the current time is greater than the polling date planned for object 2), discard polling object and inform event user by sending one event with error (Polling thread is late and discard….)

  • New polling algorithm introduced in Tango 9 as the default one. This means:

    • For one device, poll all attributes with the same polling period using a single device call (read_attributes)

    • Allow the polling thread to be late but only if number of late objects decreases.

The advantages of new polling algorithm are

  1. In case of several attributes polled on the same device at the same period a lower device occupation time by the polling thread (due to a single read_attributes() call instead of several single read_attribute() calls)

  2. Less Polling thread late errors in the event system in case of device with non constant response time

The drawback is

  1. The loss of attribute individual timing data reported in the polling thread status

It is still possible to return to pre-release 9 polling algorithm. To do so, you can use the device server process administration device polling_before_9 property by setting it to true. It is also possible to choose this pre-release 9 algorithm in device server process code in the main function of the process using the Util::set_polling_before_9() method.

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

  • From the device itself

  • From the polling buffer

  • From the polling buffer first and from the device if data in the polling buffer are invalid or if the polling is badly configured.

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:

  • The data in the buffer is not valid any more. Every time data is requested from the polling buffer, a check is done between the client request date and the date when the data were stored in the buffer. An exception is thrown if the delta is greater than the polling period multiplied by a “too old” factor. This factor has a default value and can be set via the poll_old_factor device property, see also polling properties.

  • The polling is correctly configured but there is no data yet in the polling buffer.

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. For commands, a CORBA operation called command_inout_history_2 allows this retrieval. The client specifies the command name and the record number 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).

Externally triggered polling#

Sometimes, rather than polling a command or an attribute regulary with a fixed period, it is more interesting to manually decide when the polling must occur. 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 admin device AddObjPolling or UpdObjPollingPeriod commands. Once in this mode, the attribute (or command) polling is triggered with the trigger_cmd_polling/trigger_attr_polling methods of the Util class. The following piece of code shows how this method could be used for one externally triggered command.

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

line 3 : The externally polled command name

line 4 : The device object

line 8 : Trigger polling of command MyCmd

Filling polling buffer#

Assume that some hardware to be interfaced already returned an array of value, timestamp pairs. 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/fill_attr_polling_buffer methods of the Util class. For commands, 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. See polling properties how to learn the polling buffer depth can be defined. The same way is used for attributes with the TimedAttrData and AttrHistoryStack template classes. 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.

 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      auto when = time(NULL);
19
20      Tango::TimedCmdData<DevVarLongArray> tcd(&dvla_array[k], when);
21      chs.push(tcd);
22  }
23
24  auto *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  .....

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 the command method code returns. 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.

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

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 from Tango::string_dup 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 attribute polling buffer

It is not necessary to free the memory allocated at line 10. The fill_attr_polling_buffer method will do it for you.

Setting and tuning the polling in a Tango class#

Even if the polling is normally set and tuned with external tools like Jive, it is possible to set it directly in the code of a Tango class. A set of methods of the DeviceImpl class allows the user to deal with polling. These methods are:

  • is_attribute_polled and is_command_polled to check if one command/attribute is polled

  • get_attribute_poll_period and get_command_poll_period to get polled object polling period

  • poll_attribute and poll_command to poll command or attribute

  • stop_poll_attribute and stop_poll_command to stop polling a command or an attribute

The following code snippet is just an example of how these methods could be used:

 1      void MyClass::read_attr(Tango::Attribute &attr)
 2      {
 3          ...
 4
 5          std::string att_name{"SomeAttribute"};
 6          std::string another_att_name{"AnotherAttribute"};
 7
 8          if(is_attribute_polled(att_name))
 9          {
10              stop_poll_attribute(att_name);
11          }
12          else
13          {
14              poll_attribute(another_att_name, 500);
15          }
16
17          ....
18
19      }