How to add dynamic attributes to a device class#
A non-fixed number of attributes is common while working with Input/Output devices with a variable number of channels (PLCs, DaqBoards, Timers/Counters), when depending of the operation mode more or less scalar or spectrum attributes are be needed.
Generating your class with Pogo#
DynAttr
with three types of attributes:
StaticAttr: A scalar, Tango::DevShort, READ attribute,
LongDynAttr: A scalar, Tango::DevLong, READ_WRITE attribute,
DoubleDynAttr: A scalar, Tango::DevDouble, READ_WRITE attribute.
Each device belonging to this class has one StaticAttr
and a dynamic
list of LongDynAttr
or DoubleDynAttr
attributes. This list of dynamic
attributes is defined with a device property named DynAttrList
.
Therefore, using Pogo, we define a Tango class with three scalar
attributes with the definition given above. We also create the
DynAttrList property for defining dynamic attribute. This is a vector of
strings with a couple of strings for each attribute. The first string is
the dynamic attribute type (LongDynAttr
or DoubleDynAttr
) and the second
string is the attribute name.
Note
The dynamic attributes have a light green background color on the main Pogo window.

Dynamic attribute registration#
To register device dynamic attributes, you need to:
call the
Tango::DeviceImpl::add_attribute()
method for each device dynamic attributecreate the data used with the attribute (for the Attribute::set_value() method)
Pogo generates method(s) named DynAttr::add_$<DynAttrName>_dynamic_attribute(std::string att_name)
which do
this job for you. You have to call these methods according to your needs in another Pogo generated method
named DynAttr::add_dynamic_attributes()
which is executed at device creation time.
In our example, in this method we have to:
analyze the content of the device DynAttrList property and create the necessary attributes using the helper method also generated by Pogo
The code of the DynAttr::add_dynamic_attributes() method looks like
1void DynAttr::add_dynamic_attributes()
2{
3 // Example to add dynamic attribute:
4 // Copy inside the following protected area to create instance(s) at startup.
5 // add_LongDynAttr_dynamic_attribute("MyLongDynAttrAttribute");
6 // add_DoubleDynAttr_dynamic_attribute("MyDoubleDynAttrAttribute");
7
8 /*----- PROTECTED REGION ID(DynAttr::add_dynamic_attributes) ENABLED START -----*/
9 /* clang-format on */
10
11 const auto len = dynAttrList.size();
12
13 if(len == 0)
14 {
15 return;
16 }
17
18 if((len % 2) != 0)
19 {
20 TANGO_THROW_EXCEPTION("DynAttrInvalidSetup", "Expected a multiple of two entries in dynAttrList");
21 }
22
23 for(size_t i = 0; i < len; i += 2)
24 {
25 if(dynAttrList[i] == "LongDynAttr")
26 {
27 add_LongDynAttr_dynamic_attribute(dynAttrList[i + 1]);
28 }
29 else if(dynAttrList[i] == "DoubleDynAttr")
30 {
31 add_DoubleDynAttr_dynamic_attribute(dynAttrList[i + 1]);
32 }
33 else
34 {
35 TANGO_THROW_EXCEPTION("DynAttrInvalidSetup", "Unexpected dynamic attribute name");
36 }
37 }
38
39 /* clang-format off */
40 /*----- PROTECTED REGION END -----*/ // DynAttr::add_dynamic_attributes
41}
Note
The data associated with all LongDynAttr dynamic attributes are initialized to 0 and the data associated to all DoubleDynAttr dynamic attributes are initialized with 0.0
The definition of the DoubleDynAttr attribute is simply to return when read, the last value which has been written. The code for the DoubleDynAttr reading/writing is the following
1void DynAttr::read_DoubleDynAttr(Tango::Attribute &attr)
2{
3 DEBUG_STREAM << "DynAttr::read_DoubleDynAttr(Tango::Attribute &attr) entering... " << std::endl;
4 Tango::DevDouble *att_value = get_DoubleDynAttr_data_ptr(attr.get_name());
5 /*----- PROTECTED REGION ID(DynAttr::read_DoubleDynAttr) ENABLED START -----*/
6 /* clang-format on */
7
8 attr.set_value(att_value);
9
10 /* clang-format off */
11 /*----- PROTECTED REGION END -----*/ // DynAttr::read_DoubleDynAttr
12}
13
14void DynAttr::write_DoubleDynAttr(Tango::WAttribute &attr)
15{
16 DEBUG_STREAM << "DynAttr::write_DoubleDynAttr(Tango::WAttribute &attr) entering... " << std::endl;
17 // Retrieve write value
18 Tango::DevDouble w_val;
19 attr.get_write_value(w_val);
20 /*----- PROTECTED REGION ID(DynAttr::write_DoubleDynAttr) ENABLED START -----*/
21 /* clang-format on */
22
23 auto *att_value = get_DoubleDynAttr_data_ptr(attr.get_name());
24 *att_value = w_val;
25
26 /* clang-format off */
27 /*----- PROTECTED REGION END -----*/ // DynAttr::write_DoubleDynAttr
28}
The code of the read method in it’s Pogo generated part retrieves a
pointer to the data associated with this attribute with the helper
method named DynAttr::get_$<DynAttrName>_data_ptr(std::string att_name)
.
The user code simply passes this pointer to the Tango Attribute::set_value()
method.
The user code of the write method also uses the Pogo generated helper method to get the attribute data pointer and set this data to the value sent by the caller.
The definition of the LongDynAttr is a bit more sophisticated. For one device of this Tango class, we have several dynamic attributes of this LongDynAttr type. According to which attribute is read or written, we have to call different method accessing the hardware.
The code for reading/writing the LongDynAttr attribute is given below:
1void DynAttr::read_LongDynAttr(Tango::Attribute &attr)
2{
3 DEBUG_STREAM << "DynAttr::read_LongDynAttr(Tango::Attribute &attr) entering... " << endl;
4 Tango::DevLong *att_value = get_LongDynAttr_data_ptr(attr.get_name());
5
6 /*----- PROTECTED REGION ID(DynAttr::read_LongDynAttr) ENABLED START -----*/
7
8 std::string &att_name = attr.get_name();
9 if(att_name == dynAttrList[1])
10 {
11 // Access hardware for channel 1 which is the first attribute in the list
12 *att_value = read_hardware_channel1();
13 }
14 else if(att_name == dynAttrList[3])
15 {
16 // Access hardware for channel 2 which is the second attribute in the list
17 *att_value = read_hardware_channel2();
18 }
19 else
20 {
21 TANGO_THROW_EXCEPTION("DynAttrInvalidSetup", "Unexpected dynamic attribute name");
22 }
23
24 // Set the attribute value
25 attr.set_value(att_value);
26
27 /*----- PROTECTED REGION END -----*/ // DynAttr::read_LongDynAttr
28}
29
30void DynAttr::write_LongDynAttr(Tango::WAttribute &attr)
31{
32 DEBUG_STREAM << "DynAttr::write_LongDynAttr(Tango::Attribute &attr) entering... " << endl;
33
34 // Retrieve write value
35 Tango::DevLong w_val;
36 attr.get_write_value(w_val);
37
38 /*----- PROTECTED REGION ID(DynAttr::write_LongDynAttr) ENABLED START -----*/
39
40 std::string &att_name = attr.get_name();
41 if(att_name == dynAttrList[1])
42 {
43 // Access hardware for channel 1 which is the first attribute in the list
44 write_hardware_channel1(w_val);
45 }
46 else if(att_name == dynAttrList[3])
47 {
48 // Access hardware for channel 2 which is the second attribute in the list
49 write_hardware_channel2(w_val);
50 }
51 else
52 {
53 TANGO_THROW_EXCEPTION("DynAttrInvalidSetup", "Unexpected dynamic attribute name");
54 }
55
56 /*----- PROTECTED REGION END -----*/ // DynAttr::write_LongDynAttr
57}
Running the server#
A Tango device server process hosting this DynAttr class has been defined in the database with two device. The dynamic attributes for these two devices are:


As shown by the Pogo screen-shot in the beginning of this HowTo, the Tango class also defines a static attribute for each device named StaticAttr. Running the device server and opening TestDevice panels on each device displays device attribute list:


This method fully supports restarting device(s) or server using the device server process admin device.