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:

_images/image1.png

The software bus view of devices#

_images/image21.png

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.

_images/deviceservermodel.jpg

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:

_images/image3.png

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.

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.


Device polling#

audience:developers lang:c++ lang:all

Introduction#

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

The aim of polling is threefold:

  • Speed-up response time for slow devices

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

  • Be the data source for the Tango event system

Speeding-up response time is achieved because the command_inout or read_attribute CORBA operation is able to get its data from the polling buffer or from a real access to the device. For “slow” devices, getting the data from the buffer is much faster than accessing the device. Returning a first-level command output history (or attribute value history) to a client is possible due to the polling buffer which is managed as a circular buffer. The history is the contents of this circular buffer. The polling is also the data source for the event system because detecting an event means being able to regularly read the data, store it and declaring that it is an event after some comparison with older values.

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

Configuring the polling system#

Configuring what has to be polled and how#

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

  • Any command which does not need input parameter

  • Any attribute

Configuring the polling is done by sending a command to the device server administration device automatically implemented in every device server process. Seven commands are dedicated to this feature.

These commands are:

AddObjPolling

It adds a new object (command or attribute) to the list of object(s) to be polled. It is also with this command that the polling period is specified.

RemObjPolling

To remove one object (command or attribute) from the polled object(s) list

UpdObjPollingPeriod

Changes one object polling period

StartPolling

Starts polling for the whole process

StopPolling

Stops polling for the whole process

PolledDevice

Allows a client to know which device are polled

DevPollStatus

Allows a client to precisely knows the polling status for a device

All the necessary parameters for the polling configuration are stored in the Tango database. Therefore, the polling configuration is not lost after a device server process stop and restart (or after a device server process crash).

It is also possible to automatically poll a command (or an attribute) without sending command to the admin device. This demands to add a method call in the device server source code while creating the command or attribute. In this case, for every device supporting this command or this attribute, polling configuration will be automatically updated in the database and the polling will start automatically at each device server process startup. It is possible to stop this behavior on a device basis by sending a RemObjPolling command to the admin device. The following piece of code shows how the source code should be written.

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

A polling period of 400 ms is set for the command called IOStartPoll at line 9 with the set_polling_period method of the Command class. Therefore, for a device of this class, the polling thread will start polling its IOStartPoll command at process start-up except if a RemObjPolling indicating this device and the IOStartPoll command has already been received by the admin device. This is exactly the same behavior for attribute. The polling period for attribute called String_attr is defined at line 20.

Configuring the polling means defining the device attribute/command polling period. The polling period has to be choosen with care. If reading an attribute needs 200 ms, there is no point polling this attribute with a period equal to or below 200 ms. You should also take into account that some free time has to be foreseen for external requests on the device. On average, for one attribute needing X ms as reading time, define a polling period which is equal to 1.4 X (280 ms for our example of one attribute needing 200 ms as reading time). In case the polling tuning is given to external user, Tango provides a way to define polling period minimun threshold. This is done using device properties. These properties are named min_poll_period, cmd_min_poll_period and attr_min_poll_period. The property min_poll_period defines a minimum polling period in milliseconds for the device. The property cmd_min_poll_period allows the definition of a minimun polling period for a specific device command. The property attr_min_poll_period allows the definition of a minimun polling period for one device attribute. In case these properties are defined, it is not possible to poll the device command/attribute with a polling period below those defined by these properties. See polling properties to get a precise syntax description for these properties.

Jive also allows a graphical device polling configuration.

Configuring the polling threads pool#

A Tango device server process may have several polling threads managed as a pool. For instance, this could be useful in case of devices within the same device server process but accessing to different hardware channels when one of the channels is not responding (Thus generating long timeout and de-synchronising the polling thread). By default, the polling threads pool size is set to 1 and all the polled object(s) are managed by the same thread. The configuration of the polling thread pool is done using two properties associated to the admin device. These properties are named:

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

  • polling_threads_pool_conf defining which threads in the pool manages which device

