Tango Controls 10.0.0 documentation

Skip to main content
`Ctrl`+`K`

# Welcome to the Tango Controls documentation!

audience:all

## Explanation

audience:all

### Overview of Tango Controls

audience:all

#### What is Tango Controls?

Tango Controls is an object-oriented, distributed control system framework which defines a communication protocol, an Application Programmers Interface (API) and provides a set of tools and libraries to build software for control systems, especially SCADA.

Tango Controls has been designed to manage small and large systems. It is built around concept of devices and device classes. This is unique feature of Tango Controls and make it different to other SCADA software which usually treats a controls system as a set of signals and read and write of process values.

Devices are created by device servers. Device servers are processes implementing set of device classes. Device classes implement a state machine, command (actions or methods), pipes and attributes (data fields) for each class. Each device therefore has state, zero or more commands, zero or more pipes and zero or more attributes. Device classes are responsible for translating hardware communication protocols into Tango Controls communication. This way you may control and monitor all your equipment like motors, valves, oscilloscopes, etc. Device classes can be used to implement any algorithm or act as a mailbox to any other software program or system.

Each system has a centralised database which stores configuration data used at the initialisation of a device server, and acts as name server by storing the dynamic network addresses. The database acts also as permanent store of dynamic settings which need to be memorised.

Each Tango Control system is identified by its Tango Host. A large system can be made up of tens of thousands or devices (the limit has not been reached yet) and there is no limit on the number of Tango Control systems running at the same time. The Tango protocol i.e. the API supports transparent access to devices from multiple systems.

Tango Controls communication protocol defines how all components of the system communicates with each other. Tango uses CORBA for synchronous communications and ZeroMQ for asynchronous communication. The detail of these protocols are hidden from the developer and user of Tango by the API and high level tools.

#### Tango Technologies

TANGO is based on the 21st century technologies :

- CORBA and ZMQ to communicate between device server and clients

- C++, Python and Java as reference programming languages

- Linux, Windows and MacOS as operating systems

- Modern object oriented design patterns

- Naturally implements a microservices architecture

- Unit tested, continuous integration enabled

- Hosted on Gitlab (tango-controls)

- Extensive documentation + tools, large community

#### Tango Controls Community

Since the creation of Tango, over 40 small and large facilities (see http://www.tango-controls.org/partners/) have adopted Tango for their control system. Tango is now used to control not only accelerators but also experimental lasers (ELI), wind tunnels (Onera), and most recently has been adopted by the world’s largest radio telescope observatory as its core control system (Square Kilometre Array Observatory (SKAO)).

### Device

#### Concept

A device is a key concept of Tango Controls. This concept can be directly linked to the notion of microservice: 1 device = 1 microservice.

A device can represent:

- A piece of equipment (eg: a power supply),

- Multiple pieces of equipment (eg: a set of 4 motors driven by the same controller),

- A set of software functions (eg: image processing),

- A group of devices representing a subsystem

The Tango Device abstracts away the specific nature of a piece of equipment, i.e. it hides the specific implementation details from the user who does not need to care about communication protocols etc. and provides the user with a model which speaks their languages e.g. physical or engineering parameters.

In the real world, devices vary from serial line devices to devices interfaced by field-bus to memory mapped VME cards or PC cards to entire data acquisition systems. The definition of a device depends very much on the user’s requirements. In the simple case a device server can be used to hide the serial line protocol required to communicate with a device. For more complicated devices the device server can be used to hide the entire complexity of the device timing, configuration and acquisition cycle behind a set of high level commands.

#### Description

A device is the basic entity of the control system. In the Tango world, everything is a device. A Device has an interface composed of commands and attributes, which provides the service of the device. It also has properties, stored in the relational database, which are generally used as configuration settings.

Each device belongs to a Device Class.

Devices are created by Device Servers, which will call the device classes. Each device is created and stored in a process called a device server. This will call the device class that the device belongs to. Devices are configured at runtime via a set of properties which are stored in the database.

All devices support a black box where client requests for attributes or operations are recorded. This feature allows easier debugging session for device already installed in a running control system.

#### Device hierarchy

A Tango control system can be hierarchically organized.

At the lower level, we find elementary devices which are associated with equipments, e.g. a vacuum pump, a motor, an I/O card.

At higher levels, the devices are « logical ». These devices, based on the lower-level devices, manage and represent a subset of the control system. This is usually a synthetic view of a set of equipments with a high-level steering (functions can perform sequences of actions on several basic devices).

For example, a high-level device achieves “complex” features. This device is usually bound to evolve regardless of the hardware. Therefore, it is necessary to separate and segregate responsibilities related to the logic functionality and those related to hardware interfaces.

It is possible to access any other device from every device at every level.

The following diagram illustrates the concept of hierarchy of devices:

The software bus view of devices

Hierarchical view of devices

#### Device State

Every Tango device has a state implemented by a finite state machine. It reflects the internal state of the system it represents.

The available states are limited to: `ON`, `OFF`, `CLOSE`, `OPEN`, `INSERT`, `EXTRACT`, `MOVING`, `STANDBY`, `FAULT`, `INIT`, `RUNNING`, `ALARM`, `DISABLE`, `UNKNOWN`

A color code is associated to each state and is used in the main GUI tools to have a unified manner of representing the state of equipment.

State | Colour | Meaning
--- | --- | ---
ON | green | This state could have been called OK or OPERATIONAL. It means that the device is in its operational state. (E.g. the power supply is giving its nominal current, the motor is ON and ready to move, the instrument is operating). This state is modified by the Attribute alarm checking of the DeviceImpl:dev_state method. i.e if the state is ON and one attribute has it’s quality factor to ALARM, then the state is modified to ALARM
OFF | white | The device is in normal condition but is not active. e.g the power supply main circuit breaker is open; the RF transmitter has no power etc…
CLOSE | white | Synonym of OFF state. Can be used when OFF is not adequate for the device e.g case of a valve, a door, a relay, a switch.
OPEN | green | Synonym of ON state. Can be used when ON is not adequate for the device e.g case of a valve, a door, a relay, a switch.
INSERT | white | Synonym of OFF state. Can be used when OFF is not adequate for the device. Case of insertable/extractable equipment, absorbers, etc… This state is here for compatibility reason we recommend to use OFF or CLOSE when possible.
EXTRACT | green | Synonym of ON state. Can be used when ON is not adequate for the device Case of insertable/extractable equipment, absorbers, etc… This state is here for compatibility reason we recommend to use ON or OPEN when possible.
MOVING | light blue | The device is in a transitory state. It is the case of a device moving from one state to another.( E.g a motor moving from one position to another, a big instrument is executing a sequence of operation, a macro command is being executed.)
STANDBY | yellow | The device is not fully active but is ready to operate. This state does not exist in many devices but may be useful when the device has an intermediate state between OFF and ON. E.g the main circuit breaker is closed but there is no output current. Usually Standby is used when it can be immediately switched ON. While OFF is used when a certain time is necessary before switching ON.
FAULT | red | The device has a major failure that prevents it to work. For instance, A power supply has stopped due to over temperature A motor cannot move because it has fault conditions. Usually we cannot get out from this state without an intervention on the hardware or a reset command.
INIT | beige | This state is reserved to the starting phase of the device server. It means that the software is not fully operational and that the user must wait
RUNNING | dark green | This state does not exist in many devices but may be useful when the device has a specific state above the ON state. (E.g. the detector system is acquiring data, An automatic job is being executed). Note that this state is different from the MOVING state. It is not a transitory situation and may be a normal operating state above the ON state.
ALARM | orange | The device is operating but one of this attribute is out of range. It can be linked to alarm conditions set by attribute properties or a specific case. (E.g. temperature alarm on a stepper motor, end switch pressed on a stepper motor, up water level in a tank, etc…) In alarm, usually the device does it’s job but the operator has to perform an action to avoid a bigger problem that may switch the state to FAULT.
DISABLE | magenta | The device cannot be switched ON for an external reason. e.g. the power supply has it’s door open, the safety conditions are not satisfactory to allow the device to operate
UNKNOWN | grey | The device cannot retrieve its state. It is the case when there is a communication problem to the hardware (network cut, broken cable etc…). It could also represent an incoherent situation

#### Device Class

A device class is an abstraction of a device’s interface. The device class contains a complete description and implementation of the behavior of all members of that class. It defines the list of attributes, pipes and commands that are available for a certain device, which are then available to users and to other components of a Tango system.

A device class often relates to the specific hardware that it interfaces with, for example the `SerialLine` class defines an interface to communicate with serial line hardware.

All classes are derived from one root class thus allowing some common behavior for all devices. New device classes can also be constructed out of existing device classes. In this way a new hierarchy of classes can be built up in a short time. Device classes can use existing devices as sub-classes or as sub-objects. The practice of reusing existing classes is classical for Object Oriented Programming (OOP) and is one of its main advantages.

##### The `DeviceClass`

###### Description

Every device of the same class supports the same list of commands and hence this list of available commands is stored in the `DeviceClass`. For example, the structure returned by the `info` operation contains a URL to the documentation. This URL is the same for every device belonging to the same class and hence the documentation URL is a data member of this class. There should only be one instance of this class per device. The `DeviceClass` also stores the device list.

The `DeviceClass` is an abstract class because the two methods `device_factory()` and `command_factory()` are declared as `pure virtual`. The role of the `device_factory()` method is to create all the devices belonging to the device class. The role of the `command_factory()` method is to create one instance of all the classes needed to support device commands.

The `DeviceClass` also contains the `attribute_factory` method whose role is to store the name of all the device attributes. The default implementation of this method is an empty body representing a device without any attributes.

###### Contents

The contents of this class can be summarize as:

- The `command_handler` method

- Methods to access data members

- Signal related method (C++ specific)

- Class constructor. It is protected to implements the Singleton pattern

- Class data members like the class command list, the device list, etc

#### Device Server

A Device Server is the process (i.e. the executable) that will create, run and serve instances of Devices. It must contain one or more Device Class, and can instantiate any number of Devices from those classes. Devices started from the same Device Server will run in the same process and hence share system resources such as memory, therefore it can be convenient to group devices into the same Device Server to optimise performance.

Each Device Server has a unique name made up of the name of the executable and a character string called the instance name. The pair of executable / instance name has to be unique in a Tango control system. The Device Server is responsible for querying the database to find out the list of Devices and their Device Classes to create. The Device Server must create the Devices, call their initialise routine and export them once the Device is created.

Device Servers create an internal device of their own called the Admin Device. This device is used to monitor and control the Device Server process lifecycle. It can perform tasks like restarting Devices, restarting the Device Server process, and starting or stopping polling.

Device Servers are linked to the Device classes that they will serve. Device Servers are usually managed by the Astor tool.

Runtime representation of a Device server with two classes A and B

#### Summary

Sometimes there are misuses of language regarding the concepts of a device, device server and a Tango class. Below is a summary:

- DeviceClass class: a class defining the interface and state machine (only used in C++ device classes).

- Device class: a class implementing the device control.

- Device: An instance of a Device class giving access to the services of the DeviceClass class.

- Device Server: process in which one or more Tango classes are executed (device server).

The diagrams below illustrate these concepts:

Tango Deployment

A Device Server can host several Device classes, each class can be instantiated one or more times within the same device server. There are no specific rules regarding the maximum number of classes or the maximum number of instances operating within a single Device Server.

In particular cases, due to limitations imposed by the hardware or software interface, it is not always possible to run several instances of a Device class within the same Device Server:

- Case of a DLL’s use: some DLLs can’t be used by two threads of the same process.

In other cases, it is useful to have multiple devices running in the same Device Server:

- Case of motors: a single axis controller for 4 motors.

### Command

A command is associated with an action. On, Off, Start, Stop are commons examples. Commands are well-suited for sending orders to a device, such as switching from one mode of operation to another. For example, switching a power supply on or off is typically done via a command.

#### Description

Each device class implements a list of commands. Commands are essential because they serve as the primary controls for managing a device. Commands have a fixed calling syntax, consisting of one input argument and one output argument. Argument types must be chosen from a set of predefined data types which include:

- void, boolean, short, long, long64, float, double, string, unsigned short, unsigned long, unsigned long64

- 1D array of the followings types : char, short, long, long64, float, double, unsigned short, unsigned long, unsigned long64, string

- State: enumeration, representing the different states described in the section on Device State .

- 2 particular types: longstringarray and doublestringarray. These are structures including one array of long/double and one array of string.

The list of data types is fixed. If you need to add your own data type then use the DevEncoded type and encode your own data type. Or you can use the DevPipe communication channel (available since Tango 9).

Commands can execute any sequence of actions. They can be executed synchronously (the requester is blocked until the command ends) or asynchronously (the requester sends the request and is called back when the command ends).

#### The Default Commands

There are three default commands that every device must respond to in order to enhance standard behavior in a Tango control system. These commands are State, Status, and Init.

##### State Command

The default behavior of the State command is to return the current state of a device, with one exception: if the result of the command is ON and in an ALARM state, Tango will:

- Read all attribute(s) with an alarm level defined,

- Change the device state to ALARM if any attribute read value is above/below the alarm level,

- Return the device state.

Note

This behavior can be redefined by the implementer of the device. It is recommended to check the device documentation for exact behavior.

Note

There is no difference in behavior between the State command and the State attribute. In both cases, the resulting state is the same.

##### Status Command

The Status command returns a text message that provides detailed information about the state of the device. The default behavior of the Status command follows the same logic as the default behavior of the State command. When the device state is ALARM, the command will return the device status along with a list of all attributes that are in an alarm condition.

Note

This behavior can be redefined by the implementer of the device. It is recommended to check the device documentation for exact behavior.

Note

There is no difference in behavior between the Status command and the Status attribute. In both cases, the resulting status is the same.

##### Init Command

The Init command is used to reinitialize a device without losing its network connection. The behavior of this command should reload any resources owned by the device, similar to how they are loaded after the device’s initial startup.

### Attribute

audience:all lang:all

An Attribute is a Tango concept that can represent a physical quantity of a device or equipment. It can also represent a quantity which is not tied to any equipment but might have been computed in software as there is no enforced tie to hardware for Attributes. Essentially, any value that you want available on the Tango bus is an attribute. For example:

- A device associated with a motor has a `position` attribute expressed in mm.

- A device associated with a thermocouple has a `temperature` attribute expressed in Celsius (or any another suitable unit).

The main purpose of an Attribute is to replace getters and setters by providing read and (optionally) write access to this quantity. For example: the position of a motor will be obtained by reading the associated attribute (position) and not by running a command like `get_position`.

In object oriented terminology, an Attribute corresponds to an instance variable (also called a field or a member) of a Device object or simpler an Attribute is one of the parts of a Device.

The data associated with the Tango attributes are the only values that can be archived. The Tango archiving system (HDB/TDB) doesn’t have any functions to archive the result of a command. Similarly, some mechanisms to store the experimental data (such as those implemented by the DataRecorder of SOLEIL) are only based on attributes.

Some further example use cases of an Attribute are:

- The wind speed measured by a weather station.

- A correction factor computed by an algorithm that is applied elsewhere.

- A fourier transformation of a 2d-array.

- A random number.

#### Attribute Properties

A Tango attribute has a group of settings that describe it.

These configuration parameters are called AttributeProperties. They can be considered as metadata to enhance the semantic and describe the data. They can be used by GUI clients for configuring their viewers in the best manner and displaying extra information.

Attribute properties describe the attribute data and define some of its behaviour such as alarm limits, units etc…

There are 3 types of Attribute Properties:

- Static metadata: They describe the kind of data carried by the Tango Attribute. The static metadata includes properties such as the name, the type, the dimension, if the attribute is writable or not. These data are hardcoded, defined for the whole life of the attribute and cannot be modified.

- Dynamic metadata: They describe more precisely the meaning of the data and some behaviour. They are used by GUI viewers to configure themselves. Most importantly, they can be modified at run time.

- Runtime metadata: They describe the Attribute’s value and its current condition like warnings or alarms and its timestamp.

Attributes can be statically defined in the source code of a Device or be created in a dynamic way during the runtime of a Device. When an Attribute is added during the runtime of a Device it is referred to as a Dynamic Attribute.

These metadata are hosted in the class itself and can be set by the programmer or by a configuration in the Tango database. The following section goes into more detail with examples of static and dynamic properties

##### Static attribute properties

The following list contains some of the mandatory static metadata of an Attribute. It provides an insight into what is currently supported.

- `name`: The name identifies the Attribute and is unique for a Device. There cannot be other entities with the same name in a Device. Some restrictions to the allowed characters in a name apply, but alphanumerical characters are supported. For example: OutCurrent, InCurrent…

- `data_type`: The attribute data type identifies the Tango numeric type associated to the attribute: DevBoolean, DevUChar, Dev[U]Short, Dev[U]Long, Dev[U]Long64, DevFloat, DevDouble, DevString, DevEncoded (the Tango type that encapsulates client data). Enumerations are supported and explained more in the Enumerated Attribute document. To learn more about the all of available data types, please refer to the Tango Controls RFCs, especially the RFC for the Tango Data Types.

- `writeable`: specifies if the Attribute’s quantity can be modified by clients (read-write) or not (read-only). Read-only Attributes are immutable for clients but its quantity can internally be modified by the Device that it is a member of. There are 4 possible types of access but in many cases only 2 really need to be used:

  - READ: The attribute can only be read (e.g. a temperature)

  - WRITE: The attribute can only be written (to be used only in very specific cases. The READ_WRITE is generally more suitable for real cases)

  - READ_WRITE: The attribute can be written and read (the most common case) e.g. The current of a power supply, The position of an axis…

  - READ_WITH_WRITE (deprecated, do not use)

- `data_format`: describes the dimension of the data. This can be a scalar (value), spectrum (1D array) or and image (2D array).

Attributes are allowed to contain more static metadata but not less. Which metadata (static, configuration and runtime) an Attribute can contain is listed in the full specification of Tango Attributes which is part of the Tango Controls RFCs.

Some further useful static metadata are listed below:

- `max_dim_x`: this property is valid only for a spectrum or image `data_format`. It gives the maximum number of element in the X dimension, e.g. the max length of a spectrum or the maximum number of rows of an image. This property is used to reserve memory space to host the data. Nothing prevents having a real length much shorter that this maximum. For example: 0 for a scalar, n for a spectrum of max n elements, n for an image of max n rows.

- `max_dim_y`: this property is valid only for an image `data_format`. It gives the maximum number of element in the Y dimension, e.g. the maximum number of columns of an image. Again, this property is used to reserve memory space to host the data. Nothing prevents having a real length much shorter that this maximum. For example: 0 for a scalar or a spectrum, n for an image of max n columns.

- `display_level`: enables the hiding of the attribute depending on the client mode (expert or not), i.e. `Tango::OPERATOR` or `Tango::EXPERT`.

##### Dynamic attribute properties

These properties carry information regarding the display of a value and they are editable while the device is running. These properties enhance the meaning of the attribute and should as much as possible be defined by the device server programmer as default values when known. For instance, in the general case, the programmer knows the unit of the data and is able to describe it. Knowing the attribute property at the development stage will allow all generic clients to display the data in the best manner.

Some example of dynamic properties are:

- `description`: describes the attribute, e.g. “The power supply output current”

- `label`: label used on the GUIs, e.g. “Output Current”, “Input Current”

- `unit`: attribute unit to be displayed in the client viewer, e.g. “mA”, “mm”

- `standard_unit`: conversion factor to get an attribute value into S.I (M.K.S.A)_unit. Be careful as this information is intended to be used ONLY by the client, e.g ATKPanel uses it, but jive->test device does not. This property is given as a string that is interpreted as a floating point value. For example, if the device attribute gives the current in mA we would have to divide by 1000 to obtain it in Amps (S.I. unit) meaning we would set this property to 1E-03

- `display_unit`: used by the GUIs to display the attribute in a unit more appropriate for the user. Again be careful as this information is intended to be used ONLY by the client, e.g ATKPanel uses it, but JiveTest device does not. This property is given as a string that is interpreted as a floating point value. For example, if the device attribute gives a current in A and we want to display it in mA, then we have to multiply by 1000 to obtain it in mA meaning we could set this property to 1000.0.

- `format`: specifies how a numeric attribute value should be presented, e.g. « %6.3f »

- `min_value` and `max_value`: minimum and maximum allowable value. These properties are automatically checked at each execution of a write attribute. If the value requested is not between the min_value and the max_value, an exception will be returned to the client. This property is given as a string that is interpreted as a floating point value (e.g. 10.1, 1E01, 0.12). Note that these properties are valid only for writable attributes.

##### Attributes properties related to Events configuration

These settings are used for tuning the events related to the attribute.

- `Rel_change`: relative change in the value in percent

- `Abs_change`: absolute change in the value in the standard unit.

- `Period`: period between two consecutive events

- `Archive_rel_change`: relative change in the value

- `Archive_abs_change`: absolute change in the value

- `Archive_period`: period between two consecutive events.

##### Runtime properties

Below is the output of a PyTango client that reads an attribute from a Device - it demonstrates how the runtime metadata of an Attribute can be used.

    In [7]: attr = "my_rw_attribute"  # The Attribute's name.

    In [8]: attr_value = dp.read_attribute(attr)  # Read the attributes value together with the runtime metadata.
    In [9]: print(f"{attr_value}")  # Print what we received.
    DeviceAttribute[
    data_format = tango._tango.AttrDataFormat.SCALAR
          [...]
           name = 'my_rw_attribute'
          [...]
           time = TimeVal(tv_nsec = 0, tv_sec = 1730369596, tv_usec = 629978)
           type = tango._tango.CmdArgType.DevDouble
           value = 5.4321
          [...]
           w_value = 0.0]

Some of the mandatory static metadata is part of the runtime metadata as can be seen above: `data_format`, `name`, `type` (data type) are equivalents of what has been described earlier.

#### Enumerated Attribute

Tango supports enumerated Attributes. They are not implemented on top of the enumeration types of the core Tango languages but they behave like them. In the day-to-day business one will not notice a big difference compared to the language enumerations. There are however a couple of smaller limitations, one being that only Attributes with the scalar data format can be enumerated Attributes. This means that enumerated Attribute arrays are not supported.

See the how-to section for an example on how to use enumerated attributes.

#### Memorized Attribute

Attributes with a scalar data format can be configured to have their last set quantity automatically and permanently be stored in the Tango Database. This is done by defining such an attribute in the source code.

In addition to the storing of the quantity, the stored value will be reloaded into the set value associated with this attribute at device start-up and (optionally) upon each execution of the “Init” command, effectively maintaining the Attribute’s quantity over Device restarts. The Tango code generator (Pogo) provides the interface allowing the developer to select the expected behaviour.

Note

Memorized attributes are only possible with an attribute with WRITE or READ_WRITE mode and SCALAR type

Clients are unable to tell if an Attribute is memorized or not.

See the how-to section for an example on how to use a memorized attribute.

#### Forwarded Attribute

Warning

Forwarded attribute is a feature that is not entirely mature. Its use is not recommended.

Tango supports the forwarding of Attributes of scalar data format. A forwarded attribute will let you access an attribute from another device, this will be called the root attribute as an attribute of this device. A forwarded attribute will retrieve all its metadata and information from the root attribute. All call to read, write, configuration or property settings, except for the label property, are forwarded to the root attribute and will modify its state. A typical use case is when a single hardware connection let you handle several devices. It is then common to implement a Tango class, lets call it Interface to handle the hardware connection. The Interface class will expose the attributes for each devices. We can then use another class, let’s call it device, that will logically represents each devices. The device class could use forwarded attributes to match its attributes to the one defined in the Interface class.

See the how-to section for an example on how to use a forwarded attribute.

### Property

The property concept in Tango Control System is fundamental to its approach in managing and controlling devices. In Tango, each device has properties, which are configurations that define the characteristics and the unique behavior of each device, for example, an axis controller must be configured for the motor mechanics according to the characteristics of the actuator and the movements to achieve.

The properties are used to configure a device without changing the Tango class code. They managed in the Tango database, which stores and provides persistent access to property values. The properties can be retrieved, updated and reconfiguration of devices as needed.

#### Property format

In the Tango Control System, properties are represented as key-value pairs, where the key is the property name, and the value is the property’s setting or configuration. Property values can come in several different data formats depending on the complexity and requirements of the device or class. Here’s a breakdown of common formats:

- String: Properties can be simple strings i.e: “ip” = “192.168.0.21”

- Integer: Integer values are used for properties that define discrete numerical settings i.e: “max_speed” = 1000

- Double: properties that require decimal precision i.e: “threshold” = 0.05

- Boolean: property that is either True or False i.e: “enabled” = True

- Array: Properties can also be arrays of values, typically for configurations requiring multiple entries i.e: “calibration” = [0.0, 23.2, 55.4, 76.2, 100.0]

#### Property Levels

Configuration properties are available on different levels:

- Device properties: These are properties to configure the device itself and its attributes. They are specific to individual devices. The device properties configure the device with the necessary set-up information during initialisation. Examples include network addresses, calibration parameters, or limits unique to a device.

- Class properties: Class properties apply to all instances of a particular class. They help in defining default configurations shared across devices of the same type, offering consistency and reducing the need for repetitive configuration. For example, all motors of a specific model might share a class property defining maximum speed.

  Note that a property defined on the class level will be overwritten by a property of the same name on the device level.

- Attribute properties: Attributes properties define the configuration of the device attributes. These include properties such as data type, display range, alarm limits, and units, ensuring attributes have both meaning and safety constraints. For example, a temperature sensor’s attribute might have properties defining the minimum and maximum allowable values, units in Celsius, and alarm thresholds.

- Free properties: Free properties are defined and used for the entire control system. These are configuration values which are not attached to any device or class and can be freely used by programmers. For example, the event system can be configured to send message via a multicast.

Class level, device level and attribute level properties are automatically loaded during device initialisation when starting-up a device server or calling the “init” command, while the reading and writing of free properties must be handled by the programmer. Device, attribute and class level properties can also be initialised with default values set at the interface creation time, for example, with Pogo. Default values are stored in the device server code and are overwritten when another value is found in the configuration database.

It is necessary to assign a default value for every property. This value will be used when the property is not defined in the Tango database. If a default value for a device property does not make sense, the property should be declared as mandatory. A mandatory property has to have a value configured in the Tango database. If no value is configured, the device initialisation will stop with an exception on the missing property value.

### Events

audience:all lang:all

#### Introduction

Events are a critical part of any distributed control system. Their aim is to provide a communication mechanism which is fast and efficient.

The standard CORBA communication paradigm is a synchronous or asynchronous two-way call. In this paradigm the call is initiated by the client who contacts the server. The server handles the client’s request and sends the answer to the client or throws an exception which the client catches. This paradigm involves two calls to receive a single answer and requires the client to be active in initiating the request. If the client has a permanent interest in a value it is obliged to poll the server for an update in a value every time. This is not efficient in terms of network bandwidth nor in terms of client programming.

For clients who are permanently interested in values, the event-driven communication paradigm is a more efficient and natural way of programming. In this paradigm the client registers its interest once in an event (value). After that the server informs the client every time the event has occurred. This paradigm avoids the client polling, frees it for doing other things, is fast and makes efficient use of the network.

The rest of this chapter explains how the TANGO events are implemented and the application programmer’s interface.

#### Event definition

TANGO events represent an alternative channel for reading TANGO device attributes. Device attributes values are sent to all subscribed clients when an event occurs. Events can be an attribute value change, a change in the data quality or a periodically sent event. The clients continue receiving events as long as they stay subscribed. Most of the time, the device server polling thread detects the event and then pushes the device attribute value to all clients. Nevertheless, in some cases, the delay introduced by the polling thread in the event propagation is detrimental. For such cases, some API calls can be used to directly push the event.

Until TANGO release 8, the notifd event implementation of the CORBA Notification service was used to dispatch events. Starting with TANGO 8, this CORBA Notification service has been replaced by the ZMQ library which implements a Publish/Subscribe communication model well adapted to TANGO events communication.

Alarm events are only available from TANGO release 10 and do not support the notifd event implementation.

#### Event types

The following nine event types have been implemented in TANGO :

1. change - an event is triggered and the attribute value is sent when the attribute value changes significantly. The exact meaning of significant is device attribute dependent. For analog and digital values this is a delta fixed per attribute, for string values this is any non-zero change i.e. if the new attribute value is not equal to the previous attribute value. The delta can either be specified as a relative or absolute change. The delta is the same for all clients unless a filter is specified (see below). Change events also triggered in the following cases :

   1. When a spectrum or image attribute size changes.

   2. At event subscription time

   3. When the polling thread receives an exception during attribute reading

   4. When the polling thread detects that the attribute quality factor has changed.

   5. The first good reading of the attribute after the polling thread has received exception when trying to read the attribute

   6. The first time the polling thread detects that the attribute quality factor has changed from INVALID to something else

   7. When a change event is pushed manually from the device server code. (DeviceImpl::push_change_event()).

   8. By the methods Attribute::set_quality() and Attribute::set_value_date_quality() if a client has subscribed to the change event on the attribute. This has been implemented for cases where the delay introduced by the polling thread in the event propagation is not authorized.

2. periodic - an event is sent at a fixed periodic interval. The frequency of this event is determined by the event_period property of the attribute and the polling frequency. The polling frequency determines the highest frequency at which the attribute is read. The event_period determines the highest frequency at which the periodic event is sent. Note if the event_period is not an integral number of the polling period there will be a beating of the two frequencies [1]. Clients can reduce the frequency at which they receive periodic events by specifying a filter on the periodic event counter.

3. archive - an event is sent if one of the archiving conditions is satisfied. Archiving conditions are defined via properties in the database. These can be a mixture of delta_change and periodic. Archive events can be send from the polling thread or can be manually pushed from the device server code (DeviceImpl::push_archive_event()).

4. alarm - an “alarming” subset of change events to allow clients to monitor when attributes’ quality factors are either Tango::ATTR_WARNING or Tango::ATTR_ALARM, without receiving unneeded events relating to value changes. An event is considered to be alarming if one of the following is true:

   1. The attribute quality factor transitions to or from:

      - Tango::ATTR_WARNING

      - Tango::ATTR_ALARM.

   2. The event contains an exception and the previous event did not contain an exception.

   3. The event contains an exception which is different from the exception in the previous event.

   4. The event does not contain an exception and the previous event did contain an exception.

   Alarm events are triggered in the following circumstances:

   1. At event subscription time as part of the subscription command

   2. When the polling thread detects an alarming event as defined above

   3. When an alarm event is pushed manually from the device server code. (DeviceImpl::push_alarm_event()).

   4. When a change event is pushed manually from the device server code (DeviceImpl::push_change_event()) and the following is true:

      > 1. the AutoAlarmOnChangeEvent CtrlSystem property is defined and not the case-insensitive string “false”.

      > 2. the attribute is not configured to push manual alarm events (DeviceImpl::set_alarm_event() with the implemented flag set to false).

      > 3. the event is considered to be alarming as defined above

   5. By the methods Attribute::set_quality() and Attribute::set_value_date_quality() when the following is true:

      > 1. a change event would be sent from these methods

      > 2. the AutoAlarmOnChangeEvent CtrlSystem property is defined and not the case-insensitive string “false”.

      > 3. the attribute is not configured to push manual alarm events (DeviceImpl::set_alarm_event() with the implemented flag set to false).

      > 4. the quality factor change is considered to be alarming as defined above

5. attribute configuration - an event is sent if the attribute configuration is changed.

6. data ready - This event is sent when coded by the device server programmer who uses a specific method of one of the Tango device server class to fire the event (DeviceImpl::push_data_ready_event()). The rule of this event is to inform a client that it is now possible to read an attribute. This could be useful in case of attribute with many data.

7. user - The criteria and configuration of these user events are managed by the device server programmer who uses a specific method of one of the Tango device server class to fire the event (DeviceImpl::push_event()).

8. device interface change - This event is sent when the device interface changes. Using Tango, it is possible to dynamically add/remove attribute/command to a device. This event is the way to inform client(s) that attribute/command has been added/removed from a device. Note that this type of event is attached to a device and not to one attribute (like all other event types). This event is triggered in the following case :

   1. A dynamic attribute or command is added or removed. The event is sent after a small delay (50 mS) in order to eliminate the risk of events storm in case several attributes/commands are added/removed in a loop

   2. At the end of admin device RestartServer or DevRestart command

   3. After a re-connection due to a device server restart. Because the device interface is not memorized, the event is sent even if it is highly possible that the device interface has not changed. A flag in the data propagated with the event inform listening applications that the device interface change is not guaranteed.

   4. At event re-connection time. This case is similar to the previous one (device interface change not guaranteed)

9. pipe - This is the kind of event which has to be used when the user want to push data through a pipe. This kind of event is only sent by the user code by using a specific method (DeviceImpl::push_pipe_event()). There is no way to ask the Tango kernel to automatically push this kind of event.

The first four are automatically generated by the TANGO library or fired by the user code. Events number 5 and 8 are only automatically sent by the library and events 6, 7 and 9 are fired only by the user code.

#### Configuring events

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

##### change

The attribute properties and their default values for the change event are :

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

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

##### periodic

The attribute properties and their default values for the periodic event are :

1. event_period - the minimum time between events (in milliseconds). If no property is specified then a default value of 1 second is used.

##### archive

The attribute properties and their default values for the archive event are :

1. archive_rel_change - a property of maximum 2 values which specifies the positive and negative relative change w.r.t. the previous attribute value which will trigger the event. If the attribute is a spectrum or an image then an archive event is generated if any one of the attribute value’s satisfies the above criteria. If only one property is specified then it is used for the positive and negative change. If no properties are specified then no events are generated.

2. archive_abs_change - a property of maximum 2 values which specifies the positive and negative absolute change w.r.t the previous attribute value which will trigger the event. If the attribute is a spectrum or an image then an archive event is generated if any one of the attribute value’s satisfies the above criteria. If only one property is specified then it is used for the positive and negative change. If no properties are specified then the relative change is used.

3. archive_period - the minimum time between archive events (in milliseconds). If no property is specified, no periodic archiving events are send.

[1]

note: the polling is de-correlated with the hours/minutes/seconds to avoid polling peaks when there is an hour/minute/second change.

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

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      }

### Tango object naming (device, attribute and property)

audience:all lang:all

The schema used to identify tango objects is called Tango Resource Locator.

#### Device name

A Tango device name is a three fields name. The field separator is the / character. The first field is named domain, the second field is named family and the last field is named member. A tango device name looks like

    domain/family/member

It is a hierarchical notation. The member specifies which element within a family. The family specifies which kind of equipment within a domain. The domain groups devices related to which part of the experiment they belong to. At ESRF, some of the machine control system domain name are SR for the storage ring, TL1 for the transfer line 1 or SY for the synchrotron booster. For experiment, ID11 is the domain name for all devices belonging to the experiment behind insertion device 11. Here are some examples of Tango device name used at the ESRF:

- sr/d-ct/1 : The current transformer. The domain part is `sr` for storage ring. The family part is `d-ct` for diagnostic/current transformer and the member part is `1`.

- fe/v-pen/id11-1 : A Penning gauge. The domain part is `fe` for front-end. The family part is `v-pen` for vacuum/penning and the member name is `id11-1` to specify that this is the first gauge on the front-end part after the insertion device 11.

#### Full object name

The device name as described above is not enough to cover all Tango usage like device servers without database or device access for multi control systems. With the naming schema, we must also be able to name attribute and property. Therefore, the full naming schema is

    [protocol://]\[host:port/\]device_name\[/attribute][->property][#dbase=xx]

The `protocol`, `host`, `port`, `attribute`, `property` and `dbase` fields are optional. The meaning of these fields are:

`protocol`: Specifies which protocol is used (`tango` or `taco`). `tango` is the default.

`#dbase=xx`: The supported value for xx is `yes` and `no`. This field is used to specify that the device is a device served by a device server started with or without database usage. The default value is `dbase=yes`. See the File Database for an howto using `#dbase=no` at the client side.

`host:port`: This field has different meaning according to the dbase value. If `dbase=yes` (the default), the host is the host where the control system database server is running and port is the database server port. It has a higher priority than the value defined by the TANGO_HOST environment variable. If dbase=no, host is the host name where the device server process serving the device is running and port is the device server process port.

`attribute`: The attribute name

`property`: The property name

The `host:port` and `dbase=xx` fields are necessary only when creating the DeviceProxy object used to remotely access the device. The `->` characters are used to specify a property name.

##### Some examples

###### Full device name examples

- gizmo:20000/sr/d-ct/1 : Device sr/d-ct/1 running in a specified control system with the database server running on a host called `gizmo` and using the port number `20000`. The `TANGO_HOST` environment variable will not be used.

- tango://freak:2345/id11/rv/1#dbase=no : Device served by a device server started without database. The server is running on a host called freak and use port number 2345. `freak:2345/id11/rv/1#dbase=no` is also possible for the same device.

- taco://sy/ps-ki/1 : Taco device sy/ps-ki/1

###### Attribute name examples

- id11/mot/1/Position : Attribute position for device `id11/mot/1`

- sr/d-ct/1/Lifetime : Attribute lifetime for Tango device `sr/d-ct/1`

###### Attribute property name

- id11/rv/1/temp->label : Property `label` for attribute temp for device `id11/rv/1`

- sr/d-ct/1/Lifetime->unit : The `unit` property for the Lifetime attribute of the `sr/d-ct/1` device

###### Device property name

- sr/d-ct/1->address : `address` property for device `sr/d-ct/1`

###### Class property name

- Starter->doc_url : `doc_url` property for a class called `Starter`

#### Device and attribute name alias

Within Tango, each device or attribute can have an alias name defined in the database. Every time a device or an attribute name is requested by the API’s, it is possible to use the alias. The alias is simply an open string stored in the database. The rule of the alias is to give device or attribute name a name more natural from the user/scientist’s point of view. Let’s imagine that for an experiment, the sample position is described by angles called `teta` and `psi` in text books. It is more natural for scientists when they move the motor related to sample position to use `teta` and `psi` rather device name like `idxx/mot/1` or `idxx/mot/2`. An attribute alias is a synonym for the four fields used to name an attribute. For instance, the attribute `Current` of a power-supply device called `sr/ps/dipole` could have an alias `DipoleCurrent`. This alias can be used when creating an instance of a AttributeProxy class instead of the full attribute name which is `sr/ps/dipole/Current`. Device alias names are unique within a Tango control system. Attribute alias name are also unique within a Tango control system.

#### Allowed characters and length restrictions

The device name, the command name, the attribute name, the property name, the device alias name and the device server name are all case insensitive.

In general names must only use letters (`a-z`), digits (`0-9`) and underscore (`_`) and they must start with a letter.

The allowed maximum lenghts for each component are:

Item | Max length
--- | ---
device name | 255
domain field | 85
family field | 85
member field | 85
device alias name | 255
attribute name | 255
property name | 255

Aliases follow the naming scheme and maximum lengths of the object they are pointing to.

The RFC project has the gritty nitty details about acceptable characters and maximum lengths:

- Device Class

- Devices

- Attributes

- Commands

- Property

### Tango Data Model

audience:all

This page list and links to the different elements of the Tango Data Model.

#### Simplified Model

The following diagram shows the main elements of the Tango Device Model :

            classDiagram
      class cls["Device Class"]
      class dserver["Device Server"]
      click dserver href "./device.html#device-server-explanation" "Device Server documentation"
      class Pipe
      click Pipe href "./pipe.html#tango-pipe-model" "Pipe documentation"
      class Command
      click Command href "./command.html#tango-command-model" "Command documentation"
      class Attribute
      click Attribute href "./attribute.html" "Attribute documentation"
      class Event
      click Event href "./event.html" "Event documentation"
      class Device {
        domain
        family
        member
      }
      Pipe "*" <--* cls
      Command "*" <--* cls
      Attribute "*" <--* cls
      Event "*" <--* Attribute
      State --|> Attribute
      Status --|> Attribute
      Device *--> "1" cls : belongs
      dserver *--> "1..*" Device
      Device *--> "*" DeviceProperty

#### Complete Model

Here is a more complete version of the above:

            classDiagram
      direction LR
      class cls["Device Class"]
      class dserver["Device Server"]
      click dserver href "./device.html#device-server-explanation" "Device Server documentation"
      class Pipe
      click Pipe href "./pipe.html#tango-pipe-model" "Pipe documentation"
      class Command
      click Command href "./command.html#tango-command-model" "Command documentation"
      class Attribute
      click Attribute href "./attribute.html" "Attribute documentation"
      class Event
      click Event href "./event.html" "Event documentation"
      class Device {
        domain
        family
        member
      }
      Pipe "*" <--* cls
      Command "*" <--* cls
      Attribute "*" <--* cls
      Event "*" <--* Attribute
      DeviceInterfaceChangeEvent --|> Event
      ChangeEvent --|> Event
      ArchiveEvent --|> Event
      AlarmEvent --|> Event
      PeriodicEvent --|> Event
      UserEvent --|> Event
      DataReadyEvent --|> Event
      AttributeConfigurationEvent --|> Event
      State --|> Attribute
      Status --|> Attribute
      Device *--> "1" cls : belongs
      dserver *--> "1..*" Device
      Device *--> "*" DeviceProperty
      AdminDevice --|> Device
      dserver *--> "1" AdminDevice
      cls --|> DeviceImpl
      Property *--> "0..10" PropertyHistory
      AttributeProperty --|> Property
      DeviceAttributeProperty --|> AttributeProperty
      ClassAttributeProperty --|> AttributeProperty
      Device *--> "*" DeviceAttributeProperty
      DeviceProperty --|> Property
      PipeProperty --|> Property
      DevicePipeProperty --|> PipeProperty
      ClassPipeProperty --|> PipeProperty
      FreeProperty --|> Property
      ClassProperty --|> Property
      cls *--> "*" ClassProperty
      Pipe *--> "*" PipeProperty
      Attribute *--> "*" AttributeProperty
      note for Attribute "At least State and Status are always available"
      note for cls "The main class that the developer has to implement"

#### Elements

Block | Description
--- | ---
Device | Abstract concept defined by the TANGO device server object model; it can be a piece of hardware (an interlock bit) a collection of hardware (a screen attached to a stepper motor)a logical device (a taper) or a combination of all these (an accelerator). For more information please see the device documentation.
Device Class | From Object Oriented Programming concept, this is the main class that the developer has to implement
DeviceServer | The server (also referred as device server) is a process whose main task is to offer one or more services to one or more clients. To do this, the server has to spend most of its time in a wait loop waiting for clients to connect to it. The devices are hosted in the server process. A server is able to host several classes of devices.In short, it is a process that export devices available to accept requests). Fore more information see the device server documentation.
Property | Store a generic configuration
DeviceProperty | Device specific configuration
ClassProperty | Class specific configuration
AttributeProperty | Attribute specific configuration
ClassAttributeProperty | Attribute specific configuration at class level
DeviceAttributeProperty | Specific configuration for a specific attribute of a specific device
FreeProperty | User-defined specific configuration (for instance GUI, generic system and so on)
PropertyHistory | History of the values for a property (maximum 10 latest are stored for each property)
Attribute | See attribute.
AttributeAlias | One word which can be used to identify a specific attribute. (shortcut)
Pipe | See [pipe] (./pipe.md).
PipeProperty | Pipe specific configuration
DevicePipeProperty | Configuration of a specific pipe of a specific device
ClassPipeProperty | Configuration of a specific pipe for a specific class
Event | See event.
Command | See command.
ChangeEvent | It is a type of event that gets fired when the associated attribute changes its value according to its configuration specified in system specific attribute properties (abs_change and rel_change). See events.
ArchiveEvent | It is a type of event that gets fired when the associated attribute should be archived according to its configuration specified in system specific attribute properties (archive_abs_change, archive_rel_change and archive_period). See events.
UserEvent | It is a type of event that gets fired when the device server programmer wants to. See events.
PeriodicEvent | It is a type of event that gets fired at a fixed periodic interval. See events.
DataReadyEvent | It is a type of event that gets fired to inform a client that it is now possible to read an attribute. See events.
AttributeConfigurationEvent | It is a type of event that gets fired if the attribute configuration is changed. See events.
DeviceInterfaceChangeEvent | It is a type of event that gets fired when the device interface changes. See events.
DeviceImpl | Base implementation of every class that will become a device.
State | The device state is a number which reflects the availability of the device.
Status | The state of the device as a formatted ascii string
AdminDevice | Special type of Device dedicated to creating and managing the devices, i.e. restart device, kill the device server (the process), creating polling mechanism and so on

#### Attributes

Block | Attribute | Description
--- | --- | ---
Device | name | Correspond to “Domain/family/member”
| alias | A word that you can use to access the device, like a shortcut. Device name alias(es) must also be unique within a control system. There is no predefined syntax for device name alias.
| domain | Each device has a unique name in the control system. Within Tango, a four field name space has been adopted consisting of[//FACILITY/]DOMAIN/CLASS/MEMBERFacility refers to the control system instance, domain refers to the sub-system, class the class and member the instance of the device.
| family |
| member |
| version | It correspond to the version of base device implementation class (for backward compatibility). It is used to know how to communicate with this device and what features are supported.
| class | Name of the class corresponding to the Device
| ior | Orb Identifier used to connect with the device
| host | Where the device is running
| pid | Id of the specific process
| exported | Means that the device is available to accept request
DeviceServer | Facility | Represent the host where the device server instance (aka process) lives
Attribute | alias | A shortcut that you can use to access the attribute (in the Database there is a specific table)

#### Relations

Left Block | Right Block | Multiplicity | Description
--- | --- | --- | ---
Device | Device Class | 1 | Every device belongs to a Tango Device class
Attribute | ClassAttributeProperty | 0..* | An attribute can have more than one class attribute property associated
Attribute | Event | 0..* | An attribute can have more than one event associated
Device | DeviceAttributeProperty | 0..* | A device can have more than one Device Attribute Property associated
Device | DeviceProperty | 0..* | A device can have more than one Device Property associated
Device | Attribute | 2..* | A Device can have more than one Attribute associated via reference
DeviceServer | AdminDevice | 1 | Every device server is exporting one admin device
DeviceServer | Device | 1..* | Every Device server has many devices inside itself
Pipe | PipeProperty | 0..* | A pipe can have more than one Pipe Property associated
Property | PropertyHistory | 0..10 | A Property can have more than one Property history associated (this will maintain the history of the change for the property)
Device Class | Attribute | 2..* | A Device Class can have more than one Attribute associated
Device Class | ClassProperty | 0..* | A Device Class can have more than one Class Property associated
Device Class | Command | 2..* | A Device Class can have more than one Command associated
Device Class | Pipe | 0..* | A Device Class can have more than one Pipe associated

#### Rationale

Please refer to the Tango Device Model .

audience:all

### Tango Database

The Tango Database, also referred to as TangoDB, is a service mainly used for name lookup and static and runtime configuration storage for the control system. The Tango Database itself is found by clients and servers via the environment variable `TANGO_HOST`. It is possible to run multiple databases for load-balancing and fault-tolerance, see Use multiple database servers within a Tango control system, and also to run servers without database, see Run a device server without a database.

The part of the Tango Database that offers an API is implemented as a device server and should always be running. It is abbreviated as `TDB` in the following paragraphs.

#### Name lookup

When a device server starts it registers itself with the TDB. For this to work the device name has to be added to the TDB before with tools like Jive or tango_admin. This is called device exporting/unexporting, see the RFC for the details. The device server unregisters itself automatically on shutdown from the TDB.

The device registration allows to abstract-away the location of the device server. So when the device server moves from `hostA` to `hostB` this is completely invisible to the client.

#### Alias handling

It is possible to introduce alternative names for devices and attributes and this mapping is also stored in the TDB. See Alias names for an in-depth explanation and the howto for some examples on how to use them.

#### Configuration storage

The TDB can also act as a key-value store for storing persistent device server data. The `keys` are called properties in Tango and the `values` are strings. As the properties an be read by anyone, no sensitive data should be stored in them.

The previous values of properties are also kept in the database for informative purposes. The default depth of the history is `10` and can be controlled with the `historyDepth` device property. The history is deleted when a property is deleted.

### Attribute alarms

audience:all

Tango provides an automatic way of defining alarms. An alarm condition will switch the attribute quality factor to alarm and the device state will automatically switched to ALARM in certain conditions. Four properties are available for alarm purpose.

> For example the voltage of a powersupply set via a DAC and read via an ADC convertor. Both values can be different due to various factors such as internal resistor or noise on the ADC. Additionally the powersupply may need a certain time to establish its output voltage when changing the set point. The Tango alarm system is able to handle the acceptable noise threshold and the time the device needs to establish the voltage after the writing of the setpoint (time constant).

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

Figure 7.1: Level 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.

Warning

The behaviour described above is only correct in the case the device’s method Tango::Device_[X]Impl::dev_state() is executed*.* In case of overwrite of the dev_state() in the device code, it is recommended to finish the method by calling DeviceImpl::dev_state();

Warning

min_warning and max_warning : lower and upper bound for WARNING (deprecated since version 8)

#### 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. This is very useful to handle maximum noise in constant time.

This alarm configuration is done with two attribute configuration parameters:

- delta_val and delta_t Valid for a writeable attribute. Define a maximum difference between the set_value and the read_value of an attribute after a standard time. By default, these two parameters are not defined and no check will be done.

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.

### Archiving

audience:all lang:all

The Archiving of Tango Controls allows one to store Attribute values.

The current recommendation for archiving of Attribute values is to use the Historical Data Base++ or HDB++. HDB++ supports high time resolution, multiple database backends (MySQL, Timescale, SQLite), and is based on Tango Attribute events. The improvements over its now deprecated predecessor HDB are higher throughput and a number of improvements behind the curtain, among them better error handling and smaller demand for resources (RAM and CPU).

There is also SNAP. It is not a real archiver in the way that HDB++ works, but it allows to take snapshots of sets of attributes at the same time[1]. One can think of it as a camera that can only take photos of one moment in time but not videos. HDB++ on the other hand could be configured to act almost like a video recorder if one had unlimited resources (RAM, CPU, storage space).

#### HDB++

audience:administrators audience:developers

##### An overview

The figures below have been taken from the primary presentation of HDB++. They show the runtime view of HDB++ within a Tango Controls system.

HDB++ Runtime View (part one)

HDB++ Runtime View (part two)

##### Elements

Block | Description
--- | ---
HDB++ Viewer | Standalone JAVA application designed to monitor signals coming from HDB++. It has been written using Swing and needs a JVM higher than 1.7.0. *More information*.
HDB++ Configuration | Standalone JAVA application that allows interaction with the configuration manager in order to add, modify, move or delete an attribute from the archiving system
Archiving DB | Specific Database devoted to storing attribute values. The currently supported backend are Mysql, PostgreSQL, ElasticSearch or Timescale. In the past we supported Cassandra too, but since we have deprecated its use and it is now unsupported.
Tango Configuration Database (:term:`Tango Database`) | The Tango Device Server (:term:`Databaseds`) together with the MariaDB database backend.
Archiver | The EventSubscriber Tango device server, or Archiver, is the archiving system’s engine. On typical usage, it will subscribe to archive events on request by the ConfigurationManager device. The EventSubscriber is designed to start archiving all the already configured Attributes, even if the ConfigurationManager is not running. Moreover, being a TANGO device, the EventSubscriber configuration can be managed with Jive.The list of Attributes to be gathered by each EventSubscriber is stored in the AttributeList Property of the EventSubscriber device.
Device server | Generic Device server that contains one or more devices that needs to archive one or more attributes
Configurator Server | Device server that assists in adding, modifying, moving, deleting multiple attributes in the archiving system using the Configuration Manager.
Configuration Manager | Device server that assists in adding, modifying, moving, deleting an Attribute to/from the archiving system

HDB++ inherits the database structure from the existing Tango Historical Data Base and introduces new storage architecture possibilities, better internal diagnostic capabilities and an optimized API. Different backends to store the data can be implemented through an unified interface, currently Timescaledb, MySQL, Postgresql, ElasticSearch and Apache Cassandra (support for Cassandra has been dropped) are supported.

The HDB++ archiving system must fully comply to the Tango device server model, with two immediate benefits. First, all the required configuration parameters are stored to and retrieved from the Tango database; some of these parameters are, for user convenience, duplicated into a dedicated table of the HDB++ schema by a mechanism that guarantees the consistency of the copy. Second, the HDB++ archiving system inherits the Tango scaling capability: any number of EventSubscriber instances can be deployed according to the desired architecture and overall performance.

The HDB++ architecture is fully event based; therefore, a part of HDB++ setup consists of conveniently configure Tango device servers to send events as required.

###### Backend support

A C++ interface libhdb++ offers an interface for any backend to implement for data storage. These libraries, written in C++, are addressed to the EventSubscriber Tango device server and their main purpose is to provide an abstraction layer. Actually, some shared objects are available implementing the abstraction layer and the specific interface:

HDB++ Device Servers design

- libhdb++: database abstraction layer, decouples the interface to the database back-end from the implementation.

- libhdbmysql: legacy HDB schema support for MySQL back-end

- libhdb++mysql: HDB++ schema support for MySQL back-end

- libhdb++cassandra: Cassandra back-end implementation of libhdb++

- libhdb++timescale: Timescaledb back-end implementation of libhdb++

- libhdb++postgres: Postgresql back-end implementation of libhdb++

- hdb++ELK: ElasticSearch back-end implementation of libhdb++

These libraries allow reusing the EventSubscriber, the ConfigurationManager and the GUIs without changes.

###### Configuration Manager Device Server

It configures the attributes to be archived and defines which Event Subscriber is responsible for a set of Tango attributes to be archived. It provides diagnostics data as well. Configuration Manager Tango device server will assist in the operations of adding, editing, moving and deleting an Attribute to/from the HDB++ archiving system. A specific library, exposing a suitable API, addresses the historical data extraction from the archive. The ConfigurationManager device server is able to perform the following operations on the managed EventSubscriber pool:

- handle the request of archiving a new Attribute;

- setup the Attribute archive event configuration;

- assign the new Attribute to one of the archivers;

- move an Attribute from one archiver to another;

- show the Attribute/archiver coupling;

- start/stop the archiving of an Attribute;

- remove an attribute from archiving.

The Configuration Manager also exposes some Attributes to keep trace of the global statistics:

- total number of EventSubscribers;

- total number of working/faulty attributes;

- total number of events per second;

- overall minimum and maximum processing and storing time.

These attributes could be themselves archived to enable a follow up versus time.

###### Event Subscriber Device Server (Archiver)

Also referenced as archiver, is in charge of gathering the values from the Tango devices and storing them into the historical database. To address the requirements coming from large systems the need to distribute the workload over a number of archivers shows up. The EventSubscriber Tango device server is the core of the HDB++ archiving system. It subscribes to archive events for the specified Attributes list, stored into a Property in the Tango database, as well as a number of additional parameters, such as the hostname and port number where the back-end is running, the name of the database and the username and password to be used. It subscribes to Tango archive events, which are ZeroMQ events in the latest Tango releases, and stores the received events in the historical database. It provides diagnostics data as well.

The EventSubscriber device server allows to perform the following operations:

- add/remove an Attribute to/from archiving;

- start/stop the archiving for all Attributes;

- start/stop the archiving for one Attribute;

- read the status of an Attribute;

- read the list of Attributes currently archived (started);

- read the list of Attributes currently not archived (stopped);

- read the number/list of Attributes in charge;

- read the configuration parameters of each Attribute;

- read the number/list of working Attributes;

- read the number/list of faulty Attributes;

- read the number/list of Attributes pending in the FIFO.

Working at the EventSubscriber level implies that the database entry and the archive event parameters have to be already configured. Besides, no action is performed on the archived data when removing an Attribute, which means that the data remain available in the historical database. The EventSubscriber Tango device server also exposes some additional figures of merit, such as:

- for each instance, total number of records per time;

- for each instance, total number of failures per time;

- for each Attribute, number of records per time;

- for each Attribute, number of failures per time;

- for each Attribute, time stamp of last record.

These numbers can sum up in a counter, which can be reset every hours/days/weeks, to rank each Attribute in term of data rate, error rate etc. This allows preventive maintenance and fine tuning, detecting, for instance, when an Attribute configuration is wrong because the variation threshold is lower than the noise level. These statistics are a key element for qualifying the health of the system. All these Attributes are archived themselves to enable a follow-up versus time. For each Attribute, the EventSubscriber Tango device server also computes the minimum and maximum processing and storing times, which helps to discover possible bottlenecks. There may be several EventSubscriber device servers.

###### HdbConfigurator Server

The HdbConfigurator device server is a Java device server. It was developed just to simplify the task of configurating dynamically several new attributes to be added in HDB++.

At the ESRF, it is used to configure/add automatically some dynamic attributes into HDB++. There are some cases where the same device will create some dynamic attributes at startup depending on its configuration (device properties), if the configuration changes, different attributes might be created. In this specific case, we have the requirement to ensure these dynamic attributes are archived. So the device server will configure them via the HdbConfigurator device server.

HDB++ and HdbConfiguratorServer

To be more concrete, for the people working in a synchrotron, we have a device, controlling the insertion devices used by a given beamline. Dynamic attributes are created for each undulator which can be controlled by this beamline. During a shutdown period, the undulator might have been replaced, so there will be new attributes created for the new undulators and the attributes related to the undulators which have been removed will no longer exist.

The HdbConfigurator server will handle a queue and coordinate the requests sent to the HdbConfiguration Manager device. It will help to configure several attributes in parallel. If one would like to do it with only the HdbConfiguration Manager device, one would need to lock the device, set many attributes, send the addAttribute command and finally release the lock of the device. The HdbConfigurator Server is doing that for you in the same way as the HDB++ Configuration Manager GUI is doing it.

###### HdbViewer

It visualizes the data stored in the historical database.

Two libraries have been developed to the historical data extraction:

- A java implementation, libhdbpp-extraction-java, has been used for the HdbViewer GUI and is a native choice for Java device servers. The HdbViewer Java framework, supports both legacy HDB archiving system, and the new HDB++ design.

- A C++ implementation, dedicated Qt/Qtango based GUIs or to C++ Tango device servers. The HdbExtractor++ multithread library allows fetching the data from the legacy HDB and the new HDB++ MySQL schema in a simple Object Oriented way. An additional module provides a Qt interface to the HdbExtractor++ and a dedicated GUI, exploiting the MathGL framework, aimed at displaying mono and bidimensional data over time.

Note

The C++ extraction library currently supports only the MySQL back-end.

##### Source code

The source code is available on Gitlab in the following repositories:

- hdbpp-timescale-project: A project to centralize and build all the needed components of a full HDB++ setup with the Timescaledb backend.

- hdbpp-mysql-project: A project to centralize and build all the needed components of a full HDB++ setup with the Mysql backend.

- hdbpp-cm : the HDB++ Configuration Manager device server

- hdbpp-es: the HDB++ Event Subscriber device server

- hdbpp-cm-es: Device server able to export HDB++ Event Subscriber and Configuration Manager devices in the same device server. It is a multiclass Tango device server capable of both CongigurationManager and EventSubscriber devices. Can be used in place of hdbpp-cm and hdbpp-es devices.

- libhdbpp: the HDB++ insertion (abstract) library

- libhdbpp-mysql: the HDB++ insertion library for MySQL backend

- libhdbpp-mysql-legacy: the HDB++ insertion library for MySQL backend using the old Tango HDB database schema

- libhdbpp-cassandra: the HDB++ insertion library for Cassandra backend

- libhdbpp-timescale: the HDB++ insertion library for Timescaledb backend

- libhdbpp-postgresql: the HDB++ insertion library for Postgrsql backend

- libhdbpp-elk: the HDB++ insertion library for ElasticSearch backend

- hdbpp-benchmark: A project to compare performances of different HDB++ backends using docker images

- CassandraMonitor: A Java client/server to monitor cassandra nodes using jmx calls.

- hdbpp-configurator: the HDB++ Configuration GUI (in Java).

- hdbpp-configurator-server: HDB++ Configuration device server helper (in Java).

- hdbpp-viewer: the HDB++ Viewer GUI (in Java)

- libhdbpp-extraction-java: HDB++ Java extraction library

- libhdbpp-extraction-cpp: HDB++ C++ extraction library

- libhdbpp-python: HDB++ python extraction library

- eGiga2m: Web graphic data viewer able to show HDB++ data

Note

Please, find README file in each repository. It contains a lot of necessary information.

##### Documentation for building and installing

The documentation for building and installing many of the components is available on the different git repositories from tango-controls/hdbpp Gitlab organization.

Please, have a look at the README files from the different git repositories (For instance: tango-controls/hdbpp/hdbpp-timescale-project). They explain how to install the HDB++ libraries as well as the device servers.

Note

-v5 can be used to see the DEBUG messages coming from the Tango library itself too. It also sets the logging level of the device to DEBUG. -v4 can be used to see the DEBUG messages coming from the Tango devices themselves. By doing this, DEBUG level logs will be printed on your terminal. For example, command on terminal: ./hdb++es-srv 01 -v5

Note

In recent versions of libhdb++cassandra library, there are some configuration parameters to adjust libhdb++cassandra library log level and the cassandra driver log level. These configuration parameters are parameters of LibConfiguration Tango class or device property. These parameters are named differently depending on the version of the library used (logging_enabled or logging_level and cassandra_driver_log_level)

##### Rationale

The HBD++ archiving system is built on top of the Tango Event model which provides a specific event for archiving, this is the archive event. The archive events are configured with three attributes properties:

- archive_abs_change:

    a Property of up to 2 values, positive and negative delta, that specifies the absolute change with respect to the previous Attribute value, which triggered the event. If only one value is specified it is used for both positive and negative change. If no thresholds are specified then the relative change is used.

- archive_rel_change:

    a Property of up to 2 values, positive and negative delta, that specifies the relative change with respect to the previous Attribute value, which triggered the event. If only one value is specified it is used for both positive and negative change. If no thresholds are specified archive events are not sent on value change.

- archive_period:

    the time between which periodic archive events are sent, in milliseconds. If no period is specified no periodic archive events are sent.

Usually it is composed of several TANGO device servers (Archiver aka EventSubscriber), but there must be at least one device server. Each EventSubscriber device is in charge of archiving a number of attributes from a number of devices. The number of EventSubscriber TANGO devices to deploy and the number of TANGO devices/Attributes in charge of each subscriber is not bounded and depends on the desired performance.

The ConfigurationManager device server manages a pool of EventSubscribers; the list is stored in the ArchiverList property of each ConfigurationManager device, and is updated via the ArchiverAdd, ArchiverRemove and AttributeSetArchiver commands. The list is stored in the ArchiverList device Property of the ConfigurationManager device using the FQDN syntax. This tells the ConfigurationManager everything which is needed to connect to the managed EventSubscribers: protocol, host, port and device name.

##### Presentations and papers

- `HDB++ Meeting minute 2016-05-10`

- `HDB++: a new archiving system for TANGO (Proceedings of ICALEPCS2015)`

- `How Cassandra improves performances and availability of HDB++ Tango Archiving System (Proceedings of ICALEPCS2015)`

- `HDB++: High availability with Cassandra - presentation from the 29th TANGO collaboration meeting`

- `HDB++ Cassandra presentation at the ICALEPCS 2015 Tango workshop`

#### SNAP

audience:administrators audience:developers

The SNAP is a set of device servers and an GUI application (Bensikin) providing so called SNAPshot functionality.

A snapshot is, as said in the name, a “picture” of a list of equipment’s “settings” (more precisely of their Tango attributes values) taken at a precise instant.

Snapshot are stored in a database (MySQL or Oracle). These can be retrieved and restored to the equipment (devices). This kind of functionality is often called a recipe management. It allows to create set of configuration settings (recipes) used for particular purposes (like selected mode of accelerator operation).

Bensikin manual

[1]

Same time is not really possible because a distributed control system will always introduce delays somewhere. What we refer to here is a best effort: The computer that takes the snapshot will read the values of all attribute as fast as possible. It is possible that a device does not reply immediately when one of its attributes is read. This will result in a delay that one will notice when looking at the timestamps of the attributes in the snapshot.

### Pipe

audience:all lang:all

Warning

The Pipe feature will get deprecated when the DevDict feature will be implemented.

PyTango has deprecated the Pipe feature in PyTango 10.0.2 and will remove it in PyTango 10.1.0.

#### Introduction

A Tango Pipe is like an Attribute with a flexible data structure, name and description. Unlike Commands or Attributes, a Pipe does not have a pre-defined data type. Tango Pipe data types may be a mixture of the basic Tango data types (or array of) and may change every time a pipe is read or written.

#### Use Case

In some cases, it is required to exchange data between client and device of varying data type. This is for instance the case of data gathered during a scan on one experiment. Because the number of actuators and sensors involved in the scan may change from one scan to another, it is not possible to use a well-defined data type. TANGO Pipes have been designed for such cases. A TANGO Pipe is basically a pipe dedicated to transfer data between client and device.

#### Pipe metadata

Tango Pipes are self-describing entities. They are defined by three sets of metadata which are always part of the Pipes:

- Static metadata that defines the Pipe like:

  - name: the name of the pipe

  - display-level: an enumeration describing the visibility level of the Pipe (EXPERT or OPERATOR)

  - writable: an enumeration describing whether the Pipe is Read only or Read Write

- Dynamic metadata the Pipe’s:

  - description: a string describing the pipe

  - label: a label which can be used as an alternative name by the clients.

- Runtime metadata describing the Pipe’s value and its timestamp.

#### Pipe data structure

Pipe data structure is of type `DevPipeBlob`, as specified in 9/Data types. See image below.

A DevicePipeBlob is composed of :

- a blob name (a string)

- a set of DataElement objects

DataElement objects are composed of:

- a name: a string containing the name

- The values in the DataElement objects can be of any basic Tango data types (or array of) or can be themselves a DevicePipeBlob.

The number of DataElement and their value types can change at every Pipe read or write operation.

#### More about Pipes

Pipes metadata (static, dynamic and runtime) are listed in the full specification of Tango Pipes which is part of the Tango Controls RFCs.

### Threading

audience:developers lang:all

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

#### Device server process

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 scavenger thread

- The signal thread

- The heartbeat thread (needed by the Tango event system)

- Two ZeroMQ 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 servers 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 Serialization model within a device server on which serialization models are available. In order to limit the 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, omniORB automatically switches to a thread pool model in which all connections are listened to by a single thread that dispatches incoming calls to a thread pool of up to 100 threads. The active per-connection threads are kept until they exit. When the number of connections decreases down to 50, then omniORB switches back to per-connection model for new incoming connections. More information can be read on omniORB documentation § 6.4.

If you are using events, the event system for its internal heartbeat system periodically (every 200 seconds) sends a command to the admin device. As explained above, a thread is created to execute these command. The omniORB scavenger 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.

##### 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 is 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` as there is one mutual exclusion object by device.

2. Serialization by class: With non multi-threaded legacy software, the preceding scenario could generate problems. 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 as 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 and only with device which are fully thread safe. In this model, most of the device access is 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      try
     6      {
     7          auto *tg = Tango::Util::init(argc,argv);
     8
     9          tg->set_serial_model(Tango::BY_CLASS);
    10
    11          tg->server_init();
    12
    13          sd::cout << "Ready to accept request" << std::endl;
    14          tg->server_run();
    15      }
    16      catch (std::bad_alloc&)
    17      {
    18           std::cout << "Can't allocate memory!!!" << std::endl;
    19           std::cout << "Exiting" << std::endl;
    20      }
    21      catch (CORBA::Exception &e)
    22      {
    23           Tango::Except::print_exception(e);
    24
    25           std::cout << "Received a CORBA::Exception" << std::endl;
    26           std::cout << "Exiting" << std::endl;
    27      }
    28
    29      return 0;
    30  }

The serialization model is set at line 11 before the server is initialized and the infinite loop is started. See the cppTango API documentation for all details on the methods `set_serial_model` and `get_serial_model`.

##### 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 attributes 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 attribute’s data buffers 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 an `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.

     1 void MyClass::init_device()
     2 {
     3    ...
     4    Tango::Attribute &att = dev_attr->get_attr_by_name("TheAttribute");
     5    att.set_attr_serial_model(Tango::ATTR_BY_USER);
     6    ....
     7
     8 }
     9
    10 void MyClass::read_TheAttribute(Tango::Attribute &attr)
    11 {
    12    ....
    13    the_mutex.lock();
    14    ....
    15    // Fill the attribute buffer
    16    ....
    17    attr.set_value(buffer,....);
    18    attr->set_user_attr_mutex(&the_mutex);
    19 }

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 the cppTango API documentation for all details on the methods `set_attr_serial_model` and `set_user_attr_mutex`.

#### Client process

Clients are also multi-threaded processes. The underlying C++ ORB (omniORB) tries to keep system resources to a minimum. To decrease process file descriptors usage, each connection to a 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 connection closing (the ORB scavenger thread).

Threrefore, a Tango client has two threads which are:

1. The main thread

2. The ORB scavenger thread

If the client is using the event system and 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 ZeroMQ implementation threads

- Two Tango event system related threads (`KeepAliveThread` and `EventConsumer`)

### Communication paradigms

Tango offers three communication paradigms:

- synchronous

- asynchronous

- publish-subscribe calls

In the synchronous and asynchronous paradigms the call is initiated by the client who contacts the server. The server handles the client’s request and sends the answer to the client or throws an exception, which the client catches. This paradigm involves two network calls to receive a single answer and requires the client to be active in initiating the request.

The calls initiated by the client may be done via 2 mechanisms:

1. The synchronous mechanism where the client waits (and is blocked) for the server to send the answer or until the timeout is reached

2. The asynchronous mechanism where the client sends the request and immediately returns. In this method it is not blocked and is free to do perform other tasks such as updating a graphical user interface. The client has the choice to retrieve the server answer by checking if the reply has arrived. This is done via a specific API call or by requesting that a callback method is executed when the client receives the server’s answer.

If the client needs to know a value every time it changes or at regular intervals then it must poll the server for an update every time. This is not very efficient in terms of network bandwidth nor in terms of client programming. For this the publish-subscribe events communication is more efficient:

3. The publish-subscribe communication paradigm is a more efficient and natural way of programming. In this paradigm the client registers its interest in an event once. An event can be a change in value, a regular update at a fixed frequency or an archive event. After that the server informs the client every time an event has occurred. This paradigm avoids the client needing to poll and so leaves it free to do doing other things. It is also fast and makes efficient use of the network.

### The Tango device server model

audience:developers lang:all

This chapter describes the Tango device server object model, hereafter referred to as TDSOM.

The TDSOM can be divided into the following basic elements:

- device

- server

- database

- API

This chapter will treat each of the above elements separately.

Some knowledge of COBRA might be useful when reading this chapter - please see An introduction to CORBA.

#### The model

The basic idea of the TDSOM is to treat each device as an `object`. Each device is a separate entity which has its own data and behavior. Each device has a unique name which identifies it in network name space. Devices are organized according to `classes`, each device belonging to a class. All classes are derived from one root class thus allowing some common behavior for all devices. Five kind of requests can be sent to a device:

- Execute actions via Tango `commands`

- Read/Set data specific to each device belonging to a class via Tango `attributes`

- Read/Set data specific to each device belonging to a class via Tango `pipes`

- Read some basic device data available for all devices via CORBA attributes

- Execute a predefined set of actions available for every device via CORBA operations

Each device is stored in a process called a device server. Devices are configured at runtime via properties which are stored in the database.

#### The device

The device is at the heart of the TDSOM. A device is an abstract concept defined by the TDSOM. In reality, it can be a piece of hardware (an interlock bit), a collection of hardware (a screen attached to a stepper motor), a logical device (a taper) or a combination of all these (an accelerator). Each device has a unique name in the control system and eventually one alias. Within Tango, a four field name space has been adopted consisting of

    [//TANGO_HOST:PORT/]DOMAIN/FAMILY/MEMBER

`TANGO_HOST:PORT` refers to the tango database, domain refers to the sub-system, family the group and member the instance of the device. Device name alias(es) must also be unique within a control system. There is no predefined syntax for device name alias.

Each device belongs to a class. The device class contains a complete description and implementation of the behavior of all members of that class. New device classes can be constructed out of existing device classes. In this way a new hierarchy of classes can be built up in a short time. Device classes can reuse existing devices as sub-classes.

All device classes are derived from the exact same base class (the device root class) and implements the identical CORBA interface. All devices implementing the identical CORBA interface ensures that all control objects support the same set of CORBA operations and attributes. The device root class contains part of the common device code. By inheriting from this class, all devices share a common behavior. This also makes maintenance and improvements to the TDSOM easy to carry out.

All devices also bring a `black box` where client requests for attributes and operations are recorded. This feature helps in debugging sessions of devices already installed in a running control system. This is a lightweight alternative to telemetry.

##### Tango Commands

Commands are executed using two CORBA operations named `command_inout` for synchronous commands and `command_inout_async` for asynchronous commands. These two operations call a special method implemented in the device root class, the `command_handler` method. The `command_handler` calls an `is_allowed` method implemented in the device class before calling the command itself. The `is_allowed` method is specific to each command [1]. It checks to see whether the command to be executed is compatible with the present device state. The command function is executed only if the `is_allowed` method allows it. Otherwise, an exception is sent to the client.

##### Tango attributes

In addition to commands, Tango devices also support normalized data types called attributes [2]. Commands are device specific and the data they transport are not normalized i.e. they can be any one of the Tango data types with no restriction on what each byte means. This means that it is difficult to interpret the output of a command in terms of what kind of value(s) it represents. Generic display programs need to know what the data returned represents, in what units it is, plus additional information like minimum, maximum, quality etc. Tango attributes solve this problem.

Tango attributes are zero, one or two dimensional data which have a fixed set of properties e.g. quality, minimum and maximum, alarm low and high. They are transferred in a specialized Tango type and can be READ, WRITE or READ-WRITE. A device can support a list of attributes. Clients can read one or more attributes from one or more devices. To read Tango attributes, the client uses the `read_attributes` operation. To write Tango attributes, a client uses the `write_attributes` operation. To write then read Tango attributes within the same network request, the client uses the `write_read_attributes` operation. To query a device for all the attributes it supports, a client uses the `get_attribute_config` operation. A client is also able to modify some of the parameters defining an attribute with the `set_attribute_config` operation. These five operations are defined in the device CORBA interface.

Tango support thirteen data types for attributes (and arrays for one or two dimensional data) which are: boolean, short, long (32 bits), long (64 bits), float, double, unsigned char, unsigned short, unsigned long (32 bits), unsigned long (64 bits), string, a specific data type for Tango device state and finally another specific data type to transfer data as an array of unsigned char with a string describing the coding of the data.

##### The Tango pipes

Warning

Pipes are slated for removal.

Tango devices also support pipes.

In some cases, it is required to exchange data between client and device of varying data type. This is for instance the case of data gathered during a scan on one experiment. Because the number of actuators and sensors involved in the scan may change from one scan to another, it is not possible to use a well defined data type. Tango pipes have been designed for such cases. A Tango pipe is basically a pipe dedicated to transfer data between client and device. A pipe has a set of two properties which are the pipe label and its description. A pipe can be read or read-write. A device can support a list of pipes. Clients can read one or more pipes from one or more devices. To read a Tango pipe, the client uses the `read_pipe` operation. To write a Tango pipe, a client uses the `write_pipe` operation. To write then read a Tango pipe within the same network request, the client uses the `write_read_pipe` operation. To query a device for all the pipes it supports, a client uses the `get_pipe_config` operation. A client is also able to modify some of parameters defining a pipe with the `set_pipe_config` operation. These five operations are defined in the device CORBA interface.

In contrast to commands or attributes, a Tango pipe does not have a pre-defined data type. Data transferred through pipes may be of any basic Tango data type (or array of) and this may change every time a pipe is read or written.

##### Commands, attributes or pipes?

There are no strict rules concerning what should be returned as command result and what should be implemented as an attribute or as a pipe. Nevertheless, attributes are more adapted to return physical values which have a kind of time consistency. Attribute also have more properties which help the client to precisely know what it represents. For instance, the state and the status of a power supply are not physical values and are returned as command results. The current generated by the power supply is a physical value and is implemented as an attribute. The attribute properties allow a client to know its unit, its label and some other information which are related to a physical value. Commands are well adapted to send orders to a device like switching from one mode of operation to another mode of operation. For a power supply, the switch from a STANDBY mode to a ON mode is typically done via a command. Finally, pipe is well adapted when the kind and number of data exchanged between the client and the device changes with time.

##### The CORBA attributes

Some key data implemented for each device can be read without the need to call a command or read an attribute:

- The device state: a number representing its state. A set of predefined states are defined in the TDSOM.

- The device status: a string describing in plain text the device state and any additional useful information of the device as a formatted ASCII string.

- The device name: name as defined here

- The administration device name called `adm_name`: for each set of devices grouped within the same server, an administration device is automatically added. This `adm_name` is the name of the administration device.

- The device description: an ASCII string describing the device

These five CORBA attributes are implemented in the device root class and therefore do not need to be implemented by the device server developer. As explained in the CORBA paragraph, the CORBA attributes are not allowed to raise exceptions whereas command (which are implemented using CORBA operations) can.

##### The remaining CORBA operations

The TDSOM also supports a list of actions defined as CORBA operations in the device interface and implemented in the device root class. Therefore, these actions are implemented automatically for every Tango device:

Operation | Explanation
--- | ---
ping | Ping a device to check if the device is alive and reachable over the network Obviously, it checks only the connection from a client to the device and not all the device functionalities
command_list_query | Request a list of all the commands supported by a device with their input, output types and description
command_query | Request information about a specific command which are its input, output type and description
info | Request general information on the device like its name, the host where the device server hosting the device is running etc.
black_box | Read the device black-box as an array of strings

##### The special case of the device state and status

The device state and status are the most important pieces of device information. Nearly all client software dealing with Tango devices need device state and/or status. In order to simplify client software developer work, it is possible to get these two piece of information in three different ways:

1. Using the appropriate CORBA attribute (`state` and `status`)

2. Using a command on the device. The commands are called `State` and `Status`

3. Using attributes: Even if the state and status are not real attributes, it is possible to get their value using the `read_attributes` operation. Nevertheless, it is not possible to set the attribute configuration for state and status. An error is reported by the server if a client tries to do so.

##### Device polling

Within the Tango framework, it is also possible to force executing commands or reading attributes at a fixed frequency. This is called `device polling`. This is automatically handled by Tango core software with a pool of polling threads. The command results or attribute values are stored in circular buffers. When a client wants to read an attribute value, or command result, for a polled attribute/command they have the choice to get the attribute value (or command result) with direct access to the device or from the last value stored in the device ring buffer. The latter is optimal for “slow” devices as getting data from the buffer is much faster than accessing the device itself. The disadvantage is the time taken between the data returned from the polling buffer and the time of the request. Polling a command is only possible for commands without input arguments and these commands should also be idempotent. It is not possible to poll a device pipe.

Two other CORBA operations called `command_inout_history_X` and `read_attribute_history_X` allow a client to retrieve the history of polled commands/attributes stored in the polling buffers.

See the device polling explanation for details.

#### The server

Another integral part of the TDSOM is the server concept. The server (also referred to as the device server) is a process where the main task is to offer one or more services to one or more clients. To do this, the server has to spend most of its time in a loop waiting for clients to connect to it. The devices are hosted in the server process. A server is able to host several classes of devices. In the TDSOM, a device of the `DServer` class is automatically hosted by each device server. This class of device supports commands which enable remote device server process administration.

#### The Tango Logging Service

During software life, it is always convenient to print miscellaneous information which help to:

- Debug the software

- Report on error

- Give regular information to the user

This is classically done using the `print` function in your chosen programming language. In a highly distributed control system, it is difficult to get all this information coming from a high number of different processes running on a large number of computers. Tango has incorporated a Logging Service called the Tango Logging Service (TLS) which allows print messages to be:

- Displayed on a console (the classical way)

- Sent to a file

- Sent to specific Tango device called `LogConsumer`. The Tango package has an implementation of a log consumer where every consumer device is associated to a graphical interface.

The log consumer’s graphical interface displays messages but could also be used to sort messages, to filter messages etc. With this feature it is possible to centralize these messages coming from different devices embedded within different processes.

These log consumers can be:

- Statically configured meaning that it memorizes the list of Tango devices for which it will get and display messages

- Dynamically configured. The user chooses devices from which they want to see messages

#### The database

To achieve complete device independence, it is however necessary to supplement device classes with the possibility for configuring device dependencies at runtime. The utility which does this in the TDSOM is the `property database`. Properties [3] are identified by an ASCII string and the device name. Tango attributes are also configured using properties. This database is also used to store device network addresses (CORBA IORs), list of classes hosted by a device server process and list of devices for each class in a device server process. The database ensures the uniqueness of device name and of aliases. It also links device name and its list of aliases.

Tango uses MariaDB as its SQL-database. The database is accessed via a classical Tango device hosted in a device server. Therefore, client access the database via Tango commands requested on the database device.

#### The controlled access

Warning

This prevents accidental changes only and is not secure against malicious actors.

Tango also provides a controlled access system called Tango Access Control. It’s a simple controlled access system and does not provide encrypted communication or sophisticated authentication. It simply defines which user (based on computer login authentication) is allowed to do which command (or write attribute) on which device and from which host. The information used to configure this controlled access feature is stored in the Tango database and accessed by a specific Tango device server which is not the classical Tango database device server described in the previous section. Two access levels are defined:

- Everything is allowed for this user from this host

- The write-like calls on the device are forbidden and according to configuration, a command subset is also forbidden for this user from this host

#### The Application Programming Interfaces

##### Rules of the API

While it is true that Tango clients can be programmed using only the CORBA API, CORBA knows nothing about Tango. This means clients have to know all the details of retrieving IORs from the Tango database, the additional information to send on the wire, the Tango version control etc. These details can and should be wrapped in the Tango Application Programming Interface (API). The API is implemented as a library in C++ and as a package in Java. PyTango has been implemented on top of the C++ API. The API is what makes Tango clients easy to write.

The APIs consist of the following basic classes:

- `DeviceProxy` which is a proxy to the real device

- `AttributeProxy` which is a proxy to an Attribute of a real device

- `DeviceData` to encapsulate data to send/receive from/to device via commands

- `DeviceAttribute` to encapsulate data to send/receive from/to device via attributes

- `Group` which is a proxy to a group of devices

Note

In Tango the term client usually refers to either an AttributeProxy or to a DeviceProxy.

In addition to these main classes, many other classes allow interfacing to all Tango features. The following figure is a drawing of a typical client/server application using Tango.

The database is used during the server and client startup phase to establish a connection between client and server.

##### Communication between client and server using the API

With the API, it is possible to request commands to be executed on a device or to read/write device attribute(s) using one of the two communication models implemented. These two models are:

1. The synchronous model where the client waits (and is blocked) for the server to send the answer or until a timeout is reached.

2. The asynchronous model. In this model, the clients sends the request and immediately returns. It is not blocked. It is free to do whatever it has to do like updating a graphical user interface. The client has the choice to retrieve the server answer by checking if the reply has arrived by calling an API specific call or by requesting that a callback method is executed when the client receives the server response.

##### Tango events

On top of the two communication models previously described, Tango offers an event system. The standard Tango communication paradigm is a synchronous/asynchronous two-way call. In this paradigm the call is initiated by the client who contacts the server. The server handles the client’s request and sends the answer to the client or throws an exception which the client catches. This paradigm involves two calls to receive a single answer and requires the client to be active in initiating the request. If the client has a permanent interest in a value it is obliged to poll the server for an update in a value every time. This is not efficient in terms of network bandwidth usage nor in terms of client programming.

For clients which are permanently interested in values, the event-driven communication paradigm is a more efficient and natural way of programming. In this paradigm the client registers its interest once in an event (value). After that the server informs the client every time the event has occurred. This paradigm avoids the client polling and frees it for doing other things, which is faster and makes efficient use of the network.

ZMQ is a library allowing users to create communicating systems. It implements several well known communication pattern including the Publish/Subscribe pattern, which is the basis of the new Tango event system. Using this library, a separate notification service is not needed and event communication is available with only client and server processes simplifying the overall design.

The following figure is a schematic of the Tango event system:

[1]

In contrary to the `state_handler` method of the TACO device server model which is not specific to each command.

[2]

Tango attributes were known as signals in the TACO device server model

[3]

Properties were known as resources in the TACO device server model

### Long Term Support

audience:administrators audience:developers

In 2016, the Tango-Controls Steering Committee requested the introduction of Long Term Support versions for some key components of Tango-Controls like cppTango, the C++ Tango Library and JTango.

Long Term Support (LTS) versions are special versions of Tango components which will be supported for 5 years (starting from the day when the next direct major version is released).

LTS versions will benefit from critical bug fixes and potentially some patches for simple new features and less critical bugs.

For cppTango, the latest 9.3 version became an LTS version when cppTango 9.4.0 was released on September 30th 2022. This means that cppTango 9.3.x will be supported until October 2nd 2027.

For the LTS versions only cppTango 9.3.x and starter will stay at C++98, all other projects can require newer C++ standards.

For PyTango there is no LTS policy. PyTango releases target the most recent minor release of cppTango. Critical bug fixes to PyTango for unsupported cppTango releases are not planned.

### History

audience:all

The concept of using Devices embedded in Device servers to implement access to devices in a control system was first proposed at the ESRF in 1989. The concept was implemented in the TACO control system which was presented at the ICALEPCS conference in 1991 (Object Oriented Programming Techniques Applied to Device Access and Control). It has been successfully used as the heart of the ESRF Control System of the institute accelerator complex. This control system, called TACO, was based on the SUN RPC (also used by the NFS protocol) and C as its core programming language.

In 1999, a renewal of the ESRF distributed control system was started with the aim of replacing SUN/RPC with CORBA, using C++ as the core programming languages. The new software was called TANGO and was presented for the first time at ICALEPCS in 1999 (TANGO - AN OBJECT ORIENTED CONTROL SYSTEM BASED ON CORBA)

In June 2002, Soleil and ESRF officially decided to collaborate to develop TANGO collaboratively. Soleil is the French synchrotron radiation facility based close to Paris. In December 2003, Elettra joined the club. Elettra is an Italian synchrotron radiation facility located in Trieste. Beginning of 2005 ALBA also decided to join. ALBA is a Spanish synchrotron radiation facility located in Barcelona. DESY and MaxIV were the next big synchrotrons in Europe to join the collaboration. After that things speeded up and more and more sites doing diverse things developed collaboratively. A number of lasers, telescopes, fusion facilities have decided to use TANGO as their control system. The most recent and largest facility to join is SKA, the Square Kilometer Array, which is constructing the two largest radio telescopes in the world.

In 2015 approximately TANGO was officially renamed to be Tango Controls and is referred to as Tango for short.

Tango Controls is a toolkit for building distributed object based control systems. Distributed objects are an implementation of the Actor model. Actors are primitives of concurrent computation which were proposed in the 70s but have gained renewed interest with massively parallel architectures, IoT, cloud computing etc.

The distributed object in Tango Controls is called a device and is created as an object in a container process called a device server. The device server implements the network communication and links to the configuration data base and clients. Tango device servers and clients can be written in Python, C++ or Java. Tango comes with a full set of tools for developing, supervising, monitoring and archiving.

The Tango Controls toolkit has been used to build the control systems of large and small physics experiments like synchrotrons, lasers, wind tunnels and radio telescopes. Tango can be used for a single device which requires remote control in a lab or on the internet. Tango can be used as a communication protocol for controlling anything remotely. Tango is ideal for connecting things together and its uses are only limited by your imagination!

We are glad you are with us. Please have a look of all the in-depth explanation of Tango in the next pages.

## Tutorials

audience:all

In this section you will find a set of tutorials to get you started with using Tango.

- The Getting started section is a good place to start with instructions on how to get a simple Tango environment up and running followed by a guide to creating a simple Tango device server and client to demonstrate how Tango works.

- There is an Example deployment of a Tango Control System highlighting the important components that need to be set up and the tasks that they perform. This gives a good overview of how the whole system fits together.

- A tutorial on how to use AtkPanels to control a Tango device is given in the ATKPanel subsection. This is a good example of how to interact with device servers using a client.

- The remaining tutorials contain guidance and structure for developers developing Tango clients and Tango Device servers.

### Getting started

#### Coffee Crisis!

To make sure this tutorial isn’t super boring, imagine you are a new employee at Tango MegaCorp Inc. working in the DevOps team.

On your first day, your manager calls you into her office, with a worried expression on her face. She explains that the whole organisation’s productivity has dropped significantly in the last month. Luckily, they’ve figured out that the problem is a lack of coffee for the programmers. It’s a crisis! It just so happens that a month ago they replaced all the old coffee machines with shiny new MegaCorp DC3000 series drip coffee machines. It’s not going well - the machines are often empty or broken down, instead of having a pot of steaming brew ready at all times.

She says a control system has to be built to monitor these smart, but temperamental, coffee machines and keep the java flowing ☕☕☕. Of course, it has to be built using Tango Controls, and it has to be done right away!

With a mixture of nervousness and excitement you offer to tackle this urgent project and rush back to your office.

#### First steps

##### First Tango device server

You start with the simplest Tango device to control one of these coffee machines:

Python
main.py

    from tango.server import Device

    class MegaCoffee3k(Device):
        pass

    if __name__ == "__main__":
        MegaCoffee3k.run_server()

Pogo (C++)
MegaCoffee3k.xmi

    <?xml version="1.0" encoding="ASCII"?>
    <pogoDsl:PogoSystem xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:pogoDsl="http://tango.org/pogo/PogoDsl">
      <classes name="MegaCoffee3k" pogoRevision="9.9">
        <description description="" title="Tango MegaCorp Coffee machines 3000 series" sourcePath="/Users/antjou/tango-src/tango-doc/source/tutorial/src/cpp/01" language="Cpp" filestogenerate="XMI   file,Code files,CMakeLists,Protected Regions,WindowsCMakeLists" license="LGPL" copyright="" hasMandatoryProperty="false" hasConcreteProperty="false" hasAbstractCommand="false" hasAbstractAttribute="false">
          <inheritances classname="Device_Impl" sourcePath=""/>
          <identification contact="at tango-megacorp.inc - software" author="software" emailDomain="tango-megacorp.inc" classFamily="Training" siteSpecific="" platform="All Platforms" bus="TCP/UDP" manufacturer="Tango MegaCorp" reference=""/>
        </description>
        <commands name="State" description="This command gets the device state (stored in its device_state data member) and returns it to the caller." execMethod="dev_state" displayLevel="OPERATOR" polledPeriod="0">
          <argin description="none">
            <type xsi:type="pogoDsl:VoidType"/>
          </argin>
          <argout description="Device state">
            <type xsi:type="pogoDsl:StateType"/>
          </argout>
          <status abstract="true" inherited="true" concrete="true"/>
        </commands>
        <commands name="Status" description="This command gets the device status (stored in its device_status data member) and returns it to the caller." execMethod="dev_status" displayLevel="OPERATOR" polledPeriod="0">
          <argin description="none">
            <type xsi:type="pogoDsl:VoidType"/>
          </argin>
          <argout description="Device status">
            <type xsi:type="pogoDsl:ConstStringType"/>
          </argout>
          <status abstract="true" inherited="true" concrete="true"/>
        </commands>
        <preferences docHome="./doc_html" makefileHome="/opt/homebrew/Caskroom/mambaforge/base/envs/pogo/share/pogo/preferences"/>
      </classes>
    </pogoDsl:PogoSystem>

C++
main.cpp

    /*----- PROTECTED REGION ID(MegaCoffee3k::main.cpp) ENABLED START -----*/
    /* clang-format on */
    //=============================================================================
    //
    // file :        main.cpp
    //
    // description : C++ source for the MegaCoffee3k device server main.
    //               The main rule is to initialize (and create) the Tango
    //               system and to create the DServerClass singleton.
    //               The main should be the same for every Tango device server.
    //
    // project :     Tango MegaCorp Coffee machines 3000 series
    //
    // This file is part of Tango device class.
    //
    // Tango is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Lesser General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // Tango is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Lesser General Public License for more details.
    //
    // You should have received a copy of the GNU Lesser General Public License
    // along with Tango.  If not, see <http://www.gnu.org/licenses/>.
    //
    //
    //
    //=============================================================================
    //                This file is generated by POGO
    //        (Program Obviously used to Generate tango Object)
    //=============================================================================
    #include <tango/tango.h>

    // Check if crash reporting is used.
    #if defined(ENABLE_CRASH_REPORT)
    #  include <crashreporting/crash_report.h>
    #else
    #  define DECLARE_CRASH_HANDLER
    #  define INSTALL_CRASH_HANDLER
    #endif

    DECLARE_CRASH_HANDLER

    int main(int argc,char *argv[])
    {
    	INSTALL_CRASH_HANDLER
    	Tango::Util *tg = nullptr;
    	try
    	{
    		// Initialize the device server
    		//----------------------------------------
    		tg = Tango::Util::init(argc,argv);

    		// Create the device server singleton
    		//	which will create everything
    		//----------------------------------------
    		tg->server_init(false);

    		// Run the endless loop
    		//----------------------------------------
    		std::cout << "Ready to accept request" << std::endl;
    		tg->server_run();
    	}
    	catch (std::bad_alloc &)
    	{
    		std::cout << "Can't allocate memory to store device object !!!" << std::endl;
    		std::cout << "Exiting" << std::endl;
    	}
    	catch (CORBA::Exception &e)
    	{
    		Tango::Except::print_exception(e);

    		std::cout << "Received a CORBA_Exception" << std::endl;
    		std::cout << "Exiting" << std::endl;
    	}

    	if(tg)
    	{
    		tg->server_cleanup();
    	}
    	return(0);
    }

    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::main.cpp

MegaCoffee3kClass.h

    /*----- PROTECTED REGION ID(MegaCoffee3kClass.h) ENABLED START -----*/
    /* clang-format on */
    //=============================================================================
    //
    // file :        MegaCoffee3kClass.h
    //
    // description : Include for the MegaCoffee3k root class.
    //               This class is the singleton class for
    //                the MegaCoffee3k device class.
    //               It contains all properties and methods which the
    //               MegaCoffee3k requires only once e.g. the commands.
    //
    // project :     Tango MegaCorp Coffee machines 3000 series
    //
    // This file is part of Tango device class.
    //
    // Tango is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Lesser General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // Tango is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Lesser General Public License for more details.
    //
    // You should have received a copy of the GNU Lesser General Public License
    // along with Tango.  If not, see <http://www.gnu.org/licenses/>.
    //
    //
    //
    //=============================================================================
    //                This file is generated by POGO
    //        (Program Obviously used to Generate tango Object)
    //=============================================================================

    #ifndef MegaCoffee3kClass_H
    #define MegaCoffee3kClass_H

    #include <tango/tango.h>
    #include "MegaCoffee3k.h"

    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass.h

    namespace MegaCoffee3k_ns
    {
    /*----- PROTECTED REGION ID(MegaCoffee3kClass::classes for dynamic creation) ENABLED START -----*/
    /* clang-format on */

    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::classes for dynamic creation

    /**
     *	The MegaCoffee3kClass singleton definition
     */

    #ifdef _TG_WINDOWS_
    class __declspec(dllexport)  MegaCoffee3kClass : public Tango::DeviceClass
    #else
    class MegaCoffee3kClass : public Tango::DeviceClass
    #endif
    {
    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::Additional DServer data members) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::Additional DServer data members

    	public:
    		//	write class properties data members
    		Tango::DbData	cl_prop;
    		Tango::DbData	cl_def_prop;
    		Tango::DbData	dev_def_prop;
    		//	Method prototypes
    		static MegaCoffee3kClass *init(const char *);
    		static MegaCoffee3kClass *instance();
    		~MegaCoffee3kClass();
    		Tango::DbDatum	get_class_property(std::string &);
    		Tango::DbDatum	get_default_device_property(std::string &);
    		Tango::DbDatum	get_default_class_property(std::string &);

    	protected:
    		MegaCoffee3kClass(std::string &);
    		static MegaCoffee3kClass *_instance;
    		void command_factory();
    		void attribute_factory(std::vector<Tango::Attr *> &);
    		void pipe_factory();
    		void write_class_property();
    		void set_default_property();
    		void get_class_property();
    		std::string get_cvstag();
    		std::string get_cvsroot();

    	private:
    		void device_factory(TANGO_UNUSED(const Tango::DevVarStringArray *));
    		void create_static_attribute_list(std::vector<Tango::Attr *> &);
    		void erase_dynamic_attributes(const Tango::DevVarStringArray *,std::vector<Tango::Attr *> &);
    		std::vector<std::string>	defaultAttList;
    		Tango::Attr *get_attr_object_by_name(std::vector<Tango::Attr *> &att_list, std::string attname);
    };

    }	//	End of namespace

    #endif   //	MegaCoffee3k_H

MegaCoffee3kClass.cpp

    /*----- PROTECTED REGION ID(MegaCoffee3kClass.cpp) ENABLED START -----*/
    /* clang-format on */
    //=============================================================================
    //
    // file :        MegaCoffee3kClass.cpp
    //
    // description : C++ source for the MegaCoffee3kClass.
    //               A singleton class derived from DeviceClass.
    //               It implements the command and attribute list
    //               and all properties and methods required
    //               by the MegaCoffee3k once per process.
    //
    // project :     Tango MegaCorp Coffee machines 3000 series
    //
    // This file is part of Tango device class.
    //
    // Tango is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Lesser General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // Tango is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Lesser General Public License for more details.
    //
    // You should have received a copy of the GNU Lesser General Public License
    // along with Tango.  If not, see <http://www.gnu.org/licenses/>.
    //
    //
    //
    //=============================================================================
    //                This file is generated by POGO
    //        (Program Obviously used to Generate tango Object)
    //=============================================================================

    #include "MegaCoffee3kClass.h"
    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass.cpp

    //-------------------------------------------------------------------
    /**
     *	Create MegaCoffee3kClass singleton and
     *	return it in a C function for Python usage
     */
    //-------------------------------------------------------------------
    extern "C" {
    #ifdef _TG_WINDOWS_

    __declspec(dllexport)

    #endif

    	Tango::DeviceClass *_create_MegaCoffee3k_class(const char *name) {
    		return MegaCoffee3k_ns::MegaCoffee3kClass::init(name);
    	}
    }

    namespace MegaCoffee3k_ns
    {
    //===================================================================
    //	Initialize pointer for singleton pattern
    //===================================================================
    MegaCoffee3kClass *MegaCoffee3kClass::_instance = NULL;

    //===================================================================
    //	Class constants
    //===================================================================
    //--------------------------------------------------------
    /**
     * method : 		MegaCoffee3kClass::MegaCoffee3kClass(std::string &s)
     * description : 	constructor for the MegaCoffee3kClass
     *
     * @param s	The class name
     */
    //--------------------------------------------------------
    MegaCoffee3kClass::MegaCoffee3kClass(std::string &s):Tango::DeviceClass(s)
    {
    	TANGO_LOG_INFO << "Entering MegaCoffee3kClass constructor" << std::endl;
    	set_default_property();
    	write_class_property();

    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::constructor) ENABLED START -----*/
    	/* clang-format on */
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::constructor

    	TANGO_LOG_INFO << "Leaving MegaCoffee3kClass constructor" << std::endl;
    }

    //--------------------------------------------------------
    /**
     * method : 		MegaCoffee3kClass::~MegaCoffee3kClass()
     * description : 	destructor for the MegaCoffee3kClass
     */
    //--------------------------------------------------------
    MegaCoffee3kClass::~MegaCoffee3kClass()
    {
    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::destructor) ENABLED START -----*/
    	/* clang-format on */
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::destructor

    	_instance = NULL;
    }

    //--------------------------------------------------------
    /**
     * method : 		MegaCoffee3kClass::init
     * description : 	Create the object if not already done.
     *                  Otherwise, just return a pointer to the object
     *
     * @param	name	The class name
     */
    //--------------------------------------------------------
    MegaCoffee3kClass *MegaCoffee3kClass::init(const char *name)
    {
    	if (_instance == NULL)
    	{
    		try
    		{
    			std::string s(name);
    			_instance = new MegaCoffee3kClass(s);
    		}
    		catch (std::bad_alloc &)
    		{
    			throw;
    		}
    	}
    	return _instance;
    }

    //--------------------------------------------------------
    /**
     * method : 		MegaCoffee3kClass::instance
     * description : 	Check if object already created,
     *                  and return a pointer to the object
     */
    //--------------------------------------------------------
    MegaCoffee3kClass *MegaCoffee3kClass::instance()
    {
    	if (_instance == NULL)
    	{
    		std::cerr << "Class is not initialized !!" << std::endl;
    		exit(-1);
    	}
    	return _instance;
    }

    //===================================================================
    //	Command execution method calls
    //===================================================================

    //===================================================================
    //	Properties management
    //===================================================================
    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3kClass::get_class_property()
     *	Description: Get the class property for specified name.
     */
    //--------------------------------------------------------
    Tango::DbDatum MegaCoffee3kClass::get_class_property(std::string &prop_name)
    {
    	for (unsigned int i=0 ; i<cl_prop.size() ; i++)
    		if (cl_prop[i].name == prop_name)
    			return cl_prop[i];
    	//	if not found, returns  an empty DbDatum
    	return Tango::DbDatum(prop_name);
    }

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3kClass::get_default_device_property()
     *	Description: Return the default value for device property.
     */
    //--------------------------------------------------------
    Tango::DbDatum MegaCoffee3kClass::get_default_device_property(std::string &prop_name)
    {
    	for (unsigned int i=0 ; i<dev_def_prop.size() ; i++)
    		if (dev_def_prop[i].name == prop_name)
    			return dev_def_prop[i];
    	//	if not found, return  an empty DbDatum
    	return Tango::DbDatum(prop_name);
    }

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3kClass::get_default_class_property()
     *	Description: Return the default value for class property.
     */
    //--------------------------------------------------------
    Tango::DbDatum MegaCoffee3kClass::get_default_class_property(std::string &prop_name)
    {
    	for (unsigned int i=0 ; i<cl_def_prop.size() ; i++)
    		if (cl_def_prop[i].name == prop_name)
    			return cl_def_prop[i];
    	//	if not found, return  an empty DbDatum
    	return Tango::DbDatum(prop_name);
    }

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3kClass::set_default_property()
     *	Description: Set default property (class and device) for wizard.
     *                For each property, add to wizard property name and description.
     *                If default value has been set, add it to wizard property and
     *                store it in a DbDatum.
     */
    //--------------------------------------------------------
    void MegaCoffee3kClass::set_default_property()
    {
    	std::string	prop_name;
    	std::string	prop_desc;
    	std::string	prop_def;
    	std::vector<std::string>	vect_data;

    	//	Set Default Class Properties

    	//	Set Default device Properties
    }

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3kClass::write_class_property()
     *	Description: Set class description fields as property in database
     */
    //--------------------------------------------------------
    void MegaCoffee3kClass::write_class_property()
    {
    	//	First time, check if database used
    	if (Tango::Util::_UseDb == false)
    		return;

    	Tango::DbData	data;
    	std::string	classname = get_name();
    	std::string	header;

    	//	Put title
    	Tango::DbDatum	title("ProjectTitle");
    	std::string	str_title("Tango MegaCorp Coffee machines 3000 series");
    	title << str_title;
    	data.push_back(title);

    	//	Put Description
    	Tango::DbDatum	description("Description");
    	std::vector<std::string>	str_desc;
    	str_desc.push_back("");
    	description << str_desc;
    	data.push_back(description);

    	//  Put inheritance
    	Tango::DbDatum	inher_datum("InheritedFrom");
    	std::vector<std::string> inheritance;
    	inheritance.push_back("TANGO_BASE_CLASS");
    	inher_datum << inheritance;
    	data.push_back(inher_datum);

    	//	Call database and and values
    	get_db_class()->put_property(data);
    }

    //===================================================================
    //	Factory methods
    //===================================================================

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3kClass::device_factory()
     *	Description: Create the device object(s)
     *                and store them in the device list
     */
    //--------------------------------------------------------
    void MegaCoffee3kClass::device_factory(const Tango::DevVarStringArray *devlist_ptr)
    {
    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::device_factory_before) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::device_factory_before

    	//	Create devices and add it into the device list
    	for (unsigned long i=0 ; i<devlist_ptr->length() ; i++)
    	{
    		TANGO_LOG_DEBUG << "Device name : " << (*devlist_ptr)[i].in() << std::endl;
    		device_list.push_back(new MegaCoffee3k(this, (*devlist_ptr)[i]));
    	}

    	//	Manage dynamic attributes if any
    	erase_dynamic_attributes(devlist_ptr, get_class_attr()->get_attr_list());

    	//	Export devices to the outside world
    	for (unsigned long i=1 ; i<=devlist_ptr->length() ; i++)
    	{
    		//	Add dynamic attributes if any
    		MegaCoffee3k *dev = static_cast<MegaCoffee3k *>(device_list[device_list.size()-i]);
    		dev->add_dynamic_attributes();

    		//	Check before if database used.
    		if ((Tango::Util::_UseDb == true) && (Tango::Util::_FileDb == false))
    			export_device(dev);
    		else
    			export_device(dev, dev->get_name().c_str());
    	}

    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::device_factory_after) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::device_factory_after
    }
    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3kClass::attribute_factory()
     *	Description: Create the attribute object(s)
     *                and store them in the attribute list
     */
    //--------------------------------------------------------
    void MegaCoffee3kClass::attribute_factory(std::vector<Tango::Attr *> &)
    {
    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::attribute_factory_before) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::attribute_factory_before

    	//	Create a list of static attributes
    	create_static_attribute_list(get_class_attr()->get_attr_list());
    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::attribute_factory_after) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::attribute_factory_after
    }
    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3kClass::pipe_factory()
     *	Description: Create the pipe object(s)
     *                and store them in the pipe list
     */
    //--------------------------------------------------------
    void MegaCoffee3kClass::pipe_factory()
    {
    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::pipe_factory_before) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::pipe_factory_before
    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::pipe_factory_after) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::pipe_factory_after
    }
    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3kClass::command_factory()
     *	Description: Create the command object(s)
     *                and store them in the command list
     */
    //--------------------------------------------------------
    void MegaCoffee3kClass::command_factory()
    {
    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::command_factory_before) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::command_factory_before

    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::command_factory_after) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::command_factory_after
    }

    //===================================================================
    //	Dynamic attributes related methods
    //===================================================================

    //--------------------------------------------------------
    /**
     * method : 		MegaCoffee3kClass::create_static_attribute_list
     * description : 	Create the a list of static attributes
     *
     * @param	att_list	the created attribute list
     */
    //--------------------------------------------------------
    void MegaCoffee3kClass::create_static_attribute_list(std::vector<Tango::Attr *> &att_list)
    {
    	for (unsigned long i=0 ; i<att_list.size() ; i++)
    	{
    		std::string att_name(att_list[i]->get_name());
    		std::transform(att_name.begin(), att_name.end(), att_name.begin(), ::tolower);
    		defaultAttList.push_back(att_name);
    	}

    	TANGO_LOG_INFO << defaultAttList.size() << " attributes in default list" << std::endl;

    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::create_static_att_list) ENABLED START -----*/
    	/* clang-format on */
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::create_static_att_list
    }

    //--------------------------------------------------------
    /**
     * method : 		MegaCoffee3kClass::erase_dynamic_attributes
     * description : 	delete the dynamic attributes if any.
     *
     * @param	devlist_ptr	the device list pointer
     * @param	list of all attributes
     */
    //--------------------------------------------------------
    void MegaCoffee3kClass::erase_dynamic_attributes(const Tango::DevVarStringArray *devlist_ptr, std::vector<Tango::Attr *> &att_list)
    {
    	Tango::Util *tg = Tango::Util::instance();

    	for (unsigned long i=0 ; i<devlist_ptr->length() ; i++)
    	{
    		Tango::DeviceImpl *dev_impl = tg->get_device_by_name(((std::string)(*devlist_ptr)[i]).c_str());
    		MegaCoffee3k *dev = static_cast<MegaCoffee3k *> (dev_impl);

    		std::vector<Tango::Attribute *> &dev_att_list = dev->get_device_attr()->get_attribute_list();
    		std::vector<Tango::Attribute *>::iterator ite_att;
    		for (ite_att=dev_att_list.begin() ; ite_att != dev_att_list.end() ; ++ite_att)
    		{
    			std::string att_name((*ite_att)->get_name_lower());
    			if ((att_name == "state") || (att_name == "status"))
    				continue;
    			std::vector<std::string>::iterator ite_str = find(defaultAttList.begin(), defaultAttList.end(), att_name);
    			if (ite_str == defaultAttList.end())
    			{
    				TANGO_LOG_INFO << att_name << " is a UNWANTED dynamic attribute for device " << (*devlist_ptr)[i] << std::endl;
    				Tango::Attribute &att = dev->get_device_attr()->get_attr_by_name(att_name.c_str());
    				dev->remove_attribute(att_list[att.get_attr_idx()], true, false);
    				--ite_att;
    			}
    		}
    	}
    	/*----- PROTECTED REGION ID(MegaCoffee3kClass::erase_dynamic_attributes) ENABLED START -----*/
    	/* clang-format on */
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::erase_dynamic_attributes
    }

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3kClass::get_attr_object_by_name()
     *	Description: returns Tango::Attr * object found by name
     */
    //--------------------------------------------------------
    Tango::Attr *MegaCoffee3kClass::get_attr_object_by_name(std::vector<Tango::Attr *> &att_list, std::string attname)
    {
    	std::vector<Tango::Attr *>::iterator it;
    	for (it=att_list.begin() ; it<att_list.end() ; ++it)
    		if ((*it)->get_name()==attname)
    			return (*it);
    	//	Attr does not exist
    	return NULL;
    }

    /*----- PROTECTED REGION ID(MegaCoffee3kClass::Additional Methods) ENABLED START -----*/
    /* clang-format on */
    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3kClass::Additional Methods
    } //	namespace

MegaCoffee3k.h

    /*----- PROTECTED REGION ID(MegaCoffee3k.h) ENABLED START -----*/
    /* clang-format on */
    //=============================================================================
    //
    // file :        MegaCoffee3k.h
    //
    // description : Include file for the MegaCoffee3k class
    //
    // project :     Tango MegaCorp Coffee machines 3000 series
    //
    // This file is part of Tango device class.
    //
    // Tango is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Lesser General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // Tango is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Lesser General Public License for more details.
    //
    // You should have received a copy of the GNU Lesser General Public License
    // along with Tango.  If not, see <http://www.gnu.org/licenses/>.
    //
    //
    //
    //=============================================================================
    //                This file is generated by POGO
    //        (Program Obviously used to Generate tango Object)
    //=============================================================================

    #ifndef MegaCoffee3k_H
    #define MegaCoffee3k_H

    #include <tango/tango.h>

    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.h

    #ifdef TANGO_LOG
    	// cppTango after c934adea (Merge branch 'remove-cout-definition' into 'main', 2022-05-23)
    	// nothing to do
    #else
    	// cppTango 9.3-backports and older
    	#define TANGO_LOG       cout
    	#define TANGO_LOG_INFO  cout2
    	#define TANGO_LOG_DEBUG cout3
    #endif // TANGO_LOG

    /**
     *  MegaCoffee3k class description:
     *
     */

    namespace MegaCoffee3k_ns
    {
    /*----- PROTECTED REGION ID(MegaCoffee3k::Additional Class Declarations) ENABLED START -----*/
    /* clang-format on */
    //	Additional Class Declarations
    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::Additional Class Declarations

    class MegaCoffee3k : public TANGO_BASE_CLASS
    {

    /*----- PROTECTED REGION ID(MegaCoffee3k::Data Members) ENABLED START -----*/
    /* clang-format on */
    //	Add your own data members
    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::Data Members

    //	Constructors and destructors
    public:
    	/**
    	 * Constructs a newly device object.
    	 *
    	 *	@param cl	Class.
    	 *	@param s 	Device Name
    	 */
    	MegaCoffee3k(Tango::DeviceClass *cl,std::string &s);
    	/**
    	 * Constructs a newly device object.
    	 *
    	 *	@param cl	Class.
    	 *	@param s 	Device Name
    	 */
    	MegaCoffee3k(Tango::DeviceClass *cl,const char *s);
    	/**
    	 * Constructs a newly device object.
    	 *
    	 *	@param cl	Class.
    	 *	@param s 	Device name
    	 *	@param d	Device description.
    	 */
    	MegaCoffee3k(Tango::DeviceClass *cl,const char *s,const char *d);
    	/**
    	 * The device object destructor.
    	 */
    	~MegaCoffee3k();

    //	Miscellaneous methods
    public:
    	/*
    	 *	will be called at device destruction or at init command.
    	 */
    	void delete_device();
    	/*
    	 *	Initialize the device
    	 */
    	virtual void init_device();
    	/*
    	 *	Always executed method before execution command method.
    	 */
    	virtual void always_executed_hook();

    //	Attribute methods
    public:
    	//--------------------------------------------------------
    	/*
    	 *	Method     : MegaCoffee3k::read_attr_hardware()
    	 *	Description: Hardware acquisition for attributes.
    	 */
    	//--------------------------------------------------------
    	virtual void read_attr_hardware(std::vector<long> &attr_list);

    	//--------------------------------------------------------
    	/**
    	 *	Method     : MegaCoffee3k::add_dynamic_attributes()
    	 *	Description: Add dynamic attributes if any.
    	 */
    	//--------------------------------------------------------
    	void add_dynamic_attributes();

    //	Command related methods
    public:

    	//--------------------------------------------------------
    	/**
    	 *	Method     : MegaCoffee3k::add_dynamic_commands()
    	 *	Description: Add dynamic commands if any.
    	 */
    	//--------------------------------------------------------
    	void add_dynamic_commands();

    /*----- PROTECTED REGION ID(MegaCoffee3k::Additional Method prototypes) ENABLED START -----*/
    /* clang-format on */
    //	Additional Method prototypes
    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::Additional Method prototypes
    };

    /*----- PROTECTED REGION ID(MegaCoffee3k::Additional Classes Definitions) ENABLED START -----*/
    /* clang-format on */
    //	Additional Classes Definitions
    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::Additional Classes Definitions

    }	//	End of namespace

    #endif   //	MegaCoffee3k_H

MegaCoffee3k.cpp

    /*----- PROTECTED REGION ID(MegaCoffee3k.cpp) ENABLED START -----*/
    /* clang-format on */
    //=============================================================================
    //
    // file :        MegaCoffee3k.cpp
    //
    // description : C++ source for the MegaCoffee3k class and its commands.
    //               The class is derived from Device. It represents the
    //               CORBA servant object which will be accessed from the
    //               network. All commands which can be executed on the
    //               MegaCoffee3k are implemented in this file.
    //
    // project :     Tango MegaCorp Coffee machines 3000 series
    //
    // This file is part of Tango device class.
    //
    // Tango is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Lesser General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // Tango is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Lesser General Public License for more details.
    //
    // You should have received a copy of the GNU Lesser General Public License
    // along with Tango.  If not, see <http://www.gnu.org/licenses/>.
    //
    //
    //
    //=============================================================================
    //                This file is generated by POGO
    //        (Program Obviously used to Generate tango Object)
    //=============================================================================

    #include "MegaCoffee3k.h"
    #include "MegaCoffee3kClass.h"
    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.cpp

    /**
     *  MegaCoffee3k class description:
     *
     */

    //================================================================
    //  The following table gives the correspondence
    //  between command and method names.
    //
    //  Command name  |  Method name
    //================================================================
    //  State         |  Inherited (no method)
    //  Status        |  Inherited (no method)
    //================================================================

    //================================================================
    //  Attributes managed is:
    //================================================================
    //================================================================

    namespace MegaCoffee3k_ns
    {
    /*----- PROTECTED REGION ID(MegaCoffee3k::namespace_starting) ENABLED START -----*/
    /* clang-format on */
    //	static initializations
    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::namespace_starting

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3k::MegaCoffee3k()
     *	Description: Constructors for a Tango device
     *                implementing the classMegaCoffee3k
     */
    //--------------------------------------------------------
    MegaCoffee3k::MegaCoffee3k(Tango::DeviceClass *cl, std::string &s)
     : TANGO_BASE_CLASS(cl, s.c_str())
    {
    	/*----- PROTECTED REGION ID(MegaCoffee3k::constructor_1) ENABLED START -----*/
    	/* clang-format on */
    	init_device();
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::constructor_1
    }
    //--------------------------------------------------------
    MegaCoffee3k::MegaCoffee3k(Tango::DeviceClass *cl, const char *s)
     : TANGO_BASE_CLASS(cl, s)
    {
    	/*----- PROTECTED REGION ID(MegaCoffee3k::constructor_2) ENABLED START -----*/
    	/* clang-format on */
    	init_device();
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::constructor_2
    }
    //--------------------------------------------------------
    MegaCoffee3k::MegaCoffee3k(Tango::DeviceClass *cl, const char *s, const char *d)
     : TANGO_BASE_CLASS(cl, s, d)
    {
    	/*----- PROTECTED REGION ID(MegaCoffee3k::constructor_3) ENABLED START -----*/
    	/* clang-format on */
    	init_device();
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::constructor_3
    }
    //--------------------------------------------------------
    MegaCoffee3k::~MegaCoffee3k()
    {
    	delete_device();
    }

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3k::delete_device()
     *	Description: will be called at device destruction or at init command
     */
    //--------------------------------------------------------
    void MegaCoffee3k::delete_device()
    {
    	DEBUG_STREAM << "MegaCoffee3k::delete_device() " << device_name << std::endl;
    	/*----- PROTECTED REGION ID(MegaCoffee3k::delete_device) ENABLED START -----*/
    	/* clang-format on */
    	//	Delete device allocated objects
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::delete_device
    }

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3k::init_device()
     *	Description: will be called at device initialization.
     */
    //--------------------------------------------------------
    void MegaCoffee3k::init_device()
    {
    	DEBUG_STREAM << "MegaCoffee3k::init_device() create device " << device_name << std::endl;
    	/*----- PROTECTED REGION ID(MegaCoffee3k::init_device_before) ENABLED START -----*/
    	/* clang-format on */
    	//	Initialization before get_device_property() call
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::init_device_before

    	//	No device property to be read from database

    	/*----- PROTECTED REGION ID(MegaCoffee3k::init_device) ENABLED START -----*/
    	/* clang-format on */
    	//	Initialize device
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::init_device
    }

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3k::always_executed_hook()
     *	Description: method always executed before any command is executed
     */
    //--------------------------------------------------------
    void MegaCoffee3k::always_executed_hook()
    {
    	DEBUG_STREAM << "MegaCoffee3k::always_executed_hook()  " << device_name << std::endl;
    	/*----- PROTECTED REGION ID(MegaCoffee3k::always_executed_hook) ENABLED START -----*/
    	/* clang-format on */
    	//	code always executed before all requests
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::always_executed_hook
    }

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3k::read_attr_hardware()
     *	Description: Hardware acquisition for attributes
     */
    //--------------------------------------------------------
    void MegaCoffee3k::read_attr_hardware(TANGO_UNUSED(std::vector<long> &attr_list))
    {
    	DEBUG_STREAM << "MegaCoffee3k::read_attr_hardware(std::vector<long> &attr_list) entering... " << std::endl;
    	/*----- PROTECTED REGION ID(MegaCoffee3k::read_attr_hardware) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::read_attr_hardware
    }

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3k::add_dynamic_attributes()
     *	Description: Create the dynamic attributes if any
     *                for specified device.
     */
    //--------------------------------------------------------
    void MegaCoffee3k::add_dynamic_attributes()
    {
    	/*----- PROTECTED REGION ID(MegaCoffee3k::add_dynamic_attributes) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code to create and add dynamic attributes if any
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::add_dynamic_attributes
    }

    //--------------------------------------------------------
    /**
     *	Method     : MegaCoffee3k::add_dynamic_commands()
     *	Description: Create the dynamic commands if any
     *                for specified device.
     */
    //--------------------------------------------------------
    void MegaCoffee3k::add_dynamic_commands()
    {
    	/*----- PROTECTED REGION ID(MegaCoffee3k::add_dynamic_commands) ENABLED START -----*/
    	/* clang-format on */
    	//	Add your own code to create and add dynamic commands if any
    	/* clang-format off */
    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::add_dynamic_commands
    }

    /*----- PROTECTED REGION ID(MegaCoffee3k::namespace_ending) ENABLED START -----*/
    /* clang-format on */
    //	Additional Methods
    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::namespace_ending
    } //	namespace

MegaCoffee3kStateMachine.cpp

    /*----- PROTECTED REGION ID(MegaCoffee3kStateMachine.cpp) ENABLED START -----*/
    /* clang-format on */
    //=============================================================================
    //
    // file :        MegaCoffee3kStateMachine.cpp
    //
    // description : State machine file for the MegaCoffee3k class
    //
    // project :     Tango MegaCorp Coffee machines 3000 series
    //
    // This file is part of Tango device class.
    //
    // Tango is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Lesser General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // Tango is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Lesser General Public License for more details.
    //
    // You should have received a copy of the GNU Lesser General Public License
    // along with Tango.  If not, see <http://www.gnu.org/licenses/>.
    //
    //
    //
    //=============================================================================
    //                This file is generated by POGO
    //        (Program Obviously used to Generate tango Object)
    //=============================================================================

    #include "MegaCoffee3k.h"

    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::MegaCoffee3kStateMachine.cpp

    //================================================================
    //  States  |  Description
    //================================================================

    namespace MegaCoffee3k_ns
    {
    //=================================================
    //		Attributes Allowed Methods
    //=================================================

    //=================================================
    //		Commands Allowed Methods
    //=================================================

    /*----- PROTECTED REGION ID(MegaCoffee3k::MegaCoffee3kStateAllowed.AdditionalMethods) ENABLED START -----*/
    /* clang-format on */
    //	Additional Methods
    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::MegaCoffee3kStateAllowed.AdditionalMethods

    }	//	End of namespace

CMakeLists.txt

    #=============================================================================
    #
    # file :        CMakeLists.txt
    #
    # description : File to generate a TANGO device server using cmake.
    #
    # project :     MegaCoffee3k
    #
    #=============================================================================
    #                This file is generated by POGO
    #        (Program Obviously used to Generate tango Object)
    #=============================================================================
    #
    #

    cmake_minimum_required (VERSION 3.18...3.26 FATAL_ERROR)
    set(CMAKE_SKIP_RPATH true)

    # Windows cmakelists

    # MAKE_ENV is the path to find the common environment to build the project
    #
    if (DEFINED ENV{MAKE_ENV})
        set(MAKE_ENV $ENV{MAKE_ENV})
    else()
        set(MAKE_ENV /opt/homebrew/Caskroom/mambaforge/base/envs/pogo/share/pogo/preferences)
    endif()
    #
    # Project definitions
    #
    project(MegaCoffee3k)

    #
    # optional compiler flags
    #
    set(CXXFLAGS_USER -g)

    #
    # Get global information
    #
    include(CmakeTangoWin.cmake)

    #
    # Files for MegaCoffee3k TANGO class
    #
    set(MegaCoffee3k MegaCoffee3k)
    set(MegaCoffee3k_INCLUDE ${CMAKE_SOURCE_DIR})
    set(MegaCoffee3k_SRC  ${MegaCoffee3k}.cpp
                           ${MegaCoffee3k}Class.cpp
                           ${MegaCoffee3k}StateMachine.cpp)

    #
    # User additional include, link folders/libraries and source files
    #
    set(USER_INCL_DIR )
    set(USER_LIB_DIR )
    set(USER_LIBS )
    set(USER_SRC_FILES )

    #
    # Set global info and include directories
    #
    set(ALL_CLASS_INCLUDE  ${MegaCoffee3k_INCLUDE}  ${USER_INCL_DIR})
    set(SERVER_SRC ${MegaCoffee3k_SRC}  ${USER_SRC_FILES} ClassFactory.cpp main.cpp)
    include_directories(${ALL_CLASS_INCLUDE}  ${USER_INCL_DIR} ${TANGO_INCLUDES})
    link_directories(${TANGO_LNK_DIR})

    #
    # Device Server generation
    #
    set(SERVER_NAME MegaCoffee3k)
    add_executable(MegaCoffee3k ${SERVER_SRC})
    target_link_libraries(MegaCoffee3k PUBLIC ${TANGO_LIBS} ${WIN_LIBS} ${ZMQ_LIB})
    # Cpack target
    install(TARGETS MegaCoffee3k
    	RUNTIME DESTINATION bin
    	LIBRARY DESTINATION bin
    	ARCHIVE DESTINATION bin)

Pogo (Java)
MegaCoffee3k.xmi

    <?xml version="1.0" encoding="ASCII"?>
    <pogoDsl:PogoSystem xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:pogoDsl="http://tango.org/pogo/PogoDsl">
      <classes name="MegaCoffee3k" pogoRevision="9.9">
        <description description="" title="Tango MegaCorp Coffee machines 3000 series" sourcePath="/Users/antjou/tango-src/tango-doc/source/tutorial/src/java/01" language="Java" filestogenerate="XMI   file,Code files,Makefile,Protected Regions,pom.xml" license="LGPL" copyright="" hasMandatoryProperty="false" hasConcreteProperty="false" hasAbstractCommand="false" hasAbstractAttribute="false">
          <inheritances classname="Device_Impl" sourcePath=""/>
          <identification contact="at tango-megacorp.inc - software" author="software" emailDomain="tango-megacorp.inc" classFamily="Training" siteSpecific="" platform="All Platforms" bus="TCP/UDP" manufacturer="Tango MegaCorp" reference=""/>
        </description>
        <commands name="State" description="This command gets the device state (stored in its device_state data member) and returns it to the caller." execMethod="dev_state" displayLevel="OPERATOR" polledPeriod="0">
          <argin description="none">
            <type xsi:type="pogoDsl:VoidType"/>
          </argin>
          <argout description="Device state">
            <type xsi:type="pogoDsl:StateType"/>
          </argout>
          <status abstract="true" inherited="true" concrete="true"/>
        </commands>
        <commands name="Status" description="This command gets the device status (stored in its device_status data member) and returns it to the caller." execMethod="dev_status" displayLevel="OPERATOR" polledPeriod="0">
          <argin description="none">
            <type xsi:type="pogoDsl:VoidType"/>
          </argin>
          <argout description="Device status">
            <type xsi:type="pogoDsl:ConstStringType"/>
          </argout>
          <status abstract="true" inherited="true" concrete="true"/>
        </commands>
        <preferences docHome="./doc_html" makefileHome="/opt/homebrew/Caskroom/mambaforge/base/envs/pogo/share/pogo/preferences"/>
      </classes>
    </pogoDsl:PogoSystem>

Java
MegaCoffee3k.java

    /*----- PROTECTED REGION ID(MegaCoffee3k.java) ENABLED START -----*/
    //=============================================================================
    //
    // file :        MegaCoffee3k.java
    //
    // description : Java source for the MegaCoffee3k class and its commands.
    //               The class is derived from Device. It represents the
    //               CORBA servant object which will be accessed from the
    //               network. All commands which can be executed on the
    //               MegaCoffee3k are implemented in this file.
    //
    // project :     Tango MegaCorp Coffee machines 3000 series
    //
    // This file is part of Tango device class.
    //
    // Tango is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Lesser General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // Tango is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Lesser General Public License for more details.
    //
    // You should have received a copy of the GNU Lesser General Public License
    // along with Tango.  If not, see <http://www.gnu.org/licenses/>.
    //
    //
    //
    //=============================================================================
    //                This file is generated by POGO
    //        (Program Obviously used to Generate tango Object)
    //=============================================================================

    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.java

    package org.tango.megacoffee3k;

    /*----- PROTECTED REGION ID(MegaCoffee3k.imports) ENABLED START -----*/
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.slf4j.ext.XLogger;
    import org.slf4j.ext.XLoggerFactory;
    import org.tango.DeviceState;
    import org.tango.server.InvocationContext;
    import org.tango.server.ServerManager;
    import org.tango.server.annotation.AroundInvoke;
    import org.tango.server.annotation.Attribute;
    import org.tango.server.annotation.AttributeProperties;
    import org.tango.server.annotation.ClassProperty;
    import org.tango.server.annotation.Command;
    import org.tango.server.annotation.Delete;
    import org.tango.server.annotation.Device;
    import org.tango.server.annotation.DeviceProperty;
    import org.tango.server.annotation.DynamicManagement;
    import org.tango.server.annotation.Init;
    import org.tango.server.annotation.State;
    import org.tango.server.annotation.StateMachine;
    import org.tango.server.annotation.Status;
    import org.tango.server.annotation.DeviceManagement;
    import org.tango.server.annotation.Pipe;
    import org.tango.server.attribute.ForwardedAttribute;import org.tango.server.pipe.PipeValue;
    import org.tango.server.dynamic.DynamicManager;
    import org.tango.server.device.DeviceManager;
    import org.tango.server.dynamic.DynamicManager;
    import org.tango.server.events.EventManager;
    import org.tango.server.events.EventType;
    import org.tango.utils.DevFailedUtils;

    //	Import Tango IDL types
    import fr.esrf.Tango.*;
    import fr.esrf.TangoDs.Except;
    import fr.esrf.TangoApi.PipeBlob;
    import fr.esrf.TangoApi.PipeDataElement;

    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.imports

    /**
     *  MegaCoffee3k class description:
     *
     */

    @Device
    public class MegaCoffee3k {

    	protected static final Logger logger = LoggerFactory.getLogger(MegaCoffee3k.class);
    	protected static final XLogger xlogger = XLoggerFactory.getXLogger(MegaCoffee3k.class);
    	//========================================================
    	//	Programmer's data members
    	//========================================================
        /*----- PROTECTED REGION ID(MegaCoffee3k.variables) ENABLED START -----*/

        //	Put static variables here

        /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.variables
    	/*----- PROTECTED REGION ID(MegaCoffee3k.private) ENABLED START -----*/

    	//	Put private variables here

    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.private

    	//========================================================
    	//	Property data members and related methods
    	//========================================================

    	//========================================================
    	//	Miscellaneous methods
    	//========================================================
    	/**
    	 * Initialize the device.
    	 *
    	 * @throws DevFailed if something fails during the device initialization.
    	 */
    	@Init(lazyLoading = false)
    	public void initDevice() throws DevFailed {
    		xlogger.entry();
    		logger.debug("init device " + deviceManager.getName());
    		/*----- PROTECTED REGION ID(MegaCoffee3k.initDevice) ENABLED START -----*/

    		//	Put your device initialization code here

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.initDevice
    		xlogger.exit();
    	}

    	/**
    	 * all resources may be closed here. Collections may be also cleared.
    	 *
    	 * @throws DevFailed if something fails during the device object deletion.
    	 */
    	@Delete
    	public void deleteDevice() throws DevFailed {
    		xlogger.entry();
    		/*----- PROTECTED REGION ID(MegaCoffee3k.deleteDevice) ENABLED START -----*/

    		//	Put your device clearing code here

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.deleteDevice
    		xlogger.exit();
    	}

    	/**
    	 * Method called before and after command and attribute calls.
    	 * @param ctx the invocation context
    	 * @throws DevFailed if something fails during this method execution.
    	 */
    	@AroundInvoke
    	public void aroundInvoke(final InvocationContext ctx) throws DevFailed {
    		xlogger.entry();
    			/*----- PROTECTED REGION ID(MegaCoffee3k.aroundInvoke) ENABLED START -----*/

    			//	Put aroundInvoke code here

    			/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.aroundInvoke
    		xlogger.exit();
    	}

    	/**
    	 * dynamic command and attribute management. Will be injected by the framework.
    	 */
    	@DynamicManagement
    	protected DynamicManager dynamicManager;
    	/**
    	 * @param dynamicManager the DynamicManager instance
    	 * @throws DevFailed if something fails during this method execution.
    	 */
    	public void setDynamicManager(final DynamicManager dynamicManager) throws DevFailed {
    		this.dynamicManager = dynamicManager;
    		/*----- PROTECTED REGION ID(MegaCoffee3k.setDynamicManager) ENABLED START -----*/

    		//	Put your code here

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.setDynamicManager
    	}

    	/**
    	 * Device management. Will be injected by the framework.
    	 */
    	@DeviceManagement
    	DeviceManager deviceManager;
    	public void setDeviceManager(DeviceManager deviceManager){
    		this.deviceManager= deviceManager ;
    	}

    	//========================================================
    	//	Command data members and related methods
    	//========================================================
    	/**
    	 * The state of the device
    	*/
    	@State
    	private DevState state = DevState.UNKNOWN;
    	/**
    	 * Execute command "State".
    	 * description: This command gets the device state (stored in its 'state' data member) and returns it to the caller.
    	 * @return Device state
    	 * @throws DevFailed if command execution failed.
    	 */
    	public final DevState getState() throws DevFailed {
    		/*----- PROTECTED REGION ID(MegaCoffee3k.getState) ENABLED START -----*/

    		//	Put state code here

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.getState
    		return state;
    	}
    	/**
    	 * Set the device state
    	 * @param state the new device state
    	 */
    	public void setState(final DevState state) {
    		this.state = state;
    	}

    	/**
    	 * The status of the device
    	 */
    	@Status
    	private String status = "Server is starting. The device state is unknown";
    	/**
    	 * Execute command "Status".
    	 * description: This command gets the device status (stored in its 'status' data member) and returns it to the caller.
    	 * @return Device status
    	 * @throws DevFailed if command execution failed.
    	 */
    	public final String getStatus() throws DevFailed {
    		/*----- PROTECTED REGION ID(MegaCoffee3k.getStatus) ENABLED START -----*/

    		//	Put status code here

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.getStatus
    		return status;
    	}
    	/**
    	 * Set the device status
    	 * @param status the new device status
    	 */
    	public void setStatus(final String status) {
    		this.status = status;
    	}

    	//========================================================
    	//	Programmer's methods
    	//========================================================
    	/*----- PROTECTED REGION ID(MegaCoffee3k.methods) ENABLED START -----*/

    	//	Put your own methods here

    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.methods

    	/**
    	 * Starts the server.
    	 * @param args program arguments (instance_name [-v[trace level]]  [-nodb [-dlist <device name list>] [-file=fileName]])
    	 */
    	public static void main(final String[] args) {
    		/*----- PROTECTED REGION ID(MegaCoffee3k.main) ENABLED START -----*/

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.main
    		ServerManager.getInstance().start(args, MegaCoffee3k.class);
    		System.out.println("------- Started -------------");
    	}
    }

For the Python example, copy that to a file `main.py`.

Make sure your Pixi shell is active, so the prompt should look something like this.

    (tango-tut) $

Now you can run your first Tango device server, using PyTango’s `test_context` utility:

    (tango-tut) $ python -m tango.test_context main.MegaCoffee3k --host 127.0.0.1

You see the output:

    Ready to accept request
    MegaCoffee3k started on port 8888 with properties {}
    Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
    Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no

The `Ready to accept request` line tells you that device server has started up.

The lines after that are from the “test context” used to launch it. You’ll use the cryptic `Device access` part in the next section.

Hint

Don’t worry about the `notifd` warning, if you see it. It isn’t used in newer Tango systems.

Troubleshooting errors when launching

If TCP port 8888 is already in use on your system, you’ll get an error like this:

    omniORB: (? 6151811072) 2024-10-28 08:05:02.317646: Failed to bind to address 127.0.0.1 port 10000. Address in use?
    omniORB: (? 6151811072) 2024-10-28 08:05:02.317662: Error: Unable to create an endpoint of this description: giop:tcp:127.0.0.1:10000

Try a different port number, like 8889:

    (tango-tut) $ python -m tango.test_context main.MegaCoffee3k --host 127.0.0.1 --port 8889

If you get something like

    zsh: command not found: python

Or

    python3.11: Error while finding module specification for 'tango.test_context' (ModuleNotFoundError: No module named 'tango')

Then your Pixi shell might not be activated. Try this, and check the Pixi installation again.

    $ pixi shell

Or, if you’re not using Pixi, your virtual environment isn’t active, or you haven’t installed PyTango.

##### First Tango client

Tango uses a distributed client-server architecture. Now that you have your MegaCoffee3k device server running, you need to connect a client.

Start a new terminal, so that the server keeps running in the old terminal. Again, activate your environment.

    $ pixi shell
    (tango-tut) $ python

We use PyTango, and the Device access Tango resource locator, `tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no` to connect our client, a Tango Device Proxy.

    >>> import tango
    >>> dp = tango.DeviceProxy("tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no")
    >>> dp.ping()
    382
    >>> dp.State()
    tango._tango.DevState.UNKNOWN
    >>> dp.Status()
    'The device is in UNKNOWN state.'
    >>>

It works! That was super east, but it isn’t super useful yet. Read on!

Hint

If you’re wondering, the value `382` is the ping response time in microseconds.

Troubleshooting errors when using the client

    >>> dp.ping()
    Traceback (most recent call last):
      File "<python-input-8>", line 1, in <module>
        dp.ping()
        ~~~~~~~^^
      File "/path/to/pytango/tango/green.py", line 226, in greener
        return executor.run(fn, args, kwargs, wait=wait, timeout=timeout)
               ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      File "/path/to/pytango/tango/green.py", line 116, in run
        return fn(*args, **kwargs)
      File "/path/to/pytango/tango/device_proxy.py", line 2056, in __DeviceProxy__ping
        return self._ping(*args, **kwargs)
               ~~~~~~~~~~^^^^^^^^^^^^^^^^^
    PyTango.DevFailed: DevFailed[
        DevError[
            desc = TRANSIENT CORBA system exception: TRANSIENT_ConnectFailed
            origin = void Tango::Connection::connect(const std::string &) at (/Users/runner/miniforge3/conda-bld/cpptango_1739462625904/work/src/client/devapi_base.cpp:635)
            reason = API_CorbaException
            severity = ERR
        ],
        DevError[
            desc = Failed to connect to device test/nodb/megacoffee3k
            origin = void Tango::Connection::connect(const std::string &) at (/Users/runner/miniforge3/conda-bld/cpptango_1739462625904/work/src/client/devapi_base.cpp:635)
            reason = API_ServerNotRunning
            severity = ERR
        ]
    ]

This means the device server is not running. Make sure it is still running in a different terminal.

    (tango-tut) $ python -m tango.test_context main.MegaCoffee3k --host 127.0.0.1

Bonus tip: admin device

The second Tango resource locator, for Server access, points to another Tango device that is run automatically. It is called the “admin” device or “DServer”, and is used for management of the device server instance. E.g., restarting individual devices, or configuring logging.

##### Finishing up

You can use the keyboard combination `Ctrl`+`C` to end the Tango device server application.

    Can't create notifd event supplier. Notifd event not available
    Ready to accept request
    MegaCoffee3k started on port 8888 with properties {}
    Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
    Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
    ^CDone
    (tango-tut) $

Similarly, you can exit the Python interpreter console when you are done with the client.

#### State and Status

All Tango devices automatically provide State and Status information that can be queried at any time.

In the previous lesson you saw that this was just `UNKNOWN` and `'The device is in UNKNOWN state.'` for your first device. You want to make this a little better.

Python
main.py

    from tango import DevState
    from tango.server import Device

    class MegaCoffee3k(Device):
        def init_device(self):
            super().init_device()
            self.set_state(DevState.OFF)
            self.set_status("Hello world - device is off.")

    if __name__ == "__main__":
        MegaCoffee3k.run_server()

Pogo (C++)
MegaCoffee3k.xmi

    <?xml version="1.0" encoding="ASCII"?>
    <pogoDsl:PogoSystem xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:pogoDsl="http://tango.org/pogo/PogoDsl">
      <classes name="MegaCoffee3k" pogoRevision="9.9">
        <description description="" title="Tango MegaCorp Coffee machines 3000 series" sourcePath="/Users/antjou/tango-src/tango-doc/source/tutorial/src/cpp/01" language="Cpp" filestogenerate="XMI   file,Code files,CMakeLists,Protected Regions,WindowsCMakeLists" license="LGPL" copyright="" hasMandatoryProperty="false" hasConcreteProperty="false" hasAbstractCommand="false" hasAbstractAttribute="false">
          <inheritances classname="Device_Impl" sourcePath=""/>
          <identification contact="at tango-megacorp.inc - software" author="software" emailDomain="tango-megacorp.inc" classFamily="Training" siteSpecific="" platform="All Platforms" bus="TCP/UDP" manufacturer="Tango MegaCorp" reference=""/>
        </description>
        <commands name="State" description="This command gets the device state (stored in its device_state data member) and returns it to the caller." execMethod="dev_state" displayLevel="OPERATOR" polledPeriod="0">
          <argin description="none">
            <type xsi:type="pogoDsl:VoidType"/>
          </argin>
          <argout description="Device state">
            <type xsi:type="pogoDsl:StateType"/>
          </argout>
          <status abstract="true" inherited="true" concrete="true"/>
        </commands>
        <commands name="Status" description="This command gets the device status (stored in its device_status data member) and returns it to the caller." execMethod="dev_status" displayLevel="OPERATOR" polledPeriod="0">
          <argin description="none">
            <type xsi:type="pogoDsl:VoidType"/>
          </argin>
          <argout description="Device status">
            <type xsi:type="pogoDsl:ConstStringType"/>
          </argout>
          <status abstract="true" inherited="true" concrete="true"/>
        </commands>
        <preferences docHome="./doc_html" makefileHome="/opt/homebrew/Caskroom/mambaforge/base/envs/pogo/share/pogo/preferences"/>
      </classes>
    </pogoDsl:PogoSystem>

C++
main.cpp

    /*----- PROTECTED REGION ID(MegaCoffee3k::main.cpp) ENABLED START -----*/
    /* clang-format on */
    //=============================================================================
    //
    // file :        main.cpp
    //
    // description : C++ source for the MegaCoffee3k device server main.
    //               The main rule is to initialize (and create) the Tango
    //               system and to create the DServerClass singleton.
    //               The main should be the same for every Tango device server.
    //
    // project :     Tango MegaCorp Coffee machines 3000 series
    //
    // This file is part of Tango device class.
    //
    // Tango is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Lesser General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // Tango is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Lesser General Public License for more details.
    //
    // You should have received a copy of the GNU Lesser General Public License
    // along with Tango.  If not, see <http://www.gnu.org/licenses/>.
    //
    //
    //
    //=============================================================================
    //                This file is generated by POGO
    //        (Program Obviously used to Generate tango Object)
    //=============================================================================
    #include <tango/tango.h>

    // Check if crash reporting is used.
    #if defined(ENABLE_CRASH_REPORT)
    #  include <crashreporting/crash_report.h>
    #else
    #  define DECLARE_CRASH_HANDLER
    #  define INSTALL_CRASH_HANDLER
    #endif

    DECLARE_CRASH_HANDLER

    int main(int argc,char *argv[])
    {
    	INSTALL_CRASH_HANDLER
    	Tango::Util *tg = nullptr;
    	try
    	{
    		// Initialize the device server
    		//----------------------------------------
    		tg = Tango::Util::init(argc,argv);

    		// Create the device server singleton
    		//	which will create everything
    		//----------------------------------------
    		tg->server_init(false);

    		// Run the endless loop
    		//----------------------------------------
    		std::cout << "Ready to accept request" << std::endl;
    		tg->server_run();
    	}
    	catch (std::bad_alloc &)
    	{
    		std::cout << "Can't allocate memory to store device object !!!" << std::endl;
    		std::cout << "Exiting" << std::endl;
    	}
    	catch (CORBA::Exception &e)
    	{
    		Tango::Except::print_exception(e);

    		std::cout << "Received a CORBA_Exception" << std::endl;
    		std::cout << "Exiting" << std::endl;
    	}

    	if(tg)
    	{
    		tg->server_cleanup();
    	}
    	return(0);
    }

    /* clang-format off */
    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k::main.cpp

MegaCoffee3kClass.h

MegaCoffee3kClass.cpp

MegaCoffee3k.h

MegaCoffee3k.cpp

MegaCoffee3kStateMachine.cpp

CMakeLists.txt

Pogo (Java)
MegaCoffee3k.xmi

    <?xml version="1.0" encoding="ASCII"?>
    <pogoDsl:PogoSystem xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:pogoDsl="http://tango.org/pogo/PogoDsl">
      <classes name="MegaCoffee3k" pogoRevision="9.9">
        <description description="" title="Tango MegaCorp Coffee machines 3000 series" sourcePath="/Users/antjou/tango-src/tango-doc/source/tutorial/src/java/01" language="Java" filestogenerate="XMI   file,Code files,Makefile,Protected Regions,pom.xml" license="LGPL" copyright="" hasMandatoryProperty="false" hasConcreteProperty="false" hasAbstractCommand="false" hasAbstractAttribute="false">
          <inheritances classname="Device_Impl" sourcePath=""/>
          <identification contact="at tango-megacorp.inc - software" author="software" emailDomain="tango-megacorp.inc" classFamily="Training" siteSpecific="" platform="All Platforms" bus="TCP/UDP" manufacturer="Tango MegaCorp" reference=""/>
        </description>
        <commands name="State" description="This command gets the device state (stored in its device_state data member) and returns it to the caller." execMethod="dev_state" displayLevel="OPERATOR" polledPeriod="0">
          <argin description="none">
            <type xsi:type="pogoDsl:VoidType"/>
          </argin>
          <argout description="Device state">
            <type xsi:type="pogoDsl:StateType"/>
          </argout>
          <status abstract="true" inherited="true" concrete="true"/>
        </commands>
        <commands name="Status" description="This command gets the device status (stored in its device_status data member) and returns it to the caller." execMethod="dev_status" displayLevel="OPERATOR" polledPeriod="0">
          <argin description="none">
            <type xsi:type="pogoDsl:VoidType"/>
          </argin>
          <argout description="Device status">
            <type xsi:type="pogoDsl:ConstStringType"/>
          </argout>
          <status abstract="true" inherited="true" concrete="true"/>
        </commands>
        <preferences docHome="./doc_html" makefileHome="/opt/homebrew/Caskroom/mambaforge/base/envs/pogo/share/pogo/preferences"/>
      </classes>
    </pogoDsl:PogoSystem>

Java
MegaCoffee3k.java

    /*----- PROTECTED REGION ID(MegaCoffee3k.java) ENABLED START -----*/
    //=============================================================================
    //
    // file :        MegaCoffee3k.java
    //
    // description : Java source for the MegaCoffee3k class and its commands.
    //               The class is derived from Device. It represents the
    //               CORBA servant object which will be accessed from the
    //               network. All commands which can be executed on the
    //               MegaCoffee3k are implemented in this file.
    //
    // project :     Tango MegaCorp Coffee machines 3000 series
    //
    // This file is part of Tango device class.
    //
    // Tango is free software: you can redistribute it and/or modify
    // it under the terms of the GNU Lesser General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // Tango is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU Lesser General Public License for more details.
    //
    // You should have received a copy of the GNU Lesser General Public License
    // along with Tango.  If not, see <http://www.gnu.org/licenses/>.
    //
    //
    //
    //=============================================================================
    //                This file is generated by POGO
    //        (Program Obviously used to Generate tango Object)
    //=============================================================================

    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.java

    package org.tango.megacoffee3k;

    /*----- PROTECTED REGION ID(MegaCoffee3k.imports) ENABLED START -----*/
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.slf4j.ext.XLogger;
    import org.slf4j.ext.XLoggerFactory;
    import org.tango.DeviceState;
    import org.tango.server.InvocationContext;
    import org.tango.server.ServerManager;
    import org.tango.server.annotation.AroundInvoke;
    import org.tango.server.annotation.Attribute;
    import org.tango.server.annotation.AttributeProperties;
    import org.tango.server.annotation.ClassProperty;
    import org.tango.server.annotation.Command;
    import org.tango.server.annotation.Delete;
    import org.tango.server.annotation.Device;
    import org.tango.server.annotation.DeviceProperty;
    import org.tango.server.annotation.DynamicManagement;
    import org.tango.server.annotation.Init;
    import org.tango.server.annotation.State;
    import org.tango.server.annotation.StateMachine;
    import org.tango.server.annotation.Status;
    import org.tango.server.annotation.DeviceManagement;
    import org.tango.server.annotation.Pipe;
    import org.tango.server.attribute.ForwardedAttribute;import org.tango.server.pipe.PipeValue;
    import org.tango.server.dynamic.DynamicManager;
    import org.tango.server.device.DeviceManager;
    import org.tango.server.dynamic.DynamicManager;
    import org.tango.server.events.EventManager;
    import org.tango.server.events.EventType;
    import org.tango.utils.DevFailedUtils;

    //	Import Tango IDL types
    import fr.esrf.Tango.*;
    import fr.esrf.TangoDs.Except;
    import fr.esrf.TangoApi.PipeBlob;
    import fr.esrf.TangoApi.PipeDataElement;

    /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.imports

    /**
     *  MegaCoffee3k class description:
     *
     */

    @Device
    public class MegaCoffee3k {

    	protected static final Logger logger = LoggerFactory.getLogger(MegaCoffee3k.class);
    	protected static final XLogger xlogger = XLoggerFactory.getXLogger(MegaCoffee3k.class);
    	//========================================================
    	//	Programmer's data members
    	//========================================================
        /*----- PROTECTED REGION ID(MegaCoffee3k.variables) ENABLED START -----*/

        //	Put static variables here

        /*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.variables
    	/*----- PROTECTED REGION ID(MegaCoffee3k.private) ENABLED START -----*/

    	//	Put private variables here

    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.private

    	//========================================================
    	//	Property data members and related methods
    	//========================================================

    	//========================================================
    	//	Miscellaneous methods
    	//========================================================
    	/**
    	 * Initialize the device.
    	 *
    	 * @throws DevFailed if something fails during the device initialization.
    	 */
    	@Init(lazyLoading = false)
    	public void initDevice() throws DevFailed {
    		xlogger.entry();
    		logger.debug("init device " + deviceManager.getName());
    		/*----- PROTECTED REGION ID(MegaCoffee3k.initDevice) ENABLED START -----*/

    		//	Put your device initialization code here

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.initDevice
    		xlogger.exit();
    	}

    	/**
    	 * all resources may be closed here. Collections may be also cleared.
    	 *
    	 * @throws DevFailed if something fails during the device object deletion.
    	 */
    	@Delete
    	public void deleteDevice() throws DevFailed {
    		xlogger.entry();
    		/*----- PROTECTED REGION ID(MegaCoffee3k.deleteDevice) ENABLED START -----*/

    		//	Put your device clearing code here

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.deleteDevice
    		xlogger.exit();
    	}

    	/**
    	 * Method called before and after command and attribute calls.
    	 * @param ctx the invocation context
    	 * @throws DevFailed if something fails during this method execution.
    	 */
    	@AroundInvoke
    	public void aroundInvoke(final InvocationContext ctx) throws DevFailed {
    		xlogger.entry();
    			/*----- PROTECTED REGION ID(MegaCoffee3k.aroundInvoke) ENABLED START -----*/

    			//	Put aroundInvoke code here

    			/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.aroundInvoke
    		xlogger.exit();
    	}

    	/**
    	 * dynamic command and attribute management. Will be injected by the framework.
    	 */
    	@DynamicManagement
    	protected DynamicManager dynamicManager;
    	/**
    	 * @param dynamicManager the DynamicManager instance
    	 * @throws DevFailed if something fails during this method execution.
    	 */
    	public void setDynamicManager(final DynamicManager dynamicManager) throws DevFailed {
    		this.dynamicManager = dynamicManager;
    		/*----- PROTECTED REGION ID(MegaCoffee3k.setDynamicManager) ENABLED START -----*/

    		//	Put your code here

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.setDynamicManager
    	}

    	/**
    	 * Device management. Will be injected by the framework.
    	 */
    	@DeviceManagement
    	DeviceManager deviceManager;
    	public void setDeviceManager(DeviceManager deviceManager){
    		this.deviceManager= deviceManager ;
    	}

    	//========================================================
    	//	Command data members and related methods
    	//========================================================
    	/**
    	 * The state of the device
    	*/
    	@State
    	private DevState state = DevState.UNKNOWN;
    	/**
    	 * Execute command "State".
    	 * description: This command gets the device state (stored in its 'state' data member) and returns it to the caller.
    	 * @return Device state
    	 * @throws DevFailed if command execution failed.
    	 */
    	public final DevState getState() throws DevFailed {
    		/*----- PROTECTED REGION ID(MegaCoffee3k.getState) ENABLED START -----*/

    		//	Put state code here

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.getState
    		return state;
    	}
    	/**
    	 * Set the device state
    	 * @param state the new device state
    	 */
    	public void setState(final DevState state) {
    		this.state = state;
    	}

    	/**
    	 * The status of the device
    	 */
    	@Status
    	private String status = "Server is starting. The device state is unknown";
    	/**
    	 * Execute command "Status".
    	 * description: This command gets the device status (stored in its 'status' data member) and returns it to the caller.
    	 * @return Device status
    	 * @throws DevFailed if command execution failed.
    	 */
    	public final String getStatus() throws DevFailed {
    		/*----- PROTECTED REGION ID(MegaCoffee3k.getStatus) ENABLED START -----*/

    		//	Put status code here

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.getStatus
    		return status;
    	}
    	/**
    	 * Set the device status
    	 * @param status the new device status
    	 */
    	public void setStatus(final String status) {
    		this.status = status;
    	}

    	//========================================================
    	//	Programmer's methods
    	//========================================================
    	/*----- PROTECTED REGION ID(MegaCoffee3k.methods) ENABLED START -----*/

    	//	Put your own methods here

    	/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.methods

    	/**
    	 * Starts the server.
    	 * @param args program arguments (instance_name [-v[trace level]]  [-nodb [-dlist <device name list>] [-file=fileName]])
    	 */
    	public static void main(final String[] args) {
    		/*----- PROTECTED REGION ID(MegaCoffee3k.main) ENABLED START -----*/

    		/*----- PROTECTED REGION END -----*/	//	MegaCoffee3k.main
    		ServerManager.getInstance().start(args, MegaCoffee3k.class);
    		System.out.println("------- Started -------------");
    	}
    }

Here the `set_state` and `set_status` methods have been used to modify the device’s state and status on startup.

If you run this example, and in a second terminal, use the device proxy client to check if it is working :

    >>> dp.State()
    tango._tango.DevState.OFF
    >>> dp.Status()
    'Hello world - device is off.'
    >>>

It works, super easy!

Tip

It is good practice to set the state and status values at the same time, since clients will often read both of them, and they need to agree.

The `DevState` enum has many values, including `STANDBY`, `ON`, `RUNNING`, `ALARM`, and `FAULT`.

But what was that `init_device` method? It is part of the device implementation class, and it is called automatically when the device starts up. You’ll learn more about it soon.

#### Init and Delete Device

The basic lifecycle of a Tango device can be simplified into two parts: initialisation and clean-up. This happens within the operating system process that is called the device server.

As you saw in the previous lesson there is an `init_device()` method. Your device can also have a `delete_device()` method. Both of these override the default implementation in the base class `Device`.

Python
main.py

    from tango.server import Device

    class MegaCoffee3k(Device):

        def __init__(self, *args, **kwargs):
            print("MegaCoffee3k: __init__ start")
            super().__init__(*args, **kwargs)
            print("MegaCoffee3k: __init__ end")

        def init_device(self):
            super().init_device()
            print("MegaCoffee3k: init_device")

        def delete_device(self):
            print("MegaCoffee3k: delete_device")
            super().delete_device()

    if __name__ == "__main__":
        MegaCoffee3k.run_server()

C++

Sorry, still TODO!

Java

Sorry, still TODO!

Here the `init_device` and `delete_device` methods simply print a message. The`__init__` method is included with some print statements to show when it is called in the lifecycle.

Tip

It is important to still call the super class methods, as shown. `init_device` at the start of your method, and `delete_device` at the end of your method.

Bonus tip: `__init__`

Device methods that use the Tango “machinery” cannot be called before `Device.__init__` has completed. E.g., the logging methods like `info_stream`, and things like `push_change_event`, which you’ll learn about later.

Run this example server, and you’ll see the output:

    MegaCoffee3k: __init__ start
    MegaCoffee3k: init_device
    MegaCoffee3k: __init__ end
    Ready to accept request
    MegaCoffee3k started on port 8888 with properties {}
    Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
    Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no

The “init_device” message is printed before the device is ready to accept requests. In this Python example, the `init_device` call came from the base class constructor, `Device.__init__`.

In a second terminal, use the device proxy client and send the built-in `Init()` command, which will re-initialise the device.

    >>> dp.Init()
    >>>

    MegaCoffee3k: __init__ start
    MegaCoffee3k: init_device
    MegaCoffee3k: __init__ end
    Ready to accept request
    MegaCoffee3k started on port 8888 with properties {}
    Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
    Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
    MegaCoffee3k: delete_device
    MegaCoffee3k: init_device

To re-initialise the MegaCoffee3k device, Tango first calls the `delete_device` method, and then the `init_device` method. The constructor, `__init__`, is not called, so you still have the same Python object instance.

Finally, use the keyboard combination `Ctrl`+`C` to end the Tango device server application.

    MegaCoffee3k: __init__ start
    MegaCoffee3k: init_device
    MegaCoffee3k: __init__ end
    Ready to accept request
    MegaCoffee3k started on port 8888 with properties {}
    Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
    Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
    MegaCoffee3k: delete_device
    MegaCoffee3k: init_device
    ^CMegaCoffee3k: delete_device
    Done

You can see that `delete_device` method was called again during the graceful shutdown.

Tip

Variables needed for the device state are normally initialised in `init_device` rather than in `__init__`, since `init_device` gets called again when the `Init()` command is used.

Tip

If any system resources (memory, connections, file handles, etc.) need to be released when the device shuts down, this kind of clean-up is usually done in `delete_device`.

##### Restarting the device

Using the admin device, you can restart the device completely, while the device server, i.e., operating system process, remains running. This is typically done less often than calling `Init()` on the device.

Start your example device server again.

Create another client, this time using the Server access Tango resource locator. Send the built-in `DevRestart()` command, with the name of the device: `test/nodb/megacoffee3k`.

    >>> import tango
    >>> ap = tango.DeviceProxy("tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no")
    >>> ap.DevRestart("test/nodb/megacoffee3k")
    >>>

You’ll see the following output:

    MegaCoffee3k: __init__ start
    MegaCoffee3k: init_device
    MegaCoffee3k: __init__ end
    Ready to accept request
    MegaCoffee3k started on port 8888 with properties {}
    Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
    Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
    MegaCoffee3k: delete_device
    MegaCoffee3k: __init__ start
    MegaCoffee3k: init_device
    MegaCoffee3k: __init__ end

This time you notice a second call to `__init__`, so a new instance of the Python object representing the device has been created.

Bonus tip: `RestartServer`

The admin device also has a `RestartServer()` command than can be used to restart all the devices in a server, without naming a specific one. In this example `ap.RestartServer()`.

##### Summary

There are various ways of restarting/re-initialising a Tango device. From least to most drastic:

What? | How? | Process | Object
--- | --- | --- | ---
Re-initialise device | Send `Init()` command to device | Unchanged | Unchanged
Re-start device | Send `DevRestart()` or `RestartServer()` to admin device | Unchanged | New
Re-start device server | Terminate operating system process and run it again | New | New

#### About and version info

Tango devices are discoverable on the network, and their APIs can be queried. They can also provide general information about themselves that is easily accessible by clients. You know that source version info is important for debugging issues in production! Luckily, it is super easy to add extra details to your Tango device.

Python
main.py

    from tango.server import Device

    __version__ = "0.1.0"

    class MegaCoffee3k(Device):

        def init_device(self):
            super().init_device()
            self.add_version_info("MegaCoffee3k.Name", "MegaCoffee3k Tango device")
            self.add_version_info("MegaCoffee3k.Source", __file__)
            self.add_version_info("MegaCoffee3k.Version", __version__)
            self.add_version_info(
                "MegaCoffee3k.Repo",
                "https://gitlab.tango-mega-corp.com/controls/dev-tmc-megacoffee3k",
            )

    if __name__ == "__main__":
        MegaCoffee3k.run_server()

C++

Sorry, still TODO!

Java

Sorry, still TODO!

The `add_version_info()` method can be called as many times as you like to add key-value pairs of strings.

If you run this example, and in a second terminal, use the device proxy client to read back the information:

    >>> dp.info()
    DeviceInfo(dev_class = 'MegaCoffee3k', dev_type = 'MegaCoffee3k', doc_url = 'Doc URL = http://www.tango-controls.org', server_host = 'my.computer', server_id = 'MegaCoffee3k/MegaCoffee3k', server_version = 6, version_info = {'Build.PyTango.NumPy': '2.2.3', 'Build.PyTango.Pybind11': '2.13.6', 'Build.PyTango.Python': '3.13.2', 'Build.PyTango.cppTango': '10.0.2', 'MegaCoffee3k.Name': 'MegaCoffee3k Tango device', 'MegaCoffee3k.Repo': 'https://gitlab.tango-mega-corp.com/controls/dev-tmc-megacoffee3k', 'MegaCoffee3k.Source': '/path/to/tango-tut/src/04-version-info/python/main.py', 'MegaCoffee3k.Version': '0.1.0', 'NumPy': '2.2.3', 'PyTango': '10.1.0rc2', 'Python': '3.13.2', 'cppTango': '10.0.2', 'cppTango.git_revision': 'unknown', 'cppzmq': '41000', 'idl': '6.0.2', 'omniORB': '4.3.2', 'opentelemetry-cpp': '1.18.0', 'zmq': '40305'})
    >>> print(dp.info())
    DeviceInfo[
        dev_class = "MegaCoffee3k"
        dev_type = "MegaCoffee3k"
        doc_url = "Doc URL = http://www.tango-controls.org"
        server_host = "my.computer"
        server_id = "MegaCoffee3k/MegaCoffee3k"
        server_version = 6
        version_info = {
            "Build.PyTango.NumPy": "2.2.3",
            "Build.PyTango.Pybind11": "2.13.6",
            "Build.PyTango.Python": "3.13.2",
            "Build.PyTango.cppTango": "10.0.2",
            "MegaCoffee3k.Name": "MegaCoffee3k Tango device",
            "MegaCoffee3k.Repo": "https://gitlab.tango-mega-corp.com/controls/dev-tmc-megacoffee3k",
            "MegaCoffee3k.Source": "/path/to/tango-tut/src/04-version-info/python/main.py",
            "MegaCoffee3k.Version": "0.1.0",
            "NumPy": "2.2.3",
            "PyTango": "10.1.0rc2",
            "Python": "3.13.2",
            "cppTango": "10.0.2",
            "cppTango.git_revision": "unknown",
            "cppzmq": "41000",
            "idl": "6.0.2",
            "omniORB": "4.3.2",
            "opentelemetry-cpp": "1.18.0",
            "zmq": "40305"
        }
    ]

Using `print` gives us a much more readable ouput of the data structure.

Bonus tip: getting project URL in Python

Given `pyproject.toml` with:

    [project]
    name = "tangods-megacoffee3k"

    [project.urls]
    Source = "https://gitlab.tango-mega-corp.com/controls/dev-tmc-megacoffee3k"

This code can fetch the URL at runtime:

    import importlib.metadata

    def _get_repo_info_from_package() -> str:
        try:
            metadata = importlib.metadata.metadata("tangods-megacoffee3k")
            repo_url = (
                metadata["Project-URL"].split(" ")[1]
                if "Project-URL" in metadata
                else "Unknown"
            )
        except importlib.metadata.PackageNotFoundError:
            repo_url = "Unknown"
        return repo_url

Next up, you’ll add a command so the device can finally do something!

#### Commands

You are going to need your coffee machines to make coffee on demand, so you’ll need a way to tell the Tango device to do something. In Tango, an action is triggered using a command.

For starters, here are some very simple commands. Commands have a name, an optional input parameter, and an optional return value. Multiple parameters are not supported, and neither are complex types.

Python
main.py

    from tango.server import Device, command

    class MegaCoffee3k(Device):

        @command
        def Brew(self):
            print("brewing coffee! (but nobody knows)")

        @command
        def BrewNoName(self) -> str:
            return "brewing coffee for someone!"

        @command
        def BrewName(self, name: str) -> str:
            return f"brewing coffee for {name}!"

        @command
        def BrewNames(self, names: list[str]) -> list[str]:
            return [f"brewing coffee for {name}!" for name in names]

        @command(doc_in="Name of coffee drinker", doc_out="Order response")
        def BrewNameDoc(self, name: str) -> str:
            return f"brewing coffee for {name}!"

        @command(
            dtype_in=str,
            doc_in="Name of coffee drinker",
            dtype_out=str,
            doc_out="Order response",
        )
        def BrewNameDocDtype(self, name):
            return f"brewing coffee for {name}!"

    if __name__ == "__main__":
        MegaCoffee3k.run_server()

C++

Sorry, still TODO!

Java

Sorry, still TODO!

In Python, you need to import `command()` and then use that to decorate a method on the `Device`. In other languages, it is a little more complicated.

You have the following commands:

- `Brew` has no input or output parameters.

- `BrewNoName` has no input, but returns a string.

- `BrewName` accepts an input string and returns a string.

- `BrewNames` accepts a list of strings and returns a list of strings.

- `BrewNameDoc` shows how the input and output parameters can be documented. There isn’t a way to document the command itself.

- `BrewNameDocDtype` is the same as the previous, but shows an alternative (older) way of declaring the types.

The command names use capitalisation as per the Tango Naming Rules.

Tip

Some names are bad choice for commands:

- `Init`, `State`, and `Status` commands already exist

- Methods that already exist on the `DeviceProxy` class, including: `alias`, `connect`, `description`, `info`, `lock`, `name`, `ping`, `reconnect`, `unlock`, `get_...`, `set_...`, `is_...`, `put_`, `read_...`, `write_...`, etc.

- Anything starting with an underscore, `_`.

Run this example, and in a second terminal, use the device proxy client to check if it is working:

    >>> dp.Brew()  # nothing on client, but server will print a message
    >>> dp.BrewNoName()
    'brewing coffee for someone!'
    >>> dp.BrewName("Java01")
    'brewing coffee for Java01!'
    >>> dp.BrewNames(["I", "need", "coffee"])
    ['brewing coffee for I!', 'brewing coffee for need!', 'brewing coffee for coffee!']

Calling the command as a function is a convenience provided by the `DeviceProxy` object. You can also use the more low-level `command_inout()` method:

    >>> dp.command_inout("BrewNoName")
    'brewing coffee for someone!'
    >>> dp.command_inout("BrewName", "Java01")
    'brewing coffee for Java01!'

Tango is case insensitive when accessing commands by name, so all of the following calls access the same command:

    >>> dp.BrewNoName()
    'brewing coffee for someone!'
    >>> dp.brewnoname()
    'brewing coffee for someone!'
    >>> dp.command_inout("brewNONAME")
    'brewing coffee for someone!'

You can also see how the documentation is available to the client:

    >>> help(dp.BrewNameDoc)
    # shows:

    Help on function f in module tango.device_proxy:

    f(*args, **kwds)
        BrewNameDoc(DevString) -> DevString

        -  in (DevString): Name of coffee drinker
        - out (DevString): Order response

    >>> print(dp.get_command_config("BrewNameDoc"))
    CommandInfo[
        cmd_name = "BrewNameDoc"
        cmd_tag = 0
        disp_level = tango._tango.DispLevel.OPERATOR
        in_type = tango._tango.CmdArgType.DevString
        in_type_desc = "Name of coffee drinker"
        out_type = tango._tango.CmdArgType.DevString
        out_type_desc = "Order response"
    ]

    >>> print(dp.get_command_config("BrewNameDocDtype"))
    CommandInfo[
        cmd_name = "BrewNameDocDtype"
        cmd_tag = 0
        disp_level = tango._tango.DispLevel.OPERATOR
        in_type = tango._tango.CmdArgType.DevString
        in_type_desc = "Name of coffee drinker"
        out_type = tango._tango.CmdArgType.DevString
        out_type_desc = "Order response"
    ]

    >>> print(dp.get_command_config("BrewName"))
    CommandInfo[
        cmd_name = "BrewName"
        cmd_tag = 0
        disp_level = tango._tango.DispLevel.OPERATOR
        in_type = tango._tango.CmdArgType.DevString
        in_type_desc = "Uninitialised"
        out_type = tango._tango.CmdArgType.DevString
        out_type_desc = "Uninitialised"
    ]

The `get_command_config()` method provides all the details about a command.

To simplify the implementation of all clients and servers, the data types available to commands are limited:

- simple types: integer, float, string, boolean

- lists of simple types

- special structures:

  - a list of numbers combined with a list of strings: `DevVarDoubleStringArray` and `DevVarLongStringArray`

  - an encoded byte array, with string indicating the format: `DevEncoded`

Tip

For more complicated input and output data structures, it is common to use a string that is serialised and de-serialised using JSON. This allows structures like dicts to be passed between client and server. The downside is that the schema of those dicts is not obvious.

Tip

You can easily get a list of all the commands a Tango device offers:

    >>> dp.get_command_list()
    ['Brew', 'BrewName', 'BrewNameDoc', 'BrewNameDocDtype', 'BrewNames', 'BrewNoName', 'Init', 'State', 'Status']

State and Status are special, and show up as commands and attributes. Normally we access them as commands. Init is a built-in command.

Note

For PyTango, here is the full list of the data types.

Tango does not support 2-D arrays (images) for commands. PyTango does not support enumerated types (`DevEnum`) for commands.

So far, so good. Commands were easy, next up: attributes.

#### Attributes

Running out of coffee is dangerous! You need to monitor the level of beans. And to get the perfect cup, you’ll tweak the brewing temperature and coarseness of the grind. In Tango, reading and writing a value like that is done with an attribute.

Attributes have a name and a data type. They can be read-only, read/write or write-only. Similar to commands, there is a pre-defined list of data types, so arbitrarily complex types are not supported.

Python
main.py

    from enum import IntEnum

    from tango import AttrWriteType
    from tango.server import Device, attribute

    class GrinderSetting(IntEnum):
        FINE = 0
        MEDIUM = 1
        COARSE = 2

    class MegaCoffee3k(Device):

        def init_device(self):
            super().init_device()
            self._water_level = 54.2
            self._bean_levels = [82.5, 100.0]
            self._brewing_temperature = 94.4
            self._grind = GrinderSetting.FINE

        # decorator-style attributes -------------

        @attribute
        def waterLevel(self) -> float:
            print("reading water level")
            return self._water_level

        @attribute(max_dim_x=2)
        def beanLevels(self) -> list[float]:
            print("reading bean levels")
            return self._bean_levels

        @attribute(
            dtype=[float], max_dim_x=2, doc="How full is each bean dispenser", unit="%"
        )
        def beanLevelsDoc(self):
            return self._bean_levels

        @attribute
        def grind(self) -> GrinderSetting:
            """Setting for the coffee bean grinder"""
            print("reading grinder setting")
            return self._grind

        @grind.setter
        def grind(self, value: int):
            self._grind = GrinderSetting(value)
            print(f"Set grinder to {self._grind}")

        # non-decorator style attributes -------------

        brewingTemperature = attribute(access=AttrWriteType.READ_WRITE, doc="Temperature to brew coffee at [deg C]")

        def read_brewingTemperature(self) -> float:
            return self._brewing_temperature

        def write_brewingTemperature(self, temperature: float):
            self._brewing_temperature = temperature

    if __name__ == "__main__":
        MegaCoffee3k.run_server()

C++

Sorry, still TODO!

Java

Sorry, still TODO!

In Python, you need to import `attribute` and then use that to decorate a method on the `Device`, or use it to create objects (non-decorator syntax), and link those to methods. In other languages, it is a little more complicated.

You have the following attributes:

- `waterLevel` a read-only float. It is a scalar value (in other words, zero-dimensional, or 0-D).

- `beanLevels` is also read-only, but returns a list of up to two floats. In Tango 1-D arrays are called spectrum attributes, and 2-D arrays are image attributes.

- `beanLevelsDoc` shows how documentation and the units can be defined.

- `grind` is a read-write, scalar attribute using an enumeration data type. There is one method handling reading, and one handling writing. The docstring for the read method is an alternative to the `doc` keyword argument.

- `brewingTemperature` is defined using assignment rather than decorator syntax. It is a read-write scalar float. The pattern for naming the methods is critical: `read_` and `write_`, followed by the attribute name.

The attribute names use capitalisation as per the Tango Naming Rules.

Tip

Some names are bad choice for attributes:

- `Init`, `State`, and `Status` already exist as commands.

- Methods that already exist on the `DeviceProxy` class, including: `alias`, `connect`, `description`, `info`, `lock`, `name`, `ping`, `reconnect`, `unlock`, `get_...`, `set_...`, `is_...`, `put_`, `read_...`, `write_...`, etc.

- Anything starting with an underscore, `_`.

Run this example, and in a second terminal, use the device proxy client to check if it is working :

    >>> dp.waterLevel
    54.2
    >>> dp.beanLevels
    array([ 82.5, 100. ])
    >>> dp.grind
    <grind.FINE: 0>
    >>> dp.grind = "MEDIUM"
    >>> dp.grind
    <grind.MEDIUM: 1>
    >>> dp.brewingTemperature
    94.4
    >>> dp.brewingTemperature = 95.1
    >>> dp.brewingTemperature
    95.1

Getting the value by reading the attribute name directly on the `DeviceProxy` object is a convenience. There is actually a lot more information available when an attribute is read. You can also use the more low-level `read_attribute()` and `write_attribute()` methods to see the full data structure:

    >>> reading = dp.read_attribute("waterLevel")
    >>> print(reading)
    DeviceAttribute[
        data_format = tango._tango.AttrDataFormat.SCALAR
        dim_x = 1
        dim_y = 0
        has_failed = False
        is_empty = False
        name = "waterLevel"
        nb_read = 1
        nb_written = 0
        quality = tango._tango.AttrQuality.ATTR_VALID
        r_dimension = AttributeDimension[
            dim_x = 1
            dim_y = 0
        ]
        time = TimeVal(tv_nsec = 0, tv_sec = 1743749828, tv_usec = 308868)
        type = tango._tango.CmdArgType.DevDouble
        value = 54.2
        w_dim_x = 0
        w_dim_y = 0
        w_dimension = AttributeDimension[
            dim_x = 0
            dim_y = 0
        ]
        w_value = None
    ]

    >>> reading.value
    54.2
    >>> dp.write_attribute("brewingTemperature", 94.9)
    >>> dp.brewingTemperature
    94.9

As a high-level convenience, you can also get the struct using indexed access in Python:

    >>> reading = dp["waterLevel"]
    >>> print(reading)
    DeviceAttribute[
        data_format = tango._tango.AttrDataFormat.SCALAR
        dim_x = 1
        dim_y = 0
        has_failed = False
        is_empty = False
        name = "waterLevel"
        nb_read = 1
        nb_written = 0
        quality = tango._tango.AttrQuality.ATTR_VALID
        r_dimension = AttributeDimension[
            dim_x = 1
            dim_y = 0
        ]
        time = TimeVal(tv_nsec = 0, tv_sec = 1743749877, tv_usec = 460608)
        type = tango._tango.CmdArgType.DevDouble
        value = 54.2
        w_dim_x = 0
        w_dim_y = 0
        w_dimension = AttributeDimension[
            dim_x = 0
            dim_y = 0
        ]
        w_value = None
    ]

Tango is case insensitive when accessing attributes by name, so all of the following calls access the same attribute:

    >>> dp.brewingTemperature
    95.1
    >>> dp.BREWINGTemperature
    95.1
    >>> dp.brewingtemperature
    95.1
    >>> dp.read_attribute("BreWingTemPeraTure").value
    95.1
    >>> dp["BreWingTemPeraTure"].value
    95.1

The documentation for each attribute is available to the client:

    >>> dp.get_attribute_config("beanLevels").description
    'No description'
    >>> dp.get_attribute_config("beanLevelsDoc").description
    'How full is each bean dispenser'
    >>> dp.get_attribute_config("grind").description
    'Setting for the coffee bean grinder'

The `get_attribute_config()` method provides all the details about an attribute:

    >>> config = dp.get_attribute_config("brewingTemperature")
    >>> print(config)
    AttributeInfoEx[
        alarms = AttributeAlarmInfo[
            delta_t = "Not specified"
            delta_val = "Not specified"
            extensions = []
            max_alarm = "Not specified"
            max_warning = "Not specified"
            min_alarm = "Not specified"
            min_warning = "Not specified"
        ]
        data_format = tango._tango.AttrDataFormat.SCALAR
        data_type = tango._tango.CmdArgType.DevDouble
        description = "Temperature to brew coffee at [deg C]"
        disp_level = tango._tango.DispLevel.OPERATOR
        display_unit = "No display unit"
        enum_labels = []
        events = AttributeEventInfo[
            arch_event = ArchiveEventInfo[
                archive_abs_change = "Not specified"
                archive_period = "Not specified"
                archive_rel_change = "Not specified"
                extensions = []
            ]
            ch_event = ChangeEventInfo[
                abs_change = "Not specified"
                extensions = []
                rel_change = "Not specified"
            ]
            per_event = PeriodicEventInfo[
                extensions = []
                period = "1000"
            ]
        ]
        extensions = []
        format = "%6.2f"
        label = "brewingTemperature"
        max_alarm = "Not specified"
        max_dim_x = 1
        max_dim_y = 0
        max_value = "Not specified"
        memorized = tango._tango.AttrMemorizedType.NONE
        min_alarm = "Not specified"
        min_value = "Not specified"
        name = "brewingTemperature"
        root_attr_name = "Not specified"
        standard_unit = "No standard unit"
        sys_extensions = []
        unit = ""
        writable = tango._tango.AttrWriteType.READ_WRITE
        writable_attr_name = "brewingTemperature"
    ]

To simplify the implementation of all clients and servers, the data types available to attributes are limited:

- simple types: integer, float, string, boolean, enum

- 1-D lists of simple types (spectrum)

- 2-D lists of simple types (image)

- special structures:

  - an encoded byte array, with string indicating the format: `DevEncoded`

Tip

For more examples of using Python type hints for declaring attributes see device server type hints.

Tip

For more complicated input and output data structures, it is common to use a string that is serialised and de-serialised using JSON. This allows structures like dicts to be passed between client and server. The downside is that the schema of those dicts is not obvious.

Tip

You can easily get a list of all the attributes a Tango device offers:

    >>> dp.get_attribute_list()
    ['waterLevel', 'beanLevels', 'beanLevelsDoc', 'grind', 'brewingTemperature', 'State', 'Status']

State and Status are special, and show up as commands and attributes. Normally we access them as commands.

Note

Python limits/simplifies the data types that can be used, compared to C++ and Java. For PyTango, here is the full list of the data types.

You’ve already learned quite a lot of the basics! Next up is a way to configure persistent settings.

#### Device properties

You are definitely going to need more than one coffee machine to satisfy all your colleagues! With one Tango device per coffee machine, you’ll need to provided network connection details to use for each one. You know that hard-coding that information in your source files is a bad idea, so how can you make it configurable? The answer is to use a property in Tango. There are a few different types, but we’ll use a device property.

The values of the device properties are stored in the Tango Database, so they can persist. Every time the device starts up, it reads the property values from the database. You’ll learn more about the database later.

Python
main.py

    from tango.server import Device, device_property

    class MegaCoffee3k(Device):

        host: str = device_property(mandatory=True)
        port: int = device_property(default_value=9788)
        location: str = device_property()

        def init_device(self):
            print(f"init_device before super: {self.host}:{self.port} @ {self.location}")
            super().init_device()
            print(f"init_device after super: {self.host}:{self.port} @ {self.location}")

    if __name__ == "__main__":
        MegaCoffee3k.run_server()

C++

Sorry, still TODO!

Java

Sorry, still TODO!

In Python, you import `device_property` and use it to define you properties.

You’ve defined three device properties:

- The host name, a string with no default, and `mandatory` set to true, so it has to be configured.

- The port number, an integer with default. Most machines will use the same TCP port.

- The location at MegaCorp, an optional string with no default.

Now when you run the Tango device server, using PyTango’s `test_context` utility we will get an error:

    (tango-tut) $ python -m tango.test_context main.MegaCoffee3k --host 127.0.0.1

    init_device before super: None:None @ None
    Traceback (most recent call last):
    ...
      File "/path/to/pytango/tango/server.py", line 1694, in tango_loop
        util.server_init()
        ~~~~~~~~~~~~~~~~^^
    tango._tango.PyTango.DevFailed: DevFailed[
        DevError[
            desc = Exception: Device property host is mandatory
            origin = Traceback (most recent call last):
                  File "/path/to/pytango/tango/device_class.py", line 648, in __DeviceClass__device_factory
                    device = self._new_device(deviceImplClass, klass, dev_name)
                  File "/path/to/pytango/tango/device_class.py", line 627, in __DeviceClass__new_device
                    return klass(dev_class, dev_name)
                  File "/path/to/pytango/tango/server.py", line 806, in __init__
                    self.init_device()
                    ~~~~~~~~~~~~~~~~^^
                  File "/path/to/pytango/tango/server.py", line 404, in init_device
                    return worker.execute(init_device_orig, self)
                           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
                  File "/path/to/pytango/tango/green.py", line 105, in execute
                    return fn(*args, **kwargs)
                  File "/path/to/tango-tut/src/07-device-properties/python/main.py", line 12, in init_device
                    super().init_device()
                    ~~~~~~~~~~~~~~~~~~~^^
                  File "/path/to/pytango/tango/server.py", line 404, in init_device
                    return worker.execute(init_device_orig, self)
                           ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
                  File "/path/to/pytango/tango/green.py", line 105, in execute
                    return fn(*args, **kwargs)
                  File "/path/to/pytango/tango/server.py", line 825, in init_device
                    self.get_device_properties()
                    ~~~~~~~~~~~~~~~~~~~~~~~~~~^^
                  File "/path/to/pytango/tango/server.py", line 877, in get_device_properties
                    raise Exception(msg)
                Exception: Device property host is mandatory
            reason = PyDs_PythonError
            severity = ErrSeverity.ERR
        ]
    ]

You don’t have a real database to store the property values yet, but they can be provided on the command line when launching the device server via the `--prop` argument:

    (tango-tut) $ python -m tango.test_context main.MegaCoffee3k --prop '{"host": "localhost"}' --host 127.0.0.1

    init_device before super: None:None @ None
    init_device after super: localhost:9788 @ None
    Ready to accept request
    MegaCoffee3k started on port 8888 with properties {'host': 'localhost'}
    Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
    Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no

This time you see no errors. The printout shows that before the `super().init_device()` call the property values were `None`, but after the call, you have the expected values: `"localhost"`, which you provided, and the default port number of `9788`. The location wasn’t provided, so it still has the value `None`.

Now you provide the location as well:

    (tango-tut) $ python -m tango.test_context main.MegaCoffee3k --prop '{"host": "localhost", "location": "control room"}' --host 127.0.0.1

    init_device before super: None:None @ None
    init_device after super: localhost:9788 @ control room
    Ready to accept request
    MegaCoffee3k started on port 8888 with properties {'host': 'localhost', 'location': 'control room'}
    Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
    Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no

Next up, you’ll look into using an external Tango database for persisting and configuring these values.

#### Running a Tango Database

So far, we have been running our Tango device server, using PyTango’s `test_context` utility to run with `nodb`. This is very useful during development but has some limitations. You can’t interact with your server using jive for example.

The official Tango Database is written in C++ and requires MariaDB as backend.

PyTango contains its own Database implementation, which should work as a drop-in replacement. It stores data using SQLite so it has no external dependencies, making it easier to run. This is what we will use in this tutorial.

Warning

This implementation is in an experimental state, and has not been extensively tested for compatibility or for performance. Don’t use it for anything mission critical!

##### Running PyTango Database Device Server

Using our existing environment with pytango 10:

    (tango-tut) $ TANGO_HOST=127.0.0.1:10000 python -m tango.databaseds.database 2
    Ready to accept request

That’s it!

If you already have a Databaseds running on port 10000, you can pick another port (like 11000).

Note

The previous command will create a file named `tango_database.db` in the current directory. This is the file containing the SQLite database. You can change the name of that file with the `PYTANGO_DATABASE_NAME` environment variable.

Tip

As we use SQLite, you can easily store the database in memory instead of the ordinary disk file. Set `PYTANGO_DATABASE_NAME` variable to the special filename `:memory:`.

Bonus tip: `pytango-db`

The PyTango Database Device Server code has been forked to a separate repository for easier development. It makes it easy to use even if you don’t work with pytango 10.

Install `pytango-db` globally with pixi:

    $ pixi global install pytango-db
    Global environments as specified in '~/.pixi/manifests/pixi-global.toml'
    └── pytango-db: 0.3.0 (installed)
        └─ exposes: PyDatabaseds

In a terminal, run:

    $ TANGO_HOST=127.0.0.1:10000 PyDatabaseds 2
    Ready to accept request

##### Setting TANGO_HOST

Any Tango client or server needs to know where the database is running. It won’t run otherwise.

This information shall be defined via the TANGO_HOST environment variable. You can export this variable in your shell or define it in a file (`$HOME/.tangorc` or `/etc/tangorc`). See the reference on environment variables for more information.

In this tutorial, we’ll export it in our environment using `export TANGO_HOST=127.0.0.1:10000` on macOS and Linux or `set TANGO_HOST=127.0.0.1:10000` on Windows.

You can set it before of after running `pixi shell`. If you start a new terminal, make sure to define it again (if you prefer you can use your `.bashrc` / `.zshrc` or `.tangorc` file).

    (tango-tut) $ export TANGO_HOST=127.0.0.1:10000

You can check at any time that it is properly set by printing it:

    (tango-tut) $ echo $TANGO_HOST
    127.0.0.1:10000

##### Checking the Database Device Server

###### Using pytango from the command line

Start the python interpreter under the pixi shell.

Warning

Make sure the `TANGO_HOST` variable is defined in your environment before to start python.

    (tango-tut) $ python

To interact with a Tango Database from PyTango, you use the tango.Database class, which provides methods for all database commands.

Try it and query the database for some general information about the tables:

    >>> import tango
    >>> db = tango.Database()
    >>> print(db.get_info())
    TANGO Database tango_database.db

    Running since ...2025-04-10 13:17:37

    Devices defined = 8
    Devices exported = 2
    Device servers defined = 4
    Device servers exported = 1

    Device properties defined = 0 [History lgth = 0]
    Class properties defined = 53 [History lgth = 0]
    Device attribute properties defined = 0 [History lgth = 0]
    Class attribute properties defined = 0 [History lgth = 0]
    Object properties defined = 0 [History lgth = 0]

If this worked, you connected properly to a Tango Database! Otherwise, see the troubleshooting tip below.

Troubleshooting errors when instantiating tango.Database()

If you forgot to set the `TANGO_HOST` variable or set it to an invalid value, instantiating `tango.Database()` will raise an exception:

    >>> db = tango.Database()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/Users/username/tango-tut/.pixi/envs/default/lib/python3.12/site-packages/tango/db.py", line 92, in __Database____init__
        return Database.__init_orig__(self, *args, **kwargs)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    PyTango.DevFailed: DevFailed[
    DevError[
        desc = TANGO_HOST env. variable not set, set it and retry (e.g. TANGO_HOST=<host>:<port>)
      origin = Tango::Database::Database(CORBA::ORB_var) at (/Users/runner/miniforge3/conda-bld/cpptango_1742400675684/work/src/client/dbapi_base.cpp:78)
      reason = API_TangoHostNotSet
    severity = ERR]

To use `tango.Database()` without arguments, make sure to export the `TANGO_HOST` variable.

You can also pass the hostname and port directly to the `Database()` class.

    >>> db = tango.Database("127.0.0.1", 10000)

Many more commands are available. You can for example list the servers registered in the database:

    >>> db.get_server_list()
    DbDatum(name = 'server', value_string = ['DataBaseds/2', 'DataBaseds/pydb-test', 'TangoAccessControl/1', 'TangoTest/test'])

See the Database API for more information.

###### Using Jive

Another way to interact with the Tango Database is using Jive.

Jive is a java application and it’s already installed in our pixi environment for this tutorial.

Bonus tip: Install Jive globally

As Jive is a very common tool and standalone java application, you might want to install it globally to not depend on a specific environment. You can use `pixi global` for that purpose.

    $ pixi global install jive
    Global environments as specified in '/Users/username/.pixi/manifests/pixi-global.toml'
    └── jive: 7.45 (installed)
        └─ exposes: jive

That will make the `jive` command available globally, without having to activate any environment.

As before with PyTango, to run `jive`, the `TANGO_HOST` variable must already be set in your environment!

    (tango-tut) $ jive

You should be able to see the predefined servers in the database: `DataBaseds`, `TangoAccessControl` and `TangoTest`.

You can access `sys/database/2`, but `sys/tg_test/1` isn’t exported because `TangoTest` isn’t running.

Troubleshooting errors when starting Jive

If you get the following error when running `jive`, then the database isn’t running on the given port or host. Check that you used the same `TANGO_HOST` when starting the database and Jive.

###### Running TangoTest

Now that we have a Tango Database running, we can start `TangoTest` (the `test` instance is already defined in the database).

In another terminal, run:

    (tango-tut) $ TangoTest test

As you started `TangoTest` after Jive, you have to refresh the tree:

You can now test the device by sending commands or reading/writing attributes.

You can of course use PyTango DeviceProxy to check the status or any attribute:

    >>> import tango
    >>> dp = tango.DeviceProxy("sys/tg_test/1")
    >>> dp.Status()
    'The device is in RUNNING state.'
    >>> dp.double_scalar
    161.10643365358285

If you don’t need `TangoTest`, you can use the keyboard combination `Ctrl`+`C` in your terminal to stop it.

##### Summary

You now know how to start a Tango `DatabaseDS` for local development and how to interact with it using PyTango or Jive. We’ll see next how to add a new device to the database.

#### Adding a new server to the Tango Database

In the previous lesson you saw how to run a Tango Databaseds. You could start `TangoTest` because the device `sys/tg_test/1` and the `test` device server instance were already defined in the database.

##### Adding a server

If you want to run a new server, you first have to add the device and instance to the database. Let’s add our MegaCoffee3k server using `tango-admin`.

Warning

Make sure the `TANGO_HOST` variable is defined in your environment before to run any of the following command.

    (tango-tut) $ tango_admin --add-server MegaCoffee3k/test MegaCoffee3k sys/coffee/1
    (tango-tut) $ echo $?
    0

Note

The `tango_admin` command isn’t very verbose. Even in case of failure, it won’t print any error message. You need to print the return code to check if it succeeded (0) or not. If you set an invalid `TANGO_HOST`, the command will fail:

    (tango-tut) $ TANGO_HOST=unknown:10000 tango_admin --add-server MegaCoffee3k/test MegaCoffee3k sys/coffee/1
    (tango-tut) $ echo $?
    255

Given the example from the first steps, you can run it using:

main.py

    from tango.server import Device

    class MegaCoffee3k(Device):
        pass

    if __name__ == "__main__":
        MegaCoffee3k.run_server()

    (tango-tut) $ python main.py test
    Ready to accept request

`test` is the name of the instance you defined above.

If you try to start the server with another instance name, you’ll get an error:

    (tango-tut) $ python main.py foo
    The device server MegaCoffee3k/foo is not defined in database. Exiting!

You can’t start a server which isn’t defined in the database.

##### Adding properties

If you take the example from device properties, and run it with `python main.py test`, you’ll get the same error as when running PyTango’s `test_context` the first time.

main.py

    from tango.server import Device, device_property

    class MegaCoffee3k(Device):

        host: str = device_property(mandatory=True)
        port: int = device_property(default_value=9788)
        location: str = device_property()

        def init_device(self):
            print(f"init_device before super: {self.host}:{self.port} @ {self.location}")
            super().init_device()
            print(f"init_device after super: {self.host}:{self.port} @ {self.location}")

    if __name__ == "__main__":
        MegaCoffee3k.run_server()

This server requires a mandatory property that you need to define in the database. Let’s add the `host` property using `tango_admin`:

    (tango-tut) $ tango_admin --add-property sys/coffee/1 host localhost

Now that the property is defined, you can start the server:

    (tango-tut) $ python main.py test
    init_device before super: None:None @ None
    init_device after super: localhost:9788 @ None
    Ready to accept request

This server is using other properties. Let’s change the port value using Jive. Open Jive. Go to the `sys/coffee/1` device properties and click `New property`. Enter `port` in the pop-up window.

Press `OK`. Click on the new property value column and enter the new value to overwrite the default.

Click `Apply`.

For the server to read the new property, you could just restart it. Another way is to run the `Init` command. Let’s do that from Jive as you have it opened.

In the terminal where you started the Tango server, you should see the following output:

    init_device before super: localhost:9788 @ None
    init_device after super: localhost:9700 @ None

The port defined in the database overwrites the default value defined in the code.

You saw that a server shall be defined in the database before to start it. We used `tango_admin`, a simple command line tool, but it can also be done using Jive or json2tango.

This tutorial shows you how to get started with Tango, covering the major features, step by step.

The topics will build on the previous ones, but you can jump in at any point to learn about a specific aspect.

Note

The code in this getting started tutorial is licensed under the MIT No Attribution License.

#### Installation

There are many ways to install Tango, but for this tutorial we will use Pixi, which provides a similar experience across multiple platforms, is fast, and very easy to use.

Follow the instructions on the Pixi home page. We’ll wait for you here…

⏳

… welcome back. Now open a new terminal, and run the following to check if the installation worked:

    $ pixi

Create a folder to keep the tutorial code. You could start in your home directory, and make a directory called `projects` for all your projects.

    $ cd
    $ mkdir projects
    $ cd projects

Create a Pixi project, and install all the dependencies we will need.

    $ pixi init tango-tut
    $ cd tango-tut
    $ pixi add python=3.12 pytango tango-test tango-admin jtango jive pogo

Enter the Pixi project shell, which is a bit like activating a Python virtual environment. This will make our newly installed version of Python and its dependencies available at the prompt.

    $ pixi shell

Test it:

    (tango-tut) $ python

    >>> import tango
    >>> print(tango.utils.info())
    PyTango 10.0.0 (10, 0, 0)
    PyTango compiled with:
        Python   : 3.12.7
        Numpy    : 2.0.2
        Tango    : 10.1.0
        pybind11 : 2.13.6

    PyTango runtime is:
        Python   : 3.12.5
        Numpy    : 2.1.2
        Tango    : 10.1.0

    PyTango running on:
    uname_result(system='Darwin', node='my.machine', release='24.0.0', version='Darwin Kernel Version 24.0.0: Tue Sep 24 23:39:07 PDT 2024; root:xnu-11215.1.12~1/RELEASE_ARM64_T6000', machine='arm64')

We can exit the python console with the `exit()` command or using `CTRL+D`.

We can exit the Pixi shell with the `exit` command:

    (tango-tut) $ exit
    $

### Example deployment of a Tango Controls System

audience:administrators audience:developers

There are different tasks that need to be performed in a Tango Cotnrols system. One can categorise the tasks as follows:

- Tango Host: Keep the configuration of all components in the Tango Controls system permanently stored and make it available through its API.

- Run Tango Applications: Execute CLI programmes. e.g.tango_admin and iTango, or GUI programmes, e.g. Jive and Synoptic.

- Run Tango Device Server(s): Execute the Tango Device Server that host Tango Devices.

- Tango development: Implement Device Classes for Tango Devices and Tango clients.

Tip

It is possible that all of the tasks above are done on the same computer at the same time.

Hint

Tango Host, Databaseds

In order to allow clients to connect to components in a Tango Controls system, i.e. connect to Tango Tango device servers and their Tango devices, such a system needs to have at least one Tango Host. The `Tango Host`’s responsibility is maintain a catalog of the Tango `device servers` and `devices` that have been configured to run in the system. A `Tango Host` typically runs the DataBaseds `device server`. This `device server` provides the configuration information about the Tango Controls system to the `device servers`, `devices` and clients in the system. Usually clients will make use of the TANGO_HOST environment variable which contains information about the host name or IPv4 address and the port on which the `Databaseds` is listening. The `TANGO_HOST` environment variable consists of a host and a port part separated by a column:

host_name_or_IPv4_address:port, example: `localhost:10000`

Simple Tango Controls systems can consist of just a single computer that acts as `Tango Host` and runs at the same time only one or a few `device servers` with only a couple `devices`. A complex system on the other hand can easily consist of tens of thousands of `device servers` and their`devices` that are spread out over multiple Tango Controls systems, each with their own `Tango Host` but still allowing clients to connect to every `device` in every individual Tango Controls system.

#### Starter

Having to start many `device servers` in a larger Tango Controls system can turn into a laborious task that takes much longer than one would want it to. Fortunately Tango comes with some batteries included and it provides the Starter `device server`. One can think of it as a boot-strapping device server that is able to start and stop other device servicers on the same host. Usually one puts `Starter` under control of one of the init systems of the OS.

#### Example for a very small installation

Here we give an example for a very small installation:

- A `Tango Host`. It will require a port number on that host which will be used by `Databaseds` for Tango requests. The hostname needs to be resolveable by all computers that run Tango software in this Tango Controls system or the host’s IP address needs to be known by the same computers. It is mandatory to start the `Databaseds` before any other Tango program.

- A different computer on which a cppTango, JTango or PyTango `device server` will run. On the same computer can also run clients.

- - On that computer:

- - - cppTango and/or JTango and/or PyTango

- - - Oracle Java JRE (Java Runtime Environment) >= 1.7 to run Tango’s Java tools or JTango code.

- - - Python >= 3.9 in order to run PyTango (clients or `devices` and `device servers`).

Note

An even smaller Tango Controls installation could even be a single computer. One could run everything on it. `Device servers` with their `devices`, the `TangoDB` and even JTango GUIs or PyTango clients.

#### Multiple Tango Controls systems

Tango makes it easy to bridge the apparent boundary betweeen Tango Controls systems. This boundary exists because every individual Tango Controls system will always have its own `TangoDB` that manages the `device servers` and `devices`. But clients can easily cross this boundary. One just needs to address a `device` or `attribute` by its full Tango Resource Locator (TRL).

#### No database

It is possible to run a `device server` on without a TangoDB. This can come handy for very small deployments that do not require the entire set of Tango functionality or when one wants to test something. Running a Tango system without a `TangoDB` is not advised.

Warning

Running a Tango system without a `TangoDB` can be useful for testing purposess. However, the lack of a TangoDB will remove core functionality of Tango.`

See section Running a device server without SQL database to understand how to use this configuration and what the limitations are.

### ATKPanel

audience:all

#### Overview

AtkPanel is a generic control panel application. It can be used to control any Tango device. It supports most of Tango features and data types. Using AtkPanel you can view and set attribute values, execute commands, test the device and access diagnostic data.

AtkPanel 5.5 main window

1. Menu bar

2. Device state

3. Device name

4. Commands drop-down

5. Device status

6. Device attributes

7. Attribute switcher

By default AtkPanel opens the scalar attribute view. If your device has attributes of non-scalar types, they will appear on the attribute switcher on the bottom of the window.

Scalar attributes view

Double array attribute view

Boolean array attribute view

Image attribute view

You can run a command by selecting the command name from the command drop-down list.

Command drop-down list

#### Device testing

By selecting View > Test device from the menu you can open the device testing panel. In this panel you can check how the device is reponding to different commands and attribute values and how much time the requests take.

Test commands view

Test attributes view

Test pipes view

Test admin view

On the Admin tab of Test Device window you can set the device’s administrative configuration such as value source, timeout, etc.

#### Trends

By choosing View > Numeric & State Trend or View > Boolean Trend you can see the numeric & state or boolean trend of selected attributes.

Numeric & State trend

Boolean trend

You can add attributes to the trend by right-clicking the attribute and selecting the desired axis. You can plot the data on X and two Y axes.

#### Error History

The View > Error history menu option opens the list of recent Tango errors that occured with the device.

Error history

#### Diagnostics

The View > Diagnostic menu option provides an overview on device diagnostic information. You can check the device interface version, events and polling statistics and command execution counts.

Device diagnostics

Attribute diagnostics

Polled attribute diagnostics

Command diagnostics

#### Preferences

From the Preferences menu you can tweak AtkPanel refreshing, set the timeout and switch between Operator View and Expert View. In Expert View you can see the attributes that have display level set to `EXPERT`.

AtkPanel Preferences

### Developing clients with the TangoATK

audience:developers lang:java

This chapter is only a brief Tango ATK (Application ToolKit) programmer’s guide. You can find a reference guide with a full description of TangoATK classes and methods in the ATK JavaDoc (Tango ATK reference on-line documentation).

The Tango ATK Tutorial includes the detailed description of the ATK architecture and the ATK components. In the tutorial you can find some code examples and also Flash Demos which explain how to start using Tango ATK.

#### Introduction

This chapter describes how to develop applications using the Tango Application Toolkit, TangoATK for short. It will start with a brief description of the main concepts behind the toolkit, and then continue with more practical, real-life examples to explain key parts.

##### Assumptions

The author assumes that the reader has a good knowledge of the Java programming language, and a thorough understanding of object-oriented programming. Also, it is expected that the reader is fluent in all aspects regarding Tango devices, attributes, and commands.

#### The key concepts of TangoATK

TangoATK was developed with these goals in mind

- TangoATK should help minimize development time

- TangoATK should help minimize bugs in applications

- TangoATK should support applications that contain attributes and commands from several different devices.

- TangoATK should help avoid code duplication.

Since most Tango-applications were foreseen to be displayed on some sort of graphic terminal, TangoATK needed to provide support for some sort of graphic building blocks. To enable this, and since the toolkit was to be written in Java, we looked to Swing to figure out how to do this.

Swing is developed using a variant over a design-pattern the Model-View-Controller (MVC) pattern called model-delegate, where the view and the controller of the MVC-pattern are merged into one object.

This pattern made the choice of labor division quite easy: all non-graphic parts of TangoATK reside in the packages beneath `fr.esrf.tangoatk.core`, and anything remotely graphic are located beneath `fr.esrf.tangoatk.widget`. More on the content and organization of this will follow.

The communication between the non-graphic and graphic objects are done by having the graphic object registering itself as a listener to the non-graphic object, and the non-graphic object emmiting events telling the listeners that its state has changed.

##### Minimize development time

For TangoATK to help minimize the development time of graphic applications, the toolkit has been developed along two lines of thought

- Things that are needed in most applications are included, eg `Splash`, a splash window which gives a graphical way for the application to show the progress of a long operation. The splash window is moslty used in the startup phase of the application.

- Building blocks provided by TangoATK should be easy to use and follow certain patterns, eg every graphic widget has a `setModel` method which is used to connect the widget with its non-graphic model.

In addition to this, TangoATK provides a framework for error handling, something that is often a time consuming task.

##### Minimize bugs in applications

Together with the Tango API, TangoATK takes care of most of the hard things related to programming with Tango. Using TangoATK the developer can focus on developing her application, not on understanding Tango.

##### Attributes and commands from different devices

To be able to create applications with attributes and commands from different devices, it was decided that the central objects of TangoATK were not to be the device, but rather the attributes and the commands. This will certainly feel a bit awkward at first, but trust me, the design holds.

For this design to be feasible, a structure was needed to keep track of the commands and attributes, so the command-list and the attribute-list was introduced. These two objects can hold commands and attributes from any number of devices.

##### Avoid code duplication

When writing applications for a control-system without a framework much code is duplicated. Anything from simple widgets for showing numeric values to error handling has to be implemented each time. TangoATK supplies a number of frequently used widgets along with a framework for connecting these widgets with their non-graphic counterparts. Because of this, the developer only needs to write the glue - the code which connects these objects in a manner that suits the specified application.

#### The real getting started

Generally there are two kinds of end-user applications: Applications that only know how to treat one device, and applications that treat many devices.

##### Single device applications

Single device applications are quite easy to write, even with a gui. The following steps are required

1. Instantiate an AttributeList and fill it with the attributes you want.

2. Instantiate a CommandList and fill it with the commands you want.

3. Connect the whole `AttributeList` with a `list viewer` and / or each individual attribute with an attribute viewer.

4. Connect the whole `CommandList` to a command list viewer and / or connect each individual command in the command list with a command viewer.

The following program (FirstApplication) shows an implementation of the list mentioned above. It should be rather self-explanatory with the comments.

      1 package examples;
      2
      3
      4 import javax.swing.JFrame;
      5 import javax.swing.JMenuItem;
      6 import javax.swing.JMenuBar;
      7 import javax.swing.JMenu;
      8
      9
     10 import java.awt.event.ActionListener;
     11 import java.awt.event.ActionEvent;
     12 import java.awt.BorderLayout;
     13
     14
     15 import fr.esrf.tangoatk.core.AttributeList;
     16 import fr.esrf.tangoatk.core.ConnectionException;
     17
     18
     19 import fr.esrf.tangoatk.core.CommandList;
     20 import fr.esrf.tangoatk.widget.util.ErrorHistory;
     21 import fr.esrf.tangoatk.widget.util.ATKGraphicsUtils;
     22 import fr.esrf.tangoatk.widget.attribute.ScalarListViewer;
     23 import fr.esrf.tangoatk.widget.command.CommandComboViewer;
     24
     25
     26 public class FirstApplication extends JFrame
     27 {
     28 JMenuBar menu;                    // So that our application looks
     29                                   // halfway decent.
     30 AttributeList attributes;         // The list that will contain our
     31                                   // attributes
     32 CommandList commands;             // The list that will contain our
     33                                   // commands
     34 ErrorHistory errorHistory;        // A window that displays errors
     35 ScalarListViewer sListViewer;     // A viewer which knows how to
     36                                   // display a list of scalar attributes.
     37                                   // If you want to display other types
     38                                   // than scalars, you'll have to wait
     39                                   // for the next example.
     40 CommandComboViewer commandViewer; // A viewer which knows how to display
     41                                   // a combobox of commands and execute
     42                                   // them.
     43 String device;                    // The name of our device.
     44
     45
     46 public FirstApplication()
     47 {
     48    // The swing stuff to create the menu bar and its pulldown menus
     49    menu = new JMenuBar();
     50    JMenu fileMenu = new JMenu();
     51    fileMenu.setText("File");
     52    JMenu viewMenu = new JMenu();
     53    viewMenu.setText("View");
     54
     55    JMenuItem quitItem = new JMenuItem();
     56    quitItem.setText("Quit");
     57    quitItem.addActionListener(new
     58       java.awt.event.ActionListener()
     59       {
     60        public void
     61        actionPerformed(ActionEvent evt)
     62        {quitItemActionPerformed(evt);}
     63       });
     64    fileMenu.add(quitItem);
     65
     66    JMenuItem errorHistItem = new JMenuItem();
     67    errorHistItem.setText("Error History");
     68    errorHistItem.addActionListener(new
     69            java.awt.event.ActionListener()
     70            {
     71             public void
     72             actionPerformed(ActionEvent evt)
     73             {errHistItemActionPerformed(evt);}
     74            });
     75    viewMenu.add(errorHistItem);
     76    menu.add(fileMenu);
     77    menu.add(viewMenu);
     78
     79    //
     80    // Here we create ATK objects to handle attributes, commands and errors.
     81    //
     82    attributes = new AttributeList();
     83    commands = new CommandList();
     84    errorHistory = new ErrorHistory();
     85    device = "id14/eh3_mirror/1";
     86    sListViewer = new ScalarListViewer();
     87    commandViewer = new CommandComboViewer();
     88
     89
     90 //
     91 // A feature of the command and attribute list is that if you
     92 // supply an errorlistener to these lists, they'll add that
     93 // errorlistener to all subsequently created attributes or
     94 // commands. So it is important to do this _before_ you
     95 // start adding attributes or commands.
     96 //
     97
     98    attributes.addErrorListener(errorHistory);
     99    commands.addErrorListener(errorHistory);
    100
    101 //
    102 // Sometimes we're out of luck and the device or the attributes
    103 // are not available. In that case a ConnectionException is thrown.
    104 // This is why we add the attributes in a try/catch
    105 //
    106
    107    try
    108    {
    109
    110 //
    111 // Another feature of the attribute and command list is that they
    112 // can add wildcard names, currently only `*' is supported.
    113 // When using a wildcard, the lists will add all commands or
    114 // attributes available on the device.
    115 //
    116    attributes.add(device + "/*");
    117    }
    118    catch (ConnectionException ce)
    119    {
    120       System.out.println("Error fetching " +
    121                          "attributes from " +
    122                          device + " " + ce);
    123    }
    124
    125
    126 //
    127 // See the comments for attributelist
    128 //
    129
    130
    131    try
    132    {
    133       commands.add(device + "/*");
    134    }
    135    catch (ConnectionException ce)
    136    {
    137       System.out.println("Error fetching " +
    138                          "commands from " +
    139                          device + " " + ce);
    140    }
    141
    142
    143 //
    144 // Here we tell the scalarViewer what it's to show. The
    145 // ScalarListViewer loops through the attribute-list and picks out
    146 // the ones which are scalars and show them.
    147 //
    148
    149    sListViewer.setModel(attributes);
    150
    151
    152 //
    153 // This is where the CommandComboViewer is told what it's to
    154 // show. It knows how to show and execute most commands.
    155 //
    156
    157
    158    commandViewer.setModel(commands);
    159
    160
    161 //
    162 // add the menubar to the frame
    163 //
    164
    165
    166    setJMenuBar(menu);
    167
    168
    169 //
    170 // Make the layout nice.
    171 //
    172
    173
    174    getContentPane().setLayout(new BorderLayout());
    175    getContentPane().add(commandViewer, BorderLayout.NORTH);
    176    getContentPane().add(sListViewer, BorderLayout.SOUTH);
    177
    178
    179 //
    180 // A third feature of the attributelist is that it knows how
    181 // to refresh its attributes.
    182 //
    183
    184
    185    attributes.startRefresher();
    186
    187
    188 //
    189 // JFrame stuff to make the thing show.
    190 //
    191
    192
    193    pack();
    194    ATKGraphicsUtils.centerFrameOnScreen(this); //ATK utility to center window
    195
    196    setVisible(true);
    197    }
    198
    199
    200    public static void main(String [] args)
    201    {
    202       new FirstApplication();
    203    }
    204
    205    public void quitItemActionPerformed(ActionEvent evt)
    206    {
    207       System.exit(0);
    208    }
    209
    210    public void errHistItemActionPerformed(ActionEvent evt)
    211    {
    212       errorHistory.setVisible(true);
    213    }
    214 }

The program should look something like this (depending on your platform and your device)

##### Multi device applications

Multi device applications are quite similar to the single device applications, the only difference is that it does not suffice to add the attributes by wildcard, you need to add them explicitly, like this:

     1  try
     2  {
     3      // a StringScalar attribute from the device one
     4     attributes.add("jlp/test/1/att_cinq");
     5     // a NumberSpectrum attribute from the device one
     6     attributes.add("jlp/test/1/att_spectrum");
     7     // a NumberImage attribute from the device two
     8     attributes.add("sr/d-ipc/id25-1n/Image");
     9  }
    10  catch (ConnectionException ce)
    11  {
    12     System.out.println("Error fetching " +
    13         "attributes" + ce);
    14  }

The same goes for commands.

##### More on displaying attributes

So far, we’ve only considered scalar attributes, and not only that, we’ve also cheated quite a bit since we just passed the attribute list to the `fr.esrf.tangoatk.widget.attribute.ScalarListViewer` and let it do all the magic. The attribute list viewers are only available for scalar attributes (NumberScalarListViewer and ScalarListViewer). If you have one or several spectrum or image attributes you must connect each spectrum or image attribute to it’s corresponding attribute viewer individually. So let’s take a look at how you can connect individual attributes (and not a whole attribute list) to an individual attribute viewer (and not to an attribute list viewer).

###### Connecting an attribute to a viewer

Generally it is done in the following way:

1. You retrieve the attribute from the attribute list

2. You instantiate the viewer

3. Your call the `setModel` method on the viewer with the attribute as argument.

4. You add your viewer to some panel

The following example (SecondApplication), is a Multi-device application. Since this application uses individual attribute viewers and not an attribute list viewer, it shows an implementation of the list mentioned above.

      1  package examples;
      2
      3
      4  import javax.swing.JFrame;
      5  import javax.swing.JMenuItem;
      6  import javax.swing.JMenuBar;
      7  import javax.swing.JMenu;
      8
      9
     10  import java.awt.event.ActionListener;
     11  import java.awt.event.ActionEvent;
     12  import java.awt.BorderLayout;
     13  import java.awt.Color;
     14
     15
     16  import fr.esrf.tangoatk.core.AttributeList;
     17  import fr.esrf.tangoatk.core.ConnectionException;
     18
     19  import fr.esrf.tangoatk.core.IStringScalar;
     20  import fr.esrf.tangoatk.core.INumberSpectrum;
     21  import fr.esrf.tangoatk.core.INumberImage;
     22  import fr.esrf.tangoatk.widget.util.ErrorHistory;
     23  import fr.esrf.tangoatk.widget.util.Gradient;
     24  import fr.esrf.tangoatk.widget.util.ATKGraphicsUtils;
     25  import fr.esrf.tangoatk.widget.attribute.NumberImageViewer;
     26  import fr.esrf.tangoatk.widget.attribute.NumberSpectrumViewer;
     27  import fr.esrf.tangoatk.widget.attribute.SimpleScalarViewer;
     28
     29  public class SecondApplication extends JFrame
     30  {
     31       JMenuBar            menu;
     32       AttributeList       attributes;   // The list that will contain our attributes
     33       ErrorHistory        errorHistory; // A window that displays errors
     34       IStringScalar        ssAtt;
     35       INumberSpectrum      nsAtt;
     36       INumberImage         niAtt;
     37       public SecondApplication()
     38       {
     39          // Swing stuff to create the menu bar and its pulldown menus
     40          menu = new JMenuBar();
     41          JMenu fileMenu = new JMenu();
     42          fileMenu.setText("File");
     43          JMenu viewMenu = new JMenu();
     44          viewMenu.setText("View");
     45          JMenuItem quitItem = new JMenuItem();
     46          quitItem.setText("Quit");
     47          quitItem.addActionListener(new java.awt.event.ActionListener()
     48                                        {
     49                                         public void actionPerformed(ActionEvent evt)
     50                                         {quitItemActionPerformed(evt);}
     51                                        });
     52
     53          fileMenu.add(quitItem);
     54          JMenuItem errorHistItem = new JMenuItem();
     55          errorHistItem.setText("Error History");
     56          errorHistItem.addActionListener(new java.awt.event.ActionListener()
     57                  {
     58                   public void actionPerformed(ActionEvent evt)
     59                   {errHistItemActionPerformed(evt);}
     60                  });
     61          viewMenu.add(errorHistItem);
     62          menu.add(fileMenu);
     63          menu.add(viewMenu);
     64        //
     65        // Here we create TangoATK objects to view attributes and errors.
     66        //
     67          attributes = new AttributeList();
     68          errorHistory = new ErrorHistory();
     69        //
     70        // We create a SimpleScalarViewer, a NumberSpectrumViewer and
     71        // a NumberImageViewer, since we already knew that we were
     72        // playing with a scalar attribute, a number spectrum attribute
     73        // and a number image attribute this time.
     74        //
     75        SimpleScalarViewer     ssViewer = new SimpleScalarViewer();
     76          NumberSpectrumViewer   nSpectViewer = new NumberSpectrumViewer();
     77          NumberImageViewer      nImageViewer = new NumberImageViewer();
     78          attributes.addErrorListener(errorHistory);
     79       //
     80       // The attribute (and command) list has the feature of returning the last
     81       // attribute that was added to it. Just remember that it is returned as an
     82       // IEntity object, so you need to cast it into a more specific object, like
     83       // IStringScalar, which is the interface which defines a string scalar
     84       //
     85         try
     86          {
     87
     88             ssAtt = (IStringScalar) attributes.add("jlp/test/1/att_cinq");
     89             nsAtt = (INumberSpectrum) attributes.add("jlp/test/1/att_spectrum");
     90             niAtt = (INumberImage) attributes.add("sr/d-ipc/id25-1n/Image");
     91          }
     92          catch (ConnectionException ce)
     93          {
     94             System.out.println("Error fetching one of the attributes  "+" " + ce);
     95             System.out.println("Application Aborted.");
     96             System.exit(0);
     97          }
     98          //
     99          // Pay close attention to the following three lines!! This is how it's done!
    100          // This is how it's always done! The setModelsetModel method of any viewer takes care
    101         // of connecting the viewer to the attribute (model) it's in charge of displaying.
    102         // This is the way to tell each viewer what (which attribute) it has to show.
    103         // Note that we use a viewer adapted to each type of attribute
    104         //
    105          ssViewer.setModel(ssAtt);
    106          nSpectViewer.setModel(nsAtt);
    107          nImageViewer.setModel(niAtt);
    108       //
    109          nSpectViewer.setPreferredSize(new java.awt.Dimension(400, 300));
    110          nImageViewer.setPreferredSize(new java.awt.Dimension(500, 300));
    111          Gradient  g = new Gradient();
    112          g.buidColorGradient();
    113          g.setColorAt(0,Color.black);
    114          nImageViewer.setGradient(g);
    115          nImageViewer.setBestFit(true);
    116
    117          //
    118          // Add the viewers into the frame to show them
    119          //
    120          getContentPane().setLayout(new BorderLayout());
    121          getContentPane().add(ssViewer, BorderLayout.SOUTH);
    122          getContentPane().add(nSpectViewer, BorderLayout.CENTER);
    123          getContentPane().add(nImageViewer, BorderLayout.EAST);
    124          //
    125          // To have the attributes values refreshed we should start the
    126          // attribute list's refresher.
    127          //
    128          attributes.startRefresher();
    129          //
    130          // add the menubar to the frame
    131          //
    132          setJMenuBar(menu);
    133          //
    134          // JFrame stuff to make the thing show.
    135          //
    136          pack();
    137          ATKGraphicsUtils.centerFrameOnScreen(this); //ATK utility to center window
    138          setVisible(true);
    139       }
    140       public static void main(String [] args)
    141       {
    142          new SecondApplication();
    143       }
    144       public void quitItemActionPerformed(ActionEvent evt)
    145       {
    146          System.exit(0);
    147       }
    148       public void errHistItemActionPerformed(ActionEvent evt)
    149       {
    150          errorHistory.setVisible(true);
    151       }
    152  }

This program (SeondApplication) should look something like this (depending on your platform and your device attributes)

###### Synoptic viewer

TangoATK provides a generic class to view and to animate the synoptics. The name of this class is fr.esrf.tangoatk.widget.jdraw.SynopticFileViewer. This class is based on a “home-made” graphical layer called jdraw. The jdraw package is also included inside TangoATK distribution.

SynopticFileViewer is a sub-class of the class TangoSynopticHandler. All the work for connection to tango devices and run time animation is done inside the TangoSynopticHandler.

The recipe for using the TangoATK synoptic viewer is the following

1. You use Jdraw graphical editor to draw your synoptic

2. During drawing phase don’t forget to associate parts of the drawing to tango attributes or commands. Use the “name” in the property window to do this

3. During drawing phase you can also aasociate a class (frequently a “specific panel” class) which will be displayed when the user clicks on some part of the drawing. Use the “extension” tab in the property window to do this.

4. Test the run-time behaviour of your synoptic. Use “Tango Synoptic view” command in the “views” pulldown menu to do this.

5. Save the drawing file.

6. There is a simple synoptic application (SynopticAppli) which is provided ready to use. If this generic application is enough for you, you can forget about the step 7.

7. You can now develop a specific TangoATK based application which instantiates the SynopticFileViewer. To load the synoptic file in the SynopticFileViewer you have the choice : either you load it by giving the absolute path name of the synoptic file or you load the synoptic file using Java input streams. The second solution is used when the synoptic file is included inside the application jarfile.

The SynopticFilerViewer will browse the objects in the synoptic file at run time. It discovers if some parts of the drawing is associated with an attribute or a command. In this case it will automatically connect to the corresponding attribute or command. Once the connection is successful SynopticFileViewer will animate the synoptic according to the default behaviour described below :

- For tango state attributes : the colour of the drawing object reflects the value of the state. A mouse click on the drawing object associated with the tango state attribute will instantiate and display the class specified during the drawing phase. If no class is specified the atkpanel generic device panel is displayed.

- For tango attributes : the current value of the attribute is displayed through the drawing object

- For tango commands : the mouse click on the drawing object associated with the command will launch the device command.

- If the tooltip property is set to “name” when the mouse enters any tango object ( attribute or command), inside the synoptic drawing the name of the tango object is displayed in a tooltip.

The following example (ThirdApplication), is a Synoptic application. We assume that the synoptic has already been drawn using Jdraw graphical editor.

      1  package examples;
      2  import java.io.*;
      3  import java.util.*;
      4  import javax.swing.JFrame;
      5  import javax.swing.JMenuItem;
      6  import javax.swing.JMenuBar;
      7  import javax.swing.JMenu;
      8  import java.awt.event.ActionListener;
      9  import java.awt.event.ActionEvent;
     10  import java.awt.BorderLayout;
     11  import fr.esrf.tangoatk.widget.util.ErrorHistory;
     12  import fr.esrf.tangoatk.widget.util.ATKGraphicsUtils;
     13  import fr.esrf.tangoatk.widget.jdraw.SynopticFileViewer;
     14  import fr.esrf.tangoatk.widget.jdraw.TangoSynopticHandler;
     15  public class ThirdApplication extends JFrame
     16  {
     17       JMenuBar              menu;
     18       ErrorHistory          errorHistory;  // A window that displays errors
     19       SynopticFileViewer    sfv;           // TangoATK generic synoptic viewer
     20
     21
     22       public ThirdApplication()
     23       {
     24          // Swing stuff to create the menu bar and its pulldown menus
     25          menu = new JMenuBar();
     26          JMenu fileMenu = new JMenu();
     27          fileMenu.setText("File");
     28          JMenu viewMenu = new JMenu();
     29          viewMenu.setText("View");
     30          JMenuItem quitItem = new JMenuItem();
     31          quitItem.setText("Quit");
     32          quitItem.addActionListener(new java.awt.event.ActionListener()
     33                                        {
     34                                         public void actionPerformed(ActionEvent evt)
     35                                         {quitItemActionPerformed(evt);}
     36                                        });
     37          fileMenu.add(quitItem);
     38          JMenuItem errorHistItem = new JMenuItem();
     39          errorHistItem.setText("Error History");
     40          errorHistItem.addActionListener(new java.awt.event.ActionListener()
     41                  {
     42                   public void actionPerformed(ActionEvent evt)
     43                   {errHistItemActionPerformed(evt);}
     44                  });
     45          viewMenu.add(errorHistItem);
     46          menu.add(fileMenu);
     47          menu.add(viewMenu);
     48          //
     49          // Here we create TangoATK synoptic viewer and error window.
     50          //
     51          errorHistory = new ErrorHistory();
     52          sfv = new SynopticFileViewer();
     53          try
     54          {
     55              sfv.setErrorWindow(errorHistory);
     56          }
     57          catch (Exception setErrwExcept)
     58          {
     59              System.out.println("Cannot set Error History Window");
     60          }
     61
     62          //
     63          // Here we define the name of the synoptic file to show and the tooltip mode to use
     64          //
     65          try
     66          {
     67            sfv.setJdrawFileName("/users/poncet/ATK_OLD/jdraw_files/id14.jdw");
     68            sfv.setToolTipMode (TangoSynopticHandler.TOOL_TIP_NAME);
     69          }
     70          catch (FileNotFoundException  fnfEx)
     71          {
     72             javax.swing.JOptionPane.showMessageDialog(
     73                null, "Cannot find the synoptic file : id14.jdw.\n"
     74                     + "Check the file name you entered;"
     75                     + " Application will abort ...\n"
     76                     + fnfEx,
     77                     "No such file",
     78                     javax.swing.JOptionPane.ERROR_MESSAGE);
     79             System.exit(-1);
     80          }
     81          catch (IllegalArgumentException  illEx)
     82          {
     83             javax.swing.JOptionPane.showMessageDialog(
     84                null, "Cannot parse the synoptic file : id14.jdw.\n"
     85                     + "Check if the file is a Jdraw file."
     86                     + " Application will abort ...\n"
     87                     + illEx,
     88                     "Cannot parse the file",
     89                     javax.swing.JOptionPane.ERROR_MESSAGE);
     90             System.exit(-1);
     91          }
     92          catch (MissingResourceException  mrEx)
     93          {
     94             javax.swing.JOptionPane.showMessageDialog(
     95                null, "Cannot parse the synoptic file : id14.jdw.\n"
     96                     + " Application will abort ...\n"
     97                     + mrEx,
     98                     "Cannot parse the file",
     99                     javax.swing.JOptionPane.ERROR_MESSAGE);
    100             System.exit(-1);
    101          }
    102          //
    103          // Add the viewers into the frame to show them
    104          //
    105          getContentPane().setLayout(new BorderLayout());
    106          getContentPane().add(sfv, BorderLayout.CENTER);
    107          //
    108          // add the menubar to the frame
    109          //
    110          setJMenuBar(menu);
    111          //
    112          // JFrame stuff to make the thing show.
    113          //
    114          pack();
    115          ATKGraphicsUtils.centerFrameOnScreen(this); //TangoATK utility to center window
    116          setVisible(true);
    117       }
    118       public static void main(String [] args)
    119       {
    120          new ThirdApplication();
    121       }
    122       public void quitItemActionPerformed(ActionEvent evt)
    123       {
    124          System.exit(0);
    125       }
    126       public void errHistItemActionPerformed(ActionEvent evt)
    127       {
    128          errorHistory.setVisible(true);
    129       }
    130  }

The synoptic application (ThirdApplication) should look something like this (depending on your synoptic drawing file)

##### A short note on the relationship between models and viewers

As seen in the examples above, the connection between a model and its viewer is generally done by calling `setModel(model)` on the viewer, it is never explained what happens behind the scenes when this is done.

###### Listeners

Most of the viewers implement some sort of listener interface, eg INumberScalarListener. An object implementing such a listener interface has the capability of receiving and treating events from a model which emits events.

     1  // this is the setModel of a SimpleScalarViewer
     2    public void setModelsetModel(INumberScalar scalar) {
     3
     4      clearModel();
     5
     6      if (scalar != null) {
     7        format = scalar.getProperty("format").getPresentation();
     8        numberModel = scalar;
     9
    10     // this is where the viewer connects itself to the
    11     // model. After this the viewer will (hopefully) receive
    12     // events through its numberScalarChange() method
    13
    14     numberModel.addNumberScalarListener(this);
    15
    16
    17          numberModel.getProperty("format").addPresentationListener(this);
    18        numberModel.getProperty("unit").addPresentationListener(this);
    19      }
    20
    21    }
    22
    23
    24
    25  // Each time the model of this viewer (the numberscalar attribute) decides it is time, it
    26  // calls the numberScalarChange method of all its registered listeners
    27  // with a NumberScalarEvent object which contains the
    28  // the new value of the numberscalar attribute.
    29  //
    30
    31    public void numberScalarChange(NumberScalarEvent evt) {
    32      String val;
    33      val = getDisplayString(evt);
    34      if (unitVisible) {
    35        setText(val + " " + numberModel.getUnit());
    36      } else {
    37        setText(val);
    38      }
    39    }

All listeners in TangoATK implement the `IErrorListener` interface which specifies the `errorChange(ErrorEvent e)` method. This means that all listeners are forced to handle errors in some way or another.

#### The key objects of TangoATK

As seen from the examples above, the key objects of TangoATK are the `CommandList` and the `AttributeList`. These two classes inherit from the abstract class `AEntityList` which implements all of the common functionality between the two lists. These lists use the functionality of the `CommandFactory`, the `AttributeFactory`, which both derive from `AEntityFactory,` and the `DeviceFactory`.

In addition to these factories and lists there is one (for the time being) other important functionality lurking around, the refreshers.

##### The Refreshers

The refreshers, represented in TangoATK by the `Refresher` object, is simply a subclass of `java.lang.Thread` which will sleep for a given amount of time and then call a method refresh on whatever kind of `IRefreshee` it has been given as parameter, as shown below

    1  // This is an example from DeviceFactory.
    2  // We create a new Refresher with the name "device"
    3  // We add ourself to it, and start the thread
    4
    5
    6  Refresher refresher = new Refresher("device");
    7  refresher.addRefreshee(this).start();

Both the `AttributeList` and the `DeviceFactory` implement the `IRefreshee` interface which specify only one method, `refresh()`, and can thus be refreshed by the `Refresher`. Even if the new release of TangoATK is based on the Tango Events, the refresher mecanisme will not be removed. As a matter of fact, the method refresh() implemented in AttributeList skips all attributes (members of the list) for which the subscribe to the tango event has succeeded and calls the old refresh() method for the others (for which subscribe to tango events has failed).

In a first stage this will allow the TangoATK applications to mix the use of the old tango device servers (which do not implement tango events) and the new ones in the same code. In other words, TangoATK subscribes for tango events if possible otherwise TangoATK will refresh the attributes through the old refresher mecanisme.

Another reason for keeping the refresher is that the subscribe event can fail even for the attributes of the new Tango device servers. As soon as the specified attribute is not polled the Tango events cannot be generated for that attribute. Therefore the event subscription will fail. In this case the attribute will be refreshed thanks to the ATK attribute list refresher.

The `AttributePolledList` class allows the application programmer to force explicitly the use of the refresher method for all attributes added in an AttributePolledList even if the corresponding device servers implement tango events. Some viewers (fr.esrf.tangoatk.widget.attribute.Trend) need an AttributePolledList in order to force the refresh of the attribute without using tango events.

###### What happens on a refresh

When `refresh` is called on the `AttributeList` and the `DeviceFactory`, they loop through their objects, `IAttributes` and `IDevices`, respectively, and ask them to refresh themselves if they are not event driven.

When AttributeFactory, creates an `IAttribute`, TangoATK tries to subscribe for Tango Change event for that attribute. If the subscription succeeds then the attribute is marked as event driven. If the subscription for Tango Change event fails, TangoATK tries to subscribe for Tango Periodic event. If the subscription succeeds then the attribute is marked as event driven. If the subscription fails then the attribute is marked as to be “ without events”.

In the refresh() method of the AttributeList during the loop through the objects if the object is marked event driven then the object is simply skipped. But if the object (attribute) is not marked as event driven, the refresh() method of the AttributeList, asks the object to refresh itself by calling the “refresh()” method of that object (attribute or device). The refresh() method of an attribute will in turn call the “readAttribute” on the Tango device.

The result of this is that the `IAttributes` fire off events to their registered listeners containing snapshots of their state. The events are fired either because the IAttribute has received a Tango Change event, respectively a Tango Periodic event (event driven objects), or because the refresh() method of the object has issued a readAttribute on the Tango device.

##### The DeviceFactory

The device factory is responsible for two things

1. Creating new devices (Tango device proxies) when needed

2. Refreshing the state and status of these devices

Regarding the first point, new devices are created when they are asked for and only if they have not already been created. If a programmer asks for the same device twice, she is returned a reference to the same device-object.

The `DeviceFactory` contains a Refresher as described above, which makes sure that the all in the updates their state and status and fire events to its listeners.

##### The AttributeFactory and the CommandFactory

These factories are responsible for taking a name of an attribute or command and returning an object representing the attribute or command. It is also responsible for making sure that the appropriate `IDevice` is already available. Normally the programmer does not want to use these factory classes directly. They are used by TangoATK classes indirectly when the application programmer calls the AttributeList’s (or CommandList’s) add() method.

##### The AttributeList and the CommandList

These lists are containers for attributes and commands. They delegate the construction-work to the factories mentioned above, and generally do not do much more, apart from containing refreshers, and thus being able to make the objects they contain refresh their listeners.

##### The Attributes

The attributes come in several flavors. Tango supports the following types:

- Short

- Long

- Double

- String

- Unsigned Char

- Boolean

- Unsigned Short

- Float

- Unsigned Long

According to Tango specifications, all these types can be of the following formats:

- Scalar, a single value

- Spectrum, a single array

- Image, a two dimensional array

For the sake of simplicity, TangoATK has combined all the numeric types into one, presenting all of them as doubles. So the TangoATK classes which handle the numeric attributes are : NumberScalar, NumberSpectrum and NumberImage (Number can be short, long, double, float, …).

###### The hierarchy

The numeric attribute hierarchy is expressed in the following interfaces:

`INumberScalar` extends `INumber`

`INumberSpectrum` extends `INumber`

`INumberImage` extends `INumber`

`INumber` in turn extends `IAttribute`

Each of these types emit their proper events and have their proper listeners. Please consult the javadoc for further information.

##### The Commands

The commands in Tango are rather ugly beasts. There exists the following kinds of commands

- Those which take input

- Those which do not take input

- Those which do output

- Those which do not do output

Now, for both input and output we have the following types:

- Double

- Float

- Unsigned Long

- Long

- Unsigned Short

- Short

- String

These types can appear in scalar or array formats. In addition to this, there are also four other types of parameters:

1. Boolean

2. Unsigned Char Array

3. The StringLongArray

4. The StringDoubleArray

The last two types mentioned above are two-dimensional arrays containing a string array in the first dimension and a long or double array in the second dimension, respectively.

As for the attributes, all numeric types have been converted into doubles, but there has been made little or no effort to create an hierarchy of types for the commands.

###### Events and listeners

The commands publish results to their `IResultListener`s, by the means of a `ResultEvent`. The `IResultListener` extends `IErrorListener`, any viewer of command-results should also know how to handle errors. So a viewer of command-results implements IResultListener interface and registers itself as a resultListener for the command it has to show the results.

### Guidelines for developing a Tango Device Server

audience:developers lang:all

This chapter describes guidelines for developing device servers. The Tango Device Server Model is flexible and permits different interpretations of how to implement Device Servers, however there is a recommended way of using Tango to implement device servers. This chapter will document some of the best practices from experienced developers for device development.

This chapter focuses on:

1. Device Server design consideration

2. Implementation good practices

These guidelines apply to features available in Tango v8 or higher.

### Tango Concepts

Some understanding of the basic Tango concepts is required to make use of this tutorial. Please refer to the Explanation section if further information on these concepts is required.

### Tango Device Design

#### Elements of general design

##### Reusability

In a Tango control system, each device is a software component and potentially reusable.

It is necessary to:

- Systematically evaluate prior to the coding of a device, the possibility of reusing a device available in the code repositories (Tango Controls community, local repository), in order to avoid several implementations of the same equipment.

- Design the device to be as reusable/extensible as possible because it may interest other developers in the community.

As such, the device must be:

- Configurable: (e.g. no port number “hard coded”, but use of a parameter via a property),

- Self-supporting: the device must be usable outside the private programming environment (e.g. all the necessary elements to use the device (compile, link) must be provided to the community). The use of the GPL should be considered, and the use of proprietary libraries should be avoided if possible,

- Portable: the device code must be (as far as possible) independent of the target platform unless it depends on platform specific drivers,

- Documented in English

##### Generic interface programming

The device must be as generic as possible which means the definition of its interface should:

- Reflect the service rather than its underlying implementation. For example, a command named `WriteRead` reflects the communication service of a bus (type: message exchange), while a command named `NI488_Send` reflects a specific implementation of the supplier.

- Show the general characteristics (attributes and commands) of a common type of equipment that it represents. For example, a command `On` reflects the action of powering on a PowerSupply , while a command named `BruckerPSON` reflects a specific implementation which must be avoided.

The device interface must be service oriented, and not implementation oriented.

##### Abstract interfaces

##### Singleton device

Tango allows a device server to host several devices which are instantiations of the same Tango class.

However, in particular cases some technical constraints may forbid this. In such a case, the Device Server programmer should anticipate this in the device design phase and add, for example, a static variable to count device instances and detect this misconfiguration. For example, it can authorize the creation of a second instance (within the meaning of the device creation) but systematically put the state to `FAULT` (in the method `init_device`) and indicate the problem in the Status.

In the case where technical constraints prohibit the deployment of multiple instances of a Tango device within the same device server, the developer has to ensure that only one instance can be created and inform the user with a clear message in case more than one device is configured in the database.

##### Device states

When designing the device, you should clearly define the state machine that will reflect the different states in which the device can be, and also the associated transitions.

The state machine must follow these rules:

- At any time, the device state must reflect the internal state of the system it represents.

- The state should represent any change made by a client’s request.

- The device behaviour is specified and documented.

#### Device interface definition

A Tango Device must be “self-consistent”. In the case where it represents a subset of the control system, it must enable access to all of the associated features (unless otherwise specified). The limit of its “responsibilities”, meaning “separation of concerns”, is clearly defined: 1 Device = 1 microservice = 1 element of the system. The analogy with object-oriented programming is straightforward.

A Device is a microservice made available to any number of unspecified clients. Its implementation and/or behaviour must not make assumptions about the nature and the number of its potential clients. In all cases, reactivity must be ensured (i.e. the response time of the device must be minimised).

The first step in designing a device is to define the commands and the attributes using Pogo (use Pogo to define the Tango interface).

Except in (very) particular cases, always use an attribute to expose the data produced by the device. The command concept exists (see Device Commands ) but its use as an attribute substitute is prohibited. For example, a motor must be moved by writing its associated ‘position’ attribute instead of using a ‘GotoPosition’ command.

The choice will be made following these rules:

- Attribute: used for all values that are presented to the “client”. It is imperative to use the attributes and to not use Tango commands that would act like a get/set couple.

- Command: used for every action. In most cases this is a void-void type.

Any deviation from these rules must be justified in the description of the attribute or command.

#### Service availability

From the operator’s perspective, the “response time” or “reactivity” (i.e. the device is always responsive) is the reference metric to describe the performance of a device. Ideally, the device implementation must ensure the service availability regardless of the external client load or the internal load. For the end user, it is always very unpleasant to suffer a Tango timeout and receive an exception instead of the expected response.

The response time of the device should be minimised and in all cases it should be lower than the default Tango timeout of 3 seconds.

If the action to be performed takes longer than that, execution should be done asynchronously in the Tango class and its progress reported in the state/status.

Several technical solutions are available to the device developer to ensure service availability:

- Use the Tango polling mechanism,

- Use a threading mechanism, managed by the developer.

#### Tango polling mechanism

##### Polling interest

The polling mechanism is detailed in the Tango documentation Device Polling .

Tango implements a mechanism called polling which alleviates the problem of equipment response time (which is usually the weak point in terms of performance). The response time of a GPIB link or a RS-232 link is usually one to two orders of magnitude higher than the performance of the Tango code executed by a client request.

##### Polling limitations

From the perspective of the device activity, the polling is in direct competition with client requests. The client load is therefore competing with the polling activity.

This means that any polling activity has to be tuned in order to keep some free time for the device to answer client requests. Do not try to poll a device object with a polling period of 200 ms if the object access time is 300 ms (even if Tango implements some algorithm to minimize the bad behavior of such badly tuned polling).

For polled Tango device objects (attribute or command), client reading does not generate any activity on the device whatever the number of clients. The data are returned from the so-called polling buffer instead of coming from the device itself. Therefore, an obvious rule is to poll the key device object (state attribute, pressure attribute for a vacuum valve…).

The recommendation for device polling tuning is to keep the device free 40% of time.

Let’s take an example: for a power supply device, you want to poll the device state and its current attribute, which for such a device are the device key objects.

- State access needs 100 ms while current attribute reading needs 50 ms.

- Because, you want to poll these two objects, the time required on the device by the polling mechanism will be 150 ms (100 + 50).

- In order to keep the 40% ratio, tune the polling period for this device to 250 ms.

- The device is then occupied by the polling mechanism during 150 ms (60 %) but free for other client activity for 100 ms (40 %).

Device polling is easily tunable at run time using the Jive and/or Astor Tango tools.

##### Threading mechanism

Threading is another possible solution for the load problem as a thread (managed by the device developer) supports communication with the material (polling or other) and the data obtained are put in the “cache”. You can now produce the “last known value” to the client at any time and optimize the response time. This approach, however, has a limit as it is necessary to reread the value of the hardware to assure clients that the returned value is the system “current state”.

For a C++ device, the implementation of a threading mechanism can be done via the DeviceTask class from the Yat4Tango library. This class owns a thread associated with a FIFO message list. Processing messages can be synchronous or asynchronous.

See a complete C++ example of this in the AttributeSequenceWriter.

If the design of the Tango class requires threading, follow these rules:

- if it requires a simple thread, in C++ the recommendation is to use a C++11 thread,

- if it requires an acquisition thread with message exchanges then in C++ the recommendation is to use the `Yat4Tango::DeviceTask` class.

### Tango device implementation

#### General rules

##### Language

The Tango Controls community is international and the code can be shared with the community, so it is recommended to use English for documenting a device development.

English will be used for:

- The interfaces definition (attributes and commands),

- The device documentation (online help for command usage and attributes description),

- The comments inserted in the code by the developer,

- The error messages,

- The name of variables and internal methods added by the developer.

The choice of the language used for the user’s documentation of the device server (“Device Server User’s Guide”) is left free, to focus on the editorial quality. In the case of a joint development with another institute, English will be used.

##### Types

The types used for the device interface definition are Tango types (`Tango::DevDouble`, `Tango::DevFloat` …). These types are presented by Pogo and are not modifiable.

The types used by the developer in their own code are left free to choose as long as they are not platform specific. Standard types of the language used (Boolean, int, double …), Tango types or types from a common library (Yat, Yat4Tango for C++) can potentially be used.

Direct conversions from the C++ type long to `Tango::DevLong` are only supported on 32-bit platforms and should be avoided.

##### Generated code

The automatically generated code by Pogo must not be modified by the developer.

The developer must include their own code in the specified “PROTECTED REGION” parts.

#### Device interface

##### Naming rules

Having homogeneous conventions for naming attributes, commands and properties is a good way to promote the reuse of device servers inside the Tango collaboration.

It makes the development carried out by another institute easier to understand and integrate into another Control System.

###### Class name

The Tango class name is obtained by concatenating the fields that it is compose of – each field beginning with a capital letter:

Eg : `MyDeviceClass`

###### Device attributes

The device command and attribute names must be explicit and should enable a quick understanding of the nature of the attribute or the command.

- Eg: for a power supply, you would have an attribute `outputCurrent` (not OC1) or a command `ActivateOutput1` (not ActO1).

The nomenclature recommendations are detailed in the section Naming Rules .

The attribute naming recommendations are:

- Name composed of at least two characters,

- Only alphanumeric characters are allowed (no underscore, no dashes),

- Start with a lowercase letter,

- In case of a composite name, each sub-words must be capitalized (except the first letter),

- Prohibit any use of vague terms (eg: readValue).

###### Device Commands

The recommendations are the same as those proposed for an attribute, except for the first letter of the name.

The command naming recommendations are:

- Name composed of at least two characters,

- Only alphanumeric characters are allowed (no underscore, no dashes),

- Start with a uppercase letter,

- In case of a composite name, each sub-words must be capitalized,

- Prohibit any use of vague terms (eg: Control).

###### Device properties

The recommendations are the same as those proposed for a command.

The property naming recommendations are:

- Name composed of at least two characters,

- Only alphanumeric characters are allowed (no underscore, no dashes),

- Start with a uppercase letter,

- In case of a composite name, each sub-words must be capitalized,

- Prohibit any use of vague terms (eg: Prop1).

##### Device attributes nomenclature

It is a good practice that a particular signal type is always named in a similar way in various device servers.

For example the intensity of a current should always be named `intensity` (and not “intens”, “current”,”I” depending on the device server).

This allows the user to quickly make the link between the software information and the physical sensor and reciprocally.

##### Data types choice

Always use data types consistent with the underlying information.

- Unsigned integer must be used for the physical quantities that are suitable.

  - Eg: A number of samples `numSamples`, where negative values have no meaning, will be a `Tango::DevULong` (unsigned integer 32 bits) and not a `Tango::DevLong` (signed integer 32 bits).

  - Similarly, in such a case, the use of a floating point number should be prohibited as non-integer values also have no meaning in this context.s

- This rule is also applicable to input/output arguments of commands.

##### Interface level choice

The choice between the Expert or the Operator level for an interface must be thought through.

Only necessary commands for a nominal control of the equipment must be accessible at the Operator level. The commands for fine control of the equipment (eg: metrology, maintenance, unit test) must only be accessible at the Expert level.

#### Pogo use

##### Device generation

The use of Pogo is mandatory for creating or modifying the device interface.

Tango is constantly evolving but this tool will support all or part of the porting associated to the kernel and the consequences on the IDL interface.

In addition, it simplifies maintenance/development operations.

Every command, attribute, property or device state must be fully documented; this documentation is done via the Pogo tool.

When creating an attribute with Pogo, the entire configuration of the attribute must be fully filled in by the developer (maximum possible) to avoid ambiguities.

Similarly, the states and their transitions must be described with precision and clarity.

In fact:

- In operation, this documentation will be the reference for understanding the device behaviour. Remember that the operator will have this information with the generic tools (like Test Device from Jive).

- The html documentations generated by Pogo can also be accessed from a local server (particular to the institute).

- Consider also filling in the alarm values.

  - E.g. set the alarm values according to the specifications of the power supply, i.e., 0-24V for the voltage, or 0-3A for the output current.

Example for a temperature reading:

##### Attributes generation in C++

In C++, Pogo automatically generates pointers to the data associated with the attributes values (i.e. a pointer is generated for the read part). The use of these pointers is not mandatory. The developer is free to use their own data structure in the attribute value affectation.

#### Internal device implementation

##### Separation between the Tango interface and the internal system function

Don’t forget that the Tango interface is only a means to insert a microservice in a control system. Therefore, it is necessary to think about the internal design of the device like one would for any other application and just use Tango as an interface on top of it.

As a rule of thumb if the code implemented within the Pogo markers is too long it is good practice to move it to another class meaning the Pogo generated methods will be only a few lines of code long.

In practice, it is necessary to avoid mixing the code generated by Pogo and the code of the developer.

The Tango sub-class inherited from `Tango::DeviceImpl[_X]` instantiates a class derived from the model object implementing the system, and ensure the replacement between the external requests (clients) and the implementation class(es).

In the choice of data structures, we are talking about those of the developer’s object model, we will consider the technical constraints imposed by Tango and/or the underlying layers (CORBA/ZMQ). The idea here is to avoid copy and/or reorganisation of the data when transferred to the client. For this, the developer needs to know/master the underlying memory management mechanism (especially in C++). Please see the dedicated section Exchanging data between client and server for further details.

##### Accessing the hardware: `always_executed_hook` vs. `read_attr_hardware`

It is essential to understand the concepts implemented by these two methods, which are common methods for all Tango devices.

It is also necessary to clearly identify, in the design phase, the possible consequences of implementing these two methods on the device behaviour (remember that they are initially just empty shells generated by Pogo).

- The `always_executed_hook()` method is called before each command is executed or before every read/write of an attribute (but note, it is called only once when reading several attributes: see calling sequence below).

- The `read_attr_hardware()` is called before every read of attribute(s)(but note again, it is called only once when reading several attributes: see calling sequence below). This method aims to optimise (minimise) the equipment access in case of simultaneous reads of multiple attributes in the same request.

Given below is the calling sequence of these methods:

- Command execution

  - 1 – `always_executed_hook()`

  - 2 – `is_MyCmd_allowed()`

  - 3 – `MyCmd()`

- Attribute reading

  - 1 – `always_executed_hook()`

  - 2 – `read_attr_hardware()`

  - 3 – `is_MyAttr_allowed()`

  - 4 – `read_MyAttr()`

- Attribute writing

  - 1 – `always_executed_hook()`

  - 2 – `is_MyAttr_allowed()`

  - 3 – `write_MyAttr()`

- Attributes reading

  - 1 – `always_executed_hook()`

  - 2 – `read_attr_hardware()`

  - 3 – `is_MyAttr_allowed()`

  - 4 – `read_MyAttr()`

- Attributes writing

  - 1 – `always_executed_hook()`

  - 2 – `is_MyAttr_allowed()`

  - 3 – `write_MyAttr()`

This demonstrates why having “slow code” in the `MyDevice::always_executed_hook` method can have serious consequences on the device performance.

Note

There is no obligation to use the `read_attr_hardware` method; it depends on the equipment and its communication channel (Ethernet, GPIB, DLL). Instead, one could have a call to the equipment in the code of each attribute reading method.

For example, for an attribute “temperature”, of READ type, we can insert the call to the equipment in the generated attribute reading method `read_Temperature` instead of `read_attr_hardware`.

##### Static database as persistent data storage

The Tango database can (in some cases) be used to ensure persistence of set values and to store the value as a property (of device or attribute).

However, this practice should be reserved for special cases that don’t require writing at high frequency. An over-solicitation of the Tango database will penalize the entire control system.

It is therefore recommended to use a property for storage only for methods that are performed rarely, compared to other functions.

One example is the storage of calibration operations results.

In the general case, we recommend to:

- Use a property to store configuration data,

- Use a memorized attribute to store values changing during the execution,

- Use a memorized attribute to store values that you want to re-inject during a new execution of the device.

##### Device property vs memorized attributes

In some cases, you could be tempted to use a property for a memorized attribute and vice-versa. It is important to distinguish the function of each, and use them wisely.

- The use of a property must be limited to configuration data which value doesn’t change at runtime (the IP address of equipment for example).

- The memorized attributes are reserved for physical quantities subject to change at runtime (attribute read/write) for which you want to retain (store) the value from one execution to the other.

  e.g. speed or acceleration on a motor.

Tip

In the case you want to manually manage the memorization of the attribute set points, you should use an attribute property called `__value` (as natively done by Tango).

#### Device state management

##### States choice

In Tango, the state is seen as an enumerated type with a fix number of values - see the Device State section for a full list of allowed value. These states have an implicit default meaning and are not equivalent.

Unless strictly specified, the developer is free to use the Tango state she considers appropriate to the situation, with all the subjectivity involved.

The only practice that ensures overall consistency is to use a limited number of Tango states, especially for a family of equipment.

For example, it is recommended for an equipment of type motor, slit, monochromator and more generally for any equipment that can change its position, to use the “MOVING” state when the equipment is in “movement” toward his set point.

##### Semantics of non-nominal states

Although the developer is free to choose the device states, we must define a common error state for all the devices.

In general, any dysfunction is associated with the state `Tango::FAULT`.

The use of the `Tango::ALARM` state should be reserved for very special cases where it is necessary to define an intermediate state between normal operation and fault. Its use must be documented via Pogo in order to define the semantics.

In the case of a problem occurring at initialization, it is recommended to set the device state to `FAULT`.

For the `init_device` method, we recommend:

- If the initialization method is long, thread it.

- The device state INIT must be used only in the start-up of the device.

The device states changes when the init execution is over.

Semantics recommended for `FAULT` and `ALARM` states are as follows:

- `UNKNOWN` (grey): communication problem with the equipment or the “sub”-devices which prevents the device to really know its real state

- `FAULT` (red): A problem which prevents the normal functioning (including during the initialization). Getting out from a FAULT state is possible only by repairing the cause of the problem and/or executing a Reset command.

- `ALARM` (orange): the device is functional but one element is out of range (bad parameters but not preventing the functioning, limit switch of a motor). An attribute is out of range.

##### Managing state transitions

It is import to consider how state transitions are handled as a mis-managed transition can cause misleading information to be transmitted to the client.

For example, consider the case of a motor system. The client can use a poll (i.e. periodically read the state attribute of the motor) the motor to get the motor state, e.g. `STANDBY, MOVING, FAULT`. This could lead to inconsistent behaviour due to inappropriate management of the state.

A typical example is to launch an axis movement through the writing of the position attribute. The motor should make the transition from `STANDBY` to the `MOVING` state and the client will be expecting it to be in the `MOVING` state.

However, this will only work if the device state is switched to `MOVING` before the position write request returns. Otherwise, the client could read back that the motor is still in the `STANDBY` state and hence interpret that the move has ended even though it has not started.

This behaviour is illustrated in the figure below:

Example of State transitions

Note

The state transitions and the “associated guarantees” must be documented. In the previous example, rereading the STANDBY state after performing any movement must ensure that the required movement is completed (and not that it has not yet been started!!).

##### State machine management

###### Pogo or developer code

Tango has a basic management of its state machine. `Is_allowed` methods filter the external request depending on the current device state. The developer must define the device behaviour (regarding its internal state) via Pogo.

By default, any request (reading, writing, or command execution) is authorized whatever the current device state is.

The example below illustrates two ways for the state machine management of a device (here NITC01) in C++:

- Managing the “On” command via Pogo

- Managing the reading of the attribute “temperature” directly in the code

However, the Pogo implementation is “basic”. If, for example, the execution of the `On` command on a power supply is prohibited when the current state is `Tango::ON`, then the Tango layer, generated by Pogo, will systematically trigger an exception to the client. From the operator perspective, this may be a surprise.

In such a case, it is recommended to authorize the command but to ignore it.

###### Particular case : FAULT state

The `Tango::FAULT` state shouldn’t prohibit everything. The attributes and/or commands that are valid and/or allows the device to get out of the Tango::FAULT state must remain accessible.

For example, in some cases, when a device used several elementary devices, its state is a combination of the elementary devices states. If one of them is in “FAULT”, we must be able to execute commands on others elementary devices, and, in all cases, have a command to get out of this state.

The transition to a `FAULT` state needs reflection and a clear definition of the device management in this state and the output conditions of this state.

###### Init and error acknowledgement

A common mistake is to associate the generic command `MyDevice::Init` to an acknowledgement mechanism for the current defect.

The execution of the `Init` command must be reserved to the device re-initialization (hardware reconnection after a reboot or reconfiguration following a property modification).

Any device that requires an acknowledgement mechanism must have a dedicated command (like `Reset` or `AcknowledgeError`).

###### Other implementations

You can also create a specific state machine, without using Tango types, in the interface class with the device. Thus, we use this state machine to determine the Tango state of the device. The aims here is to define an internal state machine (with a design pattern “state” for example) then do a mapping with the existing Tango states to determine the device state.

The developer also has the ability to override the `State` and `Status` methods in order to centralize, in a unique method, the management of the internal device state, which simplifies the update of this fundamental information.

#### Logging management

##### The importance of rigorous logging management

The introduction of logging in the device code enables easy development, debugging and the user understanding of the device operations.

The device developer must always use the facilities offered by the Tango Logging Service to produce “Runtime” messages, facilitating the understanding of the device operations. Implementations classes can inherit `Tango::LogAdapter` to redirect the logs to the common service.

The rules to follow are:

- Logs to the console are prohibited. The developer must use the logging stream proposed by Tango (there is a stream for every logging level, the levels being inclusive in the order specified below): DEBUG_STREAM, INFO_STREAM, WARN_STREAM, ERROR_STREAM, FATAL_STREAM

- It is important to use the right level of logging: on a higher level than DEBUG, the device should be a little wordy. Beyond the INFO level, it should produce only critical logs.

Recommendations of use:

- `DEBUG_STREAM`: developer information (route trace)

- `INFO_STREAM`: user information (measure, start/stop of a process)

- `WARN_STREAM`: warning (eg deprecated operation)

- `ERROR_STREAM`: general error

- `FATAL_STREAM`: fatal error, shutdown

It is important to use these streams early in the development as they allow for easier debugging.

You shouldn’t have to modify the code to add traces. E.g. use a `debug_stream` level for the input parameters, the display of a conversion result, the return code from a DLL function…

It is also recommended to adopt a unified formalism for logs, for example:

- “<class_name>::<method_name>() - <text trace with parameter (eventually)>”

Example of using different logs levels in C++:

It is also possible to redirect the stream to a file (via Jive). This can be useful in the case of “intermittent” bugs, for which a long log may be required.

##### Implementation

It is not mandatory, but it is highly recommended to add an attribute named “log” in the device interface with a string spectrum type. This tracks all the internal activity of the device (as defined in Tango Logging).

- In C++, the class `Yat4Tango::InnerAppender` implements this functionality based on a dynamic attribute (no need to use Pogo).

- This system facilitates the recovery of errors and therefore the diagnosis of problems. Problem solving will therefore be faster and optimized.

- This feature is particularly interesting for devices that manage automatic processes (for example, scanning) which involve other devices. The operator then has easy access through this “log” attribute to the behaviour and decisions taken by the device.

Example in C++ is shown below (look at the YAT documentation for further explanations):

- In the header file of the device

  - Declaration of the service to use

- In the source code of the device

  - `init_device` method: initialization of the “innerAppender”

  - `delete_device` method: deletion of the “innerAppender”

#### Error handling

##### The importance of rigorous error handling

Error handling is often overlooked but good error handling means easier debugging and maintenance. It is essential for good code quality. These concepts are present in the section Reporting errors.

Below are some typical cases to avoid:

- A device doesn’t behave as expected but there is no indication why.

- The device is in `FAULT` state but the `Status` (the attribute) gives no indication on the problem nature, or worse, a bad indication (thus guiding the users in the wrong direction with a loss of time and energy).

- The error messages are written in the jargon of the developer or the system expert.

The developer has to ensure that:

- Any exception is caught, completed (so Tango allows it) and propagated using the `rethrow_exception` method,

- If an error occurs it must be logged using the Tango Logging Service

- The return code of a function is always analyzed,

- The device `Status` is always coherent with the `State`,

- The error messages are understandable for the end user and that they are supplemented by logs (with the `ERROR` level - use of the `error_stream` macro). The `Status` is the indicator that will help the user to find the reason for the error.

- All of the possible error situations are considered and handled. For example, in the use of communication sockets the developer should anticipate all the common communication problems such as a cable not connected, the equipment is off, a sub-devices did not start or is in a `FAULT` state.

##### Implementation

On a more technical side, the Tango exceptions don’t provide numerical identifier for discriminating exceptions. In the code, it isn’t possible to distinguish two exceptions without having knowledge of the text (as a string) conveyed by the said exception.

All exceptions are of type `Tango::DevFailed`. A DevFailed exception consists of these fields:

- Reason: string, defining the error type

  - Aim: refer the operator to the root cause

- Description: string, giving a more precise description

  - Aim: refer the expert of this system to the root cause.

- Origin: string, method where the exception was thrown

  - Aim : refer the computer scientist to the location of the failure in the code

- Severity: enumeration (rarely uses)

##### Standardized name for error types

To easily distinguish exceptions, it is recommended to use a finite list of error types for the Reason field and specify in capital letters:

Standardized name for the error types
OUT_OF_MEMORY
HARDWARE_FAILURE
SOFTWARE_FAILURE
HDB_FAILURE
DATA_OUT_OF_RANGE
COMMUNICATION_BROKEN
OPERATION_NOT_ALLOWED
DRIVER_FAILURE
UNKNOWN_ERROR
CORBA_TIMEOUT
Tango_CONNECTION_FAILED
Tango_COMMUNICATION_ERROR
Tango_WRONG_NAME_SYNTAX_ERROR
Tango_NON_DB_DEVICE_ERROR
Tango_WRONG_DATA_ERROR
Tango_NON_SUPPORTED_FEATURE_ERROR
Tango_ASYNC_CALL_ERROR
Tango_ASYNC_REPLY_NOT_ARRIVED_ERROR
Tango_EVENT_ERROR
Tango_DEVICE_ERROR
CONFIGURATION_ERROR
DEPENDENCY_ERROR
NO_DEPENDENCY

Below is an example of an exception message:

> Reason: DATA_OUT_OF_RANGE

> Description: `AxisMotionAccuracy` must be at least 1 motor step!

> Origin: `GalilAxis::write_attr_hardware`

The exception hierarchy defined by Tango is only available for internal use (Tango core) and so the developer cannot inherit and define their own inherited exceptions classes. This strong constraint is related to the underlying CORBA IDL.

Further advice for managing exceptions:

- Always keep the original exception. It must be the first visible item in the device status.

- If there is a succession of exceptions, the logic dictates that the first exception has possibly generated all the others. By resolving the first exception, the others can disappear.

For exception handling in `init_device` method:

- no exceptions should be propagated from the method `MyDevice::init_device`. Otherwise, the device quits. The device should be kept alive regardless of any failure.

- The code for this method must contain a try/catch block, which guarantees that no exception is propagated in this context.

- If an exception is thrown, the developer must set the device state to `FAULT` and update the `Status` to indicate the error nature. (The goal is to understand easily why the device failed to initialize properly, while still allowing the operator to adjust this or these problems)

Examples of error handling in C++:

- If an error occurs, always log it

- Always update State AND Status

- Manage the return code for function that have one

- Manage the exceptions for methods which can throw some

##### Details for an attribute

Although Tango supports the notion of quality on an attribute value (`Tango::VALID`, `Tango::INVALID`, …), only few clients use this information to judge the validity of the data returned so it is best to not make assumptions on the use of this to report an invalid value to the client. That is to say that forcing the attribute quality to `Tango::INVALID` is necessary but not sufficient.

For floating point values, it is possible to set the value to “NaN”, but there is no equivalent for an integer. To avoid the handling of special cases, it is recommended to throw an exception to indicate the data invalidity.

It is recommended to throw an exception for all invalid values, regardless of their type. There is, however, two exceptions to this rule: State and Status. For these two attributes, always return a value.

This solution has the disadvantage of showing a pop-up on the client side, but this is the most effective method to indicate that the attribute reading has failed.

##### Details for the properties

###### Properties reading during device initialization

As it stands, the code generated by Pogo doesn’t wrap the method which ensures the reading of properties from the Tango database in a try/catch block (see `MyDevice::init_device`). However, it may fail and cause the generation of an exception. As mentioned above, the developer must ensure that any exception thrown in the `init_device` method (or a method called from it) is caught and not propagated.

In the case of a Tango exception during the properties reading, the developer should systematically:

1. detect the error (catch it).

2. log it with level `ERROR`.

3. set the device to the `FAULT` state.

4. update the Status indicating the origin of the problem.

Example in C++ :

As a reminder, the default value for a property is defined with Pogo and the value is stored in the database via the `put_property()` method.

###### Properties without default values

Pogo allows a default value for a property not present in the Tango database to be defined.

For mandatory properties that have no default values, the developer should systematically:

- detect the absence of the value in the database.

- log the problem explicitly with the level `ERROR` (indicate the missing property).

- set the device to the `FAULT` state.

- update the Status indicating the problem origin.

### Appendices

#### About these guidelines

These guidelines originated within the collaborative framework between SOLEIL and MAX-IV to define common software quality rules for shared software between these 2 institutes. It has since been adopted by the Tango Controls community and is maintained for and by the community.

The objectives are therefore to enhance the general software quality of Device Servers developed by the various sites using Tango. This will also facilitate the reusability of code between sites by providing “reliable off-the-shelves” Tango servers in public repositories.

This document can be freely distributed (under the Creative Commons license) to subcontractors, students, etc…

Note

The content of this document is generally independent of the programming language used. However, there are some “C++ oriented” recommendations. For Java and Python refer to the relevant documentation for language specific issues.

### Developing a Tango device server

audience:developers lang:c++

#### The device server framework

This chapter will present the TANGO device server framework. It will introduce what is the device server pattern and then it will describe a complete device server framework. A definition of classes used by the device server framework is given in this chapter. This manual is not intended to give the complete and detailed description of classes data member or methods, refer to cppTango API reference to get this full description. But first, the naming convention used in this project is detailed.

The aim of the class definition given in this chapter is only to help the reader to understand how a TANGO device server works. For a detailed description of these classes (and their methods), refer to chapter Writing a device server process or to cppTango API reference.

##### Naming convention and programming language

TANGO fully supports three different programming languages which are C++, Java and Python. This documentation focuses on C++ Tango class. For Java and Python Tango class, have a look at the JTango API reference and PyTango API reference pages where similar chapter for Java and Python are available.

Every software project needs a naming convention. The naming convention adopted for the TDSOM is very simple and only defines two guidelines which are:

- Class names start with uppercase and use capitalization for compound words (For instance MyClassName).

- Method names are in lowercase and use underscores for compound words (For instance my_method_name).

##### The device pattern

Device server are written using the Device pattern. The aim of this pattern is to provide the control programmer with a framework in which s/he can develop new control objects. The device pattern uses other design patterns like the Singleton and Command patterns. The device pattern class diagram for stepper motor device is drawn in figure 6.1.

Figure 6.1: Device pattern class diagram

In this figure, only classes surrounded with a dash line square are device specific. All the other classes are part of the TDSOM core and are developed by the Tango system team. Different kind of classes are used by the device pattern.

- Three of them are root classes and it is only necessary to inherit from them. These classes are the DeviceImpl, DeviceClass and Command classes.

- Classes necessary to implement commands. The TDSOM supports two ways to create command : Using inheritance or using the template command model. It is possible to mix model within the same device pattern

  1. Using inheritance. This model of creating command heavily used the polymorphism offered by each modern object oriented programming language. In this schema, each command supported by a device via the command_inout or command_inout_async operation is implemented by a separate class. The Command class is the root class for each of these classes. It is an abstract class. A execute method must be defined in each sub-class. A is_allowed method may also be re-defined in each class if the default one does not fulfil all the needs [1]. In our stepper motor device server example, the DevReadPosition command follows this model.

  2. Using the template command model. Using this model, it is not necessary to write one class for each command. You create one instance of classes already defined in the TDSOM for each command. The link between command name and method which need to be executed is done through pointers to method. To support different kind of command, four classes are part of the TDSOM. These classes are :

     1. The TemplCommand class for command without input or output parameter

     2. The TemplCommandIn class for command with input parameter but without output parameter

     3. The TemplCommandOut class for command with output parameter but without input parameter

     4. The TemplCommandInOut class for all the remaining commands

- Classes necessary to implement TANGO device attributes. All these classes are part of the TANGO core classes. These classes are the MultiAttribute, Attribute, WAttribute, Attr, SpectrumAttr and ImageAttr classes. The last three are used to create user attribute. Each attribute supported by a device is implemented by a separate class. The Attr class is the root class for each of these classes. According to the attribute data format, the user class implementing the attribute must inherit from the Attr, SpectrumAttr or ImageAtttr class. SpectrumAttr class inherits from Attr class and Image Attr class inherits from the SpectrumAttr class. The Attr base class defined three methods called is_allowed, read and write. These methods may be redefined in sub-classes in order to implement the attribute specific behaviour.

- The other are device specific. For stepper motor device, they are named StepperMotor, StepperMotorClass and DevReadPosition.

###### The Tango base class (DeviceImpl class)

###### Description

This class is the device root class and is the link between the Device pattern and CORBA. It inherits from CORBA classes and implements all the methods needed to execute CORBA operations and attributes. For instance, its method command_inout is executed when a client requests a command_inout operation. The method name of the DeviceImpl class is executed when a client requests the name CORBA attribute. This class also encapsulates some key device data like its name, its state, its status, its black box…. This class is an abstract class and cannot be instantiated as is.

###### Contents

The contents of this class can be summarized as :

- Different constructors and one destructor

- Methods to access instance data members outside the class or its derivate classes. These methods are necessary because data members are declared as protected.

- Methods triggered by CORBA attribute request

- Methods triggered by CORBA operation request

- The init_device() method. This method makes the class abstract. It should be implemented by a sub-class. It is used by the inherited classes constructors.

- Methods triggered by the automatically added State and Status commands. These methods are declared virtual and therefore can be redefined in sub-classes. These two commands are automatically added to the list of commands defined for a class of devices. They are discussed in The automatically added commands.

- A method called always_executed_hook() always executed for each command before the device state is tested for command execution. This method gives the programmer a hook where he(she) can program some mandatory action which must be done before any command execution. An example of the such action is an hardware access to the device to read its real hardware state.

- A method called read_attr_hardware() triggered by the read_attributes CORBA operation. This method is called once for each read_attributes call. This method is virtual and may be redefined in sub-classes.

- A method called write_attr_hardware() triggered by the write_attributes CORBA operation. This method is called once for each write_attributes call. This method is virtual and may be redefined in sub-classes.

- Methods for signal management (C++ specific)

- Data members like the device name, the device status, the device state

- Some private methods and data members

###### The DbDevice class

Each DeviceImpl instance is an aggregate with one instance of the DbDevice class. This DbDevice class can be used to query or modify device properties. It provides an easy to use interface for device objects in the database. The description of this class can be found in the Tango API reference documentation available on the Tango WEB pages.

###### The Command class

###### Description of the inheritance model

Within the TDSOM, each command supported by a device and implemented using the inheritance model is implemented by a separate class. The Command class is the root class for each of these classes. It is an abstract class. It stores the command name, the command argument types and description and mainly defines two methods which are the execute and is_allowed methods. The execute method should be implemented in each sub-class. A default is_allowed method exists for command always allowed. A command also stores a parameter which is the command display type. It is also used to select if the command must be displayed according to the application mode (every day operation or expert mode).

###### Description of the template model

Using this method, it is not necessary to create a separate class for each device command. In this method, each command is represented by an instance of one of the template command classes. They are four template command classes. All these classes inherits from the Command class. These four classes are :

1. The TemplCommand class. One object of this class must be created for each command without input nor output parameters

2. The TemplCommandIn class. One object of this class must be created for each command without output parameter but with input parameter

3. The TemplCommandOut class. One object of this class must be created for each command without input parameter but with output parameter

4. The TemplCommandInOut class. One object of this class must be created for each command with input and output parameters

These four classes redefine the execute and is_allowed method of the Command class. These classes provides constructors which allow the user to :

- specify which method must be executed by these classes execute method

- optionally specify which method must be executed by these classes is_allowed method.

The method specification is done via pointer to method.

Remember that it is possible to mix command implementation method within the same device pattern.

###### Contents

The content of this class can be summarizes as :

- Class constructors and destructor

- Declaration of the execute method

- Declaration of the is_allowed method

- Methods to read/set class data members

- Methods to extract data from the object used to transfer data on the network

- Methods to insert data into the object used to transfer data on the network

- Class data members like command name, command input data type, command input data description…

###### The DeviceClass class

###### Description

This class implements all what is specific for a controlled object class. For instance, every device of the same class supports the same list of commands and therefore, this list of available commands is stored in this DeviceClass. The structure returned by the info operation contains a documentation URL [2]. This documentation URL is the same for every device of the same class. Therefore, the documentation URL is a data member of this class. There should have only one instance of this class per device pattern implementation. The device list is also stored in this class. It is an abstract class because the two methods device_factory() and command_factory() are declared as pure virtual. The rule of the device_factory() method is to create all the devices belonging to the device class. The rule of the command_factory() method is to create one instance of all the classes needed to support device commands. This class also stored the attribute_factory method. The rule of this method is to store in a vector of strings, the name of all the device attributes. This method has a default implementation which is an empty body for device without attribute.

###### Contents

The contents of this class can be summarize as :

- The command_handler method

- Methods to access data members.

- Signal related method (C++ specific)

- Class constructor. It is protected to implements the Singleton pattern

- Class data members like the class command list, the device list…

###### The DbClass class

Each DeviceClass instance is an aggregate with one instance of the DbClass class. This DbClass class can be used to query or modify class properties. It provides an easy to use interface for device objects in the database. The description of this class can be found in the reference Tango C++ API documentation available in the Tango WEB pages.

###### The MultiAttribute class

###### Description

This class is a container for all the TANGO attributes defined for the device. There is one instance of this class for each device. This class is mainly an aggregate of Attribute object(s). It has been developed to ease TANGO attribute management.

###### Contents

The class contents could be summarizes as :

- Miscellaneous methods to retrieve one attribute object in the aggregate

- Method to retrieve a list of attribute with an alarm level defined

- Get attribute number method

- Miscellaneous methods to check if an attribute value is outside the authorized limits

- Method to add messages for all attribute with an alarm set

- Data members with the attribute list

###### The Attribute class

###### Description

There is one object of this class for each device attribute. This class is used to store all the attribute properties, the attribute value and all the alarm related data. Like commands, this class also stores th attribute display type. It is foreseen to be used by future Tango graphical application toolkit to select if the attribute must be displayed according to the application mode (every day operation or expert mode).

###### Contents

- Miscellaneous method to get boolean attribute information

- Methods to access some data members

- Methods to get/set attribute properties

- Method to check if the attribute is in alarm condition

- Methods related to attribute data

- Friend function to print attribute properties

- Data members (properties value and attribute data)

###### The WAttribute class

###### Description

This class inherits from the Attribute class. There is one instance of this class for each writable device attribute. On top of all the data already managed by the Attribute class, this class stores the attribute set value.

###### Contents

Within this class, you will mainly find methods related to attribute set value storage and some data members.

###### The Attr class

Within the TDSOM, each attribute supported by a device is implemented by a separate class. The Attr class is the root class for each of these classes. It is used in conjonction with the Attribute and Wattribute classes to implement Tango attribute behaviour. It defines three methods which are the is_allowed, read and write methods. A default is_allowed method exists for attribute always allowed. Default read and write empty methods are defined. For readable attribute, it is necessary to overwrite the read method. For writable attribute, it is necessary to overwrite the write method and for read and write attribute, both methods must be overwritten.

###### The SpectrumAttr class

This class inherits from the Attr class. It is the base class for user spectrum attribute. It is used in conjonction with the Attribute and WAttribute class to implement Tango spectrum attribute behaviour. From the Attr class, it inherits the Attr is_allowed, read and write methods.

###### The ImageAttr class

This class inherits from the SpectrumAttr class. It is the base class for user image attribute. It is used in conjonction with the Attribute and WAttribute class to implement Tango image attribute behaviour. From the Attr class, it inherits the Attr is_allowed, read and write methods.

###### The StepperMotor class

###### Description

This class inherits from the DeviceImpl class and is the class implementing the controlled object behavior. Each command will trigger a method in this class written by the device server programmer and specific to the object to be controlled. This class also stores all the device specific data.

###### Definition

     1 class StepperMotor: public TANGO_BASE_CLASS
     2 {
     3 public :
     4    StepperMotor(Tango::DeviceClass *,string &);
     5    StepperMotor(Tango::DeviceClass *,const char *);
     6    StepperMotor(Tango::DeviceClass *,const char *,const char *);
     7    ~StepperMotor() {};
     8
     9    DevLong dev_read_position(DevLong);
    10   DevLong dev_read_direction(DevLong);
    11   bool direct_cmd_allowed(const CORBA::Any &);
    12
    13   virtual Tango::DevState dev_state();
    14   virtual Tango::ConstDevString dev_status();
    15
    16   virtual void always_executed_hook();
    17
    18   virtual void read_attr_hardware(vector<long> &attr_list);
    19   virtual void write_attr_hardware(vector<long> &attr_list);
    20
    21   void read_position(Tango::Attribute &);
    22   bool is_Position_allowed(Tango::AttReqType req);
    23   void write_SetPosition(Tango::WAttribute &);
    24   void read_Direction(Tango::Attribute &);
    25
    26   virtual void init_device();
    27   virtual void delete_device();
    28
    29   void get_device_properties();
    30
    31 protected :
    32   long axis[AGSM_MAX_MOTORS];
    33   DevLong position[AGSM_MAX_MOTORS];
    34   DevLong direction[AGSM_MAX_MOTORS];
    35   long state[AGSM_MAX_MOTORS];
    36
    37   Tango::DevLong *attr_Position_read;
    38   Tango::DevLong *attr_Direction_read;
    39   Tango::DevLong attr_SetPosition_write;
    40
    41   Tango::DevLong min;
    42   Tango::DevLong max;
    43
    44   Tango::DevLong *ptr;
    45 };
    46
    47 } /* End of StepperMotor namespace */

Line 1 : The StepperMotor class inherits from the DeviceImpl class

Line 4-7 : Class constructors and destructor

Line 9 : Method triggered by the DevReadPosition command

Line 10-11 : Methods triggered by the DevReadDirection command

Line 13 : Redefinition of the dev_state method of the DeviceImpl class. This method will be triggered by the State command

Line 14 : Redefinition of the dev_status method of the DeviceImpl class. This method will be triggered by the Status command

Line 16 : Redefinition of the always_executed_hook method.

Line 26 : Definition of the init_device method (declared as pure virtual by the DeviceImpl class)

Line 27 : Definition of the delete_device method

Line 31-45 : Device data

###### The StepperMotorClass class

###### Description

This class inherits from the DeviceClass class. Like the DeviceClass class, there should be only one instance of the StepperMotorClass. This is ensured because this class is written following the Singleton pattern. All controlled object class data which should be defined only once per class must be stored in this object.

###### Definition

     1 class StepperMotorClass : public DeviceClass
     2 {
     3   public:
     4     static StepperMotorClass *init(const char *);
     5     static StepperMotorClass *instance();
     6     ~StepperMotorClass() {_instance = NULL;}
     7
     8   protected:
     9     StepperMotorClass(string &);
    10     static StepperMotorClass *_instance;
    11     void command_factory();
    12
    13   private:
    14     void device_factory(Tango_DevVarStringArray *);
    15 };

Line 1 : This class is a sub-class of the DeviceClass class

Line 4-5 and 9-10: Methods and data member necessary for the Singleton pattern

Line 6 : Class destructor

Line 11 : Definition of the command_factory method declared as pure virtual in the DeviceClass call

Line 13-14 : Definition of the device_factory method declared as pure virtual in the DeviceClass class

###### The DevReadPosition class

###### Description

This is the class for the DevReadPosition command. This class implements the execute and is_allowed methods defined by the Command class. This class is necessary because this command is implemented using the inheritance model.

###### Definition

    1  class DevReadPositionCmd : public Command
    2  {
    3  public:
    4      DevReadPositionCmd(const char *,Tango_CmdArgType, Tango_CmdArgType, const char *, const char*);
    5      ~DevReadPositionCmd() {};
    6
    7      virtual bool is_allowed (DeviceImpl *, const CORBA::Any &);
    8      virtual CORBA::Any *execute (DeviceImpl *, const CORBA::Any &);
    9  };

Line 1 : The class is a sub class of the Command class

Line 4-5 : Class constructor and destructor

Line 7-8 : Definition of the is_allowed and execute method declared as pure virtual in the Command class.

###### The PositionAttr class

###### Description

This is the class for the Position attribute. This attribute is a scalar attribute and therefore inherits from the Attr base class. This class implements the read and is_allowed methods defined by the Attr class.

###### Definition

     1  class PositionAttr: public Tango::Attr
     2  {
     3  public:
     4     PositionAttr():Attr("Position",Tango::DEV_LONG,Tango::READ);
     5     ~PositionAttr() {};
     6
     7     virtual void read(Tango::DeviceImpl *dev,Tango::Attribute &att)
     8     {(static_cast<StepperMotor *>(dev))->read_Position(att);}
     9     virtual bool is_allowed(Tango::DeviceImpl *dev,Tango::AttReqType ty)
    10     {return (static_cast<StepperMotor *>(dev))->is_Position_allowed(ty);}
    11  };

Line 1 : The class is a sub class of the Attr class

Line 4-5 : Class constructor and destructor

Line 7 : Re-definition of the read method defined in the Attr class. This is simply a forward to the read_Position method of the StepperMotor class

Line 9 : Re-definition of the is_allowed method defined in the Attr class. This is also a forward to the is_Position_allowed method of the StepperMotor class

##### Startup of a device pattern

To start the device pattern implementation for stepper motor device, four methods of the StepperMotorClass class must be executed. These methods are :

1. The creation of the StepperMethodClass singleton via its init() method

2. The command_factory() method of the StepperMotorClass class

3. The attribute_factory() method of the StepperMotorClass class. This method has a default empty body for device class without attributes.

4. The device_factory() method of the StepperMotorClass class

This startup procedure is described in figure 6.2.

Figure 6.2: Device pattern startup sequence

The creation of the StepperMotorClass will automatically create an instance of the DeviceClass class. The constructor of the DeviceClass class will create the Status, State and Init command objects and store them in its command list.

The command_factory() method will simply create all the user defined commands and add them in the command list.

The attribute_factory() method will simply build a list of device attribute names.

The device_factory() method will create each StepperMotor object and store them in the StepperMotorClass instance device list. The list of devices to be created and their names is passed to the device_factory method in its input argument. StepperMotor is a sub-class of DeviceImpl class. Therefore, when a StepperMotor object is created, a DeviceImpl object is also created. The DeviceImpl constructor builds all the device attribute object(s) from the attribute list built by the attribute_factory() method.

##### Command execution sequence

The figure 6.3

Figure 6.3: Command execution timing

described how the method implementing a command is executed when a command_inout CORBA operation is requested by a client. The command_inout method of the StepperMotor object (inherited from the DeviceImpl class) is triggered by an instance of a class generated by the CORBA IDL compiler. This method calls the command_handler() method of the StepperMotorClass object (inherited from the DeviceClass class). The command_handler method searches in its command list for the wanted command (using its name). If the command is found, the always_executed_hook method of the StepperMotor object is called. Then, the is_allowed method of the wanted command is executed. If the is_allowed method returns correctly, the execute method is executed. The execute method extracts the incoming data from the CORBA object use to transmit data over the network and calls the user written method which implements the command.

##### The automatically added commands

In order to increase the common behavior of every kind of devices in a Tango control system, three commands are automatically added to each class of devices. These commands are :

- State

- Status

- Init

The default behavior of the method called by the State command depends on the device state. If the device state is ON or ALARM, the method will :

- read the attribute(s) with an alarm level defined

- check if the read value is above/below the alarm level and eventually change the device state to ALARM.

- returns the device state.

For all the other device state, the method simply returns the device state stored in the DeviceImpl class. Nevertheless, the method used to return this state (called dev_state) is defined as virtual and can be redefined in DeviceImpl sub-class. The difference between the default State command and the state CORBA attribute is the ability of the State command to signal an error to the caller by throwing an exception.

The default behavior of the method called by the Status command depends on the device state. If the device state is ON or ALARM, the method returns the device status stored in the DeviceImpl class plus additional message(s) for all the attributes which are in alarm condition. For all the other device state, the method simply returns the device status as it is stored in the DeviceImpl class. Nevertheless, the method used to return this status (called dev_status) is defined as virtual and can be redefined in DeviceImpl sub-class. The difference between the default Status command and the status CORBA attribute is the ability of the Status command to signal an error to the caller by throwing an exception.

The Init command is used to re-initialize a device without changing its network connection. This command calls the device delete_device method and the device init_device method. The rule of the delete_device method is to free memory allocated in the init_device method in order to avoid memory leak.

##### Reading/Writing attributes

###### Reading attributes

A Tango client is able to read Tango attribute(s) with the CORBA read_attributes call. Inside the device server, this call will trigger several methods of the device class (StepperMotor in our example) :

1. The always_executed_hook() method.

2. A method call read_attr_hardware(). This method is called one time per read_attributes CORBA call. The aim of this method is to read the device hardware and to store the result in a device class data member.

3. For each attribute to be read

   1. A method called is_<att name>_allowed(). The rule of this method is to allow (or disallow) the next method to be executed. It is usefull for device with some attributes which can be read only in some precise conditions. It has one parameter which is the request type (read or write)

   2. A method called read_<att name>(). The aim of this method is to extract the real attribute value from the hardware read-out and to store the attribute value into the attribute object. It has one parameter which is a reference to the Attribute object to be read.

The figure 6.4 is a drawing of these method calls sequencing. For attribute always readable, a default is_allowed method is provided. This method always returns true.

Figure 6.4: Read attribute sequencing

###### Writing attributes

A Tango client is able to write Tango attribute(s) with the CORBA write_attributes call. Inside a device server, this call will trigger several methods of the device class (StepperMotor in our example)

1. The always_executed_hook() method.

2. For each attribute to be written

   1. A method called is_<att name>_allowed(). The rule of this method is to allow (or disallow) the next method to be executed. It is usefull for device with some attributes which can be written only in some precise conditions. It has one parameter which is the request type (read or write)

   2. A method called write_<att name>(). It has one parameter which is a reference to the WAttribute object to be written. The aim of this method is to get the data to be written from the WAttribute object and to write this value into the corresponding hardware. If the hardware support writing several data in one go, code the hardware access in the write_attr_harware() method.

3. The write_attr_hardware() method. The rule of this method is to effectively write the hardware in case it is able to support writing several data in one go. If this is not the case, don’t code this method (a default implementation is coded in the Tango base class) and code the real hardware access in each write_<att name>() method.

The figure 6.5 is a drawing of these method calls sequencing. For attribute always writeable, a default is_allowed method is provided. This method always allways returns true.

Figure 6.5: Write attribute sequencing

##### The device server framework

###### Vocabulary

A device server pattern implementation is embedded in a process called a device server. Several instances of the same device server process can be used in a Tango control system. To identify instances, a device server process is started with an instance name which is different for each instance. The device server name is the couple device server executable name/device server instance name. For instance, a device server started with the following command

    $ Perkin id11

starts a device server process with an instance name id11, an executable name Perkin and a device server name Perkin/id11.

###### The DServer class

In order to simplify device server process administration, a device of the DServer class is automatically added to each device server process. Thus, every device server process supports the same set of administration commands. The implementation of this DServer class follows the device pattern and therefore, its device behaves like any other devices. The device name is

dserver/device server executable name/device server instance name

For instance, for the device server process described in Vocabulary, the dserver device name is dserver/perkin/id11. This name is returned by the adm_name CORBA attribute available for every device. On top of the three automatically added commands, this device supports the following commands :

- DevRestart

- RestartServer

- QueryClass

- QueryDevice

- Kill

- AddLoggingTarget (C++ server only)

- RemoveLoggingTarget (C++ server only)

- GetLoggingTarget (C++ server only)

- GetLoggingLevel (C++ server only)

- SetLoggingLevel (C++ server only)

- StopLogging (C++ server only)

- StartLogging (C++ server only)

- PolledDevice

- DevPollStatus

- AddObjPolling

- RemObjPolling

- UpdObjPollingPeriod

- StartPolling

- StopPolling

- EventSubscriptionChange

- ZmqEventSubscriptionChange

- LockDevice

- UnLockDevice

- ReLockDevices

- DevLockStatus

These commands will be fully described later in this document.

Several controlled object classes can be embedded within the same device server process and it is the rule of this device to create all these device server patterns and to call their command and device factories as described in Startup of a device pattern. The name and number of all the classes to be created is known to this device after the execution of a method called class_factory. It is the user responsibility to write this method.

###### The Tango::Util class

###### Description

This class merges a complete set of utilities in the same class. It is implemented as a singleton and there is only one instance of this class per device server process. It is mandatory to create this instance in order to run a device server. The description of all the methods implemented in this class can be found in cppTango API reference.

###### Contents

Within this class, you can find :

- Static method to create/retrieve the singleton object

- Miscellaneous utility methods like getting the server output trace level, getting the CORBA ORB pointer, retrieving device server instance name, getting the server PID and more. Please, refer to cppTango API reference to get a complete list of all these utility methods.

- Method to create the device pattern implementing the DServer class (server_init())

- Method to start the server (server_run())

- TANGO database related methods

###### A complete device server

Within a complete device server, at least two implementations of the device server pattern are created (one for the dserver object and the other for the class of devices to control). On top of that, one instance of the Tango::Util class must also be created.

Figure 6.6: A complete device server

A drawing of a complete device server is in figure 6.6.

###### Device server startup sequence

The device server startup sequence is the following :

1. Create an instance of the Tango::Util class. This will initialize the CORBA Object Request Broker

2. Called the server_init method of the Tango::Util instance The call to this method will :

   1. Create the DServerClass object of the device pattern implementing the DServer class. This will create the dserver object which during its construction will :

      1. Called the class_factory method of the DServer object. This method must create all the xxxClass instance for all the device pattern implementation embedded in the device server process.

      2. Call the command_factory and device_factory of all the classes previously created. The list of devices passed to each call to the device_factory method is retrieved from the TANGO database.

3. Wait for incoming request with the server_run() method of the Tango::Util class.

#### Exchanging data between client and server

Exchanging data between clients and server means most of the time passing data between processes running on different computer using the network. Tango limits the type of data exchanged between client and server and defines a way to exchange these data. This chapter details these features. Memory allocation and error reporting are also discussed.

All the rules described in this chapter are valid only for data exchanged between client and server. For device server internal data, classical C++ types can be used.

##### Command / Attribute data types

Commands have a fixed calling syntax - consisting of one input argument and one output argument. Arguments type must be chosen out of a fixed set of 24 data types. Attributes support a sub-set of these data types (those are the data type with the (1) note) plus the DevEnum data type. The following table details type name, code and the corresponding CORBA IDL types.

The type name used in the type name column of this table is the C++ name. In the IDL file, all the Tango definition are grouped in a IDL module named Tango. The IDL module maps to C++ namespace. Therefore, all the data type are parts of a namespace called Tango.

Type name | IDL type
--- | ---
Tango::DevBoolean (1) | boolean
Tango::DevShort (1) | short
Tango::DevEnum (2) | short (See chapter on advanced features)
Tango::DevLong (1) | long
Tango::DevLong64 (1) | long long
Tango::DevFloat (1) | float
Tango::DevDouble (1) | double
Tango::DevUShort (1) | unsigned short
Tango::DevULong (1) | unsigned long
Tango::DevULong64 (1) | unsigned long long
Tango::DevString (1) | string
Tango::DevVarCharArray | sequence of unsigned char
Tango::DevVarShortArray | sequence of short
Tango::DevVarLongArray | sequence of long
Tango::DevVarLong64Array | sequence of long long
Tango::DevVarFloatArray | sequence of float
Tango::DevVarDoubleArray | sequence of double
Tango::DevVarUShortArray | sequence of unsigned short
Tango::DevVarULongArray | sequence of unsigned long
Tango::DevVarULong64Array | sequence of unsigned long long
Tango::DevVarStringArray | sequence of string
Tango::DevVarLongStringArray | structure with a sequence of long and a sequence of string
Tango::DevVarDoubleStringArray | structure with a sequence of double and a sequence of string
Tango::DevState (1) | enumeration
Tango::DevEncoded (1) | structure with a string and a sequence of char

The CORBA Interface Definition Language uses a type called sequence for variable length array. The Tango::DevUxxx types are used for unsigned types. The Tango::DevVarxxxxArray must be used when the data to be transferred are variable length array. The Tango::DevVarLongStringArray and Tango::DevVarDoubleStringArray are structures with two fields which are variable length array of Tango long (32 bits) and variable length array of strings for the Tango::DevVarLongStringArray and variable length array of double and variable length array of string for the Tango::DevVarDoubleStringArray. The Tango::State type is used by the State command to return the device state.

###### Using data types with C++

Unfortunately, the mapping between IDL and C++ was defined before the C++ class library had been standardized. This explains why the standard C++ string class or vector classes are not used in the IDL to C++ mapping.

TANGO commands/attributes argument types can be grouped on five groups depending on the IDL data type used. These groups are :

1. Data type using basic types (Tango::DevBoolean, Tango::DevShort, Tango::DevEnum, Tango::DevLong, Tango::DevFloat, Tango::DevDouble, Tango::DevUshort and Tango::DevULong)

2. Data type using strings (Tango::DevString type)

3. Data types using sequences (Tango::DevVarxxxArray types except Tango::DevVarLongStringArray and Tango::DevVarDoubleStringArray)

4. Data types using structures (Tango::DevVarLongStringArray and Tango::DevVarDoubleStringArray types)

5. Data type using IDL enumeration (Tango::DevState type)

In the following sub chapters, only summaries of the IDL to C++ mapping are given.

###### Basic types

For these types, the mapping between IDL and C++ is obvious and defined in the following table.

Tango type name | IDL type | C++ | typedef
--- | --- | --- | ---
Tango::DevBoolean | boolean | CORBA::Boolean | unsigned char
Tango::DevShort | short | CORBA::Short | short
Tango::DevEnum | short | CORBA::Short |
Tango::DevLong | long | CORBA::Long | int
Tango::DevLong64 | long long | CORBA::LongLong | long long or long (64 bits chip)
Tango::DevFloat | float | CORBA::Float | float
Tango::DevDouble | double | CORBA::Double | double
Tango::DevUShort | unsigned short | CORBA::UShort | unsigned short
Tango::DevULong | unsigned long | CORBA::ULong | unsigned long
Tango::DevULong64 | unsigned long long | CORBA:ULongLong | unsigned long long or unsigned long (64 bits chip)

The types defined in the column named C++ should be used for a better portability. All these types are defined in the CORBA namespace and therefore their qualified names is CORBA::xxx. The Tango data type DevEnum is a special case described in detail in the chapter about advanced features.

###### Strings

Strings are mapped to char *. The use of new and delete for dynamic allocation of strings is not portable. Instead, you must use helper functions defined by CORBA (in the CORBA namespace) and Tango. These functions are :

    1      char *CORBA::string_alloc(unsigned long len);
    2      char *Tango::string_dup(const char *);
    3      void Tango::string_free(char *);

These functions handle dynamic memory for strings. The string_alloc function allocates one more byte than requested by the len parameter (for the trailing 0). The function string_dup combines the allocation and copy. Both string_alloc and string_dup return a null pointer if allocation fails. The string_free function must be used to free memory allocated with string_alloc and string_dup. Calling string_free for a null pointer is safe and does nothing. Tango::string_free is available only since cppTango 9.3.3. Tango::string_dup is available only since Tango 9. If you are using an older version of the Tango C++ library, you should use CORBA::string_free and CORBA::string_dup instead. The following code fragment is an example of the Tango::DevString type usage :

    1     Tango::DevString str = CORBA::string_alloc(5);
    2     strcpy(str,"TANGO");
    3
    4     Tango::DevString str1 = Tango::string_dup("Do you want to danse TANGO?");
    5
    6     Tango::string_free(str);
    7     Tango::string_free(str1);

Line 1-2 : TANGO is a five letters string. The CORBA::string_alloc function parameter is 5 but the function allocates 6 bytes

Line 4 : Example of the Tango::string_dup function

Line 6-7 : Memory deallocation

###### Sequences

IDL sequences are mapped to C++ classes that behave like vectors with a variable number of elements. Each IDL sequence type results in a separate C++ class. Within each class representing a IDL sequence types, you find the following method (only the main methods are related here) :

1. Four constructors.

   1. A default constructor which creates an empty sequence.

   2. The maximum constructor which creates a sequence with memory allocated for at least the number of elements passed as argument. This does not limit the number of element in the sequence but only the way how memory is allocated to store element

   3. A sophisticated constructor where it is possible to assign the memory used by the sequence with a preallocated buffer.

   4. A copy constructor which does a deep copy

2. An assignment operator which does a deep copy

3. A length accessor which simply returns the current number of elements in the sequence

4. A length modifier which changes the length of the sequence (which is different than the number of elements in the sequence)

5. Overloading of the [] operator. The subscript operator [] provides access to the sequence element. For a sequence containing elements of type T, the [] operator is overloaded twice to return value of type T & and const T &. Insertion into a sequence using the [] operator for the const T & make a deep copy. Sequence are numbered between 0 and length() -1.

Note that using the maximum constructor will not prevent you from setting the length of the sequence with a call to the length modifier. The following code fragment is an example of how to use a Tango::DevVarLongArray type

     1     Tango::DevVarLongArray *mylongseq_ptr;
     2     mylongseq_ptr = new Tango::DevVarLongArray();
     3     mylongseq_ptr->length(4);
     4
     5     (*mylongseq_ptr)[0] = 1;
     6     (*mylongseq_ptr)[1] = 2;
     7     (*mylongseq_ptr)[2] = 3;
     8     (*mylongseq_ptr)[3] = 4;
     9
    10     // (*mylongseq_ptr)[4] = 5;
    11
    12     CORBA::Long nb_elt = mylongseq_ptr->length();
    13
    14     mylongseq_ptr->length(5);
    15     (*mylongseq_ptr)[4] = 5;
    16
    17     for (int i = 0;i < mylongseq_ptr->length();i++)
    18          cout << "Sequence elt " << i + 1 << " = " << (*mylongseq_ptr)[i] << endl;

Line 1 : Declare a pointer to Tango::DevVarLongArray type which is a sequence of long

Line 2 : Create an empty sequence

Line 3 : Change the length of the sequence to 4

Line 5 - 8 : Initialize sequence elements

Line 10 ; Oups !!! The length of the sequence is 4. The behavior of this line is undefined and may be a core can be dumped at run time

Line 12 : Get the number of element actually stored in the sequence

Line 14-15 : Grow the sequence to five elements and initialize element number 5

Line 17-18 : Print sequence element

Another example for the Tango::DevVarStringArray type is given

     1     Tango::DevVarStringArray mystrseq(4);
     2     mystrseq.length(4);
     3
     4     mystrseq[0] = Tango::string_dup("Rock and Roll");
     5     mystrseq[1] = Tango::string_dup("Bossa Nova");
     6     mystrseq[2] = Tango::string_dup("Waltz");
     7     mystrseq[3] = Tango::string_dup("Tango");
     8
     9     CORBA::Long nb_elt = mystrseq.length();
    10
    11     for (int i = 0;i < mystrseq.length();i++)
    12          cout << "Sequence elt " << i + 1 << " = " << mystrseq[i] << endl;

Line 1 : Create a sequence using the maximum constructor

Line 2 : Set the sequence length to 4. This is mandatory even if you used the maximum constructor.

Line 4-7 : Populate the sequence

Line 9 : Get how many strings are stored into the sequence

Line 11-12 : Print sequence elements.

###### Structures

Only three TANGO types are defined as structures. These types are the Tango::DevVarLongStringArray, the Tango::DevVarDoubleStringArray and the Tango::DevEncoded data type. IDL structures map to C++ structures with corresponding members. For the Tango::DevVarLongStringArray, the two members are named svalue for the sequence of strings and lvalue for the sequence of longs. For the Tango::DevVarDoubleStringArray, the two structure members are called svalue for the sequence of strings and dvalue for the sequence of double. For the Tango::DevEncoded, the two structure members are called encoded_format for a string describing the data coding and encoded_data for the data themselves. The encoded_data field type is a Tango::DevVarCharArray. An example of the usage of the Tango::DevVarLongStringArray type is detailed below.

    1     Tango::DevVarLongStringArray my_vl;
    2
    3     myvl.svalue.length(2);
    4     myvl.svalue[0] = CORBA_string_dup("Samba");
    5     myvl.svalue[1] = CORBA_string_dup("Rumba");
    6
    7     myvl.lvalue.length(1);
    8     myvl.lvalue[0] = 10;

Line 1 : Declaration of the structure

Line 3-5 : Initialization of two strings in the sequence of string member

Line 7-8 : Initialization of one long in the sequence of long member

###### The DevState data type

The Tango::DevState data type is used to transfer device state between client and server. It is a IDL enumeration. IDL enumerated types map to C++ enumerations (amazing no!) with a trailing dummy enumerator to force enumeration to be a 32 bit type. The first enumerator will have the value 0, the next one will have the value 1 and so on.

    1     Tango::DevState state;
    2
    3     state = Tango::ON;
    4     state = Tango::FAULT;

##### Passing data between client and server

In order to have one definition of the CORBA operation used to send a command to a device whatever the command data type is, TANGO uses CORBA IDL any object. The IDL type any provides a universal type that can hold a value of arbitrary IDL types. Type any therefore allows you to send and receive values whose types are not fixed at compile time.

Type any is often compared to a void * in C. Like a pointer to void, an any value can denote a datum of any type. However, there is an important difference; whereas a void * denotes a completely untyped value that can be interpreted only with advance knowledge of its type, values of type any maintain type safety. For example, if a sender places a string value into an any, the receiver cannot extract the string as a value of the wrong type. Attempt to read the contents of an any as the wrong type cause a run-time error.

Internally, a value of type any consists of a pair of values. One member of the pair is the actual value contained inside the any and the other member of the pair is the type code. The type code is a description of the value’s type. The type description is used to enforce type safety when the receiver extracts the value. Extraction of the value succeeds only if the receiver extracts the value as a type that matches the information in the type code.

Within TANGO, the command input and output parameters are objects of the IDL any type. Only insertion/extraction of all types defined as command data types is possible into/from these any objects.

###### C++ mapping for IDL any type

The IDL any maps to the C++ class CORBA::Any. This class contains a large number of methods with mainly methods to insert/extract data into/from the any. It provides a default constructor which builds an any which contains no value and a type code that indicates “no value”. Such an any must be used for command which does not need input or output parameter. The operator <<= is overloaded many times to insert data into an any object. The operator >>= is overloaded many times to extract data from an any object.

###### Inserting/Extracting TANGO basic types

The insertion or extraction of TANGO basic types is straight forward using the <<= or >>= operators. Nevertheless, the Tango::DevBoolean type is mapped to a unsigned char and other IDL types are also mapped to char C++ type (The unsigned is not taken into account in the C++ overloading algorithm). Therefore, it is not possible to use operator overloading for these IDL types which map to C++ char. For the Tango::DevBoolean type, you must use the CORBA::Any::from_boolean or CORBA::Any::to_boolean intermediate objects defined in the CORBA::Any class.

###### Inserting/Extracting TANGO strings

The <<= operator is overloaded for const char * and always makes a deep copy. This deep copy is done using the Tango::string_dup function. The extraction of strings uses the >>= overloaded operator. The main point is that the Any object retains ownership of the string, so the returned pointer points at memory inside the Any. This means that you must not deallocate the extracted string and you must treat the extracted string as read-only.

###### Inserting/Extracting TANGO sequences

Insertion and extraction of sequences also uses the overloaded <<= and >>= operators. The insertion operator is overloaded twice: once for insertion by reference and once for insertion by pointer. If you insert a value by reference, the insertion makes a deep copy. If you insert a value by pointer, the Any assumes the ownership of the pointed-to memory.

Extraction is always by pointer. As with strings, you must treat the extracted pointer as read-only and must not deallocate it because the pointer points at memory internal to the Any.

###### Inserting/Extracting TANGO structures

This is identical to inserting/extracting sequences.

###### Inserting/Extracting TANGO enumeration

This is identical to inserting/extracting basic types

     1    CORBA::Any a;
     2    Tango::DevLong l1,l2;
     3    l1 = 2;
     4    a <<= l1;
     5    a >>= l2;
     6
     7    CORBA::Any b;
     8    Tango::DevBoolean b1,b2;
     9    b1 = true;
    10    b <<= CORBA::Any::from_boolean(b1);
    11    b >>= CORBA::Any::to_boolean(b2);
    12
    13    CORBA::Any s;
    14    Tango::DevString str1,str2;
    15    str1 = "I like dancing TANGO";
    16    s <<= str1;
    17    s >>= str2;
    18
    19  //   Tango::string_free(str2);
    20  //   a <<= Tango::string_dup("Oups");
    21
    22    CORBA::Any seq;
    23    Tango::DevVarFloatArray fl_arr1;
    24    fl_arr1.length(2);
    25    fl_arr1[0] = 1.0;
    26    fl_arr1[1] = 2.0;
    27    seq <<= fl_arr1;
    28    const Tango::DevVarFloatArray *fl_arr_ptr;
    29    seq >>= fl_arr_ptr;
    30
    31  //   delete fl_arr_ptr;

Line 1-5 : Insertion and extraction of Tango::DevLong type

Line 7-11 Insertion and extraction of Tango::DevBoolean type using the CORBA::Any::from_boolean and CORBA::Any::to_boolean intermediate structure

Line 13-17 : Insertion and extraction of Tango::DevString type

Line 19 : Wrong ! You should not deallocate a string extracted from an any

Line 20 : Wrong ! Memory leak because the <<= operator will do the copy.

Line 22-29 : Insertion and extraction of Tango::DevVarxxxArray types. This is an insertion by reference and the use of the <<= operator makes a deep copy of the sequence. Therefore, after line 27, it is possible to deallocate the sequence

Line 31: Wrong.! You should not deallocate a sequence extracted from an any

###### The insert and extract methods of the Command class

In order to simplify the insertion/extraction into/from Any objects, small helper methods have been written in the Command class. The signatures of these methods are :

    1          void extractextract(const CORBA::Any &,<Tango type> &);
    2          CORBA::Any *insertinsert(<Tango type>);

An extract method has been written for all Tango types. These method extract the data from the Any object passed as parameter and throw an exception if the Any data type is incompatible with the awaiting type. An insert method have been written for all Tango types. These method create an Any object, insert the data into the Any and return a pointer to the created Any. For Tango types mapped to sequences or structures, two insert methods have been written: one for the insertion from pointer and the other for the insertion from reference. For Tango strings, two insert methods have been written: one for insertion from a classical Tango::DevString type and the other from a const Tango::DevString type. The first one deallocate the memory after the insert into the Any object. The second one only inserts the string into the Any object.

The previous example can be rewritten using the insert/extract helper methods (We suppose that we can use the Command class insert/extract methods)

     1    Tango::DevLong l1,l2;
     2    l1 = 2;
     3    CORBA::Any *a_ptr = insert(l1);
     4    extract(*a_ptr,l2);
     5
     6    Tango::DevBoolean b1,b2;
     7    b1 = true;
     8    CORBA::Any *b_ptr = insert(b1);
     9    extract(*b_ptr,b2);
    10
    11    Tango::DevString str1,str2;
    12    str1 = "I like dancing TANGO";
    13    CORBA::Any *s_ptr = insert(str1);
    14    extract(*s_ptr,str2);
    15
    16    Tango::DevVarFloatArray fl_arr1;
    17    fl_arr1.length(2);
    18    fl_arr1[0] = 1.0;
    19    fl_arr1[1] = 2.0;
    20    insert(fl_arr1);
    21    CORBA::Any *seq_ptr = insert(fl_arr1);
    22    Tango::DevVarFloatArray *fl_arr_ptr;
    23    extract(*seq_ptr,fl_arr_ptr);

Line 1-4 : Insertion and extraction of Tango::DevLong type

Line 6-9 : Insertion and extraction of Tango::DevBoolean type

Line 11-14 : Insertion and extraction of Tango::DevString type

Line 16-23 : Insertion and extraction of Tango::DevVarxxxArray types. This is an insertion by reference which makes a deep copy of the sequence. Therefore, after line 20, it is possible to deallocate the sequence

##### C++ memory management

The rule described here are valid for variable length command data types like Tango::DevString or all the Tango:: DevVarxxxxArray types.

The method executing the command must allocate the memory used to pass data back to the client or use static memory (like buffer declares as object data member. If necessary, the ORB will deallocate this memory after the data have been sent to the caller. Fortunately, for incoming data, the method have no memory management responsibilities. The details about memory management given in this chapter assume that the insert/extract methods of the Tango::Command class are used and only the method in the device object is discussed.

###### For string

Example of a method receiving a Tango::DevString and returning a Tango::DevString is detailed just below

     1  Tango::DevString MyDev::dev_string(Tango::DevString argin)
     2  {
     3      Tango::DevString        argout;
     4
     5      cout << "the received string is " << argin << endl;
     6
     7      string str("Am I a good Tango dancer ?");
     8      argout = new char[str.size() + 1];
     9      strcpy(argout,str.c_str());
    10
    11      return argout;
    12  }

Note that there is no need to deallocate the memory used by the incoming string. Memory for the outgoing string is allocated at line 8, then it is initialized at the following line. The memory allocated at line 8 will be automatically freed by the usage of the Command::insert() method. Using this schema, memory is allocated/freed each time the command is executed. For constant string length, a statically allocated buffer can be used.

    1  Tango::ConstDevString MyDev::dev_string(Tango::DevString argin)
    2  {
    3      Tango::ConstDevString   argout;
    4
    5      cout << "the received string is " << argin << endl;
    6
    7      argout = "Hello world";
    8      return argout;
    9  }

A Tango::ConstDevString data type is used. It is not a new data Tango data type. It has been introduced only to allows Command::insert() method overloading. The argout pointer is initialized at line 7 with memory statically allocated. In this case, no memory will be freed by the Command::insert() method. There is also no memory copy in the contrary of the previous example. A buffer defined as object data member can also be used to set the argout pointer.

###### For array/sequence

Example of a method returning a Tango::DevVarLongArray is detailed just below

     1  Tango::DevVarLongArray *MyDev::dev_array()
     2  {
     3      Tango::DevVarLongArray  *argout  = new Tango::DevVarLongArray();
     4
     5      long output_array_length = ...;
     6      argout->length(output_array_length);
     7      for (int i = 0;i < output_array_length;i++)
     8          (*argout)[i] = i;
     9
    10      return argout;
    11  }

In this case, memory is allocated at line 3 and 6. Then, the sequence is populated. The sequence is created and returned using pointer. The Command::insert() method will insert the sequence into the CORBA::Any object using this pointer. Therefore, the CORBA::Any object will take ownership of the allocated memory. It will free it when it will be destroyed by the CORBA ORB after the data have been sent away. It is also possible to use a statically allocated memory and to avoid copying in the sequence used to returned the data. This is explained in the following example assuming a buffer of long data is declared as device data member and named buffer.

    1  Tango::DevVarLongArray *MyDev::dev_array()
    2  {
    3      Tango::DevVarLongArray  *argout;
    4
    5      long output_array_length = ...;
    6      argout = create_DevVarLongArray(buffer,output_array_length);
    7      return argout;
    8  }

At line 3 only a pointer to a DevVarLongArray is defined. This pointer is set at line 6 using the create_DevVarLongArray() method. This method will create a sequence using this buffer without memory allocation and with minimum copying. The Command::insert() method used here is the same than the one used in the previous example. The sequence is created in a way that the destruction of the CORBA::Any object in which the sequence will be inserted will not destroy the buffer. The following create_xxx methods are defined in the DeviceImpl class :

Method name | data type
--- | ---
create_DevVarCharArray() | unsigned char
create_DevVarShortArray() | short
create_DevVarLongArray() | DevLong
create_DevVarLong64Array() | DevLong64
create_DevVarFloatArray() | float
create_DevVarDoubleArray() | double
create_DevVarUShortArray() | unsigned short
create_DevVarULongArray() | DevULong
create_DevVarULong64Array() | DevULong64

###### For string array/sequence

Example of a method returning a Tango::DevVarStringArray is detailed just below

     1  Tango::DevVarStringArray *MyDev::dev_str_array()
     2  {
     3     Tango::DevVarStringArray *argout  = new Tango::DevVarStringArray();
     4
     5     argout->length(3);
     6     (*argout)[0] = Tango::string_dup("Rumba");
     7     (*argout)[1] = Tango::string_dup("Waltz");
     8     string str("Jerck");
     9     (*argout)[2] = Tango::string_dup(str.c_str());
    10     return argout;
    11  }

Memory is allocated at line 3 and 5. Then, the sequence is populated at lines 6,7 and 9. The usage of the Tango::string_dup function also allocates memory. The sequence is created and returned using pointer. The Command::insert() method will insert the sequence into the CORBA::Any object using this pointer. Therefore, the CORBA::Any object will take ownership of the allocated memory. It will free it when it will be destroyed by the CORBA ORB after the data have been sent away. For portability reason, the ORB uses the CORBA::string_free function to free the memory allocated for each string. This is why the corresponding Tango::string_dup or CORBA::string_alloc function must be used to reserve this memory.It is also possible to use a statically allocated memory and to avoid copying in the sequence used to returned the data. This is explained in the following example assuming a buffer of pointer to char is declared as device data member and named int_buffer.

    1  Tango::DevVarStringArray *DocDs::dev_str_array()
    2  {
    3     int_buffer[0] = "first";
    4     int_buffer[1] = "second";
    5
    6     Tango::DevVarStringArray *argout;
    7     argout = create_DevVarStringArray(int_buffer,2);
    8     return argout;
    9  }

The intermediate buffer is initialized with statically allocated memory at lines 3 and 4. The returned sequence is created at line 7 with the create_DevVarStringArray() method. Like for classical array, the sequence is created in a way that the destruction of the CORBA::Any object in which the sequence will be inserted will not destroy the buffer.

###### For Tango composed types

Tango supports only two composed types which are Tango::DevVarLongStringArray and Tango::DevVarDoubleStringArray. These types are translated to C++ structure with two sequences. It is not possible to use memory statically allocated for these types. Each structure element must be initialized as described in the previous sub-chapters using the dynamically allocated memory case.

##### Reporting errors

Tango uses the C++ try/catch plus exception mechanism to report errors. Two kind of errors can be transmitted between client and server :

1. CORBA system error. These exceptions are raised by the ORB and indicates major failures (A communication failure, An invalid object reference…)

2. CORBA user exception. These kind of exceptions are defined in the IDL file. This allows an exception to contain an arbitrary amount of error information of arbitrary type.

TANGO defines one user exception called DevFailed. This exception is a variable length array of DevError type (a sequence of DevError). The DevError type is a four fields structure. These fields are :

1. A string describing the type of the error. This string replaces an error code and allows a more easy management of include files.

2. The error severity. It is an enumeration with the three values which are WARN, ERR or PANIC.

3. A string describing in plain text the reason of the error

4. A string describing the origin of the error

The Tango::DevFailed type is a sequence of DevError structures in order to transmit to the client what is the primary error reason when several classes are used within a command. The sequence element 0 must be the DevError structure describing the primary error. A method called print_exception() defined in the Tango::Except class prints the content of exception (CORBA system exception or Tango::DevFailed exception). Some static methods of the Tango::Except class called throw_exception() can be used to throw Tango::DevFailed exception. Some other static methods called re_throw_exception() may also be used when the user want to add a new element in the exception sequence and re-throw the exception. Details on these methods can be found in cppTango API reference.

###### Example of throwing exception

This example is a piece of code from the command_handler() method of the DeviceImpl class. An exception is thrown to the client to indicate that the requested command is not defined in the command list.

     1    TangoSys_OMemStream o;
     2
     3    o << "Command " << command << " not found" << ends;
     4    Tango::Except::throw_exception("API_CommandNotFound",
     5                                o.str(),
     6                                "DeviceClass::command_handler");
     7
     8
     9    try
    10    {
    11        .....
    12    }
    13    catch (Tango::DevFailed &e)
    14    {
    15        TangoSys_OMemStream o;
    16
    17        o << "Command " << command << " not found" << ends;
    18        Tango::Except::re_throw_exception(e,
    19                                  "API_CommandNotFound",
    20                                  o.str(),
    21                                  "DeviceClass::command_handler");
    22    }

Line 1 : Build a memory stream. Use the TangoSys_MemStream because memory streams are not managed the same way between Windows and Unix

Line 3 : Build the reason string in the memory stream

Line 4-5 : Throw the exception to client using one of the throw_exception static method of the Except class. This throw_exception method used here allows the definition of the error type string, the reason string and the origin string of the DevError structure. The remaining DevError field (the error severity) will be set to its default value. Note that the first and third parameters are casted to a const char *. Standard C++ defines that such a string is already a const char * but the GNU C++ compiler (release 2.95) does not use this type inside its function overloading but rather uses a char * which leads to calling the wrong function.

Line 13-22 : Re-throw an already catched tango::DevFailed exception with one more element in the exception sequence.

#### The Tango Logging Service

A first introduction about this logging service has been done in section The Tango Logging Service.

The TANGO Logging Service (TLS) gives the user the control over how much information is actually generated and to where it goes. In practice, the TLS allows to select both the logging level and targets of any device within the control system.

##### Logging Targets

The TLS implementation allows each device logging requests to print simultaneously to multiple destinations. In the TANGO terminology, an output destination is called a logging target. Currently, targets exist for console, file and log consumer device.

CONSOLE: logs are printed to the console (i.e. the standard output),

FILE: logs are stored in a XML file. A rolling mechanism is used to backup the log file when it reaches a certain size (see below),

DEVICE: logs are sent to a device implementing a well known TANGO interface (see section The Log Consumer interface for a definition). One implementation of a log consumer associated to a graphical user interface is available within the Tango package. It is called the LogViewer.

The device’s logging behavior can be control by adding and/or removing targets.

Note : When the size of a log file (for file logging target) reaches the so-called rolling-file-threshold (rft), it is backuped as current_log_file_name + _1 and a new current_log_file_name is opened. Obviously, there is only one backup file at a time (i.e. any existing backup is destroyed before the current log file is backuped). The default threshold is 20 Mb, the minimum is 500 Kb and the maximum is 1000 Mb.

##### Logging Levels

Devices can be assigned a logging level. It acts as a filter to control the kind of information sent to the targets. Since, there are (usually) much more low level log statements than high level statements, the logging level also control the amount of information produced by the device. The TLS provides the following levels (semantic is just given to be indicative of what could be log at each level):

OFF: Nothing is logged

FATAL: A fatal error occurred. The process is about to abort

ERROR: An (unrecoverable) error occurred but the process is still alive

WARN: An error occurred but could be recovered locally

INFO: Provides information on important actions performed

DEBUG: Generates detailed information describing the internal behavior of a device

Levels are ordered the following way:

DEBUG < INFO < WARN < ERROR < FATAL < OFF

For a given device, a level is said to be enabled if it is greater or equal to the logging level assigned to this device. In other words, any logging request which level is lower than the device’s logging level is ignored.

Note: The logging level can’t be controlled at target level. The device’s targets shared the same device logging level.

##### Sending TANGO Logging Messages

###### Logging macros in C++

The TLS provides the user with easy to use C++ macros with printf and stream like syntax. For each logging level, a macro is defined in both styles:

- LOG_{FATAL, ERROR, WARN, INFO or DEBUG}

- {FATAL, ERROR, WARN, INFO or DEBUG}_STREAM

These macros are supposed to be used within the device’s main implementation class (i.e. the class that inherits (directly or indirectly) from the Tango::DeviceImpl class). In this context, they produce logging messages containing the device name. In other words, they automatically identify the log source. Section C++ logging in the name of a device gives a trick to log in the name of device outside its main implementation class. Printf like example:

LOG_DEBUG((Msg#%d - Hello world, i++));

Stream like example:

DEBUG_STREAM << Msg# << i++ << - Hello world << endl;

These two logging requests are equivalent. Note the double parenthesis in the printf version.

###### C++ logging in the name of a device

A device implementation is sometimes spread over several classes. Since all these classes implement the same device, their logging requests should be associated with this device name. Unfortunately, the C++ logging macros can’t be used because they are outside the device’s main implementation class. The Tango::LogAdapter class is a workaround for this limitation.

Any method not member of the device’s main implementation class, which send log messages associated to a device must be a member of a class inheriting from the Tango::LogAdapter class. Here is an example:

     1 class MyDeviceActualImpl: public Tango::LogAdapter
     2 {
     3 public :
     4    MyDeviceActualImpl(...,Tango::DeviceImpl *device,...)
     5    :Tango::LogAdpater(device)
     6    {
     7          ....
     8 //
     9 // The following log is associated to the device passed to the constructor
    10 //
    11         DEBUG_STREAM << "In MyDeviceActualImpl constructor" << endl;
    12
    13         ....
    14    }
    15 };

#### Writing a device server process

Writing a device server can be made easier by adopting the correct approach. This chapter will describe how to write a device server process. It is divided into the following parts : understanding the device, defining device commands/attributes/pipes, choosing device state and writing the necessary classes. All along this chapter, examples will be given using the stepper motor device server. Writing a device server for our stepper motor example device means writing :

- The main function

- The class_factory method (only for C++ device server)

- The StepperMotorClass class

- The DevReadPositionCmd and DevReadDirectionCmd classes

- The PositionAttr, SetPositionAttr and DirectionAttr classes

- The StepperMotor class.

All these functions and classes will be detailed. The stepper motor device server described in this chapter supports 2 commands and 3 attributes which are :

- Command DevReadPosition implemented using the inheritance model

- Command DevReadDirection implemented using the template command model

- Attribute Position (position of the first motor). This attribute is readable and is linked with a writable attribute (called SetPosition). When the value of this attribute is requested by the client, the value of the associated writable attribute is also returned.

- Attribute SetPosition (writable attribute linked with the Position attribute). This attribute has some properties with user defined default value.

- Attribute Direction (direction of the first motor)

As the reader will understand during the reading of the following sub-chapters, the command and attributes classes (DevReadPositionCmd, DevReadDirectionCmd, PositionAttr, SetPositionAttr and DirectionAttr) are very simple classes. A tool called Pogo has been developped to automatically generate/maintain these classes and to write part of the code needed in the remaining one. See Pogo manual to know more on this Pogo tool.

In order to also gives an example of how the database objects part of the Tango device pattern could be used, our device have two properties. These properties are of the Tango long data types and are named “Max” and “Min”.

##### Understanding the device

The first step before writing a device server is to develop an understanding of the hardware to be programmed. The Equipment Responsible should have description of the hardware and its operating modes (manuals, spec sheets etc.). The Equipment Responsible must also provide specifications of what the device server should do. The Device Server Programmer should demand an exact description of the registers, alarms, interlocks and any timing constraints which have to be kept. It is very important to have a good understanding of the device interfacing before starting designing a new class.

Once the Device Server Programmer has understood the hardware the next important step is to define what is a logical device i.e. what part of the hardware will be abstracted out and treated as a logical device. In doing so the following points of the TDSOM should be kept in mind

- Each device is known and accessed by its ascii name.

- The device is exported onto the network to be imported by applications.

- Each device belongs to a class.

- A list of commands exists per device.

- Applications use the device server api to execute commands on a device.

The above points have to be taken into account when designing the level of device abstraction. The definition of what is a device for a certain hardware is primarily the job of the Device Server Programmer and the Applications Programmer but can also involve the Equipment Responsible. The Device Server Programmer should make sure that the Applications Programmer agrees with her definition of what is a device.

Here are some guidelines to follow while defining the level of device abstraction -

- efficiency, make sure that not a too fine level of device abstraction has been chosen. If possible group as many attributes together to form a device. Discuss this with the Applications Programmer to find out what is efficient for her application.

- hardware independency, one of the main reasons for writing device servers is to provide the Applications Programmer with a software interface as opposed to a hardware interface. Hide the hardware structure of the device. For example if the user is only interested in a single channel of a multichannel device then define each channel to be a logical device. The user should not be aware of hardware addresses or cabling details. The user is very often a scientist who has a physics-oriented world view and not a hardware-oriented world view. Hardware independency also has the advantage that applications are immune to hardware changes to the device

- object oriented world view, another raison d’etre behind the device server model is to build up an object oriented view of the world. The device should resemble the user’s view of the object as closely as possible. In the case of the ESRF’s beam lines for example, the devices should resemble beam line scientist’s view of the machine.

- atomism, each device can be considered like an atom - is a independent object. It should appear independent to the client even if behind the scenes it shares some hardware or software with other objects. This is often the case with multichannel devices where the user would like to see each channel as a device but it is obvious that the channels cannot be programmed completely independently. The logical device is there to hide or make transparent this fact. If it is impossible to send commands to one device without modifying another device then a single device should be made out the two devices.

- tailored vs general, one of the philosophies of the TDSOM is to provide tailored solutions. For example instead of writing one serial line class which treats the general case of a serial line device and leaving the device protocol to be implemented in the client the TDSOM advocates implementing a device class which handles the protocol of the device. This way the client only has to know the commands of the class and not the details of the protocol. Nothing prevents the device class from using a general purpose serial line class if it exists of course.

##### Defining device commands

Each device has a list of commands which can be executed by the application across the network or locally. These commands are the Application Programmer’s network knobs and dials for interacting with the device.

The list of commands to be implemented depends on the capabilities of the hardware, the list of sensible functions which can be executed at a distance and of course the functionality required by the application. This implies a close collaboration between the Equipment Responsible, Device Server Programmer and the Application Programmer.

When drawing up the list of commands particular attention should be paid to the following points

- performance, no single command should monopolize the device server for a long time (a nominal value for long is one second). Commands should be implemented in such a way that it executes immediately returning with a response. At best try to keep command execution time down to less than the typical overhead of an rpc call i.e. som milliseconds. This of course is not always possible e.g. a serial line device could require 100 milliseconds of protocol exchange. The Device Server Programmer should find the best trade-off between the users requirements and the devices capabilities. If a command implies a sequence of events which could last for a long time then implement the sequence of events in another thread - don’t block the device server.

- robustness, should be provided which allow the client to recover from error conditions and or do a warm startup.

###### Standard commands

A minimum set of three commands exist for all devices. These commands are

- State which returns the state of a device

- Status which returns the status of the device as a formatted ascii string

- Init which re-initialize a device without changing its network connection

These commands have already been discussed in The automatically added commands.

##### Choosing device state

The device state is a number which reflects the availability of the device. To simplify the coding for generic application, a predefined set of states are supported by TANGO. This list has 14 members which are

State name
ON
OFF
CLOSE
OPEN
INSERT
EXTRACT
MOVING
STANDBY
FAULT
INIT
RUNNING
ALARM
DISABLE
UNKNOWN

The names used here have obvious meaning.

##### Device server utilities to ease coding/debugging

The device server framework supports one set of utilities to ease the process of coding and debugging device server code. This utility is :

1. The device server verbose option

Using this facility avoids the usage of the classical “#ifdef DEBUG” style which makes code less readable.

###### The device server verbose option

Each device server supports a verbose option called -v. Four verbose levels are defined from 1 to 4. Level 4 is the most talkative one. If you use the -v option without specifying level, level 4 will be assumed.

A Tango Logging Service has been introduced (detailed in The Tango Logging Service). This -v option set-up the logging service. If it used, it will automatically add a console target to all devices embedded within the device server process. Level 1 and 2 will set the logging level to all devices embedded within the device server to INFO. Level 3 and 4 will set the logging level to all devices embedded within the device server to DEBUG. All messages sent by the API layer are associated to the administration device.

###### C++ utilities to ease device server coding

Some utilities functions have been added in the C++ release to ease Tango device server development. These utilities allow the user to

- Init a C++ vector from a data of one of the Tango DevVarXXXArray data types

- Init a data of one of the Tango::DevVarxxxArray data type from a C++ vector

- Print a data of one of Tango::DevVarxxxArray data type

They mainly used the “<<” operator overloading features. The following code lines are an example of usage of these utilities.

     1    vector<string> v1;
     2    v1.push_back("one");
     3    v1.push_back("two");
     4    v1.push_back("three");
     5
     6    Tango::DevVarStringArray s;
     7    s << v1;
     8    cout << s << endl;
     9
    10    vector<string> v2;
    11    v2 << s;
    12
    13    for (int i = 0;i < v2.size();i++)
    14       cout << "vector element = " << v2[i] << endl;

Line 1-4 : Create and Init a C++ string vector

Line 7 : Init a Tango::DevVarStringArray data from the C++ vector

Line 8 : Print all the Tango::DevVarStringArray element in one line of code.

Line 11 : Init a second empty C++ string vector with the content of the Tango::DevVarStringArray

Line 13-14 : Print vector element

Warning

  Note that due to a strange behavior of the Windows VC++ compiler compared to other compilers, to use these utilities with the Windows VC++ compiler, you must add the line “using namespace tango” at the beginning of your source file.

##### Avoiding name conflicts

Namespace are used to avoid name conflicts. Each device pattern implementation is defined within its own namespace. The name of the namespace is the device pattern class name. In our example, the namespace name is StepperMotor.

##### The device server main function

A device server main function (or method) always follows the same framework. It exactly implements all the action described in Device server startup sequence. Even if it could be always the same, it has not been included in the library because some linkers are perturbed by the presence of two main functions.

     1  #include <tango.h>
     2
     3  int main(int argc,char *argv[])
     4  {
     5
     6      Tango::Util *tg;
     7
     8      try
     9      {
    10
    11          tg = Tango::Util::init(argc,argv);
    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      tg->server_cleanup();
    32
    33      return(0);
    34  }

Line 1 : Include the tango.h file. This file is a master include file. It includes several other files. The list of files included by tango.h can be found in cppTango API reference

Line 11 : Create the instance of the Tango::Util class (a singleton). Passing argc,argv to this method is mandatory because the device server command line is checked when the Tango::Util object is constructed.

Line 13 : Start all the device pattern creation and initialization with the server_init() method

Line 16 : Put the server in a endless waiting loop with the server_run() method. In normal case, the process should never returns from this line.

Line 18-22 : Catch all exceptions due to memory allocation error, display a message to the user and exit

Line 23 : Catch all standard TANGO exception which could occur during device pattern creation and initialization

Line 25 : Print exception parameters

Line 27-28 : Print an additional message

Line 31 : Cleanup the server before exiting by calling the server_cleanup() method.

##### The DServer::class_factory method

As described in The DServer class, a C++ device server needs a class_factory() method. This method creates all the device pattern implemented in the device server by calling their init() method. The following is an example of a class_factory method for a device server with one implementation of the device server pattern for stepper motor device.

    1  #include <tango.h>
    2  #include <steppermotorclass.h>
    3
    4  void Tango::DServer::class_factory()
    5  {
    6
    7     add_class(StepperMotor::StepperMotorClass::init("StepperMotor"));
    8
    9  }

Line 1 : Include the Tango master include file

Line 2 : Include the steppermotorclass class definition file

Line 7 : Create the StepperMotorClass singleton by calling its init method and stores the returned pointer into the DServer object. Remember that all classes for the device pattern implementation for the stepper motor class is defined within a namespace called StepperMotor.

##### Writing the StepperMotorClass class

###### The class declaration file

     1  #include <tango.h>
     2
     3  namespace StepperMotor
     4  {
     5
     6  class StepperMotorClass : public Tango::DeviceClass
     7  {
     8  public:
     9      static StepperMotorClass *init(const char *);
    10      static StepperMotorClass *instance();
    11      ~StepperMotorClass() {_instance = NULL;}
    12
    13  protected:
    14      StepperMotorClass(string &);
    15      static StepperMotorClass *_instance;
    16      void command_factory();
    17      void attribute_factory(vector<Tango::Attr *> &);
    18
    19  public:
    20      void device_factory(const Tango::DevVarStringArray *);
    21  };
    22
    23  } /* End of StepperMotor namespace */

Line 1 : Include the Tango master include file

Line 3 : This class is defined within the StepperMotor namespace

Line 6 : Class StepperMotorClass inherits from Tango::DeviceClass

Line 9-10 : Definition of the init and instance methods. These methods are static and can be called even if the object is not already constructed.

Line 11: The destructor

Line 14 : The class constructor. It is protected and can’t be called from outside the class. Only the init method allows a user to create an instance of this class. It uses the singleton design pattern.

Line 15 : The instance pointer. It is static in order to set it to NULL during process initialization phase

Line 16 : Definition of the command_factory method

Line 17 : Definition of the attribute_factory method

Line 20 : Definition of the device_factory method

###### The singleton related methods

     1  #include <tango.h>
     2
     3  #include <steppermotor.h>
     4  #include <steppermotorclass.h>
     5
     6  namespace StepperMotor
     7  {
     8
     9  StepperMotorClass *StepperMotorClass::_instance = NULL;
    10
    11  StepperMotorClass::StepperMotorClass(string &s):
    12  Tango::DeviceClass(s)
    13  {
    14      INFO_STREAM << "Entering StepperMotorClass constructor" << endl;
    15
    16      INFO_STREAM << "Leaving StepperMotorClass constructor" << endl;
    17  }
    18
    19
    20  StepperMotorClass *StepperMotorClass::init(const char *name)
    21  {
    22      if (_instance == NULL)
    23      {
    24            try
    25            {
    26                 string s(name);
    27                 _instance = new StepperMotorClass(s);
    28            }
    29            catch (bad_alloc)
    30            {
    31                 throw;
    32            }
    33      }
    34      return _instance;
    35  }
    36
    37  StepperMotorClass *StepperMotorClass::instance()
    38  {
    39      if (_instance == NULL)
    40      {
    41            cerr << "Class is not initialised !!" << endl;
    42            exit(-1);
    43      }
    44      return _instance;
    45  }

Line 1-4 : include files: the Tango master include file (tango.h), the StepperMotorClass class definition file (steppermotorclass.h) and the StepperMotor class definition file (steppermotor.h)

Line 6 : Open the StepperMotor namespace.

Line 9 : Initialize the static _instance field of the StepperMotorClass class to NULL

Line 11-18 : The class constructor. It takes an input parameter which is the controlled device class name. This parameter is passed to the constructor of the DeviceClass class. Otherwise, the constructor does nothing except printing a message

Line 20-35 : The init method. This method needs an input parameter which is the controlled device class name (StepperMotor in this case). This method checks is the instance is already constructed by testing the _instance data member. If the instance is not constructed, it creates one. If the instance is already constructed, the method simply returns a pointer to it.

Line 37-45 : The instance method. This method is very similar to the init method except that if the instance is not already constructed. the method print a message and abort the process.

As you can understand, it is not possible to construct more than one instance of the StepperMotorClass (it is a singleton) and the init method must be called prior to any other method.

###### The command_factory method

Within our example, the stepper motor device supports two commands which are called DevReadPosition and DevReadDirection. These two command takes a Tango::DevLong argument as input and output parameter. The first command is created using the inheritance model and the second command is created using the template command model.

     1  void StepperMotorClass::command_factory()
     2  {
     3          command_list.push_back(new DevReadPositionCmd("DevReadPosition",
     4                                                        Tango::DEV_LONG,
     5                                                        Tango::DEV_LONG,
     6                                                        "Motor number (0-7)",
     7                                                        "Motor position"));
     8
     9          command_list.push_back(
    10              new TemplCommandInOut<Tango::DevLong,Tango::DevLong>
    11                  ((const char *)"DevReadDirection",
    12                   static_cast<Tango::Lg_CmdMethPtr_Lg>
    13                          (&StepperMotor::dev_read_direction),
    14                   static_cast<Tango::StateMethPtr>
    15                          (&StepperMotor::direct_cmd_allowed))
    16                                );
    17  }

Line 4 : Creation of one instance of the DevReadPositionCmd class. The class is created with five arguments which are the command name, the command type code for its input and output parameters and two strings which are the command input and output parameters description. The pointer returned by the new C++ keyword is added to the vector of available command.

Line 10-14 : Creation of the object used for the DevReadDirection command. This command has one input and output parameter. Therefore the created object is an instance of the TemplCommandInOut class. This class is a C++ template class. The first template parameter is the command input parameter type, the second template parameter is the command output parameter type. The second TemplCommandInOut class constructor parameter (set at line 13) is a pointer to the method to be executed when the command is requested. A casting is necessary to store this pointer as a pointer to a method of the DeviceImpl class [3]. The third TemplCommandInOut class constructor parameter (set at line 15) is a pointer to the method to be executed to check if the command is allowed. This is necessary only if the default behavior (command always allowed) does not fulfill the needs. A casting is necessary to store this pointer as a pointer to a method of the DeviceImpl class. When a command is created using the template command method, the input and output parameters type are determined from the template C++ class parameters.

###### The device_factory method

The device_factory method has one input parameter. It is a pointer to Tango::DevVarStringArray data which is the device name list for this class and the instance of the device server process. This list is fetch from the Tango database.

     1  void StepperMotorClass::device_factory(const Tango::_DevVarStringArray *devlist_ptr)
     2  {
     3
     4      for (long i = 0;i < devlist_ptr->length();i++)
     5      {
     6           DEBUG_STREAM << "Device name : " << (*devlist_ptr)[i] << endl;
     7
     8           device_list.push_back(new StepperMotor(this,(*devlist_ptr)[i]));       9
     9           if (Tango::Util::_UseDb == true)
    10                export_device(device_list.back());
    11           else
    12                export_device(device_list.back(),(*devlist_ptr[i]));
    13      }
    14  }

Line 4 : A loop for each device

Line 8 : Create the device object using a StepperMotor class constructor which needs two arguments. These two arguments are a pointer to the StepperMotorClass instance and the device name. The pointer to the constructed object is then added to the device list vector

Line 10-13 : Export device to the outside world using the export_device method of the DeviceClass class.

###### The attribute_factory method

The rule of this method is to fulfill a vector of pointer to attributes. A reference to this vector is passed as argument to this method.

     1  void StepperMotorClass::attribute_factory(vector<Tango::Attr *> &att_list)
     2  {
     3      att_list.push_back(new PositionAttr());
     4
     5      Tango::UserDefaultAttrProp def_prop;
     6      def_prop.set_label("Set the motor position");
     7      def_prop.set_format("scientific;setprecision(4)");
     8      Tango::Attr *at = new SetPositionAttr();
     9      at->set_default_properties(def_prop);
    10      att_list.push_back(at);
    11
    12      att_list.push_back(new DirectcionAttr());
    13  }

Line 3 : Create the PositionAttr class and store the pointer to this object into the attribute pointer vector.

Line 5-7 : Create a Tango::UserDefaultAttrProp instance and set the label and format properties default values in this object

Line 8 : Create the SetPositionAttr attribute.

Line 9 : Set attribute user default value with the set_default_properties() method of the Tango::Attr class.

Line 10 : Store the pointer to this object into the attribute pointer vector.

Line 12 : Create the DirectionAttr class and store the pointer to this object into the attribute pointer vector.

Please, note that in some rare case, it is necessary to add attribute to this list during the device server life cycle. This attribute_factory() method is called once during device server start-up. A method add_attribute() of the DeviceImpl class allows the user to add a new attribute to the attribute list outside of this attribute_factory() method. See cppTango API reference for more information on this method.

##### The DevReadPositionCmd class

###### The class declaration file

     1  #include <tango.h>
     2
     3  namespace StepperMotor
     4  {
     5
     6  class DevReadPositionCmd : public Tango::Command
     7  {
     8  public:
     9      DevReadPositionCmd(const char *,Tango::CmdArgType,
    10                             Tango::CmdArgType,
    11                             const char *,const char *);
    12      ~DevReadPositionCmd() {};
    13
    14      virtual bool is_allowed (Tango::DeviceImpl *, const CORBA::Any &);
    15      virtual CORBA::Any *execute (Tango::DeviceImpl *, const CORBA::Any &);
    16  };
    17
    18  } /* End of StepperMotor namespace */

Line 1 : Include the tango master include file

Line 3 : Open the StepperMotor namespace.

Line 6 : The DevReadPositionCmd class inherits from the Tango::Command class

Line 9 : The constructor

Line 12 : The destructor

Line 14 : The definition of the is_allowed method. This method is not necessary if the default behavior implemented by the default is_allowed method fulfill the requirements. The default behavior is to always allows the command execution (always return true).

Line 15: The definition of the execute method

###### The class constructor

The class constructor does nothing. It simply invoke the Command constructor by passing it its five arguments which are:

1. The command name

2. The command input type code

3. The command output type code

4. The command input parameter description

5. The command output parameter description

With this 5 parameters command class constructor, the command display level is not specified. Therefore it is set to its default value (OPERATOR). If the command does not have input or output parameter, it is not possible to use the Command class constructor defined with five parameters. In this case, the command constructor execute the Command class constructor with three elements (class name, input type, output type) and set the input or output parameter description fields with the set_in_type_desc or set_out_type_desc Command class methods. To set the command display level, it is possible to use a 6 parameters constructor or it is also possible to set it in the constructor code with the set_disp_level method. Many Command class constructors are defined. See cppTango API reference for a complete list.

###### The is_allowed method

In our example, the DevReadPosition command is allowed only if the device is in the ON state. This method receives two argument which are a pointer to the device object on which the command must be executed and a reference to the command input Any object. This method returns a boolean which must be set to true if the command is allowed. If this boolean is set to false, the DeviceClass command_handler method will automatically send an exception to the caller.

    1  bool DevReadPositionCmd::is_allowed(Tango::DeviceImpl *device,
    2                                      const CORBA::Any &in_any)
    3  {
    4       if (device->get_state() == Tango::ON)
    5            return true;
    6       else
    7            return false;
    8  }

Line 4 : Call the get_state method of the DeviceImpl class which simply returns the device state

Line 5 : Authorize command if the device state is ON

Line 7 : Refuse command execution in all other cases.

###### The execute method

This method receives two arguments which are a pointer to the device object on which the command must be executed and a reference to the command input Any object. This method returns a pointer to an any object which must be initialized with the data to be returned to the caller.

     1  CORBA::Any *DevReadPositionCmd::execute(
     2                          Tango::DeviceImpl *device,
     3                          const CORBA::Any &in_any)
     4  {
     5       INFO_STREAM << "DevReadPositionCmd::execute(): arrived" << endl;
     6       Tango::DevLong motor;
     7
     8       extract(in_any,motor);
     9       return insert(
    10          (static_cast<StepperMotor *>(device))->dev_read_position(motor));
    11  }

Line 8 : Extract incoming data from the input any object using a Command class extract helper method. If the type of the data in the Any object is not a Tango::DevLong, the extract method will throw an exception to the client.

Line 9 : Call the stepper motor object method which execute the DevReadPosition command and insert the returned value into an allocated Any object. The Any object allocation is done by the insert method which return a pointer to this Any.

##### The PositionAttr class

###### The class declaration file

     1  #include <tango.h>
     2  #include <steppermotor.h>
     3
     4  namespace StepperMotor
     5  {
     6
     7
     8  class PositionAttr: public Tango::Attr
     9  {
    10  public:
    11      PositionAttr():Attr("Position",
    12                          Tango::DEV_LONG,
    13                          Tango::READ_WITH_WRITE,
    14                          "SetPosition") {};
    15      ~PositionAttr() {};
    16
    17      virtual void read(Tango::DeviceImpl *dev,Tango::Attribute &att)
    18      {(static_cast<StepperMotor *>(dev))->read_Position(att);}
    19      virtual bool is_allowed(Tango::DeviceImpl *dev,Tango::AttReqType ty)
    20      {return (static_cast<StepperMotor *>(dev))->is_Position_allowed(ty);}
    21  };
    22
    23  } /* End of StepperMotor namespace */
    24
    25  #endif // _STEPPERMOTORCLASS_H

Line 1-2 : Include the tango master include file and the steppermotor class definition include file

Line 4 : Open the StepperMotor namespace.

Line 8 : The PosiitionAttr class inherits from the Tango::Attr class

Line 11-14 : The constructor with 4 arguments

Line 15 : The destructor

Line 17 : The definition of the read method. This method forwards the call to a StepperMotor class method called read_Position()

Line 19 : The definition of the is_allowed method. This method is not necessary if the default behaviour implemented by the default is_allowed method fulfills the requirements. The default behaviour is to always allows the attribute reading (always return true). This method forwards the call to a StepperMotor class method called is_Position_allowed()

###### The class constructor

The class constructor does nothing. It simply invoke the Attr constructor by passing it its four arguments which are:

1. The attribute name

2. The attribute data type code

3. The attribute writable type code

4. The name of the associated write attribute

With this 4 parameters Attr class constructor, the attribute display level is not specified. Therefore it is set to its default value (OPERATOR). To set the attribute display level, it is possible to use in the constructor code the set_disp_level method. Many Attr class constructors are defined. See cppTango API reference for a complete list.

This Position attribute is a scalar attribute. For spectrum attribute, instead of inheriting from the Attr class, the class must inherits from the SpectrumAttr class. Many SpectrumAttr class constructors are defined. See cppTango API reference for a complete list.

For Image attribute, instead of inheriting from the Attr class, the class must inherits from the ImageAttr class. Many ImageAttr class constructors are defined. See cppTango API reference for a complete list.

###### The is_allowed method

This method receives two argument which are a pointer to the device object to which the attribute belongs to and the type of request (read or write). In the PositionAttr class, this method simply forwards the request to a method of the StepperMotor class called is_Position_allowed() passing the request type to this method. This method returns a boolean which must be set to true if the attribute is allowed. If this boolean is set to false, the DeviceImpl read_attribute method will automatically send an exception to the caller.

###### The read method

This method receives two arguments which are a pointer to the device object to which the attribute belongs to and a reference to the corresponding attribute object. This method forwards the request to a StepperMotor class called read_Position() passing it the reference on the attribute object.

##### The StepperMotor class

###### The class declaration file

     1 #include <tango.h>
     2
     3 #define AGSM_MAX_MOTORS 8 // maximum number of motors per device
     4
     5 namespace StepperMotor
     6 {
     7
     8 class StepperMotor: public TANGO_BASE_CLASS
     9 {
    10 public :
    11    StepperMotor(Tango::DeviceClass *,string &);
    12    StepperMotor(Tango::DeviceClass *,const char *);
    13    StepperMotor(Tango::DeviceClass *,const char *,const char *);
    14    ~StepperMotor() {};
    15
    16    DevLong dev_read_position(DevLong);
    17    DevLong dev_read_direction(DevLong);
    18    bool direct_cmd_allowed(const CORBA::Any &);
    19
    20    virtual Tango::DevState dev_state();
    21    virtual Tango::ConstDevString dev_status();
    22
    23    virtual void always_executed_hook();
    24
    25    virtual void read_attr_hardware(vector<long> &attr_list);
    26    virtual void write_attr_hardware(vector<long> &attr_list);
    27
    28    void read_position(Tango::Attribute &);
    29    bool is_Position_allowed(Tango::AttReqType req);
    30    void write_SetPosition(Tango::WAttribute &);
    31    void read_Direction(Tango::Attribute &);
    32
    33    virtual void init_device();
    34    virtual void delete_device();
    35
    36    void get_device_properties();
    37
    38 protected :
    39    long axis[AGSM_MAX_MOTORS];
    40    DevLong position[AGSM_MAX_MOTORS];
    41    DevLong direction[AGSM_MAX_MOTORS];
    42    long state[AGSM_MAX_MOTORS];
    43
    44    Tango::DevLong *attr_Position_read;
    45    Tango::DevLong *attr_Direction_read;
    46    Tango::DevLong attr_SetPosition_write;
    47
    48    Tango::DevLong min;
    49    Tango::DevLong max;
    50
    51    Tango::DevLong *ptr;
    52 };
    53
    54 } /* End of StepperMotor namespace */

Line 1 : Include the Tango master include file

Line 5 : Open the StepperMotor namespace.

Line 8 : The StepperMotor class inherits from a Tango base class

Line 11-13 : Three different object constructors

Line 14 : The destructor which calls the delete_device() method

Line 16 : The method to be called for the execution of the DevReadPosition command. This method must be declared as virtual if it is needed to redefine it in a class inheriting from StepperMotor. See Inheriting for more details.

Line 17 : The method to be called for the execution of the DevReadDirection command

Line 18 : The method called to check if the execution of the DevReadDirection command is allowed. This method is necessary because the DevReadDirection command is created using the template command method and the default behavior is not acceptable

Line 20 : Redefinition of the dev_state. This method is used by the State command

Line 21 : Redefinition of the dev_status. This method is used by the Status command

Line 23 : Redefinition of the always_executed_hook method. This method is the place to code mandatory action which must be executed prior to any command.

Line 25-31 : Attribute related methods

Line 32 : Definition of the init_device method.

Line 33 : Definition of the delete_device method

Line 35 : Definition of the get_device_properties method

Line 38-50 : Data members.

Line 43-44 : Pointers to data for readable attributes Position and Direction

Line 45 : Data for the SetPosition attribute

Line 47-48 : Data members for the two device properties

###### The constructors

Three constructors are defined here. It is not mandatory to defined three constructors. But at least one is mandatory. The three constructors take a pointer to the StepperMotorClass instance as first parameter [4]. The second parameter is the device name as a C++ string or as a classical pointer to char array. The third parameter necessary only for the third form of constructor is the device description string passed as a classical pointer to a char array.

     1  #include <tango.h>
     2  #include <steppermotor.h>
     3
     4  namespace StepperMotor
     5  {
     6
     7  StepperMotor::StepperMotor(Tango::DeviceClass *cl,string &s)
     8  :TANGO_BASE_CLASS(cl,s.c_str())
     9  {
    10    init_device();
    11 }
    12
    13 StepperMotor::StepperMotor(Tango::DeviceClass *cl,const char *s)
    14 :TANGO_BASE_CLASS(cl,s)
    15 {
    16    init_device();
    17 }
    18
    19 StepperMotor::StepperMotor(Tango::DeviceClass *cl,const char *s,const char *d)
    20 :TANGO_BASE_CLASS(cl,s,d)
    21 {
    22    init_device();
    23 }
    24
    25 void StepperMotor::init_device()
    26 {
    27    cout << "StepperMotor::StepperMotor() create " << device_name << endl;
    28
    29    long i;
    30
    31    for (i=0; i< AGSM_MAX_MOTORS; i++)
    32    {
    33       axis[i] = 0;
    34       position[i] = 0;
    35       direction[i] = 0;
    36    }
    37
    38    ptr = new Tango::DevLong[10];
    39
    40    get_device_properties();
    41 }
    42
    43 void StepperMotor::delete_device()
    44 {
    45    delete [] ptr;
    46 }

Line 1-2 : Include the Tango master include file (tango.h) and the StepperMotor class definition file (steppermotor.h)

Line 4 : Open the StepperMotor namespace

Line 7-11 : The first form of the class constructor. It execute the Tango base class constructor with the two parameters. Note that the device name passed to this constructor as a C++ string is passed to the Tango::DeviceImpl constructor as a classical C string. Then the init_device method is executed.

Line 13-17 : The second form of the class constructor. It execute the Tango base class constructor with its two parameters. Then the init_device method is executed.

Line 19-23: The third form of constructor. Again, it execute the Tango base class constructor with its three parameters. Then the init_device method is executed.

Line 25-41 : The init_device method. All the device data initialization is done in this method. The device properties are also retrieved from database with a call to the get_device_properties method at line 40. The device data member called ptr is initialized with allocated memory at line 38. It is not needed to have this pointer, it has been added only for educational purpose.

Line 43-46 : The delete_device method. The rule of this method is to free memory allocated in the init_device method. In our case , only the device data member ptr is allocated in the init_device method. Therefore, its memory is freed at line 45. This method is called by the automatically added Init command before it calls the init_device method. It is also called by the device destructor.

###### The methods used for the DevReadDirection command

The DevReadDirection command is created using the template command method. Therefore, there is no specific class needed for this command but only one object of the TemplCommandInOut class. This command needs two methods which are the dev_read_direction method and the direct_cmd_allowed method. The direct_cmd_allowed method defines here implements exactly the same behavior than the default one. This method has been used only for pedagogic issue. The dev_read_direction method will be executed by the execute method of the TemplCommandInOut class. The direct_cmd_allowed method will be executed by the is_allowed method of the TemplCommandInOut class.

     1  DevLong StepperMotor::dev_read_direction(DevLong axis)
     2  {
     3     if (axis < 0 || axis > AGSM_MAX_MOTORS)
     4     {
     5         WARNING_STREAM << "Steppermotor::dev_read_direction(): axis out of range !";
     6         WARNING_STREAM << endl;
     7         TangoSys_OMemStream o;
     8
     9         o << "Axis number " << axis << " out of range" << ends;
    10         throw_exception("StepperMotor_OutOfRange",
    11                         o.str(),
    12                         "StepperMotor::dev_read_direction");
    13      }
    14
    15      return direction[axis];
    16  }
    17
    18
    19  bool StepperMotor::direct_cmd_allowed(const CORBA::Any &in_data)
    20  {
    21      INFO_STREAM << "In direct_cmd_allowed() method" << endl;
    22
    23      return true;
    24  }

Line 1-16 : The dev_read_direction method

Line 5-12 : Throw exception to client if the received axis number is out of range

Line 7 : A TangoSys_OMemStream is used as stream. The TangoSys_OMemStream has been defined in improve portability across platform. For Unix like operating system, it is a ostrtream type. For operating system with a full implementation of the standard library, it is a ostringstream type.

Line 19-24 : The direct_cmd_allowed method. The command input data is passed to this method in case of it is needed to take the decision. This data is still packed into the CORBA Any object.

###### The methods used for the Position attribute

To enable reading of attributes, the StepperMotor class must re-define two or three methods called

read_attr_hardware(), read_<Attribute_name>()

and if necessary a method called

is_<Attribute_name>_allowed().

The aim of the first one is to read the hardware. It will be called only once at the beginning of each read_attribute CORBA call. The second method aim is to build the exact data for the wanted attribute and to store this value into the Attribute object. Special care has been taken in order to minimize the number of data copy and allocation. The data passed to the Attribute object as attribute value is passed using pointers. It must be allocated by the method

[5]

and the Attribute object will not free this memory. Data members called attr_<Attribute_name>_read are foreseen for this usage. The

read_attr_hardware()

method receives a vector of long which are indexes into the main attributes vector of the attributes to be read. The

read_Position()

method receives a reference to the Attribute object. The third method (

is_Position_allowed()

) aim is to allow or dis-allow, the attribute reading. In some cases, some attributes can be read only if some conditions are met. If this method returns true, the

read_<Attribute_name>()

method will be called. Otherwise, an error will be generated for the attribute. This method receives one argument which is an emumeration describing the attribute request type (read or write). In our example, the reading of the Position attribute is allowed only if the device state is ON.

     1  void StepperMotor::read_attr_hardware(vector<long> &attr_list)
     2  {
     3     INFO_STREAM << "In read_attr_hardware for " << attr_list.size();
     4     INFO_STREAM << " attribute(s)" << endl;
     5
     6     for (long i = 0;i < attr_list.size();i++)
     7     {
     8        string attr_name;
     9        attr_name = dev_attr->get_attr_by_ind(attr_list[i]).get_name();
    10
    11        if (attr_name == "Position")
    12        {
    13           attr_Position_read = &(position[0]);
    14        }
    15        else if (attr_name == "Direction")
    16        {
    17           attr_Direction_read = &(direction[0]);
    18        }
    19     }
    20  }
    21
    22  void read_Position(Tango::Attribute &att)
    23  {
    24     att.set_value(attr_Position_read);
    25  }
    26
    27  bool is_Position_allowed(Tango::AttReqType req)
    28  {
    29     if (req == Tango::WRITE_REQ)
    30        return false;
    31     else
    32     {
    33        if (get_state() == Tango::ON)
    34           return true;
    35        else
    36           return false;
    37     }
    38  }

Line 6 : A loop on each attribute to be read

Line 9 : Get attribute name

Line 11 : Test on attribute name

Line 13 : Read hardware (pretty simple in our case)

Line 24 : Set attribute value in Attribute object using the set_value() method. This method will also initializes the attribute quality factor to Tango::ATTR_VALID if no alarm level are defined and will set the attribute returned date. It is also possible to use a method called set_value_date_quality() which allows the user to set the attribute quality factor as well as the attribute date.

Line 33 : Test on device state

###### The methods used for the SetPosition attribute

To enable writing of attributes, the StepperMotor class must re-define one or two methods called write_<Attribute_name>() and if necessary a method called is_<Attribute_name>_allowed(). The aim of the first one is to write the hardware. The write_Position() method receives a reference to the WAttribute object. The value to write is in this WAttribute object. The third method (is_Position_allowed()) aim is to allow or dis-allow, the attribute writing. In some cases, some attributes can be write only if some conditions are met. If this method returns true, the write_<Attribute_name>() method will be called. Otherwise, an error will be generated for the attribute. This method receives one argument which is an emumeration describing the attribute request type (read or write). For read/write attribute, this method is the same for reading and writing. The input argument value makes the difference.

For our example, it is always possible to write the SetPosition attribute. Therefore, the StepperMotor class only defines a write_SetPosition() method.

     1 void StepperMotor::write_SetPosition(Tango::WAttribute &att)
     2 {
     3    att.get_write_value(sttr_SetPosition_write);
     4
     5    DEBUG_STREAM << "Attribute SetPosition value = ";
     6    DEBUG_STREAM << attr_SetPosition_write << endl;
     7
     8    position[0] = attr_SetPosition_write;
     9 }
    10
    11 void StepperMotor::write_attr_hardware(vector<long> &attr_list)
    12 {
    13
    14 }

Line 3 : Retrieve new attribute value

Line 5-6 : Send some messages using Tango Logging system

Line 8 : Set the hardware (pretty simple in our case)

Line 11 - 14: The write_attr_hardware() method.

In our case, we don’t have to do anything in the

write_attr_hardware()

method. It is coded here just for educational purpose. When its not needed, this method has a default implementation in the Tango base class and it is not mandatory to declare and defin it in your own Tango class

###### Retrieving device properties

Retrieving properties is fairly simple with the use of the database object. Each Tango device is an aggregate with a DbDevice object (see figure 6.1). This has been grouped in a method called get_device_properties(). The classes and methods of the Dbxxx objects are described in the Tango API documentation.

     1  void DocDs::get_device_property()
     2  {
     3     Tango::DbData   data;
     4     data.push_back(DbDatum("Max"));
     5     data.push_back(DbDatum("Min"));
     6
     7     get_db_device()->get_property(data);
     8
     9     if (data[0].is_empty()==false)
    10        data[0]  >>  max;
    11     if (data[1].is_empty()==false)
    12        data[1]  >>  min;
    13  }

Line 4-5 : Two DbDatum (one per property) are stored into a DbData object

Line 7 : Call the database to retrieve properties value

Line 9-10 : If the Max property is defined in the database, extract its value from the DbDatum object and store it in a device data member

Line 11-12 : If the Min property is defined in the database, extract its value from the DbDatum object and store it in a device data member

###### The remaining methods

The remaining methods are the dev_state, dev_status, always_executed_hook, dev_read_position and read_Direction() methods. The dev_state method parameters are fixed. It does not receive any input parameter and must return a Tango_DevState data type. The dev_status parameters are also fixed. It does not receive any input parameter and must return a Tango string. The always_executed_hook receives nothing and return nothing. The dev_read_position method input parameter is the motor number as a long and the returned parameter is the motor position also as a long data type. The read_Direction() method is the method for reading the Direction attribute.

     1  DevLong StepperMotor::dev_read_position(DevLong axis)
     2  {
     3
     4     if (axis < 0 || axis > AGSM_MAX_MOTORS)
     5     {
     6          WARNING_STREAM << "Steppermotor::dev_read_position(): axis out of range !";
     7          WARNING_STREAM << endl;
     8
     9          TangoSys_OMemStream o;
    10
    11          o << "Axis number " << axis << " out of range" << ends;
    12          throw_exception("StepperMotor_OutOfRange",
    13                          o.str(),
    14                          "StepperMotor::dev_read_position");
    15     }
    16
    17     return position[axis];
    18  }
    19
    20  void always_executed_hook()
    21  {
    22     INFO_STREAM << "In the always_executed_hook method << endl;
    23  }
    24
    25  Tango_DevState StepperMotor::dev_state()
    26  {
    27     INFO_STREAM << "In StepperMotor state command" << endl;
    28     return DeviceImpl::dev_state();
    29  }
    30
    31  Tango_DevString StepperMotor::dev_status()
    32  {
    33     INFO_STREAM << "In StepperMotor status command" << endl;
    34     return DeviceImpl::dev_status();
    35  }
    36
    37  void read_Direction(Tango::Attribute att)
    38  {
    39     att.set_value(attr_Direction_read);
    40  }

Line 1-18 : The dev_read_position method

Line 6-14 : Throw exception to client if the received axis number is out of range

Line 9 : A TangoSys_OMemStream is used as stream. The TangoSys_OMemStream has been defined in improve portability across platform. For Unix like operating system, it is a ostrtream type. For operating system with a full implementation of the standard library, it is a ostringstream type.

Line 20-23 : The always_executed_hook method. It does nothing. It has been included here only as pedagogic usage.

Line 25-29 : The dev_state method. It does exactly what the default dev_state does. It has been included here only as pedagogic usage

Line 31-35 : The dev_status method. It does exactly what the default dev_status does. It has been included here only as pedagogic usage

Line 37-40 : The read_Direction method. Simply set the Attribute object internal value

#### Device server under Windows

Two kind of programs are available under Windows. These kinds of programs are called console application or Windows application. A console application is started from a MS-DOS window and is very similar to classical UNIX program. A Windows application is most of the time not started from a MS-DOS window and is generally a graphical application without standard input/output. Writing a device server in a console application is straight forward following the rules described in the previous sub-chapters. Writing a device server in a Windows application needs some changes detailed in the following sub-chapters.

##### The Tango device server graphical interface

Within the Windows operating system, most of the running application has a window user interface. This is also true for the Windows Tango device server. Using or not this interface is up to the device server programmer. The choice is done with an argument to the server_init() method of the Tango::Util class. This interface is pretty simple and is based on three windows which are :

- The device server main window

- The device server console window

- The device server help window

###### The device server main window

This window looks like :

Figure 6.7: Tango device server main window

Four menus are available in this window. The File menu allows the user to exit the device server. The View menu allows you to display/hide the device server console window. The Debug menu allows the user to change the server output verbose level. All the outputs goes to the console window even if it is hidden. The Help menu displays the help window. The device server name is displayed in the window title. The text displayed at the bottom of the window has a default value (the one displayed in this window dump) but may be changed by the device server programmer using the set_main_window_text() method of the Tango::Util class. If used, this method must be called prior to the call of the server_init() method. Refer to cppTango API reference for a complete description of this method.

###### The console window

This window looks like :

It simply displays all the logging** message when a console target is used in the device server.

###### The help window

This window looks like :

This window displays

- The device server name

- The Tango library release

- The Tango IDL definition release

- The device server release. The device server programmer may set this release number using the set_server_version() method of the Tango::Util class. If used, this must be done prior to the call of the server_init() method. If the set_server_version() method is not used, x.y is displays as version number. Refer to cppTango API reference for a complete description of this method.

##### MFC device server

There is no main function within a classical MFC program. Most of the time, your application is represented by one instance of a C++ class which inherits from the MFC CWinApp class. This CWinApp class has several methods that you may overload in your application class. For a device server to run correctly, you must overload two methods of the CWinApp class. These methods are the InitInstance() and ExitInstance() methods. The rule of these methods is obvious following their names.

Remember that if the Tango device server graphical user interface is used, you must link your device server with the Tango windows resource file. This is done by adding the Tango resource file to the Project Settings/Link/Input/Object, library modules window in VC++.

###### The InitInstance method

The code to be added here is the equivalent of the code written in a classical main() function. Don’t forget to add the tango.h file in the list of included files.

     1 BOOL FluidsApp::InitInstance()
     2 {
     3    AfxEnableControlContainer();
     4
     5 // Standard initialization
     6 // If you are not using these features and wish to reduce the size
     7 //  of your final executable, you should remove from the following
     8 //  the specific initialization routines you do not need.
     9
    10  #ifdef _AFXDLL
    11    Enable3dControls();          // Call this when using MFC in a shared DLL
    12  #else
    13    Enable3dControlsStatic();    // Call this when linking to MFC statically
    14  #endif
    15    Tango::Util *tg;
    16    try
    17    {
    18
    19        tg = Tango::Util::init(m_hInstance,m_nCmdShow);
    20
    21        tg->server_init(true);
    22
    23        tg->server_run();
    24
    25    }
    26    catch (bad_alloc)
    27    {
    28        MessageBox((HWND)NULL,"Memory error","Command line",MB_ICONSTOP);
    29        return(FALSE);
    30    }
    31    catch (Tango::DevFailed &e)
    32    {
    33        MessageBox((HWND)NULL,,e.errors[0].desc.in(),"Command line",MB_ICONSTOP);
    34        return(FALSE);
    35    }
    36    catch (CORBA::Exception &)
    37    {
    38        MessageBox((HWND)NULL,"Exception CORBA","Command line",MB_ICONSTOP);
    39        return(FALSE);
    40    }
    41
    42    m_pMainWnd = new CWnd;
    43    m_pMainWnd->Attach(tg->get_ds_main_window());
    44
    45    return TRUE;
    46  }

Line 19 : Initialise Tango system. This method also analises the argument used in command line.

Line 21 : Create Tango classes requesting the Tango Windows graphical interface to be used

Line 23 : Start Network listener. Note that under NT, this call returns in the contrary of UNIX like operating system.

Line 26-30 : Display a message box in case of memory allocation error and leave method with a return value set to false in order to stop the process

Line 31-35 : Display a message box in case of error during server initialization phase.

Line 36-40 : Display a message box in case of error other than memory allocation. Leave method with a return value set to false in order to stop the process.

Line 37-38 : Create a MFC main window and attach the Tango graphical interface main window to this MFC window.

###### The ExitInstance method

This method is called when the application is stopped. For Tango device server, its rule is to destroy the Tango::Util singleton if this one has been correctly constructed.

     1  int FluidsApp::ExitInstance()
     2  {
     3     bool del = true;
     4
     5     try
     6     {
     7         Tango::Util *tg = Tango::Util::instance();
     8     }
     9     catch(Tango::DevFailed)
    10     {
    11         del = false;
    12     }
    13
    14     if (del == true)
    15         delete (Tango::Util::instance());
    16
    17     return CWinApp::ExitInstance();
    18  }

Line 7 : Try to retrieve the Tango::Util singleton. If this one has not been constructed correctly, this call will throw an exception.

Line 9-12 : Catch the exception in case of incomplete Tango::Util singleton construction

Line 14-15 : Delete the Tango::Util singleton. This will unregister the Tango device server from the Tango database.

Line 17 : Execute the ExitInstance method of the CWinApp class.

If you don’t want to use the Tango device server graphical interface, do not pass any parameter to the server_init() method and instead of the code display in lines 37 and 38 in the previous example of the InitInstance() method, use your own code to initialize your own application.

###### Example of how to build a Windows device server MFC based

This sub-chapter gives an example of what it is needed to do to build a MFC Windows device server. Rather than being a list of actions to strictly follow, this is some general rules of how using VC++ to build a Tango device server using MFC.

1. Create your device server using Pogo. For a class named MyMotor, the following files will be needed : class_factory.cpp, MyMotorClass.h, MyMotorClass.cpp, MyMotor.h and MyMotor.cpp.

2. On a Windows computer running VC++, create a new project of type “MFC app Wizard (exe)” using static MFC libs. Ask for a dialog based project without ActiveX controls.

3. Copy the five files generated by Pogo to the Windows computer and add them to your project

4. Remove the dialog window files (xxxDlg.cpp and xxxDlg.h), the Resource include file and the resource script file from your project

5. Add #include <stdafx.h> as first line of the include files list in class_factory.cpp, MyMotorClass.cpp and MyMotor.cpp file. Also add your own directory and the Tango include directory to the project pre-compiler include directories list.

6. Enable RTTI in your project settings (see chapter [Compiling NT])

7. Change your application class:

   1. Add the definition of an ExitInstance method in the declaration file. (xxx.h file)

   2. Remove the include of the dialog window file in the xxx.cpp file and add an include of the Tango master include files (tango.h)

   3. Replace the InitInstance() method as described in previous sub-chapter. (xx.cpp file)

   4. Add an ExitInstance() method as described in previous sub-chapter (xxx.cpp file)

8. Add all the libraries needed to compile a Tango device server (see chapter [Compiling NT]) and the Tango resource file to the linker Object/Libraries modules.

##### Win32 application

Even if it is more natural to use the C++ structure of the MFC class to write a Tango device server, it is possible to write a device server as a Win32 application. Instead of having a main() function as the application entry point, the operating system, provides a WinMain() function as the application entry point. Some code must be added to this WinMain function in order to support Tango device server. Don’t forget to add the tango.h file in the list of included files. If you are using the project files generated by Pogo, don’t forget to change the linker SUBSYSTEM option to Windows (Under Linker/System in the project properties window).

     1  int APIENTRY WinMain(HINSTANCE hInstance,
     2                       HINSTANCE hPrevInstance,
     3                       LPSTR     lpCmdLine,
     4                       int       nCmdShow)
     5  {
     6     MSG msg;
     7     Tango::Util *tg;
     8
     9     try
    10     {
    11         tg = Tango::Util::init(hInstance,nCmdShow);
    12
    13         string txt;
    14         txt = "Blabla first line\n";
    15         txt = txt + "Blabla second line\n";
    16         txt = txt + "Blabla third line\n";
    17         tg->set_main_window_text(txt);
    18         tg->set_server_version("2.2");
    19
    20         tg->server_init(true);
    21
    22         tg->server_run();
    23
    24     }
    25     catch (bad_alloc)
    26     {
    27         MessageBox((HWND)NULL,"Memory error","Command line",MB_ICONSTOP);
    28         return (FALSE);
    29     }
    30     catch (Tango::DevFailed &e)
    31     {
    32         MessageBox((HWND)NULL,e.errors[0].desc.in(),"Command line",MB_ICONSTOP);
    33         return (FALSE);
    34     }
    35     catch (CORBA::Exception &)
    36     {
    37         MessageBox((HWND)NULL,"Exception CORBA","Command line",MB_ICONSTOP);
    38         return(FALSE);
    39     }
    40
    41     while (GetMessage(&msg, NULL, 0, 0))
    42     {
    43         TranslateMessage(&msg);
    44         DispatchMessage(&msg);
    45     }
    46
    47     delete tg;
    48
    49     return msg.wParam;
    50  }

Line 11 : Create the Tango::Util singleton

Line 13-18 : Set parameters for the graphical interface

Line 20 : Initialize Tango device server requesting the display of the graphical interface

Line 22 : Run the device server

Line 25-39 : Display a message box for all the kinds of error during Tango device server initialization phase and exit WinMain function.

Line 41-45 : The Windows message loop

Line 47 : Delete the Tango::Util singleton. This class destructor unregisters the device server from the Tango database.

Remember that if the Tango device server graphical user interface is used, you must add the Tango windows resource file to your project.

If you don’t want to use the tango device server graphical user interface, do not use any parameter in the call of the server_init() method and do not link your device server with the Tango Windows resource file.

##### Device server as service

With Windows, if you want to have processes which survive to logoff sequence and/or are automatically started during computer startup sequence, you have to write them as service. It is possible to write Tango device server as service. You need to

1. Write a class which inherits from a pre-written Tango class called NTService. This class must have a start method.

2. Write a main function following a predefined skeleton.

###### The service class

It must inherits from the NTService class and defines a start method. The NTService class must be constructed with one argument which is the device server executable name. The start method has three arguments which are the number of arguments passed to the method, the argument list and a reference to an object used to log info in the NT event system. The first two args must be passed to the Tango::Util::init method and the last one is used to log error or info messages. The class definition file looks like

     1  #include <tango.h>
     2  #include <ntservice.h>
     3
     4  class MYService: public Tango::NTService
     5  {
     6  public:
     7     MYService(char *);
     8
     9     void start(int,char **,Tango::NTEventLogger *);
    10  };

Line 1-2 : Some include files

Line 4 : The MYService class inherits from Tango::NTService class

Line 7 : Constructor with one parameter

Line 9 : The

start()

method

The class source code looks like

     1  #include <myservice.h>
     2  #include <tango.h>
     3
     4  using namespace std;
     5
     6  MYService::MYService(char *exec_name):NTService(exec_name)
     7  {
     8  }
     9
    10  void MYService::start(int argc,char **argv,Tango::NTEventLogger *logger)
    11  {
    12     Tango::Util *tg;
    13     try
    14     {
    15        Tango::Util::_service = true;
    16
    17        tg = Tango::Util::init(argc,argv);
    18
    19        tg->server_init();
    20
    21        tg->server_run();
    22     }
    23     catch (bad_alloc)
    24     {
    25        logger->error("Can't allocate memory to store device object");
    26     }
    27     catch (Tango::DevFailed &e)
    28     {
    29        logger->error(e.errors[0].desc.in());
    30     }
    31     catch (CORBA::Exception &)
    32     {
    33        logger->error("CORBA Exception");
    34     }
    35  }

Line 6-8 : The MYService class constructor code.

Line 15 : Set to true the _service static variable of the Tango::Util class.

Line 17-21 : Classical Tango device server startup code

Line 23-34 : Exception management. Please, note that within a service. it is not possible to print data on a console. This method receives a reference to a logger object. This object sends all its output to the Windows event system. It is used to send messages when an exception has occurred.

###### The main function

The main function is used to create one instance of the class describing the service, to check the service option and to run the service. The code looks like :

     1  #include <tango.h>
     2  #include <MYService.h>
     3
     4  using namespace std;
     5
     6
     7  int main(int argc,char *argv[])
     8  {
     9     MYService service(argv[0]);
    10
    11     int ret;
    12     if ((ret = service.options(argc,argv)) <= 0)
    13         return ret;
    14
    15     service.run(argc,argv);
    16
    17     return 0;
    18  }

Line 9 : Create one instance of the MYService class with the executable name as parameter

Line 12 : Check service option with the options() method inherited from the NTService class.

Line 15 : Run the service. The run() method is inherited from the NTService class. This method will after some NT initialization sequence execute the user start() method.

###### Service options and messages

When a Tango device server is written as a Windows service, it supports several new options. These option are linked to Windows service usage.

Before it can be used, a service must be installed. A name and a title is associated to each service. For Tango device server used as service, the service name is build from the executable name followed by the underscore character and the instance name. For example, a device server service executable file named “opc” and started with “fluids” as instance name, will be named “opc_fluids”. The title string is built from the service executable name followed by the sentence “Tango device server” and the instance name between parenthesis. In the previous example, the service title will be “opc Tango device server (fluids)”. Once a service is installed, you can configure it with the “Services” application of the control panel. Services title are displayed by this application and allow the user to select one specific service. Once a service is selected, it is possible to start/stop it and to configure its startup type as manual (with the Services application) or as automatic. When the automatic mode is chosen, the service starts when the computer is started. In this case, the service executable code must resides on the computer local disk.

Tango device server logs message in the Windows event system when the service is started or stopped. You can see these messages with the “Event Viewer” application (Start->Programs->Administrative tools->Event Viewer) and choose the Application events.

The new options are -i, -s, -u, -h and -d.

- -i : Install the service

- -s : Install the service and choose the automatic startup mode

- -u : Un-install the service

- -dbg : Run in console mode to debug service. The service must have been installed prior to used it. The classical -v device server option can be used with the -d option.

On the command line, all these options must be used after the device server instance name (“opc fluids -i” to install the service, “opc fluids -u” to un-install the service, “opc fluids -v -d” to debug the service)

###### Tango device server using MFC as Windows service

If your Tango device server uses MFC and must be written as a Windows NT service, follow these rules :

- Don’t forget to add the stdafx.h file as the first file included in all the source files making the project.

- Comment out the definition of VC_EXTRALEAN in the stdafx.h file.

- Change the pre-processor definitions, replace _WINDOWS by _CONSOLE

- Add the /SUBSYSTEM:CONSOLE option in the linker options window of the project settings.

- Add a call to initialize the MFC (AfxWinInit()) in the service main function

     1  int main(int argc,char *argv[])
     2  {
     3     if (!AfxWinInit(::GetModuleHandle(NULL),NULL,::GetCommandLine(),0))
     4     {
     5        cerr << "Can't initialise MFC !" << endl;
     6        return -1;
     7     }
     8
     9     service serv(argv[0]);
    10
    11     int ret;
    12     if ((ret = serv.options(argc,argv)) <= 0)
    13          return ret;
    14
    15     serv.run(argc,argv);
    16
    17     return 0;
    18  }

Line 3 : The MFC classes are initialized with the AfxWinInit() function call.

#### Compiling, linking and executing a TANGO device server process

##### Compiling and linking a C++ device server

###### On UNIX like operating system

###### Supported development tools

The supported compiler for Linux is gcc release 3.3 and above. Please, note that to debug a Tango device server running under Linux, gdb release 7 and above is needed in order to correctly handle threads.

###### Compiling

TANGO for C++ uses omniORB (release 4) as underlying CORBA Object Request Broker (see OMG home page) and starting with Tango 8, the ZMQ library. To compile a TANGO device server, your include search path must be set to :

- The omniORB include directory

- The ZMQ include directory

- The Tango include directory

- Your development directory

###### Linking

To build a running device server process, you need to link your code with several libraries:

- The Tango library (called libtango)

- Three omniORB package libraries (called libomniORB4, libomniDynamic4 and libCOS4)

- The omniORB threading library (called libomnithread)

- The ZMQ library (callled libzmq)

On top of that, you need additional libraries depending on the operating system :

- For Linux, add the POSIX thread library (libpthread)

The following table summarizes the necessary options to compile a Tango C++ device server. Please, note that starting with Tango 8 and for gcc release 4.3 and later, some C++11 code has been used. This requires the compiler option -std=c++0x (or better). Obviously, the options -I and -L must be updated to reflect your file system organization.

Operating system | Compiling option | Linking option
--- | --- | ---
Linux gcc | -D_REENTRANT -std=c++0x -I.. | -L.. -ltango -lomniORB4 -lomniDynamic4 -lCOS4 -lomnithread -lzmq -lpthread

The following is an example of a Makefile for Linux. Obviously, all the paths are set to the ESRF file system structure.

     1 #
     2 # Makefile to generate a Tango server
     3 #
     4
     5 CC = c++
     6 BIN_DIR = ubuntu1104
     7 TANGO_HOME = /segfs/tango
     8
     9 INCLUDE_DIRS = -I $(TANGO_HOME)/include/$(BIN_DIR) -I .
    10
    11
    12 LIB_DIRS = -L $(TANGO_HOME)/lib/$(BIN_DIR)
    13
    14
    15 CXXFLAGS = -D_REENTRANT -std=c++0x $(INCLUDE_DIRS)
    16 LFLAGS = $(LIB_DIRS) -ltango \
    17                      -lomniORB4 \
    18                      -lomniDynamic4 \
    19                      -lCOS4 \
    20                      -lomnithread \
    21                      -lzmq \
    22                      -lpthread
    23
    24
    25 SVC_OBJS = main.o \
    26            ClassFactory.o \
    27            SteppermotorClass.o \
    28            Steppermotor.o \
    29            SteppermotorStateMachine.o
    30
    31
    32 .SUFFIXES: .o .cpp
    33 .cpp.o:
    34     $(CC) $(CXXFLAGS) -c $<
    35
    36
    37 all: StepperMotor
    38
    39 StepperMotor: $(SVC_OBJS)
    40     $(CC) $(SVC_OBJS) -o $(BIN_DIR)/StepperMotor $(LFLAGS)
    41
    42 clean:
    43     rm -f *.o core

Line 5-7 : Define Makefile macros

Line 9-10 : Set the include file search path

Line 12 : Set the linker library search path

Line 15 : The compiler option setting

Line 16-23 : The linker option setting

Line 26-30 : All the object files needed to build the executable

Line 33-35 : Define rules to generate object files

Line 38 : Define a “all” dependency

Line 40-41 : How to generate the StepperMotor device server executable

Line 43-44 : Define a “clean” dependency

###### On Windows using Visual Studio

Supported Windows compiler for Tango is Visual Studio 2008 (VC 9), Visual Studio 2010 (VC10) and Visual Studio 2013 (VC12). Most problems in building a Windows device server revolve around the /M compiler switch family. This switch family controls which run-time library names are embedded in the object files, and consequently which libraries are used during linking. Attempt to mix and match compiler settings and libraries can cause link error and even if successful, may produce undefined run-time behavior.

Selecting the correct /M switch in Visual Studio is done through a dialog box. To open this dialog box, click on the “Project” menu (once the correct project is selected in the Solution Explorer window) and select the “Properties” option. To change the compiler switch open the “C/C++” tree and select “Code Generation”. The following options are supported.

- Multithreaded = /MT

- Multithreaded DLL = /MD

- Debug Multithreaded = /MTd

- Debug Multithreaded DLL = /MDd

Compiling a file with a value of the /M switch family will impose at link phase the use of libraries also compiled with the same value of the /M switch family. If you compiled your source code with the /MT option (Multithreaded), you must link it with libraries also compiled with the /MT option.

On both 32 or 64 bits computer, omniORB and TANGO relies on the preprocessor identifier WIN32 being defined in order to configure itself. If you build an application using static libraries (option /MT or /MTd), you must add _WINSTATIC to the list of the preprocessor identifiers. If you build an application using DLL (option /MD or /MDd), you must add LOG4TANGO_HAS_DLL and TANGO_HAS_DLL to the list of preprocessor identifiers.

To build a running device server process, you need to link your code with several libraries on top of the Windows libraries. These libraries are:

- The Tango library (called tango.lib or tangod.lib for debug mode)

- The omniORB package libraries (see next table)

Compile mode | Libraries
--- | ---
Debug Multithreaded | omniORB4d.lib, omniDynamic4d.lib, omnithreadd.lib and COS4d.lib
Multithreaded | omniORB4.lib, omniDynamic4.lib, omnithread.lib and COS4.lib
Debug Multithreaded DLL | omniORB420_rtd.lib, omniDynamic420_rtd.lib, omnithread40_rtd.lib, and COS420_rtd.lib
Multithreaded DLL | omniORB420_rt.lib, omniDynamic420_rt.lib, omnithread40_rt.lib and COS420_rt.lib

- The ZMQ library (zmq.lib or zmqd.lib for debug mode)

- Windows network libraries (mswsock.lib and ws2_32.lib)

- Windows graphic library (comctl32.lib)

To add these libraries in Visual Studio, open the project property pages dialog box and open the “Link” tree. Select “Input” and add these library names to the list of library in the “Additional Dependencies” box.

The “Win32 Debug” or “Win32 Release” configuration that you change within the Configuration Manager window changes the /M switch compiler. For instance, if you select a “Win32 Debug” configuration in a non-DLL project, use the omniORB4d.lib, omniDynamic4d.lib and omnithreadd.lib libraries plus the tangod.lib and zmqd.lib libraries. If you select the “Win32 Release” configuration, use the omniORB4.lib, omniDynamic4.lib and omnithread.lib libraries plus the tango.lib and zmq.lib libraries.

WARNING: In some cases, the Microsoft Visual Studio wizard used during project creation generates one include file called Stdafx.h. If this file itself includes windows.h file, you have to add the preprocessor macro _WIN32_WINNT and set it to 0x0500.

##### Running a C++ device server

To run a C++ Tango device server, you must set an environment variable. This environment variable is called TANGO_HOST and has a fixed syntax which is

TANGO_HOST=<host>:<port>

The host field is the host name where the TANGO database device server is running. The port field is the port number on which this server is listening. For instance, a valid syntax is TANGO_HOST=dumela:10000. For UNIX like operating system, setting environment variable is possible with the export or setenv command depending on the shell used. For Windows, setting environment variable is possible with the “Environment” tab of the “System” application in the control panel.

If you need to start a Tango device server on a pre-defined port (For Tango database device server or device server without database usage), you must use one of the underlying ORB option endPoint like

myserver myinstance_name -ORBendPoint giop:tcp::<port number>

#### Advanced programming techniques

The basic techniques for implementing device server pattern are required by each device server programmer. In certain situations, it is however necessary to do things out of the ordinary. This chapter will look into programming techniques which permit the device server serve more than simply the network.

##### Receiving signal

It is UNSAFE to use any CORBA call in a signal handler. It is also UNSAFE to use some system calls in a signal handler. Tango device server solved this problem by using threads. A specific thread is started to handle signals. Therefore, every Tango device server is automatically a threaded process. This allows the programmer to write the code which must be executed when a signal is received as ordinary code. All device server threads masks all signals except the specific signal thread which is permanently waiting for signal. If a signal is sent to a device server process, only the signal thread will receive it because it is the single thread which does not mask signals.

Nevertheless, signal management is not trivial and some care have to be taken. The signal management differs from operating system to operating system. It is not recommended that you install your own signal routine using any of the signal routines provided by the operating system calls or library.

###### Using signal

It is possible for C++ device server to receive signals from drivers or other processes. The TDSOM supports receiving signal at two levels: the device level and the class level. Supporting signal at the device level means that it is possible to specify interest into receiving signal on a device basis. This feature is supported via three methods defined in the DeviceImpl class. These methods are called register_signal, unregister_signal and signal_handler.

The *register_signal* method has one parameter which is the signal number. This method informs the device server signal system that the device want to be informed when the signal passed as parameter is received by the process. There is a special case for Linux as explained in the previous sub-chapter. It is possible to register a signal to be executed in the a signal handler context (with all its restrictions). This is done with a second parameter to this register_signal method. This second parameter is simply a boolean data. If it is true, the signal_handler will be executed in a signal handler context in the device server main thread. A default value (false) has been defined for this parameter.

The *unregister_signal* method also have an input parameter which is the signal number. This method removes the device from the list of object which should be warned when the signal is received by the process.

The *signal_handler* method is the method which is triggered when a signal is received if the corresponding register_signal has been executed. This method is defined as virtual and can be redefined by the user. It has one input argument which is the signal number.

The same three methods also exist in the DeviceClass class. Their action and their usage are similar to the DeviceImpl class methods. Installing a signal at the class level does not mean that all the device belonging to this class will receive the signal. This only means that the signal_handler method of the DeviceClass instance will be executed. This is useful if an action has to be executed once for a class of devices when a signal is received.

The following code is an example with our stepper motor device server configured via the database to serve three motors. These motors have the following names : id04/motor/01, id04/motor/02 and id04/motor/03. The signal SIGALRM (alarm signal) must be propagated only to the motor number 2 (id04/motor/02)

     1  void StepperMotor::init_device()
     2  {
     3      cout << "StepperMotor::StepperMotor() create motor " << dev_name << endl;
     4
     5      long i;
     6
     7      for (i=0; i< AGSM_MAX_MOTORS; i++)
     8      {
     9          axis[i] = 0;
    10          position[i] = 0;
    11          direction[i] = 0;
    12      }
    13
    14      if (dev_name == "id04/motor/02")
    15          register_signal(SIGALRM);
    16  }
    17
    18  StepperMotor::~StepperMotor()
    19  {
    20      unregister_signal(SIGALRM);
    21  }
    22
    23  void StepperMotor::signal_handler(long signo)
    24  {
    25      INFO_STREAM << "Inside signal handler for signal " << signo << endl;
    26
    27  //  Do what you want here
    28
    29  }

The init_device method is modified.

Line 14-15 : The device name is checked and if it is the correct name, the device is registered in the list of device wanted to receive the SIGALARM signal.

The destructor is also modified

Line 20 : Unregister the device from the list of devices which should receives the SIGALRM signal. Note that unregister a signal for a device which has not previously registered its interest for this signal does nothing.

The signal_handler method is redefined

Line 25 : Print signal number

Line 27 : Do what you have to do when the signal SIGALRM is received.

If all devices must be warned when the device server process receives the signal SIGALRM, removes line 14 in the init_device method.

###### Exiting a device server gracefully

A device server has to exit gracefully by unregistering itself from the database. The necessary action to gracefully exit are automatically executed on reception of the following signal :

- SIGINT, SIGTERM and SIGQUIT for device server running on Linux

- SIGINT, SIGTERM, SIGABRT and SIGBREAK for device server running on Windows

This does not prevents device server to also register interest at device or class levels for those signals. The user installed signal_handler method will first be called before the graceful exit.

##### Inheriting

This sub-chapter details how it is possible to inherit from an existing device pattern implementation. As the device pattern includes more than a single class, inheriting from an existing device pattern needs some explanations.

Let us suppose that the existing device pattern implementation is for devices of class A. This means that classes A and AClass already exists plus classes for all commands offered by device of class A. One new device pattern implementation for device of class B must be written with all the features offered by class A plus some new one. This is easily done with the inheritance. Writing a device pattern implementation for device of class B which inherits from device of class A means :

- Write the BClass class

- Write the B class

- Write B class specific commands

- Eventually redefine A class commands

The miscellaneous code fragments given below detail only what has to be updated to support device pattern inheritance

###### Writing the BClass

As you can guess, BClass has to inherit from AClass. The command_factory method must also be adapted.

     1  namespace B
     2  {
     3
     4  class BClass : public A::AClass
     5  {
     6  .....
     7  }
     8
     9  BClass::command_factory()
    10  {
    11      A::AClass::command_factory();
    12
    13      command_list.push_back(....);
    14  }
    15
    16  } /* End of B namespace */

Line 1 : Open the B namespace

Line 4 : BClass inherits from AClass which is defined in the A namespace.

Line 11 : Only the command_factory method of the BClass will be called at start-up. To create the AClass commands, the command_factory method of the AClass must also be executed. This is the reason of the line

Line 13 : Create BClass commands

###### Writing the B class

As you can guess, B has to inherits from A.

     1  namespace B
     2  {
     3
     4  class B : public A:A
     5  {
     6     .....
     7  };
     8
     9  B::B(Tango::DeviceClass *cl,const char *s):A::A(cl,s)
    10  {
    11     ....
    12     init_device();
    13  }
    14
    15  void B::init_device()
    16  {
    17     ....
    18  }
    19
    20  } /* End of B namespace */

Line 1 : Open the B namespace.

Line 4 : B inherits from A which is defined in the A namespace

Line 9 : The B constructor calls the right A constructor

###### Writing B class specific command

Noting special here. Write these classes as usual

###### Redefining A class command

It is possible to redefine a command which already exist in class A only if the command is created using the inheritance model (but keeping its input and output argument types). The method which really execute the class A command is a method implemented in the A class. This method must be defined as virtual. In class B, you can redefine the method executing the command and implement it following the needs of the B class.

##### Using another device pattern implementation within the same server

It is often necessary that inside the same device server, a method executing a command needs a command of another class to be executed. For instance, a device pattern implementation for a device driven by a serial line class can use the command offered by a serial line class embedded within the same device server process. To execute one of the command (or any other CORBA operations/attributes) of the serial line class, just call it as a normal client will do by using one instance of the DeviceProxy class*.* The ORB will recognize that all the devices are inside the same process and will execute calls as a local calls. To create the DeviceProxy class instance, the only thing you need to know is the name of the device you gave to the serial line device. Retrieving this could be easily done by a Tango device property. The DeviceProxy class is fully described in Tango Application Programming Interface (API) reference WEB pages

##### Device pipe

What a Tango device pipe is has been defined in the Chapter 3 about device server model. How you read or write a pipe in a client software is documented in chapter 4 about the Tango API. In this section, we describe how you can read/write into/from a device pipe on the server side (In a Tango class with pipe).

###### Client reading a pipe

When a client reads a pipe, the following methods are executed in the Tango class:

1. The always_executed_hook() method.

2. A method called is_<pipe_name>_allowed(). The rule of this method is to allow (or disallow) the next method to be executed. It is usefull for device with some pipes which can be read only in some precise conditions. It has one parameter which is the request type (read or write)

3. A method called read_<pipe_name>(). The aim of this method is to store the pipe data in the pipe object. It has one parameter which is a reference to the Pipe object to be read.

The figure 6.8 is a drawing of these method calls sequencing for our class StepperMotor with one pipe named DynData.

Figure 6.8: Read pipe sequencing

The class DynDataPipe is a simple class which follow the same skeleton from one Tango class to another. Therefore, this class is generated by the Tango code generator Pogo and the Tango class developper does not have to modify it. The method is_DynData_allowed() is relatively simple and in most cases the default code generated by Pogo is enough. The method read_DynData() is the method on which the Tango class developper has to concentrate on. The following code is one example of these two methods.

     1  bool StepperMotor::is_DynData_allowed(Tango::PipeReqType req)
     2  {
     3      if (get_state() == Tango::ON)
     4          return true;
     5      else
     6          return false;
     7  }
     8
     9  void StepperMotor::read_DynData(Tango::Pipe &pipe)
    10  {
    11      nb_call++;
    12      if (nb_call % 2 == 0)
    13      {
    14          pipe.set_root_blob_name("BlobCaseEven");
    15
    16          vector<string> de_names {"EvenFirstDE","EvenSecondDE"};
    17          pipe.set_data_elt_names(de_names);
    18
    19          dl = 666;
    20          v_db.clear();
    21          v_db.push_back(1.11);
    22          v_db.push_back(2.22);
    23
    24          pipe << dl << v_db;
    25      }
    26      else
    27      {
    28          pipe.set_root_blob_name("BlobCaseOdd");
    29
    30          vector<string> de_names {"OddFirstDE"};
    31          pipe.set_data_elt_names(de_names);
    32
    33          v_str.clear();
    34          v_str.push_back("Hola");
    35          v_str.push_back("Salut");
    36          v_str.push_back("Hi");
    37
    38          pipe << v_str;
    39      }
    40  }

The is_DynData_allowed method is defined between lines 1 and 7. It is allowed to read or write the pipe only is the device state is ON. Note that the input parameter req is not used. The parameter allows the user to know the type of request. The data type PipeReqType is one enumeration with two possible values which are READ_REQ and WRITE_REQ.

The read_DynData method is defined between lines 9 and 40. If the number of times this method has been called is even, the pipe contains two data elements. The first one is named EvenFirstDE and its data is a long. The second one is named EvenSecondDE and its data is an array of double. If the number of call is odd, the pipe contains only one data element. Its name is OddFirstDe and its data is an array of strings. Data are inserted into the pipe at lines 24 and 38. The variables nb_call, dl, v_db and v_str are device data member and therefore declare in the .h file. Refer to pipe section in chapter 3 and to the API reference documentation (in Tango WEB pages) to learn more on how you can insert data into a pipe and to know how data are organized within a pipe.

###### Client writing a pipe

When a client writes a pipe, the following methods are executed in the Tango class:

1. The always_executed_hook() method.

2. A method called is_<pipe_name>_allowed(). The rule of this method is to allow (or disallow) the next method to be executed. It is usefull for device with some pipes which can be read only in some precise conditions. It has one parameter which is the request type (read or write)

3. A method called write_<pipe_name>(). It has one parameter which is a reference to the WPipe object to be written. The aim of this method is to get the data to be written from the WPipe oject and to write them into the corresponding Tango class objects.

The figure 6.9 is a drawing of these method calls sequencing for our class StepperMotor with one pipe named DynData.

Figure 6.9: Write pipe sequencing

The class DynDataPipe is a simple class which follow the same skeleton from one Tango class to another. Therefore, this class is generated by the Tango code generator Pogo and the Tango class developper does not have to modify it. The method is_DynData_allowed() is relatively simple and in most cases the default code generated by Pogo is enough. The method write_DynData() is the method on which the Tango class developper has to concentrate on. The following code is one example of the write_DynData() method.

    1  void StepperMotor::write_DynData(Tango::WPipe &w_pipe)
    2  {
    3     string str;
    4     vector<float> v_fl;
    5
    6     w_pipe >> str >> v_fl;
    7     .....
    8  }

In this example, we know that the pipe will always contain a srting followed by one array of float. On top of that, we are not niterested by the

data element names. Data are extracted from the pipe at line 6 and are available for further use starting at line 7. If the content of the pipe is not a string followed by one array of float, the data extraction line (6) will throw one exception which will be reported to the client who has tried to write the pipe. Refer to pipe section in chapter 3 and to the API reference documentation (in Tango WEB pages) to learn more on how you can insert data into a pipe and to know how data are organized within a pipe.

[1]

The default is_allowed method behavior is to always allows the command

[2]

URL stands for Uniform Resource Locator

[3]

The StepperMotor class inherits from the DeviceImpl class and therefore is a DeviceImpl

[4]

The StepperMotorClass inherits from the DeviceClass and therefore is a DeviceClass

[5]

It can also be data declared as object data members or memory declared as static

## How-Tos

audience:all

This section contains a set of useful ‘How tos’ with instructions on how to perform some common tasks with Tango.

1. Getting started

2. For information on how to contribute to Tango including the IDL, documentation and source code in C++, Python and Java please see the Contributing section

3. The Debugging and Testing section provides instructions on how to use the Tango Docker containers to test newly developed Tango device servers.

4. The Deployment section provides a set of how tos describing different ways to deploy and run Tango. This includes running with and without a DB, using access control and other available services.

### Installation

audience:administrators audience:developers

Here you will find recipes on how to install the Tango Controls on various platforms. There are binary packages available on all platforms to get you started quickly.

#### Conda Packages

audience:administrators audience:developers audience:all

Most Tango packages are available on conda-forge for Linux, macOS and Windows.

Conda packages can be used in production or for development on your machine.

Note

Running the Tango `Databaseds` requires a `mariadb` database. The preferred method to install `mariadb` is to use your OS package manager. This will come with `systemd` integration on Linux. For local development, `docker` can be used. It is not recommended to use conda for `mariadb` itself (there is currently no `mariadb-server` on `conda-forge` but there is a `mysql-server`).

You can then run `Databaseds` by installing the `tango-database` conda package.

Tip

For local development, you can run `PyDatabaseds` instead by installing `pytango-db`, a pure Python implementation of the Tango Database relying on sqlite. No `mariadb` needed. This isn’t recommended for production at this point!

C++ packages

- tango-idl

- cpptango

- tango-database

- tango-admin[1]

- tango-access-control

- tango-starter

- tango-test

- universaltest

- libhdbpp

- libhdbpp-timescale

- hdbpp-cm

- hdbpp-es

Java packages

- jtango

- tango-atk

- tango-atk-panel

- tango-atk-tuning

- jive

- tango-astor[2]

- pogo

Python packages

- pytango

- pytango-db

- itango

- dsconfig

- taurus

- sardana

- svgsynoptic2

- tango-gateway

Tip

The above list is not exhaustive, especially for Python packages. You can always search on anaconda or prefix.dev to check if a package is available.

conda is the original package manager to work with conda packages, but there are alternatives:

- mamba: a Python-based CLI conceived as a drop-in replacement for `conda`, offering higher speed.

- micromamba: a pure C++-based CLI, self-contained in a single-file executable.

- pixi: a Rust-based package manager and workflow tool built on the foundation of the conda ecosystem. It is also the easiest way to install global tools.

##### conda/mamba/micromamba

In the following, we’ll show commands using `conda`. You should be able to replace `conda` with `mamba` or `micromamba` based on your preference.

`micromamba` supports a subset of all `mamba` or `conda` commands, but the most frequent ones are identical.

Warning

Anaconda distribution and Miniconda are linked to Anaconda Terms of Service. They come with Anaconda’s defaults channels, which have a pay-for-user policy.

Please review the terms of service and only use Anaconda Repository if you understand the consequences.

To install conda and mamba, you should use the Miniforge3 installer as it comes with both and `conda-forge` as the configured channel.

To install micromamba, check the official documentation.

If you have `conda` already installed, you should run `conda info` to check the configured channels.

    $ conda info
    ...
    channel URLs : https://conda.anaconda.org/conda-forge/...
                   https://conda.anaconda.org/conda-forge/noarch
    ...

Ensure you have `conda-forge` channel configured. It is the only channel you need to install all above packages. If `https://repo.anaconda.com/pkgs/...` is in your list of channels, you should remove it by editing your `.condarc` file. Refer to the official documentation.

Warning

When using `conda` or `mamba`, you should not install packages in the `base` environment. It is reserved for their dependencies. Always create a separate environment.

`micromamba` is a single executable and doesn’t come with a `base` environment.

To install `tango-test` and `pytango`, you can run:

    $ conda create -y -n tango tango-test python=3.12 pytango
    $ conda activate tango
    (tango) $ TangoTest --help
    (tango) $ python -c 'import tango; print(tango.utils.info())'

You can also run an executable in a conda environment without activating it:

    $ conda run -n tango TangoTest --help

##### pixi

`Pixi` is a package management tool for developers. It allows you to install libraries and applications in a reproducible way. It is also very convenient to install global tools.

Install `pixi` following the instructions on pixi website.

###### Installing global tools with pixi

To install tools that aren’t linked to any project, you can use the `pixi global` command. Install `jive`, `pogo`, `tango-database` on your machine:

    $ pixi global install jive pogo tango-database

This will create a separate environment for each of the application and make them available in your `PATH`. You can now invoke any of them (no activation needed):

    $ jive

See https://pixi.sh/latest/reference/cli/#global-options for more information.

###### Working on projects

audience:developers

When working on a project, `pixi` can manage environments for you.

If you clone a project with an existing `pixi.toml` file, like pytango repository, running `pixi run` will automatically create the defined environment.

If you work on a new project, create a `pixi.toml` file by running `pixi init`:

    $ cd myproject
    $ pixi init

You can then add all the requirements you need:

    $ pixi add python=3.12 pytango

This will automatically update the `pixi.toml` file as well as a `pixi.lock` file and create the environment under the `.pixi` directory. You can use that environment by running `pixi run` or `pixi shell` to activate it.

    $ pixi run python -c 'import tango; print(tango.utils.info())'

    $ pixi shell
    (myproject) $ python -c 'import tango; print(tango.utils.info())'

When using `pixi shell`, just enter `exit` to exit the shell.

[1]

`tango-admin` is not available on Windows

[2]

`astor` on conda-forge is taken by a Python package

#### Debian

audience:administrators audience:developers

Note

Below are instructions on how to install Tango 10. For instructions on how to install older versions of Tango please see previous versions of the Tango documentation.

##### Binary packages

If you don’t have special requirements for a specific tango version, the easiest way is to use the standard debian packages.

1. Installing the packages

    sudo apt install libtango-dev tango-db tango-test

You can use the default answers when asked questions during installation.

2. Starting the TangoTest device server

    /usr/lib/tango/TangoTest test

This should print `Ready to accept request`.

Now you can ping the TangoTest device server in another console

    tango_admin --ping-device sys/tg_test/1; echo $?

which should return `0`.

The drawback of the stock debian packages is that they don’t include any java applications like jive or astor.

If you need these, or a newer tango version, you can consider compiling the tango-controls package (called TangoSourceDistribution) in the next section.

##### Compilation of the TangoSourceDistribution

The following steps have been written for Debian bookworm and Ubuntu 24.04.

1. Install packages required to compile tango-controls:

   Debian

       sudo apt-get install g++ openjdk-17-jdk mariadb-server libmariadb-dev \
       zlib1g-dev libzmq3-dev cmake git protobuf-compiler-grpc libprotobuf-dev \
       libcurl4-openssl-dev libjpeg-dev libgrpc++-dev libabsl-dev

   For omniORB you need to use the packages from bookworm/backports.

   - Add them to APT by opening the file:

         sudo nano /etc/apt/sources.list.d/omniorb-bookworm-backports.list

     and adding the line:

         deb http://deb.debian.org/debian bookworm-backports main

   - Update the package sources

         sudo apt update

   - Install omniORB 4.3.x

         sudo apt install libomniorb4-dev/bookworm-backports libcos4-dev/bookworm-backports \
         omniidl/bookworm-backports

   Ubuntu

       sudo apt-get install g++ openjdk-17-jdk mariadb-server libmariadb-dev \
       zlib1g-dev libzmq3-dev cmake git libomniorb4-dev libcos4-dev omniidl \
       protobuf-compiler-grpc libprotobuf-dev libcurl4-openssl-dev libjpeg-dev \
       libgrpc++-dev

2. Start mariadb:

       sudo service mariadb start

3. Set the password for the mariabdb root user. Replace `<mypassword>` with an appropriate password in the command below:

       sudo mysql -u root -e "SET PASSWORD = PASSWORD('<mypassword>');"

4. Download OpenTelemetry dependency:

   Note

   Tango is built with OpenTelemetry by default. If you wish to install Tango without OpenTelemetry then please go straight to step 6.

   Check for the latest released version at open-telemetry/opentelemetry-cpp.

       mkdir opentelemetry
       cd opentelemetry

       wget https://github.com/open-telemetry/opentelemetry-cpp/archive/refs/tags/vX.X.X.tar.gz
       tar xzvf v*.tar.gz
       cd opentelemetry-cpp-*

5. Build OpenTelemetry

   - Create build directory

         mkdir build
         cd build

   - Configure build

         cmake .. -DWITH_OTLP_GRPC=ON -DWITH_OTLP_HTTP=ON -DBUILD_SHARED_LIBS=ON \
         -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DWITH_ABSEIL=ON -DWITH_BENCHMARK=OFF \
         -DWITH_EXAMPLES=OFF -DWITH_FUNC_TESTS=OFF -DWITH_DEPRECATED_SDK_FACTORY=OFF \
         -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo

   - Compile

         cmake --build . --target all

   - Install:

         sudo cmake --install .

6. Download the Tango source code:

   - Create a new directory under ~/:

         mkdir ~/tango
         cd ~/tango

   - The latest version can be downloaded from here, you want the `Tango Source Distribution` file. You can use `wget` to get the version you require:

         wget https://gitlab.com/api/v4/projects/24125890/packages/generic/TangoSourceDistribution/X.X.X/tango-X.X.X.tar.gz

   - Unpack:

         tar xzvf tango-*.tar.gz
         cd tango-X.X.X

7. Configure tango-controls to build and install in /usr/local (replacing `<mypassword>` with the password you set in step 3):

       cmake -B build -S . -DMYSQL_ADMIN=root -DMYSQL_ADMIN_PASSWD=<mypassword> \
       -DTDB_DATABASE_SCHEMA=ON

   Note

   To build without OpenTelemetry, add the argument `-DTANGO_USE_TELEMETRY=OFF` to the above command.

   Further CMake compilations flags are described in CMake options.

8. Compile

       cmake --build build --parallel $(nproc)

   where `$(nproc)` is the number of processes to use, for example `2`.

9. Install:

       sudo cmake --build build --target install

10. Add the following lines to the start script /usr/local/bin/tango, replacing `<mypassword>` with the password you set in step 3:

        sudo nano /usr/local/bin/tango

        # add lines near the top:

        export MYSQL_USER=root
        export MYSQL_PASSWORD=<mypassword>

11. Start tango-controls database server:

        sudo /usr/local/bin/tango start

12. Set the TANGO_HOST variable in `/etc/tangorc`:

        sudo nano /etc/tangorc

    and add

        TANGO_HOST=127.0.0.1:10000

13. Start the TangoTest device server:

        /usr/local/bin/TangoTest test &

    Note

    If you receive an error at this stage similar to

        error while loading shared libraries: libtango.so.10.0: cannot open
        shared object file: No such file or directory

    you will need to run the following command to get /usr/local/lib into the default runtime search path:

        sudo ldconfig

14. Test Jive:

        /usr/local/bin/jive &

You can now define your device servers and devices, start and test them!

###### CMake options

Below are a list of CMake options that can be added accordingly to the command in step 8 to configure the tango-controls build.

Option | Description | Allowed values | Default
--- | --- | --- | ---
TSD_LIB | Install and use bundled libtango, if OFF use libtango found on system | ON/OFF | ON
TSD_CPP | Installation of cppTango and C++ applications | ON/OFF | ON
TSD_JAVA | Installation of java applications | ON/OFF | ON
TSD_DATABASE | Installation of tango database server | ON/OFF | ON
TSD_TAC | Installation of access control server | ON/OFF | OFF
TSD_HTML_DOCUMENTATION | Include the HTML documentation | ON/OFF | ON
TANGO_USE_TELEMETRY | Build with OpenTelemetry | ON/OFF | ON

#### Red Hat based

audience:administrators audience:developers

Warning

Do NOT use CentOS anymore. CentOS 7 has been end-of-life since 2024-06-30. This was the last supported version of CentOS.

Migrate to another distribution: RHEL, CentOS Stream, AlmaLinux, Rocky Linux…

RPM packages for Red Hat based systems are built from the tango-spec repository using Copr. Copr can be used as a repository but only the latest build is kept forever. To install the packages directly from Copr, please refer to the tango-spec README.

RPM packages from Copr are also available in the MAX-IV’s repository.

Available RPM packages

tango-idl:

  Tango CORBA IDL file

libtango9:

  Tango C++ library

libtango9-devel:

  Tango C++ library development files

tango-common:

  shared infrastructure for Tango packages

tango-db:

  the Tango database device server, with systemd integration

tango-admin:

  provides tango_admin, a cli to the Tango database

tango-test:

  the TangoTest device server

tango-starter:

  the Tango Starter device server, with systemd integration

tango-java:

  java based Tango applications (jive, astor…)

tango-accesscontrol:

  the TangoAccessControl device server

##### Installation

To install Tango on Red Hat, here are the steps you should follow:

1. Add the EPEL repository:

       sudo dnf install -y epel-release

2. Add the MAX-IV’s public repository by creating the following file:

       sudo nano /etc/yum.repos.d/maxiv.repo

       [maxiv-public]
       name=MAX IV public RPM Packages - $basearch
       baseurl=http://pubrepo.maxiv.lu.se/rpm/el$releasever/$basearch
       gpgcheck=0
       enabled=1

       sudo dnf makecache

3. Install and start MariaDB:

       sudo dnf install -y mariadb-server mariadb
       sudo systemctl start mariadb
       sudo systemctl enable mariadb

4. Run mysql_secure_installation script:

       sudo mysql_secure_installation

5. Install the tango Databaseds

       sudo dnf install -y tango-db

6. Create TANGO database:

       cd /usr/share/tango-db/
       sudo ./create_db.sh

7. Set up TANGO environment:

   Note

   You should not use `localhost` as your TANGO_HOST. You can set the machine hostname using sudo hostnamectl set-hostname tangobox

       sudo nano /etc/tangorc

   For example:

       TANGO_HOST=tangobox:10000

8. Set up environment variables:

       sudo nano /etc/profile.d/tango.sh

   For example:

       . /etc/tangorc
       export TANGO_HOST

9. Start and enable TANGO database:

       sudo systemctl start tango-db
       sudo systemctl enable tango-db

10. Install Starter and TangoTest:

        sudo dnf install -y tango-starter tango-test

11. Start and enable Starter:

        sudo systemctl start tango-starter
        sudo systemctl enable tango-starter

12. Install Java based tools:

    RHEL 8

        # Enabling powertools and javapackages-tools is required to install log4j12
        sudo dnf config-manager --set-enabled powertools
        sudo dnf module -y enable javapackages-tools
        sudo dnf install -y tango-java

    RHEL 9

        sudo dnf install -y tango-java

Note

`pytango` isn’t packaged anymore as RPM. Last version packaged was 9.4.2. Use pip in a virtualenv or conda to install a recent version.

#### Windows

audience:administrators audience:developers

If you need a full-fledged installation on Windows with Tango Database and JAVA tools like Jive, ATK, etc. use the Windows Installer. If you are just looking for precompiled cppTango libraries, head over to the cppTango binaries for windows section.

We don’t describe compiling cppTango from source here as that is very involved and rarely needed. Head over to the cppTango repository for the gritty details.

The packages are tested and build against Windows 10, but should work on earlier versions as well.

##### Tango installer package

Download the `Windows installer` from the TangoSourceDistribution releases page and execute it.

This packages includes a lot of the default tango tools, but not the MariaDB/MySQL database server.

###### Configure the `TANGO_HOST` environment variable

For Windows 10 and 11 you need to do:

- From the Desktop, right-click the very bottom left corner of the screen to get the `Task Menu`.

- From the `Task Menu`, click `System`.

- Click the `Advanced System Settings` link in the left column.

- In the System Properties window, click on the `Advanced` tab, then click the `Environment Variables` button near the bottom of that tab.

- In the `Environment Variables` window click the `New` button.

- In the field `Name` write `TANGO_HOST`.

- In the field `Value` write proper value.

If it is the only computer in the Tango System provide `localhost:10000`.

If there is a `Tango Host` already running on some other computer in your deployment and you have provided proper address and port in the `TANGO_HOST` you may start using client and management applications like `Jive`, `Jdraw`/`Synoptic`. In other case you have to configure the system to perform a role of `Tango Host`.

##### Tango Host role

To make a computer become a Tango Host you need to:

###### Install MariaDB server

You may use community version available from https://mariadb.com/downloads.

It is suggested to create dedicated `tango` user with DB Admin priviledges during installation. In the installation wizard on a tab `Accounts and Roles` select button `Add User` and create a dedicated user.

Set up environment variables providing credentials to access MariaDB:

- Open `Command Line`

- Invoke command: `%TANGO_ROOT%\\bin\\dbconfig.exe`

  Note

  This lets you set up two environment variables `MYSQL_USER` and `MYSQL_PASSWORD` used to access the MariaDB server. You can check if variables were set correctly, if not you can set it manually. It’s recommended to restart computer after operation. You may use `root` credentials provided upon MariaDB installation if it is your development workstation. For production environment it is suggested to create an additional user with `DB Admin` privileges. On Windows you may use `MariaDB Installer` from `Start` menu and select the option `Reconfigure` for MariaDB Server. Please refer to: https://mariadb.com/kb/en/create-user/

- Populate database with an initial Tango configuration:

  - Open `Command Line`

  - Add MariaDB client to be available in the PATH. For version 5.7 the command should be: `set PATH=%PATH%;"C:\\Program Files\\MariaDB\\MariaDB Server 5.7\\bin"`

    Note

    Adjust the path according to your MariaDB version and the path where it is installed.

  - Invoke `cd "%TANGO_ROOT%\\share\\tango\\db\\"`

  - Call `create_db.bat`

- Start a `DataBaseds` `device server`:

  - Open a new command line window.

  - In the command line call `"%TANGO_ROOT%\\bin\\start-db.bat"`.

    Note

    To make your Tango installation operational you have to have this `DataBaseds` running permanently. You may either add the command above to `Autostart` or run it as a service.

- Make `DataBaseds` run as a service

  Note

  The proposed solution uses NSSM tool which works on all versions of Windows but you may find some other tools available including native srvany.exe.

  - Download NSSM from http://nssm.cc/

  - Unpack the file to some convenient location. It is suggested to copy proper (32bit or 64bit) version to the Tango bin folder `%TANGO_ROOT%\\bin\\`.

  - Open `Command Line` as `Administrator`.

  - Change current path to where the `nssm` is unpacked or copied, eg. `cd "%TANGO_ROOT%\\bin"`.

  - Invoke `nssm.exe install Tango-DataBaseds`. This will open a window where you can define service parameters.

    - In the Application tab provide information as follows (adjust if your installation path is different).

    - In the Environment tab provide variables with credentials used for accessing the MariaDB, like:

    - Click `Install Service`.

    - Invoke `nssm.exe start Tango-DataBaseds` to start the service.

    - Test if everything is ok. Use `Start` menu to run Jive or in command line call `"%TANGO_ROOT%\\bin\\start-jive.bat"`.

##### Running Device Servers

The recommended way of running device servers is to use Starter service. Then you may use `NSSM` as for `DataBaseds`. Assuming you have downloaded it and copied to the Tango bin folder please follow:

- Open Command Line as Administrator (if it is not yet open)

- Prepare folder for `Device Servers` executable:

Note

To let your device servers start with `Starter` service their executables have to be in a path without spaces. This is a limitation of the current `Starter` implementation.

- Create a directory for `Device Servers`. Let it be `C:\\DeviceServers\\bin` with `mkdir c:\\DeviceServers\\bin`

- Change to the Tango bin directory with command (`cd "%TANGO_ROOT%\\bin"`)

- Copy `TangoTest` `device server` to the newly crated folder: `copy TangoTest.exe c:\\DeviceServers\\bin`

- Add entry about the Starter device server you will start on your computer:

  - Start a tool called `Astor`. You may use either Windows `Start` menu or call `tango-astor.bat`

  - In `Astor` window select menu `&Command --> Add a New Host`

  - In the form that appears provide your `Host name` and `Device Servers PATH`.

  - Accept with `Create`

  - Go back to `Command Line`

- Install Starter service:

    - Invoke `nssm.exe install Tango-Starter`.

    - In the Application tab provide information as follows:

    Adjust if your installation path is different. In `Arguments` exchange `pg-dell-new` with the proper name of your host.

    - In the Environment tab provide TANGO_HOST variable, like:

    - Click `Install service`

    - Start the service: `nssm.exe start Tango-Starter`.

    - Go back to `Astor`.

    - After a while you will see a green led next to your host name:

- Run `TangoTest` device server:

  You may test the configuration by starting prefigured TangoTest device.

  - Start `Astor` if it is not running.

  - Double Click on your computer name to open `Control Panel`. It opens a window as below:

  - Click `Start new`.

  - In the open window select TangoTest/test:

  - Click `Start Server`.

  - In the open window select `Controlled by Astro -> Yes`, and `Startup Level -> Level 1`.

  - When you click `OK` it should start the server. After a while you should see:

- Running your `Device Servers <device server>`:

    - You need to copy an executable to the folder configured for `Starter`. In our example it is `C:\\DeviceServers\\bin`.

    - Then use `Astor`. After opening `Control panel` for your computer (double clicking on a label) and selection `Start New`…

    - Select `Create New Server` and follow a wizard.

##### cppTango binaries for windows

There are zip and msi packages available. Download the appropriate package from the cppTango release page. If in doubt you should prefer the `XXX_x64_shared_release.zip` packages. If you need opentelemetry support, you currently have to use the `static` packages.

Regarding linkage against the Visual Studio runtime libraries, the static cppTango library links statically against the VC libraries and the dynamic library links dynamically against it.

###### Silent installation

The MSI packages support silent installation via the documented flags:

    msiexec /package libtango*.msi /quiet /passive

#### macOS

audience:administrators audience:developers lang:all

There are no macOS app installer packages available for Tango Controls. Any installation of Tango Controls libraries or tools is done on the command line.

##### Conda: Everything you’ll usually need

The easiest way to get all the necessary Tango Controls libraries and tools for development or administration installed on macOS is to use Conda. In our Conda document we explain step by step how to to get the packages installed on macOS.

##### But I need only PyTango

There are developers though that might only need PyTango and nothing else. In that case the simpler way to install just PyTango is to use pip.

###### Installation

1. Begin by creating a virtual environment:

    python3 -m venv --upgrade-deps .venv

- Next activate the virtual environment:

    source .venv/bin/activate

- Then install PyTango from PyPi:

    python3 -m pip --require-virtualenv pytango

- Finally set up TANGO environment:

Note

You should not use `localhost` as your TANGO_HOST. You should use either `127.0.0.1` or your computer’s host name.

For example:

    export TANGO_HOST=mymaclaptop:10000

###### Test your PyTango installation

To see if the PyTango installation was successful, you can run a simple command:

    # Do not forget to activate your virtual environment
    source .venv/bin/activate

    python3 -c 'import tango; print(tango.utils.info())'

The output should look similar to this:

    PyTango 10.0.1.dev0 (10, 0, 1, 'dev', 0)
    PyTango compiled with:
        Python   : 3.12.7
        Numpy    : 2.1.2
        Tango    : 10.1.0
        pybind11 : 2.13.6

    PyTango runtime is:
        Python   : 3.12.7
        Numpy    : 2.1.1
        Tango    : 10.1.0

    PyTango running on:
    uname_result(system='Darwin', node='XX:XX:XX:XX:XX:XX', release='24.0.0', version='Darwin Kernel Version 24.0.0: Tue Sep 24 23:39:07 PDT 2024; root:xnu-11215.1.12~1/RELEASE_ARM64_T6000', machine='arm64')

If Python cannot find the `tango` import, then you likely forgot to activate your environment.

##### For the really adventurous among you

If you fell lucky and would really like to build everything from the source codes, then you might be interested in Thomas Juerges’ Tango Controls build scripts. They are relatively easy to use, can be configured quite a lot and usually get you to a working Tango Controls system with cppTango, PyTango and a handful of tools within two or three minutes depending on the speed of your mac. Please head over to the Gitlab repository where the project’s README will guide you through the installation steps. Please note that you will need Homebrew installed.

#### Virtual Machine

audience:administrators audience:developers audience:all

The purpose of the TANGO Box Virtual Machine is to give you a fast, out-of-the-box experience of a working TANGO system. There is a set of shortcuts to all essential TANGO tools on the virtual machine desktop. Together with the user documentation, that allow you to experience the power and elegance of a fully configured TANGO system with all the latest tools first hand. After this “guided tour” of the TANGO system, TANGO Box is an excellent tool to make further explorations on your own, to use it for demonstration purposes, to make studies, proof-of-concepts and even production ready systems.

These images can also be used on cloud providers by converting them to the appropriate format.

##### TangoBox 10.3

- You may download TangoBox 10.3 from here.

There is currently no up-to-date documentation.

##### TangoBox 9.3

- You may download TangoBox 9.3 from here.

- Please read Tango Controls demo VM’s documentation.

- See also a release note and README.

###### Minimum Requirements

- 2vCPU

- 2GB (preferred 4GB) RAM

- 30GB of disk space

#### TangoBox 9.3

audience:all

TangoBox is a VM image running Tango Controls system and its various tools. It is intended to be used for demonstration and training. It also simulates distributed deployment by using Docker.

##### Download

The latest version of the TangoBox 9.3 can be downloaded from here.

##### What is installed

Below there is list of provide packages/features. Please note that some of them are installed as docker container and maybe switched off (stopped) and requires to be switched on for being explored, see Switching containers on and off

- Tango 9.3.3

- Tango Access Control

- PyTango 9.3.3

- Taurus 4.5.1

- QTango

- Cumbia

- ITango

- Sardana 2.7.0

- PANIC/PyAlarm

- Bensikin

- Mambo

- Docker

- Linac system simulation (as docker container tangobox-sim)

- HDB/TDB, SNAP DS (as docker container tangobox-archiving)

- HDB++ (as docker container tangobox-hdbpp)

- JupyTango (as docker container tangobox-jupytango)

- SerialLine, Modbus and PyPLC device server (as docker container tangobox-com)

- mTango + restAPI , Tango WebApp (as docker container tangobox-web)

- E-giga (as docker container tangobox-egiga)

- PyCharm

- ModbusPal to simulate Modbus

##### First steps

- First of all you have to download latest release of VirtualBox. It can be downloaded from www.virtualbox.org . Simply install it and start the program.

- TangoBox is released as an .ova package so it can be easily imported.

- Select import and choose downloaded TangoBox file

- If you want, you can change VM’s configuration (i.e graphics, RAM). It is highly recommended to increase default RAM size

  > A virtual machine settings window.

  > You may change number of CPUs and increase RAM size. Memory size has major impact on VM performance.

- Wait for VirtualBox to import machine

After importing the VM image to VirtualBox you may start it.

- Username is: `tango-cs`

- Password is: `tango`

`tango-cs` user has sudo privileges, so he may invoke commands as superuser with command sudo.

You may explore the Tango Controls feature by clicking related shortcuts on the Desktop.

Note

Plese note that some shortcuts are related to features running on containers. Please start related container first. See the following section.

##### Switching containers on and off

Some of the features of Tango are provided inside pre-build docker containers. These can be switched on and off by starting or stopping related containers. Containers behave similar to virtual machines with they own network cards and operating system stack, however, lacking full separations.

To start a container, open terminal and invoke docker start {container-name}. For example, to star a linac simulation use the following statement:

    docker start tangobox-sim

To stop a container, open terminal and invoke docker stop {container-name}. For example, to stop a linac simulation use the following statement:

    docker stop tangobox-sim

To see which containers are running please, call docker ps

##### Deployment structure

###### Network

Containers are created withing their own subnet: `172.18.0.0/16`. The network is called `tango_nw` The subnet was created with the following docker command:

    docker network create --driver=bridge --subnet=172.18.0.0/16 --opt com.docker.network.bridge.enable_icc=true \
    --opt com.docker.network.bridge.host_binding_ipv4="0.0.0.0" --opt com.docker.network.bridge.mtu=1500 \
    --opt com.docker.network.bridge.enable_ip_masquerade=true  tango_nw

Containers are assigned static IPs. List of the IPs assignment maybe seen in `/etc/hosts`. Use command cat /etc/hosts to see its contents.

###### Containers and images dependency

Each container is based on its image. All images are already build but, if neccessary, Dockerfiles are stored in `home/Dockerfiles` directory. Below is the list of all containers and corresponding images:

Container | Image | Remarks
--- | --- | ---
tangobox-com | registry/tangobox-com | -
tangobox-sim | registry/tangobox-sim | -
tangobox-archiving | registry/tangobox-archive | -
tangobox-hdbpp | registry/tangobox-hdbpp | -
tangobox-web | registry/tangobox-web | -
tangobox-egiga | registry/tangobox-egiga | -
tangobox-jupytango | registry/tangobox-jupytango | -
- | registry/tangobox-base | Base container
- | ubuntu | Ubuntu image to build others

Currently images are stored in `registry.gitlab.com/s2innovation/tangobox-docker` registry.

Some device servers may be stopped when launching containers. It is so to get better performance (high cpu and ram usage). To control and start/stop particular DS according to your needs, use Tango Manager (Astor) to it.

##### Example applications

###### Modbus simulation

To simulate Modbus, we suggest to use ModbusPal. To do so, use ModbusPal (simulation) desktop shortcut. Once it is started, declare new modbus slave by clicking ADD. Choose 1 and name it 10.0.2.15. Now click “eye” button in modbus slaves section and add at least 10 holding registers. You can change their values according to your needs.

After that, use RUN button to start simulation. Values in registers can be changed by clicking “eye” button in Modbus slaves section.

Keep in mind that in ModbusPal, registers are counted from 1 while on DS from 0!

To monitor changes, use ATKPanel started from Jive. Both ModbusComposer and PyPLC have two attributes configured:

- ModbusComposer: Temperature uses 4th; Pressure uses 5th register in ModbusPal

- PyPLC: Voltage uses 6th; Flow uses 7th register in ModbusPal

View on a ModbusComposer device and configured ModbusPal simulator.

###### JupyTango

JupyTango is a Jupyter featuring Tango related kernels. With JupyterLab you may interact and do scripting for Tango through a web browser.

Browser window with JupyTango in action

In case you want to try it, here’s the procedure:

1. start jupyterlab (it is started by default): `docker start tangobox-jupytango`

2. open a new browser window and go to `http://tangobox-jupytango:8888/lab`

3. enjoy!

Here are the JupyTango additions to itango:

Plotting a tango attribute

Syntax:

    pta [options] <tab for device selection> + <tab for attribute selection>

Supported options:

- `-w` or `--width`: plot width in pixels

- `-h` or `--height`: plot height in pixels

Monitoring a tango attribute: Syntax:

    tm [options]  + <tab for device selection> + <tab for attribute selection>

Supported options:

- `-w` or `--width`: plot width in pixels

- `-h` or `--height`: plot height in pixels

- `-p` or `--period`: plot refresh period in [0.1, 5] seconds - defaults to 1s

- `-d` or `--depth`: scalar attribute history depth in [1, 3600] seconds - defaults to 900s

You can try to kill the monitored device will the JupyTango monitor is running to see how errors are handled.

###### JLinac simulation

To start simulation, you need to run tangobox-sim container (use docker start tangobox-sim to start it from a terminal). It is also important to make sure that all related device servers are running. The easiest way to do it is to check it in Astor - a bulb next to `tangobox-sim` should be green.

JLinac simulation running.

###### HDB/TDB/SNAP Archiving (Mambo, Bensikin)

Prior to use `HDB/TDB` (Mambo) or `SNAP` (Bensikin) you need to make sure that the tangobox-archiving container and related device servers are running:

- Call docker start tangobox-archive on a terminal.

- Start Astor and check if the `tangobox-archive` node is green.

Then, you may start Mambo or Bensikin by clicking icons on the desktop.

Screen of running Mambo

Please take note of a green bulb of the `tangobox-archive` node in the Astor window.

###### HDB++ Archiving

To use `HDB++` and its tools ( HDB Configurator and HDB Viewer) please make sure that the tangobox-hdbpp container and related device servers are running:

- Call docker start tangobox-hdbpp on a terminal.

- Start Astor and check if the `tangobox-hdbpp` node is green.

Then, you may start HDB Configurator or HDB Viewer by clicking icons on the desktop.

HDB Configurator and HDB Viewer

###### E-giga

E-giga is a web application for archiving data visualization (HDB/TDB and HDB++). The TangoBox deployment uses HDB/TDB.

E-giga in a web browser window

To use e-giga following conditions must be fulfilled:

- `tangobox-archive` and `tangobox-web` containers must be started and archiving device servers must be running

- use i.e. Mambo to enable data archiving for HDB database. It is required. If you do not see any attributes in `E-giga` it is probably due to archiving being disabled. Check with Astor if the tangobox-archive LED is green and with Mambo if there are any attributes configured to be archivied.

To open browser with E-giga click on the relate desktop icon.

###### Tango WebApp

Tango may be available through a web browser. `Tango WebApp` is a general purpose Tango web application. You may try it on the TangoBox.

A screenshot of Tango WebApp in a browser

To play with Tango WebApp make sure that the ‘tangobox-web` container is running (use docker start tangobox-web to start it from a terminal). Then, you may open a browser with a related desktop icon. Use username `tango-cs` and password `tango` to log-in.

###### REST API

Tango Controls specifies REST API interface and provides its reference implementation. For details see REST API documentation

The TangoBox comes with REST API installed. Related desktop icon opens a web browser pointing to REST API interface. The REST server requires authentication. User is `tango-cs` and password is `tango`.

A web browser window presenting JSON response of the Tango REST server

If you would like to play with it with other tools (Python, curl) it is avaialabe at the following address: `http://localhost:10001/tango/rest/rc4/hosts/tangobox/10000`.

###### Sardana

Sardana is a software suite for Supervision, Control and Data Acquisition in scientific installations. I t aims to reduce cost and time of design, development and support of the control and data acquisition systems. For more information about it please refer to Sardana documentation.

SardanaGUI in action

To play with Sardana you may double-click the SardanaGUI icon on the desktop or run it from a terminal (type SardanaGUI).

###### Cumbia

> Cumbia is a new library that offers a carefree approach to multi-threaded application design and implementation. Written from scratch, it can be seen as the evolution of the QTango library, because it offers a more flexible and object oriented multi-threaded programming style.

For more details please check Cumbia webpage and source repository.

Cumbia is installed in `/usr/local/cumbia-libs`. This directory is added to `ld`’s default search path.

To see an example Cumbia application, please run below command or use desktop shortcut CumbiaClientDemo:

    cumbia client sys/tg_test/1/double_scalar

Cumbia demo application

###### PANIC/PyAlarm

> PANIC Alarm System is a set of tools (api, Tango device server, user interface) that provides: Periodic evaluation of a set of conditions, Notification (email, sms, pop-up, speakers), Notification (email, sms, pop-up, speakers), Keep a log of what happened. (files, Tango Snapshots), Taking automated actions (Tango commands / attributes), Tools for configuration/visualization.

> The Panic package contains the python AlarmAPI for managing the PyAlarm device servers from a client application or a python shell. The panic module is used by PyAlarm, Panic Toolbar and Panic GUI.

To launch PANIC GUI, use desktop shortcut PANIC.

PANIC GUI application

#### SNAPshot (Archiving) installation and configuration

audience:all

##### Overview

The SNAPshot system is distributed as a part of the Archiving solution.

The server side consists of a database (either MySQL or Oracle) and a set of device servers. On the client side, the application Bensikin is used.

To use SNAPshot system, at lease one device must be created for each class:

- SnapManager, responsible for creating snapshot contexts and applying snapshots to the devices,

- SnapArchiver, responsible for creating snapshots and storing them in the database,

- SnapExtractor, responsible for extracting snapshots from the database.

If more that one device of a particular class is defined, Archiving API will randomly assign a device for each request.

##### Prerequisites

Following components are required to set-up and use Archiving:

- Tango Controls,

- SQL database (this guide assumes MariaDB 10),

- Java Runtime Environment (this guide assumes OpenJDK 8).

Note

This guide assumes that the installation is performed on the Ubuntu system but it should be applicable also to the other GNU/Linux-based systems.

To install SNAPshot on Windows, minor changes need to be applied, e.g. the applications from the win32 directory should be used instead of the applications from the linux directory.

##### Installation

Installation steps:

1. download and extract the *ArchivingRoot* package,

2. add the executable flag to the scripts located in `bin/linux`,

3. add the executable flat to the scripts located in `device/linux`,

4. replace the `sh` interpreter with `bash` in shebang in scripts located in `bin/linux` and in `device/linux`. The scripts are not POSIX compliant,

5. run SQL script `db/create-SNAPDB-InnoDB.sql` to create the database and users,

6. set `ARCHIVING_ROOT` environment variable to point to the directory with the contents of the ArchivingRoot package.

##### Configuration

Configuration steps:

1. define following devices:

     Server/instance | Class | Device
     --- | --- | ---
     SnapManager/1 | SnapManager | archiving/snap/manager.1
     SnapExtractor/1 | SnapExtractor | archiving/snap/extractor.1
     SnapArchiver/1 | SnapArchiver | archiving/snap/archiver.1

2. configure SnapManager class properties:

     - DbUser: snapmanager,

     - DbPassword: snapmanager,

3. configure SnapExtractor device properties:

     - DbUser: snapbrowser,

     - DbPassword: snapbrowser,

4. configure SnapArchiver device properties:

     - DbUser: snaparchiver,

     - DbPassword: snaparchiver,

     - BeansFileName: beansBeamline.xml.

Note

If BeansFileName is configured as a device property but the server is still looking for the default beans.xml during startup, the init command must be executed as a workaround.

    ----- Hibernate Resources -----
    configure hibernate resource: beans.xml
    Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from class path resource [beans.xml]; nested exception is java.io.FileNotFoundException: class path resource [beans.xml] cannot be opened because it does not exist
    ...

Alternatively, BeansFileName can be configured as a class property.

##### Usage

To use SNAPshot system, all device servers must be started.

The application `bin/linux/bensikin-rw` can be used to configure and trigger snapshots. See Bensikin manual for more details.

Note

The machine where Bensikin is running must have access to the snapshot database. Bensikin will use credentials specified in SnapManager class configuration.

##### Properties reference

Following tables summarize configuration properties. The Class column indicates that this is the class property. Otherwise it is a device property.

Note

The database connection properties are defined in device classes:

- SnapManager for Snap

Every device and application that has to connect to a database will read the properties of these classes. However, every device can have its database connection properties redefined in the device properties.

###### SnapManager properties

Name | Description | Default | Mand. | Class
--- | --- | --- | --- | ---
Description | Device description |  |  | ✓
ProjectTitle | Project description |  |  | ✓
DbUser | User name used to connect to the database | archiver | ✓ |
DbPassword | Password used to connect ot the database | archiver | ✓ |
DbHost | Database Host name | localhost | ✓ | ✓
DbName | Database name |  | ✓ | ✓
DbSchema | Schema name |  | ✓ | ✓
isRac | Oracle database is in Rac Mode | false | ✓ | ✓

###### SnapArchiver properties

Name | Description | Default | Mand. | Class
--- | --- | --- | --- | ---
Description | Device description |  |  | ✓
ProjectTitle | Project description |  |  | ✓
DbUser | User name used to connect to the database | archiver |  |
DbPassword | Password used to connect ot the database | archiver |  |
DbHost | Database Host name | localhost |  |
DbName | Database name |  |  |
DbSchema | Schema name |  |  |
beansFileName | Name of the beans file (on CLASSPATH) | beans.xml |  |

Note

This device will check SnapManager class properties to discover how to connect to the database.

###### SnapExtractor properties

Name | Description | Default | Mand. | Class
--- | --- | --- | --- | ---
Description | Device description |  |  | ✓
ProjectTitle | Project description |  |  | ✓
DbUser | User name used to connect to the database | snap | ✓ |
DbPassword | Password used to connect ot the database | snap | ✓ |

Note

This device will check SnapManager class properties to discover how to connect to the database.

### Getting Started

audience:all lang:all

In this section we will guide you step-by-step to help you getting started with Tango-Controls.

#### First steps

Some first steps might include:

- Reading through the Overview section will provide you with the basic information on Tango Controls. It will help you understand some of the key concepts behind Tango Controls.

- Start with either a preconfigured virtual machine or try installing a basic Tango controls set up on your own computer - see trying Tango Controls .

- Installing Tango Controls on your own system following instructions from the Installation guides .

- Start connecting your devices to Tango Controls :

  - browse the Device Classes Catalogue to find device servers for your equipment

  - read how to start a device server

  - or read Write your first C++ TANGO device class and follow a guide How to write your device class if your device is not yet supported by any existing device server.

- Writing your first client following instruction from Write your first Tango client.

- Learn how to use some of the tools provided in Tango:

  - Jive

  - ATKPanel

  - Astor

  - JDraw

  - Pogo

- Explore the content of the Tango Controls web page

#### Specific how to guides

##### How to try Tango Controls

audience:developers audience:all

There are several ways to try the Tango Controls System.

For a first quick look, you can download and run the TangoBox Virtual Machine. Alternatively, you can go ahead and install Tango on your system - see the installation section for a full set of instructions on how to do this on your chosen operating system.

It may also be worth looking at configuring the Tango components to run as services using systemd to automate the start up, please see Use Tango with systemd integration.

Assuming you have installed and configured Tango you can begin to use it.

###### Play with Tango Controls

The Tango ecosystem provides a lot of management applications and frameworks to visualize the data. This section provides a quick overview of a basic use case for Tango Controls.

If you have set up a tango-starter systemd service then it will automatically add the new host, however this can also be done manually using Astor. This application is used to configure the Control System and its components. It also provides a quick view of the statuses of all device servers in the Tango Database. To add a new host manually using Astor see: Add a new controlled host.

TangoTest this is a device class that provides all types of attributes available in Tango Devices and so can be used for testing purposes. Astor can be used to start this device server. After opening the control panel for the specific host, you can start a new device server, e.g.:

Tango Starter also needs to be running. Further information is available on starting a new Tango device server in Astor.

We can the open the AtkPanel from the Jive application and view the attributes, properties, and all configuration settings for the selected device.

Open the Jive application by typing the command `jive` in a console. Then select the Monitor Device option from the right-click menu on that Tango Device, e.g.:

Users can use the AtkPanel to execute commands on the selected TangoTest device. Some useful commands one might try issuing for this device are:

- `SwitchStates` - changes the state of the device (e.g. from RUN to FAULT or FAULT to RUN)

- `DevType` - this is a DevType command example

- `State` - return the state of the device

- `CrashFromX` - simulate a crash of the device

The attributes shown in the AtkPanel are mostly real-time values. If instead a user wants to view how the attribute value changes they can use the Taurus framework widgets.

TangoTest has an attribute that is generated using a trigonometric functions, so it is easy to check if the device is working correctly. Below is the `TaurusTrend` view of the `double_scalar_rww` TangoTest attribute:

To run TaurusTrend uses a command:

    taurus trend sys/tg_test/1/double_scalar_rww

Taurus also has a custom device panel (similar to the AtkPanel) which can be started with:

    taurus device sys/tg_test/1/double_scalar_rww

The Tango Archiving System can be used to store the attribute value changes long term.

##### Use the end-user applications

audience:all

End-users and administrators will be interested in how to use some of the tool provided by Tango to help interact with the control system. A full list of the tools delivered with Tango Controls can be found in the tools section.

Below, you will find a list of tools a beginner user usually needs to know.

###### Jive

Jive is a tool used to configure components of the Tango Controls and browse a static Tango Database. See the Jive Manual for more information and examples of how to use it.

###### ATKPanel

ATKPanel is a simple application which shows (and allows one to modify or invoke) device state, attributes and commands. Thus it allows one to test and control all devices in the system. The tool is delivered together with Tango Controls. It may be opened as a stand-alone application or invoked from Jive. See ATKPanel Manual for more information and examples of how to use it.

###### LogViewer

Tango provides a logging facility and the LogViewer application can be used to view such logs. This application is delivered with Tango Controls.

Administrators for Tango Controls may also be interested in the following tools:

###### Astor

Astor is a tool for management of Tango Controls system.

###### Tango Database

Tango Admin is a command-line interface for Tango Database management.

##### Start a device server

audience:all

While reading this how-to please refer to manuals of Jive and Astor tools.

###### Starting device servers with Jive

- In Jive select Tools > Server Wizard from the menu.

- In the wizard, fill the server and the instance name and click Next. The server name should be the same as the server executable name, and instance name can be any name you provide.

Wizard - step 1 - device server instance

- The wizard will now wait until you start the device server, which you can do from the command line. Device servers usually take the instance name as an argument. For this example you can use the command `TangoTest test2` where test2 is the instance name. When done click Next.

Wizard - step 2 - starting the device server

- In the next steps you will be asked to configure the devices that your device server instance will host. For this you need to pick a class and name for your device. A list of supported classes is provided by the device server, and the name of the device can be any name unique in the system following the format `<domain>/<family>/<member>`.

Wizard - step 3 - device class

Wizard - step 4 - device name

- Next, you can configure properties for the newly created devices.

Wizard - step 5 - device properties

- After that you can create another device of the same class by clicking New Device or pick another class by clicking New Class. You can also finish the configuration and exit the wizard with the Finish button.

Wizard - step 6 - finish

- After the configuration, the device server must be restarted to load and start configured devices.

Wizard - step 7 - device server restart

###### Starting device servers with Astor

Note

To configure device servers with Astor, you will need Starter installed and running on the host you want to start your device server on. Device server should be available in paths configured for the Starter.

- In Astor go to the host control panel and click Start New button in the top left corner. If you want to just start an existing device server instance, pick it from the list and click Start Server.

Server list in Astor

- If you want to add new server, click Create New Server. This will open the device installation wizard from Jive. Astor will handle starting the device server for you in step 2 of the wizard.

- At the end of the wizard, you will be asked to select the startup level of the new server.

Startup level configuration

###### Other options

You can also use the Tango Admin utility to register servers from scripts or the command line. There are also some device servers (e.g. Sardana) that can register themselves upon starting.

##### Configure properties on a new device

To set-up a new device you need to know about all the device properties and their values which must be configured to make the device work. You need to have a description on the property which should indicate clearly its use. Also you need to know about a specified default value.

When creating the device interface with Pogo a description and a default value can be entered for every device property. This information is used by the device installation wizard (available with Jive) to guide you through the configuration.

When creating a new server start the wizard from the Tools menu -> Server Wizard. It allows you to create a new device and to initialise it property by property. For every property the description is displayed and the default value can be viewed. To use the wizard on an already existing device you can right click on the device and choose Device Wizard. You will be guided again through all the properties of the device. At the end the device can be re-started when necessary. Because the wizard is part of Jive, you can test the device configuration immediately.

### Deployment

audience:administrators

This section provides a set of common how-to tasks relating to the deployment of Tango.

audience:administrators audience:developers

#### Transfer events using the multicast protocol

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.

##### 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 an 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 minimal bits of information which need to be provided to Tango to get multicast transport. This configuration is done using the `MulticastEvent` free property associated to the `CtrlSystem` object.

In the above screenshot 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

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

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 transfer this event. Because PGM is a reliable protocol, data has to be buffered for re-transmission in case a receiver signals 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. When 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 a multicasting configuration

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

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

- MulticastRate associated to the CtrlSystem object. This defines the maximum rate that will be used by the multicast protocol when transferring event. The unit is Mbit/sec. In case this property is not defined, the Tango library used a value of 80 Mbit/sec.

- MulticastIvl associated to the CtrlSystem object. It specifies the maximum time (in sec) during which data has to be buffered for re-transmision in case a receiver signals some lost data. The unit is seconds. In case this property is not defined, the Tango library takes a value of 20 seconds.

- MulticastHops associated to the CtrlSystem object. This property defines the maximum number of elements (routers), the multicast packet is able to cross. Each time one element is crossed, the value is decremented. When it reaches 0, the packet is not transferred any more. In case this property is not defined, the Tango library uses a value of 5.

audience:administrators

#### Use multiple database servers within a Tango control system

The database device server, TangoDatabase in most cases, serves as the Tango Host in a control system. The host name and port number of the database server is known via the TANGO_HOST environment variable. If you want to use several tango hosts in order to handle one being unreachable (either due to a network outage, a crash or a hardware issue), 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 switch automatically and transparently to a running server in the given list if the one used is not reachable.

#### Use the property file

audience:administrators audience:developers

A property file is a file where you store all the properties related to devices belonging to a specific device server process. In this file, one can find:

- Which devices have to be created for each Tango class embedded in the device server process

- Device properties

- Device attribute properties

This type of file is not required by a Tango control system as this information is stored in the Tango Database as well.

But if you want to run the File Database this is the format that will be used. These files can either be written from scratch using the example below or exported from Jive. To generate a device server process properties file, select your device server process in the `Server` tab, right click and select `Save Server Data`. A file selection window pops up allowing you to choose your file name and path. To load a file into the Tango database, click on `File` then `Load Property File`.

##### Property file example

     1 #---------------------------------------------------------
     2 # SERVER TimeoutTest/manu, TimeoutTest device declaration
     3 #---------------------------------------------------------
     4
     5 TimeoutTest/manu/DEVICE/TimeoutTest: "et/to/01",\
     6                                      "et/to/02",\
     7                                      "et/to/03"
     8
     9
    10 # --- et/to/01 properties
    11 et/to/01->StringProp: Property
    12 et/to/01->ArrayProp: 1,\
    13                      2,\
    14                      3
    15 et/to/01->attr_min_poll_period: TheAttr,\
    16                                 1000
    17 et/to/01->AnotherStringProp: "A long string"
    18 et/to/01->ArrayStringProp: "the first prop",\
    19                            "the second prop"
    20
    21 # --- et/to/01 attribute properties
    22 et/to/01/TheAttr->display_unit: 1.0
    23 et/to/01/TheAttr->event_period: 1000
    24 et/to/01/TheAttr->format: %4d
    25 et/to/01/TheAttr->min_alarm: -2.0
    26 et/to/01/TheAttr->min_value: -5.0
    27 et/to/01/TheAttr->standard_unit: 1.0
    28 et/to/01/TheAttr->__value: 111
    29 et/to/01/BooAttr->event_period: 1000
    30 et/to/01/TestAttr->display_unit: 1.0
    31 et/to/01/TestAttr->event_period: 1000
    32 et/to/01/TestAttr->format: %4d
    33 et/to/01/TestAttr->standard_unit: 1.0
    34 et/to/01/DbAttr->abs_change: 1.1
    35 et/to/01/DbAttr->event_period: 1000
    36
    37 # --- class property
    38 CLASS/TimeoutTest->InheritedFrom: Device_6Impl
    39 CLASS/TimeoutTest->doc_url: "https://www.myfancywebsite.eu"
    40
    41 # --- class attribute property
    42 CLASS/MotorClass/position->unit: "nm"
    43
    44 # --- free CtrlSystem property
    45 FREE/CtrlSystem->AutoAlarmOnChangeEvent: false

Example explanations
Line | Explanation
--- | ---
1-3 | Comment lines start with the `#` character and extend until the end of the line
4 | Blank lines are skipped
5-7 | Device definition. `DEVICE` is the keyword to declare a devices definition sequence. The syntax is
      <DS name>/<inst name>/DEVICE/<Class name>: dev1,dev2,dev3
A device’s name can also follow on the next line if the line continuation character `\` is used as the last character, see lines 5 and 6. The `"` (quote) characters around the device names are generated by Jive and are not mandatory.
11 | Device property definition. The syntax is
        <device name>-><property name>: <property value>
In the case of array properties, the array element delimiter is the character `,`. Array definition can be split over several lines if the last line character is `\`. Allowed characters after the `:` delimiter are space, tab or nothing.
12-15 & 15-16 | Device property (array)
18 | When a device string property contains special characters (spaces), the `"` character is used to delimit the strings.
22-35 | Device attribute property definition. The syntax is
      <device name>/<attribute name>-><property name>: <property value>
Allowed characters after the `:` delimiter are space, tab or nothing.
38-39 | Class property definition. The syntax is
      CLASS/<class name>-><property name>: <property value>
Allowed characters after the `:` delimiter are space, tab or nothing. On line 39, the `"` (quote) characters around the property value are mandatory due to the `/` character being contained in the property value.
42 | Class attribute property definition. The syntax is
      CLASS/<class name>/<attribute name>-><property name>: <property value>
This differs from a class property only by the inserted `/<attribute name>`. Allowed characters after the `:` delimiter are space, tab or nothing.
45 | Free properties. The syntax is
      FREE/<object name>-><property name>: <property value>
Although `object name` can be freely choosen, it is convention to use `CtrlSystem` for free properties used in across multiple tango applications. Allowed characters after the `:` delimiter are space, tab or nothing.

#### Use the Starter device

audience:all

##### Introduction

The Starter device can be used to control other device servers running on the same host.

Possible use cases are:

- start all device servers on system startup,

- get a list of all started device servers,

- start or stop a device server,

- get logs from a device server.

Astor is a graphical client for Starter devices.

##### Installation

There are several ways to install the Starter device server:

- as a part of Tango source distribution,

- from Starter source,

- using a package manager (e.g. tango-starter for debian),

##### Configuration

General recommendations for configuring the Starter:

- there should be only one instance of a Starter device server running on a host,

- the instance name can be arbitrary,

- the Starter device server provides a Starter class,

- the member part of a Starter device name must match the short name of the host.

- the domain/family part of a Starter device name should be “tango/admin” in order for the Starter device to be detected by Astor.

Below is an example of how to define a Starter device using the tango_admin tool:

    host=$(hostname -s)
    tango_admin --add-server Starter/$host Starter tango/admin/$host
    Starter $host

Note

The requirement for the member part of the name to match the hostname can be disabled by setting the environment variable `DEBUG` to `true`. The starter will be visible in Astor under the name specified in member.

##### Usage

The Astor GUI can be used to start a new Tango device server using the Starter device.

Alternatively, the Starter interface can be used directly, e.g. to start a device server:

    import tango
    starter = tango.DeviceProxy("tango/admin/tangobox")
    starter.command_inout("DevStart", "TangoTest/test")

The Starter device will use the paths from the `StartDsPath` property when looking for an executable of a device server to start. The paths are searched in the order in which they appear in the property. If the property is empty, Starter device server’s working directory is used when searching for executables.

##### Autostarting Starter

The Starter device server can be automatically started during system startup using a service manager of choice. It can then start any other device server.

The following are example configuration files for different service managers:

- systemd

  follow instructions for systemd integration .

- NSSM (Windows)

  follow NSSM configuration instructions in the installation guide for Windows .

##### Further references

- Starter documentation,

- Starter in the Device Servers Catalogue,

- Starter source code repository.

#### Run a device server with the File Database

audience:administrators audience:developers

For device servers not able to access the Tango Database (most of the time due to network route or security reasons) or just because it is more convenient for testing, it is possible to start device servers using a file instead of a real Tango database.

The File Database has the following limitations:

- There’s no check that the same device server is running twice

- There’s no device or attribute alias name support

- If several device servers are running on the same host, the user must manually manage a list of network ports already in use

To use the File Database, pass the following command line option to the device server:

`-file=<file name>`

In this case the getting, setting and deletion of all properties are handled using the specified file instead of the Tango database. The file is a plaintext file and follows a well-defined syntax with predefined keywords and is called a property file. The simplest way to generate the file for a specific device server is to use Jive.

The Tango database is not only used to store device configuration parameters, it is also used to store device network access parameters, for example the Interoperable Tango Reference. Therefore to allow an application to connect to a device hosted by a device server using a file instead of the standard database, you need to start it on a pre-defined port using one of the underlying ORB options called endPoint. For example, to start your device server:

`myserver myinstance_name -file=path/serverFile -ORBendPoint giop:tcp::<port>`

Assuming the device `id12/rb/1` runs on host `fish` with port `1234`, clients can then connect to it via the Tango Resource Locator (TRL):

    tango://fish:1234/id12/rb/1#dbase=no

#### Starting a Tango control system

audience:all

##### With a database

Starting the Tango control system simply means starting its database device server on a well defined host using a well defined port. Use the host name and the port number to build the TANGO_HOST environment variable. See the Running a C++ device server section on how to do this. Note that the underlying database server must be started before the Tango database device server. The Tango database server connects to database server using a default login name set to `root`. You can change this behaviour with the `MYSQL_USER` and `MYSQL_PASSWORD` environment variables. Define them before starting the database server. All tango environment variables can also be set in the tangorc configuration file.

An example tango rc file `/etc/tangorc`:

    MYSQL_USER=dbuser
    MYSQL_PASSWORD=secret
    TANGO_HOST=my-hostname.eu:10000

If you are using the Tango administration graphical tool called Astor, you also need to start a specific Tango device server called Starter on each host where Tango device server(s) are running. This Starter device server is able to start even before the Tango database device server is started. In this case, it will enter a loop in which it periodically tries to access the Tango database device. The loop exits and the server starts only if the database device access succeeds.

##### Without a database

When used without a database, there is no additional process to start. Simply start a device server using the `-nodb` option (and eventually the `-dlist` option) on a specific port using `-ORBendPoint`. See Run a device server without a database to find information on how to write and start a Tango device server without using the database.

##### With a file used as a database

When used with a File Database, there is no additional process to start. Simply start a device server using the `-file` option specifying the file and `-ORBendPoint` for the port. See Run a device server with the File Database to find information on how to start Tango device server using a database on file.

##### With TAC (Tango Access Control)

Warning

This is client side only and not secure. Use that only to prevent accidental changes to tango device servers and not for security.

Using the Tango controlled access means starting a specific device server called `TangoAccessControl`. By default, this server has to be started with the instance name set to 1 and its device name is `sys/access_control/1`. The command to start this device server is:

      TangoAccessControl 1

This server connects to MariaDB using a default login name set to `root`. As mentioned above, you can change this behaviour with the `MYSQL_USER` and `MYSQL_PASSWORD` environment variables. Define them before starting the controlled access device server. This server also uses the `MYSQL_HOST` environment variable if you need to connect it to some MySQL server running on another host. The syntax of this environment varaible is `host:port`. The port is optional and if it is not defined, the MariaDB default port is used (3306). If it is not defined at all, a connection to the localhost is made. This controlled access system uses the Tango database to retrieve user rights and it is not possible to run it in a Tango control system running without a database.

#### Run a device server without a database

audience:administrators audience:developers

In some cases, for example running a device server within a lab during hardware development, testing, etc, it can be useful to have a device server able to run even if there is no Tango database or File database available in the control system. Note that running a Tango device server without a database means losing some Tango features:

- There are no checks that the same device server is running twice

- There is no device configuration via properties

- There are no memorized attributes

- There is no device attribute configuration via the database

- There are no checks that the same device name is used twice within the same control system

- If several device servers are running on the same host, the user must manually manage a list of network ports alreaady in use

To run a device server without a database, the `-nodb` command line option for the device server must be used. One problem when running a device server without the database is passing device names 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 the case of a device server with several device pattern implementations, the device name list given at the command line is only used 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 reimplemntation of the method `device_name_factory()` (cppTango and PyTango).

Device definition at the command line has higher priority than overriding `device_name_factory()`.

If nothing is passed or set, the device name NoName is used for each device pattern implementation.

##### Example of a device server started without database usage

Without a database, you need to start a Tango device server on a pre-defined port, and you must use one of the underlying ORB options called endPoint, i.e.:

`server inst -ORBendPoint giop:tcp::<port number> -nodb -dlist a/b/c`

This will start the device server executable `server` as the instance `inst` on all available network interfaces giving it the device name `a/b/c`.

Below are two examples of starting a device server without a database - note that in this case the `device_name_factory()` method has not been 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

Below is an example where the `device_name_factory()` method has been 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

##### Connecting clients to a device within a device server started without a database

In this case, the host and port on which the device server is running are part of the device name. For example, if the device name is a/b/c, the host is mycomputer and the port is 1234, then the device name to be used by a client is

`mycomputer:1234/a/b/c#dbase=no`

Some clients, like Atkpanel, require the tango:// prefix:

`tango://mycomputer:1234/a/b/c#dbase=no`

See device naming for further details on Tango object naming.

audience:administrators

#### Use the Tango controlled access system

##### User rights definition

Within the Tango control system, you give rights to a user. The ‘user’ is the name used to log in to the computer where the application trying to access a device is running. There are two kinds of users within the Tango control system:

1. Users with defined rights

2. Users without any rights defined in the control system. These users will have the rights associated with the pseudo-user called All Users

The control system manages two kinds of rights:

- Write access: all types of requests are allowed on the device

- Read access: only read access is allowed meaning that 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 same allowed commands set.

The rights given to a user are determined from checks at 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 only. 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 wildcards in the IP address (eg. 160.103.11.* meaning any host with an 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 the device name. Device families can be specified using wildcards in the device name like domain/family/*

The control system is doing the following checks when a client try to access a device:

- Get the user’s name

- Get the host IP address

- If rights are defined at the host level for this specific user and this IP address then give the user temporary write access to the control system

- If nothing is specified for this specific user on this host, give the user temporary access rights 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 then check what rights are defined at the device level:

  - If there is a right defined for the device to be accessed (or for the device family), give user that defined right

  - Otherwise if no right is defined at the device level for this user then check:

    - If rights are defined for the All Users user for this device, give this right to the user

    - Otherwise, give the user read Access only for this device

- Otherwise the default 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 control access will have only read access to all devices on the control system from any hosts. Then, on request, gives write access to specific user on specific host (or family) and on specific device (or family).

The access rights are managed using the Tango Astor tool which provides a graphical interface to grant/revoke user rights and to define device class allowed commands set. The following screenshot shows an example of the 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. They have 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 have read access only from any host.

##### 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 accesses 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 controlled access. This property is an array of strings with each string describing the services 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 MariaDB database and therefore may need the MariaDB connection related environment variables `MYSQL_USER` and `MYSQL_PASSWORD` - see database environment variables.

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.

#### Import multiple device classes to the Catalogue

audience:administrators

The Device Classes Catalogue is available on the Tango Controls web page at: https://www.tango-controls.org/developers/dsc/.

A script can be used to import multiple device classes to the catalogue from a repository. This is available to download here and below or instructions on how to use this script.

This can import information from a subversion (SVN), Git, Mercurial or FTP repositiory.

##### Installation

This script requires:

- Python > 3.6

- Subversion (if importing from an SVN repository)

A list of the required Python modules is given in the requirements.txt file.

The easiest way to install these is in a Python virtual environment:

    # Create virtual environemnt
    python -m venv venv

    # Activate
    source venv/bin/activate

    # Install from requirements.txt
    pip install -r requirements.txt

Warning

Note if importing from SVN:

The library will not work if any of files in the SVN has no author defined, which is the case for tango-ds repository on Sourceforge. To avoid this problem one can edit the Python svn module file `svn/common.py` around line 369 to have something like the following:

    author = ''
    if commit_node.find('author') is not None:
       author = commit_node.find('author').text

##### How-to use the script to import multiple classes

1. Clone the import utility with git:

       git clone https://gitlab.com/tango-controls/dsc.git`
       cd dsc-import

2. Update so general variables in the settings.py file to reflect your environment and how you want to import classes. Some of the important ones to consider might be:

       FORCE_UPDATE = False  # when True no timestamps are checked and updates are performed
       USE_DOC_FOR_NON_XMI = True # when True, parse documentation to get xmi conntent for device servers without XMI
       ADD_LINK_TO_DOCUMENTATION = True # when True it provides a link to documentation

       # set the following variables to point to the repositories
       LOCAL_REPO_PATH = '/home/tango/tmp/tango-ds-repo/'  # local copy of the repository will be synced there
       LOG_PATH = '/home/tango/tmp/logs'  # where to log some information about import process

       # Tango Controls or test server address
       SERVER_BASE_URL = 'http://www.tango-controls.org/'

3. Configure to run:

   SVN

   In the settings.py file, configure the `REMOTE_REPO_HOST` and `REMOTE_REPO_PATH`:

       REMOTE_REPO_HOST = 'svn.code.sf.net'  # host of the SVN repository (if using SVN repo)
       REMOTE_REPO_PATH = 'p/tango-ds/code'  # path within the server where the repository is located

   Other

   To import from another type of repository, for example Git, first define a .csv with the following content:

       name,repository_url,repository_type,tag,xmi_files_urls,pogo_docs_url_base,upload_xmi_file,readme_url,documentation_url,documentation_type,documentation_title,pogo_description_html,pogo_attributes_html,pogo_commands_html,pogo_properties_html

   The ‘example-csv.csv’ in the dsc-import repo provides an example on how to import from a Git repository:

       name,repository_url,repository_type,tag,xmi_files_urls,pogo_docs_url_base,upload_xmi_file,readme_url,documentation_url,documentation_type,documentation_title,pogo_description_html,pogo_attributes_html,pogo_commands_html,pogo_properties_html
       LiberaBrilliancePlus,https://github.com/MaxIV-KitsControls/Libera-BrilliancePlus,GIT,v1.2.1-alpha,https://raw.githubusercontent.com/MaxIV-KitsControls/Libera-BrilliancePlus/v1.2.1-alpha/src/LiberaBrilliancePlus.xmi,,,https://raw.githubusercontent.com/MaxIV-KitsControls/Libera-BrilliancePlus/v1.2.1-alpha/README.md,,,,,,,

   where:

   - name: LiberaBrilliancePlus

   - repository_url: https://github.com/MaxIV-KitsControls/Libera-BrilliancePlus

   - repository_type: GIT

   - tag: v1.2.1-alpha

   - xmi_files_urls: https://raw.githubusercontent.com/MaxIV-KitsControls/Libera-BrilliancePlus/v1.2.1-alpha/src/LiberaBrilliancePlus.xmi

     (Note: these need to be the ‘raw’ file from Git (View Raw))

   - readme_url: https://raw.githubusercontent.com/MaxIV-KitsControls/Libera-BrilliancePlus/v1.2.1-alpha/README.md

     (Note: Again needs to be the ‘raw’ file.)

4. Run the command:

   SVN

       python dsc_import_utility.py

   Other

   Add the `--csv-file` option to specify the file to use:

       python dsc_import_utility.py --csv-file <csv-file-name>.csv

   Note

   You will be asked for your credentials for tango-controls.org. The import/update of device classes will be carried out under the account details provided.

##### How the script works

During the import, the script will:

- Make a local copy of an SVN repository (in path defined by `LOCAL_REPO_PATH`). This speeds up the search process.

- Search this local copy for directories containing .XMI files. These are listed as candidates to be device servers.

- The list of candidates is processed and compared (by repository URL) with content in the Device Classes Catalogue:

  - If there are changes or `FORCE_UPDATE` is True the catalogue is updated

    - For device server without an .XMI file it looks for documentation servers and tries to parse html documentation generated by Pogo.

  - If there are no changes the device server is skipped

#### Use Tango with systemd integration

audience:administrators

It is useful to be able to automate the process of starting the necessary parts of the Tango Control System. For this purpose, it is recommended to create the system services.

In the case where Tango has been installed from a .deb package or is running in a Docker container, the package or the image provides thses services.

However, if Tango has been installed from scratch then the services and daemon have to be created manually.

There are a few server-side elements of the Tango environment that will want to be started in this way.

##### Tango DB Service

Create a service to start the MariaDB database. This can be done by creating a file named tango-db.service under the `/etc/systemd/system/` directory. An example of the contents of this file is shown below:

tango-db.service

    # /etc/systemd/system/tango-db.service
    [Unit]
    Description = Tango DB
    Requires=mariadb.service
    After=mariadb.service

    [Service]
    User=<user>
    Environment=TANGO_HOST=<tango_host:port>
    Environment=MYSQL_USER=<mysql_user>
    Environment=MYSQL_PASSWORD=<mysql_pwd>
    ExecStart=<path/to/DataBaseds> 2 -ORBendPoint giop:tcp::10000
    # Give a reasonable amount of time for the server to start up/shut down
    TimeoutSec=10

    [Install]
    WantedBy=tango.target

The above example starts MariaDB. If you are using a MySQL database then you will need to modify the above example with:

    Requires=mysqld.service
    After=mysqld.service

Replace:

- `<user>`: your username (e.g. tango)

- `<tango_host:port>`: TANGO_HOST varible as IP:PORT (e.g. localhost:10000)

- `<mysql_user>`: configured when setting up MariaDB/MySQL (e.g. root)

- `<mysql_pwd>`: configured when setting up MariaDB/MySQL (e.g. tango)

- `<path/to/DataBaseds>`: e.g. /usr/local/tango/bin/DataBaseds

To have this service start automatically at machine boot time, run the `enable` command and then `start` the service:

    sudo systemctl enable tango-db
    sudo systemctl start tango-db

Note

Note that if you later make changes to the .service file you will need to reload with:

    sudo systemctl daemon-reload

##### Tango Access Control Service

Create a service to start the Tango access control. This can be done by creating a file named tango-accesscontrol.service under the `/etc/systemd/system/` directory. An example of the contents of this file is shown below:

tango-accesscontrol.service

    # /etc/systemd/system/tango-accesscontrol.service
    [Unit]
    Description=TangoAccessControl device server
    Wants=tango-db.service
    After=tango-db.service

    [Service]
    Environment=TANGO_HOST=<tango_host:port>
    Environment=MYSQL_USER=<mysql_user>
    Environment=MYSQL_PASSWORD=<mysql_pwd>
    ExecStart=<path/to/TangoAccessControl> 1

    [Install]
    WantedBy=tango.target

Replace:

- `<tango_host:port>`: TANGO_HOST varible as IP:PORT (e.g. localhost:10000)

- `<mysql_user>`: configured when setting up MariaDB/MySQL (e.g. root)

- `<mysql_pwd>`: configured when setting up MariaDB/MySQL (e.g. tango)

- `<path/to/TangoAccessControl>`: e.g. /usr/local/tango/bin/TangoAccessControl

We also need to create a timer to make sure that the TangoDB is ready to accept requests before we start Tango access control. Create a file named tango-accesscontrol.timer under the `/etc/systemd/system/` directory. An example of the contents of this file is shown below:

tango-accesscontrol.timer

    # /etc/systemd/system/tango-accesscontrol.timer
    [Timer]
    OnActiveSec=3

    [Install]
    WantedBy=tango.target

To have this service and timer start automatically at machine boot time, run the `enable` command and then `start` the service:

    sudo systemctl enable tango-accesscontrol
    sudo systemctl enable tango-accesscontrol.timer

    sudo systemctl start tango-accesscontrol

Note

Note that if you later make changes to these files you will need to reload with:

    sudo systemctl daemon-reload

##### Tango Starter Service

Create a service to start the Tango Starter. This can be done by creating a file named tango-starter.service under the `/etc/systemd/system/` directory. An example of the contents of this file is shown below:

tango-starter.service

    # /etc/systemd/system/tango-starter.service
    [Unit]
    Description=Starter device server
    After=tango-accesscontrol.service
    Requires=tango-db.service
    Requires=tango-accesscontrol.service

    [Service]
    Restart=always
    RestartSec=10
    User=<user>
    Environment=TANGO_HOST=<tango_host:port>
    ExecStart=<path/to/Starter> <device>

    [Install]
    WantedBy=tango.target

Replace:

- `<user>`: your username (e.g. tango)

- `<tango_host:port>`: TANGO_HOST varible as IP:PORT (e.g. localhost:10000)

- `<path/to/Starter>`: e.g. /usr/local/tango/bin/Starter

- `<device>`: name of the Starter device to start. See Use the Starter device on how to create this device.

We also need to create a timer to make sure that the TangoDB is ready to accept requests before we start the Tango Starter device. Create a file named tango-starter.timer under the `/etc/systemd/system/` directory. An example of the contents of this file is shown below:

tango-starter.timer

    # /etc/systemd/system/tango-starter.timer
    [Timer]
    OnActiveSec=3

    [Install]
    WantedBy=tango.target

To have this service and timer start automatically at machine boot time, run the `enable` command and then `start` the service:

    sudo systemctl enable tango-starter
    sudo systemctl enable tango-starter.timer

    sudo systemctl start tango-starter

Note

Note that if you later make changes to these files you will need to reload with:

    sudo systemctl daemon-reload

##### Combined single target

Finally combine everything into a single target by creating a file named tango.target under the `/etc/systemd/system/` directory. An example of the contents of this file is shown below:

tango.target

    # /etc/systemd/system/tango.target
    [Unit]
    Description=Tango development environment target
    Requires=tango-db.service
    Requires=tango-starter.service
    Requires=tango-accesscontrol.service
    Requires=tango-accesscontrol.timer
    Requires=tango-starter.timer

    [Install]
    WantedBy=multi-user.target

Start this target with the `start` command:

    sudo systemctl start tango.target

##### Defining Tango servers as systemd units

It may be useful to create systemd units for specific Tango servers in production. Similar to the above examples create a file named device-server1.service under the `/etc/systemd/system/` directory. An example of the contents of this file is shown below:

tango-server1.service

    # /etc/systemd/system/tango-server1.service
    [Unit]
    Description=My Device Server
    After=tango-accesscontrol.service
    Requires=tango-db.service
    Requires=tango-accesscontrol.service

    [Service]
    Restart=always
    RestartSec=10
    User=<user>
    Environment=TANGO_HOST=<tango_host:port>
    ExecStart=<path/to/device-class> <device>

    [Install]
    WantedBy=tango.target

Replace:

- `<user>`: your username (e.g. tango)

- `<tango_host:port>`: TANGO_HOST varible as IP:PORT (e.g. localhost:10000)

- `<path/to/device-class>`: e.g. for TangoTest this might be /usr/local/tango/bin/TangoTest

- `<device>`: name of the device to start

#### Run a device server with an active Windows firewall

audience:administrators audience:developers

When running Tango device servers on a Windows PC with an active firewall you might have some problems trying to reconnect to a restarted server. In some cases the client will never reconnect.

This behavior is due to the device server port being automatically closed when the process stops running. In this case the client only receives a timeout exception on the blocked port instead of the expected “connection failed” exception which would trigger a reconnection.

When a device server is restarted it will dynamically open another port and new clients are able to connect to the new port, however old clients might not.

To overcome this problem, start your device server with a fixed port (port 11000 in this example):

    TangoTest win -ORBendPoint giop:tcp11000

and open the fixed port in the firewall with:

    Netsh firewall add portopening TCP 11000 TangoTest

You can verify the open ports for the firewall with:

    Netsh firewall show portopening

which should yield the the following output:

    Port configuration for Domaine profile:

    Port     Protocol    Mode       Name

    -----------------------------------------------------------------------------

    11000    TCP         Enable     TangoTest

### Development

audience:developers lang:all

Here you will find recipies on how to develop with Tango Controls.

Some common task are given below:

#### Create your first Device class

audience:developers lang:all

This How-to explains how to create your first Device class regardless of the language used.

Prerequisite: This how-to assumes that you have a Tango installed.

- Start Pogo code generator: Now you can create a new class:

  - Click on and then New.

- Add the following required information for your class:

  - The Device Class identification information (shown in the left panel below)

  - The class name, language and description (shown in the right panel below)

- Once completed, you will see an empty Pogo interface:

  You can add Properties, Commands and Attributes by double-clicking on each one. Below is an example after having defined some of these:

- Generate your files from File -> Generate and press OK in the window.

  - Choose your output path and the files you want to create. For example, on linux OS in C++, the minimum set of files that need to be created are: an XMI file, Code files and a Makefile. See the example below:

  - You will now see the Pogo generated files in your folder:

    Pogo has creates skeleton files with your Properties, Commands and Attributes.

- Next you will need to develop your device. Information on how to do this can be found in the language specific how-to sections: C++, Java or Python

- Finally, to compile and run your class see the section on how to start a device server.

#### Write your first Tango client

audience:developers lang:all

Below is an example of how to write a client to connect to a Tango device and interact with it:

C++

     1  /*
     2   * Example of a client using the Tango C++ api.
     3   */
     4  #include <tango.h>
     5
     6  using namespace Tango;
     7
     8  int main(unsigned int argc, char **argv)
     9  {
    10      try
    11      {
    12        // Create a connection to a Tango device
    13        DeviceProxy *device = new DeviceProxy("sys/tg_test/1");
    14
    15        // Ping the device
    16        device->ping();
    17
    18        // Execute a command on the device and extract the reply as a string
    19        string db_info;
    20        DeviceData cmd_reply;
    21        cmd_reply = device->command_inout("DbInfo");
    22        cmd_reply >> db_info;
    23        cout << "Command reply " << db_info << endl;
    24
    25        // Read a device attribute (string data type)
    26        string spr;
    27        DeviceAttribute att_reply;
    28        att_reply = device->read_attribute("StoredProcedureRelease");
    29        att_reply >> spr;
    30        cout << "Database device stored procedure release: " << spr << endl;
    31      }
    32      catch (DevFailed &e)
    33      {
    34        Except::print_exception(e);
    35        exit(-1);
    36      }
    37  }

Python

     1'''
     2Example of a client using the Tango Python api (PyTango).
     3'''
     4import tango
     5
     6try:
     7    # Create a connection to a Tango device
     8    tango_test = tango.DeviceProxy("sys/tg_test/1")
     9
    10    # Ping the device
    11    print(f"Ping: {tango_test.ping()}")
    12
    13    # Execute a command on the device and extract the reply as a string
    14    result = tango_test.command_inout("DbInfo", "First hello to device")
    15    print(f"Result of execution of DbInfo command = {result}")
    16
    17    # Read a device attribute (string data type)
    18    procedure_release = tango_test.read_attribute("StoredProcedureRelease")
    19    print(f"Database device stored procedure release = {procedure_release.value}")
    20
    21except DevFailed as df:
    22    print(f"Failure: {df}")

#### Generate events in a device server

audience:developers lang:c++ python

The device server is the origin of all events. It will fire events as soon as they occur or are created. For a detailed explanation of the different types of events in Tango please see the events section.

Standard events (`change`, `periodic`, `alarm`, `data ready` and `archive`) are detected automatically in the polling thread and fired immediately (if enabled). The `periodic` events can only be fired by the polling thread. The `alarm`, `change`, `data ready` and `archive` events can also be manually pushed from the device server. To allow a client to subscribe to events of attributes that are not polled, the server has to declare that events are pushed from the code. Four methods are available for this purpose:

C++

    1  Attr::set_change_event(bool implemented, bool detect = true);
    2  Attr::set_archive_event(bool implemented, bool detect = true);
    3  Attr::set_alarm_event(bool implemented, bool detect = true);
    4  Attr::set_data_ready_event(bool implemented);

For example:

    1  // Create a new read-only, short attribute
    2  auto *at = new Tango::Attr("some_attribute", Tango::DEV_SHORT, Tango::READ);
    3
    4  // Indicate that the following events will be push manually
    5  // for this attribute
    6  at->set_change_event(true)
    7  at->set_archive_event(true)
    8  at->set_alarm_event(true)
    9  at->set_data_ready_event(true)

Python

    1  Attribute.set_change_event(bool implemented, bool detect = True)
    2  Attribute.set_archive_event(bool implemented, bool detect = True)
    3  Attribute.set_alarm_event(bool implemented, bool detect = True)
    4  Attribute.set_data_ready_event(bool implemented)

For example:

     1  # Create a new read-only, short attribute
     2  at = attribute(
     3    dtype=int, access=AttrWriteType.READ
     4  )
     5
     6  # Indicate that the following events will be push manually
     7  # for this attribute
     8  at.set_change_event(True)
     9  at.set_archive_event(True)
    10  at.set_alarm_event(True)
    11  at.set_data_ready_event(True)

where `implemented=true` indicates that events are pushed manually from the code and `detect=true` triggers verification of the event using the same event properties defined for the polling thread. Note that when `detect=false`, no value checking is done on the pushed value.

The class `DeviceImpl` also supports the first two methods with an additional parameter `attr_name` defining the attribute name:

C++

    1  DeviceImpl::set_change_event(std::string attr_name, bool implemented, bool detect = true);
    2  DeviceImpl::set_archive_event(std::string attr_name, bool implemented, bool detect = true);
    3  DeviceImpl::set_alarm_event(std::string attr_name, bool implemented, bool detect = true);
    4  DeviceImpl::set_data_ready_event(std::string attr_name, bool implemented);

For example:

     1  // Constructor
     2  TestDevice::TestDevice(Tango::DeviceClass *cl, std::string &s)
     3    : TANGO_BASE_CLASS(cl, s.c_str())
     4  {
     5    init_device()
     6  }
     7
     8  // ...
     9
    10  void TestDevice::init_device()
    11  {
    12    // Indicate that the following events will be push manually for the attribute
    13    // named "some_attribute"
    14    set_change_event("some_attribute", true);
    15    set_archive_event("some_attribute", true);
    16    set_alarm_event("some_attribute", true);
    17    set_data_ready_event("some_attribute", true);
    18  }

Python

    1  DeviceImpl.set_change_event(str attr_name, bool implemented, bool detect = True)
    2  DeviceImpl.set_archive_event(str attr_name, bool implemented, bool detect = True)
    3  DeviceImpl.set_alarm_event(str attr_name, bool implemented, bool detect = True)
    4  DeviceImpl.set_data_ready_event(str attr_name, bool implemented)

For example:

     1  class TestDevice(Device):
     2
     3    def init_device(self):
     4
     5      super().init_device()
     6
     7      # Indicate that the following events will be pushed manually for the attribute
     8      # named "some_attribute"
     9      self.set_change_event("some_attribute", True)
    10      self.set_archive_event("some_attribute", True)
    11      self.set_alarm_event("some_attribute", True)
    12      self.set_data_ready_event("some_attribute", True)

To push events manually from the code a set of data type dependent methods can be used:

C++

    1  DeviceImpl::push_change_event(std::string attr_name, ...);
    2  DeviceImpl::push_archive_event(std::string attr_name, ...);
    3  DeviceImpl::push_alarm_event(std::string attr_name, ...);
    4  // ctr = Optional "counter"
    5  DeviceImpl::push_data_ready_event(std::string attr_name, Tango::DevLong ctr = 0);

where the `ctr` is an optional counter, which will be passed within the event.

For example:

     1  void sendEventTest
     2  {
     3    Tango::DevDouble v{10};
     4    // Push an alarm event for the attribute "some_attribute"
     5    push_alarm_event("some_attribute", &v);
     6    // Push a change event for the attribute "some_attribute"
     7    push_change_event("some_attribute", &v);
     8    // Push an archive event for the attribute "some_attribute"
     9    push_archive_event("some_attribute", &v);
    10    // Push a 'data ready' event for the attribute "some_attribute"
    11    push_data_ready_event("some_attribute");
    12  }

Python

    1  DeviceImpl.push_change_event(str attr_name, ...)
    2  DeviceImpl.push_archive_event(str attr_name, ...)
    3  DeviceImpl.push_alarm_event(str attr_name, ...)
    4  DeviceImpl.push_data_ready_event(str attr_name, int ctr = 0)

where the `ctr` is an optional counter, which will be passed within the event.

For example:

    1def sendEventTest(self):
    2  # Push an alarm event for the attribute "some_attribute"
    3  self.push_alarm_event("some_attribute", 10)
    4  # Push a change event for the attribute "some_attribute"
    5  self.push_change_event("some_attribute", 10)
    6  # Push an archive event for the attribute "some_attribute"
    7  self.push_archive_event("some_attribute", 10)
    8  # Push a 'data ready' event for the attribute "some_attribute"
    9  self.push_data_ready_event("some_attribute", 10)

See the appropriate API for all available interfaces.

- C++: C++ API documentation

- Python: PyTango API documentation

Warning

CORBA events using notfid are deprecated and about to be removed, see this cppTango issue.

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, the following api call is available to all device servers:

C++

    1  DeviceImpl::push_event(std::string attr_name,
    2               std::vector<std::string> &filterable_names,
    3               std::vector<double> &filterable_vals,
    4               ...);

Python

    1  DeviceImpl.push_event(str attr_name,
    2               Sequence[str] filterable_names,
    3               Sequence[double] filterable_vals,
    4               ...)

where `attr_name` is the name of the attribute and `filterable_names` and `filterable_vals` represent any 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. This attribute is readable and 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.

C++

     1  void MyClass::read_Sinusoide(Tango::Attribute &attr)
     2  {
     3    // ...
     4       struct timeval tv{};
     5       gettimeofday(&tv, nullptr);
     6       sinusoide = 100 * sin( 2 * 3.14 * frequency * tv.tv_sec);
     7
     8       if(sinusoide >= 0)
     9       {
    10          std::vector<std::string> filterable_names;
    11          std::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(),filterable_names, filterable_value);
    17       }
    18    // ...
    19 }

line 13-14 : The filter pair name/value is initialised

line 16 : The event is pushed

Python

     1  def read_Sinusoide(self, attr):
     2    # ...
     3
     4    time_of_day = datetime.datetime.now.timestamp()
     5    sinusoide = 100 * sin( 2 * 3.14 * frequency * time_of_day)
     6
     7    if sinusoide >= 0:
     8      filterable_names = ["value"]
     9      filterable_value = [sinusoide]
    10
    11      push_event(attr.get_name(), filterable_names, filterable_value)
    12
    13    # ...

line 8-9 : The filter pair name/value is initialised

line 11: The event is pushed

#### Tune polling from inside device classes

audience:developers lang:all

It is possible to configure command or attribute polling from within a device class, i.e. an instance of `Tango::DeviceImpl`. The available functionality is similiar to the one found in `Tango::DeviceProxy`.

With them, you can:

- Check if a command or attribute is polled

- Start/stop polling for a command or an attribute

- Get or update the polling period for a polled attribute or command

C++

To display some information related to polling of the attribute named `TheAtt`:

     1std::string att_name{"TheAtt"};
     2TANGO_LOG_DEBUG << "Attribute" << att_name;
     3
     4if(is_attribute_polled(att_name))
     5{
     6   TANGO_LOG_DEBUG << " is polled with period " << get_attribute_poll_period(att_name) << " ms" << std::endl;
     7}
     8else
     9{
    10   TANGO_LOG_DEBUG << " is not polled" << std::endl;
    11}

To poll a command:

    1poll_command("TheCmd", 250);

If the command is already polled, this method will update its polling period to 250 ms.

Finally, to stop polling the same command:

    1stop_poll_command("TheCmd");

All these DeviceImpl polling related methods are documented in the DeviceImpl class documentation.

Python

To display some information related to polling of the attribute named `TheAtt` in a `DeviceImpl` class:

    1att_name = "TheAtt"
    2
    3if self.is_attribute_polled(att_name):
    4    print(f"{} is polled with period {} ms", att_name, self.get_attribute_poll_period(att_name))
    5else:
    6    print(f"{} is not polled", att_name)

To poll a command:

    1self.poll_command("TheCmd", 250)

If the command is already polled, this method will update its polling period to 250 ms.

Finally, to stop polling:

    1self.stop_poll_command("TheCmd")

All these DeviceImpl polling related methods are documented in DeviceImpl.

Java

The polling can be retrieved and modified from the DeviceManager class.

     1import org.tango.server.annotation.Device;
     2import org.tango.server.annotation.DeviceManagement;
     3import org.tango.server.device.DeviceManager;
     4import fr.esrf.Tango.DevFailed;
     5
     6@Device
     7public class Test {
     8    @DeviceManagement
     9    private DeviceManager deviceManager;
    10     ...
    11        final String attName = "TheAttr";
    12        if (deviceManager.isPolled(attName)) {
    13            System.out.println(attName + " is polled with period " + deviceManager.getPollingPeriod(attName) + " mS");
    14        } else {
    15            System.out.println(attName + " is not polled");
    16        }
    17        deviceManager.startPolling("TheCmd", 250);
    18        deviceManager.stopPolling("TheCmd")
    19        ...
    20
    21   public void setDeviceManager(final DeviceManager deviceManager) {
    22        this.deviceManager = deviceManager;
    23    }
    24}

#### Telemetry with Tango

cppTango and PyTango support telemetry via the OpenTelemetry framework.

Please see their respective documentation for more instructions:

- cppTango

- PyTango

#### Transfer images

audience:developers lang:all

There exists a set of methods that have been written to optimise image transfer between clients and servers using the attribute `DevEncoded` data type. These methods are contained in a class called `EncodedAttribute`. Within this class, you will find methods to:

- Encode an image in a compressed format (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 an 8 or 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 should be used in a server and in a client.

The server needs to creates an instance of the EncodedAttribute class within your object and can then use an encoding method from the EncodedAttribute class, e.g.

C++

     1 class MyDevice::TANGO_BASE_CLASS
     2  {
     3      ...
     4      Tango::EncodedAttribute jpeg;
     5      ...
     6  }
     7
     8  ...
     9
    10 void MyDevice::read_Encoded_attr_image(Tango::Attribute &att)
    11  {
    12      ....
    13      jpeg.encode_jpeg_gray8(imageData,256,256,50.0);
    14      att.set_value(&jpeg);
    15  }

Line 13: Perform the 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 the scale of 0 - 100. imageData is a pointer to the image data (unsigned chars).

Line 14: Set the value of the attribute using a `Attribute::set_value()` method.

Python

    1  def read_Encoded_attr_image(self, att, image_data):
    2      jpeg = tango.EncodedAttribute()
    3      jpeg.encode_jpeg_gray8(image_data,256,256,50.0)
    4      att.set_value(jpeg)

Line 4: Perform the 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 the scale of 0 - 100. image_data is the image data passed into the method.

Line 5: Set the value of the attribute using a `set_value()` method.

A snippet of the code then required on the client side is shown below (note: shown without any exception management):

C++

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

Python

    1    ...
    2    dev = tango.DeviceProxy("a/b/c")
    3    da = dev.read_attribute("Encoded_attr_image", extract_as=tango.ExtractAs.Nothing)
    4    enc = tango.EncodedAttribute()
    5    gray8 = enc.decode_gray8(da)
    6    ...

The attribute named Encoded_attr_image is read at line 3. Note that the argument `ExtractAs.Nothing` is required in this case as by default the call that returns a `DeviceAttribute` in PyTango automatically extracts the contents.

The image is decoded at line 5 in an 8 bits gray scale format. The image data are stored in the variable gray8.

For language specific how-tos please see the following subsections:

#### Get started with cppTango

audience:developers lang:c++

cppTango is the C++ implementation of Tango Controls. Below you will find some useful instructions on how to get start developing in C++.

Note that this chapter assumes that you have already installed Tango in your local computer or in your network. If you need to install Tango, please reference to the documentation.

##### Quick start: create and start a device

audience:developers

This quick tutorial will guide you to some fundamental TANGO concepts and how to do the first steps.

###### Step 1: Create a device class

Start by creating a new device class using the Pogo code generator named TangoQuickStart. See how to create a device class.

Note that in order to compile the class properly, you will also need to add the packaging. Into Pogo, go to File -> Export Package and check all the headers:

Pogo will then create a new folder with the packages.

Once you have created a skeleton and added the headers, you can add your own code into the class and compile it. Further information on how to implement a C++ device class is available here.

###### Step 2: Compile the device class

To compile the files, go to the folder that you choose to store your class and execute the following command:

    cd packaging
    ./autogen.sh
    ./configure --prefix=$HOME/packaging
    make
    make install

If everything works, you will see the files in the src directory. In this example we used Pogo to create a class named TangoQuickStart and so we will see the following files:

###### Step 3: Register the device

Open Jive and go to Edit -> Create Server

Fill out the form as follow:

The Server field must contain the ServerName (i.e. the name of the Device Server) and the instance. The Class field must contain the correct class name (in this case TangoQuickStart) and in the Device field you can add one or more devices following the naming convention: `domain/family/member`.

Finally click Register server.

###### Step 4: Start the device

In order to start the device using the command line navigate to the directory where you compiled your C++ class, e.g.

    cd packaging/src/

and use the command with the following syntax:

`<TangoClassName> <instance>`

Where `<TangoClassName>` is the name of the class that you created (in this example TangoQuickStart) and the instance is the name of the instance register in the Tango database (in this example test). So for this example the command would be:

    TangoQuickStart test

If everything is ok, the following message appears:

    Ready to accept request

E.g.

###### Step 5: explore the device

When finish, you can explore your device using Jive.

##### Write your first C++ TANGO device class

audience:developers lang:c++

The example code given in this chapter has been generated using the Tango code generator [Pogo]`pogo-documentation`. The following examples briefly describe how to write a device class with commands that receive and return different kinds of Tango data types. It also covers how to write device attributes.

This example device class implements 5 commands and 3 attributes. The commands are:

- The command `DevSimple`: handles the simple Tango data type

- The command `DevString`: handles Tango strings

- `DevArray`: receives and returns an array containing the simple Tango data type

- `DevStrArray`: does not receive any data but which returns an array of strings

- `DevStruct`: does not receive data but which returns one of the two Tango composed data types (`DevVarDoubleStringArray`)

For all of these commands, the default (always allowed)behavior of the state machine is used.

The attributes are :

- A spectrum type attribute of the Tango string type called `StrAttr`

- A readable attribute of the `Tango::DevLong` type called `LongRdAttr`. This attribute is linked with the following writable attribute

- A writable attribute also of the `Tango::DevLong` type called `LongWrAttr`.

###### The commands and attributes code

For each command called DevXxxx, Pogo generates a method named `dev_xxx` in the device class which will be executed when the command is requested by a client. In this chapter, the name of the device class is `DocDs`.

###### The `DevSimple` command

This method receives and returns a `Tango::DevFloat` data type which is simply a double representation of the input value. The code for the method executed by this command is the following:

     1  Tango::DevFloat DocDs::dev_simple(Tango::DevFloat argin)
     2  {
     3          Tango::DevFloat argout ;
     4          DEBUG_STREAM << "DocDs::dev_simple(): entering... !" << endl;
     5
     6          //      Add your own code to control device here
     7
     8          argout = argin * 2;
     9          return argout;
    10  }

This method is fairly simple. The received data is passed to the method as an argument and it is doubled at line 8 before being returning the result.

###### The `DevArray` command

This method receives and returns a `Tango::DevVarLongArray` data type. Each element of the array is doubled. The code for the method executed by this command is the following:

     1  Tango::DevVarLongArray *DocDs::dev_array(const Tango::DevVarLongArray *argin)
     2  {
     3          //      POGO has generated a method core with argout allocation.
     4          //      If you would like to use a static reference without copying,
     5          //      See "TANGO Device Server Programmer's Manual"
     6          //              (chapter x.x)
     7          //------------------------------------------------------------
     8          Tango::DevVarLongArray  *argout  = new Tango::DevVarLongArray();
     9
    10          DEBUG_STREAM << "DocDs::dev_array(): entering... !" << endl;
    11
    12          //      Add your own code to control device here
    13
    14          long argin_length = argin->length();
    15          argout->length(argin_length);
    16          for (int i = 0;i < argin_length;i++)
    17                  (*argout)[i] = (*argin)[i] * 2;
    18
    19          return argout;
    20  }

The `argout` array is created at line 8. Its length is set at line 15 from the input argument length. The array is populated at line 16 & 17 and then returned. This method allocates memory for the `argout` array, which is freed by the Tango core classes after the data have been sent to the caller, therefore the array does not need to be explictly deleted in the method. It is also possible to return data from a statically allocated array without copying.

###### The `DevString` command

This method receives and returns a `Tango::DevString` data type. The command simply displays the content of the input string and returns a hard-coded string. The code for the method executed by this command is the following:

     1  Tango::DevString DocDs::dev_string(Tango::DevString argin)
     2  {
     3          //      POGO has generated a method core with argout allocation.
     4          //      If you would like to use a static reference without copying,
     5          //      See "TANGO Device Server Programmer's Manual"
     6          //              (chapter x.x)
     7          //------------------------------------------------------------
     8          Tango::DevString        argout;
     9          DEBUG_STREAM << "DocDs::dev_string(): entering... !" << endl;
    10
    11          //      Add your own code to control device here
    12
    13          cout << "the received string is " << argin << endl;
    14
    15          string str("Am I a good Tango dancer ?");
    16          argout = new char[str.size() + 1];
    17          strcpy(argout, str.c_str());
    18
    19          return argout;
    20  }

The `argout` string is created at line 8. Internally, this method is using a standard C++ string. Memory for the returned data is allocated at line 16 and is initialized at line 17. Again, this memory is freed by the Tango core classes after the data have been sent to the caller and therefore does not need to be explictly deleted in the method. It is also possible to return data from a statically allocated string without copying.

###### The `DevStrArray` command

This method does not receive any input data but returns an array of strings (the `Tango::DevVarStringArray` data type). The code for the method executed by this command is the following:

     1  Tango::DevVarStringArray *DocDs::dev_str_array()
     2  {
     3          //      POGO has generated a method core with argout allocation.
     4          //      If you would like to use a static reference without copying,
     5          //      See "TANGO Device Server Programmer's Manual"
     6          //              (chapter x.x)
     7          //------------------------------------------------------------
     8          Tango::DevVarStringArray        *argout  = new Tango::DevVarStringArray();
     9
    10          DEBUG_STREAM << "DocDs::dev_str_array(): entering... !" << endl;
    11
    12          //      Add your own code to control device here
    13
    14          argout->length(3);
    15          (*argout)[0] = Tango::string_dup("Rumba");
    16          (*argout)[1] = Tango::string_dup("Waltz");
    17          string str("Jerck");
    18          (*argout)[2] = Tango::string_dup(str.c_str());
    19          return argout;
    20  }

The `argout` data array is created at line 8. Its length is set at line 14. The array is populated at line 15, 16 and 18. The last array element is initialized from a standard C++ string created at line 17. Note the usage of the `string_dup` function within the Tango namespace. This is necessary for string arrays due to the CORBA memory allocation schema.

###### The `DevStruct` command

This method does not receive input data but returns a structure of the `Tango::DevVarDoubleStringArray` data type. This type is a composed type with an array of doubles and an array of strings. The code for the method executed by this command is the following:

     1  Tango::DevVarDoubleStringArray *DocDs::dev_struct()
     2  {
     3          //      POGO has generated a method core with argout allocation.
     4          //      If you would like to use a static reference without copying,
     5          //      See "TANGO Device Server Programmer's Manual"
     6          //              (chapter x.x)
     7          //------------------------------------------------------------
     8          Tango::DevVarDoubleStringArray  *argout  = new Tango::DevVarDoubleStringArray();
     9
    10          DEBUG_STREAM << "DocDs::dev_struct(): entering... !" << endl;
    11
    12          //      Add your own code to control device here
    13
    14          argout->dvalue.length(3);
    15          argout->dvalue[0] = 0.0;
    16          argout->dvalue[1] = 11.11;
    17          argout->dvalue[2] = 22.22;
    18
    19          argout->svalue.length(2);
    20          argout->svalue[0] = Tango::string_dup("Be Bop");
    21          string str("Smurf");
    22          argout->svalue[1] = Tango::string_dup(str.c_str());
    23
    24          return argout;
    25  }

The `argout` data structure is created at line 8. The length of the double array in the output structure is set at line 14. The array is populated between lines 15 and 17. The length of the string array in the output structure is set at line 19. This string array is populated between lines 20 an 22 from a hard-coded string and from a standard C++ string. This method allocates memory for the `argout` data, which is freed by the Tango core classes after the data have been sent to the caller, therefore the array does not need to be explictly deleted in the method. Again, note the usage of the `string_dup` function of the Tango namespace. This is necessary for strings array due to the CORBA memory allocation schema.

###### The three attributes

Some variables have been added to the definition of the device class in order to store attribute values. These are a part of the class definition :

    1 protected :
    2         //      Add your own data members here
    3         //-----------------------------------------
    4         Tango::DevString        attr_str_array[5];
    5         Tango::DevLong          attr_rd;
    6         Tango::DevLong          attr_wr;

One variable has been created for each attribute. As the `StrAttr` attribute is of type spectrum with a maximum X dimension of 5, an array of length 5 has been reserved.

Several methods are necessary for these attributes:

- One method to read from the hardware, which is common to all readable attributes (e.g. `read_attr_hardware` in the example below)

- One read method for each readable attribute (e.g. `read_LongRdAttr`, etc in the example below) and

- One write method for each writable attribute (e.g. `write_LongWrAttr`, etc in the example below)

The code for these methods is the following:

     1 void DocDs::read_attr_hardware(vector<long> &attr_list)
     2 {
     3     DEBUG_STREAM << "DocDs::read_attr_hardware(vector<long> &attr_list) entering... "<< endl;
     4     // Add your own code here
     5
     6     string att_name;
     7     for (long i = 0;i < attr_list.size();i++)
     8     {
     9         att_name = dev_attr->get_attr_by_ind(attr_list[i]).get_name();
    10
    11        if (att_name == "LongRdAttr")
    12        {
    13            attr_rd = 5;
    14        }
    15    }
    16 }
    17
    18 void DocDs::read_LongRdAttr(Tango::Attribute &attr)
    19 {
    20     DEBUG_STREAM << "DocDs::read_LongRdAttr(Tango::Attribute &attr) entering... "<< endl;
    21
    22     attr.set_value(&attr_rd);
    23 }
    24
    25 void DocDs::read_LongWrAttr(Tango::Attribute &attr)
    26 {
    27     DEBUG_STREAM << "DocDs::read_LongWrAttr(Tango::Attribute &attr) entering... "<< endl;
    28
    29     attr.set_value(&attr_wr);
    30 }
    31
    32 void DocDs::write_LongWrAttr(Tango::WAttribute &attr)
    33 {
    34     DEBUG_STREAM << "DocDs::write_LongWrAttr(Tango::WAttribute &attr) entering... "<< endl;
    35
    36     attr.get_write_value(attr_wr);
    37     DEBUG_STREAM << "Value to be written = " << attr_wr << endl;
    38 }
    39
    40 void DocDs::read_StrAttr(Tango::Attribute &attr)
    41 {
    42     DEBUG_STREAM << "DocDs::read_StrAttr(Tango::Attribute &attr) entering... "<< endl;
    43
    44     attr_str_array[0] = const_cast<char *>("Rock");
    45     attr_str_array[1] = const_cast<char *>("Samba");
    46
    47     attr_set_value(attr_str_array, 2);
    48 }

The `read_attr_hardware()` method is executed once when a client executes the `read_attributes` CORBA request whatever the number of attribute to be read is. The rule of this method is to read values from the hardware and to store the read values somewhere in the device object. In our example, only the LongRdAttr attribute internal value is set by this method at line 13.

The method `read_LongRdAttr()` is executed by the `read_attributes` CORBA call when the `LongRdAttr` attribute is read but after the `read_attr_hardware()` method has been executed. Its rule is to set the attribute value in the TANGO core classes object representing that attribute. This is done at line 22.

The method `read_LongWrAttr()` will be executed when the `LongWrAttr` attribute is read (again, after the `read_attr_hardware()` method). The attribute value is set at line 29. In the same manner, the method called `read_StrAttr()` will be executed when the attribute `StrAttr` is read. Its value is initialized in this method at line 44 and 45. There are several ways to code spectrum or image attribute of the `DevString` data type.

The `write_LongWrAttr()` method is executed when the `LongWrAttr` attribute value is set by a client. The new attribute value coming from the client is stored in the data object at line 36.

Pogo also generates a file called DocDsStateMachine.cpp (for a Tango device server class called DocDs). This file is used to store methods for the device state machine. By default an always allowed state machine is provided. For more information about coding the state machine, refer to the chapter on state machine management.

##### Device server with user defined event loop

audience:developers lang:c++

Sometimes, it could be useful 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 for example must allow the GUI to handle windowing events in addition to allowing the ORB to handle incoming requests. These types of device servers therefore perform non-blocking event handling. They turn the main thread of control over each of the various event-handling sub-systems while not allowing any of them to block for significant amount 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 therefore the user’s responsibility 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 this feature can be used.

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

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

##### How to create inheritance link between Tango classes

audience:developers lang:c++

This howto explains how to create a new Tango device class which inherits from an already existing one.

We use the usual definition of inheritance from object oriented programming:

1. The high level class supports all the commands/attributes defined in the low level class on top of its own (if any),

2. The device(s) available for the external world are an instances of the high level class.

Warning

Once modified, Pogo will not be able to understand the high level Tango class and therefore can’t be used for further changes in this class

###### The example case

Let’s say that we already have a Tango class called `LowLevel` with one command called `LowLevelCmd` and one attribute called `LowLevelAttr`. We want to create a new Tango class called `HighLevel` with its own command called `HighLevelCmd` and its own attribute called `HighLevelAttr`. Both classes have been generated using Pogo. The following is a description of what has to be modified in the code generated by Pogo for the `HighLevel` Tango class in order to create the inheritance link.

###### The HighLevelClass.h file

In this file, we will modify the `HighLevelClass` declaration in order to inherit from the `LowLevelClass` class instead of `TANGO_BASE_CLASS` but we first need to add an extra include file at the file beginning:

    1#ifndef _HIGHLEVELCLASS_H
    2#define _HIGHLEVELCLASS_H
    3
    4#include <tango.h>
    5#include <HighLevel.h>
    6
    7#include <LowLevelClass.h>

Then, we change the HighLevelClass inheritance declaration:

     1//
     2// The HighLevelClass singleton definition
     3//
     4
     5class
     6#ifdef WIN32
     7    __declspec(dllexport)
     8#endif
     9HighLevelClass : public LowLevel_ns::LowLevelClass
    10{
    11public:
    12//  properties member data
    13
    14//  add your own data members here
    15//------------------------------------

###### The HighLevelClass.cpp file

In this file, we have to modify:

1. The HighLevelClass constructor

2. The HighLevelClass command_factory() method

3. The HighLevelClass attribute_factory() method

     1//+----------------------------------------------------------------------------
     2//
     3// method :         HighLevelClass::HighLevelClass(std::string &s)
     4//
     5// description :    constructor for the HighLevelClass
     6//
     7// in : - s : The class name
     8//
     9//-----------------------------------------------------------------------------
    10HighLevelClass::HighLevelClass(std::string &s)
    11: LowLevel_ns::LowLevelClass(s)
    12{
    13    TANGO_LOG_INFO << "Entering HighLevelClass constructor" << std::endl;
    14    set_default_property();
    15    get_class_property();
    16    write_class_property();
    17
    18    TANGO_LOG_INFO << "Leaving HighLevelClass constructor" << std::endl;
    19}

Then, the changes in the command_factory() method which needs to call the LowLevelClass command_factory() method:

     1//+----------------------------------------------------------------------------
     2//
     3// method :         HighLevelClass::command_factory
     4//
     5// description :    Create the command object(s) and store them in the
     6//          command list
     7//
     8//-----------------------------------------------------------------------------
     9void HighLevelClass::command_factory()
    10{
    11    LowLevel_ns::LowLevelClass::command_factory();
    12
    13    command_list.push_back(new HighLevelCmdClass("HighLevelCmd",
    14        Tango::DEV_VOID, Tango::DEV_VOID,
    15        "",
    16        "",
    17        Tango::OPERATOR));
    18
    19    // add polling if any
    20    for(size_t i = 0 ; i < command_list.size(); i++)
    21    {
    22      // ...
    23    }
    24}

Finally, the changes in the attribute_factory() method which needs to call the LowLevelClass attribute_factory() method:

     1//+----------------------------------------------------------------------------
     2//  Method: HighLevelClass::attribute_factory(std::vector<Tango::Attr *> &att_list)
     3//-----------------------------------------------------------------------------
     4void HighLevelClass::attribute_factory(std::vector<Tango::Attr *> &att_list)
     5{
     6    LowLevel_ns::LowLevelClass::attribute_factory(att_list);
     7
     8    //  Attribute : HighLevelAttr
     9    HighLevelAttrAttrib *high_level_attr = new HighLevelAttrAttrib();
    10    // ...
    11    att_list.push_back(high_level_attr);
    12}

###### The HighLevel.h file

This file has to be modified in order to:

1. Change the `HighLevel` class inheritance from `TANGO_BASE_CLASS` to `LowLevel_ns::LowLevel`

2. Add a new data member in the `HighLevel` class in order to correctly implement the device `Init` command (a boolean is enough)

3. Modify the class destructor for a correct management of the device `Init` command

First, we have to add a new include file:

    1#ifndef _HIGHLEVEL_H
    2#define _HIGHLEVEL_H
    3
    4#include <tango.h>
    5#include <LowLevel.h>

Then, the change in the HighLevel class inheritance:

    1class HighLevel: public LowLevel_ns::LowLevel
    2{
    3public :
    4    //  Add your own data members here
    5    //-----------------------------------------

The addition of the new data member at the end of the HighLevel class declaration:

    1protected :
    2    //  Add your own data members here
    3    //-----------------------------------------
    4    bool device_constructed{false};
    5}

And finally, the change in the HighLevel class destructor:

    1/**
    2 * The object desctructor.
    3 */
    4    ~HighLevel()
    5    {
    6      delete_device();
    7    }

###### The HighLevel.cpp file

In this file, we have to modify

1. The HighLevel class constructors to reflect the change in its inheritance and to initialize the new data member `device_constructed`

2. The HighLevel class `delete_device()` and `init_device()` to correctly handle the device `Init` command

3. The HighLevel class `always_executed_hook()` and `read_attr_hardware()` methods in order that they call the corresponding `LowLevel` class method

Let’s start with the changes in the HighLevel class constructors:

     1//+----------------------------------------------------------------------------
     2//
     3// method :         HighLevel::HighLevel(string &s)
     4//
     5// description :    constructor for HighLevel
     6//
     7// in : - cl : Pointer to the DeviceClass object
     8//      - s  : Device name
     9//      - d  : Description
    10//
    11//-----------------------------------------------------------------------------
    12HighLevel::HighLevel(Tango::DeviceClass *cl, std::string &s)
    13  :LowLevel_ns::LowLevel(cl, s.c_str())
    14{
    15    init_device();
    16    device_constructed = true;
    17}
    18
    19HighLevel::HighLevel(Tango::DeviceClass *cl, const char *s)
    20  :LowLevel_ns::LowLevel(cl, s)
    21{
    22    init_device();
    23    device_constructed = true;
    24}
    25
    26HighLevel::HighLevel(Tango::DeviceClass *cl, const char *s, const char *d)
    27  :LowLevel_ns::LowLevel(cl, s, d)
    28{
    29    init_device();
    30    device_constructed = true;
    31}

Now, the modified HighLevel class `init_device()` and `delete_device()` methods:

     1//+----------------------------------------------------------------------------
     2//
     3// method :         HighLevel::delete_device()
     4//
     5// description :    will be called at device destruction or at init command.
     6//
     7//-----------------------------------------------------------------------------
     8void HighLevel::delete_device()
     9{
    10    TANGO_LOG_INFO << "HighLevel::delete_device()" << std::endl;
    11
    12    // Delete device's allocated object
    13
    14    if(device_constructed)
    15    {
    16       LowLevel_ns::LowLevel::delete_device();
    17    }
    18}
    19
    20//+----------------------------------------------------------------------------
    21//
    22// method :         HighLevel::init_device()
    23//
    24// description :    will be called at device initialization.
    25//
    26//-----------------------------------------------------------------------------
    27void HighLevel::init_device()
    28{
    29    if(device_constructed)
    30    {
    31      LowLevel_ns::LowLevel::init_device();
    32    }
    33
    34    TANGO_LOG_INFO << "HighLevel::HighLevel() create device " << device_name << std::endl;
    35    // ...

And finally, the HighLevel class `always_executed_hook()` and `read_attr_hardware()` methods:

     1//+----------------------------------------------------------------------------
     2//
     3// method :         HighLevel::always_executed_hook()
     4//
     5// description :    method always executed before any command is executed
     6//
     7//-----------------------------------------------------------------------------
     8void HighLevel::always_executed_hook()
     9{
    10    LowLevel_ns::LowLevel::always_executed_hook();
    11    TANGO_LOG_INFO << "HighLevel::always_executed_hook()" << std::endl;
    12    // ...
    13}
    14
    15//+----------------------------------------------------------------------------
    16//
    17// method :         HighLevel::read_attr_hardware
    18//
    19// description :    Hardware acquisition for attributes.
    20//
    21//-----------------------------------------------------------------------------
    22void HighLevel::read_attr_hardware(std::vector<long> &attr_list)
    23{
    24    LowLevel_ns::LowLevel::read_attr_hardware(attr_list);
    25    TANGO_DEBUG_LOG << "HighLevel::read_attr_hardware(std::vector<long> &attr_list) entering... " << std::endl;
    26    // ...
    27}

Don’t forget to also modify `CMakeLists.txt` in order to link the three LowLevel Tango class object files (Lowlevel.o, LowLevelClass.o and LowLevelStateMachine.o) to your executable.

The `HighLevel` class can be registered and started with the usual approach by Jive or tango_admin.

###### Conclusion

With these relatively simple changes in the HighLevel class, we now have a device instance of a Tango class which “inherits” from another Tango class. The drawback of this method is that once the file has been modified, Pogo will not be able to understand the HighLevel class any more and should not be used for further changes in this class!

With a couple of `virtual` methods, it is also possible in the `HighLevel` class to overwrite a command or an attribute defined in the Lowlevel class.

##### Use C++ `std::vector` to set attributes

audience:developers lang:c++

This page contains examples on how to use the C++ vector class to set and get attribute values on the server side.

Warning

Tango does not create copies of data for optimization reasons and because of this all of the attribute `set_value()` methods only take pointers as an input. If you are going to use C++ vectors you should be aware of the fact that you are going to be copying the data, which may slow down the execution time when working with large amounts of data.

The `std::vector` class takes care of the memory in its entries so it is mandatory to leave the optional `release` parameter of `Attribute::set_value` set to the default of `false`.

Below are two examples of setting data for a read attribute using a vector of shorts and a vector of strings:

     1void MyClass::read_spectrum(Tango::Attribute &attr)
     2{
     3  DEBUG_STREAM << "MyClass::read_Spectrum(Tango::Attribute &attr) entering... "<< std::endl;
     4  /*----- PROTECTED REGION ID(MyClass::read_Spectrum) ENABLED START -----*/
     5
     6  std::vector<Tango::DevShort> val;
     7  vec.emplace_back(1);
     8  vec.emplace_back(2);
     9  vec.emplace_back(3);
    10
    11  attr.set_value(val.data(), val.size());
    12
    13  /*----- PROTECTED REGION END -----*/ // MyClass::read_Spectrum
    14}

     1void MyClass::read_string_spectrum(Tango::Attribute &attr)
     2{
     3  DEBUG_STREAM << "MyClass::read_StringSpectrum(Tango::Attribute &attr) entering... "<< std::endl;
     4  /*----- PROTECTED REGION ID(MyClass::read_StringSpectrum) ENABLED START -----*/
     5
     6  std::vector<std::string> vec;
     7  vec.emplace_back("Hello");
     8  vec.emplace_back("foggy");
     9  vec.emplace_back("garden!");
    10
    11  attr.set_value(vec.data(), vec.size());
    12
    13  /*----- PROTECTED REGION END -----*/ // MyClass::read_StringSpectrum
    14}

Below is an example for a writeable attribute using a vector of doubles:

     1void MyClass::write_double_spectrum(Tango::WAttribute &attr)
     2{
     3  DEBUG_STREAM << "MyClass::write_double_spectrum(Tango::WAttribute &attr) entering... " << std::endl;
     4  // Retrieve number of write values
     5  int w_length = attr.get_write_value_length();
     6
     7  // Retrieve pointer on write values (Do not delete !)
     8  const Tango::DevDouble  *w_val;
     9  attr.get_write_value(w_val);
    10  /*----- PROTECTED REGION ID(MyClass::write_double_spectrum) ENABLED START -----*/
    11
    12  // not strictly needed, but makes the code easier to grasp
    13  if(w_length == 0)
    14  {
    15    return;
    16  }
    17
    18  std::vector<double> vec;
    19  vec.assign(w_val, w_val + w_length);
    20
    21  /*----- PROTECTED REGION END -----*/ // MyClass::write_double_spectrum
    22}

##### How to add dynamic attributes to a device class

audience:developers lang:c++

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:

1. StaticAttr: A scalar, Tango::DevShort, READ attribute,

2. LongDynAttr: A scalar, Tango::DevLong, READ_WRITE attribute,

3. 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:

1. call the `Tango::DeviceImpl::add_attribute()` method for each device dynamic attribute

2. create 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.

##### Handle Tango string attributes in C++

audience:developers lang:c++

The underlying technology for network operations is CORBA. As this was standardized before C++, it still uses plain char pointers instead of the `std::string` class.

This means that you have to handle:

1. the pointer to the memory where the string is stored (`char *`)

2. the memory where the string characters are stored

This adds one level of complexity and you have to take care of memory allocation for these entities when you have string attributes (scalar, spectrum or image).

The first question you have to answer is whether you want static or dynamic memory allocation for your string attribute?

- Static memory allocation means that the memory is allocated once.

- Dynamic memory allocation means that the memory used for the attribute is allocated and freed each time the attribute is read.

Once one method has been chosen, both the pointer and the characters array memory has to follow the same rule (all static or all dynamic, not a mix of them).

###### Scalar attributes

###### Static allocation

Within Pogo, do not click the “allocate” toggle button when you define the attribute. This means that Pogo will not allocate memory.

The pointer to the character array is defined as one of the device data members in the `MyDev.h` file, see example below. The Tango data type `DevString` is simply an alias for a good old `char *` pointer.

     1class MyDev : public TANGO_BASE_CLASS
     2{
     3
     4    /*----- PROTECTED REGION ID(MyDev::Data Members) ENABLED START -----*/
     5
     6    // Add your own data members
     7public:
     8    Tango::DevString the_str;
     9    /*----- PROTECTED REGION END -----*/ // MyDev::Data Members
    10}

In the `init_device` method (within the MyDev.cpp file), one has to initialize the attribute data member created for you by Pogo (e.g. `attr_StringAttr_read`):

     1void MyDev::init_device()
     2{
     3    DEBUG_STREAM << "MyDev::init_device() create device " << device_name << std::endl;
     4
     5    /*----- PROTECTED REGION ID(StringAttr::init_device) ENABLED START -----*/
     6
     7    attr_StringAttr_read = &the_str;
     8
     9    /*----- PROTECTED REGION END -----*/    // MyDev::init_device
    10}

The attribute related code in the MyDev.cpp file would then follow:

     1void MyDev::read_StringAttr(Tango::Attribute &attr)
     2{
     3    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
     4
     5    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
     6
     7    // Set the attribute value
     8    the_str = const_cast<char *>("Hola Barcelona");
     9    attr.set_value(attr_StringAttr_read);
    10
    11    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
    12}

Here the pointer `the_str` is defined as a device data member and is initialized to a statically allocated string. The argument of the `Attribute::set_value` method is of type `char **` which is coherent with the definition of the `Tango::DevString` type. Nevertheless, the definition of statically allocated string in C++ is a `const char *`. This is why we need a `const_cast` during the pointer initialization.

Note that the use of the Pogo generated data member (named `attr_StringAttr_read` here) is not mandatory. You can directly give the address of the `the_str` pointer to `the Attribute::set_value` method and do not need any additional code in the `init_device` method.

###### Dynamic allocation

###### Memory freeing in the Tango layer

Within Pogo, do not click the “allocate” toggle button when you define the attribute. This means that Pogo will not allocate memory and will leave it for the developer to handle.

In this case we do not need to define anything as device data member in the header file.

The attribute related code in the file `MyDev.cpp` follows:

     1void MyDev::read_StringAttr(Tango::Attribute &attr)
     2{
     3    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
     4
     5    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
     6
     7    // Set the attribute value
     8    attr_StringAttr_read = new Tango::DevString;
     9    *attr_StringAttr_read = Tango::string_dup("Bonjour Paris");
    10    attr.set_value(attr_StringAttr_read, 1, 0, true);
    11
    12    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
    13}

As explained in the introduction, both the pointer and the char array memory are dynamically allocated. The pointer is allocated first, then it is initialized with the result of a `Tango::string_dup` method ,which allocates memory and copies the string given as argument. The Tango attribute value is set with the classical `set_value` method but requiring Tango to free all the memory previously allocated. This is achieved by specifying the optional `release` variable as `true` (default is `false`).

###### Memory freeing in the device class

This example is in the case where within Pogo, the “allocate” toggle button was selected when the attribute was defined. This means that Pogo will allocate the memory.

The `init_device` and `delete_device` method follow as:

     1void MyDev::init_device()
     2{
     3    DEBUG_STREAM << "MyDev::init_device() create device " << device_name << std::endl;
     4
     5    attr_StringAttr_read = new Tango::DevString[1];
     6
     7    /*----- PROTECTED REGION ID(StringAttr::init_device) ENABLED START -----*/
     8
     9    *attr_StringAttr_read = nullptr;
    10
    11    /*----- PROTECTED REGION END -----*/    // MyDev::init_device
    12}

The pointer for the characters array is allocated in the `init_device` method and initialized to null pointer.

     1void MyDev::delete_device()
     2{
     3    /*----- PROTECTED REGION ID(MyDev::delete_device) ENABLED START -----*/
     4
     5    Tango::string_free(*attr_StringAttr_read);
     6
     7    /*----- PROTECTED REGION END -----*/    // MyDev::delete_device
     8    delete[] attr_StringAttr_read;
     9
    10}

In the `delete_device` method, the character array memory is freed with the `Tango::string_free` method.

The attribute related code follows as:

     1void MyDev::read_StringAttr(Tango::Attribute &attr)
     2{
     3    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
     4
     5    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
     6
     7    // Set the attribute value
     8    Tango::string_free(*attr_StringAttr_read);
     9    *attr_StringAttr_read = Tango::string_dup("Bonjour Paris");
    10    attr.set_value(attr_StringAttr_read);
    11
    12    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
    13}

The `Tango::DevString` pointer created by Pogo (named `attr_StringAttr_read`) is allocated in the `init_device` method (in Pogo generated code) and freed in the `delete_device` method (Pogo generated code). Nevertheless, nothing is done for the memory used to store the characters array on attribute reading. Freeing of an existing attribute is done in this code snippet in the first line of the protected region. Then the memory is allocated for the new characters array and used to set to the Tango attribute instance value.

Note that only the memory allocated for the character array is allocated/freed at each attribute reading. The pointer is allocated once in the `init_device` method and freed in the `delete_device` method.

###### Spectrum / Image attributes

###### Static allocation

The code needed in this case is very similar to the scalar case. We also need pointers to the character arrays. They are defined as device data members in the `MyDev.h` header file.

     1{
     2constexpr long str_arr_size = 2;
     3
     4} // anonymous namespace
     5
     6class MyDev : public TANGO_BASE_CLASS
     7{
     8   /*----- PROTECTED REGION ID(MyDev::Data Members) ENABLED START -----*/
     9
    10  // Add your own data members
    11public:
    12   Tango::DevString  the_str_array[str_arr_size];
    13
    14   /*----- PROTECTED REGION END -----*/ // MyDev::Data Members
    15}

In the `init_device` method (within the `MyDev.cpp` file), one has to initialize the attribute data members created for you by Pogo.

     1void MyDev::init_device()
     2{
     3    DEBUG_STREAM << "MyDev::init_device() create device " << device_name << std::endl;
     4
     5    /*----- PROTECTED REGION ID(StringAttr::init_device) ENABLED START -----*/
     6
     7    attr_StringAttr_read = the_str_array;
     8
     9    /*----- PROTECTED REGION END -----*/    // MyDev::init_device
    10}

The attribute related code in the `MyDev.cpp` file follows:

     1void MyDev::read_StringAttr(Tango::Attribute &attr)
     2{
     3    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
     4
     5    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
     6
     7    // Set the attribute value
     8    the_str_array[0] = const_cast<char *>("Hola Barcelona");
     9    the_str_array[1] = const_cast<char *>("Ciao Trieste");
    10    attr.set_value(attr_StringAttr_read, str_arr_size);
    11
    12    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
    13}

The array `the_str_array` defined as a device data member is initialized to statically allocated strings. The argument of the `Attribute::set_value` method is of type `char **` which is coherent with the definition of the `Tango::DevString` type. Nevertheless, the definition of statically allocated string in C++ is a `const char *`. This is why we need a `const_cast` during the pointer initialization.

Note that the use of the Pogo generated data member (named `attr_StringAttr_read` in our case) is not mandatory. You can directly give the name of the `the_str_array` data member to the `Attribute::set_value` method and do not need any additional code in the `init_device` method.

Something similar can be done using a vector of C++ strings assuming that:

1. the vector is initialized somewhere in the Tango class

2. the vector is declared as a device data member (in MyDev.h header file)

3. the vector size is less than or equal to the attribute’s maximum dimension

An example of the code is shown below:

     1void MyDev::read_StringAttr(Tango::Attribute &attr)
     2{
     3    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
     4
     5    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
     6
     7    // Set the attribute value
     8    for (size_t i = 0;i < str_arr_size;i++)
     9    {
    10       the_str_array[i] = vs[i].data();
    11    }
    12
    13    attr.set_value(attr_StringAttr_read, vs.size());
    14
    15    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
    16}

###### Dynamic allocation

###### Memory freeing in the Tango layer

Within Pogo, do not click the “allocate” toggle button when you define the attribute. This means that Pogo will not allocate memory and will leave it for the developer to handle.

In this case, we do not need to define anything as device data member in the header file.

The attribute related code in the `MyDev.cpp` file follows:

     1{
     2constexpr long str_arr_size = 2;
     3
     4} // anonymous namespace
     5
     6void MyDev::read_StringAttr(Tango::Attribute &attr)
     7{
     8    DEBUG_STREAM << "MyDev::read_StringAttr(Tango::Attribute &attr) entering... " << std::endl;
     9
    10    /*----- PROTECTED REGION ID(MyDev::read_StringAttr) ENABLED START -----*/
    11
    12    // Set the attribute value
    13    Tango::DevString *ptr_array = new Tango::DevString[str_arr_size]
    14    ptr_array[0] = Tango::string_dup("Bonjour Paris");
    15    ptr_array[1] = Tango::string_dup("Salut Grenoble");
    16    attr.set_value(ptr_array, str_arr_size, 0, true);
    17
    18    /*----- PROTECTED REGION END -----*/    // MyDev::read_StringAttr
    19}

The `Tango::DevString` pointer array is allocated first, then it is initialized with the results of a `Tango::string_dup` method which allocates memory and copies the string given as argument. The Tango attribute value is set with the classical `set_value` method but requiring Tango to free all the memory previously allocated. This is achieved by specifying the optional `release` variable as `true` (default is `false`).

###### Conclusion

Note

Do not mix the two solutions. Use either dynamic or static allocation and also ensure that you are using it for both the pointer and character array.

##### How to reconnect to the Tango HOST at device server startup time

audience:developers lang:c++

The following C++ snippet shows how to allow the Device Server to start before the Tango Host is available. In this mode the device server will wait until it can reach the tango host. This mode of operation does not make sense to combine with the File Database as that is always immediately available.

Set `_daemon` and `_sleep_between_connect` from `Tango::Util` before initialization in the `main` function. This function is located in `main.cpp` when the device server was generated with Pogo:

     1  int main(int argc, char *argv[])
     2  {
     3    // Set an automatic retry on database connection
     4    //----------------------------------------------
     5    Tango::Util::_daemon = true;
     6    Tango::Util::_sleep_between_connect = 5;
     7
     8    // Initialize the device server
     9    //--------------------------------
    10    tg = Tango::Util::init(argc, argv);
    11
    12    ...
    13  }

The device server will retry to connect to the tango host in case of failures periodically. The retry interval is here set to 5 seconds, it defaults to 60 seconds.

##### How to use an enumerated attribute

audience:developers lang:c++

Enumerated attributes are supported using the data type DevEnum.

This data type is not a real C++ enumeration because:

1. The enumerated value allways start with 0

2. Values are consecutive

3. It is transferred on the network as a DevShort data type

One enumeration label is associated to each enumeration value. For the Tango kernel, it is this list of enumeration labels which will define the possible enumeration values. For instance if the enumeration has 3 labels, its value must be between 0 and 2. There are two ways to define the enumeration labels:

1. At attribute creation time. This is the most common case when the list of possible enumeration values and labels are known at compile time. The Tango code generator Pogo generates the code needed to pass the enumeration labels to the Tango kernel.

2. In the user code when the enumeration values and labels are not known at compile time but retrieved during the device startup phase. The user gives the possible enumeration values to the Tango kernel using the Attribute class `set_properties()` method.

A Tango client is able to retrieve the enumeration labels in the attribute configuration returned by that instance by a call to the `DeviceProxy::get_attribute_config()` method. A user may also change the enumeration labels using the `DeviceProxy::set_attribute_config()` call but not cannot change their number.

###### Usage in a Tango class

Within a Tango class, you set the attribute value with a C++ enum or a DevShort variable. In the case that a DevShort variable is used, its value will be checked according to the enumeration labels list passed to the Tango kernel.

###### Setting the labels with enumeration compile time knowledge

In this case the enumeration labels are given to the Tango kernel at the attribute creation time in the `attribute_factory` method of the XXXClass class. Let us take one example

     1  enum class Card: short
     2  {
     3      NORTH = 0,
     4      SOUTH,
     5      EAST,
     6      WEST
     7  };
     8
     9  struct TheEnumAttrib : Tango::Attr
    10  {
    11      ...
    12
    13      // Required for enum attributes
    14      virtual bool same_type(const type_info& in_type) override { return typeid(Card) == in_type; }
    15      virtual std::string get_enum_type() override { return "Card"; }
    16  }
    17
    18  void XXXClass::attribute_factory(vector<Tango::Attr *> &att_list)
    19  {
    20      .....
    21      TheEnumAttrib   *theenum = new TheEnumAttrib();
    22      Tango::UserDefaultAttrProp theenum_prop;
    23      vector<string> labels = {"North","South","East","West"};
    24      theenum_prop.set_enum_labels(labels);
    25      theenum->set_default_properties(theenum_prop);
    26      att_list.push_back(theenum);
    27      .....
    28   }

line 1-7 : The definition of the enumeration (C++11 in this example)

line 9-16 : The definition of the enumerated attribute

line 23 : Creation of a vector of strings with the enumeration labels. Because there is no way to get the labels from the enumeration definition, they are re-defined here.

line 24 : This vector is given to the `theenum_prop` object which contains the user default properties

line 26 : The user default properties are associated to the attribute

In most cases this code will be automatically generated by the Tango code generator Pogo. It is given here for completeness.

###### Setting the labels without enumeration compile time knowledge

In this case the enumeration labels are retrieved by the user in a way that is specific to the device and passed to the Tango kernel using the Attribute class `set_properties()` method. Let us take one example

     1  void MyDev::init_device()
     2  {
     3      ...
     4
     5      Attribute &att = get_device_attr()->get_attr_by_name("TheEnumAtt");
     6      MultiAttrProp<DevEnum> multi_prop;
     7      att.get_properties(multi_prop);
     8
     9      multi_prop.enum_labels = {....};
    10      att.set_properties(multi_prop);
    11      ....
    12   }

line 5 : Get a reference to the attribute object

line 7 : Retrieve the attribute properties

line 9 : Initialise the attribute labels in the set of attribute properties

line 10 : Set the attribute properties

###### Setting the attribute value

It is possible to set the attribute value using either a classical DevShort variable or using a variable of the C++ enumeration. The following example is when you have compile time knowledge of the enumeration definition. We assume that the enumeration is the same as the one defined above (i.e. the Card enumeration)

    1  enum Card points;
    2
    3  void MyDev::read_TheEnum(Attribute &att)
    4  {
    5      ...
    6      points = SOUTH;
    7      att.set_value(&points);
    8  }

line 1 : One instance of the Card enum is created (named points)

line 6 : The enumeration is initialized

line 7 : The value of the attribute object is set using the enumeration (using a pointer)

To get the same result using a classical DevShort variable, the code would look like

    1  DevShort sh;
    2
    3  void MyDev::read_TheEnum(Attribute &att)
    4  {
    5      ...
    6      sh = 1;
    7      att.set_value(&sh);
    8  }

line 1 : A DevShort variable is created (named `sh`)

line 6 : The variable is initialized

line 7 : The value of the attribute object is set using the DevShort variable (using a pointer)

###### Usage in a Tango client

Within a Tango client, you insert/extract enumerated attribute values in/from a DeviceAttribute object with a C++ enum or a DevShort variable. The latter case is for generic clients which do not have compile time knowledge of the enumeration. The code looks like

    1  DeviceAttribute da = the_dev.read_attribute("TheEnumAtt");
    2  Card ca;
    3  da >> ca;
    4
    5  DeviceAttribute db = the_dev.read_attribute("TheEnumAtt");
    6  DevShort sh;
    7  da >> sh;

line 2-3 : The attribute value is extracted in a C++ enumeration variable

line 6-7 : The attribute value is extracted in a DevShort variable

##### How to use a forwarded attribute

audience:developers lang:c++

###### Definition

Let’s take an example to explain what is a forwarded attribute. We assume we have to write a Tango class for a ski lift in a ski resort somewhere in the Alps. Obviously, the ski lift has a motor for which we already have a Tango class. This motor Tango class has one attribute speed. But for the ski lift, the motor speed is not the only thing which has to be controlled. For instance, you also want to give access to the wind sensor data installed on the top of the ski lift. Therefore, you write a ski-lift Tango class representing the whole ski-lift system. This ski-lift class will have at least two attributes which are:

1. The wind speed at the top of the ski-lift

2. The motor speed

The ski-lift Tango class motor speed attribute is nothing more than the motor Tango class speed attribute. All the ski-lift class has to do for this attribute is to forward the request (read/write) to the speed attribute of the motor Tango class. The speed attribute of the ski-lift Tango class is a forwarded attribute while the speed attribute of the motor Tango class is its root attribute.

A forwarded attribute get its configuration from its root attribute and it forwards to its root attribute:

- Its read / write / write_read requests

- Its configuration change

- Its event subscription

- Its locking behavior

As stated above, a forwarded attribute has the same configuration as its root attribute except its name and label which stays local. All other attribute configuration parameters are forwarded to the root attribute. If a root attribute configuration parameter is changed, the forwarded attribute is informed (via event) and its local configuration is also modified.

The association between the forwarded attribute and its root attribute is done using a property named `\_\_root_att` belonging to the forwarded attribute. This property value is simply the name of the root attribute. Multi-control system is supported and this `\_\_root_att` attribute property value can be something like tango://my_tango_host:10000/my/favorite/dev/the_root_attribute. The name of the root attribute is included in the attribute configuration.

Polling has to be done on the root attribute. Polling a forwarded attribute is not allow and an exception will be thrown if such a case happens. If the root attribute is polled, a request to read the forwarded attribute with the DeviceProxy object source parameter set to `CACHE_DEVICE` or `CACHE` will get its data from the root attribute polling buffer.

If you subscribe to events on a forwarded attribute, the subscription is forwarded to the root attribute. When the event is received by the forwarded attribute, the attribute name in the event data is modified to reflect the forwarded attribute name and the event is pushed to the original client(s).

When a device with forwarded attribute is locked, the device to which the root attribute belongs to is also locked.

###### Coding

As explained in the section Writing a Tango device server, each Tango class attribute is implemented via a C++ class which has to inherit from either `Attr*`, `SpectrumAttr` or `ImageAttr` according to the attribute data format. For a forwarded attribute, the related class has to inherit from the `FwdAttr` class no matter what its data format is. For classical attributes, the programmer can define in the Tango class code default values for the attribute properties using one instance of the `UserDefaultAttrProp` class. For a forwarded attribute, the programmer has to create one instance of the `UserDefaultFwdAttrProp` class but only the attribute label can be defined. One example of how to program a forwarded attribute is given below:

     1  class MyFwdAttr: public Tango::FwdAttr
     2  {
     3  public:
     4      MyFwdAttr(const string &_n):FwdAttr(_n) {};
     5      ~MyFwdAttr() {};
     6  };
     7
     8  void DevTestClass::attribute_factory(vector<Tango::Attr *> &att_list)
     9  {
    10     ...
    11     MyFwdAttr *att1 = new MyFwdAttr("fwd_att_name");
    12     Tango::UserDefaultFwdAttrProp att1_prop;
    13     att1_prop.set_label("Gasp a fwd attribute");
    14     att1->set_default_properties(att1_prop);
    15     att_list.push_back(att1);
    16     ...
    17  }

Line 1 : The forwarded attribute class inherits from `FwdAttr` class.

Line 4-5 : Only constructor and destructor methods are required

Line 11 : The attribute object is created

Line 12-14 : A default value for the forwarded attribute label is defined.

Line 15: The forwarded attribute is added to the list of attribute

If there is an error in the forwarded attribute configuration (for instance missing `\_\_root_att` property), the attribute is not created by the Tango kernel and is therefore not visible for the external world. The state of the device to which the forwarded attribute belongs to is set to ALARM (if not already FAULT) and a detailed error report is available in the device status. In case a device with forwarded attribute(s) is started before the device(s) with the root attribute(s), the same principle is used: forwarded attribute(s) are not created, device state is set to ALARM and device status reports the error. When the device(s) with the root attribute starts, the forwarded attributes will automatically be created.

##### How to use a memorized attribute

audience:developers lang:c++

It is possible to ask Tango to store the last written value for attribute of the SCALAR data format, which are READ_WRITE or READ_WITH_WRITE, in the database. This is fully automatic. During device startup phase all device memorized attributes with a value written in the database will have their value 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 to set an attribute as memorized and how 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 initialised.

Note

Memorized attributes have the following behaviour to consider:

- The writing of the memorized attributes is carried out after the function `init_device`, executed by the Tango layer, and not by the Tango DeviceServer code. In case an error occurs during the `init_device` it cannot be caught by the Tango DeviceServer programmer.

- If in the `init_device` method an error occurs that causes a change of state in which the writing of an attribute is impossible, this error will prohibit the restoration of the memorized value of the attribute.

- The order of reloading is deterministic but complex (order of ClassFactory then device definition in database then attribute definition in Pogo). Therefore relying on this order might have some side effects particularly in case attributes are modified through Pogo when attributes values are linked (e.g. sampling frequency and number of samples).

- There could be performance issues in the case that the setpoint is written at a high frequency as the static Tango database is queried on each write of the memorized attribute. Since Tango 9 the database has been optimised for memorized attributes and it should be possible to update memorized attributes at 10 Hz without taking a performance hit.

Tip

If this standard Tango behaviour for reloading memorized values doesn’t fit your needs then we recommend coding the reloading of attribute values yourself. This is especially true for fast (> 10 Hz) feedback loops, which can trigger the writing of attributes at a high frequency.

##### How to work with device and attribute aliases

TODO TB

##### Writing a TANGO client using TANGO C++ APIs

audience:developers lang:c++

###### Introduction

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

This chapter describes how to use these API’s. It is not a reference guide. Reference documentation is available as Web pages in the TANGO home page

###### Getting Started

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

###### Basic Philosophy

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

###### Data types

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

Tango type name | C++ equivalent type
--- | ---
DevBoolean | `boolean`
DevShort | `short`
DevEnum | enumeration (only for attribute / See chapter on advanced features)
DevLong | `int` (always 32 bits data)
DevLong64 | `long long` on 32 bits chip or `long` on 64 bits chip (always 64 bits data)
DevFloat | `float`
DevDouble | `double`
DevString | `char \*`
DevEncoded | `structure` with 2 fields: a string and an array of unsigned char
DevUChar | `unsigned char`
DevUShort | `unsigned short`
DevULong | `unsigned int` (always 32 bits data)
DevULong64 | `unsigned long long` on 32 bits chip or `unsigned long` on 64 bits chip (always 64 bits data)
DevState | Tango specific data type

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

###### Request model

For the most important API remote calls (command_inout, read_attribute(s) and write_attribute(s)), Tango supports two kind of requests which are the synchronous model and the asynchronous model. Synchronous model means that the client wait (and is blocked) for the server to send an answer. Asynchronous model means that the client does not wait for the server to send an answer. The client sends the request and immediately returns allowing the CPU to do anything else (like updating a graphical user interface). Device pipe supports only the synchronous model. Within Tango, there are two ways to retrieve the server answer when using asynchronous model. They are:

1. The polling mode

2. The callback mode

In polling mode, the client executes a specific call to check if the answer is arrived. If this is not the case, an exception is thrown. If the reply is there, it is returned to the caller and if the reply was an exception, it is re-thrown. There are two calls to check if the reply is arrived:

- Call which does not wait before the server answer is returned to the caller.

- Call which wait with timeout before returning the server answer to the caller (or throw the exception) if the answer is not arrived.

In callback model, the caller must supply a callback method which will be executed when the command returns. They are two sub-modes:

1. The pull callback mode

2. The push callback mode

In the pull callback mode, the callback is triggered if the server answer is arrived when the client decide it by calling a synchronization method (The client pull-out the answer). In push mode, the callback is executed as soon as the reply arrives in a separate thread (The server pushes the answer to the client).

###### Synchronous model

Synchronous access to Tango device are provided using the DeviceProxy or AttributeProxy class. For the DeviceProxy class, the main synchronous call methods are :

- command_inout() to execute a Tango device command

- read_attribute() or read_attributes() to read a Tango device attribute(s)

- write_attribute() or write_attributes() to write a Tango device attribute(s)

- write_read_attribute() or write_read_attributes() to write then read Tango device attribute(s)

- read_pipe() to read a Tango device pipe

- write_pipe() to write a Tango device pipe

- write_read_pipe() to write then read Tango device pipe

For commands, data are sent/received to/from device using the DeviceData class. For attributes, data are sent/received to/from device attribute using the DeviceAttribute class. For pipes, data are sent/receive to/from device pipe using the DevicePipe and DevicePipeBlob classes.

In some cases, only attributes provided by a Tango device are interesting for the application. You can use the AttributeProxy class. Its main synchronous methods are :

- read() to read the attribute value

- write() to write the attribute value

- write_read() to write then read the attribute value

Data are transmitted using the DeviceAttribute class.

###### Asynchronous model

Asynchronous access to Tango device are provided using DeviceProxy or AttributeProxy, CallBack and ApiUtil classes methods. The main asynchronous call methods and used classes are :

- To execute a command on a device

  - DeviceProxy::command_inout_asynch() and DeviceProxy::command_inout_reply() in polling model.

  - DeviceProxy::command_inout_asynch(), DeviceProxy::get_asynch_replies() and CallBack class in callback pull model

  - DeviceProxy::command_inout_asynch(), ApiUtil::set_asynch_cb_sub_model() and CallBack class in callback push model

- To read a device attribute

  - DeviceProxy::read_attribute_asynch() and DeviceProxy::read_attribute_reply() in polling model

  - DeviceProxy::read_attribute_asynch(), DeviceProxy::get_asynch_replies() and CallBack class in callback pull model.

  - DeviceProxy::read_attribute_asynch(), ApiUtil::set_asynch_cb_sub_model() and CallBack class in callback push model

- To write a device attribute

  - DeviceProxy::write_attribute_asynch() in polling model

  - DeviceProxy::write_attribute_asynch() and CallBack class in callback pull model

  - DeviceProxy::write_attribute_asynch(), ApiUtil::set_asynch_cb_sub_model() and CallBack class in callback push model

For commands, data are send/received to/from device using the DeviceData class. For attributes, data are send/received to/from device attribute using the DeviceAttribute class. It is also possible to generate asynchronous request(s) using the AttributeProxy class following the same schema than above. Methods to use are :

- read_asynch() and read_reply() to asynchronously read the attribute value

- write_asynch() and write_reply() to asynchronously write the attribute value

###### Events

###### Introduction

For an introduction to events, see Events.

###### Application Programmer’s Interface

How to set up and use the TANGO events ? The interfaces described here are intended as user-friendly interfaces to the underlying CORBA calls. The interface is modeled after the asynchronous command_inout() interface to maintain coherency. The event system supports push callback model as well as the pull callback model.

The two event reception modes are:

- Push callback model : On event reception a callback method gets immediately executed.

- Pull callback model : The event will be buffered in the client until the client is ready to receive the event data. The client triggers the execution of the callback method.

The event reception buffer in the pull callback model, is implemented as a round-robin buffer. The client can choose the size when subscribing for the event. This way the client can set up different ways to receive events.

- Event reception buffer size = 1 : The client is interested only in the value of the last event received. All other events that have been received since the last reading are discarded.

- Event reception buffer size > 1 : The client has chosen to keep an event history of a given size. When more events arrive since the last reading, older events will be discarded.

- Event reception buffer size = ALL_EVENTS : The client buffers all received events. The buffer size is unlimited and only restricted by the available memory for the client.

###### Configuring events

Please refer to the Event explanation part for more details on how to configure the change, periodic and archive events.

###### C++ Clients

This is the interface for clients who want to receive events. The main action of the client is to subscribe and unsubscribe to events. Once the client has subscribed to one or more events the events are received in a separate thread by the client.

Two reception modes are possible:

- On event reception a callbacks method gets immediately executed.

- The event will be buffered until the client until the client is ready to receive the event data.

The mode to be used has to be chosen when subscribing for the event.

###### Subscribing to events

The client call to subscribe to an event is named DeviceProxy::subscribe_event() . During the event subscription the client has to choose the event reception mode to use.

Push model:

    1int DeviceProxy::subscribe_event(
    2             const string &attribute,
    3             Tango::EventType event,
    4             Tango::CallBack *callback,
    5             bool stateless = false);

The client implements a callback method which is triggered when the event is received. Note that this callback method will be executed by a thread started by the underlying ORB. This thread is not the application main thread.

Pull model:

    1int DeviceProxy::subscribe_event(
    2             const string &attribute,
    3             Tango::EventType event,
    4             int event_queue_size,
    5             bool stateless = false);

The client chooses the size of the round robin event reception buffer. Arriving events will be buffered until the client uses DeviceProxy::get_events() to extract the event data. For Tango releases before 8, a similar call with one extra parameter for event filtering is also available.

On top of the user filter defined by the filters parameter, basic filtering is done based on the reason specified and the event type. For example when reading the state and the reason specified is change the event will be fired only when the state changes. Events consist of an attribute name and the event reason. A standard set of reasons are implemented by the system, additional device specific reasons can be implemented by device servers programmers.

The stateless flag = false indicates that the event subscription will only succeed when the given attribute is known and available in the Tango system. Setting stateless = true will make the subscription succeed, even if an attribute of this name was never known. The real event subscription will happen when the given attribute will be available in the Tango system.

Note that in this model, the callback method will be executed by the thread doing the DeviceProxy::get_events() call.

###### The CallBack class

In C++, the client has to implement a class inheriting from the Tango CallBack class and pass this to the DeviceProxy::subscribe_event() method. The CallBack class is the same class as the one proposed for the TANGO asynchronous call. This is as follows for events :

     1class MyCallback : public Tango::CallBack
     2{
     3   .
     4   .
     5   .
     6   public:
     7   void push_event(Tango::EventData *);
     8   void push_event(Tango::AttrConfEventData *);
     9   void push_event(Tango::DataReadyEventData *);
    10   void push_event(Tango::DevIntrChangeEventData *);
    11   void push_event(Tango::PipeEventData *);
    12}

where EventData is defined as follows :

    1class EventData
    2{
    3   DeviceProxy       *device;
    4   string            attr_name;
    5   string            event;
    6   DeviceAttribute   *attr_value;
    7   bool              err;
    8   DevErrorList      errors;
    9}

AttrConfEventData is defined as follows :

    1class AttrConfEventData
    2{
    3   DeviceProxy       *device;
    4   string            attr_name;
    5   string            event;
    6   AttributeInfoEx   *attr_conf;
    7   bool              err;
    8   DevErrorList      errors;
    9}

DataReadyEventData is defined as follows :

     1class DataReadyEventData
     2{
     3   DeviceProxy       *device;
     4   string            attr_name;
     5   string            event;
     6   int               attr_data_type;
     7   int               ctr;
     8   bool              err;
     9   DevErrorList      errors;
    10}

DevIntrChangeEventData is defined as follows :

     1class DevIntrChangeEventData
     2{
     3   DeviceProxy            device;
     4   string                 event;
     5   string                 device_name;
     6   CommandInfoList        cmd_list;
     7   AttributeInfoListEx    att_list;
     8   bool                   dev_started;
     9   bool                   err;
    10   DevErrorList           errors;
    11}

and PipeEventData is defined as follows :

    1class PipeEventData
    2{
    3   DeviceProxy       *device;
    4   string            pipe_name;
    5   string            event;
    6   DevicePipe        *pipe_value;
    7   bool              err;
    8   DevErrorList      errors;
    9}

In push model, there are some cases (same callback used for events coming from different devices hosted in device server process running on different hosts) where the callback method could be executed concurently by different threads started by the ORB. The user has to code his callback method in a thread safe manner.

###### Unsubscribing from an event

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

    1void DeviceProxy::unsubscribe_event(int event_id);

###### Extract buffered event data

When the pull model was chosen during the event subscription, the received event data can be extracted with DeviceProxy::get_events(). Two possibilities are available for data extraction. Either a callback method can be executed for every event in the buffer when using

    1int DeviceProxy::get_events(
    2             int event_id,
    3             CallBack *cb);

Or all the event data can be directly extracted as EventDataList, AttrConfEventDataList , DataReadyEventDataList, DevIntrChangeEventDataList or PipeEventDataList when using

     1int DeviceProxy::get_events(
     2             int event_id,
     3             EventDataList &event_list);
     4
     5int DeviceProxy::get_events(
     6             int event_id,
     7             AttrConfEventDataList &event_list);
     8
     9int DeviceProxy::get_events(
    10             int event_id,
    11             DataReadyEventDataList &event_list);
    12
    13int DeviceProxy::get_events(
    14             int event_id,
    15             DevIntrChangeEventDataList &event_list);
    16
    17int DeviceProxy::get_events(
    18             int event_id,
    19             PipeEventDataList &event_list);

The event data lists are vectors of EventData, AttrConfEventData, DataReadyEventData or PipeEventData pointers with special destructor and clean-up methods to ease the memory handling.

    1class EventDataList:public vector<EventData *>
    2class AttrConfEventDataList:public vector<AttrConfEventData *>
    3class DataReadyEventDataList:public vector<DataReadyEventData *>
    4class DevIntrChangeEventDataList:public vector<DevIntrChangeEventData *>
    5class PipeEventDataList:public vector<PipeEventData *>

###### Example

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

     1class DoubleEventCallBack : public Tango::CallBack
     2{
     3   void push_event(Tango::EventData*);
     4};
     5
     6
     7void DoubleEventCallBack::push_event(Tango::EventData *myevent)
     8{
     9    Tango::DevVarDoubleArray *double_value;
    10    try
    11    {
    12        cout << "DoubleEventCallBack::push_event(): called attribute "
    13             << myevent->attr_name
    14             << " event "
    15             << myevent->event
    16             << " (err="
    17             << myevent->err
    18             << ")" << endl;
    19
    20
    21         if (!myevent->err)
    22         {
    23             *(myevent->attr_value) >> double_value;
    24             cout << "double value "
    25                  << (*double_value)[0]
    26                  << endl;
    27             delete double_value;
    28         }
    29    }
    30    catch (...)
    31    {
    32         cout << "DoubleEventCallBack::push_event(): could not extract data !\n";
    33    }
    34}

Note

If the event is an error event (myevent->err == true), the attr_value field in myevent EventData object will be empty (null pointer). The same applies for some other kinds of events. For example, attr_conf field in AttrConfEventData object will be a null pointer in case of error event (if the err field is true). As a consequence, the device server programmer should always check the err field before trying to extract the EventData::attr_value or AttrConfEventData::attr_conf fields associated to the event.

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

Push model:

     1DoubleEventCallBack *double_callback = new DoubleEventCallBack;
     2
     3Tango::DeviceProxy *mydevice = new Tango::DeviceProxy("my/device/1");
     4
     5int event_id;
     6const string attr_name("current");
     7event_id = mydevice->subscribe_event(attr_name,
     8                         Tango::CHANGE_EVENT,
     9                         double_callback);
    10cout << "event_client() id = " << event_id << endl;
    11
    12// The callback methods are executed by the Tango event reception thread.
    13// The main thread is not concerned of event reception.
    14// Whatch out with synchronisation and data access in a multi threaded environment!
    15
    16sleep(1000); // wait for events
    17
    18mydevice->unsubscribe_event(event_id);

Pull model:

     1DoubleEventCallBack *double_callback = new DoubleEventCallBack;
     2int event_queue_size = 100; // keep the last 100 events
     3
     4Tango::DeviceProxy *mydevice = new Tango::DeviceProxy("my/device/1");
     5
     6int event_id;
     7const string attr_name("current");
     8event_id = mydevice->subscribe_event(attr_name,
     9                         Tango::CHANGE_EVENT,
    10                         event_queue_size);
    11cout << "event_client() id = " << event_id << endl;
    12
    13// Check every 3 seconds whether new events have arrived and trigger the callback method
    14// for the new events.
    15
    16for (int i=0; i < 100; i++)
    17{
    18    sleep (3);
    19
    20    // Read the stored event data from the queue and call the callback method for every event.
    21    mydevice->get_events(event_id, double_callback);
    22}
    23
    24event_test->unsubscribe_event(event_id);

###### Group

A Tango Group provides the user with a single point of control for a collection of devices. By analogy, one could see a Tango Group as a proxy for a collection of devices. For instance, the Tango Group API supplies a command_inout() method to execute the same command on all the elements of a group.

A Tango Group is also a hierarchical object. In other words, it is possible to build a group of both groups and individual devices. This feature allows creating logical views of the control system - each view representing a hierarchical family of devices or a sub-system.

In this chapter, we will use the term hierarchy to refer to a group and its sub-groups. The term Group designates to the local set of devices attached to a specific Group.

###### Getting started with Tango group

The quickest way of getting started is to study an example…

Imagine we are vacuum engineers who need to monitor and control hundreds of gauges distributed over the 16 cells of a large-scale instrument. Each cell contains several penning and pirani gauges. It also contains one strange gauge. Our main requirement is to be able to control the whole set of gauges, a family of gauges located into a particular cell (e.g. all the penning gauges of the 6th cell) or a single gauge (e.g. the strange gauge of the 7th cell). Using a Tango Group, such features are quite straightforward to obtain.

Reading the description of the problem, the device hierarchy becomes obvious. Our gauges group will have the following structure:

     1-> gauges
     2  |  -> cell-01
     3  |     |-> inst-c01/vac-gauge/strange
     4  |     |-> penning
     5  |     |   |-> inst-c01/vac-gauge/penning-01
     6  |     |   |-> inst-c01/vac-gauge/penning-02
     7  |     |   |- ...
     8  |     |   |-> inst-c01/vac-gauge/penning-xx
     9  |     |-> pirani
    10  |         |-> inst-c01/vac-gauge/pirani-01
    11  |         |-> ...
    12  |         |-> inst-c01/vac-gauge/pirani-xx
    13  |  -> cell-02
    14  |     |-> inst-c02/vac-gauge/strange
    15  |     |-> penning
    16  |     |   |-> inst-c02/vac-gauge/penning-01
    17  |     |   |-> ...
    18  |     |
    19  |     |-> pirani
    20  |     |   |-> ...
    21  |  -> cell-03
    22  |     |-> ...
    23  |         | -> ...

In the C++, such a hierarchy can be build as follows (basic version):

     1//- step0: create the root group
     2Tango::Group *gauges = new Tango::Group("gauges");
     3
     4
     5//- step1: create a group for the n-th cell
     6Tango::Group *cell = new Tango::Group("cell-01");
     7
     8
     9//- step2: make the cell a sub-group of the root group
    10gauges->add(cell);
    11
    12
    13//- step3: create a "penning" group
    14Tango::Group *gauge_family = new Tango::Group("penning");
    15
    16
    17//- step4: add all penning gauges located into the cell (note the wildcard)
    18gauge_family->add("inst-c01/vac-gauge/penning*");
    19
    20
    21//- step5: add the penning gauges to the cell
    22cell->add(gauge_family);
    23
    24
    25//- step6: create a "pirani" group
    26gauge_family = new Tango::Group("pirani");
    27
    28
    29//- step7: add all pirani gauges located into the cell (note the wildcard)
    30gauge_family->add("inst-c01/vac-gauge/pirani*");
    31
    32
    33//- step8: add the pirani gauges to the cell
    34cell->add(gauge_family);
    35
    36
    37//- step9: add the "strange" gauge to the cell
    38cell->add("inst-c01/vac-gauge/strange");
    39
    40
    41//- repeat step 1 to 9 for the remaining cells
    42cell = new Tango::Group("cell-02");
    43...

Important note: There is no particular order to create the hierarchy. However, the insertion order of the devices is conserved throughout the lifecycle of the Group and cannot be changed. That way, the Group implementation can guarantee the order in which results are returned (see below).

Keeping a reference to the root group is enough to manage the whole hierarchy (i.e. there no need to keep trace of the sub-groups or individual devices). The Group interface provides methods to retrieve a sub-group or an individual device.

Be aware that a C++ group allways gets the ownership of its children and deletes them when it is itself deleted. Therefore, never try to delete a Group (respectively a DeviceProxy) returned by a call to Tango::Group::get_group() (respectively to Tango::Group::get_device()). Use the Tango::Group::remove() method instead (see the Tango Group class API documentation for details).

We can now perform any action on any element of our gauges group. For instance, let’s ping the whole hierarchy to be sure that all devices are alive.

    1//- ping the whole hierarchy
    2if (gauges->ping() == true)
    3{
    4    std::cout << "all devices alive" << std::endl;
    5}
    6else
    7{
    8    std::cout << "at least one dead/busy/locked/... device" << std::endl;
    9}

###### Enabling and disabling group members

Devices belonging to a group can be temporarily excluded from all operations performed on the group using the Group::disable and Group::enable calls.

Device name passed to the disable (enable) methods can contain wildcards (`*`). Note that only the first matching device will be disabled (enabled). The search algorithm is breadth-first search. All group elements are searched (in the insertion order) for a match before descending recursively to sub-groups. Recursive search can be disabled with the forward flag (see a section dedicated to the forwarding ).

During group operations like attribute read or command calls, entries for disabled elements will be included in the result set, however they will not have any value and will be marked as disabled (GroupReply::group_element_enabled will be false).

Note that if exceptions are enabled , any attempt to access the result (e.g. via get_data()) from a disabled device will raise `Tango::DevFailed`. Otherwise an empty value will be returned.

Below is an example using the gauges group:

     1// will disable: inst-c01/vac-gauge/penning-01
     2gauges->disable("inst-c01/*/penn");
     3
     4// will disable nothing
     5const bool forwarded = true;
     6gauges->disable("inst-c01/vac-gauge/pirani-01", not forwarded);
     7
     8// will disable: inst-c01/vac-gauge/pirani-01
     9gauge_family->disable("inst-c01/vac-gauge/pirani-01");
    10
    11// will enable: inst-c01/vac-gauge/penning-01
    12gauges->enable("inst-c01/*");
    13
    14auto states = gauges->command_inout("State");
    15for (auto& state : states)
    16{
    17    if (state.group_element_enabled())
    18    {
    19        // it's safe to access the value
    20        std::cout << state.dev_name() << ": " << state.get_data() << "\n";
    21    }
    22    else
    23    {
    24        std::cout << state.dev_name() << ": is disabled\n";
    25    }
    26}

###### Forward or not forward?

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

###### Executing a command

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

###### Obtaining command results

Command results are returned using a Tango::GroupCmdReplyList. This is nothing but a vector containing a Tango::GroupCmdReply for each device in the group. The Tango::GroupCmdReply contains the actual data (i.e. the Tango::DeviceData). By inheritance, it may also contain any error occurred during the execution of the command (in which case the data is invalid).

We previously indicated that the Tango Group implementation guarantees that the command results are returned in the order in which its elements were attached to the group. For instance, if g1 is a group containing three devices attached in the following order:

    1g1->add("my/device/01");
    2g1->add("my/device/03");
    3g1->add("my/device/02");

the results of

    1Tango::GroupCmdReplyList crl = g1->command_inout("Status");

will be organized as follows:

crl[0]

contains the status of my/device/01

crl[1]

contains the status of my/device/03

crl[2]

contains the status of my/device/02

Things get more complicated if sub-groups are added between devices.

     1g2->add("my/device/04");
     2g2->add("my/device/05");
     3
     4
     5g4->add("my/device/08");
     6g4->add("my/device/09");
     7
     8
     9g3->add("my/device/06");
    10g3->add(g4);
    11g3->add("my/device/07");
    12
    13
    14g1->add("my/device/01");
    15g1->add(g2);
    16g1->add("my/device/03");
    17g1->add(g3);
    18g1->add("my/device/02");

The result order in the Tango::GroupCmdReplyList depends on the value of the forward option. If set to true, the results will be organized as follows:

    1Tango::GroupCmdReplyList crl = g1->command_inout("Status", true);

crl[0]

contains the status of my/device/01 which belongs to g1

crl[1]

contains the status of my/device/04 which belongs to g1.g2

crl[2]

contains the status of my/device/05 which belongs to g1.g2

crl[3]

contains the status of my/device/03 which belongs to g1

crl[4]

contains the status of my/device/06 which belongs to g1.g3

crl[5]

contains the status of my/device/08 which belongs to g1.g3.g4

crl[6]

contains the status of my/device/09 which belongs to g1.g3.g

crl[7]

contains the status of my/device/07 which belongs to g1.g3

crl[8]

contains the status of my/device/02 which belongs to g1

If the forward option is set to false, the results are:

    1Tango::GroupCmdReplyList crl = g1->command_inout("Status", false);

crl[0]

contains the status of my/device/01 which belongs to g

crl[1]

contains the status of my/device/03 which belongs to g1

crl[2]

contains the status of my/device/02 which belongs to g1

The Tango::GroupCmdReply contains some public members allowing the identification of both the device (Tango::GroupCmdReply::dev_name) and the command (Tango::GroupCmdReply::obj_name). It means that, depending of your application, you can associate a response with its source using its position in the response list or using the Tango::GroupCmdReply::dev_name member.

###### Case 1: a command, no argument

As an example, we execute the Status command on the whole hierarchy synchronously.

    1Tango::GroupCmdReplyList crl = gauges->command_inout("Status");

As a first step in the results processing, it could be interesting to check value returned by the has_failed() method of the GroupCmdReplyList. If it is set to true, it means that at least one error occurred during the execution of the command (i.e. at least one device gave error).

    1if (crl.has_failed())
    2{
    3    cout << "at least one error occurred" << endl;
    4}
    5else
    6{
    7    cout << "no error " << endl;
    8}

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

###### A few words on error handling and data extraction

Depending of the application and/or the developer’s programming habits, each individual error can be handle by the C++ (or Java) exception mechanism or using the dedicated has_failed() method. The GroupReply class - which is the mother class of both GroupCmdReply and GroupAttrReply - contains a static method to enable (or disable) exceptions called enable_exception(). By default, exceptions are disabled. The following example is proposed with both exceptions enable and disable.

In C++, data can be extracted directly from an individual reply. The GroupCmdReply interface contains a template operator >> allowing the extraction of any supported Tango type (in fact the actual data extraction is delegated to DeviceData::operator >>). One dedicated extract method is also provided in order to extract DevVarLongStringArray and DevVarDoubleStringArray types to std::vectors.

Error and data handling C++ example:

     1//-------------------------------------------------------
     2//- synch. group command example with exception enabled
     3//-------------------------------------------------------
     4//- enable exceptions and save current mode
     5bool last_mode = GroupReply::enable_exception(true);
     6//- process each response in the list ...
     7for (int r = 0; r < crl.size(); r++)
     8{
     9//- enter a try/catch block
    10   try
    11   {
    12//- try to extract the data from the r-th reply
    13//- suppose data contains a double
    14       double ans;
    15       crl[r] >> ans;
    16       cout << crl[r].dev_name()
    17            << "::"
    18            << crl[r].obj_name()
    19            << " returned "
    20            << ans
    21            << endl;
    22    }
    23    catch (const DevFailed& df)
    24    {
    25//- DevFailed caught while trying to extract the data from reply
    26      for (int err = 0; err < df.errors.length(); err++)
    27      {
    28           cout << "error: " << df.errors[err].desc.in() << endl;
    29      }
    30//- alternatively, one can use crl[r].get_err_stack() see below
    31    }
    32    catch (...)
    33    {
    34       cout << "unknown exception caught";
    35    }
    36}
    37//- restore last exception mode (if needed)
    38GroupReply::enable_exception(last_mode);
    39//- Clear the response list (if reused later in the code)
    40crl.reset();
    41
    42
    43//-------------------------------------------------------
    44//- synch. group command example with exception disabled
    45//-------------------------------------------------------
    46//- disable exceptions and save current mode bool
    47last_mode = GroupReply::enable_exception(false);
    48//- process each response in the list ...
    49for (int r = 0; r < crl.size(); r++)
    50{
    51//- did the r-th device give error?
    52    if (crl[r].has_failed() == true)
    53    {
    54//- printout error description
    55       cout << "an error occurred while executing "
    56            << crl[r].obj_name()
    57            << " on "
    58            << crl[r].dev_name() << endl;
    59//- dump error stack
    60       const DevErrorList& el = crl[r].get_err_stack();
    61       for (int err = 0; err < el.size(); err++)
    62       {
    63           cout << el[err].desc.in();
    64       }
    65    }
    66    else
    67    {
    68//- no error (suppose data contains a double)
    69       double ans;
    70       bool result = crl[r] >> ans;
    71       if (result == false)
    72       {
    73           cout << "could not extract double from "
    74                << crl[r].dev_name()
    75                << " reply"
    76                << endl;
    77       }
    78       else
    79       {
    80           cout << crl[r].dev_name()
    81                << "::"
    82                << crl[r].obj_name()
    83                << " returned "
    84                << ans
    85                << endl;
    86       }
    87    }
    88}
    89//- restore last exception mode (if needed)
    90GroupReply::enable_exception(last_mode);
    91//- Clear the response list (if reused later in the code)
    92crl.reset();

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

     1//-------------------------------------------------------
     2//- asynch. group command example (C++ example)
     3//-------------------------------------------------------
     4long request_id = gauges->command_inout_asynch("Status");
     5//- do some work
     6do_some_work();
     7
     8
     9//- get results
    10crl = gauges->command_inout_reply(request_id);
    11//- process responses as previously describe in the synch. implementation
    12for (int r = 0; r < crl.size(); r++)
    13{
    14//- data processing and error handling goes here
    15//- copy/paste code from previous example
    16. . .
    17}
    18//- clear the response list (if reused later in the code)
    19crl.reset();

###### Case 2: a command, one argument

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

In C++:

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

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

In C++ we should have something like:

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

See case 1 for an example of asynchronous command.

###### Case 3: a command, several arguments

Here, we give an example in which a specific input argument is applied to each device in the hierarchy. In order to use this form of command_inout, the user must have an a priori and perfect knowledge of the devices order in the hierarchy. In such a case, command arguments are passed in an array (with one entry for each device in the hierarchy).

The C++ implementation provides a template method which accepts a std::vector of C++ type for command argument. This allows passing any kind of data using a single method.

The size of this vector must equal the number of device in the hierarchy (respectively the number of device in the group) if the forward option is set to true (respectively set to false). Otherwise, an exception is thrown.

The first item in the vector is applied to the first device in the hierarchy, the second to the second device in the hierarchy, and so on…That’s why the user must have a perfect knowledge of the devices order in the hierarchy.

Assuming that gauges are ordered by name, the SetDummyFactor command can be executed on group cell-01 (and its sub-groups) as follows:

Remember, cell-01 has the following internal structure:

     1-> gauges
     2   | -> cell-01
     3   |    |-> inst-c01/vac-gauge/strange
     4   |    |-> penning
     5   |    |   |-> inst-c01/vac-gauge/penning-01
     6   |    |   |-> inst-c01/vac-gauge/penning-02
     7   |    |   |-> ...
     8   |    |   |-> inst-c01/vac-gauge/penning-xx
     9   |    |-> pirani
    10   |        |-> inst-c01/vac-gauge/pirani-01
    11   |        |-> ...
    12   |        |-> inst-c01/vac-gauge/pirani-xx

Passing a specific argument to each device in C++:

     1//- get a reference to the target group
     2Tango::Group *g = gauges->get_group("cell-01");
     3//- get number of device in the hierarchy (starting at cell-01)
     4long n_dev = g->get_size(true);
     5//- Build argin list
     6std::vector<double> argins(n_dev);
     7//- argument for inst-c01/vac-gauge/strange
     8argins[0] = 0.0;
     9//- argument for inst-c01/vac-gauge/penning-01
    10argins[1] = 0.1;
    11//- argument for inst-c01/vac-gauge/penning-02
    12argins[2] = 0.2;
    13//- argument for remaining devices in cell-01.penning
    14. . .
    15//- argument for devices in cell-01.pirani
    16. . .
    17//- the reply list
    18Tango::GroupCmdReplyList crl;
    19//- enter a try/catch block (see below)
    20try
    21{
    22//- execute the command
    23    crl = g->command_inout("SetDummyFactor", argins, true);
    24    if (crl.has_failed())
    25    {
    26//- error handling goes here (see case 1)
    27    }
    28}
    29catch (const DevFailed& df)
    30{
    31//- see below
    32}
    33crl.reset();

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

     1//- get a reference to the target group
     2Tango::Group *g = gauges->get_group("cell-01");
     3//- get number of device in the group (starting at cell-01)
     4long n_dev = g->get_size(false);
     5//- Build argin list
     6std::vector<double> argins(n_dev);
     7//- argins for inst-c01/vac-gauge/penning-01
     8argins[0] = 0.1;
     9//- argins for inst-c01/vac-gauge/penning-02
    10argins[1] = 0.2;
    11//- argins for remaining devices in cell-01.penning
    12. . .
    13//- the reply list
    14Tango::GroupCmdReplyList crl;
    15//- enter a try/catch block (see below)
    16try
    17{
    18//- execute the command
    19    crl = g->command_inout("SetDummyFactor", argins, false);
    20    if (crl.has_failed())
    21    {
    22//- error handling goes here (see case 1)
    23    }
    24}
    25catch (const DevFailed& df)
    26{
    27//- see below
    28}
    29crl.reset();

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

     1//- get a reference to the target group
     2Group g = gauges.get_group("cell-01");
     3//- get pre-build arguments list for the group (starting@cell-01)
     4DeviceData[] argins = g.get_command_specific_argument_list(false);
     5//- argins for inst-c01/vac-gauge/penning-01
     6argins[0].insert(0.1);
     7//- argins for inst-c01/vac-gauge/penning-02
     8argins[1].insert(0.2);
     9//- argins for remaining devices in cell-01.penning
    10. . .
    11//- the reply list
    12GroupCmdReplyList crl;
    13//- enter a try/catch block (see below)
    14try
    15{
    16//- execute the command
    17    crl = g.command_inout("SetDummyFactor", argins, false, false);
    18    if (crl.has_failed())
    19    {
    20//- error handling goes here (see case 1)
    21    }
    22}
    23catch (DevFailed d)
    24{
    25//- see below
    26}

This form of command_inout (the one that accepts an array of value as its input argument), may throw an exception before executing the command if the number of elements in the input array does not match the number of individual devices in the group or in the hierarchy (depending on the forward option).

An asynchronous version of this method is also available. See case 1 for an example of asynchronous command.

###### Reading attribute(s)

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

###### Obtaining attribute values

Attribute values are returned using a GroupAttrReplyList. This is nothing but an array containing a GroupAttrReply for each device in the group. The GroupAttrReply contains the actual data (i.e. the DeviceAttribute). By inheritance, it may also contain any error occurred during the execution of the command (in which case the data is invalid).

Here again, the Tango Group implementation guarantees that the attribute values are returned in the order in which its elements were attached to the group. See Obtaining command results for details.

The GroupAttrReply contains some public methods allowing the identification of both the device (GroupAttrReply::dev_name) and the attribute (GroupAttrReply::obj_name). It means that, depending of your application, you can associate a response with its source using its position in the response list or using the Tango::GroupAttrReply::dev_name member.

###### A few words on error handling and data extraction

Here again, depending of the application and/or the developer’s programming habits, each individual error can be handle by the C++ exception mechanism or using the dedicated has_failed() method. The GroupReply class - which is the mother class of both GroupCmdReply and GroupAttrReply - contains a static method to enable (or disable) exceptions called enable_exception(). By default, exceptions are disabled. The following example is proposed with both exceptions enable and disable.

In C++, data can be extracted directly from an individual reply. The GroupAttrReply interface contains a template operator>> allowing the extraction of any supported Tango type (in fact the actual data extraction is delegated to DeviceAttribute::operator>>).

Reading an attribute is very similar to executing a command.

Reading an attribute in C++:

     1//-----------------------------------------------------------------
     2//- synch. read "vacuum" attribute on each device in the hierarchy
     3//- with exceptions enabled - C++ example
     4//-----------------------------------------------------------------
     5//- enable exceptions and save current mode
     6bool last_mode = GroupReply::enable_exception(true);
     7//- read attribute
     8Tango::GroupAttrReplyList arl = gauges->read_attribute("vacuum");
     9//- for each response in the list ...
    10for (int r = 0; r < arl.size(); r++)
    11{
    12//- enter a try/catch block
    13   try
    14   {
    15//- try to extract the data from the r-th reply
    16//- suppose data contains a double
    17      double ans;
    18      arl[r] >> ans;
    19      cout << arl[r].dev_name()
    20           << "::"
    21           << arl[r].obj_name()
    22           << " value is "
    23           << ans << endl;
    24   }
    25   catch (const DevFailed& df)
    26   {
    27//- DevFailed caught while trying to extract the data from reply
    28      for (int err = 0; err < df.errors.length(); err++)
    29      {
    30         cout << "error: " << df.errors[err].desc.in() << endl;
    31      }
    32//- alternatively, one can use arl[r].get_err_stack() see below
    33   }
    34   catch (...)
    35   {
    36      cout << "unknown exception caught";
    37   }
    38}
    39//- restore last exception mode (if needed)
    40GroupReply::enable_exception(last_mode);
    41//- clear the reply list (if reused later in the code)
    42arl.reset();

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

     1//- read the attribute asynchronously
     2long request_id = gauges->read_attribute_asynch("vacuum");
     3//- do some work
     4do_some_work();
     5
     6
     7//- get results
     8Tango::GroupAttrReplyList arl = gauges->read_attribute_reply(request_id);
     9//- process replies as previously described in the synch. implementation
    10for (int r = 0; r < arl.size(); r++)
    11{
    12//- data processing and/or error handling goes here
    13...
    14}
    15//- clear the reply list (if reused later in the code)
    16arl.reset();

###### Writing an attribute

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

###### Obtaining acknowledgement

Acknowledgements are returned using a GroupReplyList. This is nothing but an array containing a GroupReply for each device in the group. The GroupReply may contain any error occurred during the execution of the command. The return value of the has_failed() method indicates whether an error occurred or not. If this flag is set to true, the GroupReply::get_err_stack() method gives error details.

Here again, the Tango Group implementation guarantees that the attribute values are returned in the order in which its elements were attached to the group. See Obtaining command results for details.

The GroupReply contains some public members allowing the identification of both the device (GroupReply::dev_name) and the attribute (GroupReply::obj_name). It means that, depending of your application, you can associate a response with its source using its position in the response list or using

###### Case 1: one value for all devices

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

Writing an attribute in C++:

     1//-----------------------------------------------------------------
     2//- synch. write "dummy" attribute on each device in the hierarchy
     3//-----------------------------------------------------------------
     4//- assume each device support a "dummy" writable attribute
     5//- insert the value to be written into a generic container
     6Tango::DeviceAttribute value(std::string("dummy"), 3.14159);
     7//- write the attribute
     8Tango::GroupReplyList rl = gauges->write_attribute(value);
     9//- any error?
    10if (rl.has_failed() == false)
    11{
    12    cout << "no error" << endl;
    13}
    14else
    15{
    16    cout << "at least one error occurred" << endl;
    17//- for each response in the list ...
    18    for (int r = 0; r < rl.size(); r++)
    19    {
    20//- did the r-th device give error?
    21       if (rl[r].has_failed() == true)
    22       {
    23//- printout error description
    24           cout << "an error occurred while reading "
    25                << rl[r].obj_name()
    26                << " on "
    27                << rl[r].dev_name()
    28                << endl;
    29//- dump error stack
    30           const DevErrorList& el = rl[r].get_err_stack();
    31           for (int err = 0; err < el.size(); err++)
    32           {
    33              cout << el[err].desc.in();
    34           }
    35        }
    36     }
    37}
    38//- clear the reply list (if reused later in the code)
    39rl.reset();

Here is a C++ asynchronous version:

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

###### Case 2: a specific value per device

Here, we give an example in which a specific attribute value is applied to each device in the hierarchy. In order to use this form of write_attribute(), the user must have an a priori and perfect knowledge of the devices order in the hierarchy.

The C++ implementation provides a template method which accepts a std::vector of C++ type for command argument. This allows passing any kind of data using a single method.

The size of this vector must equal the number of device in the hierarchy (respectively the number of device in the group) if the forward option is set to true (respectively set to false). Otherwise, an exception is thrown.

The first item in the vector is applied to the first device in the group, the second to the second device in the group, and so on…That’s why the user must have a perfect knowledge of the devices order in the group.

Assuming that gauges are ordered by name, the dummy attribute can be written as follows on group cell-01 (and its sub-groups) as follows:

Remember, cell-01 has the following internal structure:

     1-> gauges
     2    | -> cell-01
     3    |     |-> inst-c01/vac-gauge/strange
     4    |     |-> penning
     5    |     |    |-> inst-c01/vac-gauge/penning-01
     6    |     |    |-> inst-c01/vac-gauge/penning-02
     7    |     |    |-> ...
     8    |     |    |-> inst-c01/vac-gauge/penning-xx
     9    |     |-> pirani
    10    |          |-> inst-c01/vac-gauge/pirani-01
    11    |          |-> ...
    12    |          |-> inst-c01/vac-gauge/pirani-xx

C++ version:

     1//- get a reference to the target group
     2Tango::Group *g = gauges->get_group("cell-01");
     3//- get number of device in the hierarchy (starting at cell-01)
     4long n_dev = g->get_size(true);
     5//- Build value list
     6std::vector<double> values(n_dev);
     7//- value for inst-c01/vac-gauge/strange
     8values[0] = 3.14159;
     9//- value for inst-c01/vac-gauge/penning-01
    10values[1] = 2 * 3.14159;
    11//- value for inst-c01/vac-gauge/penning-02
    12values[2] = 3 * 3.14159;
    13//- value for remaining devices in cell-01.penning
    14. . .
    15//- value for devices in cell-01.pirani
    16. . .
    17//- the reply list
    18Tango::GroupReplyList rl;
    19//- enter a try/catch block (see below)
    20try
    21{
    22//- write the "dummy" attribute
    23    rl = g->write_attribute("dummy", values, true);
    24    if (rl.has_failed())
    25    {
    26//- error handling (see previous cases)
    27    }
    28}
    29catch (const DevFailed& df)
    30{
    31//- see below
    32}
    33rl.reset();

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

     1//- get a reference to the target group
     2Tango::Group *g = gauges->get_group("cell-01");
     3//- get number of device in the group
     4long n_dev = g->get_size(false);
     5//- Build value list
     6std::vector<double> values(n_dev);
     7//- value for inst-c01/vac-gauge/penning-01
     8values[0] = 2 * 3.14159;
     9//- value for inst-c01/vac-gauge/penning-02
    10values[1] = 3 * 3.14159;
    11//- value for remaining devices in cell-01.penning
    12. . .
    13//- the reply list
    14Tango::GroupReplyList rl;
    15//- enter a try/catch block (see below)
    16try
    17{
    18//- write the "dummy" attribute
    19   rl = g->write_attribute("dummy", values, false);
    20   if (rl.has_failed())
    21   {
    22//- error handling (see previous cases)
    23   }
    24}
    25catch (const DevFailed& df)
    26{
    27//- see below
    28}
    29rl.reset();

This form of write_attribute() (the one that accepts an array of value as its input argument), may throw an exception before executing the command if the number of elements in the input array does not match the number of individual devices in the group or in the hierarchy (depending on the forward option).

An asynchronous version of this method is also available.

###### Reading/Writing device pipe

Reading or writing device pipe is made possible using DeviceProxy class methods. To read a pipe, you have to use the method read_pipe(). To write a pipe, use the write_pipe() method. A method write_read_pipe() is also provided in case you need to write then read a pipe in a non-interuptible way. All these calls generate synchronous request and support only reading or writing a single pipe at a time. Those pipe related DeviceProxy class methods (read_pipe, write_pipe,…) use DevicePipe class instances. A DevicePipe instance is nothing more than a string for the pipe name and a DevicePipeBlob instance called the root blob. In a DevicePipeBlob instance, you have:

- The blob name

- One array of DataElement. Each instance of this DataElement class has:

  - A name

  - A value which can be either

    - Scalar or array of any basic Tango type

    - Another DevicePipeBlob

Therefore, this is a recursive data structure and you may have DevicePipeBlob in DevicePipeBlob. There is no limit on the depth of this recursivity even if it is not recommended to have a too large depth. The following figure summarizes DevicePipe data structure

Figure 4.1: DevicePipe data structure

Many methods to insert/extract data into/from a DevicePipe are available. In the DevicePipe class, these methods simply forward their action to the DevicePipe root blob. The same methods are available in the DevicePipeBlob in case you need to use the recursivity provided by this data structure.

###### Reading a pipe

When you read a pipe, you have to extract data received from the pipe. Because data transferred through a pipe can change at any moment, two differents cases are possible:

1. The client has a prior knowledge of what should be transferred through the pipe

2. The client does not know at all what has been received through the pipe

Those two cases are detailed in the following sub-chapters.

###### Extracting data with pipe content prior knowledge

To extract data from a DevicePipe object (or from a DevicePipeBlob object), you have to use its extraction operator >>. Let’s suppose that we already know (prior knowledge) that the pipe contains 3 data elements with a Tango long, an array of double and finally an array of unsigned short. The code you need to extract these data is (Without error case treatment detailed in a next sub-chapter)

    1DevicePipe dp = mydev.read_pipe("MyPipe");
    2
    3DevLong dl;
    4vector<double> v_db;
    5DevVarUShortArray *dvush = new DevVarUShortArray();
    6
    7dp >> dl >> v_db >> dvush;
    8
    9delete dvush;

The pipe is read at line 1. Pipe (or root blob) data extracttion is at line 7. As you can see, it is just a matter of chaining extraction operator (>>) into local data (declared line 3 to 5). In this example, the transported array of double is extracted into a C++ vector while the unsigned short array is extracted in a Tango sequence data type. When you extract data into a vector, there is a unavoidable memory copy between the DevicePipe object and the vector. When you extract data in a Tango sequence data type, there is no memory copy but the extraction method consumes the memory and it is therefore caller responsability to delete the memory. This is the rule of line 9. If there is a DevicePipeBlob inside the DevicePipe, simply extract it into one instance of the DevicePipeBlob class.

You may notice that the pipe root blob data elements name are lost in the previous example. The Tango API also has a DataElement class which allows you to retrieve/set data element name. The following code is how you can extract pipe data and retrieve data element name (same pipe then previously)

    1DevicePipe dp = mydev.read_pipe("MyPipe");
    2
    3DataElement<DevLong> de_dl;
    4DataElement<vector<double> > de_v_db;
    5DataElement<DevVarUShortArray *> de_dvush(new DevVarUShortArray());
    6
    7dp >> de_dl >> de_v_db >> de_dvush;
    8
    9delete de_dvush.value;

The extraction line (number 7) is similar to the previous case but local data are instances of DataElement class. This is template class and instances are created at lines 4 to 6. Each DataElement instance has only two elements which are:

1. The data element name (a C++ string): name

2. The data element value (One instance of the template parameter): value

###### Extracting data in a generic way (without prior knowledge)

Due to the dynamicity of the data transferred through a pipe, the API alows to extract data from a pipe without any prior knowledge of its content. This is achived with methods get_data_elt_nb(), get_data_elt_type(), get_data_elt_name() and the extraction operator >>. These methods belong to the DevicePipeBlob class but they also exist on the DevicePipe class for its root blob. Here is one example of how you use them:

     1DevicePipe dp = mydev.read_pipe("MyPipe");
     2
     3size_t nb_de = dp.get_data_elt_nb();
     4for (size_t loop = 0;loop < nb_de;loop++)
     5{
     6   int data_type = dp.get_data_elt_type(loop);
     7   string de_name = dp.get_data_elt_name(loop);
     8   switch(data_type)
     9   {
    10      case DEV_LONG:
    11      {
    12          DevLong lg;
    13          dp >> lg;
    14      }
    15      break;
    16
    17      case DEVVAR_DOUBLEARRAY:
    18      {
    19          vector<double> v_db;
    20          dp >> v_db;
    21      }
    22      break;
    23      ....
    24   }
    25   ...
    26}

The number of data element in the pipe root blob is retrieve at line 3. Then a loop for each data element is coded. For each data element, its value data type and its name are retrieved at lines 6 and 7. Then, according to the data element value data type, the data are extracted using the classical extraction operator (lines 13 or 20)

###### Error management

By default, in case of error, the DevicePipe object throws different kind of exceptions according to the error kind. It is possible to disable exception throwing. If you do so, the code has to test the DevicePipe state after extraction. The possible error cases are:

- DevicePipe object is empty

- Wrong data type for extraction (For instance extraction into a double data while the DataElement contains a string)

- Wrong number of DataElement (Extraction code extract 5 data element while the pipe contains only four)

- Mix of extraction (or insertion) method kind (classical operators << or >>) and [] operator.

Methods exceptions() and reset_exceptions() of the DevicePipe and DevicePipeBlob classes allow the user to select which kind of error he is interested in. For error treatment without exceptions, methods has_failed() and state() has to be used. See reference documentation for details about these methods.

###### Writing a pipe

Writing data into a DevicePipe or a DevicePipeBlob is similar to reading data from a pipe. The main method is the insertion operator <<. Let’s have a look at a first example if you want to write a pipe with a Tango long, a vector of double and finally an array of unsigned short.

     1DevicePipe dp("MyPipe");
     2
     3vector<string> de_names {"FirstDE","SecondDE","ThirdDE"};
     4db.set_data_elt_names(de_names);
     5
     6DevLong dl = 666;
     7vector<double> v_db {1.11,2.22};
     8unsigned short *array = new unsigned short [100];
     9DevVarUShortArray *dvush = create_DevVarUShortArray(array,100);
    10
    11try
    12{
    13   dp << dl << v_db << dvush;
    14   mydev.write_pipe(dp);
    15}
    16catch (DevFailed &e)
    17{
    18   cout << "DevicePipeBlob insertion failed" << endl;
    19   ....
    20}

Insertion into the DevicePipe is done at line 12 with the insert operators. The main difference with extracting data from the pipe is at line 3 and 4. When inserting data into a pipe, you need to FIRST define its number od name of data elements. In our example, the device pipe is initialized to carry three data element and the names of these data elements is defined at line 4. This is a mandatory requirement. If you don’t define data element number, exception will be thrown during the use of insertion methods. The population of the array used for the third pipe data element is not represented here.

It’s also possible to use DataElement class instances to set the pipe data element. Here is the previous example modified to use DataElement class.

     1DevicePipe dp("MyPipe");
     2
     3DataElement<DevLong> de_dl("FirstElt",666);
     4vector<double>  v_db {1.11,2.22};
     5DataElement<vector<double> > de_v_db("SecondElt,v_db");
     6
     7unsigned short *array = new unsigned short [100];
     8DevVarUShortArray *dvush = create_DevVarUShortArray(array,100);
     9DataElement<DevVarUShortArray *> de_dvush("ThirdDE",array);
    10
    11try
    12{
    13   dp << de_dl << de_v_db << de_dvush;
    14   mydev.write_pipe(dp);
    15}
    16catch (DevFailed &e)
    17{
    18   cout << "DevicePipeBlob insertion failed" << endl;
    19   ....
    20}

The population of the array used for the third pipe data element is not represented here. Finally, there is a third way to insert data into a device pipe. You have to defined number and names of the data element within the pipe (similar to first insertion method) but you are able to insert data into the data element in any order using the operator overwritten for the DevicePipe and DevicePipeBlob classes. Look at the following example:

     1DevicePipe dp("MyPipe");
     2
     3vector<string> de_names {"FirstDE","SecondDE","ThirdDE"};
     4db.set_data_elt_names(de_names);
     5
     6DevLong dl = 666;
     7vector<double> v_db = {1.11,2.22};
     8unsigned short *array = new unsigned short [100];
     9DevVarUShortArray *dvush = create_DevVarUShortArray(array,100);
    10
    11dp["SecondDE"] << v_db;
    12dp["FirstDE"] << dl;
    13dp["ThirdDE"] << dvush;

Insertion into the device pipe is now done at lines 11 to 13. The population of the array used for the third pipe data element is not represented here. Note that the data element name is case insensitive.

###### Error management

When inserting data into a DevicePipe or a DevicePipeBlob, error management is very similar to reading data from from a DevicePipe or a DevicePipeBlob. The difference is that there is one more case which could trigger one exception during the insertion. This case is

- Insertion into the DevicePipe (or DevicePipeBlob) if its data element number have not been set.

###### Device locking

This can be used by an application doing a scan on a synchrotron beam line. In such a case, you want to move an actuator then read a sensor, move the actuator again, read the sensor…You don’t want the actuator to be moved by another client while the application is doing the scan. If the application doing the scan locks the actuator device, it will be sure that this device is reserved for the application doing the scan and other client will not be able to move it until the scan application un-locks this actuator.

A locked device is protected against:

- command_inout call except for device state and status requested via command and for the set of commands defined as allowed following the definition of allowed command in the Tango control access schema.

- write_attribute and write_pipe call

- write_read_attribute, write_read_attributes and write_read_pipe call

- set_attribute_config and set_pipe_config call

- polling and logging commands related to the locked device

Other clients trying to do one of these calls on a locked device will get a DevFailed exception. In case of application with locked device crashed, the lock will be automatically release after a defined interval. The API provides a set of methods for application code to lock/unlock device. These methods are:

- DeviceProxy::lock() and DeviceProxy::unlock() to lock/unlock device

- DeviceProxy::locking_status(), DeviceProxy::is_locked(), DeviceProxy::is_locked_by_me() and DeviceProxy::get_locker() to get locking information

These methods are precisely described in the API reference chapters.

###### Reconnection and exception

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

###### Thread safety

Starting with Tango 7.2, some classes of the C++ API has been made thread safe. These classes are:

- DeviceProxy

- Database

- Group

- ApiUtil

- AttributeProxy

This means that it is possible to share between threads a pointer to a DeviceProxy instance. It is safe to execute a call on this DeviceProxy instance while another thread is also doing a call to the same DeviceProxy instance. Obviously, this also means that it is possible to create thread local DeviceProxy instances and to execute method calls on these instances. Nevertheless, data local to a DeviceProxy instance like its timeout are not managed on a per thread basis. For a DeviceProxy instance shared between two threads, if thread 1 changes the instance timeout, thread 2 will also see this change.

###### Compiling and linking a Tango client

Compiling and linking a Tango client is similar to compiling and linking a Tango device server. Please, refer to chapter Compiling and linking a C++ device server to get all the details.

#### Getting started with JTango (Java implementation of Tango-Controls)

audience:developers lang:java

##### Developing your first Java TANGO client

##### Developing your first Java TANGO device class

audience:developers lang:java

In this section we describe how one can start developing Tango device server using Java.

Three methods will be described:

> 1. Using jtango-maven-archetype

> 2. Using POGO

> 3. Starting from scratch

Prerequisites

> - Java >1.7

> - Maven >3

> - Tango-Controls environment (Tango Database aka Tango host is deployed)

###### Using jtango-maven-archetype

Perhaps the simplest way to start to develop your first Tango device server in Java is to use jtango-maven-archetype.

Maven is an Apache project and it is widely used in Java development nowdays. More information can be found in the Internet. Here we just name main features of Maven:

First of all Maven is a build system, i.e. it automatizes the build process of the project. As Maven is a plugin platform various plugins are used to achieve the desired result e.g. define compilation target (aka javac -target 1.8) or package the project into a single executable jar.

Secondly Maven automatically manages dependencies (required versions are being automatically downloaded from so called Maven central repository from the Internet).

Finally Maven provides a way to generate skeleton projects. This section is based on this feature.

So to start execute the following command:

    $> mvn archetype:generate \
          -DarchetypeGroupId=org.tango-controls \
          -DarchetypeArtifactId=jtango-maven-archetype \
          -DarchetypeVersion=1.4

This command generates skeleton project using special Maven artifact that defines the template of the project. While generating new project you have to define several properties:

- groupId – target project’s groupId. Typically it is reversed domain name of the company e.g. com.company

- artifactId – target project’s artifactId. This is can be considered as the name of the target executable. This value must follow java class naming conventions e.g. MyDevice

- version – target project version. Simply the first version of the project e.g. 1.0-SNAPSHOT

- package – Java package for newly generated class. Typically can be left as default i.e. groupId

- license – name of the license under which the project is distributed e.g. LGPL-3, GPL, MIT etc

- organization – name of the organization that maintains the project e.g. Company

- organization-url – organization’s URL e.g. http://www.company.com

- author-name – name of the author/maintainer e.g. JoeDoe

- author-email – author/maintainer’s email e.g. joe.doe@company.com

- facility – facility at which project is being developed e.g. DESY, ESRF, etc

- platform – Windows, MacOS, Unix/Linux etc. Typically Java projects will have All in this property

- family – as in POGO. Typically Java high level projects will have SoftwareSystem in this property

- bus – bus to the device (underlying hardware) e.g. Serial. For Java this might be NA if there is no real hardware associated with this Tango server.

- jtango-version – a version of JTango dependency or LATEST if you are know what are you doing.

Latest version of JTango is

The following output indicates that project has been successfully generated:

    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------

Now you can goto to the project folder and build it:

    $>cd MyDevice
    $>mvn package
    $>java -jar target/MyDevice-1.0-SNAPSHOT.jar development

Assuming that Tango-Controls environment is set up properly (TODO ref) and MyDevice/development (TODO ref) server is defined in the Tango Database the later command will start the device server.

Now using your favorite IDE open the newly generated project and develop your JTango server. Please read more in JTango documentation.

###### Using POGO

See POGO documentation.

###### Starting from scratch

Example Java device and detailed documentation can be found in the JTango documentation.

##### Integrate Java Tango servers with Astor

audience:administrators audience:developers lang:java

###### Problem overview

One wants to control Java Tango servers using Astor after deployment.

###### Detailed cases

Simplify deployment and integration of new Java Tango servers with existing Tango environment. Give users ability to use well known tools for controlling and monitoring Java Tango servers.

###### Solution overview

The following describes solutions for linux. Windows users may use the same strategy except bash files must be replaced with corresponding batch files.

There are two possible ways we can prepare this receipt:

1. Generate fat jar, i.e. executable jar that contains everything.

     1    <!—pom.xml –>
     2    …
     3    <build>
     4            <plugins>
     5                <plugin>
     6                    <artifactId>maven-assembly-plugin</artifactId>
     7                    <!—assemble executable jar with all dependencies –>
     8                    <configuration>
     9                        <appendAssemblyId>false</appendAssemblyId>
    10                        <descriptorRefs>
    11                            <descriptorRef>jar-with-dependencies</descriptorRef>
    12                        </descriptorRefs>
    13                        <archive>
    14                            <manifest>
    15                                <mainClass>hzg.wpn.tango.TestServer</mainClass>
    16                            </manifest>
    17                        </archive>
    18                    </configuration>
    19                </plugin>
    20            </plugins>
    21        </build>
    22    …

Executing mvn assembly:single produces one big jar file in the target folder, i.e. TestServer-1.4.jar.

To use our server we just copy it to some location on the target machine and use the following bash script:

    1    #!/bin/bash
    2
    3    /usr/bin/java -jar /absolute/path/to/our/jar $1 hzg.wpn.tango.TestServer $1

Do not mind the last two parameters they are here to workaround an issue.

This script is saved into `/usr/lib/tango/server/TestServer`. `/usr/lib/tango/server` can be replaced with any other location where Starter can find the script, i.e. defined in StartDsPath property.

We need to specify an absolute path to the jar file as Astor runs servers from `/var/tmp/ds.log` folder

PROS

> - It is much easier to deal with immutable artifacts

CONS

> - A lot of duplication, imagine 1000 servers each requiring 17MB

2. Use exploded assembly, i.e. first package everything into tar.gz

     1    <!—pom.xml –>
     2    <build>
     3            <plugins>
     4                <plugin>
     5                    <artifactId>maven-assembly-plugin</artifactId>
     6                    <configuration>
     7                        <appendAssemblyId>false</appendAssemblyId>
     8                        <descriptors>
     9                            <descriptor>src/main/assembly.xml</descriptor>
    10                        </descriptors>
    11                    </configuration>
    12                </plugin>
    13            </plugins>
    14        </build>
    15    <!—src/main/assembly.xml –>
    16    <assembly schemaLocation="http://maven.apache.org/xsd/assembly-1.0.0.xsd">
    17        <id>distr</id>
    18        <formats>
    19            <format>tar.gz</format>
    20        </formats>
    21        <dependencySets>
    22            <dependencySet>
    23                <outputDirectory>/lib</outputDirectory>
    24                <fileMode>0777</fileMode>
    25            </dependencySet>
    26        </dependencySets>
    27        <fileSets>
    28            <fileSet>
    29                <directory>conf</directory>
    30                <outputDirectory>/conf</outputDirectory>
    31            </fileSet>
    32        </fileSets>
    33    </assembly>

When deployed it is extracted from the archive and copied to some place e.g. `SERVER_ROOT`. Exploded archive has the following structure:

    SERVER_ROOT\
                |- lib\
                |      |- *.jar
                |
                |- conf\
                        |- test.xml

Startup bash script may look like this:

    1    #!/bin/bash
    2
    3    # import essential environmental variables like absolute path to our server root aka SERVER_ROOT
    4    . /etc/tangorc
    5
    6    /usr/bin/java -cp $SERVER_ROOT/lib/* -Dconf=$SERVER_ROOT/conf/test.xml hzg.wpn.tango.TestServer

Again the script is in `/usr/lib/tango/server/TestServer`. `/usr/lib/tango/server` can be replaced with any other location where Starter can find the script, i.e. defined in `StartDsPath` property.

We need to specify an absolute path to the `lib` and `conf` folders as Astor runs servers from `/var/tmp/ds.log` folder:

PROS:

> - if there are several servers common dependencies can be placed into a single location, hence save some disc space

> - server may use external resources (like conf in the example above), just make sure to use absolute paths

CONS:

> - dealing with exploded assemblies quickly becomes messy

Both solutions assume that maven is used to handle project’s lifecycle.

For a more complete guide on JTango please refer to the JTango documentation.

#### Getting started with PyTango (Python implementation of Tango-Controls)

audience:developers lang:python

##### Developing Python TANGO device class

##### How to PyTango

audience:developers lang:python

A list of short recipes for common tasks.

###### Installation notes

See the PyTango installation guide.

###### Before anything else

Import the PyTango module in python.

    import tango

###### Using the DeviceProxy object

Getting the polling buffer values

Only for polled attributes we can get the last N read values. the polling buffer depth is managed by the admin device.

    dp = tango.DeviceProxy('some/tango/device')
    dp.attribute_history('cpustatus', 10)

###### Get/Set polled attributes

    def get_polled_attributes(dev_name):
        dp = tango.DeviceProxy(dev_name)
        attrs = dp.get_attribute_list()
        periods = [(a, dp.get_attribute_poll_period(a)) for a in attrs]
        return dict((a, p) for a, p in periods if p)

    [plc4.poll_attribute(a, 5000) for k, v in periods if v]

###### Modify the polling of attributes

     import re

     period = 10000
     devs = tango.Database().get_device_exported('some/tango/devices*')
     for dev in devs:
         dp = tango.DeviceProxy(dev)
         attrs = sorted([a for a in dp.get_attribute_list() if re.match('(Output|Temperature)_[0-9]$',a)])
         [dp.poll_attribute(a,period) for a in attrs]
         print('\n'.join(dp.polling_status()))

###### Events

Creating an event callback

    # The callback must be a callable or an object with a push_event(self, event) method

    def callback_function(event):
        print(event)

Configuring an event

    # From the client side
    # subscribe_event(attr_name, event_type, cb_or_queuesize, filters=[], stateless=False, extract_as=tango._tango.ExtractAs.Numpy)
    event_id = tango.DeviceProxy.subscribe_event(attributeName, tango.EventType.CHANGE_EVENT, callback_function, [], True)

    # From inside the device server, if it will manually push events (not using server-side polling)
    self.set_change_event('State', True, True)

    # when an event needs to be pushed manually
    self.push_change_event('State', True, True)

###### Device Server Internal Objects

###### Forcing in which host the device is exported

This environment variable must be set before launching the device:

    $ export OMNIORB_USEHOSTNAME=10.0.0.10

###### Creating a Device Server from ipython

Having defined your device in MyDS.py:

    from MyDS import *
    py = tango.Util(['MyDS.py', 'InstanceName'])
    py.add_TgClass(MyDSClass, MyDS, 'MyDS')
    U = tango.Util.instance()
    U.server_init()
    U.server_run()

###### Modify internal polling

Note

It doesn’t work at init_device(); must be done later on in a hook method.

     U = tango.Util.instance()
     dserver = U.get_dserver_device()
     admin = tango.DeviceProxy(dserver.get_name())
     dir(admin)
         [
             StartPolling
             StopPolling
             AddObjPolling
             RemObjPolling
             UpdObjPollingPeriod
             DevPollStatus
             PolledDevice
         ]

     polled_attrs = {}
     for st in admin.DevPollStatus(name):
         lines = st.split('\n')
         try:
            polled_attrs[lines[0].split()[-1]] = lines[1].split()[-1]
         except tango.DevFailed:
            pass

     type_ = 'command' or 'attribute'
     for aname in args:
        if aname in polled_attrs:
            admin.UpdObjPollingPeriod([[200], [name, type_, aname]])
        else:
            admin.AddObjPolling([[3000], [name, type_, aname]])

###### Get all polling attributes

The polling of the attributes is recorded in the property_device table of the tango database in the format of a list like [ATTR1, PERIOD1, ATTR2, PERIOD2, …]

The list of polled attributes can be accessed using this method of admin device:

    dp = tango.DeviceProxy('dserver/myServerClass/id22')
    polled_attrs = [a.split('\n')[0].split(' ')[-1] for a in dp.DevPollStatus('domain/family/member-01')]

###### Get the device class object from the device itself

    self.get_device_class()

###### Get the devices inside a Device Server

    def get_devs_in_server(self,MyClass=None):
         """
         Method for getting a dictionary with all the devices running in this server
         """
         MyClass = MyClass or type(self) or DynamicDS
         if not hasattr(MyClass, '_devs_in_server'):
             MyClass._devs_in_server = {}  # This dict will keep an access to the class objects instantiated in this Tango server
         if not MyClass._devs_in_server:
             U = tango.Util.instance()
             for klass in U.get_class_list():
                 for dev in U.get_device_list_by_class(klass.get_name()):
                     if isinstance(dev, DynamicDS):
                         MyClass._devs_in_server[dev.get_name()]=dev
         return MyClass._devs_in_server

###### Identify each attribute inside read_attr_hardware()

    def read_attr_hardware(self,data):
        self.debug("In DynDS::read_attr_hardware()")
        try:
            attrs = self.get_device_attr()
            for d in data:
                a_name = attrs.get_attr_by_ind(d).get_name()
                if a_name in self.dyn_attrs:
                    self.lock.acquire()  # This lock will be released at the end of read_dyn_attr
                    self.myClass.DynDev=self  # VITAL: It tells the admin class which device attributes are going to be read
                    self.lock_acquired += 1
            self.debug(f'DynamicDS::read_attr_hardware(): lock acquired {self.lock_acquired} times')
        except Exception as e:
            self.last_state_exception = f'Exception in read_attr_hardware: {e}'
            self.error(f'Exception in read_attr_hardware: {e}')

###### Device server logging (using Tango logs)

See the PyTango logging docs.

###### Adding dynamic attributes to a device

     self.add_attribute(
         tango.Attr( #or tango.SpectrumAttr
             new_attr_name,tango.DevArg.DevState,tango.AttrWriteType.READ, #or READ_WRITE
             #max_size or dyntype.dimx #If Spectrum
             ),
         self.read_new_attribute, #(attr)
         None, #self.write_new_attribute #(attr)
         self.is_new_attribute_allowed, #(request_type)
         )

###### Using Database Object

    # use the TANGO_HOST environment variable/tangorc config file
    db = tango.Database()

    # Using a specified host and port
    db = tango.Database("other-db-host", 10000)

###### Register a new device server

     dev = f'SR{sector:02}/VC/ALL'
     klass = 'PyStateComposer'
     server = klass + '/' + dev.replace('/', '_')

     di = tango.DbDevInfo()
     di.name, di._class, di.server = device, klass, server
     db.add_device(di)

###### Remove “empty” servers from database

     tango = tango.Database()
     [tango.delete_server(s)
         for s in tango.get_server_list()
         if all(d.lower().startswith('dserver') for d in tango.get_device_class_list(s))
     ]

###### Force unexport of a failing server

You can check using db object if a device is still exported after killed

    >>> bool(db.import_device('dserver/HdbArchiver/11').exported)
    True

You can unexport this device or server with the following call:

    db.unexport_server('HdbArchiver/11')

It would normally allow you to restart the server again.

###### Get all servers of a given class

     class_name = 'Modbus'
     list_of_names = ['/'.join((class_name,name)) for name in db.get_instance_name_list(class_name)]

Differences between DB methods:

    get_instance_name_list(exec_name)  # return names of **instances**
    get_server_list()  # returns list of all **executable/instance**
    get_server_name_list()  # returns names of all **executables**

###### Get all devices of a server or a given class

The command is:

     db.get_device_class_list(server_name)
     # returns: ['device/name/family', 'device_class'] * num_of_devs_in_server

The list returned includes the admin server (dserver/exec_name/instance) that must be pruned from the result:

     list_of_devs = [dev for dev in db.get_device_class_list(server_name) if '/' in dev and not dev.startswith('dserver')]

###### Get all devices of a given class from the database

     import operator
     list_of_devs = reduce(operator.add,(list(dev for dev in db.get_device_class_list(n) \
         if '/' in dev and not dev.startswith('dserver')) for n in \
         ('/'.join((class_name,instance)) for instance in db.get_instance_name_list(class_name)) \
         ))

###### Get property values for a list of devices

     db.get_device_property_list(device_name,'*')  # returns list of available properties
     db.get_device_property(device_name,[property_name])  # returns {property_name : value}

     prop_names = db.get_device_property_list(device_name)
         ['property1','property2']
     dev_props = db.get_device_property(device_name,prop_names)
         {'property1':'first_value' , 'property2':'second_value' }

###### Get the history (last ten values) of a property

     >>> [ph.get_value().value_string for ph in tango.get_device_property_history('some/alarms/device','AlarmsList')]
     [['MyAlarm:a/gauge/controller/Pressure > 1e-05', 'TempAlarm:a/nice/device/Temperature_Max > 130'],

###### Get the server for a given device

     >>> print(db.get_server_list('Databaseds/*'))
     ['DataBaseds/2']
     >>> print(db.get_device_name('DataBaseds/2', 'DataBase'))
     ['sys/database/2']
     >>> db_dev=tango.DeviceProxy('sys/database/2')
     >>> print(db_dev.command_inout('DbImportDevice', 'et/wintest/01'))
     ([0, 2052], ['et/wintest/01', 'IOR:0100000017000xxxxxx', '4',
     'WinTest/manu', 'PCTAUREL.esrf.fr', 'WinTest'])

###### Get the Info of a not running device (exported, host, server)

     def get_device_info(dev):
         vals = tango.DeviceProxy('sys/database/2').DbGetDeviceInfo(dev)
         di = dict((k,v) for k,v in zip(('name', 'ior', 'level', 'server', 'host', 'started', 'stopped'), vals[1]))
         di['exported'], di['PID'] = vals[0]
         return di

###### Set property values for a list of devices

Attention , Tango property values are always inserted as lists! {property_name : [ property_value ]}

    >>> prop_name, prop_value = 'Prop1', 'Value1'
    [db.put_device_property(dev,{prop_name:[prop_value]}) for dev in list_of_devs]

###### Get Starter Level configuration for a list of servers

     [(si.name, si.mode, si.level) for si in [db.get_server_info(s) for s in list_of_servers]]

###### Set Memorized Value for an Attribute

     db.get_device_attribute_property('tcoutinho/serial/01/Baudrate',['__value'])
     db.put_device_attribute_property('tcoutinho/serial/01/Baudrate',{'__value':VALUE})

###### Useful constants and enums

     In [3]:tango.ArgType.values
     Out[3]:
    {0: tango._tango.CmdArgType.DevVoid,
     1: tango._tango.CmdArgType.DevBoolean,
     2: tango._tango.CmdArgType.DevShort,
     3: tango._tango.CmdArgType.DevLong,
     4: tango._tango.CmdArgType.DevFloat,
     5: tango._tango.CmdArgType.DevDouble,
     6: tango._tango.CmdArgType.DevUShort,
     7: tango._tango.CmdArgType.DevULong,
     8: tango._tango.CmdArgType.DevString,
     9: tango._tango.CmdArgType.DevVarCharArray,
     10: tango._tango.CmdArgType.DevVarShortArray,
     11: tango._tango.CmdArgType.DevVarLongArray,
     12: tango._tango.CmdArgType.DevVarFloatArray,
     13: tango._tango.CmdArgType.DevVarDoubleArray,
     14: tango._tango.CmdArgType.DevVarUShortArray,
     15: tango._tango.CmdArgType.DevVarULongArray,
     16: tango._tango.CmdArgType.DevVarStringArray,
     17: tango._tango.CmdArgType.DevVarLongStringArray,
     18: tango._tango.CmdArgType.DevVarDoubleStringArray,
     19: tango._tango.CmdArgType.DevState,
     20: tango._tango.CmdArgType.ConstDevString,
     21: tango._tango.CmdArgType.DevVarBooleanArray,
     22: tango._tango.CmdArgType.DevUChar,
     23: tango._tango.CmdArgType.DevLong64,
     24: tango._tango.CmdArgType.DevULong64,
     25: tango._tango.CmdArgType.DevVarLong64Array,
     26: tango._tango.CmdArgType.DevVarULong64Array,
     28: tango._tango.CmdArgType.DevEncoded,
     29: tango._tango.CmdArgType.DevEnum,
     30: tango._tango.CmdArgType.DevPipeBlob,
     31: tango._tango.CmdArgType.DevVarStateArray}

    In [4]: tango.AttrWriteType.values
    Out[4]:
    {0: tango._tango.AttrWriteType.READ,
     1: tango._tango.AttrWriteType.READ_WITH_WRITE,
     2: tango._tango.AttrWriteType.WRITE,
     3: tango._tango.AttrWriteType.READ_WRITE,
     4: tango._tango.AttrWriteType.WT_UNKNOWN}

    In [5]: tango.AttrWriteType.values[3] is tango.READ_WRITE
    Out[5]: True

###### Using Tango Groups

This example uses PyTangoGroup to read the status of all devices in a Device Server

     server_name = 'VacuumController/AssemblyArea'
     group = tango.Group(server_name)
     devs = [d for d in tango.Database().get_device_class_list(server_name) if '/' in d and 'dserver' not in d]
     for d in devs:
         group.add(d)

     answers = group.command_inout('Status', [])
     for reply in answers:
         print(f'Device {reply.dev_name()} Status is:')
         print(reply.get_data())

###### Passing Arguments to Device command_inout

When type of Arguments is special like DevVarLongStringArray the introduction of arguments is something like:

     api.manager.command_inout('UpdateSnapComment', [[40], ['provant, provant...']])

###### Using asynchronous commands

     import time

     cid = self.modbus.command_inout_asynch(command, arr_argin)
     while True:
         self.debug('Waiting for asynchronous answer ...')
         time.sleep(0.1)
         try:
             result = self.modbus.command_inout_reply(cid)
             self.debug(f'Received: {result}')
             break
         except tango.DevFailed as e:
             self.debug(f'Received DevFailed: {e}')
             if e.args[0]['reason'] != 'API_AsynReplyNotArrived':
                raise RuntimeException(f'Weird exception received!: {e}')

###### Setting Attribute Config

     for server in astor.values():
         for dev in server.get_device_list():
             dp = server.get_proxy(dev)
             attrs = dp.get_attribute_list()
             if dev.rsplit('/')[-1].lower() not in [a.lower() for a in attrs]: continue
             conf = dp.get_attribute_config(dev.rsplit('/')[-1])
             conf.format = "%1.1e"
             conf.unit = "mbar"
             conf.label = f"{dev}-Pressure"
             print('setting config for {dev}/{conf.name}')
             dp.set_attribute_config(conf)

##### PyTangoArchiving Recipes

audience:developers lang:python

by Sergi Rubio

PyTangoArchiving is the python API for Tango Archiving.

This package allows to:

- Integrate Hdb and Snap archiving with other python/PyTango tools.

- Start/Stop Archiving devices in the appropiated order.

- Increase the capabilities of configuration and diagnostic.

- Import/Export .csv and .xml files between the archiving and the database.

Don’t edit this wiki directly, the source for this documentation is available at PyTangoArchiving UserGuide

###### Installing PyTangoArchiving:

Repository is available on sourceforge:

    1$ svn co https://svn.code.sf.net/p/tango-cs/code/archiving/tool/PyTangoArchiving/trunk

###### Dependencies:

1. Tango Java Archiving, ArchivingRoot from sourceforge,

2. PyTango

3. python-mysql

4. Taurus (optional)

5. fandango:

>     1$ svn co https://svn.code.sf.net/p/tango-cs/code/share/fandango/trunk/fandango fandango

###### Setup:

- Follow Tango Java Archiving installation document to setup Java Archivers and Extractors.

- Some of the most common installation issues are solved in several topics in Tango forums (search for Tdb/Hdb/Snap Archivers):

  - tango-archiving,

  - installation-of-hdb-and-tdb-in-linux.

- Install PyTango and MySQL-python using their own setup.py scripts.

- fandango, and PyTangoArchiving parent folders must be added to your PYTHONPATH environment variable.

- Although Java Extractors may be used, it is recommended to configure direct MySQL access for PyTangoArchiving

###### Accessing MySQL:

Although not needed, I recommend you to create a new MySQL user for data querying:

     1$ mysql -u hdbmanager -p hdb
     2
     3$ GRANT USAGE ON hdb.* TO 'user'@'localhost' IDENTIFIED BY '**********';
     4$ GRANT USAGE ON hdb.* TO 'user'@'%' IDENTIFIED BY '**********';
     5$ GRANT SELECT ON hdb.* TO 'user'@'localhost';
     6$ GRANT SELECT ON hdb.* TO 'user'@'%';
     7
     8$ mysql -u tdbmanager -p tdb
     9
    10$ GRANT USAGE ON tdb.* TO 'user'@'localhost' IDENTIFIED BY '**********';
    11$ GRANT USAGE ON tdb.* TO 'user'@'%' IDENTIFIED BY '**********';
    12$ GRANT SELECT ON tdb.* TO 'user'@'localhost';
    13$ GRANT SELECT ON tdb.* TO 'user'@'%';

Check in a python shell that your able to access the database:

    1import PyTangoArchiving
    2
    3PyTangoArchiving.Reader(db='hdb',config='user:password@hostname')

Then configure the Hdb/Tdb Extractor class properties to use this user/password for querying:

    1import PyTango
    2
    3PyTango.Database().put_class_property('HdbExtractor',{'DbConfig':'user:password@hostname'})
    4
    5PyTango.Database().put_class_property('TdbExtractor',{'DbConfig':'user:password@hostname'})

You can test now access from a Reader (see recipes below) object or from a taurustrend/ArchivingBrowser UI (Taurus required):

    1python PyTangoArchiving/widget/ArchivingBrowser.py

###### Download

Download PyTangoArchiving from sourceforge:

    1svn co https://svn.code.sf.net/p/tango-cs/code/archiving/tool/PyTangoArchiving/trunk

###### Submodules

- api,

  - getting servers/devices/instances implied in the archiving system and allowing

- historic,

  - configuration and reading of historic data

- snap,

  - configuration and reading of snapshot data,

- xml,

  - conversion between xml and csv files

- scripts,

  - configuration scripts

- reader,

  - providing the useful Reader and ReaderProcess objects to retrieve archived data

###### General usage

In all these examples you can use hdb or tdb just replacing one by the other

###### Get archived values for an attribute

The reader object provides a fast access to archived values

    In [9]: import PyTangoArchiving
    In [10]: rd = PyTangoArchiving.Reader('hdb')
    In [11]: rd.get_attribute_values('expchan/eh_emet02_ctrl/3/value','2013-03-20 10:00','2013-03-20 11:00')
    Out[11]:
    [(1363770788.0, 5.79643e-14),
     (1363770848.0, 5.72968e-14),
     (1363770908.0, 5.7621e-14),
     (1363770968.0, 6.46782e-14),
     ...

###### Start/Stop/Check attributes

You must create an Archiving api object and pass to it the list of attributes with its archiving config:

     1import PyTangoArchiving
     2hdb = PyTangoArchiving.ArchivingAPI('hdb')
     3attrs = ['expchan/eh_emet03_ctrl/3/value','expchan/eh_emet03_ctrl/4/value']
     4
     5#Archive every 15 seconds if change> +/-1.0, else every 300 seconds
     6modes = {'MODE_A': [15000.0, 1.0, 1.0], 'MODE_P': [300000.0]}
     7
     8#If you omit the modes argument then archiving will be every 60s
     9hdb.start_archiving(attrs, modes)
    10
    11hdb.load_last_values(attrs)
    12{'expchan/eh_emet02_ctrl/3/value': [[datetime.datetime(2013, 3, 20, 11, 38, 9),
    13    7.27081e-14]],
    14    'expchan/eh_emet02_ctrl/4/value': [[datetime.datetime(2013, 3, 20, 11, 39),
    15    -3.78655e-08]]
    16}
    17
    18hdb.stop_archiving(attrs)

###### Loading a .CSV file into Archiving

The .csv file must have a shape like this one (any row starting with ‘#’ is ignored):

     1Host  Device  Attribute   Type    ArchivingMode   Periode >15  MinRange    MaxRange
     2
     3#This header lines are mandatory!!!
     4@LABEL  Unique ID
     5@AUTHOR Who?
     6@DATE   When?
     7@DESCRIPTION    What?
     8
     9#host   domain/family/member    attribute   HDB/TDB/STOP    periodic/absolute/relative
    10
    11cdi0404 LI/DI/BPM-ACQ-01    @DEFAULT        periodic    300
    12                            ADCChannelAPeak HDB absolute    15  1   1
    13                                            TDB absolute    5   1   1
    14                            ADCChannelBPeak HDB absolute    15  1   1
    15                                            TDB absolute    5   1   1
    16                            ADCChannelCPeak HDB absolute    15  1   1
    17                                            TDB absolute    5   1   1
    18                            ADCChannelDPeak HDB absolute    15  1   1
    19                                            TDB absolute    5   1   1

The command to insert it is:

    1import PyTangoArchiving
    2PyTangoArchiving.LoadArchivingConfiguration('/...fbecheri_20130319.csv','hdb',launch=True)

There are some arguments to modify Loading behavior.

launch:

    if not explicitly True then archiving is not triggered, it just verifies that format of the file is Ok and attributes are available

force:

    if False the loading will stop at first error, if True then it tries all attributes even if some failed

overwrite:

    if False attributes already archived will be skipped.

###### Checking the status of the archiving

     1hdb = PyTangoArchiving.ArchivingAPI('hdb')
     2hdb.load_last_values()
     3filter = "/" #Put here whatever you want to filter the attribute names
     4lates = [a for a in hdb if filter in a and hdb[a].archiver and hdb[a].modes.get('MODE_P') and hdb[a].last_date<(time.time()-(3600+1e-3*hdb[a].modes['MODE_P'][0]))]
     5
     6#Get the list of attributes that cannot be read from the control system (ask system responsibles)
     7unav = [a for a in lates if not fandango.device.check_attribute(a,timeout=6*3600)]
     8#Get the list of attributes that are not being archived
     9lates = sorted(l for l in lates if l not in unav)
    10#Get the list of archivers not running properly
    11bad_archs = [a for a,v in hdb.check_archivers().items() if not v]
    12
    13#Restarting the archivers/attributes that failed
    14bads = [l for l in lates if hdb[l] not in bad_archs]
    15astor = fandango.Astor()
    16astor.load_from_devs_list(bad_archs)
    17astor.restart_servers()
    18hdb.restart_archiving(bads)

###### Restart of the whole archiving system

    1admin@archiving:> archiving_service.py stop-all
    2...
    3admin@archiving:> archiving_service.py start-all
    4...
    5admin@archiving:> archiving_service.py status
    6
    7#see archiving_service.py help for other usages

###### Using the Python API

###### Start/Stop of an small (<10) list of attributes

     1#Stopping ...
     2api.stop_archiving(['bo/va/dac/input','bo/va/dac/settings'])
     3
     4#Starting with periodic=60s ; relative=15s if +/-1% change
     5api.start_archiving(['bo/va/dac/input','bo/va/dac/settings'],{'MODE_P':[60000],'MODE_R':[15000,1,1]})
     6
     7#Restarting and keeping actual configuration
     8
     9attr_name = 'bo/va/dac/input'
    10api.start_archiving([attr_name],api.attributes[attr_name].extractModeString())

###### Checking if a list of attributes is archived

    In [16]: hdb = PyTangoArchiving.api('hdb')
    In [17]: sorted([(a,hdb.load_last_values(a)) for a in hdb if a.startswith('bl04')])
    Out[17]:
    [('bl/va/elotech-01/output_1',
      [[datetime.datetime(2010, 7, 2, 15, 53), 6.0]]),
     ('bl/va/elotech-01/output_2',
      [[datetime.datetime(2010, 7, 2, 15, 53, 11), 0.0]]),
     ('bl/va/elotech-01/output_3',
      [[datetime.datetime(2010, 7, 2, 15, 53, 23), 14.0]]),
     ('bl/va/elotech-01/output_4',
      [[datetime.datetime(2010, 7, 2, 15, 52, 40), 20.0]]),
    ...

###### Getting information about attributes archived

###### Getting the total number of attributes:

    1import PyTangoArchiving
    2api = PyTangoArchiving.ArchivingAPI('hdb')
    3len(api.attributes) #All the attributes in history
    4len([a for a in api.attributes.values() if a.archiving_mode]) #Attributes configured

###### Getting the configuration of attribute(s):

    1#Getting as string
    2modes = api.attributes['rs/da/bpm-07/CompensateTune'].archiving_mode
    3
    4#Getting it as a dict
    5api.attributes['sr/da/bpm-07/CompensateTune'].extractModeString()
    6
    7#OR
    8PyTangoArchiving.utils.modes_to_dict(modes)

###### Getting the list of attributes not updated in the last hour:

    1failed = sorted(api.get_attribute_failed(3600).keys())

###### Getting values for an attribute:

    1import PyTangoArchiving,time
    2
    3reader = PyTangoArchiving.Reader() #An HDB Reader object using HdbExtractors
    4#OR
    5reader = PyTangoArchiving.Reader(db='hdb',config='pim:pam@pum') #An HDB reader accessing to MySQL
    6
    7attr = 'bo04/va/ipct-05/state'
    8dates = time.time()-5*24*3600,time.time() #5days
    9values = reader.get_attribute_values(attr,*dates) #it returns a list of (epoch,value) tuples

###### Exporting values from a list of attributes as a text (csv / ascii) file

     1from PyTangoArchiving import Reader
     2rd = Reader(db='hdb') #If HdbExtractor.DbConfig property is set one argument is enough
     3attrs = [
     4         'bl11-ncd/vc/eps-plc-01/pt100_1',
     5         'bl11-ncd/vc/eps-plc-01/pt100_2',
     6        ]
     7
     8#If you ignore text argument you will get lists of values, if text=True then you get a tabulated file.
     9ascii_values = rd.get_attributes_values(attrs,
    10                      start_date='2010-10-22',stop_date='2010-10-23',
    11                      correlate=True,text=True)
    12
    13print ascii_values
    14
    15#Save it as .csv if you want ...
    16open('myfile.csv','w').write(ascii_values)

###### Filtering State changes for a device

     1import PyTangoArchiving as pta
     2rd = pta.Reader('hdb','...:...@...')
     3vals = rd.get_attribute_values('bo02/va/ipct-02/state','2010-05-01 00:00:00','2010-07-13 00:00:00')
     4bads = []
     5for i,v in enumerate(vals[1:]):
     6    if v[1]!=vals[i-1][1]:
     7        bads.append((v[0],vals[i-1][1],v[1]))
     8report = [(time.ctime(v[0]),str(PyTango.DevState.values[int(v[1])] if v[1] is not None else 'None'),str(PyTango.DevState.values[int(v[2])] if v[2] is not None else 'None')) for v in bads]
     9
    10report =
    11[('Sat May  1 00:07:03 2010', 'UNKNOWN', 'ON'),
    12...

###### Getting a table with last values for all attributes of a same device

     1hours = 1
     2device = 'bo/va/ipct-05'
     3attrs = [a for a in reader.get_attributes() if a.lower().startswith(device)]
     4vars = dict([(attr,reader.get_attribute_values(attr,time.time()-hours*3600)) for attr in attrs])
     5table = [[time.ctime(t0)]+
     6         [([v for t,v in var if t<=t0] or [None])[-1] for attr,var in sorted(vars.items())]
     7        for t0,v0 in vars.values()[0]]
     8print('\n'.join(
     9      ['\t'.join(['date','time']+[k.lower().replace(device,'') for k in sorted(vars.keys())])]+
    10      ['\t'.join([str(s) for s in t]) for t in table]))

###### Using CSV files

###### Loading an HDB/TDB configuration file

###### Create dedicated archivers first

If you want to use this option it will require some RAM resources in the host machine (64MbRAM/250Attributes) and installing the ALBA-Archiving bliss package.

    1from PyTangoArchiving.files import DedicateArchiversFromConfiguration
    2DedicateArchiversFromConfiguration('LX_I_Archiving.csv','hdb',launch=True)

TDB Archiving works different as it shouldn’t be working on diskless machines, using instead a centralized host for all archiver devices.

    1DedicateArchiversFromConfiguration('LX_I_Archiving.csv','tdb',centralized='archiving01',launch=True)

###### Loading the .csv files

All the needed code to do it is:

     1import PyTangoArchiving
     2
     3#With launch=False this function will do a full check of the attributes and print the results
     4PyTangoArchiving.LoadArchivingConfiguration('/data/Archiving//LX_I_Archiving_.csv','hdb',launch=False)
     5
     6#With launch=True configuration will be recorded and archiving started
     7PyTangoArchiving.LoadArchivingConfiguration('/data/Archiving//LX_I_Archiving_.csv','hdb',launch=True)
     8
     9#To force archiving of all not-failed attributes
    10PyTangoArchiving.LoadArchivingConfiguration('/data/Archiving//LX_I_Archiving_.csv','hdb',launch=True,force=True)
    11
    12#Starting archiving in TDB mode (kept 5 days only)
    13PyTangoArchiving.LoadArchivingConfiguration('/data/Archiving//LX_I_Archiving_.csv','tdb',launch=True,force=True)

Note

You must take in account the following conditions:

- Names of attributes must match the NAME, not the LABEL! (that’s a common mistake)

- Devices providing the attributes must be running when you setup archiving.

- Regular expressions are NOT ALLOWED (I know previous releases allowed it, but never worked really well)

###### filtering a list of CSV configurations / attributes to load

You can use GetConfigFiles and filters/exclude to select a predefined list of attributes

     1import PyTangoArchiving as pta
     2
     3filters = {'name':".*"}
     4exclude = {'name':"(s.*bpm.*)|(s10.*rf.*)|(s14.*rf.*)"}
     5
     6#TDB
     7confs = pta.GetConfigFiles(mask='.*(RF|VC).*')
     8for target in confs:
     9    pta.LoadArchivingConfiguration(target,launch=True,force=True,overwrite=True,dedicated=False,schema='tdb',filters=filters,exclude=exclude)
    10
    11#HDB
    12confs = pta.GetConfigFiles(mask='.*BO.*(RF|VC).*')
    13for target in confs:
    14    pta.LoadArchivingConfiguration(target,launch=True,force=True,overwrite=True,dedicated=True,schema='hdb',filters=filters,exclude=exclude)

###### Comparing a CSV file with the actual configuration

    1import PyTangoArchiving
    2api = PyTangoArchiving.ArchivingAPI('hdb')
    3config = PyTangoArchiving.ParseCSV('Archiving_RF_.csv')
    4
    5for attr,conf in config.items():
    6    if attr not in api.attributes or not api.attributes[attr].archiving_mode:
    7        print '%s not archived!' % attr
    8    elif PyTangoArchiving.utils.modes_to_string(api.check_modes(conf['modes']))!=api.attributes[attr].archiving_mode:
    9        print '%s: %s != %s' %(attr,PyTangoArchiving.utils.modes_to_string(api.check_modes(conf['modes'])),api.attributes[attr].archiving_mode)

###### Checking and restarting a known system from a .csv

     1import PyTangoArchiving.files as ptaf
     2borf = '/data/Archiving/BO_20100603_v2.csv'
     3config = ptaf.ParseCSV(borf)
     4import PyTangoArchiving.utils as ptau
     5hdb = PyTangoArchiving.ArchivingAPI('hdb')
     6
     7missing = [
     8    'bo/ra/fim-01/remotealarm',
     9    'bo/ra/fim-01/rfdet1',
    10    'bo/ra/fim-01/rfdet2',
    11    'bo/ra/fim-01/arcdet5',
    12    'bo/ra/fim-01/rfdet3',
    13    'bo/ra/fim-01/arcdet3',
    14    'bo/ra/fim-01/arcdet2',
    15    'bo/ra/fim-01/vacuum']
    16
    17ptau.check_attribute('bo/ra/fim-01/remotealarm')
    18missing = 'bo/ra/fim-01/arcdet4|bo/ra/fim-01/remotealarm|bo/ra/fim-01/rfdet1|bo/ra/fim-01/rfdet2|bo/ra/fim-01/arcdet5|bo/ra/fim-01/rfdet3|bo/ra/fim-01/arcdet3|bo/ra/fim-01/arcdet2|bo/ra/fim-01/vacuum'
    19
    20ptaf.LoadArchivingConfiguration(borf,filters={'name':missing},launch=True)
    21ptaf.LoadArchivingConfiguration(borf,filters={'name':'bo/ra/eps-plc.*'},stop=True,force=True)
    22ptaf.LoadArchivingConfiguration(borf,filters={'name':'bo/ra/eps-plc.*'},launch=True,force=True)
    23
    24rfplc = ptaf.ParseCSV(borf,filters={'name':'bo/ra/eps-.*'})
    25stats = ptaf.CheckArchivingConfiguration(borf,period=300)

### Debugging and Testing

audience:developers lang:all

In the following articles you will find useful information on testing and debugging of your code.

One recommended way of testing newly developed Tango device servers is by using the available Tango Docker containers.

#### Using Tango docker containers

audience:developers lang:all

In this section we describe how one can test newly developed tango device server using docker containers.

Tango docker containers provide a lightweight solution for deploying tango, especially useful for local workstation development.

To get info on how to install docker on your machine please refer to the docker documentation.

Once docker is installed one can pull docker images with pre-installed tango.

##### Tango docker containers

SKAO provides several docker containers for Tango development and deployment, including but not limited to:

> - ska-tango-images-tango-db: a (mysql-compatible) mariadb container with the Tango schema

> - ska-tango-images-tango-cpp: a container with core Tango libraries and dependencies that can run the `DatabaseDS` Device Server

> - ska-tango-images-rest-server: a Tango REST server

> - ska-tango-images-tango-test: the well-established TangoTest device server

The sources for these docker images are hosted and maintained on the SKAO gitlab: ska-telescope/ska-tango-images

These container images are hosted on SKAO artefact repository: https://artefact.skao.int. At the moment they cannot be browsed for via the repository website, however, it is possible to search for images at the SKAO Harbor service: https://harbor.skao.int/. Just enter ska-tango-image in the search box and Harbor will list all images that SKAO provides. The images can must be pulled with the prefix `artefact.skao.int/`. SKAO also provide a catalogue of images which can be viewed here: https://developer.skao.int/projects/ska-tango-images/en/stable/. This catalogue contains basic usage instructions for each image.

###### Tango docker stack

The easiest way to setup the whole stack on a local development/test computer is to use docker compose. The SKAO provides How-To guides for spinning up different Tango configurations using docker compose.

For example, this guide describes how to start a Tango database and Tango RestServer using the images provided by SKAO and docker compose.

More guides are available here.

#### JUNIT helper classes for device server testing

audience:developers lang:java

One wants to test a Device Server using the full tango stack. This test must not be related to any environment and be performed together with other tests. It is possible to start the tango server without database or with a File Database for testing. These are the default approaches in Python or C++ and that would work for Java as well. The below solution in Java is yet another approach.

Below is how one can integrate it into JUnit framework:

     1    public class ServerTest {
     2
     3        public static final String NO_DB_GIOP_PORT = "12345";//any random non-occupied port
     4        public static final String NO_DB_INSTANCE = "test_server";
     5        public static final String NO_DB_DEVICE_NAME = "test/junit/" +NO_DB_INSTANCE;
     6        //our server will run in a dedicated thread
     7        private final ExecutorService server = Executors.newSingleThreadExecutor(new ThreadFactory() {
     8            @Override
     9            public Thread newThread(Runnable r) {
    10                Thread thread = new Thread(r);
    11                thread.setName("My fancy Tango server");
    12                thread.setDaemon(true);
    13                return thread;
    14            }
    15        });
    16
    17        @Before
    18        public void before() throws InterruptedException {
    19            System.setProperty("OAPort", NO_DB_GIOP_PORT);
    20
    21            final CountDownLatch latch = new CountDownLatch(1);
    22            server.submit(new Runnable() {
    23                @Override
    24                public void run() {
    25                    ServerManager.getInstance().start(new String[]{NO_DB_INSTANCE, "-nodb", "-dlist", NO_DB_DEVICE_NAME}, TestServer.class);//TestServer - is our Tango server we want to test against
    26                    latch.countDown();
    27                }
    28            });
    29            //make sure server has been started before leaving method
    30            latch.await();
    31        }
    32
    33        @After
    34        public void after() {
    35            server.shutdownNow();
    36        }
    37
    38
    39        @Test
    40        public void testGetClientId() throws Exception {
    41            TangoProxy proxy = TangoProxies.newDeviceProxyWrapper("tango://localhost:" + NO_DB_GIOP_PORT + "/" + NO_DB_DEVICE_NAME + "#dbase=no");
    42
    43            assertSame(DeviceState.ON,proxy.readAttribute("State"));
    44        }
    45    }

It is also possible to use 3rd party Tango server to test against. Suppose our Tango server uses other Tango server to perform its tasks (Data aggregation, state monitoring etc). In this case 3rd party binary can be added to the project. This binary can be then launched from within JUnit test:

     1    //this test cases uses precompiled TangoTest stored in {PRJ_ROOT}/exec/tango/<win64|debian> folder
     2    public class TangoTestProxyWrapperTest {
     3        public static final String TANGO_DEV_NAME = "test/local/0";
     4        public static final int TANGO_PORT = 16547;
     5        public static final String TEST_TANGO = "tango://localhost:" + TANGO_PORT + "/" + TANGO_DEV_NAME + "#dbase=no";
     6        public static final String X64 = "x64";
     7        public static final String LINUX = "linux";
     8        public static final String WINDOWS_7 = "windows 7";
     9        public static final String AMD64 = "amd64";
    10
    11        private static Process PRC;
    12
    13        @BeforeClass
    14        public static void beforeClass() throws Exception {
    15            String crtDir = System.getProperty("user.dir");
    16            //TODO define executable according to current OS
    17            String os = System.getProperty("os.name");
    18            String arch = System.getProperty("os.arch");
    19            StringBuilder bld = new StringBuilder(crtDir);
    20            //TODO other platforms or rely on the environmet
    21            if (LINUX.equalsIgnoreCase(os) && AMD64.equals(arch))
    22                bld.append("/exec/tango/debian/").append("TangoTest");
    23            else if (WINDOWS_7.equalsIgnoreCase(os) && AMD64.equals(arch))
    24                bld.append("\\exec\\tango\\win64\\").append("TangoTest");
    25            else
    26                throw new RuntimeException(String.format("Unsupported platform: name=%s arch=%s", os, arch));
    27
    28            PRC = new ProcessBuilder(bld.toString(), "test", "-ORBendPoint", "giop:tcp::" + TANGO_PORT, "-nodb", "-dlist", TANGO_DEV_NAME)
    29                    .start();
    30
    31            //drain slave's out stream
    32            new Thread(new Runnable() {
    33                @Override
    34                public void run() {
    35                    char bite;
    36                    try {
    37                        while ((bite = (char) PRC.getInputStream().read()) > -1) {
    38                            System.out.print(bite);
    39                        }
    40                    } catch (IOException ignore) {
    41                    }
    42                }
    43            }).start();
    44
    45            //drains slave's err stream
    46            new Thread(new Runnable() {
    47                @Override
    48                public void run() {
    49                    char bite;
    50                    try {
    51                        while ((bite = (char) PRC.getErrorStream().read()) > -1) {
    52                            System.err.print(bite);
    53                        }
    54                    } catch (IOException ignore) {
    55                    }
    56                }
    57            }).start();
    58        }
    59
    60        @AfterClass
    61        public static void afterClass() throws Exception {
    62            PRC.destroy();
    63        }
    64
    65        //this test directly writes/reads  to/from TangoTest double_scalar_w
    66        @Test
    67        public void testWriteReadAttribute_Double() throws Exception {
    68            TangoProxy instance = TangoProxies.newDeviceProxyWrapper(TEST_TANGO);
    69
    70            instance.writeAttribute("double_scalar_w", 0.1984D);
    71
    72            double result = instance.<Double>readAttribute("double_scalar_w");
    73
    74            assertEquals(0.1984D, result);
    75        }
    76
    77        //in other test case one can create instance of his own server (see previous code snippet)
    78
    79    }

### Contributing

Instructions on how to contribute to the main projects within Tango can be found under this section. This include contributing to:

- The Tango IDL: the CORBA IDL file

- cppTango: the C++ library for Tango

- pyTango: the Python bindings for Tango

- jTango: the Java implementation of the Tango Kernel

- Tango Documentation: this documentation of the Tango project

#### Tango IDL

#### cppTango

For details on how to contribute to cppTango please see the contributing page on the GitLab project.

#### PyTango

For information on how to contribute to PyTango, see the project’s How to Contribute guide.

#### JTango

#### Tango Documentation

audience:developers

Please follow these guidelines when writing or improving the Tango Controls documentation in order to keep it as consistent as possible. It is also important to know how the contents is structured. You will find necessary information below.

##### About this documentation

The documentation is built using the documentation generator Sphinx based on Docutils. The source content is written in MyST Markdown.

The documentation source files are stored on GitLab: tango-controls/tango-doc .

It is publicised in HTML on the readthedoc.io: http://tango-controls.readthedocs.io/

###### Source structure

###### Versions

The readthedocs.io allows for various versions of published documentation. These are achieved by specifying branches in the GitLab repository. The branches for the official Tango Controls versions are named numerically with the format: #.#.#.

###### Chapters and headers

Chapters’ order is defined by the master table of contents, contained in the file `source/index.md`. This file references further `index.md` files contained in the subdirectories.

###### Glossary

Centralised definitions of the main concepts of Tango Controls are contained in the `source/Reference/glossary.md` file. Entries defined there may be referenced from anywhere in the documentation using the format `{term}`xxx``, e.g. `{term}`device`` produces the following link to the `device` definition in the Glossary device

###### Images

Images should be stored in a subdirectory of the directory where the source documentation is stored. As an example please refer to `source/tools/pogo`. When a folder contains more than one Markdown file then the directory containing images referenced by that file should be named the same as the Markdown file, for example see `source/How-To/installation/tango-on-windows`.

###### Configuration

###### source/conf.py

This is a standard build configuration file used by Sphinx. The project name, version, and copyright info are defined here. Please refer to conf.py documentation for further details.

###### requirements.txt

This is a standard pip requirements file used to specify Python packages version.

###### readthedocs.yml

This is the configuration file for the `readthedocs` application. The output format for Tango Controls is standard HTML.

##### Updating the documentation

If you find that some useful information is missing, misleading or improvements could be made please either:

- send a request through the GitLab project by creating a issue: tango-controls/tango-doc/issues

- or make the correction yourself and contribute this back to the project.

If you decide to contribute, the preferred way is to:

- fork the repository,

- create your own local branch containing the fixes,

- create a merge request from your forked branch to the tango-doc `origin/main` branch.

Note

GitLab online edit

For small fixes, you may use GitLab online editing feature. It is a good practice to avoid direct commits to the ‘main’ branch. Please select Create a new branch and start pull request before sending the change.

###### Building/previewing documentation locally

###### Prerequisites

To work with documentation, first you need to have the following programs installed on your system:

- Python >=3.10 (as Sphinx is a Python tool),

- Git (since the sources are kept in a git repository).

###### Building

To build the documentation you will need a Python virtual environment with Sphinx and other pip installable packages. To create this virtual environment and install the required packages:

    python3 -m venv .venv
    source .venv/bin/activate
    pip install --upgrade pip
    pip install -r requirements.txt

Build the html with:

    make html

This will build the HTML output in the `build` directory.You can then use any web browser to view the documentation, e.g.:

    firefox build/html/index.html

Note

Don’t see your changes?

Sometimes you will need to do a fresh build to see some changes. You can do this by first cleaning the build directory with:

    make clean

Followed by:

    make html

###### Contributing changes

1. Fork the tango-doc repository:

   From the tango-controls/tango-doc click Fork in the top right corner.

2. Clone your forked repository to work on it locally:

       git clone git@gitlab.com:<user>/tango-doc.git

   The `origin` remote will point to your forked repository.

3. Create your local working branch:

   Note

   The following command creates a branch based on `origin/main`. If you would like to contribute to another branch, e.g. directly to `9.2.5`, you need to use:

       git checkout -b update-docs-example origin/9.2.5

   To see what what branch is the current one use: git branch -a. The current branch is marked with an asterisk (*).

       git checkout -b update-docs-example origin/main

4. Edit the appropriate file (or create it if it doesn’t exist).

5. Make sure that the file appears in the relevant toc-tree (in the `index.md` file or in the master `source/index.md`).

6. Check if your changes have built correctly:

       make clean html

7. Check results with a browser (open the `build/html/<filename>.html`)

If everything is OK, follow the next steps to commit your changes and create a merge request, which will be reviewed and eventually merged online.

###### Committing changes

1. Add modifications to a commit list. For example:

       git add source/How-To/contributing/docs.md
       git add source/How-To/contributing/contributing.md

2. Commit the changes providing some meaningful message. For example:

       git commit -m "Updating docs"

   Note

   The changes are now committed to your local repository. You may continue editing, checking, and committing steps until you are happy with your work in order to track the history of changes. When finished you will need to share them by pushing your changes to the online repository.

3. If you continue to work locally on your branch for a long time it is good to perform a rebase to update your branch with any recent changes added by someone else. Firstly update your fork using the Update fork button on your forked repository. Then perform the rebase, for example:

       git fetch origin
       git rebase origin/main

   Note

   If you are contributing to other branch than `main`, for example directly to the `9.5.2`, you need to call

       git rebase 9.5.2

###### Pushing (to the GitLab repository)

1. Push your changes to your forked repository. For example:

       git push -u origin update-docs-example

Now you are ready to ask for your changes to be merged by creating a merge request on GitLab.

###### Pull request (asking for merge)

1. Go to your branch on your forked repository.

2. Click Create merge request.

3. Check the `From` and `into` locations. `From` should be the branch on your forked repository and `into` should be into the upstream repository `tango-doc:main` branch.

4. Provide a relevant comment and check that the `Commits` and `Changes` are as expected.

5. Click Create merge request.

Now, someone will review your contribution, merge into selected branch and publish. If they finds any issues, they will get back to you and request further changes.

##### Including Read the Docs Subprojects

Existing documentation from other projects can be included as a Read the Docs subproject of the parent tango-controls project. The subproject will appear under the tango-controls domain, e.g. the Jive project documentation is available from https://tango-controls.readthedocs.io/projects/jive. Links to subproject content can easily be included in the parent tango-controls project and vice versa. This prevents content from being repeated in multiple locations.

###### Setting up a subproject

The initial setup on Read the Docs needs to performed by someone who is a Read the Docs maintainer of both the parent tango-controls project and the subproject that you wish to include.

1. Add the project as a subproject of tango-controls on Read the Docs:

   From the Read the Docs project: Project > Admin > Subproject > Add subproject.

2. Configure the subproject mapping in the parent tango-controls project.

   In the `src/conf.py` add to the `intersphinx_mapping` definition:

       intersphinx_mapping = {
        'subproj_name': ('https://<subproject_name>.readthedocs.io/en/latest/', None)
        }

3. Configure the mapping in the subproject to be able to link to the parent tango-controls project

   In the subproject `src/conf.py` ensure the `intersphinx` extension is specified:

       extensions = [
        	...
        'sphinx.ext.intersphinx',
        ]

   In the subproject `src/conf.py` add to the `intersphinx_mapping` definition:

       intersphinx_mapping = {
        'tango-controls': ('https://tango-controls.readthedocs.io/en/latest/', None)
        }

4. In the parent tango-controls you can then reference sections in the subproject using the following link format:

       [Explicit text](inv:subproj_name:std#index)

5. Similarly in the subproject you can reference sections in the parent tango-controls project using the following link format:

       [Explicit text](inv:tango-controls:std#index)

Tip

You can list the references available in a project by using the `myst-inv` command (it comes with `myst-parser` and is available in the virtualenv you created).

    myst-inv https://<subproject_name>.readthedocs.io/en/latest/

You can filter by name with `-n` option. Check the help.

#### How to add a subproject to the readthedocs tango-controls documentation

audience:developers

##### Introduction

In some specific cases, it can be useful to integrate the documentation of an existing Tango-related project into https://tango-controls.readthedocs.io website as subproject while keeping the source of this documentation in its own independent repository.

The main advantages of this solution will be:

- the subproject documentation will be searchable from https://tango-controls.readthedocs.io website

- the subproject documentation can stay in the subproject repository, so the subproject documentation can be easily updated/synchronized when new features are implemented

##### Official Readthedocs documentation

Please refer to https://docs.readthedocs.io/en/stable/subprojects.html for the official readthedocs documentation describing how several documentation projects can be combined and presented to the reader on the same website.

##### Adding a subproject

To add a subproject to https://tango-controls.readthedocs.io website, you need to have admin rights on the readthedocs project you want to integrate and on tango-controls readthedocs project. If it’s not the case, please get in touch with the all mighty admins of these readthedoc projects. You can know who is admin by looking at TangoTickets Readme file.

You can then add one of the tango-controls readthedocs maintainers as maintainers of the project you want to add as subproject. To do that, you need to: 1- login on https://readthedocs.org with your readthedocs account 2- click on the project you want to integrate as subproject 3- click on the `admin` button/tab 4- click on the `maintainers` left menu entry 5- add one of the tango-controls readthedocs maintainers e-mail address or readthedocs username in the appropriate text field in the `Add maintainer` section and click on the `Add` button.

Then the tango-controls readthedoc admin which is now co-maintainer of the subproject will have to follow the procedure described on the official Readthedocs documentation to know how to add a subproject.

Once this step is done, the subproject documentation of a subproject named tangosubproject will be available under https://tango-controls.readthedocs.io/projects/tangosubproject/ URL.

Note

When adding a subproject, you can specify an alias name for the subproject (e.g. tangosubprojectalias). This name will be used in the URL used to access to the subproject documentation. e.g.: https://tango-controls.readthedocs.io/projects/tangosubprojectalias/ Please refer to https://docs.readthedocs.io/en/stable/subprojects.html#using-aliases for more details.

##### Use Intersphinx to reference your subproject from other projects

You should use Intersphinx to reference your subproject from tango-controls readthedocs and other readthedocs projects. Please refer to the ReadTheDocs official documentation to learn how to configure your subproject to use Intersphinx.

## Tools

### Developer’s Toolkit

audience:developers lang:all

Tango is a developer’s toolkit. There are many libraries and tools available for implementing device clients and servers.

#### C++ and Python

This clickable map shows the libraries and tools available for C++ and Python developers.

#### Java

This clickable map shows the libraries and tools available for Java developers.

### Tango Application Toolkit “ATK”

audience:developers lang:java

Tango Application ToolKit, (ATK) is a TANGO client framework for building GUIs based on Java Swing.

Speeding up the development and standardizing look and feels ATK provides several swing based components to view and/or to interact with Tango device attributes and Tango device commands and also a complete synoptic viewing system.

Implementing the core of “any” Tango application ATK takes in charge the automatic update of device data either through Tango events or by polling the device attributes. ATK takes also in charge the error handling and display.

The ATK swing components are the Java Beans, so they can easily be added to a Java IDE (like NetBeans) to speed up the development of graphical control applications.

ATK is composed of two jarfiles ATKCore.jar and ATKWidget.jar. You can download them from the ATK release page.

A tutorial on how to develop clients using the TangoATK is available in the tutorial section.

### Astor

See Astor documentation project.

### Jive

See Jive documentation project.

### Starter

### LogViewer

audience:all

#### Overview

LogViewer is an application for displaying logs pushed by the devices to the Tango logging system. You can fetch logs from different Tango devices and filter them by several properties to find what you need.

LogViewer 2.0.1 main window

1 - Menu bar

2 - Filter settings

3 - Clear button - clears the logs window

4 - Pause button

5 - Device tree

6 - Logs

7 - History of actions

##### Tango Logging

Tango provides a logging service for devices. It is configured by several device properties, and support sending logs to various targets, such as files, log consumer devices or the console. For more information you can check out more details about logging service .

#### Adding devices

To add logs from device to the viewer you need to right click on the device in the device tree and click add. Then you need to set which level logs from that device should be visible. You can do this with Set Logging Level options in the device context menu.

Add logs from device

Alternatively, you can add device with log level already set with Add/Set Logging Level context menu option.

Add and set logging level

The new device logs will then be displayed in LogViewer window.

##### Add colocated devices

The Add Colocated option adds all devices, running on the same device server instance, as logging sources. For example, if you have a `TangoTest/test` instance with `sys/tg_test/1` and `sys/tg_test/2` devices, choosing Add Colocated on any of these devices will add both of them. The same applies for Remove Colocated and Set Logging Level (colocated) options.

#### Actions menu

The actions menu provides access to some useful functions:

Actions menu

- Refresh Device Tree - refresh devices

- Logging Source List - list all added devices in the history window

- Remove All Logging Source - remove all devices from source list

- Clear history - clear history window

#### Loading logs from files

With the File > Load file menu option you can load a log file generated by the devices which logging target is set to file. This allows you to use the LogViewer features to filter and search through the logs offline. The log file format is XML.

### Tango Admin utility

audience:administrators

The Tango Database can be maintained using a command-line interface with the tango_admin tool.

The following features are available:

- Ping the tango database server

- Check if a device/device server is defined

- Create/Delete a server

- Create a property

- List server/devices

- Unexport devices

#### Tango Admin help

    tango_admin --help

Output:

    Usage:
     --help  		Prints this help
     --ping-database	[max_time (s)] Ping database
     --check-device <dev>    Check if the device is defined in DB
     --check-device-exported <dev>    Check if the device is exported in DB
     --add-server <exec/inst> <class> <dev list (comma separated)>   Add a server in DB
     --delete-server <exec/inst> [--with-properties]   Delete a server from DB
     --check-server <exec/inst>   Check if a device server is defined in DB
     --server-list  Display list of server names
     --server-instance-list <exec>   Display list of server instances for the given server name
     --add-property <dev> <prop_name> <prop_value (comma separated for array)>    Add a device property in DB
     --delete-property <dev> <prop_name>   Delete a device property from DB
     --tac-enabled Check if the TAC (Tango Access Control) is enabled
     --ping-device <dev> [max_time (s)] Check if the device is running
     --ping-network [max_time (s)] [-v] Ping network
     --unexport-device <dev> Unexport a device from DB.  USE WITH CARE.

This is the output of version 1.24.

#### Examples

##### Adding a device server

    # For python this is the device server source file name without `.py` extension,
    # for C++ this is the executable name
    executable=fancyMotor

    class=fancyMotorClass

    instance=1
    device=vacuum/innerCircle/valve1

    tango_admin --add-server "${executable}/${instance}" $class $device

##### Adding a device server property

    device=vacuum/innerCircle/valve1

    tango_admin --add-property $device RemotePort 127.0.0.1

##### Checking if a device server is alive

    tango_admin --check-device sys/database/2

##### Checking if a device server is exported

    tango_admin --check-device-exported sys/tg_test/1

### JDraw

See Jdraw documentation project.

### ITango

ITango is a PyTango CLI based on IPython. It is designed to be used as an IPython profile.

See ITango documentation.

### Taurus (Python GUI library)

audience:developers lang:python

Taurus is a Python framework for control and data acquisition CLIs and GUIs in scientific/industrial environments. It supports multiple control systems or data sources: Tango Controls, EPICS, spec… New control system libraries can be integrated through plugins.

For non-programmers: Taurus allows the creation of fully-featured GUI (with forms, plots, synoptics, etc) from scratch in a few minutes using a “wizard”, which can also be customized and expanded by drag-and-dropping elements around at execution time.

For programmers: Taurus gives full control to more advanced users to create and customize CLIs and GUIs programmatically using Python and a very simple and economical API which abstracts data sources as “models”.

Taurus - based on Python and PyQt or PySide. Widely used by the Python and other communities.

Download taurus from PyPi. Source code is on Gitlab.

You can find its full documentation here.

### PANIC Alarm System

PANIC is a set of tools (api, Tango device server, user interface) that provides:

- Periodic evaluation of a set of conditions.

- Notification (email, sms, pop-up, speakers)

- Keep a log of what happened. (files, Tango Snapshots)

- Taking automated actions (Tango commands / attributes)

- Tools for configuration/visualization

- The Panic package contains the python AlarmAPI for managing the PyAlarm device servers from a client application or a python shell. The panic module is used by PyAlarm, Panic Toolbar and Panic GUI.

All the information connected to the project can be found visiting the following links:

- PANIC on Gitlab

- PANIC presentation at PCAPAC'14

- PANIC documentation

### POGO

audience:developers lang:c++ lang:java lang:python

- Pogo is the TANGO code generator written in Java swing.

- This graphical interface allows to define a TANGO class model.

- This class model will be saved in a .xmi file.

- From this TANGO class model Pogo is able to generate:

    - a device server in C++, Java or Python.

    - An html documentation based on information entered during class model creation.

- The code generation part is based on EMF (Eclipse Model Framework) associated with Xtext and Xtend classes.

- Requirement: This tool needs java 1.7 or higher to be able to run.

- Source:

  > project: tango-controls/pogo

  > download: https://repo1.maven.org/maven2/org/tango-controls/Pogo/

### Bensikin User Manual

audience:all

Authors:

R.Girardot, M.Ounsy, A.Buteau, M.Thiam - Soleil

#### Introduction

This document is an end-user guide to using the Bensikin application, and a brief developer-oriented presentation of the application’s architecture.

#### Application’s context: Contexts and Snapshots

A snapshot is, as said in the name, a “picture” of a list of equipment’s “settings” (more precisely of their Tango attributes values) taken at a precise instant.

A snapshot is based on a context, which is a group of Tango devices and their attributes that must be part of the snapshots. A context is described by meta-data (author, description, etc.), so that users know which context is used what for.

A typical use case of a snapshot is to memorize a particular configuration of a set of equipment’s, to be able in the future to reset them to the values of this snapshot (for example, reposition all Insertion devices to their parking position after a beam loss).

#### Application’s description and goals

##### Application’s goals

Bensikin allows the user to define contexts and take snapshots. Snapshots can be saved as files and modified.

Bensikin is ready for multi-user use, which has for consequence the need to define accounts. An account is a way to map a user with a working directory. An important consequence of this is that 2 different users must not use the same working directory, or you may encounter application misbehaviors. An account has a name and a path to a working directory.

Bensikin is thus naturally divided (both in functionalities and display) in three parts:

- The account part, which is an introduction to the rest of the

    application

- The context part

- The snapshot part

##### A first look to Bensikin

The Bensikin Account Manager is here to manage accounts, which means:

- Creating a new account

- Deleting an existing account

- Launching application with an account chosen in a list

  Figure 1: Bensikin Account Manager

The Context Control Panel is where user can manage contexts, which means creating, loading and modifying contexts, and launching snapshots based on the defined contexts.

The Snapshot Control Panel is where user can manage snapshots, which means saving snapshots in files, loading snapshots from database and files, temporary modify snapshots attributes values and set equipment with defined snapshots (with or without modifying snapshots) or a subpart of them.

The application’s logs panel displays the application information and error messages (like database interaction, encountered problems, etc.)

The Menu and the Tool bar are for actions shortcuts and application’s options.

Figure 2: Bensikin main panel

#### Account Manager

The Figure 1: Bensikin Account Manager presents the account Manager Interface, on application start. With this manager, you can create a new account, or delete or use an existing one.

To quit the application, simply click on button.

Existing accounts are listed in the account Selection Combo Box, which you can reload by clicking on button (if you think that someone could have modified it by creating a new account or deleting an existing one, for example).

##### Creating a new account

To create a new account, click on the button (at the bottom left of the panel). A new dialog will appear, as following.

Figure 3: Creating a new account

In this new dialog, you will have to enter the name of your new account and the path of the application working directory for this account. If you prefer, you can browse for the path by clicking on the button. Then, a classic browsing dialog will be displayed, in which you can choose the directory. When both fields (Name and Path) are fulfilled, click on button to validate your new account, which will be automatically added in the list of existing accounts. If you click on button, you go back to the first dialog, as presented in Figure 1: Bensikin Account Manager , and nothing is done.

##### Deleting an existing Account

To delete an existing account, first select the account in the account selection combo box, as following:

Figure 4: Account selection

When the account is selected, click on Delete button to delete it. If you do it, you won’t be able to use this account any more (and no other user either), because the account is definitely removed from list. The account deletion doesn’t involve the corresponding directory (neither its content) deletion.

If you want to see your account path, you can check Show account path.

Figure 5: Show account path

##### Launching application with an existing account

To launch application with an existing account, first select the account in the account selection combo box, as presented in Figure 4: Account selection .

Then, click on Ok button, and you will reach the application main panel configured with this account (the account name is displayed in frame title).

#### Contexts Management

This section describes how to control contexts with Bensikin. A context is a list of attributes for which you can make a snapshot. A context has an ID and a creation date, both defined by the database. A context also has a name, an author, a reason and a description. The reason usually describes why the context was created (example: because of an incident or in order to set some equipment), whereas the description is here to have an idea of what kind of attributes you will find in this context.

Contexts are managed in the context control panel:

Figure 6: Context control panel

##### Creating a new context

To create a new context, click on the new icon in toolbar (), or choose option to make a new context from File menu or Contexts menu:

You also are ready to make a new context at application first start or by clicking on the reset icon ():

Figure 7: Application first start

The difference between the reset icon() and the new icon () is, that the “reset” icon will clear every panel, whereas the “new” icon will only clear the snapshot list and the Context Details sub panel.

###### Classic way (tree)

The tree on the left side of the Context Details sub panel allows you to check for available attributes. The one on the right side represents your context attributes.

To add attributes in your context browse the left tree, select attributes (represented by the icon ), and click on the green arrow to transfer them to the right tree.

To remove attributes from your context, select them in the right tree and click on the red cross .

Finally, fill the context Meta data (Name, Author, Reason and Description) in the corresponding fields (Note that filling the fields activates the register button ).

Then, you can save your context in database by clicking on the register button .

Doing so will deactivate the register button and activate the launch snapshot button .

You can save your context in a file using the save icon .

###### Alternate way (table)

To select this alternate way, go to tools menu and select options.

Then select the context tab and click on the table radio button.

Figure 8: Option –context tab

Click on the ok button. The context panel now has the “table selection mode”.

Figure 9: Bensikin with context table selection mode (new context)

- Attribute selection and automatic attributes adding:

  - Choose a Domain. This refreshes the list of possible Device classes for this Domain.

  - Choose a Device class. This refreshes the list of possible Attributes for this Domain and Device class.

  - Choose an Attribute and press OK:

    All Attributes with the selected name AND belonging to any Device of the selected Class and Domain are added to the current Context’s list of attributes.

  All new attributes are light red until the Context is registered.

- Line level sub-selection of loaded attributes:

  Each attributes are initially checked, but this check can be removed by the user. When the user clicks on validate, all unchecked attributes will be removed from the current Context.

  - Click All to select all lines

  - Click None to select no lines

  - Highlight lines in the list (CTRL and SHIFT are usable), then click Reverse highlighted to reverse the checked/unchecked status of all highlighted lines.

As for the classic way, you will have to fill the Meta data fields and register your context in database by clicking on the register button

##### Modifying an existing context

As a matter of fact, you can’t really “modify” a context. What you can do is to create a new context with its information (attributes and Meta data) based on another one.

The very difference is in alternate mode, where former attributes are in white and new ones in light red:

Figure 10: Bensikin with context table selection mode (modified context)

The “register” button changed a little too: its text is Register this context instead of “Register this new context”, as you can see on the figure above.

##### Loading a context

There are 2 ways to load a context:

- Load it from the database

- Load it from a file

In both cases, loading a context will apply a quick filter on the snapshot list, so you can see the snapshots about this context that have been created this day (the day when you load the context).

###### Loading a context from database

In the Contexts menu, choose load then select DB:

A dialog will then appear to allow you to filter the list of contexts in database following different criteria:

Figure 11: Data base Context filter dialog

Select no criterion to search for all contexts present in database. Click on the button to apply the filter. The list of corresponding contexts will then appear in the Context List sub panel, as shown in Figure 6: Context control panel . Double click on a context in table to load it and see its details in the Context Details sub panel (See Figure 6: Context control panel ).

If there are too many contexts in the list, you can remove some contexts from list (not from database) by selecting them in list and clicking on the cross on the top right corner of the list:

###### Loading a context from file

In the Contexts menu, choose load then select File, or in File menu choose load then select Context:

A classic file browser will appear. Search for your “.ctx” file and select it to load the corresponding context in the Context Details sub panel (See Figure 6: Context control panel ).

##### Printing a context

Once you have context ready, click on the print icon () and select context:

The classic print dialog will then appear. Validate your print configuration to print an xml representation of your context.

User can also print context by pressing the button

##### Saving a context

Once you have context ready, click on the save icon () and select context:

You can also go to menu Contexts and click on save, or go to menu File, select Save and click on Context.

Then, the behavior is “Word-like”. This means that if this is the first time you save this context, you will see the classic file browser to choose where to save your context, with file name. However, else, it will automatically save in the corresponding file. If you want to save in another file, you have to go to File menu, select Save As and click on Context or go to Contexts menu and click on Save As:

#### Snapshot Management

This section describes how to control snapshots with Bensikin. A Snapshot is a view of your equipment at a precise date, view based on a context. A Snapshot has an ID, a creation date (Time), and a comment to describe it (which can be left empty).

Snapshots are managed in the snapshot control panel:

Figure 12: Snapshot control panel

##### Creating a new snapshot

To create a new snapshot, first select a valid context in the context control panel (see Figure 6: Context control panel ). Then click on the button . The corresponding snapshot is added in the list of snapshots in the Snapshot List sub panel.

##### Loading a snapshot

There are 2 ways to load a snapshot:

- Load it from the database

- Load it from a file

###### Loading a snapshot from database

Loading a snapshot from database consists in adding this snapshot in the list of snapshots in the Snapshot List sub panel.

As you can see in Figure 12: Snapshot control panel , the Snapshot List sub panel allows you to filter snapshots from database to find the snapshot you want to load. However, have in mind that this filter is “context dependent”, which means that the snapshots which will appear in the list by clicking on the filter button () are the one that correspond to your filter criteria AND the selected context in the Context Control Panel. If the filter is cleared (which you can obtain by clicking on the button ), you will search for all the snapshots in database that correspond to the selected context.

###### Loading a snapshot from file

In the Snapshots menu, choose load then select File, or in File menu choose load then select Snapshot:

A classic file browser will appear. Search for your “.snap” file and select it to load the corresponding snapshot in the Snapshot Details sub panel (See Figure 12: Snapshot control panel )

##### Editing a snapshot

To edit a snapshot, double click on the snapshot you want to edit in the snapshot list (in the Snapshot List sub panel). This will open a new tab about this snapshot in the Snapshot Details sub panel, tab named by this snapshot ID. If you load a snapshot from file, the name of the tab is the name of the file. To differentiate snapshots loaded from file and the ones loaded from database, the snapshot loaded from file tabs have the icon .

##### Setting equipment with a snapshot

A snapshot allows you to set equipment with its attributes write values. You can choose which attributes will set equipment, and which not, by selecting or unselecting the corresponding check box in the column Can Set Equipment (See Figure 12: Snapshot control panel ). By default, every attribute is selected. If you unselect some attributes, an icon will appear in tab title to notify you that these attributes will not set equipments. You can quick select/unselect all the attributes by clicking on All and None buttons. When you are ready to set equipment with the selected write values, click on the button .

You can also modify the write value before setting equipment by editing it in the table. If you do so, the value becomes red and an icon appears to warn you about the fact that you made modifications in this snapshot (these modifications will not be saved in database, they are just here to set equipment).

Figure 13: Modified snapshot

##### Snapshot comparison

There are 2 ways to compare snapshots:

- Compare a snapshot with another one:

  To do so, select a tab in Snapshot Details sub panel (Figure 12: Snapshot control panel ). Click on button . You will see the tab title of this attribute appear in the field “1st snapshot”. Select another tab and click again on button to put this attribute tab title in the field “2nd snapshot”. Click then on button to see the comparison between these 2 snapshots.

  If user wants to see only the first line of comparison, he must check filter

  Else if he/she wants to see all the details of the comparison, he/she must check

  Figure 14: Snapshot comparison - full table

  To print this comparison table, click on Print button.

- Compare a snapshot with current state:

  To compare a snapshot with current state, set this snapshot as “1st snapshot”, as explained above, and leave the “2nd snapshot” empty. Note that once the “1st snapshot is selected, you only can update the “2nd snapshot or clear the comparison selection. To do so, click on the button . What is hidden behind this is a creation of a snapshot, named “BENSIKIN_AUTOMATIC_SNAPSHOT”, and you compare this snapshot with your snapshot. Have in mind that this automatic snapshot is registered in database. So, in the comparison table, the current state will appear as the second snapshot with the name “Current state” (red block in the comparison table).

##### Snapshot Details copy

As you can see in Figure 12: Snapshot control panel , snapshots are detailed in a table. You can copy this table to clipboard as a text-CSV formatted table by clicking on or button. If you want to see this text result and may be filter it (like removing lines), click on button. You will see the text appear in a dialog.

Figure 15: Snapshot edit clipboard dialog

##### Modifying a snapshot comment

Once your snapshot details are loaded, click on button to modify its comment (and save it in database or file).

##### Printing a snapshot

Once you have context ready, click on the print icon () and select snapshot:

The classic print dialog will then appear. Validate your print configuration to print an xml representation of your snapshot.

##### Saving a snapshot

Once you have context ready, click on the save icon () and select snapshot:

You can also go to menu Context and click on Save, or go to menu File -> Save -> Snapshot.

Then, the behavior is “Word-like”. This means that if this is the first time you save this snapshot, you will see the classic file browser to choose where to save your snapshot, with file name. However, if not, it will automatically save in the corresponding file. If you want to save in another file, you have to go to File menu, select Save As and click on Snapshot, or go to Snapshots menu and click on Save As.

#### Favorites

Bensikin manages a list of favorite context, so you can quickly switch to anyone of them. Those favorites are saved at application shutdown and loaded on startup.

##### Adding a context to favorites

To add a context to your favorites, have your context ready by creating or loading it. Then go to Favorites menu and click on Add selected context.

##### Switching to a context in favorites

To switch to a context in favorites, which means to load it from favorites, go to “Favorites” menu, select “contexts”, and click on the context you want to load.

#### Options

Bensikin manages global options. Those options are saved at application shutdown, and loaded on startup. The Options menu is located in the Menu bar: Tools -> Options.

##### Application’s history save/load Options

Define whether Bensikin has a history, i.e. a persistent state when closed/reopened.

If yes is checked, a XML History file will be saved in Bensikin’s workspace, and on next startup the current Context and Snapshot will be loaded.

Figure 16: Save option

##### Snapshot Options

These are the Bensikin Snapshot Options:

Figure 17: Snapshot options

- In the Comment Panel, you can choose to automatically set or not a value to a new snapshot comment. This means, when you click on button, the newly created snapshot will or will not have a pre-defined comment.

- In the Comparison Panel, you can choose which columns you want to show/hide for every block in the Snapshot Comparison table. You can choose to show/hide the Difference block too (See Figure 14: Snapshot comparison - full table )

- In the Export Panel, you can choose the column separator for your text-CSV formatted tables (See Figure 15: Snapshot edit clipboard dialog ), and which columns to export.

##### Context Options

Context options allow you to select which way you want to edit your contexts, see Figure 8: Option –context tab and the Creating a new snapshot section.

##### Print Options

The Print option allows you to print text or table in the Snapshots or in the Contexts.

Figure 18: Print option

When you check , you adapt the size of your print to the size of your page.

When you check , you cut the length of your print on several parts and the width of your print takes the width of your page.

When you check , the length and the width of your print are cut on several parts.

#### The Bensikin toolbar

The toolbar is located under the menu bar, and consists mainly of a set of shortcuts to often used functionalities.

Figure 19: Bensikin toolbar

- is a shortcut to creating a new Context

- is a shortcut to saving the selected Context/Snapshot into a Context/Snapshot file

- is a shortcut to doing a saving all opened Contexts and Snapshots

- is a shortcut to printing the xml representation of the current Context/Snapshot

- is a shortcut to removing all opened Contexts and Snapshots from display

### Taranta

audience:all lang:javascript

Taranta (Webjive until v.1.1.5) is a web application that allows a user to create a graphical user interface to interact with Tango devices. The interface may include a variety of charts, numerical indicators, dials, commands that can be used to monitor and to control devices. Very little knowledge of web technologies is needed.

With Taranta you can use your web browser to:

- View a list of all Tango devices

- View and modify device properties

- View and execute device commands

- Create dashboards for interacting with Tango devices.

See Taranta documentation

### Tango REST API

audience:developers lang:all

Warning

The Tango REST API is in life support mode.

The Tango collaboration will not make any improvements or fix bugs. If you are interested in becoming its maintainer, please get in touch with us on our mailing list, in our forum, or at our bi-weekly Tango kernel meetings.

Tango provides a REST API implementation. It can be used to integrate Tango with 3rd party products using the http protocol instead of the Tango protocol. A simple example could be a mobile client application for monitoring Tango.

This section addresses:

- What is REST and why it is useful?

- Getting started with simple example

- Example of a safe REST API deployment at ESRF

- Further steps

#### Tango REST API specification

In short a REST API exports resources. These resources are accessible using URL addresses. Clients can then perform actions on these resources using a set of http methods. Typically these are:

- `GET` - request a resource

- `PUT` - update a resource

- `POST` - create a new resource

- `DELETE` - remove a resource

The Tango REST API exports Tango hosts; Tango devices; Tango device attributes, commands and pipes. So it follows Tango Device Server Model.

For example, one can request Tango Host using Tango REST API:

    GET tango/rest/rc4/hosts/tango_host/10000

Here one uses the Tango REST API implementation related to version RC4 as indicated by the `tango/rest/rc4` part of the example URL. The `hosts` part will export the Tango Host accessible through this Tango REST API implementation and the `tango_host/10000` assumes that the `TANGO_HOST` address is set to `tango_host:10000`.

In the following example a client makes a request for a device attribute:

    GET tango/rest/rc4/hosts/tango_host/10000/devices/sys/tg_test/1/attributes/double_scalar/value

Again here `tango/rest/rc4` relates to Tango REST API version RC4 implementation; `hosts/tango_host/10000` relates to `TANGO_HOST=tango_host:10000`; the `devices/sys/tg_test/1` relates to the device `tango://tango_host:10000/sys/tg_test/1` exported through the Tango REST API and finally `attributes/double_scalar/value` relates to the value of the attribute.

This third example shows how a client can execute a command on a device:

    PUT tango/rest/rc4/hosts/tango_host/10000/devices/sys/tg_test/1/commands/DevDouble?v=3.14

Here the client executes `tango://tango_host:10000/sys/tg_test/1/DevDouble` with the input `arg=3.14`.

#### Tango REST API implementations

Since the Tango REST API itself is only a specification one needs an actual implementation running somewhere.

Known implementations are listed in the Tango REST API readme.

Please refer to the corresponding implementation documentation on how to install and use it.

In a nutshell download the latest fat jar distribution and start the server via `java -DTANGO_HOST=localhost:10000 -jar mtangorest.server.jar -nodb -dlist sys/rest/0`/

One can test the installation by attempting to read an attribute value from a device. Typically there is a `TangoTest` server running on the Tango host, whihc can be accesses through a browser at:

    http://localhost:10001/tango/rest/rc4/hosts/localhost/10000/devices/sys/tg_test/1/attributes/double_scalar/value

showing:

    {
        "name":"double_scalar",
        "value":179.04696279859678,
        "quality":"ATTR_VALID",
        "timestamp":1493918496122
    }

#### Deployment

As Tango REST exports Tango via http to the internet the usual question is how to protect Tango from the unwanted activity?

The deployment of the Tango REST API can be made quite safe. Usually one wants to put the Tango REST API server behind a reverse proxy and restrict its access to a single Tango Host. A reverse proxy can also allow connections only via https.

As every request via REST API must be validated against Tango Access Control this adds an extra layer of security.

Below is a deployment scheme of REST API at ESRF. In this installation the REST API exports readonly forwarded attributes and is accessible via a secured http connection.

Every request passes through [HAProxy]https://www.haproxy.org/ configured to use the https protocol for a secure connection. On its backend HAproxy communicates with the Tango REST server, which in turn can access only the Tango host where a device of class ForwardComposer is defined. This device provides read only access to the `MStatus` Tango device with status information about the storage ring at ESRF.

In addition the Tango REST API can be integrated with authentication and authorisation services like [Kerberos]https://web.mit.edu/kerberos/#what_is.

Finally, the Tango REST API implementation should use Tango Access Control to validate every request made from the internet.

#### Further steps

- Install Tango REST API server locally or using docker.

- Develop your REST client or use a 3rd party framework.

- Deploy everything on the local network or in the cloud.

#### References

[1] Tango REST API specification on GitLab

## Reference

### RFC

audience:all

The Tango Controls RFCs (Request For Comments) are a collection of documents that contain the Tango Controls specification of concepts, terminology, Protocol behaviour and conventions. They are the definitive reference material and contain answers to all questions that are related to how Tango Controls is supposed to work.

### Ecosystem

audience:all

Tango Controls offers a rich ecosystem for developers and users alike.

Since Tango is a also a developer’s framework there are many libraries and tools for existing devices and clients. Refer to the overview for developers for more information.

Tango is designed to run small and large systems. To support managing large installations a number of administration tools are provided.

All control systems need to be able to archive data so that one can look at past data when needed. Tango comes with a number of archiving solutions and the archiving overview is our starting point for them.

In addition to the Python, C++ and Java APIs for Tango, a number of other languages and tools can be used together with Tango. Please have a look at our bindings overview where we list the currently known ones.

Here is the official list of the active Tango Ecosystem software:

Software | Maintainer / Institute | Description
--- | --- | ---
Taranta | Yimeng Li / MAX IV Laboratory | Web Front End to Tango with no-code Dashboard, synoptic, …
| Matteo Canzari / INAF |
Pogo | Damien Lacoste / ESRF | Code generator for tango device server, to help get started
|  | when writing device servers
TangoDatabase | Thomas Braun / byte physics | The database server using MariaDB/MySQL
| Graziano Scalamera / Elettra |
TangoTest | Thomas Braun / byte physics | Famous device server for testing
cppTango | Reynald Bourtembourg / ESRF | C++ implementation of Tango
| Damien Lacoste / ESRF |
| Thomas Juerges / SKAO |
| Thomas Braun / byte physics |
| Thomas Ives / OSL |
PyTango | Anton Joubert / MAX IV Laboratory | Python binding for Tango
| Yury Matveyev / DESY |
ITango | Benjamin Bertrand / MAX IV Laboratory | IPython extension for PyTango
dsconfig | Johan Forsberg / MAX IV Laboratory | Tango configuration management tools
| Benjamin Bertrand / MAX IV Laboratory |
tango-admin | Thomas Braun / byte physics | Commandline tool for various tango admin tasks
| Thomas Juerges / SKAO |
TangoSourceDistribution | Thomas Braun / byte physics | Integrated package of various tango tools
ATK | Jean-Luc Pons / ESRF | Java Application Toolkit
Jive | Jean-Luc Pons / ESRF | Tango Database Browser GUI
Starter | Nicolas Tappret / ESRF | device server to start/stop Tango device servers
Astor | Nicolas Tappret / ESRF | Graphical Tango control system administration tool
ATKPanel | Jean-Luc Pons / ESRF | Generic Java GUI to interact with a Tango device
| Nicolas Tappret / ESRF |
jdraw | Jean-Luc Pons / ESRF | Synoptic drawing tool (part of ATK project)
Tango Access Control | Nicolas Tappret / ESRF | Access control device server for Tango
C Language binding | Jens Meyer / ESRF | Tango C binding
Taurus | Oriol Vallcorba / ALBA | Python Qt GUI framework based on PyTango for device
| Benjamin Bertrand / MAX IV Laboratory | monitoring and control
| Arturo Hoffstadt / ESO |
Sardana | Oriol Vallcorba / ALBA | Experiment control toolkit based on PyTango and Taurus for
| Johan Forsberg / MAX IV Laboratory | device orchestration and data acquisition
| Teresa Nuñez / DESY |
| Michal Piekarski / SOLARIS |
fandango | Sergi Rubio Manrique / ALBA | Python Device Servers and CLI utilities library
panic | Sergi Rubio Manrique / ALBA | Alarm System based on PyTango (Device Server and Qt GUI)
HDB++ | Damien Lacoste / ESRF | High Performance Archiving System for Tango
| Reynald Bourtembourg / ESRF |
| Graziano Scalamera / Elettra |
| Sergi Rubio Manrique / ALBA |
| Johan Forsberg / MAX IV Laboratory |
pyhdbpp | Sergi Rubio Manrique / ALBA | python client for hdb++ (backend agnostic)
| Jose Ramos / ALBA |
| Damien Lacoste / ESRF |

### Tango Core: C++

#### cppTango API reference

Please refer to: C++ API documentation generated by Doxygen.

### Tango Core: Python

#### PyTango - a Python binding to Tango

Python is a commonly used programming language in the scientific community, due to its many advantages (the most important of those is probably simplicity of its syntax). Tango Controls also supports it in a form of a pybind11-based binding to the cppTango implementation.

In “pythonic” terms, it is a package available at PyPI that exposes the complete Tango API (both the client and the server parts of it) as well as provides a framework for unit-testing your device servers.

Note

You should use a PyTango version that has the same major and minor version numbers as cppTango that you have. So if you have cppTango version X.Y.Z, you should have PyTango version X.Y.V (where V might equal Z, but its not required).

#### PyTango usage

For instructions on how to use PyTango, the Python binding for Tango Controls, please refer to the PyTango documentation

#### PyTango API reference

Please refer to PyTango API documentation

### Tango Core: Java

#### JTango usage

For instructions on how to use JTango, the Java binding for Tango Controls, please refer to the JTango documentation.

#### JTango API reference

- JTango Server

- JTango Client

- JTango Common

### Bindings

#### C Language

audience:developers

C is supported for clients written in old style C (like SPEC). The C binding does not support all or the latest features of Tango. We strongly encourage everybody to use the C++ API instead because it will provide access to all features of Tango.

- C language

    - A minimal client binding for the good old C language

    - Release 3.0.2 including features of Tango 8 but no events

    - Source code distribution

#### Igor Pro

audience:all

- Client API for Igor Pro

- Release 3.0.0 for Igor Pro 7.x

    - Runs on Windows x64 [no official support for x86 any more]

    - Binary distribution for 7.x for Windows x64

- Release 2.5.0 for Igor Pro 6.x

    - Runs on Windows x86 & x64

    - Binary distribution for 6.x for Windows x86

- Source code available on GitLab

    - compiling this binding requires the WaveMetrics’ XOP Toolkit

#### LabVIEW

audience:developers

- Client and server API for LabVIEW

- Runs on Windows and Linux

- Provides 32 and 64 bits support on both platforms

- Release 3.0.0 for LabVIEW 2015

    - Binary distribution for Windows [LabVIEW 32 bits]

- Release 3.0.0 for LabVIEW 2014

    - Binary distribution for LabView 2014 on Windows [LabVIEW 64 bits]

    - Binary distribution for Linux [LabVIEW 32 bits]

- Patch for release 3.0.0

    - requires LabVIEW >= 2014

    - this Vi library contains some bug fixes, simply replace the original one in the /vis directory

- Source code available on GitLab

#### Matlab & Octave

audience:all

- Client API for Matlab and Octave

- Release 2.0.6 for Matlab >= R2009b or Octave >= 3.6.2

- Runs on Windows and Linux

- Provides 64 bits support on both platforms

    - the 32 bits mode is still available on Linux but could be abandoned in a near future

- Binary distribution 2.0.6 for Windows x86

    - tested with Matlab R2009b

- Binary distribution 3.1.0 for Windows x64

    - tested with Matlab R2016b

    - this release contains a major change - see the README file for details

- Source code available on GitLab

    - please visit the MathWorks web site in order to identify the official gcc version associated with your Matlab version

#### REST API

audience:all

- Server implementations

- Client implementations

The API specification is discussed in Tango REST API.

#### Overview

audience:all

Tango has a number of bindings to other languages and tools. Below we list the currently known bindings.

- C-language

- Igor Pro

- LabView

- Matlab and Octave

- REST API

It is quite possible that more exist. If you need a binding which is not listed here or if you would like to contribute a binding you have implemented just get in touch with us. Preferably you open a new issue on the Tango Ticks project at Gitlab.

### ATK Java documentation

audience:developers lang:java

The most important information Dependencies, Connection of models and views and etc. you will find following this link.

### Glossary

audience:all

Archiving

  In Tango archiving refers to storing Attributes in a Database. See HDB++. For more details please see the archiving section.

AtkPanel

  AtkPanel is a generic control panel application. It can be used to control any Tango device. It supports most of Tango features and data types. Using AtkPanel you can view and set attribute values, execute commands, test the device and access diagnostic data. For more details please see the atkpanel section.

Attribute

  An attribute represents a process value (or values) in the system. It may have different formats or dimensions like scalar(0D), spectrum(1D) or image(2D). The attribute allows to read and/or write these values depending on programmer-defined access. The values may have different data types. In addition, an attribute provides some metadata like attribute quality, timestamp or configuration properties. For a complete list please refer to the manual. A list of attributes available for a certain device is defined by its device class. For more details please see the attribute section.

attribute quality

  A value returned by an attribute has a runtime quality factor which is an enumeration describing the state of the read value (one of VALID, INVALID, ALARM, CHANGING, WARNING).

Attribute quality

  Another name for Attribute quality factor. For more details please see the attribute section.

Attribute quality factor

  A value returned by an Attribute has a runtime quality factor which is an enumeration describing the state of the read value (one of VALID, INVALID, ALARM, CHANGING, WARNING). For more details please see the attribute section.

AttributeProxy

  The AttributeProxy is a placeholder on the client side with exactly the same interface that a real Attribute exposes. Only when an operation on an AttributeProxy is performed, a connection to the Attribute of a real Device is attempted and on success the operation is performed. In case the Attribute of the real Device cannot be reached, a client-side Tango exception is raised. For more details please see the device-server-model section.

client

  In Tango a client is either a DeviceProxy or an AttributeProxy instance created by a program. For more details please see the device-server-model section.

command

  A command is an operation a user may invoke on a device (eg. `SwitchOn`, `SwitchOff`). It also relates to a specific method in OOP (Object-Oriented Programming). Tango Controls allows a command to get input argument (argin) and to return a value (argout). List of available commands for a certain device is defined by its device class. See the command section of this documentation for more details. For more details please see the command section.

CORBA

  The underlying technology for network operations is CORBA. This software package allows Tango to work with multiple programming languages as the network interface itself is defined in a markup language from which the code is generated from. For Tango the interface is defined in the tango-idl repository.

Databaseds

  The Tango device server of the TangoDB. For more details please see the tangodb section.

device

  A device is a key concept of Tango Controls. It is an object providing access to its attributes, pipes and commands. The list of attributes, pipes and commands available for a certain device is defined by its class. The device may be related to a hardware device it interfaces with or it may be a kind of a logical device providing some functionalities not directly related to hardware. For more details please see the device section.

device class

  A Device Class is an abstraction of a device’s interface. It defines attributes, pipes, commands and properties which a device of the class provides to users and to other components of a Tango system. A device class ofter relates to a specific kind of equipment it allows to interface with like a `SerialLine` class defines interface to communicate with serial line equipment. For more details please see the device section.

device pattern

  Provides programmers with a framework on which they can develop new control objects. For more details please see the device-server-writing section.

device server

  A Device Server is a program (executable) which is able to create devices of certain classes. A Device Server may implement one or multiple classes and instantiate one or more devices. A running device server is called a device server instance. For more details please see the device section.

device server instance

  A running device server is called a device server instance. So it means, it is a process. Every device server instance has an unique name in Tango Controls by which it can be referenced. The name is built as {DeviceServerName}/{instanceName}. For each running device server the system creates a special device of `DServer` device class: dserver/{DeviceServerName}/{instanceName}. This device provides a management facility for the corresponding device server instance (see DServer class device commands ). For more details please see the device section.

device state

  A device may be in a certain state which is determined at runtime. The state of a device may reflect the state of a piece of equipment it interfaces with or be determined in another way. The behaviour is defined by the device class which implements a state machine. The state may define attributes’, commands’ and pipes’ operations available at that moment. Tango Controls has a set of allowed states the device may be in; these are: ON, OFF, CLOSE, OPEN, INSERT, EXTRACT, MOVING, STANDBY, FAULT, INIT, RUNNING, ALARM, DISABLE, and UNKNOWN.

DeviceProxy

  The DeviceProxy is a placeholder on the client side with exactly the same interface that a real Device exposes. Only when an operation on a DeviceProxy is performed, a connection to the real Device is attempted and on success the operation performed. In case the real Device cannot be reached, a client-side Tango exception is raised. For more details please see the device-server-model section.

dynamic attribute

  A device may create attributes for which the configuration is determined during device initialization or even at runtime. This kind of attributes is called dynamic.

Dynamic Attribute

  A device can create Attributes that have their configuration determined during device initialization or even later when the Device is already running. This kind of Attribute is called Dynamic Attribute. For more details please see the attribute section.

Enumerated Attribute

  Attributes with a scalar data format can be enumerated allowing a set of defined label+value pairs. For more details please see the attribute section.

File Database

  The File Database is a lightweight, file-based alternative for the Tango Database for testing purposes and single device server setups. or more details please see the filedatabase section.

Forwarded Attribute

  A forwarded attribute is one that gets its configuration from another attribute, which is known as the root attribute. It will forward requests, configuration changes, event subscriptions and locking behaviour to the root attribute. For more details please see the attribute section.

HDB++

  Historical Database++ (HDB++) is the successor of HDB, Tango’s Attribute ariving solution. For more details please see the archiving section.

Interoperable Tango Reference

  Unique identifier for referencing remote device servers, based on the CORBA IOR

Memorized Attribute

  The last written value for this type of attribute will automatically be stored in the database so that on startup this value is fetched and written to the attribute. For more details please see the attribute section.

pipe

  A pipe allows to read and/or write structured data from and/or to a device. The data consists of one or more basic Tango data types. The structure of the data is defined by a device class but is not static. It can be changed at runtime by the device itself or modified upon request by a client according to the `set_pipe_config` operation provided by pipe. The list of available pipes in a device is defined by the device’s device class. For more details please see the pipe section.

Pogo

  Java tool for generating boiler plate code for C++/Java Tango Device servers. For more details please see the pogo section.

property

  A configuration parameter stored in the Tango Database. Properties can be assigned to a device class, device or elements of device interface (attributes, commands, pipes). Properties can be also not related to device - such properties are called `free properties`. Property values are often used by elements of Tango Controls system during its startup. These usually provides information required to configure things like connections to hardware or to adjust to user preferences. For more details please see the property section.

Property files

  Text format to store device classes and device servers including their properties. or more details please see the property-file section.

SCADA

  It is an abbreviation standing for Supervisory Control and Data Acquisition.

state machine

  A state machine for a device class defines the operations (commands’, attributes’ and pipes’ access) available in different states of a device.

Tango Access Control

  A device server that manages user’s rights to perform read/write requests on a particular device. For more details please see the access-control section.

Tango Controls

  Tango Controls is an object-oriented, distributed control system framework which defines a communication protocol, an Application Programmers Interface (API) and provides a set of tools and libraries to build software for control systems, especially SCADA. For more details please see the overview section.

Tango Core

  Tango Core is a set of main tools, libraries and specifications of the Tango Controls framework. It consists of libraries and API definitions for C++, Java and Python as well as tools to manage the system: Astor, Jive, etc.

Tango Database

  A combination of the Tango device server Databaseds and a MariaDB backend. It provides static and runtime configuration information about Tango Controls components in a Tango Controls system. It is used by the Databaseds device server and constitutes the Tango Host. For more details please see the tangodb section.

Tango Host

  Each Tango Controls system/deployment has to have at least one running DataBaseds device server. The machine on which DataBaseds device server is running has the role of a so called Tango Host. DataBaseds is a device server providing configuration information to all other components of the system as well as a runtime catalog of the components/devices. It allows (among other things) client applications to find devices in distributed environment.

Tango Resource Locator

  Schema used to identify tango objects, similiar to URLs. The Tango Resource Locator is defined in Tango’s RFC 16 For more details please see the naming section.

tango_admin

  Command line utility for administrative tasks related to the TangoDB For more details please see the tango-admin section.

TANGO_HOST

  An environment variable that specifies on which host and port a Databaseds device server is running. The host part and port are separated by a colon `:`. Commonly this is also referred to as the TangoDB. See also how to user multiple database servers. For more details please see the tangodb section.

TangoDB

  A shorter way to say Tango Databaseds device server and its MariaDB backend. For more details please see the tangodb section.

tangorc

  Tango configuration file holding various settings in the format `key=value`. This can be created globally in `/etc/tangorc` for unix-like OSes and in `${TANGO_ROOT}/tangorc` for Windows. Or for the current user only in `~/.tangorc` for unix-like OSes (not available for Windows). Environment variables with the same name override the entries from the files and the per-user file overrides the gobal one.

TDSOM

  The Tango device server object model. Abstract model how Tango devices and client interact. For more details please see the device-server-model section.

### Reference part

audience:developers lang:all

This chapter is only part of the TANGO device server reference guide. To get reference documentation about the:

- C++ library classes, see cppTango API reference.

- Java classes, see JTango API reference.

- Python classes, see PyTango API reference.

#### Device parameter

A black box, a device description field, a device state and status are associated with each TANGO device.

##### The device black box

The device black box is managed as a circular buffer. It is possible to tune the buffer depth via a device property. This property name is

device name->blackbox_depth

A default value is hard-coded to 50 if the property is not defined. This black box depth property is retrieved from the Tango property database during the device creation phase.

##### The device description field

There are two ways to initialise the device description field.

- At device creation time. Some constructors of the DeviceImpl class supports this field as parameter. If these constructor are not used, the device description field is set to a default value which is A Tango device.

- With a property. A description field defines with this method overrides a device description defined at construction time. The property name is

  device name->description

##### The device state and status

Some constructors of the DeviceImpl class allows the initialisation of device state and/or status or device creation time. If these fields are not defined, a default value is applied. The default state is Tango::UNKOWN, the default status is Not Initialised.

##### The device polling

Seven device properties allow the polling tunning. These properties are described in the following table

Property name | property rule | default value
--- | --- | ---
poll_ring_depth | Polling buffer depth | 10
cmd_poll_ring_depth | Cmd polling buffer depth |
attr_poll_ring_depth | Attr polling buffer depth |
poll_old_factor | Data too old factor | 4
min_poll_period | Minimun polling period |
cmd_min_poll_period | Min. polling period for cmd |
attr_min_poll_period | Min. polling period for attr |

The rule of the poll_ring_depth property is obvious. It defines the polling ring depth for all the device polled command(s) and attribute(s). Nevertheless, when filling the polling buffer via the fill_cmd_polling_buffer() (or fill_attr_polling_buffer()) method, it could be helpfull to define specific polling ring depth for a command (or an attribute). This is the rule of the cmd_poll_ring_depth and attr_poll_ring_depth properties. For each polled object with specific polling depth (command or attribute), the syntax of this property is the object name followed by the ring depth (ie State,20,Status,15). If one of these properties is defined, for the specific command or attribute, it will overwrite the value set by the poll_ring_depth property. The poll_old_factor property allows the user to tune how long the data recorded in the polling buffer are valid. Each time some data are read from the polling buffer, a check is done between the date when the data were recorded in the polling buffer and the date when the user request these data. If the interval is greater than the object polling period multiply by the value of the poll_old_factor factory, an exception is returned to the caller. These two properties are defined at device level and therefore, it is not possible to tune this parameter for each polled object (command or attribute). The last 3 properties are dedicated to define a polling period minimum threshold. The property min_poll_period defines in (mS) a device minimum polling period. Property cmd_min_poll_period defines (in mS) a minimum polling period for a specific command. The syntax of this property is the command name followed by the minimum polling period (ie MyCmd,400). Property attr_min_poll_period defines (in mS) a minimum polling period for a specific attribute. The syntax of this property is the attribute name followed by the minimum polling period (ie MyAttr,600). These two properties has a higher priority than the min_poll_period property. By default these three properties are not defined mening that there is no minimun polling period.

Four other properties are used by the Tango core classes to manage the polling thread. These properties are :

- polled_cmd to memorize the name of the device polled command

- polled_attr to memorize the name of the device polled attribute

- non_auto_polled_cmd to memorize the name of the command which shoule not be polled automatically at the first request

- non_auto_polled_attr to memorize the name of the attribute which should not be polled automatically at the first request

You don’t have to change these properties values by yourself. They are automatically created/modified/deleted by Tango core classes.

##### The device logging

The Tango Logging Service (TLS) uses device properties to control device logging at startup (static configuration). These properties are described in the following table

Property name | property rule | default value
--- | --- | ---
logging_level | Initial device logging level | WARNING
current_logging_level | Logging level applied to the current session | No default
logging_target | Initial device logging target | No default
current_logging_larget | Logging target applied to the current session | No default
logging_rft | Logging rolling file threshold | 20 Mega bytes
logging_path | Logging file path | /tmp/tango-<logging name> or C:/tango-<logging name> (Windows)

- The logging_level property controls the initial logging level of a device. Its set of possible values is: OFF, FATAL, ERROR, WARNING, INFO or DEBUG. This property is overwritten by the verbose command line option (-v).

- The logging_target property is a multi-valued property containing the initial target list. Each entry must have the following format: target_type::target_name (where target_type is one of the supported target types and target_name, the name of the target). Supported target types are: console, file and device. For a device target, target_name must contain the name of a log consumer device (as defined in [sec:Tango-log-consumer]). For a file target, target_name is the name of the file to log to. If omitted the device’s name is used to build the file name (domain_family_member.log). Finally, target_name is ignored in the case of a console target. The TLS does not report any error occurred while trying to setup the initial targets.

  - Logging_target property example : logging_target = [ console, file, <file::/home/me/mydevice.log> , device::tmp/log/1 In this case, the device will automatically logs to the standard output, to its default file (which is something like domain_family_member.log), to a file named mydevice.log and located in /home/me. Finally, the device logs are also sent to a log consumer device named tmp/log/1. Currently, the target do not take into account uppercase characters, so the logging_target property “file::/Home/He/MyDevice.log” will write into “file::/home/me/mydevice.log”

- The current_logging_target and current_logging_level properties behave as read-write properties which are set when device is started, respectively from logging_target and logging_level property values. Thoses properties are not stored into Tango database like other properties so they will only be available through the device, and only until it is stopped. If one of those properties is modified during the device running, the (last) value modification will be taken into account, and only until the next device stop.

- The logging_rft property specifies the rolling file threshold (rft), of the device’s file targets. This threshold is expressed in Kb. When the size of a log file reaches the so-called rolling-file-threshold (rft), it is backuped as current_log_file_name + _1 and a new current_log_file_name is opened. Obviously, there is only one backup file at a time (i.e. any existing backup is destroyed before the current log file is backuped). The default threshold is 20 Mb, the minimum is 500 Kb and the maximum is 1000 Mb.

- The logging_path property overwrites the TANGO_LOG_PATH environment variable. This property can only be applied to a DServer class device and has no effect on other devices.

Note: setting logging properties into the device will give access to logging of the device itself, while setting logging_level properties to DEBUG in dserver admin associated device will give access to cppTango library logging. This will not work with other logging level.

#### Device attribute

Attribute are configured with two kind of parameters: Parameters hard-coded in source code and modifiable parameters

##### Hard-coded device attribute parameters

Seven attribute parameters are defined at attribute creation time in the Tango class source code. Obviously, these parameters are not modifiable except with a new source code compilation. These parameters are

Parameter name | Parameter description
--- | ---
name | Attribute name
data_type | Attribute data type
data_format | Attribute data format
writable | Attribute read/write type
max_dim_x | Maximum X dimension
max_dim_y | Maximum Y dimension
writable_attr_name | Associated write attribute
level | Attribute display level
root_attr_name | Root attribute name

###### The Attribute data type

Thirteen data types are supported. These data types are

- Tango::DevBoolean

- Tango::DevShort

- Tango::DevLong

- Tango::DevLong64

- Tango::DevFloat

- Tango::DevDouble

- Tango::DevUChar

- Tango::DevUShort

- Tango::DevULong

- Tango::DevULong64

- Tango::DevString

- Tango::DevState

- Tango::DevEncoded

###### The attribute data format

Three data format are supported for attribute

Format | Description
--- | ---
Tango::SCALAR | The attribute value is a single number
Tango::SPECTRUM | The attribute value is a one dimension number
Tango::IMAGE | The attribute value is a two dimension number

###### The max_dim_x and max_dim_y parameters

These two parameters defined the maximum size for attributes of the SPECTRUM and IMAGE data format.

data format | max_dim_x | max_dim_y
--- | --- | ---
Tango::SCALAR | 1 | 0
Tango::SPECTRUM | User Defined | 0
Tango::IMAGE | User Defined | User Defined

For attribute of the Tango::IMAGE data format, all the data are also returned in a one dimension array. The first array is value[0],[0], array element X is value[0],[X-1], array element X+1 is value[1][0] and so forth.

###### The attribute read/write type

Tango supports four kind of read/write attribute which are :

- Tango::READ for read only attribute

- Tango::WRITE for writable attribute

- Tango::READ_WRITE for attribute which can be read and write

- Tango::READ_WITH_WRITE for a readable attribute associated to a writable attribute (For a power supply device, the current really generated is not the wanted current. To handle this, two attributes are defined which are generated_current and wanted_current. The wanted_current is a Tango::WRITE attribute. When the generated_current attribute is read, it is very convenient to also get the wanted_current attribute. This is exactly what the Tango::READ_WITH_WRITE attribute is doing)

When read, attribute values are always returned within an array even for scalar attribute. The length of this array and the meaning of its elements is detailed in the following table for scalar attribute.

Name | Array length | Array[0] | Array[1]
--- | --- | --- | ---
Tango::READ | 1 | Read value |
Tango::WRITE | 1 | Last write value |
Tango::READ_WRITE | 2 | Read value | Last write value
Tango::READ_WITH_WRITE | 2 | Read value | Associated attribute last write value

When a spectrum or image attribute is read, it is possible to code the device class in order to send only some part of the attribute data (For instance only a Region Of Interest for an image) but never more than what is defined by the attribute configuration parameters max_dim_x and max_dim_y. The number of data sent is also transferred with the data and is named dim_x and dim_y. When a spectrum or image attribute is written, it is also possible to send only some of the attribute data but always less than max_dim_x for spectrum and max_dim_x * max_dim_y for image. The following table describe how data are returned for spectrum attribute. dim_x is the data size sent by the server when the attribute is read and dim_x_w is the data size used during the last attribute write call.

Name | Array length | Array[0->dim_x-1] | Array[dim_x-> dim_x + dim_x_w -1] |
--- | --- | --- | --- | ---
Tango::READ | dim_x | Read values |  |
Tango::WRITE | dim_x_w | Last write values |  |
Tango::READ_WRITE | dim_x + dim_x_w | Read value | Last write values |
Tango::READ_WITH_WRITE | dim_x + dim_x_w | Read value | Associated attributelast write values |

The following table describe how data are returned for image attribute. dim_r is the data size sent by the server when the attribute is read (dim_x * dim_y) and dim_w is the data size used during the last attribute write call (dim_x_w * dim_y_w).

Name | Array length | Array[0->dim_r-1] | Array[dim_r->dim_r + dim_w -1]
--- | --- | --- | ---
Tango::READ | dim_r | Read values |
Tango::WRITE | dim_w | Last write values |
Tango::READ_WRITE | dim_r + dim_w | Read value | Last write values
Tango::READ_WITH_WRITE | dim_r + dim_w | Read value | Associated attributelast write values

Until a write operation has been performed, the last write value is initialized to 0 for scalar attribute of the numeriacal type, to Not Initialised for scalar string attribute and to true for scalar boolean attribute. For spectrum or image attribute, the last write value is initialized to an array of one element set to 0 for numerical type, to an array of one element set to true for boolean attribute and to an array of one element set to Not initialized for string attribute

###### The associated write attribute parameter

This parameter has a meaning only for attribute with a Tango::READ_WITH_WRITE read/write type. This is the name of the associated write attribute.

###### The attribute display level parameter

This parameter is only an help for graphical application. It is a C++ enumeration starting at 0. The code associated with each attribute display level is defined in the following table (Tango::DispLevel).

name | Value
--- | ---
Tango::OPERATOR | 0
Tango::EXPERT | 1

This parameter allows a graphical application to support two types of operation :

- An operator mode for day to day operation

- An expert mode when tuning is necessary

According to this parameter, a graphical application knows if the attribute is for the operator mode or for the expert mode.

###### The root attribute name parameter

In case the attribute is a forwarded one, this parameter is the name of the associated root attribute. In case of classical attribute, this string is set to Not specified.

##### Modifiable attribute parameters

Each attribute has a configuration set of 20 modifiable parameters. These can be grouped in three different purposes:

1. General purpose parameters

2. Alarm related parameters

3. Event related parameters

###### General purpose parameters

Eight attribute parameters are modifiable at run-time via a device call or via the property database.

Parameter name | Parameter description
--- | ---
description | Attribute description
label | Attribute label
unit | Attribute unit
standard_unit | Conversion factor to MKSA unit
display_unit | The attribute unit in a printable form
format | How to print attribute value
min_value | Attribute min value
max_value | Attribute max value
enum_labels | Enumerated labels
memorized | Attribute memorization

The description parameter describes the attribute. The label parameter is used by graphical application to display a label when this attribute is used in a graphical application. The unit parameter is the attribute value unit. The standard_unit parameter is the conversion factor to get attribute value in MKSA units. Even if this parameter is a number, it is returned as a string by the device get_attribute_config call. The display_unit parameter is the string used by graphical application to display attribute unit to application user. The enum_labels parameter is defined only for attribute of the DEV_ENUM data type. This is a vector of strings with one string for each enumeration label. It is an ordered list.

###### The format attribute parameter

This parameter specifies how the attribute value should be printed. It is not valid for string attribute. This format is a string of C++ streams manipulators separated by the ; character. The supported manipulators are :

- fixed

- scientific

- uppercase

- showpoint

- showpos

- setprecision()

- setw()

Their definition are the same than for C++ streams. An example of format parameter is

`scientific;uppercase;setprecision(3)`.

A class called Tango::AttrManip has been written to handle this format string. Once the attribute format string has been retrieved from the device, its value can be printed with

`cout << Tango::AttrManip(format) << value << endl;`

###### The min_value and max_value parameters

These two parameters have a meaning only for attribute of the Tango::WRITE read/write type and for numerical data types. Trying to set the value of an attribute to something less than or equal to the min_value parameter is an error. Trying to set the value of the attribute to something more or equal to the max_value parameter is also an error. Even if these parameters are numbers, they are returned as strings by the device get_attribute_config() call.

These two parameters have no meaning for attribute with data type DevString, DevBoolean or DevState. An exception is thrown in case the user try to set them for attribute of these 3 data types.

###### The memorized attribute parameter

This parameter describes the attribute memorization. It is an enumeration with the following values:

- NOT_KNOWN : The device is too old to return this information.

- NONE : The attribute is not memorized

- MEMORIZED : The attribute is memorized

- MEMORIZED_WRITE_INIT : The attribute is memorized and the memorized value is applied at device initialization time.

###### The alarm related configuration parameters

Six alarm related attribute parameters are modifiable at run-time via a device call or via the property database.

Parameter name | Parameter description
--- | ---
min_alarm | Attribute low level alarm
max_alarm | Attribute high level alarm
min_warning | Attribute low level warning
max_warning | Attribute high level warning
delta_t | delta time for RDS alarm (mS)
delta_val | delta value for RDS alarm (absolute)

These parameters have no meaning for attribute with data type DevString, DevBoolean or DevState. An exception is thrown in case the user try to set them for attribute of these 3 data types.

###### The min_alarm and max_alarm parameters

These two parameters have a meaning only for attribute of the Tango::READ, Tango::READ_WRITE and Tango::READ_WITH_WRITE read/write type and for numerical data type. When the attribute is read, if its value is something less than or equal to the min_alarm parameter or if it is something more or equal to the max_alarm parameter, 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. Even if these parameters are numbers, they are returned as strings by the device get_attribute_config() call.

###### The min_warning and max_warning parameters

These two parameters have a meaning only for attribute of the Tango::READ, Tango::READ_WRITE and Tango::READ_WITH_WRITE read/write type and for numerical data type. When the attribute is read, if its value is something less than or equal to the min_warning parameter or if it is something more or equal to the max_warning parameter, the attribute quality factor will be set to Tango::ATTR_WARNING and if the device state is Tango::ON, it is switched to Tango::ALARM. Even if these parameters are numbers, they are returned as strings by the device get_attribute_config() call.

###### The delta_t and delta_val parameters

These two parameters have a meaning only for attribute of the Tango::READ_WRITE and Tango::READ_WITH_WRITE read/write type and for numerical data type. They specify if and how the RDS alarm is used. When the attribute is read, if the difference between its read value and the last written value is something more than or equal to the delta_val parameter and if at least delta_val 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. Even if these parameters are numbers, they are returned as strings by the device get_attribute_config() call.

###### The event related configuration parameters

Six event related attribute parameters are modifiable at run-time via a device call or via the property database.

Parameter name | Parameter description
--- | ---
rel_change | Relative change triggering change event
abs_change | Absolute change triggering change event
period | Period for periodic event
archive_rel_change | Relative change for archive event
archive_abs_change | Absolute change for archive event
archive_period | Period for change archive event

###### The rel_change and abs_change parameters

Rel_change is a property with a maximum of 2 values (comma separated). It specifies the increasing and decreasing relative change of the attribute value (w.r.t. the value of the previous change event) which will trigger the event. If the attribute is a spectrum or an image then a change event is generated if any one of the attribute value’s satisfies the above criterium. It’s the absolute value of these values which is taken into account. If only one value is specified then it is used for the increasing and decreasing change.

Abs_change is a property of maximum 2 values (comma separated). It specifies the increasing and decreasing absolute change of the attribute value (w.r.t the value of the previous change event) which will trigger the event. If the attribute is a spectrum or an image then a change event is generated if any one of the attribute value’s satisfies the above criterium. If only one value is specified then it is used for the increasing and decreasing change. If no values are specified then the relative change is used.

###### The periodic period parameter

The minimum time between events (in milliseconds). If no property is specified then a default value of 1 second is used.

###### The archive_rel_change, archive_abs_change and archive_period parameters

archive_rel_change is an array property of maximum 2 values which specifies the positive and negative relative change w.r.t. the previous attribute value which will trigger the event. If the attribute is a spectrum or an image then an archive event is generated if any one of the attribute value’s satisfies the above criterium. If only one property is specified then it is used for the positive and negative change. If no properties are specified then a default fo +-10% is used

archive_abs_change is an array property of maximum 2 values which specifies the positive and negative absolute change w.r.t the previous attribute value which will trigger the event. If the attribute is a spectrum or an image then an archive event is generated if any one of the attribute value’s satisfies the above criterium. If only one property is specified then it is used for the positive and negative change. If no properties are specified then the relative change is used.

archive_period is the minimum time between archive events (in milliseconds). If no property is specified, no periodic archiving events are send.

##### Setting modifiable attribute parameters

A default value is given to all modifiable attribute parameters by the Tango core classes. Nevertheless, it is possible to modify these values in source code at attribute creation time or via the database. Values retrieved from the database have a higher priority than values given at attribute creation time. The attribute parameters are therefore initialized from:

1. The Database

2. If nothing in database, from the Tango class default

3. If nothing in database nor in Tango class default, from the library default value

The default value set by the Tango core library are

Parameter type | Parameter name | Library default value
--- | --- | ---
general purpose | description | No description
label | attribute name
unit | One empty string
standard_unit | No standard unit
display_unit | No display unit
format | 6 characters with 2 decimal
min_value | Not specified
max_value | Not specified
alarm parameters | min_alarm | Not specified
max_alarm | Not specified
min_warning | Not specified
max_warning | Not specified
& delta_t | Not specified
delta_val | Not specified
event parameters | rel_change | Not specified
abs_change | Not specified
period | 1000 (mS)
archive_rel_change | Not specified
archive_abs_change | Not specified
archive_period | Not specified

It is possible to set modifiable parameters via the database at two levels :

1. At class level

2. At device level. Each device attribute have all its modifiable parameters sets to the value defined at class level. If the setting defined at class level is not correct for one device, it is possible to re-define it.

If we take the example of a class called BumperPowerSupply with three devices called sr/bump/1, sr/bump/2 and sr/bump/3 and one attribute called wanted_current. For the first two bumpers, the max_value is equal to 500. For the third one, the max_value is only 400. If the max_value parameter is defined at class level with the value 500, all devices will have 500 as max_value for the wanted_current attribute. It is necessary to re-defined this parameter at device level in order to have the max_value for device sr/bump/3 set to 400.

For the description, label, unit, standard_unit, display_unit and format parameters, it is possible to return them to their default value by setting them to an empty string.

##### Resetting modifiable attribute parameters

It is possible to reset attribute parameters to their default value at any moment. This could be done via the network call available through the DeviceProxy::set_attribute_config() method family. This call takes attribute parameters as strings. The following table describes which string has to be used to reset attribute parameters to their default value. In this table, the user default are the values given within Pogo in the Properties tab of the attribute edition window (or in in Tango class code using the Tango::UserDefaultAttrProp class).

Input string | Action
--- | ---
‘Not specified’ | Reset to library default
‘’(empty string) | Reset to user default if any. Otherwise, reset to library default
‘NaN’ | Reset to Tango class default if any. Otherwise, reset to user default (if any) or to library default

Let’s take one exemple: For one attribute belonging to a device, we have the following attribute parameters:

Parameter name | Def. class | Def. user | Def. lib
--- | --- | --- | ---
standard_unit |  |  | No standard unit
min_value |  | 5 | Not specified
max_value | 50 |  | Not specified
rel_change | 5 | 10 | Not specified

The string Not specified sent to each attribute parameter will set attribute parameter value to No standard unit for standard_unit, Not specified for min_value, Not specified for max_value and Not specified as well for rel_change. The empty string sent to each attribute parameter will result with No stanadard unit for standard_unit, 5 for min_value, Not specified for max_value and 10 for rel_change. The string NaN will give No standard unit for standard_unit, 5 for min_value, 50 for max_value and 5 for rel_change.

C++ specific: Instead of the string Not specified and NaN, the preprocessor define AlrmValueNotSpec and NotANumber can be used.

#### Device pipe

Pipe are configured with two kind of parameters: Parameters hard-coded in source code and modifiable parameters

##### Hard-coded device pipe parameters

Three pipe parameters are defined at pipe creation time in the Tango class source code. Obviously, these parameters are not modifiable except with a new source code compilation. These parameters are

Parameter name | Parameter description
--- | ---
name | Pipe name
writable | Pipe read/write type
disp_level | Pipe display level

###### The pipe read/write type.

Tango supports two kinds of read/write pipe which are :

- Tango::PIPE_READ for read only pipe

- Tango::PIPE_READ_WRITE for pipe which can be read and written

###### The pipe display level parameter

This parameter is only an help for graphical application. It is a C++ enumeration starting at 0. The code associated with each pipe display level is defined in the following table (Tango::DispLevel).

name | Value
--- | ---
Tango::OPERATOR | 0
Tango::EXPERT | 1

This parameter allows a graphical application to support two types of operation :

- An operator mode for day to day operation

- An expert mode when tuning is necessary

According to this parameter, a graphical application knows if the pipe is for the operator mode or for the expert mode.

##### Modifiable pipe parameters

Each pipe has a configuration set of 2 modifiable parameters. These parameters are modifiable at run-time via a device call or via the property database.

Parameter name | Parameter description
--- | ---
description | Pipe description
label | Pipe label

The description parameter describes the pipe. The label parameter is used by graphical application to display a label when this pipe is used in a graphical application.

##### Setting modifiable pipe parameters

A default value is given to all modifiable pipe parameters by the Tango core classes. Nevertheless, it is possible to modify these values in source code at pipe creation time or via the database. Values retrieved from the database have a higher priority than values given at pipe creation time. The pipe parameters are therefore initialized from:

1. The Database

2. If nothing in database, from the Tango class default

3. If nothing in database nor in Tango class default, from the library default value

The default value set by the Tango core library are

Parameter name | Library default value
--- | ---
description | No description
label | pipe name

It is possible to set modifiable parameters via the database at two levels :

1. At class level

2. At device level. Each device pipe have all its modifiable parameters sets to the value defined at class level. If the setting defined at class level is not correct for one device, it is possible to re-define it.

This is the same principle than the one used for attribute configuration modifiable parameters.

##### Resetting modifiable pipe parameters

It is possible to reset pipe parameters to their default value at any moment. This could be done via the network call available through the DeviceProxy::set_pipe_config() method family. It uses the same principle than the one used for resetting modifiable attribute pipe parameters. Refer to their documentation if you want to know details about this feature.

#### Device class parameter

A device documentation field is also defined at Tango device class level. It is defined as Tango device class level because each device belonging to a Tango device class should have the same behaviour and therefore the same documentation. This field is store in the DeviceClass class. It is possible to set this field via a class property. This property name is

class name->doc_url

and is retrieved when instance of the DeviceClass object is created. A default value is defined for this field.

#### The device black box

This black box is a help tool to ease debugging session for a running device server. The TANGO core software records every device request in this black box. A tango client is able to retrieve the black box contents with a specific CORBA operation availabble for every device. Each black box entry is returned as a string with the following information :

- The date where the request has been executed by the device. The date format is dd/mm/yyyy hh24:mi:ss:SS (The last field is the second hundredth number).

- The type of CORBA requests. In case of attributes, the name of the requested attribute is returned. In case of operation, the operation type is returned. For “command_inout” operation, the command name is returned.

- The client host name

#### Automatically added commands

As already mentionned in this documentation, each Tango device supports at least three commands which are State, Status and Init. The following array details command input and output data type

Command name | Input data type | Output data type
--- | --- | ---
State | void | Tango::DevState
Status | void | Tango::DevString
Init | void | void

##### The State command

This command gets the device state (stored in its device_state data member) and returns it to the caller. The device state is a variable of the Tango_DevState type (packed into a CORBA Any object when it is returned by a command)

##### The Status command

This command gets the device status (stored in its device_status data member) and returns it to the caller. The device status is a variable of the string type.

##### The Init command

This commands re-initialise a device keeping the same network connection. After an Init command executed on a device, it is not necessary for client to re-connect to the device. This command first calls the device delete_device() method and then execute its init_device() method. For C++ device server, all the memory allocated in the init_device() method must be freed in the delete_device() method. The language device desctructor automatically calls the delete_device() method.

#### DServer class device commands

As already explained in [DServer_class], each device server process has its own Tango device. This device supports the three commands previously described plus 32 commands which are DevRestart, RestartServer, QueryClass, QueryDevice, Kill, QueryWizardClassProperty, QueryWizardDevProperty, QuerySubDevice, the polling related commands which are StartPolling, StopPolling, AddObjPolling, RemObjPolling, UpdObjPollingPeriod, PolledDevice and DevPollStatus, the device locking related commands which are LockDevice, UnLockDevice, ReLockDevices and DevLockStatus, the event related commands called EventSubscriptionChange, ZmqEventSubscriptionChange and EventConfirmSubscription and finally the logging related commands which are AddLoggingTarget, RemoveLoggingTarget, GetLoggingTarget, GetLoggingLevel, SetLoggingLevel, StopLogging and StartLogging. The following table give all commands input and output data types

Command name | Input data type | Output data type
--- | --- | ---
State | void | Tango::DevState
Status | void | Tango::DevString
Init | void | void
DevRestart | Tango::DevString | void
RestartServer | void | void
QueryClass | void | Tango::DevVarStringArray
QueryDevice | void | Tango::DevVarStringArray
Kill | void | void
QueryWizardClassProperty | Tango::DevString | Tango::DevVarStringArray
QueryWizardDevProperty | Tango::DevString | Tango::DevVarStringArray
QuerySubDevice | void | Tango::DevVarStringArray
StartPolling | void | void
StopPolling | void | void
AddObjPolling | Tango::DevVarLongStringArray | void
RemObjPolling | Tango::DevVarStringArray | void
UpdObjPollingPeriod | Tango::DevVarLongStringArray | void
PolledDevice | void | Tango::DevVarStringArray
DevPollStatus | Tango::DevString | Tango::DevVarStringArray
LockDevice | Tango::DevVarLongStringArray | void
UnLockDevice | Tango::DevVarLongStringArray | Tango::DevLong
ReLockDevices | Tango::DevVarStringArray | void
DevLockStatus | Tango::DevString | Tango::DevVarLongStringArray
EventSubscribeChange | Tango::DevVarStringArray | Tango::DevLong
ZmqEventSubscriptionChange | Tango::DevVarStringArray | Tango::DevVarLongStringArray
EventConfirmSubscription | Tango::DevVarStringArray | void
AddLoggingTarget | Tango::DevVarStringArray | void
RemoveLoggingTarget | Tango::DevVarStringArray | void
GetLoggingTarget | Tango::DevString | Tango::DevVarStringArray
GetLoggingLevel | Tango::DevVarStringArray | Tango::DevVarLongStringArray
SetLoggingLevel | Tango::DevVarLongStringArray | void
StopLogging | void | void
StartLogging | void | void

The device description field is set to “A device server device”. Device server started with the -file command line option also supports a command called QueryEventChannelIOR. This command is used interanally by the Tango kernel classes when the event system is used with device server using database on file.

##### The State command

This device state is always set to ON

##### The Status command

This device status is always set to “The device is ON” followed by a new line character and a string describing polling thread status. This string is either “The polling is OFF” or “The polling is ON” according to polling state.

##### The DevRestart command

The DevRestart command restart a device. The name of the device to be re-started is the command input parameter. The command destroys the device by calling its destructor and re-create it from its constructor.

##### The RestartServer command

The DevRestartServer command restarts all the device pattern(s) embedded in the device server process. Therefore, all the devices implemented in the server process are destroyed and re-built [1]. The network connection between client(s) and device(s) implemented in the device server process is destroyed and re-built.

Executing this command allows a complete restart of the device server without stopping the process.

##### The QueryClass command

This command returns to the client the list of Tango device class(es) embedded in the device server. It returns only class(es) implemented by the device server programmer. The DServer device class name (implemented by the TANGO core software) is not returned by this command.

##### The QueryDevice command

This command returns to the client the list of device name for all the device(s) implemented in the device server process. Each device name is returned using the following syntax :

<class name>::<device name>

The name of the DServer class device is not returned by this command.

##### The Kill command

This command stops the device server process. In order that the client receives a last answer from the server, this command starts a thread which will after a short delay, kills the device server process.

##### The QueryWizardClassProperty command

This command returns the list of property(ies) defined for a class stored in the device server process property wizard. For each property, its name, a description and a default value is returned.

##### The QueryWizardDevProperty command

This command returns the list of property(ies) defined for a device stored in the device server process property wizard. For each property, its name, a description and a default value is returned.

##### The QuerySubDevice command

This command returns the list of sub-device(s) imported by each device within the server. A sub-device is a device used ( to execute command(s) and/or to read/write attribute(s) ) by one of the device server process devices. There is one element in the returned strings array for each sub-device. The syntax of each string is the device name, a space and the sub-device name. In case of device server process starting threads using a sub-device, it is not possible to link this sub-device to any process devices. In such a case, the string contains only the sub-device name

##### The StartPolling command

This command starts the polling thread

##### The StopPolling command

This command stops the polling thread

##### The AddObjPolling command

This command adds a new object in the list of object(s) to be polled. The command input parameters are embedded within a Tango::DevVarLongStringArray data type with one long data and three strings. The input parameters are:

Command parameter | Parameter meaning
--- | ---
svalue[0] | Device name
svalue[1] | Object type (“command“ or “attribute“)
svalue[2] | Object name
lvalue[0] | polling period in mS

The object type string is case independent. The object name string (command name or attribute name) is case dependant. This command does not start polling if it is stopped. This command is not allowed in case the device is locked and the command requester is not the lock owner.

##### The RemObjPolling command

This command removes an object of the list of polled objects. The command input data type is a Tango::DevVarStringArray with three strings. These strings meaning are :

String | Meaning
--- | ---
string[0] | Device name
string[1] | Object type (“command“ or “attribute“)
string[2] | Object name

The object type string is case independent. The object name string (command name or attribute name) is case dependant. This command is not allowed in case the device is locked and the command requester is not the lock owner.

##### The UpdObjPollingPeriod command

This command changes the polling period for a specified object. The command input parameters are embedded within a Tango::DevVarLongStringArray data type with one long data and three strings. The input parameters are:

Command parameter | Parameter meaning
--- | ---
svalue[0] | Device name
svalue[1] | Object type (“command“ or “attribute“)
svalue[2] | Object name
lvalue[0] | new polling period in mS

The object type string is case independent. The object name string (command name or attribute name) is case dependant. This command does not start polling if it is stopped. This command is not allowed in case the device is locked and the command requester is not the lock owner.

##### The PolledDevice command

This command returns the name of device which are polled. Each string in the Tango::DevVarStringArray returned by the command is a device name which has at least one command or attribute polled. The list is alphabetically sorted.

##### The DevPollStatus command

This command returns a polling status for a specific device. The input parameter is a device name. Each string in the Tango::DevVarStringArray returned by the command is the polling status for each polled device objects (command or attribute). For each polled objects, the polling status is :

- The object name

- The object polling period (in mS)

- The object polling ring buffer depth

- The time needed (in mS) for the last command execution or attribute reading

- The time since data in the ring buffer has not been updated. This allows a check of the polling thread

- The delta time between the last records in the ring buffer. This allows checking that the polling period is respected by the polling thread.

- The exception parameters in case of the last command execution or the last attribute reading failed.

A new line character is inserted between each piece of information.

##### The LockDevice command

This command locks a device for the calling process. The command input parameters are embedded within a Tango::DevVarLongStringArray data type with one long data and one string. The input parameters are:

Command parameter | Parameter meaning
--- | ---
svalue[0] | Device name
lvalue[0] | Lock validity

##### The UnLockDevice command

This command unlocks a device. The command input parameters are embedded within a Tango::DevVarLongStringArray data type with one long data and one string. The input parameters are:

Command parameter | Parameter meaning
--- | ---
svalue[0] | Device name
lvalue[0] | Force flag

The force flag parameter allows a client to unlock a device already locked by another process (for admin usage only)

##### The ReLockDevices command

This command re-lock devices. The input argument is the list of devices to be re-locked. It’s an error to re-lock a device which is not already locked.

##### The DevLockStatus command

This command returns a device locking status to the caller. Its input parameter is the device name. The output parameters are embedded within a Tango::DevVarLongStringArray data type with three strings and six long. These data are

Command parameter | Parameter meaning
--- | ---
svalue[0] | Locking string
svalue[1] | CPP client host IP address or Not defined
svalue[2] | Java VM main class for Java client or Not defined
lvalue[0] | Lock flag (1 if locked, 0 othterwise)
lvalue[1] | CPP client host IP address or 0 for Java locker
lvalue[2] | Java locker UUID part 1or 0 for CPP locker
lvalue[3] | Java locker UUID part 2 or 0 for CPP locker
lvalue[4] | Java locker UUID part 3 or 0 for CPP locker
lvalue[5] | Java locker UUID part 4 or 0 for CPP locker

##### The EventSubscriptionChange command (C++ server only)

This command is used as a piece of the heartbeat system between an event client and the device server generating the event. There is no reason to generate events if there is no client which has subscribed to it. It is used by the DeviceProxy::subscribe_event() method and one of the event thread on the client side to inform the server to keep on generating events for the attribute in question. It reloads the subscription timer with the current time. Events are not generated when there are no clients subscribed within the last 10 minutes. The input parameters are:

Command parameter | Parameter meaning
--- | ---
argin[0] | Device name
argin[1] | Attribute name
argin[2] | action (subscribe or unsubsribe)
argin[3] | event name (change, periodic, archive,attr_conf)

The command output data is the simply the Tango release used by the device server process. This is necessary for compatibility reason.

##### The ZmqEventSubscriptionChange command

This command is used as a piece of the heartbeat system between an event client and the device server generating the event. There is no reason to generate events if there is no client which has subscribed to it. It is used by the DeviceProxy::subscribe_event() method and one of the event thread on the client side to inform the server to keep on generating events for the attribute in question. It reloads the subscription timer with the current time. Events are not generated when there are no clients subscribed within the last 10 minutes. The input parameters are the same than the one used for the EventSubscriptionChange command. They are:

Command in parameter | Parameter meaning
--- | ---
argin[0] | Device name
argin[1] | Attribute/pipe name
argin[2] | action (subscribe or unsubsribe)
argin[3] | event name (change, quality, periodic, archive, user_event, attr_conf, data_ready, intr_change, pipe)
argin[4] | <Tango client IDL version>

The command output parameters aer all the necessary data to build one event connection between a client and the device server process generating the events. This means:

Command out parameter | Parameter meaning
--- | ---
svalue[0] | Heartbeat ZMQ socket connect end point
svalue[1] | Event ZMQ socket connect end point
svalue[2] | <Alternate Heartbeat pub endpoint>
svalue[3] | <Alternate Event pub endpoint>
… |
svalue[n-3] | <Alternate Heartbeat pub endpoint>
svalue[n-2] | <Alternate Event pub endpoint>
svalue[n-1] | event name used by this server as zmq topic to send events
svalue[n] | channel name used by this server to send heartbeat events
lvalue[0] | Tango lib release used by device server
lvalue[1] | Device IDL release
lvalue[2] | Subscriber HWM
lvalue[3] | Rate (Multicasting related)
lvalue[4] | IVL (Multicasting related)
lvalue[5] | ZMQ release

##### The EventConfirmSubscription command

This command is used by client to regularly notify to device server process their interest in receiving events. If this command is not received, after a delay of 600 sec (10 mins), event(s) will not be sent any more. The input parameters for the EventConfirmSubscription command must be a multiple of 3. They are 3 parameters for each event confirmed by this command. Per event, these parameters are:

Command in parameter | Parameter meaning
--- | ---
argin[x] | Device name
argin[x + 1] | Attribute name
argin[x + 2] | Event name

##### The AddLoggingTarget command

This command adds one (or more) logging target(s) to the specified device(s). The command input parameter is an array of string logically composed of {device_name, target_type::target_name} groups where the elements have the following semantic:

- device_name is the name of the device which logging behavior is to be controlled. The wildcard is supported to apply the modification to all devices encapsulated within the device server (e.g. to ask all devices to log to the same device target).

- target_type::target_name: target_type is one of the supported target types and target_name, the name of the target. Supported target types are: console, file and device. For a device target, target_name must contain the name of a log consumer device (as defined in [sec:Tango-log-consumer]). For a file target, target_name is the full path to the file to log to. If omitted the device’s name is used to build the file name (domain_family_member.log). Finally, target_name is ignored in the case of a console target and can be omitted.

This command is not allowed in case the device is locked and the command requester is not the lock owner.

##### The RemoveLoggingTarget command

Remove one (or more) logging target(s) from the specified device(s).The command input parameter is an array of string logically composed of {device_name, target_type::target_name} groups where the elements have the following semantic:

- device_name: the name of the device which logging behavior is to be controlled. The wildcard is supported to apply the modification to all devices encapsulated within the device server (e.g. to ask all devices to stop logging to a given device target).

- target_type::target_name: target_type is one of the supported target types and target_name, the name of the target. Supported target types are: console, file and device. For a device target, target_name must contain the name of a log consumer device (as defined in [sec:Tango-log-consumer]). For a file target, target_name is the full path to the file to log to. If omitted the device’s name is used to build the file name (domain_family_member.log). Finally, target_name is ignored in the case of a console target and can be omitted.

The wildcard is supported for target_name. For instance, RemoveLoggingTarget ([, device::*) removes all the device targets from all the devices running in the device server. This command is not allowed in case the device is locked and the command requester is not the lock owner.

##### The GetLoggingTarget command

Returns the current target list of the specified device. The command parameter device_name is the name of the device which logging target list is requested. The list is returned as a DevVarStringArray containing target_type::target_name elements.

##### The GetLoggingLevel command

Returns the logging level of the specified devices. The command input parameter device_list contains the names of the devices which logging target list is requested. The wildcard is supported to get the logging level of all the devices running within the server. The string part of the result contains the name of the devices and its long part contains the levels. Obviously, result.lvalue[i] is the current logging level of the device named result.svalue[i].

##### The SetLoggingLevel command

Changes the logging level of the specified devices. The string part of the command input parameter contains the device names while its long part contains the logging levels. The set of possible values for levels is: 0=OFF, 1=FATAL, 2=ERROR, 3=WARNING, 4=INFO, 5=DEBUG.

The wildcard is supported to assign all devices the same logging level. For instance, SetLoggingLevel ([3]) set the logging level of all the devices running within the server to WARNING. This command is not allowed in case the device is locked and the command requester is not the lock owner.

##### The StopLogging command

For all the devices running within the server, StopLogging saves their current logging level and set their logging level to OFF.

##### The StartLogging command

For each device running within the server, StartLogging restores their logging level to the value stored during a previous StopLogging call.

#### DServer class device properties

This device has two properties related to polling threads pool management plus another one for the choice of polling algorithm. These properties are described in the following table

Property name | property rule | default value
--- | --- | ---
polling_threads_pool_size | Max number of thread in the polling pool | 1
polling_threads_pool_conf | Polling threads pool configuration |
polling_before_9 | Choice of the polling algorithm | false

The rule of the polling_threads_pool_size is to define the maximun number of thread created for the polling threads pool size. The rule of the polling_threads_pool_conf is to define which thread in the pool is in charge of all the polled object(s) of which device. This property is an array of strings with one string per used thread in the pool. The content of the string is simply a device name list with device name splitted by a comma. Example of polling_threads_pool_conf property for 3 threads used:

    1  dserver/<ds exec name>/<inst. name>/polling_threads_pool_conf-> the/dev/01
    2                    the/dev/02,the/dev/06
    3                    the/dev/03

Thread number 2 is in charge of 2 devices. Note that there is an entry in this list only for the used threads in the pool.

The rule of the polling_before_9 property is to select the old polling algorithm.

#### Tango log consumer

##### The available Log Consumer

One implementation of a log consumer associated to a graphical user interface is available within Tango. It is a standalone java application called LogViewer based on the publicly available chainsaw application from the log4j package. It supports two way of running which are:

- The static mode: In this mode, LogViewer is started with a parameter which is the name of the log consumer device implemented by the application. All messages sent by devices with a logging target type set to device and with a logging target name set to the same device name than the device name passed as application parameter will be displayed (if the logging level allows it).

- The dynamic mode: In this mode, the name of the log consumer device implemented by the application is build at application startup and is dynamic. The user with the help of the graphical interface chooses device(s) for which he want to see log messages.

##### The Log Consumer interface

A Tango Log Consumer device is nothing but a tango device supporting the following tango command :

void log (Tango::DevVarStringArray details)

where details is an array of string carrying the log details. Its structure is:

- details[0] : the timestamp in millisecond since epoch (01.01.1970)

- details[1] : the log level

- details[2] : the log source (i.e. device name)

- details[3] : the log message

- details[4] : the log NDC (contextual info) - Not used but reserved

- details[5] : the thread identifier (i.e. the thread from which the log request comes from)

These log details can easily be extended. Any tango device supporting this command can act as a device target for other devices.

#### Control system specific

It is possible to define a few control system parameters. By control system, we mean for each set of computers having the same database device server (the same TANGO_HOST environment variable)

##### The device class documentation default value

Each control system may have it’s own default device class documentation value. This is defined via a class property. The property name is

Default->doc_url

It’s retrieved if the device class itself does not define any doc_url property. If the Default->doc_url property is also not defined, a hard-coded default value is provided.

##### The services definition

The property used to defined control system services is named Services and belongs to the free object CtrlSystem. This property is an array of strings. Each string defines a service available within the control system. The syntax of each service definition is

Service name/Instance name:service device name

##### Tuning the event system buffers (HWM)

The ZMQ implementation provides asynchronous communication in the sense that the data to be transmitted is first stored in a buffer and then really sent on the network by dedicated threads. The size of this buffers (on client and device server side) is called High Water Mark (HWM) and is tunable. This is tunable at several level.

1. The library set a default value of 1000 for both buffers (client and device server side)

2. Control system properties used to tune these size are named DSEventBufferHwm (device server side) and EventBufferHwm (client side). They both belongs to the free object CtrlSystem. Each property is the max number of events storable in these buffer.

3. At client or device server level using the library calls Util::set_ds_event_buffer_hwm() documented in cppTango API reference or ApiUtil::set_event_buffer_hwm() documented in `Tango::ApiUtil`

4. Using environment variables TANGO_DS_EVENT_BUFFER_HWM or TANGO_EVENT_BUFFER_HWM

##### Allowing NaN when writing attributes (floating point)

A property named WAttrNaNAllowed belonging to the free object CtrlSystem allows a Tango control system administrator to allow or disallow NaN numbers when writing attributes of the DevFloat or DevDouble data type. This is a boolean property and by default, it’s value is taken as false (Meaning NaN values are rejected).

##### Tuning multicasting event propagation

Starting with Tango 8.1, it is possible to transfer event(s) between devices and clients using a multicast protocol. The properties MulticastEvent, MulticastRate, MulticastIvl and MulticastHops also belonging to the free object CtrlSystem allow the user to configure which events has to be sent using multicasting and with which parameters. See chapter Advanced features/Using multicast protocol to transfer events to get details about these properties.

##### Summary of CtrlSystem free object properties

The following table summarizes properties defined at control system level and belonging to the free object CtrlSystem

Property name | property rule | default value
--- | --- | ---
Services | List of defined services | No default
DsEventBufferHwm | DS event buffer high water mark | 1000
EventBufferHwm | Client event buffer high water mark | 1000
WAttrNaNAllowed | Allow NaN when writing attr. | false
MulticastEvent | List of multicasting events | No default
MulticastRate | Rate for multicast event transport | 80
MulticastIvl | Time to keep data for re-transmission | 20
MulticastHops | Max number of elements to cross | 5

#### C++ specific

##### The Tango master include file (tango.h)

Tango has a master include file called

tango.h

This master include file includes the following files :

- Tango configuration include file : tango_config.h

- CORBA include file : idl/tango.h

- Some network include files for WIN32 : winsock2.h and mswsock.h

- C++ streams include file :

  - iostream, sstream and fstream

- Some standard C++ library include files : memory, string and vector

- A long list of other Tango include files

##### Tango specific pre-processor define

The tango.h previously described also defined some pre-processor macros allowing Tango release to be checked at compile time. These macros are:

- TANGO_VERSION_MAJOR

- TANGO_VERSION_MINOR

- TANGO_VERSION_PATCH

For instance, with Tango release 100.200.300, TANGO_VERSION_MAJOR will be set to 100 while TANGO_VERSION_MINOR will be 200 and TANGO_VERSION_PATCH will be 300.

##### Tango specific types

###### Operating system free type

Some data type used in the TANGO core software have been defined. They are described in the following table.

Type name | C++ name
--- | ---
TangoSys_MemStream | stringstream
TangoSys_OMemStream | ostringstream
TangoSys_Pid | int
TangoSys_Cout | ostream

These types are defined in the tango_config.h file

###### Template command model related type

As explained in [Command fact], command created with the template command model uses static casting. Many type definition have been written for these casting.

Class name | Command allowed method (if any) | Command execute method
--- | --- | ---
TemplCommand | Tango::StateMethodPtr | Tango::CmdMethPtr
TemplCommandIn | Tango::StateMethodPtr | Tango::CmdMethPtr_xxx
TemplCommandOut | Tango::StateMethodPtr | Tango::xxx_CmdMethPtr
TemplCommandInOut | Tango::StateMethodPtr | Tango::xxx_CmdMethPtr_yyy

The Tango::StateMethPtr is a pointer to a method of the DeviceImpl class which returns a boolean and has one parameter which is a reference to a const CORBA::Any obect.

The Tango::CmdMethPtr is a pointer to a method of the DeviceImpl class which returns nothing and needs nothing as parameter.

The Tango::CmdMethPtr_xxx is a pointer to a method of the DeviceImpl class which returns nothing and has one parameter. xxx must be set according to the method parameter type as described in the next table

Tango type | short cut (xxx)
--- | ---
Tango::DevBoolean | Bo
Tango::DevShort | Sh
Tango::DevLong | Lg
Tango::DevFloat | Fl
Tango::DevDouble | Db
Tango::DevUshort | US
Tango::DevULong | UL
Tango::DevString | Str
Tango::DevVarCharArray | ChA
Tango::DevVarShortArray | ShA
Tango::DevVarLongArray | LgA
Tango::DevVarFloatArray | FlA
Tango::DevVarDoubleArray | DbA
Tango::DevVarUShortArray | USA
Tango::DevVarULongArray | ULA
Tango::DevVarStringArray | StrA
Tango::DevVarLongStringArray | LSA
Tango::DevVarDoubleStringArray | DSA
Tango::DevState | Sta

For instance, a pointer to a method which takes a Tango::DevVarStringArray as input parameter must be statically casted to a Tango::CmdMethPtr_StrA, a pointer to a method which takes a Tango::DevLong data as input parameter must be statically casted to a Tango::CmdMethPtr_Lg.

The Tango::xxx_CmdMethPtr is a pointer to a method of the DeviceImpl class which returns data of one of the Tango type and has no input parameter. xxx must be set according to the method return data type following the same rules than those described in the previous table. For instance, a pointer to a method which returns a Tango::DevDouble data must be statically casted to a Tango::Db_CmdMethPtr.

The Tango::xxx_CmdMethPtr_yyy is a pointer to a method of the DeviceImpl class which returns data of one of the Tango type and has one input parameter of one of the Tango data type. xxx and yyy must be set according to the method return data type and parameter type following the same rules than those described in the previous table. For instance, a pointer to a method which returns a Tango::DevDouble data and which takes a Tango::DevVarLongStringArray must be statically casted to a Tango::Db_CmdMethPtr_LSA.

All those type are defined in the tango_const.h file.

##### Tango device state code

The Tango::DevState type is a C++ enumeration starting at 0. The code associated with each state is defined in the following table.

State name | Value
--- | ---
Tango::ON | 0
Tango::OFF | 1
Tango::CLOSE | 2
Tango::OPEN | 3
Tango::INSERT | 4
Tango::EXTRACT | 5
Tango::MOVING | 6
Tango::STANDBY | 7
Tango::FAULT | 8
Tango::INIT | 9
Tango::RUNNING | 10
Tango::ALARM | 11
Tango::DISABLE | 12
Tango::UNKNOWN | 13

A strings array called Tango::DevStateName can be used to get the device state as a string. Use the Tango device state code as index into the array to get the correct string.

##### Tango data type

A “define” has been created for each Tango data type. This is summarized in the following table

Type name | Type code | Value
--- | --- | ---
Tango::DevBoolean | Tango::DEV_BOOLEAN | 1
Tango::DevShort | Tango::DEV_SHORT | 2
Tango::DevLong | Tango::DEV_LONG | 3
Tango::DevFloat | Tango::DEV_FLOAT | 4
Tango::DevDouble | Tango::DEV_DOUBLE | 5
Tango::DevUShort | Tango::DEV_USHORT | 6
Tango::DevULong | Tango::DEV_ULONG | 7
Tango::DevString | Tango::DEV_STRING | 8
Tango::DevVarCharArray | Tango::DEVVAR_CHARARRAY | 9
Tango::DevVarShortArray | Tango::DEVVAR_SHORTARRAY | 10
Tango::DevVarLongArray | Tango::DEVVAR_LONGARRAY | 11
Tango::DevVarFloatArray | Tango::DEVVAR_FLOATARRAY | 12
Tango::DevVarDoubleArray | Tango::DEVVAR_DOUBLEARRAY | 13
Tango::DevVarUShortArray | Tango::DEVVAR_USHORTARRAY | 14
Tango::DevVarULongArray | Tango::DEVVAR_ULONGARRAY | 15
Tango::DevVarStringArray | Tango::DEVVAR_STRINGARRAY | 16
Tango::DevVarLongStringArray | Tango::DEVVAR_LONGSTRINGARRAY | 17
Tango::DevVarDoubleStringArray | Tango::DEVVAR_DOUBLESTRINGARRAY | 18
Tango::DevState | Tango::DEV_STATE | 19
Tango::ConstDevString | Tango::CONST_DEV_STRING | 20
Tango::DevVarBooleanArray | Tango::DEVVAR_BOOLEANARRAY | 21
Tango::DevUChar | Tango::DEV_UCHAR | 22
Tango::DevLong64 | Tango::DEV_LONG64 | 23
Tango::DevULong64 | Tango::DEV_ULONG64 | 24
Tango::DevVarLong64Array | Tango::DEVVAR_LONG64ARRAY | 25
Tango::DevVarULong64Array | Tango::DEVVAR_ULONG64ARRAY | 26
Tango::DevEncoded | Tango::DEV_ENCODED | 28
Tango::DevEnum | Tango::DEV_ENUM | 29
Tango::DevPipeBlob | Tango::DEV_PIPE_BLOB | 30
Tango::DevVarStateArray | Tango::DEVVAR_STATEARRAY | 31

For command which do not take input parameter, the type code Tango::DEV_VOID (value = 0) has been defined.

Use Tango::data_type_to_string to convert the tango data type to a string. For older tango versions without that function a strings array called Tango::CmdArgTypeName can be used to get the data type as a string. Use the Tango data type code as index into the array to get the correct string.

##### Tango command display level

Like attribute, Tango command has a display level. The Tango::DispLevel type is a C++ enumeration starting at 0. The code associated with each command display level is already described in page

As for attribute, this parameter allows a graphical application to support two types of operation :

- An operator mode for day to day operation

- An expert mode when tuning is necessary

According to this parameter, a graphical application knows if the command is for the operator mode or for the expert mode.

#### Device server process option and environment variables

##### Classical device server

The synopsis of a device server process is

ds_name instance_name [OPTIONS]

The supported options are :

- -h, -? -help Print the device server synopsis and a list of instance name defined in the database for this device server. An instance name in not mandatory in the command line to use this option

- -v[trace level] Set the verbose level. If no trace level is given, a default value of 4 is used

- -file=<file name path> Start a device server using an ASCII file instead of the Tango database.

- -nodb Start a device server without using the database.

- -dlist <device name list> Give the device name list. This option is supported only with the -nodb option.

- ORB options (started with -ORBxxx) Options directly passed to the underlying ORB. Should be rarely used except the -ORBendPoint option for device server not using the database

##### Device server process as Windows service

When used as a Windows service, a Tango device server supports several new options. These options are :

- -i Install the service

- -s Install the service and choose the automatic startup mode

- -u Un-install the service

- -dbg Run in console mode to debug service. The service must have been installed prior to use it.

Note that these options must be used after the device server instance name.

##### Environment variables

A few environment variables can be used to tune a Tango control system. TANGO_HOST is the most important one but on top it, some Tango features like Tango logging service or controlled access (if used) can be tuned using environment variable. If these environment variables are not defined, the software searches in the file $HOME/.tangorc for its value. If the file is not defined or if the environment variable is also not defined in this file, the software searches in the file /etc/tangorc for its value. For Windows, the file is $TANGO_ROOT/tangorc TANGO_ROOT being the mandatory environment variable of the Windows binary distribution.

###### TANGO_HOST

This environment variable is the anchor of the system. It specifies where the Tango database server is running. Most of the time, its syntax is

TANGO_HOST=<host>:<port>

host is the name of the computer where the database server is running and port is the port number on which it is listening. <host> should be a FQDN (Fully Qualified Domain Name), a hostname or an IP. If you want to have a Tango control system which has several database servers (but only one database) in order to survive a database server crash, use the following syntax

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

Obviously, host_1 is the name of the computer where the first database server is running, port_1 is the port number on which this server is listening. host_2 is the name of the computer where the second database server is running and port_2 is its port number. All access to database will automatically switch from one server to another one in the list if the one which was used has died.

###### Tango Logging Service (TANGO_LOG_PATH)

The TANGO_LOG_PATH environment variable can be used to specify the log files location. If not set it defaults to /tmp/tango-<user logging name> under Unix and C:/tango-<user logging name> under Windows. For a given device-server, the files are actually saved into $TANGO_LOG_PATH/{ server_name}/{ server_instance_name}. This means that all the devices running within the same process log into the same directory.

###### The database and controlled access server (MYSQL_USER, MYSQL_PASSWORD, MYSQL_HOST and MYSQL_DATABASE)

The Tango database server and the controlled access server (if used) need to connect to the MySQL database. They are using four environment variables called MYSQL_USER, MYSQL_PASSWORD to know which user/password they must use to access the database, MYSQL_HOST in case the MySQL database is running on another host and MYSQL_DATABASE to specify the name of the database to connect to. The MYSQL_HOST environment variable allows you to specify the host and port number where MySQL is running. Its syntax is

host:port

The port definition is optional. If it is not specified, the default MySQL port will be used. If these environment variables are not defined, they will connect to the DBMS using the root login on localhost with the MySQL default port number (3306). The MYSQL_DATABASE environment variable has to be used in case your are using the same Tango Database device server executable code to connect to several Tango databases each of them having a different name.

###### The controlled access

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.

###### The event buffer size

If required, the event buffer used by the ZMQ software could be tuned using environment variables. These variables are named TANGO_DS_EVENT_BUFFER_HWM for the event buffer on a device server side and TANGO_EVENT_BUFFER_HWM for the event buffer on the client size. Both of them are a number which is the maximum number of events which could be stored in these buffers.

[1]

Their black-box is also destroyed and re-built

### CORBA

audience:developers lang:all

Below are the 10 important things you should know about CORBA. More detail is provided below for those that are interested.

1. You don’t need to know CORBA to work with TANGO

2. CORBA is the acronym for Common Object Request Broker Architecture and it is a standard defined by the Object Management Group (OMG)

3. CORBA enables communication between software written in different languages and running on different computers

4. CORBA applications are composed of many objects; objects are running software that provides functionalities and that can represent something in the real world

5. Every object has a type which is defined with a language called IDL (Interface Definition Language)

6. An object has an interface and an implementation: this is the essence of CORBA because it allows interoperability.

7. CORBA allows an application to request an operation to be performed by a distributed object and for the results of the operation to be returned back to the application making the request.

8. CORBA is based on a Remote Procedure Call model

9. The TANGO Device is a CORBA Object

10. The TANGO Device Server is a CORBA Application

#### An introduction to CORBA

CORBA is a definition of how to write object request brokers (ORB). The definition is managed by the Object Management Group ([OMG home page]). Various open-source and proprietary implementations exist for CORBA for nearly all operating systems. CORBA uses a programming language independent definition language (called the IDL) to define network object interfaces. Language mappings are defined from IDL to a variety of programming languages e.g. C++, Java, C, Python. Within an interface, CORBA defines two kinds of actions available to the outside world. These actions are called `attributes` and `operations`.

`Operations` are all the actions offered by an interface. For instance, within an interface for a Thermostat class, operations could be the action to read the temperature or to set the nominal temperature.

An `attribute` defines a pair of operations a client can call to send or receive a value. For instance, the position of a motor can be defined as an attribute because it is data that you only set or get. A read-only attribute defines a single operation the client can call to receive a value. In case of error, an operation is able to throw an exception to the client, attributes cannot raises exception except system exception (due to a network fault for instance).

Intuitively, the IDL interfaces correspond to C++ classes and the IDL operations correspond to C++ member functions and attributes as a way to read/write public member variables. Nevertheless, the IDL only defines the interface to an object and says nothing about the object implementation. The IDL is only a descriptive language. Once the interface is fully described in the IDL language, a compiler (from IDL to C++, from IDL to Java…) generates code to implement this interface. Obviously, you still have to write how operations are implemented.

The act of invoking an operation on an interface causes the ORB to send a message to the corresponding object implementation. If the target object is in another address space, the ORB runtime sends a remote procedure call to the implementation. If the target object is in the same address space as the caller, the invocation is accomplished as an ordinary function call to avoid the overhead of using a networking protocol.

For the curious the tango-idl file is available here.

audience:administrators audience:developers lang:c++

### HDB++ Design and implementation

Author: Lorenzo Pivetta

Below, details on deployment and configuration of the `HDB++` service are provided. For overview, see HDB++ of Tools section.

#### HDB++ Contributions

Summary

The is a novel device server for Historical Data Base (HDB) archiving. It’s written in C++ and is fully event-driven.

Contributions

R. Bourtembourg, J.M. Chaize, F.Poncet, J.L. Pons, P.Verdier, D.Lacoste

- ESRF

C.Scafuri, G.Scalamera, G.Strangolino, L.Zambon

- ELETTRA

Revisions

Date | Rev. | Author |
--- | --- | --- | ---
2012-12-04 | 1.0 | L.Pivetta | First release
2013-01-29 | 1.1 | L.Pivetta | Merged suggestions from ESRF
2013-01-31 | 1.2 | L.Pivetta | Cleanup
2013-05-10 | 1.3 | L.Pivetta | Revision after HDB++ meeting on 2013.03.14
2014-01-30 | 1.4 | L.Pivetta | details + Extraction library
2014-03-07 | 1.5 | L.Pivetta | Database interface
2014-05-05 | 1.6 | L.Pivetta | Cleanup, full ES and CM doc
2014-07-28 | 1.7 | L.Pivetta | Revision after HDB++ meeting on 2014.06.25
2016-08-12 | 1.8 | L.Pivetta | Revision after HDB++ meeting on 2016.05.10

#### Historical Database

The Historical Database is a tool that allows to store the values of Attributes into a database. The core implements an event-based interface to allow device servers to publish the data to be archived. The archive event can be triggered by two mechanisms:

- delta_change: the attribute value changed significantly

- periodic: at a fixed periodic interval

The configuration parameters of each attribute, i.e. polling period, delta change thresholds, archiving period, are defined as properties in the database. In addition the archive event can be manually pushed from the device server code.

For additional information concerning the event subsystem please refer to The Control System Manual Version 9.2.

#### HDB++ TANGO Device Server

The architecture is composed by several TANGO devices. More in detail, at least one, but actually many, EventSubscriber TANGO device jointly with one ConfigurationManager device TANGO and one or more DataExtraction TANGO device are foreseen in a TANGO facility.

The EventSubscriber device, also called archiver device, will subscribe to archive events on request by the ConfigurationManager. The EventSubscriber will be able to start archiving all the already configured events even if the ConfigurationManager is not running. The EventSubscriber device must have the following characteristics:

1. the archiving mechanism is event-based, thus the EventSubscriber device tries to subscribe to the event; an error means a fault. A transparent re-subscription to the faulty event is required.

2. one additional thread is in charge of events subscription and call-back execution; the call back, acting as producer, must put the complete data of the received events in a FIFO queue; the thread and the callback must be able to handle an arbitrary number of events, possibly limited just by the available memory and/or the required performance; moreover, a high-mark threshold must be setup on the FIFO in order to alert for an overloaded Event Subscriber

3. one additional thread, acting as consumer of the FIFO, is in charge of pushing the data into the database back-end, preserving the event data time-stamp; the code to access the database engine shall be structured to allow the use of different back-ends (Timescaledb, MySQL, Oracle, etc…)

4. the device server methods, commands and attributes, must allow to perform the following operations:

   - start the archiving for all attributes

   - stop the archiving for all attributes

   - start the archiving for one attribute

   - stop the archiving for one attribute

   - read the number of attributes in charge

   - read the list of attributes in charge

   - read the configuration parameters of each attribute

   - read the number of working attributes

   - read the list of working attributes

   - read the number of faulty attributes

   - read the list of faulty attributes with diagnostics

   - read the size of the FIFO queue

   - read the number of attributes pending in the FIFO

   - read the list of attributes pending in the FIFO

The list of attributes in charge of each EventSubscriber is stored in the database as property of the device.

The EventSubscriber device must be able to run and report on the working/faulty attributes/events by means of the standard API (commands and/or attributes) without the need of a graphical interface.

The diagnostics of faults could also be stored in the general info about each attribute; the diagnostics are used by the EventSubscriber device itself to detect that some data is not being stored as requested. Moreover, whenever the archive event period for a given Attribute has been configured, the device server checks that at least the one archive event/period is received; if not, a error is raised and the Attribute marked as faulty (NOK).

Stopping the archiving of an attribute does not persist after a restart, i.e. restarting an EventSubscriber device instance triggers the archiving of all configured attributes. A property can be setup not to start archiving at startup.

Note: A new Context based mechanism has been introduced to label and automatically start/stop archiving for groups of attributes depending on the specified archiving strategy. See Context description below.

One NULL value with time stamp is inserted whenever the archiving of an attribute is stopped, due to error or by a specific stop command. Moreover, if an error occurred, the corresponding attribute is marked as faulty in the archiving engine and the error description stored. In case the archiving was suspended due to error, it is automatically resumed when good data is available again. The quality factor of the attribute is also stored into the historical database. One or more alarms can be configured in the Alarm System to asynchronously inform about the status of the archiving devices.

Some of the attribute configuration parameters, such as display-unit, format-string and label will also be available in the archive back-end and updated by means of the attribute configuration change event.

A mechanism to specify per-attribute archiving strategies, called context, has been defined and added to the EventSubscriber. The syntax of the AttributeList Property has been modified to support a name=value syntax for the context, except for the Attribute name; fields are separated by semicolon. Keeping the current syntax for the attribute field allows for unchanged backwards compatibility:

    1$ tango://srv-tango-srf.fcs.elettra.trieste.it:20000/eos/climate/18b20 eos.01/State;strategy=RUN|SHUTDOWN

The labels for the context, are defined in a free property, and/or in the class property and/or in the device property, with increasing priority. The defaults values, as well as the default context, are pre-defined but can be modified by the user. The default values are shown in table.

label
ALWAYS
RUN
SHUTDOWN
SERVICE

Table 1: Context default labels.

Each context is exclusive, except for the ALWAYS context. An attribute configured to be run in the ALWAYS context will be archived at any time. This is why the ALWAYS context must be defined, it it is not it will be automatically added to the list of context. A new memorized attribute, named Context, written by upper layer logic, tells the archiver about the current context status or rather the required context transition. Being a memorized Attribute, the Context attribute needs to be written at least once. If not set, a warning will be displayed in the device status, and only the attributes with the ALWAYS context will be archived. This means that, once the device has been deployed, and the AttributeList pupulated with the relevant attributes to be archived, complete with the strategy, the appropriate label has to be written in Context.

The device server shall also expose some additional figures of merit such as:

- for each instance, total number of records per time

- for each instance, total number of failures per time

- for each attribute, number of records per time

- for each attribute, number of failures per time

- for each attribute, time stamp of last record

The system can sum these numbers in a counter which can be reset every period to rank each attribute in term of data rate, error rate etc. This allows preventive maintenance and fine tuning, detecting, for instance, when an attribute is too verbose (e.g. variation threshold below the noise level). These statistics are a key element for qualifying the health of the system. All these attributes will be themselves archived to enable a follow-up versus time.

The device server must maintain at least the following operating states:

- ON: archiving running, everything works

- ALARM: one or more attributes faulty or the FIFO size grows above high-mark threshold

- FAULT: all attributes faulty

- OFF: archiving stopped

#### Configuration and diagnostic tools

With all the statistics kept in the device servers and the device server, the diagnostic tool can be straightforward to develop as a simple QTango or ATK GUI. This GUI will also give read access to the configuration data stored as attribute properties in the database to display the attribute polling frequency of the involved device servers, whenever available, and the archive event configuration. The HDB++ Configurator GUI is available for archiving configuration, management and diagnostics. It is written in Java. Refer to the documentation page for any additional information:

HDB++ Configuration GUI documentation Download GUI jar file

#### Database interface

A C++ API will be developed to address the writing and reading operations on the database and made availabile as a library. This library will provide the essential methods for accessing the database. The device servers, library and tools will eventually take advantage of the library. Actually a number of libraries are already available to encapsulate database access:

libhdb++ | abstraction layer
libhdb++timescale | table support, Timescaledb
libhdb++mysql | table support, MySQL
libhdb++cassandra | table support, Cassandra
libhdbmysql | legacy HDB table support, MySQL

Table 2: Available database interfacement libraries.

Additional libraries are foreseen to support different database engines, such as Oracle, Postgres or possibly noSQL implementations.

Note

The Cassandra Error: “All connections on all I/O threads are busy” is connected with incorrect name of Data Center. For example, the correct name is “datacenter1” but libhdbpp-cassandra have default value “DC1”. To change this value you should add to LibConfiguration property: local_dc =datacenter1

##### database structure

The structure of the legacy HDB is based on three tables, (adt, amt, apt) shown in appendix. In addition, one table, named att_xxxxx is created for each attribute or command to be archived. Many of the columns in the lagacy tables are used for storing HDB archiving engine configuration parameters and are no more required.

The new database structure, whose tables have been designed for the archiver, provides just the necessary columns and takes advantage of microsecond resolution support for daytime. Three SQL scripts are provided to create the necessary database structure for MySQL or Cassandra backend:

create_hdb_mysql.sql | legacy HDB MySQL schema
create_hdb++_mysql.sql | MySQL schema
create_hdb_cassandra.sql | Cassandra schema

Table 3: Database setup scripts.

The att_conf table associates the attribute name with a unique id and selects the data type; it’s worth notice that the att_name raw always contains the complete FQDN, e.g. with the hostname and the domainname.

    mysql> desc att_conf;
    +-----------------------+------------------+------+-----+---------+----------------+
    | Field                 | Type             | Null | Key | Default | Extra          |
    +-----------------------+------------------+------+-----+---------+----------------+
    | att_conf_id           | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
    | att_name              | varchar(255)     | NO   | UNI | NULL    |                |
    | att_conf_data_type_id | int(10) unsigned | NO   | MUL | NULL    |                |
    | att_ttl               | int(10) unsigned | YES  |     | NULL    |                |
    | facility              | varchar(255)     | NO   |     |         |                |
    | domain                | varchar(255)     | NO   |     |         |                |
    | family                | varchar(255)     | NO   |     |         |                |
    | member                | varchar(255)     | NO   |     |         |                |
    | name                  | varchar(255)     | NO   |     |         |                |
    +-----------------------+------------------+------+-----+---------+----------------+

The att_conf_data_type table creates an unique ID for each data type.

    mysql> desc att_conf_data_type;
    +-----------------------+------------------+------+-----+---------+----------------+
    | Field                 | Type             | Null | Key | Default | Extra          |
    +-----------------------+------------------+------+-----+---------+----------------+
    | att_conf_data_type_id | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
    | data_type             | varchar(255)     | NO   |     | NULL    |                |
    | tango_data_type       | tinyint(1)       | NO   |     | NULL    |                |
    +-----------------------+------------------+------+-----+---------+----------------+

The att_history table stores the timestamps relevant for archiving diagnostics together with the att_history_event. The copmplete list of supported TANGO data types is shown in table [db:datatypes]. As an example the table att_scalar_devlong_rw, for archiving one value, is also shown below. Three timestamp rows are currently supported: the datum timestamp, the receive time timestamp and the database insertion timestamp.

    mysql> desc att_history;
    +----------------------+------------------+------+-----+---------+-------+
    | Field                | Type             | Null | Key | Default | Extra |
    +----------------------+------------------+------+-----+---------+-------+
    | att_conf_id          | int(10) unsigned | NO   | MUL | NULL    |       |
    | time                 | datetime(6)      | NO   |     | NULL    |       |
    | att_history_event_id | int(10) unsigned | NO   | MUL | NULL    |       |
    +----------------------+------------------+------+-----+---------+-------+

    mysql> desc att_history_event;
    +----------------------+------------------+------+-----+---------+----------------+
    | Field                | Type             | Null | Key | Default | Extra          |
    +----------------------+------------------+------+-----+---------+----------------+
    | att_history_event_id | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
    | event                | varchar(255)     | NO   |     | NULL    |                |
    +----------------------+------------------+------+-----+---------+----------------+

    mysql> desc att_scalar_devlong_rw;
    +-------------------+------------------+------+-----+----------------------------+-------+
    | Field             | Type             | Null | Key | Default                    | Extra |
    +-------------------+------------------+------+-----+----------------------------+-------+
    | att_conf_id       | int(10) unsigned | NO   | MUL | NULL                       |       |
    | data_time         | timestamp(6)     | NO   |     | 0000-00-00 00:00:00.000000 |       |
    | recv_time         | timestamp(6)     | NO   |     | 0000-00-00 00:00:00.000000 |       |
    | insert_time       | timestamp(6)     | NO   |     | 0000-00-00 00:00:00.000000 |       |
    | value_r           | int(11)          | YES  |     | NULL                       |       |
    | value_w           | int(11)          | YES  |     | NULL                       |       |
    | quality           | tinyint(1)       | YES  |     | NULL                       |       |
    | att_error_desc_id | int(10) unsigned | YES  | MUL | NULL                       |       |
    +-------------------+------------------+------+-----+----------------------------+-------+

scalar | vector
--- | ---
att_scalar_devboolean_ro | att_array_devboolean_ro
att_scalar_devboolean_rw | att_array_devboolean_rw
att_scalar_devdouble_ro | att_array_devdouble_ro
att_scalar_devdouble_rw | att_array_devdouble_rw
att_scalar_devencoded_ro | att_array_devencoded_ro
att_scalar_devencoded_rw | att_array_devencoded_rw
att_scalar_devfloat_ro | att_array_devfloat_ro
att_scalar_devfloat_rw | att_array_devfloat_rw
att_scalar_devlong64_ro | att_array_devlong64_ro
att_scalar_devlong64_rw | att_array_devlong64_rw
att_scalar_devlong_ro | att_array_devlong_ro
att_scalar_devlong_rw | att_array_devlong_rw
att_scalar_devshort_ro | att_array_devshort_ro
att_scalar_devshort_rw | att_array_devshort_rw
att_scalar_devstate_ro | att_array_devstate_ro
att_scalar_devstate_rw | att_array_devstate_rw
att_scalar_devstring_ro | att_array_devstring_ro
att_scalar_devstring_rw | att_array_devstring_rw
att_scalar_devuchar_ro | att_array_devuchar_ro
att_scalar_devuchar_rw | att_array_devuchar_rw
att_scalar_devulong64_ro | att_array_devulong64_ro
att_scalar_devulong64_rw | att_array_devulong64_rw
att_scalar_devulong_ro | att_array_devulong_ro
att_scalar_devulong_rw | att_array_devulong_rw
att_scalar_devushort_ro | att_array_devushort_ro
att_scalar_devushort_rw | att_array_devushort_rw
att_scalar_double_ro | att_array_double_ro
att_scalar_double_rw | att_array_double_rw
att_scalar_string_ro | att_array_string_ro
att_scalar_string_rw | att_array_string_rw

Table 4: Supported data types.

To support temporary storage of historical data the att_ttl column has to be added to the att_conf table. The att_ttl defines the time-to-live in hours on a per-attribute basis. Deleting expired data is delegated to the SQL backend; the basic machanism foreseen is a SQL script run by cron.

The complete SQL source for all the tables is reported in appendix. The main points can be summarized as:

- microsecond timestamp resolution

- no per-attribute additional tables; the number of tables used is fixed and does not depend on the number of archived attributes

- specific data type support

- temporary storage support

Note

There are some special OS settings to tune for Cassandra to work as expected, in particular, it is recommended to disable the SWAP and to change the resource limits on Linux, as described in this documentation page: Recommended production settings for Linux.

#### Deployment best practices

To take full advantage of the high performance and scaling capability of the device server some constraints have to be taken into account. Though a single instance of the device server is capable of handling thousands of events per second, the following setup is preferrable:

- setup per-subsystem instances of the device server (homogeneous dedicated archiving)

- possibly separate attributes that have to be archived all the time, e.g. also during maintenance periods, from attributes that are run-centric

A native tool, available to be run locally, as well as a reworked web interface (E-Giga) are foreseen. A specific library with a dedicated API could be developed to address the extraction and the be used into whatever tool may be provided: a device server, a web interface, a native graphical panel, etc. The library shall be able to deal with event based archived data. The possible lack of data inside the requested time window shall be properly managed:

- returning some no-data-available error: in this case the reply contains no data and a no-data-available error is triggered. Care must be taken whenever the requirement of getting multiple data simultanously is foreseen.

- enlarging the time window itself to include some archived data: the requested time interval is enlarged in order to incorporate some archived data. A mechanism shall be provided to notify the client of the modified data set. No fake samples have to be introduced to fill the values in correspondence of the requested timestamps.

- returning the value of the last archived data anyhow: the requested time interval is kept and the last available data sample is returned. The validity of the data is guaranteed when the archiving mechanism is based on archive event on change; care must be taken when using the data in case of periodic event.

Moreover, whenever extracting multiple rows, the library shall allow to select one of the following behaviours:

- return variable length data arrays for each row

- return equal length data arrays for all rows, filling the gaps with the previous data value

A C++ native implementation, as well as a Java implementation, exposing the same API, are foreseen and are currently available. Please refer to the hdbextractor reference manual for the C++ implementation

and the HDB++ Java Extraction Library for Java HDB++ java-extraction-api

#### General remarks

Care must be taken to avoid introducing dependencies from libraries not already needed by the core.

#### Project references and source code

The HDB++ project page is available on GitLab.

The HDB++ source code for the archiving engine as well as the configuration tools, extraction libraries and GUI are available on

Sourceforge

#### Event Subscriber full documentation

Please, refer to the `HDB++ Design and implementation .pdf file (page 42)`

#### Configuration Manager full documentation

Please, refer to the `HDB++ Design and implementation .pdf file (page 72)`

audience:administrators audience:developers lang:SQL

### Legacy HDB tables structure

    mysql> describe adt;
    +-------------+-------------------------------+------+-----+---------+----------------+
    | Field       | Type                          | Null | Key | Default | Extra          |
    +-------------+-------------------------------+------+-----+---------+----------------+
    | ID          | smallint(5) unsigned zerofill | NO   | PRI | NULL    | auto_increment |
    | time        | datetime                      | YES  |     | NULL    |                |
    | full_name   | varchar(200)                  | NO   | PRI |         |                |
    | device      | varchar(150)                  | NO   |     |         |                |
    | domain      | varchar(35)                   | NO   |     |         |                |
    | family      | varchar(35)                   | NO   |     |         |                |
    | member      | varchar(35)                   | NO   |     |         |                |
    | att_name    | varchar(50)                   | NO   |     |         |                |
    | data_type   | tinyint(1)                    | NO   |     | 0       |                |
    | data_format | tinyint(1)                    | NO   |     | 0       |                |
    | writable    | tinyint(1)                    | NO   |     | 0       |                |
    | max_dim_x   | smallint(6) unsigned          | NO   |     | 0       |                |
    | max_dim_y   | smallint(6) unsigned          | NO   |     | 0       |                |
    | levelg      | tinyint(1)                    | NO   |     | 0       |                |
    | facility    | varchar(45)                   | NO   |     |         |                |
    | archivable  | tinyint(1)                    | NO   |     | 0       |                |
    | substitute  | smallint(9)                   | NO   |     | 0       |                |
    +-------------+-------------------------------+------+-----+---------+----------------+

    mysql> describe amt;
    +-------------------+-------------------------------+------+-----+---------+-------+
    | Field             | Type                          | Null | Key | Default | Extra |
    +-------------------+-------------------------------+------+-----+---------+-------+
    | ID                | smallint(5) unsigned zerofill | NO   |     | 00000   |       |
    | archiver          | varchar(255)                  | NO   |     |         |       |
    | start_date        | datetime                      | YES  |     | NULL    |       |
    | stop_date         | datetime                      | YES  |     | NULL    |       |
    | per_mod           | int(1)                        | NO   |     | 0       |       |
    | per_per_mod       | int(5)                        | YES  |     | NULL    |       |
    | abs_mod           | int(1)                        | NO   |     | 0       |       |
    | per_abs_mod       | int(5)                        | YES  |     | NULL    |       |
    | dec_del_abs_mod   | double                        | YES  |     | NULL    |       |
    | gro_del_abs_mod   | double                        | YES  |     | NULL    |       |
    | rel_mod           | int(1)                        | NO   |     | 0       |       |
    | per_rel_mod       | int(5)                        | YES  |     | NULL    |       |
    | n_percent_rel_mod | double                        | YES  |     | NULL    |       |
    | p_percent_rel_mod | double                        | YES  |     | NULL    |       |
    | thr_mod           | int(1)                        | NO   |     | 0       |       |
    | per_thr_mod       | int(5)                        | YES  |     | NULL    |       |
    | min_val_thr_mod   | double                        | YES  |     | NULL    |       |
    | max_val_thr_mod   | double                        | YES  |     | NULL    |       |
    | cal_mod           | int(1)                        | NO   |     | 0       |       |
    | per_cal_mod       | int(5)                        | YES  |     | NULL    |       |
    | val_cal_mod       | int(3)                        | YES  |     | NULL    |       |
    | type_cal_mod      | int(2)                        | YES  |     | NULL    |       |
    | algo_cal_mod      | varchar(20)                   | YES  |     | NULL    |       |
    | dif_mod           | int(1)                        | NO   |     | 0       |       |
    | per_dif_mod       | int(5)                        | YES  |     | NULL    |       |
    | ext_mod           | int(1)                        | NO   |     | 0       |       |
    | refresh_mode      | tinyint(4)                    | YES  |     | 0       |       |
    +-------------------+-------------------------------+------+-----+---------+-------+

    mysql> describe apt;
    +---------------+--------------------------+------+-----+---------+-------+
    | Field         | Type                     | Null | Key | Default | Extra |
    +---------------+--------------------------+------+-----+---------+-------+
    | ID            | int(5) unsigned zerofill | NO   | PRI | 00000   |       |
    | time          | datetime                 | YES  |     | NULL    |       |
    | description   | varchar(255)             | NO   |     |         |       |
    | label         | varchar(64)              | NO   |     |         |       |
    | unit          | varchar(64)              | NO   |     | 1       |       |
    | standard_unit | varchar(64)              | NO   |     | 1       |       |
    | display_unit  | varchar(64)              | NO   |     |         |       |
    | format        | varchar(64)              | NO   |     |         |       |
    | min_value     | varchar(64)              | NO   |     | 0       |       |
    | max_value     | varchar(64)              | NO   |     | 0       |       |
    | min_alarm     | varchar(64)              | NO   |     | 0       |       |
    | max_alarm     | varchar(64)              | NO   |     | 0       |       |
    +---------------+--------------------------+------+-----+---------+-------+

audience:administrators audience:developers lang:CQL

### schema CQL source (Cassandra)

    -- Create hdb keyspace
    -- Please adapt the replication factor (3 by default here) to your use case
    CREATE KEYSPACE IF NOT EXISTS hdb WITH REPLICATION = { 'class' : 'NetworkTopologyStrategy', 'DC1' : 3 };

    USE hdb;

    CREATE TYPE IF NOT EXISTS devencoded (
     encoded_format text,
     encoded_data blob
    );

    CREATE TABLE IF NOT EXISTS att_conf (
    cs_name text,
    att_name text,
    att_conf_id timeuuid,
    data_type text,   -- data_types set<text> in the future?
    PRIMARY KEY (cs_name, att_name)
    )
    WITH comment='Attribute Configuration Table'
    AND caching = {'keys' : 'NONE', 'rows_per_partition': 'ALL' };

    CREATE INDEX on att_conf(data_type);
    CREATE INDEX on att_conf(att_conf_id);

    CREATE TABLE IF NOT EXISTS att_history
    (
    att_conf_id timeuuid,
    time timestamp,
    time_us int,
    event text, -- 'add','remove','start','stop' or 'crash'
    PRIMARY KEY(att_conf_id, time, time_us)
    )
    WITH comment='Attribute Configuration Events History Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devboolean_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r boolean,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevBoolean ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devboolean_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r boolean,
    value_w boolean,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevBoolean ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devuchar_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r int,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    ) WITH comment='Scalar DevUChar ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devuchar_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r int,
    value_w int,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevUChar ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devshort_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r int,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    ) WITH comment='Scalar DevShort ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devshort_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r int,
    value_w int,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevShort ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devushort_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r int,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevUShort ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devushort_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r int,
    value_w int,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevUShort ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devlong_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r int,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevLong ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devlong_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r int,
    value_w int,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevLong ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devulong_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r bigint,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevULong ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devulong_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r bigint,
    value_w bigint,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevULong ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devlong64_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r bigint,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevLong64 ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devlong64_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r bigint,
    value_w bigint,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevLong64 ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devulong64_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r bigint,              // issue here with very big numbers
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevULong64 ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devulong64_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r bigint, // issue here with very big numbers
    value_w bigint, // issue here with very big numbers
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevLong64 ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devfloat_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r float,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevFloat ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devfloat_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r float,
    value_w float,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevFloat ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devdouble_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r double,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevDouble ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devdouble_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r double,
    value_w double,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevDouble ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devstring_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r text,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevString ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devstring_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r text,
    value_w text,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevString ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devstate_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r int,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevState ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devstate_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r int,
    value_w int,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevState ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devencoded_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r frozen<devencoded>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevEncoded ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devencoded_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r frozen<devencoded>,
    value_w frozen<devencoded>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Scalar DevEncoded ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devboolean_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<boolean>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevBoolean ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devboolean_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<boolean>,
    value_w list<boolean>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevBoolean ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devuchar_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<int>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevUChar ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devuchar_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<int>,
    value_w list<int>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevUChar ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devshort_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<int>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevShort ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devshort_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<int>,
    value_w list<int>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevShort ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devushort_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<int>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevUShort ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devushort_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<int>,
    value_w list<int>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevUShort ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devlong_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<int>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevLong ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devlong_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<int>,
    value_w list<int>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevLong ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devulong_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<bigint>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevULong ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devulong_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<bigint>,
    value_w list<bigint>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevULong ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devlong64_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<bigint>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevLong64 ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devlong64_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<bigint>,
    value_w list<bigint>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevLong64 ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devulong64_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<bigint>,  // issue with very big numbers
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevULong64 ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devulong64_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<bigint>, // issue with very big numbers
    value_w list<bigint>, // issue with very big numbers
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevULong64 ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devfloat_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<float>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevFloat ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devfloat_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<float>,
    value_w list<float>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevFloat ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devdouble_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<double>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevDouble ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devdouble_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<double>,
    value_w list<double>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevDouble ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devstring_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<text>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevString ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devstring_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<text>,
    value_w list<text>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevString ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devstate_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<int>, // Store a special type here
                       // where we could store an int and a string?
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevState ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devstate_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<int>,// Store a special type here
    value_w list<int>,// where we could store an int and a string?
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevState ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devencoded_ro (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<frozen<devencoded>>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevEncoded ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devencoded_rw (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    value_r list<frozen<devencoded>>,
    value_w list<frozen<devencoded>>,
    quality int,
    error_desc text,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Array DevEncoded ReadWrite Values Table';

    -------------------------
    -- att_parameter table --
    -------------------------
    CREATE TABLE IF NOT EXISTS att_parameter (
    att_conf_id timeuuid,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    label text,
    unit text,
    standard_unit text,
    display_unit text,
    format text,
    archive_rel_change text,
    archive_abs_change text,
    archive_period text,
    description text,
    PRIMARY KEY((att_conf_id), recv_time, recv_time_us)
    ) WITH COMMENT='Attribute configuration parameters';

    --------------------------------
    -- Attributes browsing tables --
    --------------------------------
    CREATE TABLE IF NOT EXISTS domains (
    cs_name text,
    domain text,
    PRIMARY KEY (cs_name, domain)
    )
    WITH CLUSTERING ORDER BY (domain ASC)
    AND comment='Domains Table'
    AND caching = {'keys' : 'NONE', 'rows_per_partition': 'ALL' };

    CREATE TABLE IF NOT EXISTS families (
    cs_name text,
    domain text,
    family text,
    PRIMARY KEY ((cs_name, domain),family)
    )
    WITH CLUSTERING ORDER BY (family ASC)
    AND comment='Families Table'
    AND caching = {'keys' : 'NONE', 'rows_per_partition': 'ALL' };

    CREATE TABLE IF NOT EXISTS members (
    cs_name text,
    domain text,
    family text,
    member text,
    PRIMARY KEY ((cs_name, domain,family),member)
    )
    WITH CLUSTERING ORDER BY (member ASC)
    AND comment='Members Table'
    AND caching = {'keys' : 'NONE', 'rows_per_partition': 'ALL' };

    CREATE TABLE IF NOT EXISTS att_names (
    cs_name text,
    domain text,
    family text,
    member text,
    name text,
    PRIMARY KEY ((cs_name, domain,family,member),name)
    )
    WITH CLUSTERING ORDER BY (name ASC)
    AND comment='Attributes Names Table'
    AND caching = {'keys' : 'NONE', 'rows_per_partition': 'ALL' };

    CREATE TABLE IF NOT EXISTS time_stats (
    att_conf_id timeuuid,
    period text,
    data_time timestamp,
    data_time_us int,
    recv_time timestamp,
    recv_time_us int,
    insert_time timestamp,
    insert_time_us int,
    PRIMARY KEY ((att_conf_id ,period),data_time,data_time_us)
    )
    WITH comment='Time Statistics Table';

audience:administrators audience:developers lang:c++

### Configuration Manager interface

In order to address large archiving systems the need to distribute the workload over a large number of event subscriber shows up. The configuration manager device server will assist in the operations of adding, editing, moving, deleting an attribute from the archiving system. All the configuration parameters, such as polling period, variation thresholds etc., are kept in the database as properties of the archived attribute. In order to be managed by the device server each instance has to added to the pool using the ArchiverAdd command.

The configuration manager shall be able to perform the following operations on the managed pool:

1. manage the request of archiving a new attribute

   > - setup the attribute’s archive event configuration

   > - assign the new attribute to one of the device servers

   >   > - following some rules of load balancing

   >   > - to the specified device server

2. move an attribute from a device server to another one

3. keep trace of which attribute is assigned to which

4. start/stop the archiving of an attribute at runtime

5. remove an attribute from archiving

The configuration shall be possible via the device server API as well as via a dedicated GUI interface; the GUI just use the provided API.

The ConfigurationManager may also expose a certain number of attributes to give the status of what is going on:

- total number of Archivers

- total number of working attributes

- total number of faulty attributes

- total number of calls per second

These attributes could be themselves archived to enable a follow up versus time.

More in detail the device server exposes the following interface.

#### Commands

The available commands are summarized in commands-table.

ArchiverAdd | add a new archiver instance to the archivers list; the instance must have been already created and configured via jive/astor and the device shall be running
ArchiverRemove | remove an archiver from the list; neither the device instance nor the attributes configured are removed from the database
AttributeAdd | add an attribute to archiving
AttributeAssign | assign attribute to an archiver
AttributeGetArchiver | return the archiver in charge of attribute
AttributePause | pause archiving specified attribute
AttributeRemove | remove an attribute from archiving; the archived data and the attribute archive event configuration are left untouched
AttributeSearch | return list of attributes containing input pattern
AttributeStart | start archiving an attribute
AttributeStatus | read attribute archiving status
AttributeStop | stop archiving an attribute
AttributeUpdate | update context of an already archived attribute
Context | set context to all managed archivers
ResetStatistics | reset statistics the configuration manager of and all archivers

Table 1: Configuration Manager Commands.

Note that the list of managed archivers is stored into the ArchiverList device property that is maintained via the ArchiverAdd, ArchiverRemove and AttributeSetArchiver commands. Therefore in the archiving system the device server instances can also be configured by hand, if required, an run independently.

#### Attributes

The attributes of the configuration manager are summarized in attributes-table.

ArchiverContext | return archiver context
ArchiverList | return list of managed archivers
ArchiverStatisticsResetTime | seconds elapsed since last statistics reset
ArchiverStatus | return archiver status information
AttributeFailureFreq | total number of failures per time
AttributeMaxPendingNumber | max number of attributes waiting to be archived (all archivers)
AttributeMaxProcessingTime | max processing time (all archivers)
AttributeMaxStoreTime | max storing time (all archivers)
AttributeMinProcessingTime | min processing time (all archivers)
AttributeMinStoreTime | min storing time (all archivers)
AttributeNokNumber | total number of archived attribute in error
AttributeNumber | total number of attributes configured for archiving
AttributeOkNumber | total number of archived attribute not in error
AttributePausedNumber | total number of paused attributes
AttributePendingNumber | total number of attributes waiting to be archived
AttributeRecordFreq | total number of records per time
AttributeStartedNumber | total number of started attributes
AttributeStoppedNumber | total number of stopped attributes
SetAbsoluteEvent | set archive absolute thresholds; for archiving setup
SetArchiver | support attribute for setup
SetAttributeName | support attribute for setup
SetCodePushedEvent | specify event pushed in the code
SetContext | set archiving context; for archiving setup
SetPeriodEvent | set archive period; for archiving setup
SetPollingPeriod | set polling period; for archiving setup
SetRelativeEvent | set archive relative thresholds; for archiving setup
SetTTL | set time-to-live for temporary storage; for archiving setup

Table 2: Configuration Manager Attributes.

The SetXxxYyy attributes are used for archive event and archiver instance configuration setup and must be filled before calling the AttributeAdd command. The AttributeAdd checks the consistency of the desired event configuration and then adds the new attribute to the archiver instance specified with SetArchiver. Then the AttributeAdd command creates the required entries into the historical database.

#### Class properties

LibConfiguration | configuration parameters for backend support library
MaxSearchSize | max size for AttributeSearch result

Table 3: Event Subscriber Class properties.

#### Device properties

ArchiverList | list of existing archivers
LibConfiguration | configuration parameters for backend support library
MaxSearchSize | max size for AttributeSearch result

Table 4: Configuration Manager device properties.

audience:administrators audience:developers lang:c++

### Event Subscriber interface

More in detail the device server interface is summarized in table 1 and table 2.

#### Commands

AttributeAdd | add an attribute to archiving; the complete FQDN has to be specified otherwise it is completed by the using getaddrinfo()
AttributeContext | read the specified attribute current context
AttributePause | pause archiving specified attribute but do not unsubscribe archive event
AttributeRemove | remove an attribute from archiving; the archived data and the attribute archive event configuration are left untouched
AttributeStatus | read attribute status
AttributeStart | start archiving specified attribute
AttributeStop | stop archiving specified attribute, unsubscribe archive event
AttributeUpdate | update context of an already archived attribute
Pause | pause archiving all attributes but do not unsubscribe archive events
Start | start archiving
Stop | stop archiving, usubscribe all archive events
ResetStatistics | reset statistics

Table 1: Event Subscriber Command.

#### Attributes

AttributeContextList | return the list of attribute contexts
AttributeErrorList | return the list of attribute errors
AttributeEventNumberList | number of events received for each attribute
AttributeFailureFreq | total number of failures per time
AttributeFailureFreqList | per-attribute number of failures per time
AttributeList | return configured attribute list
AttributeMaxPendingNumber | maximum number of attributes waiting to be archived
AttributeMaxProcessingTime | max processing time
AttributeMaxStoreTime | max storing time
AttributeMinProcessingTime | min processing time
AttributeMinStoreTime | min storing time
AttributeNokList | return the list of attribute in error
AttributeNokNumber | number of archived attribute in error
AttributeNumber | number of attributes configured for archiving
AttributeOkList | return the list of attributes not in error
AttributeOkNumber | number of archived attributes not in error
AttributePausedList | list of paused attributes
AttributePausedNumber | number of paused attributes
AttributePendingList | list of attributes waiting to be archived
AttributePendingNumber | number of attributes waiting to be archived
AttributeRecordFreq | total number of records per time
AttributeRecordFreqList | per-attribute number of records per time
AttributeStartedList | list of started attributes
AttributeStartedNumber | number of started attributes
AttributeStoppedList | list of stopped attributes
AttributeStoppedNumber | number of stopped attributes
Context | archiver current context (r/w)
StatisticsResetTime | seconds elapsed since last statistics reset

Table 2: Event Subscriber Attributes.

The class and device properties availabile for configuration are shown in table 3. According to TANGO device server design guidelines, Device Properties, when defined, override Class properties. Please note that class and device Properties have changed since release of the TANGO device server.

#### Class properties

CheckPeriodicTimeoutDelay | delay before timeout when checking periodic events, in seconds
PollingThreadPeriod | default period for polling thread, in seconds
LibConfiguration | configuration parameters for backend support library
ContextsList | definition of possible archiver operating contexts
DefaultStrategy | default context for a new attribute (Default to RUN)
StatisticsTimeWindow | timeslot for statistics in seconds
SubscribeRetryPeriod | retry period for subscribe event, in seconds

Table 3: Event Subscriber Class properties.

The LibConfiguration property contains the following multi-line configuration parameters host, user, password, dbname, libname, port. Table shows example configuration parameters for backend:

host=srv-log-srf.fcs.elettra.trieste.it
user=hdbarchiver
password=myownpassword
dbname=hdbpp
libname=dependOnDatabase (see below)
port=3306

Table 4: LibConfiguration parameters for database.

Note

The event subscriber can be built directly against a specific backend or the dynamic libhdbpp. In case of direct linking, this information is not relevant.

libname should be set to one of the following values:

libname=libhdb++mysql.so if you intend to use HDB++ with the MySQL backend libname=libhdbmysql.so if you intend to use HDB++ with the MySQL Legacy backend libname=libhdb++cassandra.so if you intend to use HDB++ with the Cassandra backend libname=libhdb++timescale.so if you intend to use HDB++ with the TimescaleDB backend

The library specified in LibConfiguration->libname is loaded dynamically by the EventSubscriber device (e.g. hdb++-es-srv). You need to have your LD_LIBRARY_PATH environment variable correctly set (including the directory where the library you intend to use is located).

libhdb++mysql and libhdb++cassandra are just implementations of the classes defined in libhdb++ library. The user can decide which implementation to use by specifying this LibConfiguration -> libname device property config parameter.

The device dynamically loads the configured library configured (using dlopen()) during the device initialization. See Database interface section for more information.

#### Device properties

AttributeList | list of configured attributes
CheckPeriodicTimeoutDelay | delay before timeout when checking periodic events, in seconds
PollingThreadPeriod | default period for polling thread, in seconds
LibConfiguration | configuration parameters for backend support library
ContextsList | definition of possible archiver operating contexts
DefaultStrategy | default context for a new attribute (Default to RUN)
StatisticsTimeWindow | timeslot for statistics
SubscribeRetryPeriod | retry period for subscribe event, in seconds

Table 5: Event Subscriber Device properties.

In addition to the already described Class properties, device Properties comprehend the AttributeList property which contains the list of attributes in charge of the current device. The syntax is fully-qualified-attribute-name;context=CONTEXT where CONTEXT can be one or a combination of the defined contexts (logic OR). Whenever not specified the DefaultContext specified in the Class property or in the Device Property applies. Table shows some examples:

    1$ tango://srv-tango-srf.fcs.elettra.trieste.it:20000/eos/climate/18b20 eos.01/State;context=RUN|SHUTDOWN
    2$ tango://srv-tango-srf.fcs.elettra.trieste.it:20000/eos/climate/18b20 eos.01/Temperature;context=RUN|SHUTDOWN
    3$ tango://srv-tango-srf.fcs.elettra.trieste.it:20000/ctf/diagnostics/ccd_ctf.01/State;context=RUN
    4$ tango://srv-tango-srf.fcs.elettra.trieste.it:20000/ctf/diagnostics/ccd_ctf.01/HorProfile;context=RUN
    5$ tango://srv-tango-srf.fcs.elettra.trieste.it:20000/ctf/diagnostics/ccd_ctf.01/VerProfile;context=RUN

Table 6: AttributeList example

The first two attributes will be archived in both RUN and SHUTDOWN contexts; the last three only when in RUN.

audience:administrators audience:developers lang:SQL

### schema SQL source (MySQL)

    CREATE TABLE IF NOT EXISTS att_conf
    (
    att_conf_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    att_name VARCHAR(255) UNIQUE NOT NULL,
    att_conf_data_type_id INT UNSIGNED NOT NULL,
    att_ttl INT UNSIGNED NULL DEFAULT NULL,
    facility VARCHAR(255) NOT NULL DEFAULT '',
    domain VARCHAR(255) NOT NULL DEFAULT '',
    family VARCHAR(255) NOT NULL DEFAULT '',
    member VARCHAR(255) NOT NULL DEFAULT '',
    name VARCHAR(255) NOT NULL DEFAULT '',
    INDEX(att_conf_data_type_id)
    ) ENGINE=MyISAM COMMENT='Attribute Configuration Table';

    DROP TABLE att_conf_data_type;
    CREATE TABLE IF NOT EXISTS att_conf_data_type
    (
    att_conf_data_type_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    data_type VARCHAR(255) NOT NULL,
    tango_data_type TINYINT(1) NOT NULL
    ) ENGINE=MyISAM COMMENT='Attribute types description';

    INSERT INTO att_conf_data_type (data_type, tango_data_type) VALUES
    ('scalar_devboolean_ro', 1),('scalar_devboolean_rw', 1),('array_devboolean_ro', 1),
    ('array_devboolean_rw', 1),('scalar_devuchar_ro', 22),('scalar_devuchar_rw', 22),
    ('array_devuchar_ro', 22),('array_devuchar_rw', 22),('scalar_devshort_ro', 2),
    ('scalar_devshort_rw', 2),('array_devshort_ro', 2),('array_devshort_rw', 2),
    ('scalar_devushort_ro', 6),('scalar_devushort_rw', 6),('array_devushort_ro', 6),
    ('array_devushort_rw', 6),('scalar_devlong_ro', 3),('scalar_devlong_rw', 3),
    ('array_devlong_ro', 3),('array_devlong_rw', 3),('scalar_devulong_ro', 7),
    ('scalar_devulong_rw', 7),('array_devulong_ro', 7),('array_devulong_rw', 7),
    ('scalar_devlong64_ro', 23),('scalar_devlong64_rw', 23),('array_devlong64_ro', 23),
    ('array_devlong64_rw', 23),('scalar_devulong64_ro', 24),('scalar_devulong64_rw', 24),
    ('array_devulong64_ro', 24),('array_devulong64_rw', 24),('scalar_devfloat_ro', 4),
    ('scalar_devfloat_rw', 4),('array_devfloat_ro', 4),('array_devfloat_rw', 4),
    ('scalar_devdouble_ro', 5),('scalar_devdouble_rw', 5),('array_devdouble_ro', 5),
    ('array_devdouble_rw', 5),('scalar_devstring_ro', 8),('scalar_devstring_rw', 8),
    ('array_devstring_ro', 8),('array_devstring_rw', 8),('scalar_devstate_ro', 19),
    ('scalar_devstate_rw', 19),('array_devstate_ro', 19),('array_devstate_rw', 19),
    ('scalar_devencoded_ro', 28),('scalar_devencoded_rw', 28),('array_devencoded_ro', 28),
    ('array_devencoded_rw', 28);

    CREATE TABLE IF NOT EXISTS att_history
    (
    att_conf_id INT UNSIGNED NOT NULL,
    time TIMESTAMP(6) DEFAULT 0,
    att_history_event_id INT UNSIGNED NOT NULL,
    INDEX(att_conf_id),
    INDEX(att_history_event_id)
    ) ENGINE=MyISAM COMMENT='Attribute Configuration Events History Table';

    DROP TABLE att_history_event;
    CREATE TABLE IF NOT EXISTS att_history_event
    (
    att_history_event_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    event VARCHAR(255) NOT NULL
    ) ENGINE=MyISAM COMMENT='Attribute history events description';

    INSERT INTO att_history_event (event) VALUES
    ('add'),('remove'),('start'),('stop'),('crash'),('pause');

    CREATE TABLE IF NOT EXISTS att_parameter
    (
    att_conf_id INT UNSIGNED NOT NULL,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    label VARCHAR(255) NOT NULL DEFAULT '',
    unit VARCHAR(64) NOT NULL DEFAULT '',
    standard_unit VARCHAR(64) NOT NULL DEFAULT '1',
    display_unit VARCHAR(64) NOT NULL DEFAULT '',
    format VARCHAR(64) NOT NULL DEFAULT '',
    archive_rel_change VARCHAR(64) NOT NULL DEFAULT '',
    archive_abs_change VARCHAR(64) NOT NULL DEFAULT '',
    archive_period VARCHAR(64) NOT NULL DEFAULT '',
    description VARCHAR(1024) NOT NULL DEFAULT '',
    INDEX(recv_time),
    INDEX(att_conf_id)
    ) ENGINE=MyISAM COMMENT='Attribute configuration parameters';

    CREATE TABLE IF NOT EXISTS att_error_desc
    (
    att_error_desc_id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    error_desc VARCHAR(255) UNIQUE NOT NULL
    ) ENGINE=MyISAM COMMENT='Error Description Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devboolean_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r TINYINT(1) UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Boolean ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devboolean_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r TINYINT(1) UNSIGNED DEFAULT NULL,
    value_w TINYINT(1) UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Boolean ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devboolean_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r TINYINT(1) UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Boolean ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devboolean_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r TINYINT(1) UNSIGNED DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w TINYINT(1) UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Boolean ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devuchar_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r TINYINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar UChar ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devuchar_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r TINYINT UNSIGNED DEFAULT NULL,
    value_w TINYINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar UChar ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devuchar_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r TINYINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array UChar ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devuchar_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r TINYINT UNSIGNED DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w TINYINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array UChar ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devshort_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r SMALLINT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Short ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devshort_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r SMALLINT DEFAULT NULL,
    value_w SMALLINT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Short ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devshort_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r SMALLINT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Short ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devshort_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r SMALLINT DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w SMALLINT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Short ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devushort_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r SMALLINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar UShort ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devushort_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r SMALLINT UNSIGNED DEFAULT NULL,
    value_w SMALLINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar UShort ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devushort_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r SMALLINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array UShort ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devushort_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r SMALLINT UNSIGNED DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w SMALLINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array UShort ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devlong_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r INT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Long ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devlong_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r INT DEFAULT NULL,
    value_w INT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Long ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devlong_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r INT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Long ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devlong_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r INT DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w INT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Long ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devulong_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r INT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar ULong ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devulong_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r INT UNSIGNED DEFAULT NULL,
    value_w INT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar ULong ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devulong_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r INT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array ULong ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devulong_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r INT UNSIGNED DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w INT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array ULong ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devlong64_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r BIGINT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Long64 ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devlong64_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r BIGINT DEFAULT NULL,
    value_w BIGINT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Long64 ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devlong64_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r BIGINT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Long64 ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devlong64_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r BIGINT DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w BIGINT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Long64 ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devulong64_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r BIGINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar ULong64 ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devulong64_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r BIGINT UNSIGNED DEFAULT NULL,
    value_w BIGINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar ULong64 ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devulong64_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r BIGINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array ULong64 ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devulong64_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r BIGINT UNSIGNED DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w BIGINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array ULong64 ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devfloat_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r FLOAT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Float ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devfloat_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r FLOAT DEFAULT NULL,
    value_w FLOAT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Float ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devfloat_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r FLOAT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Float ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devfloat_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r FLOAT DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w FLOAT DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Float ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devdouble_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r DOUBLE DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Double ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devdouble_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r DOUBLE DEFAULT NULL,
    value_w DOUBLE DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Double ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devdouble_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r DOUBLE DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Double ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devdouble_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r DOUBLE DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w DOUBLE DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Double ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devstring_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r VARCHAR(16384) DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar String ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devstring_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r VARCHAR(16384) DEFAULT NULL,
    value_w VARCHAR(16384) DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar String ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devstring_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r VARCHAR(16384) DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array String ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devstring_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r VARCHAR(16384) DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w VARCHAR(16384) DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array String ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devstate_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r TINYINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar State ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devstate_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r TINYINT UNSIGNED DEFAULT NULL,
    value_w TINYINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar State ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devstate_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r TINYINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array State ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devstate_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r TINYINT UNSIGNED DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w TINYINT UNSIGNED DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array State ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devencoded_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r BLOB DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Encoded ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_scalar_devencoded_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    value_r BLOB DEFAULT NULL,
    value_w BLOB DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Scalar Encoded ReadWrite Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devencoded_ro
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r BLOB DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Encoded ReadOnly Values Table';

    CREATE TABLE IF NOT EXISTS att_array_devencoded_rw
    (
    att_conf_id INT UNSIGNED NOT NULL,
    data_time TIMESTAMP(6) DEFAULT 0,
    recv_time TIMESTAMP(6) DEFAULT 0,
    insert_time TIMESTAMP(6) DEFAULT 0,
    idx INT UNSIGNED NOT NULL,
    dim_x_r INT UNSIGNED NOT NULL,
    dim_y_r INT UNSIGNED NOT NULL DEFAULT 0,
    value_r BLOB DEFAULT NULL,
    dim_x_w INT UNSIGNED NOT NULL,
    dim_y_w INT UNSIGNED NOT NULL DEFAULT 0,
    value_w BLOB DEFAULT NULL,
    quality TINYINT(1) DEFAULT NULL,
    att_error_desc_id INT UNSIGNED NULL DEFAULT NULL,
    INDEX att_conf_id_data_time (att_conf_id,data_time)
    ) ENGINE=MyISAM COMMENT='Array Encoded ReadWrite Values Table';

audience:administrators audience:developers lang:SQL

### schema SQL source (TimescaleDb)

All the sql resources for TimescaleDb can be found on the gitlab project tango-controls/hdbpp/hdbpp-timescale-project. Under resource/schema. On top of the general architecture you can find extensions schemas for reordering, aggregates and any other features.

### Old Presentations

Warning

Here are some old presentations and tutorials.

They might contain references to deprecated versions and/or details that aren’t accurate anymore.

Nonetheless, basic Tango concepts haven’t changed and they still contain a lot of useful information.

You can look through the following presentations to get a first overview of the TANGO Control system.

- `TANGO introduction`

- `Overview of the Tango control system`

- `TANGO basics, technical overview`

You can check some detailed documentation:

#### Scada introduction with TANGO examples

audience:all

Scada introduction with TANGO examples from Jean-Charles Tournier. He works at CERN Geneva and teach at EPFL (Ecole Polytechnique Fédérale de Lausanne). Important notes from an expert! He uses Tango as an example in his course at EPFL.

Download .pdf file `here`.

#### General TANGO training for C++

audience:developers lang:c++

The support document `TANGO training for C++` for the Tango training course which covers most of the Tango features. The source code for all exercises is also available as zipped tar archives.

#### General TANGO training for Python

audience:developers lang:python

`TANGO training for Python` - the last Tango training given at MaxLab in Sweden. The training code examples and exercises are in Python. The source code for all exercises (in Python) is available as a zipped archive.

## Authors

audience:all

Good documentation needs dedicated authors who spend lots of time writing and reading text instead of code. This labour of love is only rarely appreciated by readers. This section lists the numerous contributors to the Tango documentation. If you are reading this section don’t hesitate to send them some positive thoughts and thanks for their “labour of love” right now!

The Tango Controls documentation you are reading has been assembled into its current form at four documentation sprints and workshops:

1. Third Write-the-Doc Team - the following people participated in the third Write-the-Doc camp held at l’Escandille in Autrans (Vercors) in October 2024: Benjamin Bertrand, Reynald Bourtembourg, Thomas Braun, Andy Götz, Vincent Hardion, Anton Joubert, Thomas Jürges, Damien Lacoste, Vicente Rey-Bakaikoa, Nicolas Tappret

- Second Write-the-Doc Team - the following people participated in the second Write-the-Doc camp held at Solaris in Krakow (Poland): Reynald Bourtembourg, Thomas Braun, Sébastien Gara, Philippe Gauron, Andrew Götz, Piotr Goryl, Anton Joubert, Krystian Kędroń, Igor Khokhrakiov, Grzegorz Kowalski, Olga Merkulova, Guillaume Mugerin, Lorenzo Pivetta and Sergi Rubio

- First Write-the-Doc Team - the following people assisted to the first Write-the-Doc camp in St Nizier du Moucherotte (Vercors): Piotr Goryl, Olga Merkulova, Lukasz Zytniak, Lukasz Dudek, Matteo di Carlo, Matteo Canzari, Igor Khokhrakiov, Reynald Bourtembourg, Jean-Michel Chaize, Stuart James and Andy Götz

The following people have contributed to the Tango documentation over the years:

- Gwenaelle Abeille - for writing the original JTango documentation

- Benjamin Bertrand - for converting RST to MyST and managing the restructured documentation

- Reynald Bourtembourg - for writing the HDB++ documentation

- Thomas Braun - for doing the first conversion of the Book to Sphinx in RST and then again to MyST

- Alain Buteau - for the guidelines documentation

- Matteo di Carlo - for drawing the Device Server system model

- Tiago Couthino - for writing the PyTango documentation and showing the way with Sphinx

- Lukasz Dudek - for converting many documents to Sphinx and setting up CI

- Philippe Gauron - for converting many documents to MyST

- Piotr Goryl - for reformatting the documentation into Sphinx and documentation master

- Lajos Fülöp - for the original layered maps

- Andy Götz - for contributing and motivating to have a complete Tango documentation

- Vincent Hardion - for converting to MyST and building the restructured documentation

- Stuart James - for editing and updating the layered maps

- Thomas Juerges - for writing explanations and being picky about thos ehe didn’t write

- Igor Khokhrakiov - for writing the new version of JTango documentation, REST api, Amazon cloud etc

- Damian Lacoste - for converting documents to MyST and rewriting Pogo and HDB++ documentation

- Nicolas Leclerq - for the Yat4Tango, bindings documentations

- Olga Merkulova - for re-organising the documentation and writing getting started

- Lorenzo Pivetta - for writing the HDB++ documentation

- Faranguiss Poncet - for writing the ATK documentation

- Jean-Luc Pons - for writing the Jive documentation

- Sergi Rubio - for the Fandango and Panic documentation

- Olivier Tachet - for the how to on installing Tango on a Raspberry Pi

- Emmanuel Taurel - for writing the first Tango documentation (The Book) single handedly!

- Pascal Verdier - for writing the Pogo and Astor documentation

- Rebecca Williams - for the huge job restructing the documentation to follow the GUTD

- Lukasz Zytniak - for converting many documents to Sphinx

Last but not least :

- Guidelines Team - the following people contributed to the device server guidelines: Alain Buteau, Jens Meyer, Jean Michel Chaize, Emmanuel Taurel, Pascal Verdier, Nicolas Leclerq, M.Lindberg, Sebastien Gara, S. Minolli, and Andy Götz.

Please add your name to the above list if you have contributed to the Tango Documentation.

A huge Thank You to all of you!

### Acknowledgements

The current Tango documentation would not be possible without the help of:

- Sphinx - a big thank you especially to Georg Brandl for inventing Sphinx (by chance Georg is also a member of the Tango Controls community)

- MyST - for the simple but powerful markdown language

- Github - for hosting the tango-doc repository

- Travis - for the continuous integration of tango-doc

- Read-the-docs - for formatting and hosting the online documentation

- Tango Collaboration - who sponsored the first Tango Write-the-docs camp and the conversion to Sphinx

This is a collection of documents for our Tango Controls community, users of Tango Controls, developers and interested parties. Among the many items that we cover here are explanations of what Tango Controls is, how to use Tango Controls for your controls system, how to write software using the Tango Controls framework and how to use Tango Controls and its tools.

Nothing is perfect and neither is this documentation. In the likely case that you find that information is missing, please get in touch with us. Ideally you would simply open an issue on GitLab so that we can address what you have found.

## How this documentation is organised

The Tango Controls documentation largely follows the Grand Unified Theory of Documentation and is organised in the following categories (with some overlap):

Explanation

Overview of what Tango Controls is, its origins and who uses it. If you are new to Tango Controls, then we recommend that you start reading here.

Explanation
Tutorials

We show you how to implement Tango Devices, Tango clients and other Tango-related software.

Tutorials
How-to

Here we provide solutions to specific problems that you might encounter on the road with Tango Controls.

How-Tos
Reference

Tango Controls’ main programming languages are C++, Java and Python. You will find their APIs here. We also support other languages and tools through bindings that we also document here.

Reference
Tools

The Tango Controls ecosystem is rich with tools that make eveybody’s life easier. Here we show you which tools exist and what one can do with them.

Tools

To support our readers in their quest to quickly find the information that they are looking for, we have tagged the pages here with one or more labels:

- Programming language: Pages that contain information that is relevant to software development are tagged with either one of the three main languages that Tango Controls supports (lang:c++, lang:java, lang:python). If the information on a page is programming language independent, we have tagged it with lang:all.

- Target audience:

  - audience:all: The information on the page might be interesting for general audiences, i.e. everybody.

  - audience:developers: Developers will likely find the information on this page interesting, i.e. it will help them with their implementation of Tango Devices, clients or Tango Controls software in general.

  - audience:administrators: A page with this tag will be useful to the people who have to build, maintain or fix a Tango Controls system.

## Where to go from here?

We understand that it is easy to get lost due to the sheer amount of information, therefore we provide some suggestions below to get you started:

- The Overview will give you a quick overview of what Tango Controls is, its origins and who uses it. If you are new to Tango Controls, then this is where we recommend you start reading.

- First steps will guide you through the process of getting started with Tango Controls. This category includes an overview of Tango Controls concepts, procedures for installation and starting the system as well as Getting started tutorials.

- Tutorials provides information for Developers that comes in handy when developing Device Servers, Devices and client applications.

- The Services section is important mainly for System Administrators. However, it may provide some information for both End Users and Developers too. It contains useful information on Tango Controls system deployment, startup and maintenance.

- You will find that Tango comes with a rich set of tools. They are command line tools, graphical toolkits and programming tools for management, developing graphical applications, connecting with other systems and applications. All, End Users, Developers and System Adminstrators, should take a look at the toolkits’ manuals.

- Tutorials and How-Tos give step by step guidance and teach you how to work with Tango Controls or get your job done efficiently.

- If you would like to contribute to the documentation then please read the documentation workflow tutorial.

## Indices and tables

- Index

- Search Page

- Glossary

- Site tags