Subsections
Each Tango attribute two several alarms. These alarms are :
- A four thresholds level alarm
- The read different than set (RDS) alarm
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 value is below or equal the attribute configuration
min_alarm parameter, the attribute quality
factor is switched to Tango::ATTR_ALARM and if
the device state is Tango::ON, it is switched to Tango::ALARM.
- If the attribute value is below or equal the attribute configuration
min_warning parameter, the attribute
quality factor is switched to Tango::ATTR_WARNING
and if the device state is Tango::ON, it is switched to Tango::ALARM.
- If the attribute value is above or equal the attribute configuration
max_warning parameter, the attribute
quality factor is switched to Tango::ATTR_WARNING
and if the device state is Tango::ON, it is switched to Tango::ALARM.
- If the attribute value is above or equal the attribute configuration
max_alarm parameter, the attribute quality
factor is switched 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 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.
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.
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.
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 :
- Speed-up response time for slow device
- 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 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.
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 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.
-
- 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 }
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.
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:
- 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 arrive:
- 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
- There is no thread already created for the device. We have two sub-cases:
- 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)
- 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.
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
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 are not valid any more. Every time data are
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 is up-datable via a device property. This is detailed in the reference
part of this manual.
- The polling is correctly configured but there is no data yet in the
polling buffer.
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).
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.
-
- 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 .....
line 3 : The externally polled command name
line 4 : The device object
line 8 : Trigger polling of command MyCmd
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.
-
- 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 .....
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.
-
- 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 .....
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.
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:
- 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 exmaple of how these methods
could be used. They are documented in [24]
-
- [language=C++]advanced/poll_in_ds.cpp.lines
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.
A classical Tango device server without any connected clients has
eight threads. These threads are :
- The main thread waiting in the ORB main loop
- Two ORB implementation threads (the POA thread)
- The ORB scavanger thread
- The signal thread
- The heartbeat thread (needed by the Tango event system)
- Two Zmq implementation threads
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.
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:
- 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)
- 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)
- 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)
- 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.
-
- 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 }
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.
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:
- Serialization by kernel. This is the default case. The kernel is managing
the serialization
- 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
- 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.
-
- 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 ....
9
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
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.
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:
- The main thread
- 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:
- The main thread
- The ORB scavenger thread
- Two Zmq implementation threads
- Two Tango event system related threads (the KeepAliveThread and the
EventConsumer thread)
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.
-
- 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 }
line 13-14 : The filter pair name/value is initialised
line 16-18 : The event is pushed
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.
-
- 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 }
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.
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:
- Encode an image in a compressed way (JPEG) for images
coded on 8 (gray scale), 24 or 32 bits
- Encode a grey scale image coded on 8 or 16 bits
- Encode a color image coded on 24 bits
- Decode images coded on 8 or 16 bits (gray scale) and returned a 8
or 16 bits grey scale image
- Decode color images transmitted using a compressed format (JPEG) and
returns a 32 bits RGB image
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
-
- 1 class MyDevice::Tango::Device_4Impl
2 {
3 ...
4 Tango::EncodedAttribute jpeg;
5 ...
6 }
In the code of your device, use an encoding method of the EncodedAttribute
class
-
- 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 }
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)
-
- 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 ...
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.
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.
-
- 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 ...
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.
8 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,
- Getting, setting and deleting class properties
- Getting, setting and deleting device properties
- Getting, setting and deleting class attribute properties
- Getting, setting and deleting device attribute properties
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 :
- No check that the same device server is running twice.
- No device or attribute alias name.
- In case of several device servers running on the same host, the user
must manually manage a list of already used network port.
9 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 :
- No check that the same device server is running twice.
- No device configuration via properties.
- No event generated by the server.
- No memorized attributes
- No device attribute configuration via the database.
- No check that the same device name is used twice within the same control
system.
- In case of several device servers running on the same host, the user
must manually manage a list of already used network port.
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 :
- 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.
- 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.
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.
- StepperMotor et -nodb -dlist id11/motor/1,id11/motor/2
This command line starts the device server with two devices named
id11/motor/1 and id11/motor/2
- StepperMotor et -nodb
This command line starts a device server with one device named NoName
When the device_name_factory() method is re-defined within
the StepperMotorClass class.
-
- 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 }
- StepperMotor et -nodb
This commands starts a device server with two devices named sr/cav-tuner/1
and sr/cav-tuner/2.
- StepperMotor et -nodb -dlist id12/motor/1
Starts a device server with only one device named id12/motor/1
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.
-
- 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 }
The add_class() method is used at line 17 before the device pattern(s)
implementation initialization.
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
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.
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.
11 The Tango controlled access system
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:
- Users with defined rights
- 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:
- Write access meaning that all type of requests are allowed on the
device
- Read access meaning that only read-like access are allowed (write_attribute,
write_read_attribute and set_attribute_config network calls are
forbidden). Executing a command is also forbidden except for commands
defined as Allowed commands.
Getting a device state or status using the command_inout call is
always allowed. The definition of the allowed commands is done at
the device class level. Therefore, all devices belonging to the same
class will have the allowed commands set.
The rights given to a user is the check result splitted in two levels:
- 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.
- 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:
- Get the user name
- Get the host IP address
- If rights defined at host level for this specific user and this IP
address, gives user temporary write acccess to the control system
- If nothing is specified for this specific user on this host, gives
to the user a temporary access right equal to the host access rights
of the All User user.
- If the temporary right given to the user is write access to the control
system
- If something defined at device level for this specific user
- If there is a right defined for the device to be accessed (or for
the device family), give user the defined right
- Else
- If rights defined for the All Users
user for this device, give this right to the user
- Else, give user the Read Access for this device
- Else
- If there is a right defined for the device to be accessed (or for
the device family) for the All User
user, give user this right
- Else, give user the Read Access right for this device
- Else, access right will be Read Access
Then, when the client tries to access the device, the following algorithm
is used:
- If right is Read Access
- If the call is a write type call, refuse the call
- If the call is a command execution
- If the command is one of the command defined in the Allowed
commands for the device class, send the call
- Else, refuse the call
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.
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.
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.
Emmanuel Taurel
2012-06-06