The granularity of the polling threads pool tuning is the device. You cannot ask the polling threads pool to have thread number 1 in charge of attribute att1 of device dev1 and thread number 2 to be in charge of att2 of the same device dev1.

When you require a new object (command or attribute) to be polled, two main cases may arise:

  1. Some polled object(s) belonging to the device are already polled by one of the polling threads in the pool: There is no new thread created. The object is simply added to the list of objects to be polled for the existing thread

  2. There is no thread already created for the device. We have two sub-cases:

    1. The number of polling threads is less than the polling_threads_pool_size: A new thread is created and started to poll the object (command or attribute)

    2. The number of polling threads is already equal to the polling_threads_pool_size: The software search for the thread with the smallest number of polled objects and add the new polled object to this thread

Each time the polling threads pool configuration is changed, it is written in the database using the polling_threads_pool_conf property. If the behaviour previously described does not fulfill your needs, it is possible to update the polling_threads_pool_conf property in a graphical way using Astor or manually using Jive. These changes will be taken into account at the next device server process start-up. At start-up, the polling threads pool will allways be configured as required by the polling_threads_pool_conf property. The syntax used for this property is described in the polling properties reference part.

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

_images/ThreadsManagement.png

In this example, the polling threads pool size to set to 9 but only 4 polling threads are running. Thread 1 is in charge of all polled objects related to device pv/thread-pool/test-1 and pv/thread-pool/test-2. Thread 2 is in charge of all polled objects related to device pv/thread-pool/test-3. Thread 3 is in charge of all polled objects related to device pv/thread-pool/test-5 and finally, thread 4 is in charge of all polled objects for devices pv/thread-pool/test-4, pv/thread-pool/test-6 and pv/thread-pool/test-7.

It’s also possible to define the polling threads pool size programmatically in the main function of a device server process using the Util::set_polling_threads_pool_size() method before the call to the Util::server_init() method.

Choosing polling algorithm#

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

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

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

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

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

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

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

The advantages of new polling algorithm are

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

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

The drawback is

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

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

Reading data from the polling buffer#

For a polled command or a polled attribute, a client has three possibilities to get command result or attribute value (or the thrown exception) :

  • From the device itself

  • From the polling buffer

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

The choice is done during the command_inout CORBA operation by positioning one of the operation parameter. When reading data from the polling buffer, several error cases are possible:

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

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

Retrieving command/attribute result history#

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

Externally triggered polling#

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

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

line 3 : The externally polled command name

line 4 : The device object

line 8 : Trigger polling of command MyCmd

Filling polling buffer#

Assume that some hardware to be interfaced already returned an array of value, timestamp pairs. In order to be read with the command_inout_history or read_attribute_history calls, this array has to be transferred in the attribute or command polling buffer. This is possible only for attribute or command configured in the externally triggered polling mode. Once in externally triggered polling mode, the attribute (or command) polling buffer is filled with the fill_cmd_polling_buffer/fill_attr_polling_buffer methods of the Util class. For commands, the user uses a template class called TimedCmdData for each element of the command history. Each element is stored in a stack in one instance of a template class called CmdHistoryStack. This object is one of the argument of the fill_cmd_polling_buffer method. See polling properties how to learn the polling buffer depth can be defined. The same way is used for attributes with the TimedAttrData and AttrHistoryStack template classes. The following piece of code fills the polling buffer for a command called MyCmd which is already in externally triggered mode. It returns a DevVarLongArray data type with three elements.

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

Line 3-11 : Simulate data coming from hardware

Line 13-14 : Create one instance of the CmdHistoryStack class and reserve space for one history of 4 elements

Line 16-17 : A loop on each history element

Line 18 : Get date (hardware simulation)

Line 20 : Create one instance of the TimedCmdData class with data and date

Line 21 : Store this command history element in the history stack. The element order will be the insertion order whatever the element date is.

Line 28 : Fill command polling buffer

After one execution of this code, a command_inout_history call will return one history with 4 elements. The first array element of the oldest history record will have the value 10. The first array element of the newest history record will have the value 13. A command_inout call with the data source parameter set to CACHE will return the newest history record (ie an array with values 13,14 and 15). A command_inout call with the data source parameter set to DEVICE will return what the command method code returns. If you execute this code a second time, a command_inout_history call will return an history of 8 elements.

The next example fills the polling buffer for an attribute called MyAttr which is already in externally triggered mode. It is a scalar attribute of the DevString data type.

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

Line 3-4 : Create one instance of the AttrHistoryStack class and reserve space for an history with 3 elements

Line 6-7 : A loop on each history element

Line 8 : Get date (hardware simulation)

Line 10-11 : Create a string. Note that the DevString object from Tango::string_dup is created on the heap

Line 13 : Create one instance of the TimedAttrData class with data and date requesting the memory to be released.

Line 14 : Store this attribute history element in the history stack. The element order will be the insertion order whatever the element date is.

Line 21 : Fill attribute polling buffer

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

Setting and tuning the polling in a Tango class#

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

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

  • get_attribute_poll_period and get_command_poll_period to get polled object polling period

  • poll_attribute and poll_command to poll command or attribute

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

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

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

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:

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.

Level alarm

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.

_images/image2.png

HDB++ Runtime View (part one)#

_images/image4.png

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:

_images/libs.png

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.

_images/HdbConfiguratorServer.png

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#

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


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:

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

_images/archi.png

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:

_images/event_schematic_zmq.png

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!

_images/Ready.jpg

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.

mc3k

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:

main.py#
from tango.server import Device

class MegaCoffee3k(Device):
    pass


if __name__ == "__main__":
    MegaCoffee3k.run_server()
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>
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)
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>
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.

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.

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.

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()
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>
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#

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

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

Sorry, still TODO!

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.

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.

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.

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

Sorry, still TODO!

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.

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.

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

Sorry, still TODO!

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.

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

Sorry, still TODO!

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.

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

Sorry, still TODO!

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

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.

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.

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.

Jive TangoTest not exported
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:

Jive Refresh tree

You can now test the device by sending commands or reading/writing attributes.

Jive Test device Jive TangoTest status

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.

Jive Add new property

Press OK. Click on the new property value column and enter the new value to overwrite the default.

Jive Add property value

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.

Jive Init command

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 theirdevices 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 main window

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

Scalar attributes view#

Double array attribute view

Double array attribute view#

Boolean array attribute view

Boolean array attribute view#

Image 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

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 commands view#

Test attributes view

Test attributes view#

Test pipes view

Test pipes view#

Test admin 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.

Error History#

The View > Error history menu option opens the list of recent Tango errors that occured with the device.

Error history

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

Device diagnostics#

Attribute diagnostics

Attribute diagnostics#

Polled attribute diagnostics

Polled attribute diagnostics#

Command 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

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.

_images/core-widget.png

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.

_images/listpanel.png

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)

_images/prog_guide_exple1.jpg
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)

_images/prog_guide_exple2.jpg
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)

_images/prog_guide_exple3.jpg
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 IResultListeners, 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.

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 .

Device Commands#

The recommendations are the same as those proposed for an attribute, except for the first letter of the name.

Device properties#

The recommendations are the same as those proposed for a command.

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:

_images/image9.png
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:

_images/image4.jpeg

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

_images/image10.png
_images/image11.png

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

_images/image12.png

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

      _images/image13.png
  • In the source code of the device

    • init_device method: initialization of the “innerAppender”

      _images/image14.png
    • delete_device method: deletion of the “innerAppender”

      _images/image15.png

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

_images/image16.png
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++ :

_images/image17.png

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.

Device pattern class diagram

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.

Device pattern startup sequence

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

_images/command.png

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.

Read attribute sequencing

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.

Write attribute sequencing

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.

A complete device server

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

Tango device server main window

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 :

_images/cons.bmp

It simply displays all the logging** message when a console target is used in the device server.

The help window#

This window looks like :

_images/help.bmp

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.

Read pipe sequencing

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.

Write pipe sequencing

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.


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.

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.


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.

  1. 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:

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

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

        _images/databaseds-as-service-01.png
      • In the Environment tab provide variables with credentials used for accessing the MariaDB, like:

        _images/databaseds-as-service-02.png
      • 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.

      _images/starter-01.png
    • 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:

      _images/starter-as-service-01.png

    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:

      _images/starter-as-service-02.png
    • 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:

      _images/starter-02.png
  • Run TangoTest device server:

    You may test the configuration by starting prefigured TangoTest device.

    • Start Astor if it is not running.

      _images/device-server-01.png
    • Double Click on your computer name to open Control Panel. It opens a window as below:

      _images/device-server-02.png
    • Click Start new.

    • In the open window select TangoTest/test:

      _images/device-server-03.png
    • Click Start Server.

    • In the open window select Controlled by Astro -> Yes, and Startup Level -> Level 1.

      _images/device-server-04.png
    • When you click OK it should start the server. After a while you should see:

      _images/device-server-05.png
  • 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#
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

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

    _images/import-2.png

    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

_images/modbus.png

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.

_images/jupytango.png

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.

_images/jlinac.png

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.

_images/mambo.png

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.

_images/hdbpp.png

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.

_images/e-giga.png

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.

_images/webapp-2.png

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.

_images/rest-api.png

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.

_images/sardana.png

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
_images/cumbia.png

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.

_images/panic.png

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:

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

_images/astor-tangotest.png

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

_images/jive-tangotest.png

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:

_images/taurus-trend-example.png

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

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

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

Wizard - step 3 - device class#

Wizard - step 4

Wizard - step 4 - device name#

  • Next, you can configure properties for the newly created devices.

Wizard - step 5

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

Wizard - step 6 - finish#

  • After the configuration, the device server must be restarted to load and start configured devices.

Wizard - step 7

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.

Astor server list

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

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.

_images/jive_simpl.jpg

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

_images/jive_sophis.jpg

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

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:

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:

Further references#

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.

  1. 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:

access-control

In this example, the user taurel has write access to the device sr/d-ct/1 and to all devices belonging to the domain fe but only from the host pcantares. 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:

    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
    

    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:

    python dsc_import_utility.py
    

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

    _images/PogoFirstConfiguration.png
  • Once completed, you will see an empty Pogo interface:

    _images/PogoEmptyImage.png

    You can add Properties, Commands and Attributes by double-clicking on each one. Below is an example after having defined some of these:

    _images/PogoFilled.png
  • 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:

      _images/PogoGenerate.png
    • You will now see the Pogo generated files in your folder:

      _images/PogoFilesGenerated.png

      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:

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

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

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

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

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:

1  DeviceImpl::push_event(std::string attr_name,
2               std::vector<std::string> &filterable_names,
3               std::vector<double> &filterable_vals,
4               ...);
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.

 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

 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

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.

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.

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:

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.

 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.

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

 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.

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:

_images/imagePackaging.png

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:

_images/imageFilesFolder.png
Step 3: Register the device#

Open Jive and go to Edit -> Create Server

_images/imageCreateServer.png

Fill out the form as follow:

_images/imageCreateEditServeer.png

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.

_images/imageCommand.png
Step 5: explore the device#

When finish, you can explore your device using Jive.

_images/imageExploreDevice.png
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.

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.

_images/pogo-dynattr.jpg
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:

_images/jive-dev1.jpg
_images/prop_dev2.jpg

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:

_images/test-dev1.jpg
_images/test_device2.jpg

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

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

JTango latest version

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

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

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

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 main window

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

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

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

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:

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.

_images/taurus_showcase01.png

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:

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:

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

    _images/image5.png

    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.

_images/image6.png

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 Cancel and exit button.

Existing accounts are listed in the account Selection Combo Box, which you can reload by clicking on Reload account list 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 New account (at the bottom left of the panel). A new dialog will appear, as following.

_images/image111.png

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 Browse 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 Ok button to validate your new account, which will be automatically added in the list of existing accounts. If you click on Cancel 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:

_images/image151.png

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.

_images/image5.png

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:

_images/image161.png

Figure 6: Context control panel#

Creating a new context#

To create a new context, click on the new icon in toolbar (New file icon), 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 (Trash icon):

_images/image211.png

Figure 7: Application first start#

The difference between the reset icon(Trash icon) and the new icon (New file 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 Select icon), and click on the green arrow Main Bensikin window with Select context details 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 Main Bensikin window with Select context details.

Finally, fill the context Meta data (Name, Author, Reason and Description) in the corresponding fields (Note that filling the fields activates the register button Register this New Context icon).

Then, you can save your context in database by clicking on the register button Register this New Context icon.

Doing so will deactivate the register button and activate the launch snapshot button Launch snapshot icon.

You can save your context in a file using the save icon save.

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.

_images/image28.png

Figure 8: Option –context tab#

Click on the ok button. The context panel now has the “table selection mode”.

_images/image80.png

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 register

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:

_images/image81.png

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:

Contexts / Load / DB

A dialog will then appear to allow you to filter the list of contexts in database following different criteria:

_images/image32.png

Figure 11: Data base Context filter dialog#

Select no criterion to search for all contexts present in database. Click on the search 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:

Load / Context / Load menu Contexts / Load / File menu

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 (Print) and select context:

Print / Context menu

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 Print

Saving a context#

Once you have context ready, click on the save icon (save) and select context:

save / context menu

You can also go to menu Contexts and click on save, or go to menu File, select Save and click on Context.

File / Save / Context menu Contexts / Save menu

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:

File / Save as / Contexts menu Contexts / Save as menu

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:

_images/image45.png

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 Launch snapshot. 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 (Filter) 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 Reset filter parameters), 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:

File / Load / Snapshot menu Snapshots / Load / File menu

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 select 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 Set equipments.

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 red star icon 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).

_images/image54.png

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 Add to comparison. You will see the tab title of this attribute appear in the field “1st snapshot”. Select another tab and click again on Add to comparison button to put this attribute tab title in the field “2nd snapshot”. Click then on Compare button to see the comparison between these 2 snapshots.

    If user wants to see only the first line of comparison, he must check filter Filter

    Else if he/she wants to see all the details of the comparison, he/she must check Highlight

    _images/image57.png

    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 Ctrl+C or Copy to clipboard button. If you want to see this text result and may be filter it (like removing lines), click on Edit/Ctrl+C button. You will see the text appear in a dialog.

_images/image59.png

Figure 15: Snapshot edit clipboard dialog#

Modifying a snapshot comment#

Once your snapshot details are loaded, click on Edit comment 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 (print) and select snapshot:

Print / Snapshot menu

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 (Save) and select snapshot:

Save / Snapshot menu

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.

File / Save As / Snapshot menu Snapshots / Save AS menu

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.

Favorites / Add selected context menu

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.

Favorites / Contexts menu

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.

_images/image69.png

Figure 16: Save option#

Snapshot Options#

These are the Bensikin Snapshot Options:

_images/image70.png

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 Launch snapshot 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.

The Bensikin toolbar#

The toolbar is located under the menu bar, and consists mainly of a set of shortcuts to often used functionalities.

_images/image84.png

Figure 19: Bensikin toolbar#

  • New file is a shortcut to creating a new Context

  • Save is a shortcut to saving the selected Context/Snapshot into a Context/Snapshot file

  • Save all is a shortcut to doing a saving all opened Contexts and Snapshots

  • Print is a shortcut to printing the xml representation of the current Context/Snapshot

  • Trash 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.

_images/ESRF.png

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#

_images/pytango_logo.png

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#

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

LabVIEW#

audience:developers

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

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.

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:

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.

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

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.


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.

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#