Welcome to the Tango Controls documentation!#
Explanation#
Overview of Tango Controls#
What is Tango Controls?#
Tango Controls is an object-oriented, distributed control system framework which defines a communication protocol, an Application Programmers Interface (API) and provides a set of tools and libraries to build software for control systems, especially SCADA.
Tango Controls has been designed to manage small and large systems. It is built around concept of devices and device classes. This is unique feature of Tango Controls and make it different to other SCADA software which usually treats a controls system as a set of signals and read and write of process values.
Devices are created by device servers. Device servers are processes implementing set of device classes. Device classes implement a state machine, command (actions or methods), pipes and attributes (data fields) for each class. Each device therefore has state, zero or more commands, zero or more pipes and zero or more attributes. Device classes are responsible for translating hardware communication protocols into Tango Controls communication. This way you may control and monitor all your equipment like motors, valves, oscilloscopes, etc. Device classes can be used to implement any algorithm or act as a mailbox to any other software program or system.
Each system has a centralised database which stores configuration data used at the initialisation of a device server, and acts as name server by storing the dynamic network addresses. The database acts also as permanent store of dynamic settings which need to be memorised.
Each Tango Control system is identified by its Tango Host. A large system can be made up of tens of thousands or devices (the limit has not been reached yet) and there is no limit on the number of Tango Control systems running at the same time. The Tango protocol i.e. the API supports transparent access to devices from multiple systems.
Tango Controls communication protocol defines how all components of the system communicates with each other. Tango uses CORBA for synchronous communications and ZeroMQ for asynchronous communication. The detail of these protocols are hidden from the developer and user of Tango by the API and high level tools.
Tango Technologies#
TANGO is based on the 21st century technologies :
CORBA and ZMQ to communicate between device server and clients
C++, Python and Java as reference programming languages
Linux, Windows and MacOS as operating systems
Modern object oriented design patterns
Naturally implements a microservices architecture
Unit tested, continuous integration enabled
Hosted on Gitlab (tango-controls)
Extensive documentation + tools, large community
Tango Controls Community#
Since the creation of Tango, over 40 small and large facilities (see http://www.tango-controls.org/partners/) have adopted Tango for their control system. Tango is now used to control not only accelerators but also experimental lasers (ELI), wind tunnels (Onera), and most recently has been adopted by the world’s largest radio telescope observatory as its core control system (Square Kilometre Array Observatory (SKAO)).
Device#
Concept#
A device is a key concept of Tango Controls. This concept can be directly linked to the notion of microservice: 1 device = 1 microservice.
A device can represent:
A piece of equipment (eg: a power supply),
Multiple pieces of equipment (eg: a set of 4 motors driven by the same controller),
A set of software functions (eg: image processing),
A group of devices representing a subsystem
The Tango Device abstracts away the specific nature of a piece of equipment, i.e. it hides the specific implementation details from the user who does not need to care about communication protocols etc. and provides the user with a model which speaks their languages e.g. physical or engineering parameters.
In the real world, devices vary from serial line devices to devices interfaced by field-bus to memory mapped VME cards or PC cards to entire data acquisition systems. The definition of a device depends very much on the user’s requirements. In the simple case a device server can be used to hide the serial line protocol required to communicate with a device. For more complicated devices the device server can be used to hide the entire complexity of the device timing, configuration and acquisition cycle behind a set of high level commands.
Description#
A device is the basic entity of the control system. In the Tango world, everything is a device. A Device has an interface composed of commands and attributes, which provides the service of the device. It also has properties, stored in the relational database, which are generally used as configuration settings.
Each device belongs to a Device Class.
Devices are created by Device Servers, which will call the device classes. Each device is created and stored in a process called a device server. This will call the device class that the device belongs to. Devices are configured at runtime via a set of properties which are stored in the database.
All devices support a black box where client requests for attributes or operations are recorded. This feature allows easier debugging session for device already installed in a running control system.
Device hierarchy#
A Tango control system can be hierarchically organized.
At the lower level, we find elementary devices which are associated with equipments, e.g. a vacuum pump, a motor, an I/O card.
At higher levels, the devices are « logical ». These devices, based on the lower-level devices, manage and represent a subset of the control system. This is usually a synthetic view of a set of equipments with a high-level steering (functions can perform sequences of actions on several basic devices).
For example, a high-level device achieves “complex” features. This device is usually bound to evolve regardless of the hardware. Therefore, it is necessary to separate and segregate responsibilities related to the logic functionality and those related to hardware interfaces.
It is possible to access any other device from every device at every level.
The following diagram illustrates the concept of hierarchy of devices:
The software bus view of devices#
Hierarchical view of devices#
Device State#
Every Tango device has a state implemented by a finite state machine. It reflects the internal state of the system it represents.
The available states are limited to:
ON, OFF, CLOSE, OPEN, INSERT, EXTRACT,
MOVING, STANDBY, FAULT, INIT,
RUNNING, ALARM, DISABLE, UNKNOWN
A color code is associated to each state and is used in the main GUI tools to have a unified manner of representing the state of equipment.
State |
Colour |
Meaning |
|---|---|---|
ON |
green |
This state could have been called OK or OPERATIONAL. It means that the
device is in its operational state. (E.g. the power supply is giving its
nominal current, the motor is ON and ready to move, the instrument is
operating). This state is modified by the Attribute alarm checking of
the DeviceImpl:dev_state method. i.e if the state is ON and one
attribute has it’s quality factor to ALARM, then the state is modified
to ALARM
|
OFF |
white |
The device is in normal condition but is not active. e.g the
power supply main circuit breaker is open; the RF transmitter has no
power etc…
|
CLOSE |
white |
Synonym of OFF state. Can be used when OFF is not adequate for the
device e.g case of a valve, a door, a relay, a switch.
|
OPEN |
green |
Synonym of ON state. Can be used when ON is not adequate for the device
e.g case of a valve, a door, a relay, a switch.
|
INSERT |
white |
Synonym of OFF state. Can be used when OFF is not adequate for the
device. Case of insertable/extractable equipment, absorbers, etc…
This state is here for compatibility reason we recommend to use OFF or
CLOSE when possible.
|
EXTRACT |
green |
Synonym of ON state. Can be used when ON is not adequate for the device
Case of insertable/extractable equipment, absorbers, etc…
This state is here for compatibility reason we recommend to use ON or
OPEN when possible.
|
MOVING |
light blue |
The device is in a transitory state. It is the case of a device moving
from one state to another.( E.g a motor moving from one position to
another, a big instrument is executing a sequence of operation, a
macro command is being executed.)
|
STANDBY |
yellow |
The device is not fully active but is ready to operate. This state does
not exist in many devices but may be useful when the device has an
intermediate state between OFF and ON. E.g the main circuit breaker is
closed but there is no output current. Usually Standby is used when it
can be immediately switched ON. While OFF is used when a certain time
is necessary before switching ON.
|
FAULT |
red |
The device has a major failure that prevents it to work. For instance,
A power supply has stopped due to over temperature A motor cannot move
because it has fault conditions. Usually we cannot get out from this
state without an intervention on the hardware or a reset command.
|
INIT |
beige |
This state is reserved to the starting phase of the device server.
It means that the software is not fully operational and that the user
must wait
|
RUNNING |
dark green |
This state does not exist in many devices but may be useful when the
device has a specific state above the ON state. (E.g. the detector
system is acquiring data, An automatic job is being executed).
Note that this state is different from the MOVING state. It is not a
transitory situation and may be a normal operating state above the ON
state.
|
ALARM |
orange |
The device is operating but one of this attribute is out of range.
It can be linked to alarm conditions set by attribute properties or a
specific case. (E.g. temperature alarm on a stepper motor, end switch
pressed on a stepper motor, up water level in a tank, etc…) In alarm,
usually the device does it’s job but the operator has to perform an
action to avoid a bigger problem that may switch the state to FAULT.
|
DISABLE |
magenta |
The device cannot be switched ON for an external reason. e.g. the
power supply has it’s door open, the safety conditions are not
satisfactory to allow the device to operate
|
UNKNOWN |
grey |
The device cannot retrieve its state. It is the case when there is a
communication problem to the hardware (network cut, broken cable etc…).
It could also represent an incoherent situation
|
Device Class#
A device class is an abstraction of a device’s interface. The device class contains a complete description and implementation of the behavior of all members of that class. It defines the list of attributes, pipes and commands that are available for a certain device, which are then available to users and to other components of a Tango system.
A device class often relates to the specific hardware that it interfaces with, for example the SerialLine class defines an interface to communicate with serial line hardware.
All classes are derived from one root class thus allowing some common behavior for all devices. New device classes can also be constructed out of existing device classes. In this way a new hierarchy of classes can be built up in a short time. Device classes can use existing devices as sub-classes or as sub-objects. The practice of reusing existing classes is classical for Object Oriented Programming (OOP) and is one of its main advantages.
The DeviceClass#
Description#
Every device of the same class supports the same list of commands and hence this list of available commands is stored in the DeviceClass. For example, the structure returned by the info operation contains a URL to the documentation. This URL is the same for every device belonging to the same class and hence the documentation URL is a data member of this class. There should only be one instance of this class per device. The DeviceClass also stores the device list.
The DeviceClass is an abstract class because the two methods device_factory() and command_factory() are declared as pure virtual. The role of the device_factory() method is to create all the devices belonging to the device class. The role of the command_factory() method is to create one instance of all the classes needed to support device commands.
The DeviceClass also contains the attribute_factory method whose role is to store the name of all the device attributes. The default implementation of this method is an empty body representing a device without any attributes.
Contents#
The contents of this class can be summarize as:
The
command_handlermethodMethods to access data members
Signal related method (C++ specific)
Class constructor. It is protected to implements the Singleton pattern
Class data members like the class command list, the device list, etc
Device Server#
A Device Server is the process (i.e. the executable) that will create, run and serve instances of Devices. It must contain one or more Device Class, and can instantiate any number of Devices from those classes. Devices started from the same Device Server will run in the same process and hence share system resources such as memory, therefore it can be convenient to group devices into the same Device Server to optimise performance.
Each Device Server has a unique name made up of the name of the executable and a character string called the instance name. The pair of executable / instance name has to be unique in a Tango control system. The Device Server is responsible for querying the database to find out the list of Devices and their Device Classes to create. The Device Server must create the Devices, call their initialise routine and export them once the Device is created.
Device Servers create an internal device of their own called the Admin Device. This device is used to monitor and control the Device Server process lifecycle. It can perform tasks like restarting Devices, restarting the Device Server process, and starting or stopping polling.
Device Servers are linked to the Device classes that they will serve. Device Servers are usually managed by the Astor tool.
Runtime representation of a Device server with two classes A and B#
Summary#
Sometimes there are misuses of language regarding the concepts of a device, device server and a Tango class. Below is a summary:
DeviceClass class: a class defining the interface and state machine (only used in C++ device classes).
Device class: a class implementing the device control.
Device: An instance of a Device class giving access to the services of the DeviceClass class.
Device Server: process in which one or more Tango classes are executed (device server).
The diagrams below illustrate these concepts:
Tango Deployment#
A Device Server can host several Device classes, each class can be instantiated one or more times within the same device server. There are no specific rules regarding the maximum number of classes or the maximum number of instances operating within a single Device Server.
In particular cases, due to limitations imposed by the hardware or software interface, it is not always possible to run several instances of a Device class within the same Device Server:
Case of a DLL’s use: some DLLs can’t be used by two threads of the same process.
In other cases, it is useful to have multiple devices running in the same Device Server:
Case of motors: a single axis controller for 4 motors.
Command#
A command is associated with an action. On, Off, Start, Stop are commons examples. Commands are well-suited for sending orders to a device, such as switching from one mode of operation to another. For example, switching a power supply on or off is typically done via a command.
Description#
Each device class implements a list of commands. Commands are essential because they serve as the primary controls for managing a device. Commands have a fixed calling syntax, consisting of one input argument and one output argument. Argument types must be chosen from a set of predefined data types which include:
void, boolean, short, long, long64, float, double, string, unsigned short, unsigned long, unsigned long64
1D array of the followings types : char, short, long, long64, float, double, unsigned short, unsigned long, unsigned long64, string
State: enumeration, representing the different states described in the section on Device State .
2 particular types: longstringarray and doublestringarray. These are structures including one array of long/double and one array of string.
The list of data types is fixed. If you need to add your own data type then use the DevEncoded type and encode your own data type. Or you can use the DevPipe communication channel (available since Tango 9).
Commands can execute any sequence of actions. They can be executed synchronously (the requester is blocked until the command ends) or asynchronously (the requester sends the request and is called back when the command ends).
The Default Commands#
There are three default commands that every device must respond to in order to enhance standard behavior in a Tango control system. These commands are State, Status, and Init.
State Command#
The default behavior of the State command is to return the current state of a device, with one exception: if the result of the command is ON and in an ALARM state, Tango will:
Read all attribute(s) with an alarm level defined,
Change the device state to ALARM if any attribute read value is above/below the alarm level,
Return the device state.
Note
This behavior can be redefined by the implementer of the device. It is recommended to check the device documentation for exact behavior.
Note
There is no difference in behavior between the State command and the State attribute. In both cases, the resulting state is the same.
Status Command#
The Status command returns a text message that provides detailed information about the state of the device. The default behavior of the Status command follows the same logic as the default behavior of the State command. When the device state is ALARM, the command will return the device status along with a list of all attributes that are in an alarm condition.
Note
This behavior can be redefined by the implementer of the device. It is recommended to check the device documentation for exact behavior.
Note
There is no difference in behavior between the Status command and the Status attribute. In both cases, the resulting status is the same.
Init Command#
The Init command is used to reinitialize a device without losing its network connection. The behavior of this command should reload any resources owned by the device, similar to how they are loaded after the device’s initial startup.
Attribute#
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
positionattribute expressed in mm.A device associated with a thermocouple has a
temperatureattribute 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 imagedata_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 imagedata_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::OPERATORorTango::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-03display_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_valueandmax_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#
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 :
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 :
When a spectrum or image attribute size changes.
At event subscription time
When the polling thread receives an exception during attribute reading
When the polling thread detects that the attribute quality factor has changed.
The first good reading of the attribute after the polling thread has received exception when trying to read the attribute
The first time the polling thread detects that the attribute quality factor has changed from INVALID to something else
When a change event is pushed manually from the device server code. (DeviceImpl::push_change_event()).
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.
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.
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()).
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:
The attribute quality factor transitions to or from:
Tango::ATTR_WARNING
Tango::ATTR_ALARM.
The event contains an exception and the previous event did not contain an exception.
The event contains an exception which is different from the exception in the previous event.
The event does not contain an exception and the previous event did contain an exception.
Alarm events are triggered in the following circumstances:
At event subscription time as part of the subscription command
When the polling thread detects an alarming event as defined above
When an alarm event is pushed manually from the device server code. (DeviceImpl::push_alarm_event()).
When a change event is pushed manually from the device server code (DeviceImpl::push_change_event()) and the following is true:
the AutoAlarmOnChangeEvent CtrlSystem property is defined and not the case-insensitive string “false”.
the attribute is not configured to push manual alarm events (DeviceImpl::set_alarm_event() with the implemented flag set to false).
the event is considered to be alarming as defined above
By the methods Attribute::set_quality() and Attribute::set_value_date_quality() when the following is true:
a change event would be sent from these methods
the AutoAlarmOnChangeEvent CtrlSystem property is defined and not the case-insensitive string “false”.
the attribute is not configured to push manual alarm events (DeviceImpl::set_alarm_event() with the implemented flag set to false).
the quality factor change is considered to be alarming as defined above
attribute configuration - an event is sent if the attribute configuration is changed.
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.
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()).
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 :
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
At the end of admin device RestartServer or DevRestart command
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.
At event re-connection time. This case is similar to the previous one (device interface change not guaranteed)
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 :
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.
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 :
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 :
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.
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.
archive_period - the minimum time between archive events (in milliseconds). If no property is specified, no periodic archiving events are send.
Device polling#
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_sizedefining the maximun number of threads that you can have in the poolpolling_threads_pool_confdefining 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:
Some polled object(s) belonging to the device are already polled by one of the polling threads in the pool: There is no new thread created. The object is simply added to the list of objects to be polled for the existing thread
There is no thread already created for the device. We have two sub-cases:
The number of polling threads is less than the
polling_threads_pool_size: A new thread is created and started to poll the object (command or attribute)The number of polling threads is already equal to the
polling_threads_pool_size: The software search for the thread with the smallest number of polled objects and add the new polled object to this thread
Each time the polling threads pool configuration is changed, it is
written in the database using the polling_threads_pool_conf property.
If the behaviour previously described does not fulfill your needs, it is
possible to update the polling_threads_pool_conf property in a
graphical way using Astor
or manually using Jive. These
changes will be taken into account at the next device server process
start-up. At start-up, the polling threads pool will allways be
configured as required by the polling_threads_pool_conf property. The
syntax used for this property is described in the polling properties reference part.
The following screenshot is the Astor tool window which allows polling threads pool management.
In this example, the polling threads pool size to set to 9 but only 4
polling threads are running. Thread 1 is in charge of all polled objects
related to device pv/thread-pool/test-1 and pv/thread-pool/test-2.
Thread 2 is in charge of all polled objects related to device
pv/thread-pool/test-3. Thread 3 is in charge of all polled objects
related to device pv/thread-pool/test-5 and finally, thread 4 is in
charge of all polled objects for devices pv/thread-pool/test-4,
pv/thread-pool/test-6 and pv/thread-pool/test-7.
It’s also possible to define the polling threads pool size
programmatically in the main function of a device server process using
the Util::set_polling_threads_pool_size() method before the call
to the Util::server_init() method.
Choosing polling algorithm#
Starting with Tango 9, you can choose between two different polling algorithm:
The polling as it was in Tango since it has been introduced. This means:
For one device, always poll attribute one at a time even if the polling period is the same (use of read_attribute instead of read_attributes)
Do not allow the polling thread to be late: If it is the case (because at the end of polling object 1, the current time is greater than the polling date planned for object 2), discard polling object and inform event user by sending one event with error (Polling thread is late and discard….)
New polling algorithm introduced in Tango 9 as the default one. This means:
For one device, poll all attributes with the same polling period using a single device call (read_attributes)
Allow the polling thread to be late but only if number of late objects decreases.
The advantages of new polling algorithm are
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)
Less
Polling thread lateerrors in the event system in case of device with non constant response time
The drawback is
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_factordevice 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_polledandis_command_polledto check if one command/attribute is polledget_attribute_poll_periodandget_command_poll_periodto get polled object polling periodpoll_attributeandpoll_commandto poll command or attributestop_poll_attributeandstop_poll_commandto 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)#
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
srfor storage ring. The family part isd-ctfor diagnostic/current transformer and the member part is1.fe/v-pen/id11-1 : A Penning gauge. The domain part is
fefor front-end. The family part isv-penfor vacuum/penning and the member name isid11-1to 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
gizmoand using the port number20000. TheTANGO_HOSTenvironment 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=nois 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/1sr/d-ct/1/Lifetime : Attribute lifetime for Tango device
sr/d-ct/1
Attribute property name#
id11/rv/1/temp->label : Property
labelfor attribute temp for deviceid11/rv/1sr/d-ct/1/Lifetime->unit : The
unitproperty for the Lifetime attribute of thesr/d-ct/1device
Device property name#
sr/d-ct/1->address :
addressproperty for devicesr/d-ct/1
Class property name#
Starter->doc_url :
doc_urlproperty for a class calledStarter
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#
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 .
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#
Tango provides an automatic way of defining alarms. An alarm condition will switch the attribute quality factor to alarm and the device state will automatically switched to ALARM in certain conditions. Four properties are available for alarm purpose.
For example the voltage of a powersupply set via a DAC and read via an ADC convertor. Both values can be different due to various factors such as internal resistor or noise on the ADC. Additionally the powersupply may need a certain time to establish its output voltage when changing the set point. The Tango alarm system is able to handle the acceptable noise threshold and the time the device needs to establish the voltage after the writing of the setpoint (time constant).
The level alarms#
This alarm is defined for all Tango attribute read type and for numerical data type. The action of this alarm depend on the attribute value when it is read :
If the attribute value is below or equal the attribute configuration min_alarm parameter, the attribute quality factor is switched to Tango::ATTR_ALARM and if the device state is Tango::ON, it is switched to Tango::ALARM.
If the attribute value is below or equal the attribute configuration min_warning parameter, the attribute quality factor is switched to Tango::ATTR_WARNING and if the device state is Tango::ON, it is switched to Tango::ALARM.
If the attribute value is above or equal the attribute configuration max_warning parameter, the attribute quality factor is switched to Tango::ATTR_WARNING and if the device state is Tango::ON, it is switched to Tango::ALARM.
If the attribute value is above or equal the attribute configuration max_alarm parameter, the attribute quality factor is switched to Tango::ATTR_ALARM and if the device state is Tango::ON, it is switched to Tango::ALARM.
If the attribute is a spectrum or an image, then the alarm is set if any one of the attribute value satisfies the above criterium. By default, these four parameters are not defined and no check will be done.
The following figure is a drawing of attribute quality factor and device state values function of the the attribute value.
Figure 7.1: Level alarm#
If the min_warning and max_warning parameters are not set, the attribute quality factor will simply change between Tango::ATTR_ALARM and Tango::ATTR_VALID function of the attribute value.
Warning
The behaviour described above is only correct in the case the device’s method Tango::Device_[X]Impl::dev_state() is executed*.* In case of overwrite of the dev_state() in the device code, it is recommended to finish the method by calling DeviceImpl::dev_state();
Warning
min_warning and max_warning : lower and upper bound for WARNING (deprecated since version 8)
The Read Different than Set (RDS) alarm#
This alarm is defined only for attribute of the Tango::READ_WRITE and Tango::READ_WITH_WRITE read/write type and for numerical data type. This is very useful to handle maximum noise in constant time.
This alarm configuration is done with two attribute configuration parameters:
delta_val and delta_t Valid for a writeable attribute. Define a maximum difference between the set_value and the read_value of an attribute after a standard time. By default, these two parameters are not defined and no check will be done.
When the attribute is read (or when the device state is requested), if the difference between its read value and the last written value is something more than or equal to an authorized delta and if at least a certain amount of milli seconds occurs since the last write operation, the attribute quality factor will be set to Tango::ATTR_ALARM and if the device state is Tango::ON, it is switched to Tango::ALARM.
If the attribute is a spectrum or an image, then the alarm is set if any one of the attribute value’s satisfies the above criterium.
Archiving#
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++#
An overview#
The figures below have been taken from the primary presentation of HDB++. They show the runtime view of HDB++ within a Tango Controls system.
HDB++ Runtime View (part one)#
HDB++ Runtime View (part two)#
Elements#
Block |
Description |
|---|---|
HDB++ Viewer |
Standalone JAVA application designed to monitor signals coming from HDB++. It has been written using Swing and needs a JVM higher than 1.7.0. *More information*. |
HDB++ Configuration |
Standalone JAVA application that allows interaction with the configuration manager in order to add, modify, move or delete an attribute from the archiving system |
Archiving DB |
Specific Database devoted to storing attribute values. The currently supported backend are Mysql, PostgreSQL, ElasticSearch or Timescale. In the past we supported Cassandra too, but since we have deprecated its use and it is now unsupported. |
Tango Configuration Database (:term: |
The Tango Device Server (:term: |
Archiver |
The EventSubscriber Tango device server, or Archiver, is the archiving system’s engine. On typical usage, it will subscribe to archive events on request by the ConfigurationManager device. The EventSubscriber is designed to start archiving all the already configured Attributes, even if the ConfigurationManager is not running. Moreover, being a TANGO device, the EventSubscriber configuration can be managed with Jive.The list of Attributes to be gathered by each EventSubscriber is stored in the AttributeList Property of the EventSubscriber device. |
Device server |
Generic Device server that contains one or more devices that needs to archive one or more attributes |
Configurator Server |
Device server that assists in adding, modifying, moving, deleting multiple attributes in the archiving system using the Configuration Manager. |
Configuration Manager |
Device server that assists in adding, modifying, moving, deleting an Attribute to/from the archiving system |
HDB++ inherits the database structure from the existing Tango Historical Data Base and introduces new storage architecture possibilities, better internal diagnostic capabilities and an optimized API. Different backends to store the data can be implemented through an unified interface, currently Timescaledb, MySQL, Postgresql, ElasticSearch and Apache Cassandra (support for Cassandra has been dropped) are supported.
The HDB++ archiving system must fully comply to the Tango device server model, with two immediate benefits. First, all the required configuration parameters are stored to and retrieved from the Tango database; some of these parameters are, for user convenience, duplicated into a dedicated table of the HDB++ schema by a mechanism that guarantees the consistency of the copy. Second, the HDB++ archiving system inherits the Tango scaling capability: any number of EventSubscriber instances can be deployed according to the desired architecture and overall performance.
The HDB++ architecture is fully event based; therefore, a part of HDB++ setup consists of conveniently configure Tango device servers to send events as required.
Backend support#
A C++ interface libhdb++ offers an interface for any backend to implement for data storage. These libraries, written in C++, are addressed to the EventSubscriber Tango device server and their main purpose is to provide an abstraction layer. Actually, some shared objects are available implementing the abstraction layer and the specific interface:
HDB++ Device Servers design#
libhdb++: database abstraction layer, decouples the interface to the database back-end from the implementation.
libhdbmysql: legacy HDB schema support for MySQL back-end
libhdb++mysql: HDB++ schema support for MySQL back-end
libhdb++cassandra: Cassandra back-end implementation of libhdb++
libhdb++timescale: Timescaledb back-end implementation of libhdb++
libhdb++postgres: Postgresql back-end implementation of libhdb++
hdb++ELK: ElasticSearch back-end implementation of libhdb++
These libraries allow reusing the EventSubscriber, the ConfigurationManager and the GUIs without changes.
Configuration Manager Device Server#
It configures the attributes to be archived and defines which Event Subscriber is responsible for a set of Tango attributes to be archived. It provides diagnostics data as well. Configuration Manager Tango device server will assist in the operations of adding, editing, moving and deleting an Attribute to/from the HDB++ archiving system. A specific library, exposing a suitable API, addresses the historical data extraction from the archive. The ConfigurationManager device server is able to perform the following operations on the managed EventSubscriber pool:
handle the request of archiving a new Attribute;
setup the Attribute archive event configuration;
assign the new Attribute to one of the archivers;
move an Attribute from one archiver to another;
show the Attribute/archiver coupling;
start/stop the archiving of an Attribute;
remove an attribute from archiving.
The Configuration Manager also exposes some Attributes to keep trace of the global statistics:
total number of EventSubscribers;
total number of working/faulty attributes;
total number of events per second;
overall minimum and maximum processing and storing time.
These attributes could be themselves archived to enable a follow up versus time.
Event Subscriber Device Server (Archiver)#
Also referenced as archiver, is in charge of gathering the values from the Tango devices and storing them into the historical database. To address the requirements coming from large systems the need to distribute the workload over a number of archivers shows up. The EventSubscriber Tango device server is the core of the HDB++ archiving system. It subscribes to archive events for the specified Attributes list, stored into a Property in the Tango database, as well as a number of additional parameters, such as the hostname and port number where the back-end is running, the name of the database and the username and password to be used. It subscribes to Tango archive events, which are ZeroMQ events in the latest Tango releases, and stores the received events in the historical database. It provides diagnostics data as well.
The EventSubscriber device server allows to perform the following operations:
add/remove an Attribute to/from archiving;
start/stop the archiving for all Attributes;
start/stop the archiving for one Attribute;
read the status of an Attribute;
read the list of Attributes currently archived (started);
read the list of Attributes currently not archived (stopped);
read the number/list of Attributes in charge;
read the configuration parameters of each Attribute;
read the number/list of working Attributes;
read the number/list of faulty Attributes;
read the number/list of Attributes pending in the FIFO.
Working at the EventSubscriber level implies that the database entry and the archive event parameters have to be already configured. Besides, no action is performed on the archived data when removing an Attribute, which means that the data remain available in the historical database. The EventSubscriber Tango device server also exposes some additional figures of merit, such as:
for each instance, total number of records per time;
for each instance, total number of failures per time;
for each Attribute, number of records per time;
for each Attribute, number of failures per time;
for each Attribute, time stamp of last record.
These numbers can sum up in a counter, which can be reset every hours/days/weeks, to rank each Attribute in term of data rate, error rate etc. This allows preventive maintenance and fine tuning, detecting, for instance, when an Attribute configuration is wrong because the variation threshold is lower than the noise level. These statistics are a key element for qualifying the health of the system. All these Attributes are archived themselves to enable a follow-up versus time. For each Attribute, the EventSubscriber Tango device server also computes the minimum and maximum processing and storing times, which helps to discover possible bottlenecks. There may be several EventSubscriber device servers.
HdbConfigurator Server#
The HdbConfigurator device server is a Java device server. It was developed just to simplify the task of configurating dynamically several new attributes to be added in HDB++.
At the ESRF, it is used to configure/add automatically some dynamic attributes into HDB++. There are some cases where the same device will create some dynamic attributes at startup depending on its configuration (device properties), if the configuration changes, different attributes might be created. In this specific case, we have the requirement to ensure these dynamic attributes are archived. So the device server will configure them via the HdbConfigurator device server.
HDB++ and HdbConfiguratorServer#
To be more concrete, for the people working in a synchrotron, we have a device, controlling the insertion devices used by a given beamline. Dynamic attributes are created for each undulator which can be controlled by this beamline. During a shutdown period, the undulator might have been replaced, so there will be new attributes created for the new undulators and the attributes related to the undulators which have been removed will no longer exist.
The HdbConfigurator server will handle a queue and coordinate the requests sent to the HdbConfiguration Manager device. It will help to configure several attributes in parallel. If one would like to do it with only the HdbConfiguration Manager device, one would need to lock the device, set many attributes, send the addAttribute command and finally release the lock of the device. The HdbConfigurator Server is doing that for you in the same way as the HDB++ Configuration Manager GUI is doing it.
HdbViewer#
It visualizes the data stored in the historical database.
Two libraries have been developed to the historical data extraction:
A java implementation, libhdbpp-extraction-java, has been used for the HdbViewer GUI and is a native choice for Java device servers. The HdbViewer Java framework, supports both legacy HDB archiving system, and the new HDB++ design.
A C++ implementation, dedicated Qt/Qtango based GUIs or to C++ Tango device servers. The HdbExtractor++ multithread library allows fetching the data from the legacy HDB and the new HDB++ MySQL schema in a simple Object Oriented way. An additional module provides a Qt interface to the HdbExtractor++ and a dedicated GUI, exploiting the MathGL framework, aimed at displaying mono and bidimensional data over time.
Note
The C++ extraction library currently supports only the MySQL back-end.
Source code#
The source code is available on Gitlab in the following repositories:
hdbpp-timescale-project: A project to centralize and build all the needed components of a full HDB++ setup with the Timescaledb backend.
hdbpp-mysql-project: A project to centralize and build all the needed components of a full HDB++ setup with the Mysql backend.
hdbpp-cm : the HDB++ Configuration Manager device server
hdbpp-es: the HDB++ Event Subscriber device server
hdbpp-cm-es: Device server able to export HDB++ Event Subscriber and Configuration Manager devices in the same device server. It is a multiclass Tango device server capable of both CongigurationManager and EventSubscriber devices. Can be used in place of hdbpp-cm and hdbpp-es devices.
libhdbpp: the HDB++ insertion (abstract) library
libhdbpp-mysql: the HDB++ insertion library for MySQL backend
libhdbpp-mysql-legacy: the HDB++ insertion library for MySQL backend using the old Tango HDB database schema
libhdbpp-cassandra: the HDB++ insertion library for Cassandra backend
libhdbpp-timescale: the HDB++ insertion library for Timescaledb backend
libhdbpp-postgresql: the HDB++ insertion library for Postgrsql backend
libhdbpp-elk: the HDB++ insertion library for ElasticSearch backend
hdbpp-benchmark: A project to compare performances of different HDB++ backends using docker images
CassandraMonitor: A Java client/server to monitor cassandra nodes using jmx calls.
hdbpp-configurator: the HDB++ Configuration GUI (in Java).
hdbpp-configurator-server: HDB++ Configuration device server helper (in Java).
hdbpp-viewer: the HDB++ Viewer GUI (in Java)
libhdbpp-extraction-java: HDB++ Java extraction library
libhdbpp-extraction-cpp: HDB++ C++ extraction library
libhdbpp-python: HDB++ python extraction library
eGiga2m: Web graphic data viewer able to show HDB++ data
Note
Please, find README file in each repository. It contains a lot of necessary information.
Documentation for building and installing#
The documentation for building and installing many of the components is available on the different git repositories from tango-controls/hdbpp Gitlab organization.
Please, have a look at the README files from the different git repositories (For instance: tango-controls/hdbpp/hdbpp-timescale-project). They explain how to install the HDB++ libraries as well as the device servers.
Note
-v5 can be used to see the DEBUG messages coming from the Tango library itself too. It also sets the logging level of the device to DEBUG. -v4 can be used to see the DEBUG messages coming from the Tango devices themselves. By doing this, DEBUG level logs will be printed on your terminal. For example, command on terminal: ./hdb++es-srv 01 -v5
Note
In recent versions of libhdb++cassandra library, there are some configuration parameters to adjust libhdb++cassandra library log level and the cassandra driver log level. These configuration parameters are parameters of LibConfiguration Tango class or device property. These parameters are named differently depending on the version of the library used (logging_enabled or logging_level and cassandra_driver_log_level)
Rationale#
The HBD++ archiving system is built on top of the Tango Event model which provides a specific event for archiving, this is the archive event. The archive events are configured with three attributes properties:
- archive_abs_change:
a Property of up to 2 values, positive and negative delta, that specifies the absolute change with respect to the previous Attribute value, which triggered the event. If only one value is specified it is used for both positive and negative change. If no thresholds are specified then the relative change is used.
- archive_rel_change:
a Property of up to 2 values, positive and negative delta, that specifies the relative change with respect to the previous Attribute value, which triggered the event. If only one value is specified it is used for both positive and negative change. If no thresholds are specified archive events are not sent on value change.
- archive_period:
the time between which periodic archive events are sent, in milliseconds. If no period is specified no periodic archive events are sent.
Usually it is composed of several TANGO device servers (Archiver aka EventSubscriber), but there must be at least one device server. Each EventSubscriber device is in charge of archiving a number of attributes from a number of devices. The number of EventSubscriber TANGO devices to deploy and the number of TANGO devices/Attributes in charge of each subscriber is not bounded and depends on the desired performance.
The ConfigurationManager device server manages a pool of EventSubscribers; the list is stored in the ArchiverList property of each ConfigurationManager device, and is updated via the ArchiverAdd, ArchiverRemove and AttributeSetArchiver commands. The list is stored in the ArchiverList device Property of the ConfigurationManager device using the FQDN syntax. This tells the ConfigurationManager everything which is needed to connect to the managed EventSubscribers: protocol, host, port and device name.
Presentations and papers#
SNAP#
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).
Same time is not really possible because a distributed control system will always introduce delays somewhere. What we refer to here is a best effort: The computer that takes the snapshot will read the values of all attribute as fast as possible. It is possible that a device does not reply immediately when one of its attributes is read. This will result in a delay that one will notice when looking at the timestamps of the attributes in the snapshot.
Pipe#
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#
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:
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 (
dev1anddev2). Two clients are connected to these devices (client1andclient2).Client2will not be able to accessdev1ifclient1is using it. Nevertheless,client2is able to accessdev2whileclient1accessdev1as there is one mutual exclusion object by device.Serialization by class: With non multi-threaded legacy software, the preceding scenario could generate problems. In this mode of serialization,
client2is not able to accessdev2whileclient1accessdev1becausedev2anddev1are instances of the same class as there is one mutual exclusion object by class.Serialization by process: This is one step further than the previous case. In this mode, only one client can access any device embedded within the device server at a time. There is only one mutual exclusion object for the whole process.
No serialization: This is an exotic kind of serialization and should be used with extreme care 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_attributesandwrite_attributesCORBA 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:
Serialization by kernel: This is the default case. The kernel is managing the serialization
Serialization by user: The user code is in charge of the serialization. This serialization is done by the use of an
omni_mutexobject. Anomni_mutexis 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 methodNo 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:
The main thread
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 (
KeepAliveThreadandEventConsumer)
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:
The synchronous mechanism where the client waits (and is blocked) for the server to send the answer or until the timeout is reached
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:
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#
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
commandsRead/Set data specific to each device belonging to a class via Tango
attributesRead/Set data specific to each device belonging to a class via Tango
pipesRead 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. Thisadm_nameis 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:
Using the appropriate CORBA attribute (
stateandstatus)Using a command on the device. The commands are called
StateandStatusUsing attributes: Even if the state and status are not real attributes, it is possible to get their value using the
read_attributesoperation. 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:
DeviceProxywhich is a proxy to the real deviceAttributeProxywhich is a proxy to an Attribute of a real deviceDeviceDatato encapsulate data to send/receive from/to device via commandsDeviceAttributeto encapsulate data to send/receive from/to device via attributesGroupwhich is a proxy to a group of devices
Note
In Tango the term client usually refers to either an AttributeProxy or to a DeviceProxy.
In addition to these main classes, many other classes allow interfacing to all Tango features. The following figure is a drawing of a typical client/server application using Tango.
The database is used during the server and client startup phase to establish a connection between client and server.
Communication between client and server using the API#
With the API, it is possible to request commands to be executed on a device or to read/write device attribute(s) using one of the two communication models implemented. These two models are:
The synchronous model where the client waits (and is blocked) for the server to send the answer or until a timeout is reached.
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:
In contrary to the state_handler method of the TACO device server
model which is not specific to each command.
Tango attributes were known as signals in the TACO device server model
Properties were known as resources in the TACO device server model
Long Term Support#
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#
The concept of using Devices embedded in Device servers to implement access to devices in a control system was first proposed at the ESRF in 1989. The concept was implemented in the TACO control system which was presented at the ICALEPCS conference in 1991 (Object Oriented Programming Techniques Applied to Device Access and Control). It has been successfully used as the heart of the ESRF Control System of the institute accelerator complex. This control system, called TACO, was based on the SUN RPC (also used by the NFS protocol) and C as its core programming language.
In 1999, a renewal of the ESRF distributed control system was started with the aim of replacing SUN/RPC with CORBA, using C++ as the core programming languages. The new software was called TANGO and was presented for the first time at ICALEPCS in 1999 (TANGO - AN OBJECT ORIENTED CONTROL SYSTEM BASED ON CORBA)
In June 2002, Soleil and ESRF officially decided to collaborate to develop TANGO collaboratively. Soleil is the French synchrotron radiation facility based close to Paris. In December 2003, Elettra joined the club. Elettra is an Italian synchrotron radiation facility located in Trieste. Beginning of 2005 ALBA also decided to join. ALBA is a Spanish synchrotron radiation facility located in Barcelona. DESY and MaxIV were the next big synchrotrons in Europe to join the collaboration. After that things speeded up and more and more sites doing diverse things developed collaboratively. A number of lasers, telescopes, fusion facilities have decided to use TANGO as their control system. The most recent and largest facility to join is SKA, the Square Kilometer Array, which is constructing the two largest radio telescopes in the world.
In 2015 approximately TANGO was officially renamed to be Tango Controls and is referred to as Tango for short.
Tango Controls is a toolkit for building distributed object based control systems. Distributed objects are an implementation of the Actor model. Actors are primitives of concurrent computation which were proposed in the 70s but have gained renewed interest with massively parallel architectures, IoT, cloud computing etc.
The distributed object in Tango Controls is called a device and is created as an object in a container process called a device server. The device server implements the network communication and links to the configuration data base and clients. Tango device servers and clients can be written in Python, C++ or Java. Tango comes with a full set of tools for developing, supervising, monitoring and archiving.
The Tango Controls toolkit has been used to build the control systems of large and small physics experiments like synchrotrons, lasers, wind tunnels and radio telescopes. Tango can be used for a single device which requires remote control in a lab or on the internet. Tango can be used as a communication protocol for controlling anything remotely. Tango is ideal for connecting things together and its uses are only limited by your imagination!
We are glad you are with us. Please have a look of all the in-depth explanation of Tango in the next pages.
Tutorials#
In this section you will find a set of tutorials to get you started with using Tango.
The Getting started section is a good place to start with instructions on how to get a simple Tango environment up and running followed by a guide to creating a simple Tango device server and client to demonstrate how Tango works.
There is an Example deployment of a Tango Control System highlighting the important components that need to be set up and the tasks that they perform. This gives a good overview of how the whole system fits together.
A tutorial on how to use AtkPanels to control a Tango device is given in the ATKPanel subsection. This is a good example of how to interact with device servers using a client.
The remaining tutorials contain guidance and structure for developers developing Tango clients and Tango Device servers.
Getting started#
Coffee Crisis!#
To make sure this tutorial isn’t super boring, imagine you are a new employee at Tango MegaCorp Inc. working in the DevOps team.
On your first day, your manager calls you into her office, with a worried expression on her face. She explains that the whole organisation’s productivity has dropped significantly in the last month. Luckily, they’ve figured out that the problem is a lack of coffee for the programmers. It’s a crisis! It just so happens that a month ago they replaced all the old coffee machines with shiny new MegaCorp DC3000 series drip coffee machines. It’s not going well - the machines are often empty or broken down, instead of having a pot of steaming brew ready at all times.
She says a control system has to be built to monitor these smart, but temperamental, coffee machines and keep the java flowing ☕☕☕. Of course, it has to be built using Tango Controls, and it has to be done right away!
With a mixture of nervousness and excitement you offer to tackle this urgent project and rush back to your office.
First steps#
First Tango device server#
You start with the simplest Tango device to control one of these coffee machines:
from tango.server import Device
class MegaCoffee3k(Device):
pass
if __name__ == "__main__":
MegaCoffee3k.run_server()
<?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>
/*----- 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
/*----- 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
/*----- 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
/*----- 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
/*----- 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
/*----- 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
#=============================================================================
#
# 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)
<?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>
/*----- PROTECTED REGION ID(MegaCoffee3k.java) ENABLED START -----*/
//=============================================================================
//
// file : MegaCoffee3k.java
//
// description : Java source for the MegaCoffee3k class and its commands.
// The class is derived from Device. It represents the
// CORBA servant object which will be accessed from the
// network. All commands which can be executed on the
// MegaCoffee3k are implemented in this file.
//
// project : Tango MegaCorp Coffee machines 3000 series
//
// This file is part of Tango device class.
//
// Tango is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Tango is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Tango. If not, see <http://www.gnu.org/licenses/>.
//
//
//
//=============================================================================
// This file is generated by POGO
// (Program Obviously used to Generate tango Object)
//=============================================================================
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.java
package org.tango.megacoffee3k;
/*----- PROTECTED REGION ID(MegaCoffee3k.imports) ENABLED START -----*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;
import org.tango.DeviceState;
import org.tango.server.InvocationContext;
import org.tango.server.ServerManager;
import org.tango.server.annotation.AroundInvoke;
import org.tango.server.annotation.Attribute;
import org.tango.server.annotation.AttributeProperties;
import org.tango.server.annotation.ClassProperty;
import org.tango.server.annotation.Command;
import org.tango.server.annotation.Delete;
import org.tango.server.annotation.Device;
import org.tango.server.annotation.DeviceProperty;
import org.tango.server.annotation.DynamicManagement;
import org.tango.server.annotation.Init;
import org.tango.server.annotation.State;
import org.tango.server.annotation.StateMachine;
import org.tango.server.annotation.Status;
import org.tango.server.annotation.DeviceManagement;
import org.tango.server.annotation.Pipe;
import org.tango.server.attribute.ForwardedAttribute;import org.tango.server.pipe.PipeValue;
import org.tango.server.dynamic.DynamicManager;
import org.tango.server.device.DeviceManager;
import org.tango.server.dynamic.DynamicManager;
import org.tango.server.events.EventManager;
import org.tango.server.events.EventType;
import org.tango.utils.DevFailedUtils;
// Import Tango IDL types
import fr.esrf.Tango.*;
import fr.esrf.TangoDs.Except;
import fr.esrf.TangoApi.PipeBlob;
import fr.esrf.TangoApi.PipeDataElement;
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.imports
/**
* MegaCoffee3k class description:
*
*/
@Device
public class MegaCoffee3k {
protected static final Logger logger = LoggerFactory.getLogger(MegaCoffee3k.class);
protected static final XLogger xlogger = XLoggerFactory.getXLogger(MegaCoffee3k.class);
//========================================================
// Programmer's data members
//========================================================
/*----- PROTECTED REGION ID(MegaCoffee3k.variables) ENABLED START -----*/
// Put static variables here
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.variables
/*----- PROTECTED REGION ID(MegaCoffee3k.private) ENABLED START -----*/
// Put private variables here
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.private
//========================================================
// Property data members and related methods
//========================================================
//========================================================
// Miscellaneous methods
//========================================================
/**
* Initialize the device.
*
* @throws DevFailed if something fails during the device initialization.
*/
@Init(lazyLoading = false)
public void initDevice() throws DevFailed {
xlogger.entry();
logger.debug("init device " + deviceManager.getName());
/*----- PROTECTED REGION ID(MegaCoffee3k.initDevice) ENABLED START -----*/
// Put your device initialization code here
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.initDevice
xlogger.exit();
}
/**
* all resources may be closed here. Collections may be also cleared.
*
* @throws DevFailed if something fails during the device object deletion.
*/
@Delete
public void deleteDevice() throws DevFailed {
xlogger.entry();
/*----- PROTECTED REGION ID(MegaCoffee3k.deleteDevice) ENABLED START -----*/
// Put your device clearing code here
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.deleteDevice
xlogger.exit();
}
/**
* Method called before and after command and attribute calls.
* @param ctx the invocation context
* @throws DevFailed if something fails during this method execution.
*/
@AroundInvoke
public void aroundInvoke(final InvocationContext ctx) throws DevFailed {
xlogger.entry();
/*----- PROTECTED REGION ID(MegaCoffee3k.aroundInvoke) ENABLED START -----*/
// Put aroundInvoke code here
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.aroundInvoke
xlogger.exit();
}
/**
* dynamic command and attribute management. Will be injected by the framework.
*/
@DynamicManagement
protected DynamicManager dynamicManager;
/**
* @param dynamicManager the DynamicManager instance
* @throws DevFailed if something fails during this method execution.
*/
public void setDynamicManager(final DynamicManager dynamicManager) throws DevFailed {
this.dynamicManager = dynamicManager;
/*----- PROTECTED REGION ID(MegaCoffee3k.setDynamicManager) ENABLED START -----*/
// Put your code here
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.setDynamicManager
}
/**
* Device management. Will be injected by the framework.
*/
@DeviceManagement
DeviceManager deviceManager;
public void setDeviceManager(DeviceManager deviceManager){
this.deviceManager= deviceManager ;
}
//========================================================
// Command data members and related methods
//========================================================
/**
* The state of the device
*/
@State
private DevState state = DevState.UNKNOWN;
/**
* Execute command "State".
* description: This command gets the device state (stored in its 'state' data member) and returns it to the caller.
* @return Device state
* @throws DevFailed if command execution failed.
*/
public final DevState getState() throws DevFailed {
/*----- PROTECTED REGION ID(MegaCoffee3k.getState) ENABLED START -----*/
// Put state code here
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.getState
return state;
}
/**
* Set the device state
* @param state the new device state
*/
public void setState(final DevState state) {
this.state = state;
}
/**
* The status of the device
*/
@Status
private String status = "Server is starting. The device state is unknown";
/**
* Execute command "Status".
* description: This command gets the device status (stored in its 'status' data member) and returns it to the caller.
* @return Device status
* @throws DevFailed if command execution failed.
*/
public final String getStatus() throws DevFailed {
/*----- PROTECTED REGION ID(MegaCoffee3k.getStatus) ENABLED START -----*/
// Put status code here
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.getStatus
return status;
}
/**
* Set the device status
* @param status the new device status
*/
public void setStatus(final String status) {
this.status = status;
}
//========================================================
// Programmer's methods
//========================================================
/*----- PROTECTED REGION ID(MegaCoffee3k.methods) ENABLED START -----*/
// Put your own methods here
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.methods
/**
* Starts the server.
* @param args program arguments (instance_name [-v[trace level]] [-nodb [-dlist <device name list>] [-file=fileName]])
*/
public static void main(final String[] args) {
/*----- PROTECTED REGION ID(MegaCoffee3k.main) ENABLED START -----*/
/*----- PROTECTED REGION END -----*/ // MegaCoffee3k.main
ServerManager.getInstance().start(args, MegaCoffee3k.class);
System.out.println("------- Started -------------");
}
}
For the Python example, copy that to a file main.py.
Make sure your Pixi shell is active, so the prompt should look something like this.
(tango-tut) $
Now you can run your first Tango device server, using PyTango’s test_context utility:
(tango-tut) $ python -m tango.test_context main.MegaCoffee3k --host 127.0.0.1
You see the output:
Ready to accept request
MegaCoffee3k started on port 8888 with properties {}
Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
The Ready to accept request line tells you that device server has started up.
The lines after that are from the “test context” used to launch it. You’ll use the cryptic Device access part
in the next section.
Hint
Don’t worry about the notifd warning, if you see it. It isn’t used in newer Tango systems.
Troubleshooting errors when launching
If TCP port 8888 is already in use on your system, you’ll get an error like this:
omniORB: (? 6151811072) 2024-10-28 08:05:02.317646: Failed to bind to address 127.0.0.1 port 10000. Address in use?
omniORB: (? 6151811072) 2024-10-28 08:05:02.317662: Error: Unable to create an endpoint of this description: giop:tcp:127.0.0.1:10000
Try a different port number, like 8889:
(tango-tut) $ python -m tango.test_context main.MegaCoffee3k --host 127.0.0.1 --port 8889
If you get something like
zsh: command not found: python
Or
python3.11: Error while finding module specification for 'tango.test_context' (ModuleNotFoundError: No module named 'tango')
Then your Pixi shell might not be activated. Try this, and check the Pixi installation again.
$ pixi shell
Or, if you’re not using Pixi, your virtual environment isn’t active, or you haven’t installed PyTango.
First Tango client#
Tango uses a distributed client-server architecture. Now that you have your MegaCoffee3k device server running, you need to connect a client.
Start a new terminal, so that the server keeps running in the old terminal. Again, activate your environment.
$ pixi shell
(tango-tut) $ python
We use PyTango, and the Device access Tango resource locator, tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
to connect our client, a Tango Device Proxy.
>>> import tango
>>> dp = tango.DeviceProxy("tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no")
>>> dp.ping()
382
>>> dp.State()
tango._tango.DevState.UNKNOWN
>>> dp.Status()
'The device is in UNKNOWN state.'
>>>
It works! That was super east, but it isn’t super useful yet. Read on!
Hint
If you’re wondering, the value 382 is the ping response time in microseconds.
Troubleshooting errors when using the client
>>> dp.ping()
Traceback (most recent call last):
File "<python-input-8>", line 1, in <module>
dp.ping()
~~~~~~~^^
File "/path/to/pytango/tango/green.py", line 226, in greener
return executor.run(fn, args, kwargs, wait=wait, timeout=timeout)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/path/to/pytango/tango/green.py", line 116, in run
return fn(*args, **kwargs)
File "/path/to/pytango/tango/device_proxy.py", line 2056, in __DeviceProxy__ping
return self._ping(*args, **kwargs)
~~~~~~~~~~^^^^^^^^^^^^^^^^^
PyTango.DevFailed: DevFailed[
DevError[
desc = TRANSIENT CORBA system exception: TRANSIENT_ConnectFailed
origin = void Tango::Connection::connect(const std::string &) at (/Users/runner/miniforge3/conda-bld/cpptango_1739462625904/work/src/client/devapi_base.cpp:635)
reason = API_CorbaException
severity = ERR
],
DevError[
desc = Failed to connect to device test/nodb/megacoffee3k
origin = void Tango::Connection::connect(const std::string &) at (/Users/runner/miniforge3/conda-bld/cpptango_1739462625904/work/src/client/devapi_base.cpp:635)
reason = API_ServerNotRunning
severity = ERR
]
]
This means the device server is not running. Make sure it is still running in a different terminal.
(tango-tut) $ python -m tango.test_context main.MegaCoffee3k --host 127.0.0.1
Bonus tip: admin device
The second Tango resource locator, for Server access, points to another Tango device that is run automatically. It is called the “admin” device or “DServer”, and is used for management of the device server instance. E.g., restarting individual devices, or configuring logging.
Finishing up#
You can use the keyboard combination Ctrl+C to end the Tango device server application.
Can't create notifd event supplier. Notifd event not available
Ready to accept request
MegaCoffee3k started on port 8888 with properties {}
Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
^CDone
(tango-tut) $
Similarly, you can exit the Python interpreter console when you are done with the client.
State and Status#
All Tango devices automatically provide State and Status information that can be queried at any time.
In the previous lesson you saw that this was just UNKNOWN and 'The device is in UNKNOWN state.'
for your first device. You want to make this a little better.
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()
<?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>
/*----- 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
<?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>
/*----- 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.
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.
Bonus tip: __init__
Device methods that use the Tango “machinery” cannot be called
before Device.__init__ has completed. E.g., the logging methods like info_stream,
and things like push_change_event, which you’ll learn about later.
Run this example server, and you’ll see the output:
MegaCoffee3k: __init__ start
MegaCoffee3k: init_device
MegaCoffee3k: __init__ end
Ready to accept request
MegaCoffee3k started on port 8888 with properties {}
Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
The “init_device” message is printed before the device is ready to accept requests.
In this Python example, the init_device call came from the base class constructor, Device.__init__.
In a second terminal, use the device proxy client and send the built-in
Init() command, which will re-initialise the device.
>>> dp.Init()
>>>
MegaCoffee3k: __init__ start
MegaCoffee3k: init_device
MegaCoffee3k: __init__ end
Ready to accept request
MegaCoffee3k started on port 8888 with properties {}
Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
MegaCoffee3k: delete_device
MegaCoffee3k: init_device
To re-initialise the MegaCoffee3k device, Tango first calls the delete_device
method, and then the init_device method. The constructor, __init__, is not called, so
you still have the same Python object instance.
Finally, use the keyboard combination Ctrl+C to end the Tango device server application.
MegaCoffee3k: __init__ start
MegaCoffee3k: init_device
MegaCoffee3k: __init__ end
Ready to accept request
MegaCoffee3k started on port 8888 with properties {}
Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
MegaCoffee3k: delete_device
MegaCoffee3k: init_device
^CMegaCoffee3k: delete_device
Done
You can see that delete_device method was called again during the graceful shutdown.
Tip
Variables needed for the device state are normally initialised in init_device rather than
in __init__, since init_device gets called again when the Init() command is used.
Tip
If any system resources (memory, connections, file handles, etc.) need to be released when
the device shuts down, this kind of clean-up is usually done in delete_device.
Restarting the device#
Using the admin device, you can restart the device completely, while
the device server, i.e., operating system process, remains running. This is typically done
less often than calling Init() on the device.
Start your example device server again.
Create another client, this time using the Server access Tango resource locator.
Send the built-in DevRestart() command, with the name of the device: test/nodb/megacoffee3k.
>>> import tango
>>> ap = tango.DeviceProxy("tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no")
>>> ap.DevRestart("test/nodb/megacoffee3k")
>>>
You’ll see the following output:
MegaCoffee3k: __init__ start
MegaCoffee3k: init_device
MegaCoffee3k: __init__ end
Ready to accept request
MegaCoffee3k started on port 8888 with properties {}
Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
MegaCoffee3k: delete_device
MegaCoffee3k: __init__ start
MegaCoffee3k: init_device
MegaCoffee3k: __init__ end
This time you notice a second call to __init__, so a new instance of the Python object
representing the device has been created.
Bonus tip: RestartServer
The admin device also has a RestartServer() command than can be used to restart all
the devices in a server, without naming a specific one. In this example ap.RestartServer().
Summary#
There are various ways of restarting/re-initialising a Tango device. From least to most drastic:
What? |
How? |
Process |
Object |
|---|---|---|---|
Re-initialise device |
Send |
Unchanged |
Unchanged |
Re-start device |
Send |
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.
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.
Bonus tip: getting project URL in Python
Given pyproject.toml with:
[project]
name = "tangods-megacoffee3k"
[project.urls]
Source = "https://gitlab.tango-mega-corp.com/controls/dev-tmc-megacoffee3k"
This code can fetch the URL at runtime:
import importlib.metadata
def _get_repo_info_from_package() -> str:
try:
metadata = importlib.metadata.metadata("tangods-megacoffee3k")
repo_url = (
metadata["Project-URL"].split(" ")[1]
if "Project-URL" in metadata
else "Unknown"
)
except importlib.metadata.PackageNotFoundError:
repo_url = "Unknown"
return repo_url
Next up, you’ll add a command so the device can finally do something!
Commands#
You are going to need your coffee machines to make coffee on demand, so you’ll need a way to tell the Tango device to do something. In Tango, an action is triggered using a command.
For starters, here are some very simple commands. Commands have a name, an optional input parameter, and an optional return value. Multiple parameters are not supported, and neither are complex types.
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:
Brewhas no input or output parameters.BrewNoNamehas no input, but returns a string.BrewNameaccepts an input string and returns a string.BrewNamesaccepts a list of strings and returns a list of strings.BrewNameDocshows how the input and output parameters can be documented. There isn’t a way to document the command itself.BrewNameDocDtypeis 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, andStatuscommands already existMethods that already exist on the
DeviceProxyclass, 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:
DevVarDoubleStringArrayandDevVarLongStringArrayan 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.
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:
waterLevela read-only float. It is a scalar value (in other words, zero-dimensional, or 0-D).beanLevelsis 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.beanLevelsDocshows how documentation and the units can be defined.grindis 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 thedockeyword argument.brewingTemperatureis defined using assignment rather than decorator syntax. It is a read-write scalar float. The pattern for naming the methods is critical:read_andwrite_, 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, andStatusalready exist as commands.Methods that already exist on the
DeviceProxyclass, 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.
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
mandatoryset to true, so it has to be configured.The port number, an integer with default. Most machines will use the same TCP port.
The location at MegaCorp, an optional string with no default.
Now when you run the Tango device server, using PyTango’s test_context utility we will get an error:
(tango-tut) $ python -m tango.test_context main.MegaCoffee3k --host 127.0.0.1
init_device before super: None:None @ None
Traceback (most recent call last):
...
File "/path/to/pytango/tango/server.py", line 1694, in tango_loop
util.server_init()
~~~~~~~~~~~~~~~~^^
tango._tango.PyTango.DevFailed: DevFailed[
DevError[
desc = Exception: Device property host is mandatory
origin = Traceback (most recent call last):
File "/path/to/pytango/tango/device_class.py", line 648, in __DeviceClass__device_factory
device = self._new_device(deviceImplClass, klass, dev_name)
File "/path/to/pytango/tango/device_class.py", line 627, in __DeviceClass__new_device
return klass(dev_class, dev_name)
File "/path/to/pytango/tango/server.py", line 806, in __init__
self.init_device()
~~~~~~~~~~~~~~~~^^
File "/path/to/pytango/tango/server.py", line 404, in init_device
return worker.execute(init_device_orig, self)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/path/to/pytango/tango/green.py", line 105, in execute
return fn(*args, **kwargs)
File "/path/to/tango-tut/src/07-device-properties/python/main.py", line 12, in init_device
super().init_device()
~~~~~~~~~~~~~~~~~~~^^
File "/path/to/pytango/tango/server.py", line 404, in init_device
return worker.execute(init_device_orig, self)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
File "/path/to/pytango/tango/green.py", line 105, in execute
return fn(*args, **kwargs)
File "/path/to/pytango/tango/server.py", line 825, in init_device
self.get_device_properties()
~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/path/to/pytango/tango/server.py", line 877, in get_device_properties
raise Exception(msg)
Exception: Device property host is mandatory
reason = PyDs_PythonError
severity = ErrSeverity.ERR
]
]
You don’t have a real database to store the property values yet, but they can be provided on the command line when
launching the device server via the --prop argument:
(tango-tut) $ python -m tango.test_context main.MegaCoffee3k --prop '{"host": "localhost"}' --host 127.0.0.1
init_device before super: None:None @ None
init_device after super: localhost:9788 @ None
Ready to accept request
MegaCoffee3k started on port 8888 with properties {'host': 'localhost'}
Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
This time you see no errors. The printout shows that before the super().init_device() call the property values
were None, but after the call, you have the expected values: "localhost", which you provided, and the default
port number of 9788. The location wasn’t provided, so it still has the value None.
Now you provide the location as well:
(tango-tut) $ python -m tango.test_context main.MegaCoffee3k --prop '{"host": "localhost", "location": "control room"}' --host 127.0.0.1
init_device before super: None:None @ None
init_device after super: localhost:9788 @ control room
Ready to accept request
MegaCoffee3k started on port 8888 with properties {'host': 'localhost', 'location': 'control room'}
Device access: tango://127.0.0.1:8888/test/nodb/megacoffee3k#dbase=no
Server access: tango://127.0.0.1:8888/dserver/MegaCoffee3k/megacoffee3k#dbase=no
Next up, you’ll look into using an external Tango database for persisting and configuring these values.
Running a Tango Database#
So far, we have been running our Tango device server, using PyTango’s test_context utility to run with nodb.
This is very useful during development but has some limitations.
You can’t interact with your server using jive for example.
The official Tango Database is written in C++ and requires MariaDB as backend.
PyTango contains its own Database implementation, which should work as a drop-in replacement. It stores data using SQLite so it has no external dependencies, making it easier to run. This is what we will use in this tutorial.
Warning
This implementation is in an experimental state, and has not been extensively tested for compatibility or for performance. Don’t use it for anything mission critical!
Running PyTango Database Device Server#
Using our existing environment with pytango 10:
(tango-tut) $ TANGO_HOST=127.0.0.1:10000 python -m tango.databaseds.database 2
Ready to accept request
That’s it!
If you already have a Databaseds running on port 10000, you can pick another port (like 11000).
Note
The previous command will create a file named tango_database.db in the current directory.
This is the file containing the SQLite database.
You can change the name of that file with the PYTANGO_DATABASE_NAME environment variable.
Tip
As we use SQLite, you can easily store the database in memory instead of the ordinary disk file. Set PYTANGO_DATABASE_NAME variable to the special filename :memory:.
Bonus tip: pytango-db
The PyTango Database Device Server code has been forked to a separate repository for easier development. It makes it easy to use even if you don’t work with pytango 10.
Install pytango-db globally with pixi:
$ pixi global install pytango-db
Global environments as specified in '~/.pixi/manifests/pixi-global.toml'
└── pytango-db: 0.3.0 (installed)
└─ exposes: PyDatabaseds
In a terminal, run:
$ TANGO_HOST=127.0.0.1:10000 PyDatabaseds 2
Ready to accept request
Setting TANGO_HOST#
Any Tango client or server needs to know where the database is running. It won’t run otherwise.
This information shall be defined via the TANGO_HOST environment variable.
You can export this variable in your shell or define it in a file ($HOME/.tangorc or /etc/tangorc).
See the reference on environment variables for more information.
In this tutorial, we’ll export it in our environment using export TANGO_HOST=127.0.0.1:10000 on macOS and Linux or set TANGO_HOST=127.0.0.1:10000 on Windows.
You can set it before of after running pixi shell. If you start a new terminal, make sure to define it again (if you prefer you can use your .bashrc / .zshrc or .tangorc file).
(tango-tut) $ export TANGO_HOST=127.0.0.1:10000
You can check at any time that it is properly set by printing it:
(tango-tut) $ echo $TANGO_HOST
127.0.0.1:10000
Checking the Database Device Server#
Using pytango from the command line#
Start the python interpreter under the pixi shell.
Warning
Make sure the TANGO_HOST variable is defined in your environment before to start python.
(tango-tut) $ python
To interact with a Tango Database from PyTango, you use the tango.Database class, which provides methods for all database commands.
Try it and query the database for some general information about the tables:
>>> import tango
>>> db = tango.Database()
>>> print(db.get_info())
TANGO Database tango_database.db
Running since ...2025-04-10 13:17:37
Devices defined = 8
Devices exported = 2
Device servers defined = 4
Device servers exported = 1
Device properties defined = 0 [History lgth = 0]
Class properties defined = 53 [History lgth = 0]
Device attribute properties defined = 0 [History lgth = 0]
Class attribute properties defined = 0 [History lgth = 0]
Object properties defined = 0 [History lgth = 0]
If this worked, you connected properly to a Tango Database! Otherwise, see the troubleshooting tip below.
Troubleshooting errors when instantiating tango.Database()
If you forgot to set the TANGO_HOST variable or set it to an invalid value,
instantiating tango.Database() will raise an exception:
>>> db = tango.Database()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/username/tango-tut/.pixi/envs/default/lib/python3.12/site-packages/tango/db.py", line 92, in __Database____init__
return Database.__init_orig__(self, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
PyTango.DevFailed: DevFailed[
DevError[
desc = TANGO_HOST env. variable not set, set it and retry (e.g. TANGO_HOST=<host>:<port>)
origin = Tango::Database::Database(CORBA::ORB_var) at (/Users/runner/miniforge3/conda-bld/cpptango_1742400675684/work/src/client/dbapi_base.cpp:78)
reason = API_TangoHostNotSet
severity = ERR]
To use tango.Database() without arguments, make sure to export the TANGO_HOST variable.
You can also pass the hostname and port directly to the Database() class.
>>> db = tango.Database("127.0.0.1", 10000)
Many more commands are available. You can for example list the servers registered in the database:
>>> db.get_server_list()
DbDatum(name = 'server', value_string = ['DataBaseds/2', 'DataBaseds/pydb-test', 'TangoAccessControl/1', 'TangoTest/test'])
See the Database API for more information.
Using Jive#
Another way to interact with the Tango Database is using Jive.
Jive is a java application and it’s already installed in our pixi environment for this tutorial.
Bonus tip: Install Jive globally
As Jive is a very common tool and standalone java application, you might want to install it globally to not depend on a specific environment. You can use pixi global for that purpose.
$ pixi global install jive
Global environments as specified in '/Users/username/.pixi/manifests/pixi-global.toml'
└── jive: 7.45 (installed)
└─ exposes: jive
That will make the jive command available globally, without having to activate any environment.
As before with PyTango, to run jive, the TANGO_HOST variable must already be set in your environment!
(tango-tut) $ jive
You should be able to see the predefined servers in the database: DataBaseds, TangoAccessControl and TangoTest.
You can access sys/database/2, but sys/tg_test/1 isn’t exported because TangoTest isn’t running.
Troubleshooting errors when starting Jive
If you get the following error when running jive, then the database isn’t running on the given port or host. Check that you used the same TANGO_HOST when starting the database and Jive.
Running TangoTest#
Now that we have a Tango Database running, we can start TangoTest (the test instance is already defined in the database).
In another terminal, run:
(tango-tut) $ TangoTest test
As you started TangoTest after Jive, you have to refresh the tree:
You can now test the device by sending commands or reading/writing attributes.
You can of course use PyTango DeviceProxy to check the status or any attribute:
>>> import tango
>>> dp = tango.DeviceProxy("sys/tg_test/1")
>>> dp.Status()
'The device is in RUNNING state.'
>>> dp.double_scalar
161.10643365358285
If you don’t need TangoTest, you can use the keyboard combination Ctrl+C in your terminal to stop it.
Summary#
You now know how to start a Tango DatabaseDS for local development and how to interact with it using PyTango or Jive.
We’ll see next how to add a new device to the database.
Adding a new server to the Tango Database#
In the previous lesson you saw how to run a Tango Databaseds.
You could start TangoTest because the device sys/tg_test/1 and the test device server instance were already defined in the database.
Adding a server#
If you want to run a new server, you first have to add the device and instance to the database.
Let’s add our MegaCoffee3k server using tango-admin.
Warning
Make sure the TANGO_HOST variable is defined in your environment before to run any of the following command.
(tango-tut) $ tango_admin --add-server MegaCoffee3k/test MegaCoffee3k sys/coffee/1
(tango-tut) $ echo $?
0
Note
The tango_admin command isn’t very verbose. Even in case of failure, it won’t print any error message. You need to print the return code to check if it succeeded (0) or not. If you set an invalid TANGO_HOST, the command will fail:
(tango-tut) $ TANGO_HOST=unknown:10000 tango_admin --add-server MegaCoffee3k/test MegaCoffee3k sys/coffee/1
(tango-tut) $ echo $?
255
Given the example from the first steps, you can run it using:
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.
from tango.server import Device, device_property
class MegaCoffee3k(Device):
host: str = device_property(mandatory=True)
port: int = device_property(default_value=9788)
location: str = device_property()
def init_device(self):
print(f"init_device before super: {self.host}:{self.port} @ {self.location}")
super().init_device()
print(f"init_device after super: {self.host}:{self.port} @ {self.location}")
if __name__ == "__main__":
MegaCoffee3k.run_server()
This server requires a mandatory property that you need to define in the database. Let’s add the host property using tango_admin:
(tango-tut) $ tango_admin --add-property sys/coffee/1 host localhost
Now that the property is defined, you can start the server:
(tango-tut) $ python main.py test
init_device before super: None:None @ None
init_device after super: localhost:9788 @ None
Ready to accept request
This server is using other properties. Let’s change the port value using Jive.
Open Jive. Go to the sys/coffee/1 device properties and click New property.
Enter port in the pop-up window.
Press OK. Click on the new property value column and enter the new value to overwrite the default.
Click Apply.
For the server to read the new property, you could just restart it.
Another way is to run the Init command. Let’s do that from Jive as you have it opened.
In the terminal where you started the Tango server, you should see the following output:
init_device before super: localhost:9788 @ None
init_device after super: localhost:9700 @ None
The port defined in the database overwrites the default value defined in the code.
You saw that a server shall be defined in the database before to start it.
We used tango_admin, a simple command line tool, but it can also be done using Jive or json2tango.
This tutorial shows you how to get started with Tango, covering the major features, step by step.
The topics will build on the previous ones, but you can jump in at any point to learn about a specific aspect.
Note
The code in this getting started tutorial is licensed under the MIT No Attribution License.
Installation#
There are many ways to install Tango, but for this tutorial we will use Pixi, which provides a similar experience across multiple platforms, is fast, and very easy to use.
Follow the instructions on the Pixi home page. We’ll wait for you here…
⏳
… welcome back. Now open a new terminal, and run the following to check if the installation worked:
$ pixi
Create a folder to keep the tutorial code. You could start in your home directory, and make a directory
called projects for all your projects.
$ cd
$ mkdir projects
$ cd projects
Create a Pixi project, and install all the dependencies we will need.
$ pixi init tango-tut
$ cd tango-tut
$ pixi add python=3.12 pytango tango-test tango-admin jtango jive pogo
Enter the Pixi project shell, which is a bit like activating a Python virtual environment. This will make our newly installed version of Python and its dependencies available at the prompt.
$ pixi shell
Test it:
(tango-tut) $ python
>>> import tango
>>> print(tango.utils.info())
PyTango 10.0.0 (10, 0, 0)
PyTango compiled with:
Python : 3.12.7
Numpy : 2.0.2
Tango : 10.1.0
pybind11 : 2.13.6
PyTango runtime is:
Python : 3.12.5
Numpy : 2.1.2
Tango : 10.1.0
PyTango running on:
uname_result(system='Darwin', node='my.machine', release='24.0.0', version='Darwin Kernel Version 24.0.0: Tue Sep 24 23:39:07 PDT 2024; root:xnu-11215.1.12~1/RELEASE_ARM64_T6000', machine='arm64')
We can exit the python console with the exit() command or using CTRL+D.
We can exit the Pixi shell with the exit command:
(tango-tut) $ exit
$
Example deployment of a Tango Controls System#
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 byDatabasedsfor 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 theDatabasedsbefore any other Tango program.A different computer on which a cppTango, JTango or PyTango
device serverwill 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
devicesanddevice 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#
Overview#
AtkPanel is a generic control panel application. It can be used to control any Tango device. It supports most of Tango features and data types. Using AtkPanel you can view and set attribute values, execute commands, test the device and access diagnostic data.
AtkPanel 5.5 main window#
Menu bar
Device state
Device name
Commands drop-down
Device status
Device attributes
Attribute switcher
By default AtkPanel opens the scalar attribute view. If your device has attributes of non-scalar types, they will appear on the attribute switcher on the bottom of the window.
Scalar attributes view#
Double array attribute view#
Boolean array attribute view#
Image attribute view#
You can run a command by selecting the command name from the command drop-down list.
Command drop-down list#
Device testing#
By selecting View > Test device from the menu you can open the device testing panel. In this panel you can check how the device is reponding to different commands and attribute values and how much time the requests take.
Test commands view#
Test attributes view#
Test pipes view#
Test admin view#
On the Admin tab of Test Device window you can set the device’s administrative configuration such as value source, timeout, etc.
Trends#
By choosing View > Numeric & State Trend or View > Boolean Trend you can see the numeric & state or boolean trend of selected attributes.
Numeric & State trend#
Boolean trend#
You can add attributes to the trend by right-clicking the attribute and selecting the desired axis. You can plot the data on X and two Y axes.
Error History#
The View > Error history menu option opens the list of recent Tango errors that occured with the device.
Error history#
Diagnostics#
The View > Diagnostic menu option provides an overview on device diagnostic information. You can check the device interface version, events and polling statistics and command execution counts.
Device diagnostics#
Attribute diagnostics#
Polled attribute diagnostics#
Command diagnostics#
Preferences#
From the Preferences menu you can tweak AtkPanel refreshing, set
the timeout and switch between Operator View and
Expert View. In Expert View you can see the attributes
that have display level set to EXPERT.
AtkPanel Preferences#
Developing clients with the TangoATK#
This chapter is only a brief Tango ATK (Application ToolKit) programmer’s guide. You can find a reference guide with a full description of TangoATK classes and methods in the ATK JavaDoc (Tango ATK reference on-line documentation).
The Tango ATK Tutorial includes the detailed description of the ATK architecture and the ATK components. In the tutorial you can find some code examples and also Flash Demos which explain how to start using Tango ATK.
Introduction#
This chapter describes how to develop applications using the Tango Application Toolkit, TangoATK for short. It will start with a brief description of the main concepts behind the toolkit, and then continue with more practical, real-life examples to explain key parts.
Assumptions#
The author assumes that the reader has a good knowledge of the Java programming language, and a thorough understanding of object-oriented programming. Also, it is expected that the reader is fluent in all aspects regarding Tango devices, attributes, and commands.
The key concepts of TangoATK#
TangoATK was developed with these goals in mind
TangoATK should help minimize development time
TangoATK should help minimize bugs in applications
TangoATK should support applications that contain attributes and commands from several different devices.
TangoATK should help avoid code duplication.
Since most Tango-applications were foreseen to be displayed on some sort of graphic terminal, TangoATK needed to provide support for some sort of graphic building blocks. To enable this, and since the toolkit was to be written in Java, we looked to Swing to figure out how to do this.
Swing is developed using a variant over a design-pattern the Model-View-Controller (MVC) pattern called model-delegate, where the view and the controller of the MVC-pattern are merged into one object.
This pattern made the choice of labor division quite easy: all
non-graphic parts of TangoATK reside in the packages beneath
fr.esrf.tangoatk.core, and anything remotely graphic are located
beneath fr.esrf.tangoatk.widget. More on the content and
organization of this will follow.
The communication between the non-graphic and graphic objects are done by having the graphic object registering itself as a listener to the non-graphic object, and the non-graphic object emmiting events telling the listeners that its state has changed.
Minimize development time#
For TangoATK to help minimize the development time of graphic applications, the toolkit has been developed along two lines of thought
Things that are needed in most applications are included, eg
Splash, a splash window which gives a graphical way for the application to show the progress of a long operation. The splash window is moslty used in the startup phase of the application.Building blocks provided by TangoATK should be easy to use and follow certain patterns, eg every graphic widget has a
setModelmethod 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
Instantiate an AttributeList and fill it with the attributes you want.
Instantiate a CommandList and fill it with the commands you want.
Connect the whole
AttributeListwith alist viewerand / or each individual attribute with an attribute viewer.Connect the whole
CommandListto a command list viewer and / or connect each individual command in the command list with a command viewer.
The following program (FirstApplication) shows an implementation of the list mentioned above. It should be rather self-explanatory with the comments.
1 package examples;
2
3
4 import javax.swing.JFrame;
5 import javax.swing.JMenuItem;
6 import javax.swing.JMenuBar;
7 import javax.swing.JMenu;
8
9
10 import java.awt.event.ActionListener;
11 import java.awt.event.ActionEvent;
12 import java.awt.BorderLayout;
13
14
15 import fr.esrf.tangoatk.core.AttributeList;
16 import fr.esrf.tangoatk.core.ConnectionException;
17
18
19 import fr.esrf.tangoatk.core.CommandList;
20 import fr.esrf.tangoatk.widget.util.ErrorHistory;
21 import fr.esrf.tangoatk.widget.util.ATKGraphicsUtils;
22 import fr.esrf.tangoatk.widget.attribute.ScalarListViewer;
23 import fr.esrf.tangoatk.widget.command.CommandComboViewer;
24
25
26 public class FirstApplication extends JFrame
27 {
28 JMenuBar menu; // So that our application looks
29 // halfway decent.
30 AttributeList attributes; // The list that will contain our
31 // attributes
32 CommandList commands; // The list that will contain our
33 // commands
34 ErrorHistory errorHistory; // A window that displays errors
35 ScalarListViewer sListViewer; // A viewer which knows how to
36 // display a list of scalar attributes.
37 // If you want to display other types
38 // than scalars, you'll have to wait
39 // for the next example.
40 CommandComboViewer commandViewer; // A viewer which knows how to display
41 // a combobox of commands and execute
42 // them.
43 String device; // The name of our device.
44
45
46 public FirstApplication()
47 {
48 // The swing stuff to create the menu bar and its pulldown menus
49 menu = new JMenuBar();
50 JMenu fileMenu = new JMenu();
51 fileMenu.setText("File");
52 JMenu viewMenu = new JMenu();
53 viewMenu.setText("View");
54
55 JMenuItem quitItem = new JMenuItem();
56 quitItem.setText("Quit");
57 quitItem.addActionListener(new
58 java.awt.event.ActionListener()
59 {
60 public void
61 actionPerformed(ActionEvent evt)
62 {quitItemActionPerformed(evt);}
63 });
64 fileMenu.add(quitItem);
65
66 JMenuItem errorHistItem = new JMenuItem();
67 errorHistItem.setText("Error History");
68 errorHistItem.addActionListener(new
69 java.awt.event.ActionListener()
70 {
71 public void
72 actionPerformed(ActionEvent evt)
73 {errHistItemActionPerformed(evt);}
74 });
75 viewMenu.add(errorHistItem);
76 menu.add(fileMenu);
77 menu.add(viewMenu);
78
79 //
80 // Here we create ATK objects to handle attributes, commands and errors.
81 //
82 attributes = new AttributeList();
83 commands = new CommandList();
84 errorHistory = new ErrorHistory();
85 device = "id14/eh3_mirror/1";
86 sListViewer = new ScalarListViewer();
87 commandViewer = new CommandComboViewer();
88
89
90 //
91 // A feature of the command and attribute list is that if you
92 // supply an errorlistener to these lists, they'll add that
93 // errorlistener to all subsequently created attributes or
94 // commands. So it is important to do this _before_ you
95 // start adding attributes or commands.
96 //
97
98 attributes.addErrorListener(errorHistory);
99 commands.addErrorListener(errorHistory);
100
101 //
102 // Sometimes we're out of luck and the device or the attributes
103 // are not available. In that case a ConnectionException is thrown.
104 // This is why we add the attributes in a try/catch
105 //
106
107 try
108 {
109
110 //
111 // Another feature of the attribute and command list is that they
112 // can add wildcard names, currently only `*' is supported.
113 // When using a wildcard, the lists will add all commands or
114 // attributes available on the device.
115 //
116 attributes.add(device + "/*");
117 }
118 catch (ConnectionException ce)
119 {
120 System.out.println("Error fetching " +
121 "attributes from " +
122 device + " " + ce);
123 }
124
125
126 //
127 // See the comments for attributelist
128 //
129
130
131 try
132 {
133 commands.add(device + "/*");
134 }
135 catch (ConnectionException ce)
136 {
137 System.out.println("Error fetching " +
138 "commands from " +
139 device + " " + ce);
140 }
141
142
143 //
144 // Here we tell the scalarViewer what it's to show. The
145 // ScalarListViewer loops through the attribute-list and picks out
146 // the ones which are scalars and show them.
147 //
148
149 sListViewer.setModel(attributes);
150
151
152 //
153 // This is where the CommandComboViewer is told what it's to
154 // show. It knows how to show and execute most commands.
155 //
156
157
158 commandViewer.setModel(commands);
159
160
161 //
162 // add the menubar to the frame
163 //
164
165
166 setJMenuBar(menu);
167
168
169 //
170 // Make the layout nice.
171 //
172
173
174 getContentPane().setLayout(new BorderLayout());
175 getContentPane().add(commandViewer, BorderLayout.NORTH);
176 getContentPane().add(sListViewer, BorderLayout.SOUTH);
177
178
179 //
180 // A third feature of the attributelist is that it knows how
181 // to refresh its attributes.
182 //
183
184
185 attributes.startRefresher();
186
187
188 //
189 // JFrame stuff to make the thing show.
190 //
191
192
193 pack();
194 ATKGraphicsUtils.centerFrameOnScreen(this); //ATK utility to center window
195
196 setVisible(true);
197 }
198
199
200 public static void main(String [] args)
201 {
202 new FirstApplication();
203 }
204
205 public void quitItemActionPerformed(ActionEvent evt)
206 {
207 System.exit(0);
208 }
209
210 public void errHistItemActionPerformed(ActionEvent evt)
211 {
212 errorHistory.setVisible(true);
213 }
214 }
The program should look something like this (depending on your platform and your device)
Multi device applications#
Multi device applications are quite similar to the single device applications, the only difference is that it does not suffice to add the attributes by wildcard, you need to add them explicitly, like this:
1 try
2 {
3 // a StringScalar attribute from the device one
4 attributes.add("jlp/test/1/att_cinq");
5 // a NumberSpectrum attribute from the device one
6 attributes.add("jlp/test/1/att_spectrum");
7 // a NumberImage attribute from the device two
8 attributes.add("sr/d-ipc/id25-1n/Image");
9 }
10 catch (ConnectionException ce)
11 {
12 System.out.println("Error fetching " +
13 "attributes" + ce);
14 }
The same goes for commands.
More on displaying attributes#
So far, we’ve only considered scalar attributes, and not only that,
we’ve also cheated quite a bit since we just passed the attribute list
to the fr.esrf.tangoatk.widget.attribute.ScalarListViewer and let it
do all the magic. The attribute list viewers are only available for
scalar attributes (NumberScalarListViewer and ScalarListViewer). If you
have one or several spectrum or image attributes you must connect each
spectrum or image attribute to it’s corresponding attribute viewer
individually. So let’s take a look at how you can connect individual
attributes (and not a whole attribute list) to an individual attribute
viewer (and not to an attribute list viewer).
Connecting an attribute to a viewer#
Generally it is done in the following way:
You retrieve the attribute from the attribute list
You instantiate the viewer
Your call the
setModelmethod on the viewer with the attribute as argument.You add your viewer to some panel
The following example (SecondApplication), is a Multi-device application. Since this application uses individual attribute viewers and not an attribute list viewer, it shows an implementation of the list mentioned above.
1 package examples;
2
3
4 import javax.swing.JFrame;
5 import javax.swing.JMenuItem;
6 import javax.swing.JMenuBar;
7 import javax.swing.JMenu;
8
9
10 import java.awt.event.ActionListener;
11 import java.awt.event.ActionEvent;
12 import java.awt.BorderLayout;
13 import java.awt.Color;
14
15
16 import fr.esrf.tangoatk.core.AttributeList;
17 import fr.esrf.tangoatk.core.ConnectionException;
18
19 import fr.esrf.tangoatk.core.IStringScalar;
20 import fr.esrf.tangoatk.core.INumberSpectrum;
21 import fr.esrf.tangoatk.core.INumberImage;
22 import fr.esrf.tangoatk.widget.util.ErrorHistory;
23 import fr.esrf.tangoatk.widget.util.Gradient;
24 import fr.esrf.tangoatk.widget.util.ATKGraphicsUtils;
25 import fr.esrf.tangoatk.widget.attribute.NumberImageViewer;
26 import fr.esrf.tangoatk.widget.attribute.NumberSpectrumViewer;
27 import fr.esrf.tangoatk.widget.attribute.SimpleScalarViewer;
28
29 public class SecondApplication extends JFrame
30 {
31 JMenuBar menu;
32 AttributeList attributes; // The list that will contain our attributes
33 ErrorHistory errorHistory; // A window that displays errors
34 IStringScalar ssAtt;
35 INumberSpectrum nsAtt;
36 INumberImage niAtt;
37 public SecondApplication()
38 {
39 // Swing stuff to create the menu bar and its pulldown menus
40 menu = new JMenuBar();
41 JMenu fileMenu = new JMenu();
42 fileMenu.setText("File");
43 JMenu viewMenu = new JMenu();
44 viewMenu.setText("View");
45 JMenuItem quitItem = new JMenuItem();
46 quitItem.setText("Quit");
47 quitItem.addActionListener(new java.awt.event.ActionListener()
48 {
49 public void actionPerformed(ActionEvent evt)
50 {quitItemActionPerformed(evt);}
51 });
52
53 fileMenu.add(quitItem);
54 JMenuItem errorHistItem = new JMenuItem();
55 errorHistItem.setText("Error History");
56 errorHistItem.addActionListener(new java.awt.event.ActionListener()
57 {
58 public void actionPerformed(ActionEvent evt)
59 {errHistItemActionPerformed(evt);}
60 });
61 viewMenu.add(errorHistItem);
62 menu.add(fileMenu);
63 menu.add(viewMenu);
64 //
65 // Here we create TangoATK objects to view attributes and errors.
66 //
67 attributes = new AttributeList();
68 errorHistory = new ErrorHistory();
69 //
70 // We create a SimpleScalarViewer, a NumberSpectrumViewer and
71 // a NumberImageViewer, since we already knew that we were
72 // playing with a scalar attribute, a number spectrum attribute
73 // and a number image attribute this time.
74 //
75 SimpleScalarViewer ssViewer = new SimpleScalarViewer();
76 NumberSpectrumViewer nSpectViewer = new NumberSpectrumViewer();
77 NumberImageViewer nImageViewer = new NumberImageViewer();
78 attributes.addErrorListener(errorHistory);
79 //
80 // The attribute (and command) list has the feature of returning the last
81 // attribute that was added to it. Just remember that it is returned as an
82 // IEntity object, so you need to cast it into a more specific object, like
83 // IStringScalar, which is the interface which defines a string scalar
84 //
85 try
86 {
87
88 ssAtt = (IStringScalar) attributes.add("jlp/test/1/att_cinq");
89 nsAtt = (INumberSpectrum) attributes.add("jlp/test/1/att_spectrum");
90 niAtt = (INumberImage) attributes.add("sr/d-ipc/id25-1n/Image");
91 }
92 catch (ConnectionException ce)
93 {
94 System.out.println("Error fetching one of the attributes "+" " + ce);
95 System.out.println("Application Aborted.");
96 System.exit(0);
97 }
98 //
99 // Pay close attention to the following three lines!! This is how it's done!
100 // This is how it's always done! The setModelsetModel method of any viewer takes care
101 // of connecting the viewer to the attribute (model) it's in charge of displaying.
102 // This is the way to tell each viewer what (which attribute) it has to show.
103 // Note that we use a viewer adapted to each type of attribute
104 //
105 ssViewer.setModel(ssAtt);
106 nSpectViewer.setModel(nsAtt);
107 nImageViewer.setModel(niAtt);
108 //
109 nSpectViewer.setPreferredSize(new java.awt.Dimension(400, 300));
110 nImageViewer.setPreferredSize(new java.awt.Dimension(500, 300));
111 Gradient g = new Gradient();
112 g.buidColorGradient();
113 g.setColorAt(0,Color.black);
114 nImageViewer.setGradient(g);
115 nImageViewer.setBestFit(true);
116
117 //
118 // Add the viewers into the frame to show them
119 //
120 getContentPane().setLayout(new BorderLayout());
121 getContentPane().add(ssViewer, BorderLayout.SOUTH);
122 getContentPane().add(nSpectViewer, BorderLayout.CENTER);
123 getContentPane().add(nImageViewer, BorderLayout.EAST);
124 //
125 // To have the attributes values refreshed we should start the
126 // attribute list's refresher.
127 //
128 attributes.startRefresher();
129 //
130 // add the menubar to the frame
131 //
132 setJMenuBar(menu);
133 //
134 // JFrame stuff to make the thing show.
135 //
136 pack();
137 ATKGraphicsUtils.centerFrameOnScreen(this); //ATK utility to center window
138 setVisible(true);
139 }
140 public static void main(String [] args)
141 {
142 new SecondApplication();
143 }
144 public void quitItemActionPerformed(ActionEvent evt)
145 {
146 System.exit(0);
147 }
148 public void errHistItemActionPerformed(ActionEvent evt)
149 {
150 errorHistory.setVisible(true);
151 }
152 }
This program (SeondApplication) should look something like this (depending on your platform and your device attributes)
Synoptic viewer#
TangoATK provides a generic class to view and to animate the synoptics. The name of this class is fr.esrf.tangoatk.widget.jdraw.SynopticFileViewer. This class is based on a “home-made” graphical layer called jdraw. The jdraw package is also included inside TangoATK distribution.
SynopticFileViewer is a sub-class of the class TangoSynopticHandler. All the work for connection to tango devices and run time animation is done inside the TangoSynopticHandler.
The recipe for using the TangoATK synoptic viewer is the following
You use Jdraw graphical editor to draw your synoptic
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
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.
Test the run-time behaviour of your synoptic. Use “Tango Synoptic view” command in the “views” pulldown menu to do this.
Save the drawing file.
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.
You can now develop a specific TangoATK based application which instantiates the SynopticFileViewer. To load the synoptic file in the SynopticFileViewer you have the choice : either you load it by giving the absolute path name of the synoptic file or you load the synoptic file using Java input streams. The second solution is used when the synoptic file is included inside the application jarfile.
The SynopticFilerViewer will browse the objects in the synoptic file at run time. It discovers if some parts of the drawing is associated with an attribute or a command. In this case it will automatically connect to the corresponding attribute or command. Once the connection is successful SynopticFileViewer will animate the synoptic according to the default behaviour described below :
For tango state attributes : the colour of the drawing object reflects the value of the state. A mouse click on the drawing object associated with the tango state attribute will instantiate and display the class specified during the drawing phase. If no class is specified the atkpanel generic device panel is displayed.
For tango attributes : the current value of the attribute is displayed through the drawing object
For tango commands : the mouse click on the drawing object associated with the command will launch the device command.
If the tooltip property is set to “name” when the mouse enters any tango object ( attribute or command), inside the synoptic drawing the name of the tango object is displayed in a tooltip.
The following example (ThirdApplication), is a Synoptic application. We assume that the synoptic has already been drawn using Jdraw graphical editor.
1 package examples;
2 import java.io.*;
3 import java.util.*;
4 import javax.swing.JFrame;
5 import javax.swing.JMenuItem;
6 import javax.swing.JMenuBar;
7 import javax.swing.JMenu;
8 import java.awt.event.ActionListener;
9 import java.awt.event.ActionEvent;
10 import java.awt.BorderLayout;
11 import fr.esrf.tangoatk.widget.util.ErrorHistory;
12 import fr.esrf.tangoatk.widget.util.ATKGraphicsUtils;
13 import fr.esrf.tangoatk.widget.jdraw.SynopticFileViewer;
14 import fr.esrf.tangoatk.widget.jdraw.TangoSynopticHandler;
15 public class ThirdApplication extends JFrame
16 {
17 JMenuBar menu;
18 ErrorHistory errorHistory; // A window that displays errors
19 SynopticFileViewer sfv; // TangoATK generic synoptic viewer
20
21
22 public ThirdApplication()
23 {
24 // Swing stuff to create the menu bar and its pulldown menus
25 menu = new JMenuBar();
26 JMenu fileMenu = new JMenu();
27 fileMenu.setText("File");
28 JMenu viewMenu = new JMenu();
29 viewMenu.setText("View");
30 JMenuItem quitItem = new JMenuItem();
31 quitItem.setText("Quit");
32 quitItem.addActionListener(new java.awt.event.ActionListener()
33 {
34 public void actionPerformed(ActionEvent evt)
35 {quitItemActionPerformed(evt);}
36 });
37 fileMenu.add(quitItem);
38 JMenuItem errorHistItem = new JMenuItem();
39 errorHistItem.setText("Error History");
40 errorHistItem.addActionListener(new java.awt.event.ActionListener()
41 {
42 public void actionPerformed(ActionEvent evt)
43 {errHistItemActionPerformed(evt);}
44 });
45 viewMenu.add(errorHistItem);
46 menu.add(fileMenu);
47 menu.add(viewMenu);
48 //
49 // Here we create TangoATK synoptic viewer and error window.
50 //
51 errorHistory = new ErrorHistory();
52 sfv = new SynopticFileViewer();
53 try
54 {
55 sfv.setErrorWindow(errorHistory);
56 }
57 catch (Exception setErrwExcept)
58 {
59 System.out.println("Cannot set Error History Window");
60 }
61
62 //
63 // Here we define the name of the synoptic file to show and the tooltip mode to use
64 //
65 try
66 {
67 sfv.setJdrawFileName("/users/poncet/ATK_OLD/jdraw_files/id14.jdw");
68 sfv.setToolTipMode (TangoSynopticHandler.TOOL_TIP_NAME);
69 }
70 catch (FileNotFoundException fnfEx)
71 {
72 javax.swing.JOptionPane.showMessageDialog(
73 null, "Cannot find the synoptic file : id14.jdw.\n"
74 + "Check the file name you entered;"
75 + " Application will abort ...\n"
76 + fnfEx,
77 "No such file",
78 javax.swing.JOptionPane.ERROR_MESSAGE);
79 System.exit(-1);
80 }
81 catch (IllegalArgumentException illEx)
82 {
83 javax.swing.JOptionPane.showMessageDialog(
84 null, "Cannot parse the synoptic file : id14.jdw.\n"
85 + "Check if the file is a Jdraw file."
86 + " Application will abort ...\n"
87 + illEx,
88 "Cannot parse the file",
89 javax.swing.JOptionPane.ERROR_MESSAGE);
90 System.exit(-1);
91 }
92 catch (MissingResourceException mrEx)
93 {
94 javax.swing.JOptionPane.showMessageDialog(
95 null, "Cannot parse the synoptic file : id14.jdw.\n"
96 + " Application will abort ...\n"
97 + mrEx,
98 "Cannot parse the file",
99 javax.swing.JOptionPane.ERROR_MESSAGE);
100 System.exit(-1);
101 }
102 //
103 // Add the viewers into the frame to show them
104 //
105 getContentPane().setLayout(new BorderLayout());
106 getContentPane().add(sfv, BorderLayout.CENTER);
107 //
108 // add the menubar to the frame
109 //
110 setJMenuBar(menu);
111 //
112 // JFrame stuff to make the thing show.
113 //
114 pack();
115 ATKGraphicsUtils.centerFrameOnScreen(this); //TangoATK utility to center window
116 setVisible(true);
117 }
118 public static void main(String [] args)
119 {
120 new ThirdApplication();
121 }
122 public void quitItemActionPerformed(ActionEvent evt)
123 {
124 System.exit(0);
125 }
126 public void errHistItemActionPerformed(ActionEvent evt)
127 {
128 errorHistory.setVisible(true);
129 }
130 }
The synoptic application (ThirdApplication) should look something like this (depending on your synoptic drawing file)
A short note on the relationship between models and viewers#
As seen in the examples above, the connection between a model and its
viewer is generally done by calling setModel(model) on the viewer,
it is never explained what happens behind the scenes when this is done.
Listeners#
Most of the viewers implement some sort of listener interface, eg INumberScalarListener. An object implementing such a listener interface has the capability of receiving and treating events from a model which emits events.
1 // this is the setModel of a SimpleScalarViewer
2 public void setModelsetModel(INumberScalar scalar) {
3
4 clearModel();
5
6 if (scalar != null) {
7 format = scalar.getProperty("format").getPresentation();
8 numberModel = scalar;
9
10 // this is where the viewer connects itself to the
11 // model. After this the viewer will (hopefully) receive
12 // events through its numberScalarChange() method
13
14 numberModel.addNumberScalarListener(this);
15
16
17 numberModel.getProperty("format").addPresentationListener(this);
18 numberModel.getProperty("unit").addPresentationListener(this);
19 }
20
21 }
22
23
24
25 // Each time the model of this viewer (the numberscalar attribute) decides it is time, it
26 // calls the numberScalarChange method of all its registered listeners
27 // with a NumberScalarEvent object which contains the
28 // the new value of the numberscalar attribute.
29 //
30
31 public void numberScalarChange(NumberScalarEvent evt) {
32 String val;
33 val = getDisplayString(evt);
34 if (unitVisible) {
35 setText(val + " " + numberModel.getUnit());
36 } else {
37 setText(val);
38 }
39 }
All listeners in TangoATK implement the IErrorListener interface
which specifies the errorChange(ErrorEvent e) method. This means
that all listeners are forced to handle errors in some way or another.
The key objects of TangoATK#
As seen from the examples above, the key objects of TangoATK are the
CommandList and the AttributeList. These two classes inherit
from the abstract class AEntityList which implements all of the
common functionality between the two lists. These lists use the
functionality of the CommandFactory, the AttributeFactory, which
both derive from AEntityFactory, and the DeviceFactory.
In addition to these factories and lists there is one (for the time being) other important functionality lurking around, the refreshers.
The Refreshers#
The refreshers, represented in TangoATK by the Refresher object, is
simply a subclass of java.lang.Thread which will sleep for a given
amount of time and then call a method refresh on whatever kind of
IRefreshee it has been given as parameter, as shown below
1 // This is an example from DeviceFactory.
2 // We create a new Refresher with the name "device"
3 // We add ourself to it, and start the thread
4
5
6 Refresher refresher = new Refresher("device");
7 refresher.addRefreshee(this).start();
Both the AttributeList and the DeviceFactory implement the
IRefreshee interface which specify only one method, refresh(),
and can thus be refreshed by the Refresher. Even if the new release
of TangoATK is based on the Tango Events, the refresher mecanisme will
not be removed. As a matter of fact, the method refresh() implemented in
AttributeList skips all attributes (members of the list) for which the
subscribe to the tango event has succeeded and calls the old refresh()
method for the others (for which subscribe to tango events has failed).
In a first stage this will allow the TangoATK applications to mix the use of the old tango device servers (which do not implement tango events) and the new ones in the same code. In other words, TangoATK subscribes for tango events if possible otherwise TangoATK will refresh the attributes through the old refresher mecanisme.
Another reason for keeping the refresher is that the subscribe event can fail even for the attributes of the new Tango device servers. As soon as the specified attribute is not polled the Tango events cannot be generated for that attribute. Therefore the event subscription will fail. In this case the attribute will be refreshed thanks to the ATK attribute list refresher.
The AttributePolledList class allows the application programmer to
force explicitly the use of the refresher method for all attributes
added in an AttributePolledList even if the corresponding device servers
implement tango events. Some viewers
(fr.esrf.tangoatk.widget.attribute.Trend) need an AttributePolledList in
order to force the refresh of the attribute without using tango events.
What happens on a refresh#
When refresh is called on the AttributeList and the
DeviceFactory, they loop through their objects, IAttributes and
IDevices, respectively, and ask them to refresh themselves if they
are not event driven.
When AttributeFactory, creates an IAttribute, TangoATK tries to
subscribe for Tango Change event for that attribute. If the subscription
succeeds then the attribute is marked as event driven. If the
subscription for Tango Change event fails, TangoATK tries to subscribe
for Tango Periodic event. If the subscription succeeds then the
attribute is marked as event driven. If the subscription fails then the
attribute is marked as to be “ without events”.
In the refresh() method of the AttributeList during the loop through the objects if the object is marked event driven then the object is simply skipped. But if the object (attribute) is not marked as event driven, the refresh() method of the AttributeList, asks the object to refresh itself by calling the “refresh()” method of that object (attribute or device). The refresh() method of an attribute will in turn call the “readAttribute” on the Tango device.
The result of this is that the IAttributes fire off events to their
registered listeners containing snapshots of their state. The events are
fired either because the IAttribute has received a Tango Change event,
respectively a Tango Periodic event (event driven objects), or because
the refresh() method of the object has issued a readAttribute on the
Tango device.
The DeviceFactory#
The device factory is responsible for two things
Creating new devices (Tango device proxies) when needed
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:
Boolean
Unsigned Char Array
The StringLongArray
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#
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:
Device Server design consideration
Implementation good practices
These guidelines apply to features available in Tango v8 or higher.
Tango Concepts#
Some understanding of the basic Tango concepts is required to make use of this tutorial. Please refer to the Explanation section if further information on these concepts is required.
Tango Device Design#
Elements of general design#
Reusability#
In a Tango control system, each device is a software component and potentially reusable.
It is necessary to:
Systematically evaluate prior to the coding of a device, the possibility of reusing a device available in the code repositories (Tango Controls community, local repository), in order to avoid several implementations of the same equipment.
Design the device to be as reusable/extensible as possible because it may interest other developers in the community.
As such, the device must be:
Configurable: (e.g. no port number “hard coded”, but use of a parameter via a property),
Self-supporting: the device must be usable outside the private programming environment (e.g. all the necessary elements to use the device (compile, link) must be provided to the community). The use of the GPL should be considered, and the use of proprietary libraries should be avoided if possible,
Portable: the device code must be (as far as possible) independent of the target platform unless it depends on platform specific drivers,
Documented in English
Generic interface programming#
The device must be as generic as possible which means the definition of its interface should:
Reflect the service rather than its underlying implementation. For example, a command named
WriteReadreflects the communication service of a bus (type: message exchange), while a command namedNI488_Sendreflects 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
Onreflects the action of powering on a PowerSupply , while a command namedBruckerPSONreflects 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::DeviceTaskclass.
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 commandActivateOutput1(not ActO1).
The nomenclature recommendations are detailed in the section Naming Rules .
The attribute naming recommendations are:
Name composed of at least two characters,
Only alphanumeric characters are allowed (no underscore, no dashes),
Start with a lowercase letter,
In case of a composite name, each sub-words must be capitalized (except the first letter),
Prohibit any use of vague terms (eg: readValue).
Device Commands#
The recommendations are the same as those proposed for an attribute, except for the first letter of the name.
The command naming recommendations are:
Name composed of at least two characters,
Only alphanumeric characters are allowed (no underscore, no dashes),
Start with a uppercase letter,
In case of a composite name, each sub-words must be capitalized,
Prohibit any use of vague terms (eg: Control).
Device properties#
The recommendations are the same as those proposed for a command.
The property naming recommendations are:
Name composed of at least two characters,
Only alphanumeric characters are allowed (no underscore, no dashes),
Start with a uppercase letter,
In case of a composite name, each sub-words must be capitalized,
Prohibit any use of vague terms (eg: Prop1).
Device attributes nomenclature#
It is a good practice that a particular signal type is always named in a similar way in various device servers.
For example the intensity of a current should always be named
intensity (and not “intens”,
“current”,”I” depending on
the device server).
This allows the user to quickly make the link between the software information and the physical sensor and reciprocally.
Data types choice#
Always use data types consistent with the underlying information.
Unsigned integer must be used for the physical quantities that are suitable.
Eg: A number of samples
numSamples, where negative values have no meaning, will be aTango::DevULong(unsigned integer 32 bits) and not aTango::DevLong(signed integer 32 bits).Similarly, in such a case, the use of a floating point number should be prohibited as non-integer values also have no meaning in this context.s
This rule is also applicable to input/output arguments of commands.
Interface level choice#
The choice between the Expert or the Operator level for an interface must be thought through.
Only necessary commands for a nominal control of the equipment must be accessible at the Operator level. The commands for fine control of the equipment (eg: metrology, maintenance, unit test) must only be accessible at the Expert level.
Pogo use#
Device generation#
The use of Pogo is mandatory for creating or modifying the device interface.
Tango is constantly evolving but this tool will support all or part of the porting associated to the kernel and the consequences on the IDL interface.
In addition, it simplifies maintenance/development operations.
Every command, attribute, property or device state must be fully documented; this documentation is done via the Pogo tool.
When creating an attribute with Pogo, the entire configuration of the attribute must be fully filled in by the developer (maximum possible) to avoid ambiguities.
Similarly, the states and their transitions must be described with precision and clarity.
In fact:
In operation, this documentation will be the reference for understanding the device behaviour. Remember that the operator will have this information with the generic tools (like Test Device from Jive).
The html documentations generated by Pogo can also be accessed from a local server (particular to the institute).
Consider also filling in the alarm values.
E.g. set the alarm values according to the specifications of the power supply, i.e., 0-24V for the voltage, or 0-3A for the output current.
Example for a temperature reading:
Attributes generation in C++#
In C++, Pogo automatically generates pointers to the data associated with the attributes values (i.e. a pointer is generated for the read part). The use of these pointers is not mandatory. The developer is free to use their own data structure in the attribute value affectation.
Internal device implementation#
Separation between the Tango interface and the internal system function#
Don’t forget that the Tango interface is only a means to insert a microservice in a control system. Therefore, it is necessary to think about the internal design of the device like one would for any other application and just use Tango as an interface on top of it.
As a rule of thumb if the code implemented within the Pogo markers is too long it is good practice to move it to another class meaning the Pogo generated methods will be only a few lines of code long.
In practice, it is necessary to avoid mixing the code generated by Pogo and the code of the developer.
The Tango sub-class inherited from Tango::DeviceImpl[_X] instantiates
a class derived from the model object implementing the system, and
ensure the replacement between the external requests (clients) and the
implementation class(es).
In the choice of data structures, we are talking about those of the developer’s object model, we will consider the technical constraints imposed by Tango and/or the underlying layers (CORBA/ZMQ). The idea here is to avoid copy and/or reorganisation of the data when transferred to the client. For this, the developer needs to know/master the underlying memory management mechanism (especially in C++). Please see the dedicated section Exchanging data between client and server for further details.
Accessing the hardware: always_executed_hook vs. read_attr_hardware#
It is essential to understand the concepts implemented by these two methods, which are common methods for all Tango devices.
It is also necessary to clearly identify, in the design phase, the possible consequences of implementing these two methods on the device behaviour (remember that they are initially just empty shells generated by Pogo).
The
always_executed_hook()method is called before each command is executed or before every read/write of an attribute (but note, it is called only once when reading several attributes: see calling sequence below).The
read_attr_hardware()is called before every read of attribute(s)(but note again, it is called only once when reading several attributes: see calling sequence below). This method aims to optimise (minimise) the equipment access in case of simultaneous reads of multiple attributes in the same request.
Given below is the calling sequence of these methods:
Command execution
1 –
always_executed_hook()2 –
is_MyCmd_allowed()3 –
MyCmd()
Attribute reading
1 –
always_executed_hook()2 –
read_attr_hardware()3 –
is_MyAttr_allowed()4 –
read_MyAttr()
Attribute writing
1 –
always_executed_hook()2 –
is_MyAttr_allowed()3 –
write_MyAttr()
Attributes reading
1 –
always_executed_hook()2 –
read_attr_hardware()3 –
is_MyAttr_allowed()4 –
read_MyAttr()
Attributes writing
1 –
always_executed_hook()2 –
is_MyAttr_allowed()3 –
write_MyAttr()
This demonstrates why having “slow code” in the
MyDevice::always_executed_hook method can have serious consequences
on the device performance.
Note
There is no obligation to use the read_attr_hardware
method; it depends on the equipment and its communication
channel (Ethernet, GPIB, DLL). Instead, one could have a call to the equipment in
the code of each attribute reading method.
For example, for an attribute “temperature”, of READ type, we can insert
the call to the equipment in the generated attribute reading method
read_Temperature instead of read_attr_hardware.
Static database as persistent data storage#
The Tango database can (in some cases) be used to ensure persistence of set values and to store the value as a property (of device or attribute).
However, this practice should be reserved for special cases that don’t require writing at high frequency. An over-solicitation of the Tango database will penalize the entire control system.
It is therefore recommended to use a property for storage only for methods that are performed rarely, compared to other functions.
One example is the storage of calibration operations results.
In the general case, we recommend to:
Use a property to store configuration data,
Use a memorized attribute to store values changing during the execution,
Use a memorized attribute to store values that you want to re-inject during a new execution of the device.
Device property vs memorized attributes#
In some cases, you could be tempted to use a property for a memorized attribute and vice-versa. It is important to distinguish the function of each, and use them wisely.
The use of a property must be limited to configuration data which value doesn’t change at runtime (the IP address of equipment for example).
The memorized attributes are reserved for physical quantities subject to change at runtime (attribute read/write) for which you want to retain (store) the value from one execution to the other.
e.g. speed or acceleration on a motor.
Tip
In the case you want to manually manage the memorization of the
attribute set points, you should use an attribute property called
__value (as natively done by Tango).
Device state management#
States choice#
In Tango, the state is seen as an enumerated type with a fix number of values - see the Device State section for a full list of allowed value. These states have an implicit default meaning and are not equivalent.
Unless strictly specified, the developer is free to use the Tango state she considers appropriate to the situation, with all the subjectivity involved.
The only practice that ensures overall consistency is to use a limited number of Tango states, especially for a family of equipment.
For example, it is recommended for an equipment of type motor, slit, monochromator and more generally for any equipment that can change its position, to use the “MOVING” state when the equipment is in “movement” toward his set point.
Semantics of non-nominal states#
Although the developer is free to choose the device states, we must define a common error state for all the devices.
In general, any dysfunction is associated with the state Tango::FAULT.
The use of the Tango::ALARM state should be reserved for very special
cases where it is necessary to define an intermediate state between
normal operation and fault. Its use must be documented via Pogo in order
to define the semantics.
In the case of a problem occurring at initialization, it is recommended
to set the device state to FAULT.
For the init_device method, we recommend:
If the initialization method is long, thread it.
The device state INIT must be used only in the start-up of the device.
The device states changes when the init execution is over.
Semantics recommended for FAULT and ALARM states are as follows:
UNKNOWN(grey): communication problem with the equipment or the “sub”-devices which prevents the device to really know its real stateFAULT(red): A problem which prevents the normal functioning (including during the initialization). Getting out from a FAULT state is possible only by repairing the cause of the problem and/or executing a Reset command.ALARM(orange): the device is functional but one element is out of range (bad parameters but not preventing the functioning, limit switch of a motor). An attribute is out of range.
Managing state transitions#
It is import to consider how state transitions are handled as a mis-managed transition can cause misleading information to be transmitted to the client.
For example, consider the case of a motor system. The client can use a
poll (i.e. periodically read the state attribute of the motor) the motor
to get the motor state, e.g. STANDBY, MOVING, FAULT. This could lead
to inconsistent behaviour due to inappropriate management of the state.
A typical example is to launch an axis movement through the writing
of the position attribute. The motor should make the transition from
STANDBY to the MOVING state and the client will be expecting it
to be in the MOVING state.
However, this will only work if the device state is switched to MOVING
before the position write request returns. Otherwise, the client
could read back that the motor is still in the STANDBY state and
hence interpret that the move has ended even though it has not started.
This behaviour is illustrated in the figure below:
Example of State transitions#
Note
The state transitions and the “associated guarantees” must be documented. In the previous example, rereading the STANDBY state after performing any movement must ensure that the required movement is completed (and not that it has not yet been started!!).
State machine management#
Pogo or developer code#
Tango has a basic management of its state machine. Is_allowed methods
filter the external request depending on the current device state. The
developer must define the device behaviour (regarding its internal
state) via Pogo.
By default, any request (reading, writing, or command execution) is authorized whatever the current device state is.
The example below illustrates two ways for the state machine management of a device (here NITC01) in C++:
Managing the “On” command via Pogo
Managing the reading of the attribute “temperature” directly in the code
However, the Pogo implementation is “basic”. If, for example, the
execution of the On command on a power supply is prohibited when the
current state is Tango::ON, then the Tango layer, generated by
Pogo, will systematically trigger an exception to the client. From the
operator perspective, this may be a surprise.
In such a case, it is recommended to authorize the command but to ignore it.
Particular case : FAULT state#
The Tango::FAULT state shouldn’t prohibit everything. The
attributes and/or commands that are valid and/or allows the device to
get out of the Tango::FAULT state must remain accessible.
For example, in some cases, when a device used several elementary devices, its state is a combination of the elementary devices states. If one of them is in “FAULT”, we must be able to execute commands on others elementary devices, and, in all cases, have a command to get out of this state.
The transition to a FAULT state needs reflection and a clear
definition of the device management in this state and the output
conditions of this state.
Init and error acknowledgement#
A common mistake is to associate the generic command MyDevice::Init to
an acknowledgement mechanism for the current defect.
The execution of the Init command must be reserved to the device
re-initialization (hardware reconnection after a reboot or
reconfiguration following a property modification).
Any device that requires an acknowledgement mechanism must have a
dedicated command (like Reset or AcknowledgeError).
Other implementations#
You can also create a specific state machine, without using Tango types, in the interface class with the device. Thus, we use this state machine to determine the Tango state of the device. The aims here is to define an internal state machine (with a design pattern “state” for example) then do a mapping with the existing Tango states to determine the device state.
The developer also has the ability to override the State and Status
methods in order to centralize, in a unique method, the management of
the internal device state, which simplifies the update of this
fundamental information.
Logging management#
The importance of rigorous logging management#
The introduction of logging in the device code enables easy development, debugging and the user understanding of the device operations.
The device developer must always use the facilities offered by the
Tango Logging Service to produce “Runtime” messages, facilitating the
understanding of the device operations. Implementations classes can
inherit Tango::LogAdapter to redirect the logs to the common
service.
The rules to follow are:
Logs to the console are prohibited. The developer must use the logging stream proposed by Tango (there is a stream for every logging level, the levels being inclusive in the order specified below): DEBUG_STREAM, INFO_STREAM, WARN_STREAM, ERROR_STREAM, FATAL_STREAM
It is important to use the right level of logging: on a higher level than DEBUG, the device should be a little wordy. Beyond the INFO level, it should produce only critical logs.
Recommendations of use:
DEBUG_STREAM: developer information (route trace)INFO_STREAM: user information (measure, start/stop of a process)WARN_STREAM: warning (eg deprecated operation)ERROR_STREAM: general errorFATAL_STREAM: fatal error, shutdown
It is important to use these streams early in the development as they allow for easier debugging.
You shouldn’t have to modify the code to add traces. E.g. use a
debug_stream level for the input parameters, the display
of a conversion result, the return code from a DLL function…
It is also recommended to adopt a unified formalism for logs, for example:
“<class_name>::<method_name>() - <text trace with parameter (eventually)>”
Example of using different logs levels in C++:
It is also possible to redirect the stream to a file (via Jive). This can be useful in the case of “intermittent” bugs, for which a long log may be required.
Implementation#
It is not mandatory, but it is highly recommended to add an attribute named “log” in the device interface with a string spectrum type. This tracks all the internal activity of the device (as defined in Tango Logging).
In C++, the class
Yat4Tango::InnerAppenderimplements this functionality based on a dynamic attribute (no need to use Pogo).This system facilitates the recovery of errors and therefore the diagnosis of problems. Problem solving will therefore be faster and optimized.
This feature is particularly interesting for devices that manage automatic processes (for example, scanning) which involve other devices. The operator then has easy access through this “log” attribute to the behaviour and decisions taken by the device.
Example in C++ is shown below (look at the YAT documentation for further explanations):
In the header file of the device
Declaration of the service to use
In the source code of the device
init_devicemethod: initialization of the “innerAppender”
delete_devicemethod: deletion of the “innerAppender”
Error handling#
The importance of rigorous error handling#
Error handling is often overlooked but good error handling means easier debugging and maintenance. It is essential for good code quality. These concepts are present in the section Reporting errors.
Below are some typical cases to avoid:
A device doesn’t behave as expected but there is no indication why.
The device is in
FAULTstate but theStatus(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_exceptionmethod,If an error occurs it must be logged using the Tango Logging Service
The return code of a function is always analyzed,
The device
Statusis always coherent with theState,The error messages are understandable for the end user and that they are supplemented by logs (with the
ERRORlevel - use of theerror_streammacro). TheStatusis 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
FAULTstate.
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:
AxisMotionAccuracymust 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
FAULTand update theStatusto indicate the error nature. (The goal is to understand easily why the device failed to initialize properly, while still allowing the operator to adjust this or these problems)
Examples of error handling in C++:
If an error occurs, always log it
Always update State AND Status
Manage the return code for function that have one
Manage the exceptions for methods which can throw some
Details for an attribute#
Although Tango supports the notion of quality on an attribute value
(Tango::VALID, Tango::INVALID, …), only few clients use this
information to judge the validity of the data returned so it is best to not make
assumptions on the use of this to report an invalid value to the client. That is
to say that forcing the attribute quality to Tango::INVALID is necessary
but not sufficient.
For floating point values, it is possible to set the value to “NaN”, but there is no equivalent for an integer. To avoid the handling of special cases, it is recommended to throw an exception to indicate the data invalidity.
It is recommended to throw an exception for all invalid values, regardless of their type. There is, however, two exceptions to this rule: State and Status. For these two attributes, always return a value.
This solution has the disadvantage of showing a pop-up on the client side, but this is the most effective method to indicate that the attribute reading has failed.
Details for the properties#
Properties reading during device initialization#
As it stands, the code generated by Pogo doesn’t wrap the method
which ensures the reading of properties from the Tango database in a try/catch
block (see MyDevice::init_device). However, it may fail and cause
the generation of an exception. As mentioned above, the developer must
ensure that any exception thrown in the init_device method (or a
method called from it) is caught and not propagated.
In the case of a Tango exception during the properties reading, the developer should systematically:
detect the error (catch it).
log it with level
ERROR.set the device to the
FAULTstate.update the Status indicating the origin of the problem.
Example in C++ :
As a reminder, the default value for a property is defined with Pogo and
the value is stored in the database via the put_property() method.
Properties without default values#
Pogo allows a default value for a property not present in the Tango database to be defined.
For mandatory properties that have no default values, the developer should systematically:
detect the absence of the value in the database.
log the problem explicitly with the level
ERROR(indicate the missing property).set the device to the
FAULTstate.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#
The device server framework#
This chapter will present the TANGO device server framework. It will introduce what is the device server pattern and then it will describe a complete device server framework. A definition of classes used by the device server framework is given in this chapter. This manual is not intended to give the complete and detailed description of classes data member or methods, refer to cppTango API reference to get this full description. But first, the naming convention used in this project is detailed.
The aim of the class definition given in this chapter is only to help the reader to understand how a TANGO device server works. For a detailed description of these classes (and their methods), refer to chapter Writing a device server process or to cppTango API reference.
Naming convention and programming language#
TANGO fully supports three different programming languages which are C++, Java and Python. This documentation focuses on C++ Tango class. For Java and Python Tango class, have a look at the JTango API reference and PyTango API reference pages where similar chapter for Java and Python are available.
Every software project needs a naming convention. The naming convention adopted for the TDSOM is very simple and only defines two guidelines which are:
Class names start with uppercase and use capitalization for compound words (For instance MyClassName).
Method names are in lowercase and use underscores for compound words (For instance my_method_name).
The device pattern#
Device server are written using the Device pattern. The aim of this pattern is to provide the control programmer with a framework in which s/he can develop new control objects. The device pattern uses other design patterns like the Singleton and Command patterns. The device pattern class diagram for stepper motor device is drawn in figure 6.1.
Figure 6.1: Device pattern class diagram#
In this figure, only classes surrounded with a dash line square are device specific. All the other classes are part of the TDSOM core and are developed by the Tango system team. Different kind of classes are used by the device pattern.
Three of them are root classes and it is only necessary to inherit from them. These classes are the DeviceImpl, DeviceClass and Command classes.
Classes necessary to implement commands. The TDSOM supports two ways to create command : Using inheritance or using the template command model. It is possible to mix model within the same device pattern
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.
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 :
The TemplCommand class for command without input or output parameter
The TemplCommandIn class for command with input parameter but without output parameter
The TemplCommandOut class for command with output parameter but without input parameter
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 :
The TemplCommand class. One object of this class must be created for each command without input nor output parameters
The TemplCommandIn class. One object of this class must be created for each command without output parameter but with input parameter
The TemplCommandOut class. One object of this class must be created for each command without input parameter but with output parameter
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 :
The creation of the StepperMethodClass singleton via its init() method
The command_factory() method of the StepperMotorClass class
The attribute_factory() method of the StepperMotorClass class. This method has a default empty body for device class without attributes.
The device_factory() method of the StepperMotorClass class
This startup procedure is described in figure 6.2.
Figure 6.2: Device pattern startup sequence#
The creation of the StepperMotorClass will automatically create an instance of the DeviceClass class. The constructor of the DeviceClass class will create the Status, State and Init command objects and store them in its command list.
The command_factory() method will simply create all the user defined commands and add them in the command list.
The attribute_factory() method will simply build a list of device attribute names.
The device_factory() method will create each StepperMotor object and store them in the StepperMotorClass instance device list. The list of devices to be created and their names is passed to the device_factory method in its input argument. StepperMotor is a sub-class of DeviceImpl class. Therefore, when a StepperMotor object is created, a DeviceImpl object is also created. The DeviceImpl constructor builds all the device attribute object(s) from the attribute list built by the attribute_factory() method.
Command execution sequence#
The figure 6.3
Figure 6.3: Command execution timing#
described how the method implementing a command is executed when a command_inout CORBA operation is requested by a client. The command_inout method of the StepperMotor object (inherited from the DeviceImpl class) is triggered by an instance of a class generated by the CORBA IDL compiler. This method calls the command_handler() method of the StepperMotorClass object (inherited from the DeviceClass class). The command_handler method searches in its command list for the wanted command (using its name). If the command is found, the always_executed_hook method of the StepperMotor object is called. Then, the is_allowed method of the wanted command is executed. If the is_allowed method returns correctly, the execute method is executed. The execute method extracts the incoming data from the CORBA object use to transmit data over the network and calls the user written method which implements the command.
The automatically added commands#
In order to increase the common behavior of every kind of devices in a Tango control system, three commands are automatically added to each class of devices. These commands are :
State
Status
Init
The default behavior of the method called by the State command depends on the device state. If the device state is ON or ALARM, the method will :
read the attribute(s) with an alarm level defined
check if the read value is above/below the alarm level and eventually change the device state to ALARM.
returns the device state.
For all the other device state, the method simply returns the device state stored in the DeviceImpl class. Nevertheless, the method used to return this state (called dev_state) is defined as virtual and can be redefined in DeviceImpl sub-class. The difference between the default State command and the state CORBA attribute is the ability of the State command to signal an error to the caller by throwing an exception.
The default behavior of the method called by the Status command depends on the device state. If the device state is ON or ALARM, the method returns the device status stored in the DeviceImpl class plus additional message(s) for all the attributes which are in alarm condition. For all the other device state, the method simply returns the device status as it is stored in the DeviceImpl class. Nevertheless, the method used to return this status (called dev_status) is defined as virtual and can be redefined in DeviceImpl sub-class. The difference between the default Status command and the status CORBA attribute is the ability of the Status command to signal an error to the caller by throwing an exception.
The Init command is used to re-initialize a device without changing its network connection. This command calls the device delete_device method and the device init_device method. The rule of the delete_device method is to free memory allocated in the init_device method in order to avoid memory leak.
Reading/Writing attributes#
Reading attributes#
A Tango client is able to read Tango attribute(s) with the CORBA read_attributes call. Inside the device server, this call will trigger several methods of the device class (StepperMotor in our example) :
The always_executed_hook() method.
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.
For each attribute to be read
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)
A method called read_<att name>(). The aim of this method is to extract the real attribute value from the hardware read-out and to store the attribute value into the attribute object. It has one parameter which is a reference to the Attribute object to be read.
The figure 6.4 is a drawing of these method calls sequencing. For attribute always readable, a default is_allowed method is provided. This method always returns true.
Figure 6.4: Read attribute sequencing#
Writing attributes#
A Tango client is able to write Tango attribute(s) with the CORBA write_attributes call. Inside a device server, this call will trigger several methods of the device class (StepperMotor in our example)
The always_executed_hook() method.
For each attribute to be written
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)
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.
The write_attr_hardware() method. The rule of this method is to effectively write the hardware in case it is able to support writing several data in one go. If this is not the case, don’t code this method (a default implementation is coded in the Tango base class) and code the real hardware access in each write_<att name>() method.
The figure 6.5 is a drawing of these method calls sequencing. For attribute always writeable, a default is_allowed method is provided. This method always allways returns true.
Figure 6.5: Write attribute sequencing#
The device server framework#
Vocabulary#
A device server pattern implementation is embedded in a process called a device server. Several instances of the same device server process can be used in a Tango control system. To identify instances, a device server process is started with an instance name which is different for each instance. The device server name is the couple device server executable name/device server instance name. For instance, a device server started with the following command
$ Perkin id11
starts a device server process with an instance name id11, an executable name Perkin and a device server name Perkin/id11.
The DServer class#
In order to simplify device server process administration, a device of the DServer class is automatically added to each device server process. Thus, every device server process supports the same set of administration commands. The implementation of this DServer class follows the device pattern and therefore, its device behaves like any other devices. The device name is
dserver/device server executable name/device server instance name
For instance, for the device server process described in Vocabulary, the dserver device name is dserver/perkin/id11. This name is returned by the adm_name CORBA attribute available for every device. On top of the three automatically added commands, this device supports the following commands :
DevRestart
RestartServer
QueryClass
QueryDevice
Kill
AddLoggingTarget (C++ server only)
RemoveLoggingTarget (C++ server only)
GetLoggingTarget (C++ server only)
GetLoggingLevel (C++ server only)
SetLoggingLevel (C++ server only)
StopLogging (C++ server only)
StartLogging (C++ server only)
PolledDevice
DevPollStatus
AddObjPolling
RemObjPolling
UpdObjPollingPeriod
StartPolling
StopPolling
EventSubscriptionChange
ZmqEventSubscriptionChange
LockDevice
UnLockDevice
ReLockDevices
DevLockStatus
These commands will be fully described later in this document.
Several controlled object classes can be embedded within the same device server process and it is the rule of this device to create all these device server patterns and to call their command and device factories as described in Startup of a device pattern. The name and number of all the classes to be created is known to this device after the execution of a method called class_factory. It is the user responsibility to write this method.
The Tango::Util class#
Description#
This class merges a complete set of utilities in the same class. It is implemented as a singleton and there is only one instance of this class per device server process. It is mandatory to create this instance in order to run a device server. The description of all the methods implemented in this class can be found in cppTango API reference.
Contents#
Within this class, you can find :
Static method to create/retrieve the singleton object
Miscellaneous utility methods like getting the server output trace level, getting the CORBA ORB pointer, retrieving device server instance name, getting the server PID and more. Please, refer to cppTango API reference to get a complete list of all these utility methods.
Method to create the device pattern implementing the DServer class (server_init())
Method to start the server (server_run())
TANGO database related methods
A complete device server#
Within a complete device server, at least two implementations of the device server pattern are created (one for the dserver object and the other for the class of devices to control). On top of that, one instance of the Tango::Util class must also be created.
Figure 6.6: A complete device server#
A drawing of a complete device server is in figure 6.6.
Device server startup sequence#
The device server startup sequence is the following :
Create an instance of the Tango::Util class. This will initialize the CORBA Object Request Broker
Called the server_init method of the Tango::Util instance The call to this method will :
Create the DServerClass object of the device pattern implementing the DServer class. This will create the dserver object which during its construction will :
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.
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.
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 :
Data type using basic types (Tango::DevBoolean, Tango::DevShort, Tango::DevEnum, Tango::DevLong, Tango::DevFloat, Tango::DevDouble, Tango::DevUshort and Tango::DevULong)
Data type using strings (Tango::DevString type)
Data types using sequences (Tango::DevVarxxxArray types except Tango::DevVarLongStringArray and Tango::DevVarDoubleStringArray)
Data types using structures (Tango::DevVarLongStringArray and Tango::DevVarDoubleStringArray types)
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) :
Four constructors.
A default constructor which creates an empty sequence.
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
A sophisticated constructor where it is possible to assign the memory used by the sequence with a preallocated buffer.
A copy constructor which does a deep copy
An assignment operator which does a deep copy
A length accessor which simply returns the current number of elements in the sequence
A length modifier which changes the length of the sequence (which is different than the number of elements in the sequence)
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 :
CORBA system error. These exceptions are raised by the ORB and indicates major failures (A communication failure, An invalid object reference…)
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 :
A string describing the type of the error. This string replaces an error code and allows a more easy management of include files.
The error severity. It is an enumeration with the three values which are WARN, ERR or PANIC.
A string describing in plain text the reason of the error
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 :
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:
The command name
The command input type code
The command output type code
The command input parameter description
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:
The attribute name
The attribute data type code
The attribute writable type code
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
and the Attribute object will not free this memory. Data members called attr_<Attribute_name>_read are foreseen for this usage. The
read_attr_hardware()
method receives a vector of long which are indexes into the main attributes vector of the attributes to be read. The
read_Position()
method receives a reference to the Attribute object. The third method (
is_Position_allowed()
) aim is to allow or dis-allow, the attribute reading. In some cases, some attributes can be read only if some conditions are met. If this method returns true, the
read_<Attribute_name>()
method will be called. Otherwise, an error will be generated for the attribute. This method receives one argument which is an emumeration describing the attribute request type (read or write). In our example, the reading of the Position attribute is allowed only if the device state is ON.
1 void StepperMotor::read_attr_hardware(vector<long> &attr_list)
2 {
3 INFO_STREAM << "In read_attr_hardware for " << attr_list.size();
4 INFO_STREAM << " attribute(s)" << endl;
5
6 for (long i = 0;i < attr_list.size();i++)
7 {
8 string attr_name;
9 attr_name = dev_attr->get_attr_by_ind(attr_list[i]).get_name();
10
11 if (attr_name == "Position")
12 {
13 attr_Position_read = &(position[0]);
14 }
15 else if (attr_name == "Direction")
16 {
17 attr_Direction_read = &(direction[0]);
18 }
19 }
20 }
21
22 void read_Position(Tango::Attribute &att)
23 {
24 att.set_value(attr_Position_read);
25 }
26
27 bool is_Position_allowed(Tango::AttReqType req)
28 {
29 if (req == Tango::WRITE_REQ)
30 return false;
31 else
32 {
33 if (get_state() == Tango::ON)
34 return true;
35 else
36 return false;
37 }
38 }
Line 6 : A loop on each attribute to be read
Line 9 : Get attribute name
Line 11 : Test on attribute name
Line 13 : Read hardware (pretty simple in our case)
Line 24 : Set attribute value in Attribute object using the set_value() method. This method will also initializes the attribute quality factor to Tango::ATTR_VALID if no alarm level are defined and will set the attribute returned date. It is also possible to use a method called set_value_date_quality() which allows the user to set the attribute quality factor as well as the attribute date.
Line 33 : Test on device state
The methods used for the SetPosition attribute#
To enable writing of attributes, the StepperMotor class must re-define one or two methods called write_<Attribute_name>() and if necessary a method called is_<Attribute_name>_allowed(). The aim of the first one is to write the hardware. The write_Position() method receives a reference to the WAttribute object. The value to write is in this WAttribute object. The third method (is_Position_allowed()) aim is to allow or dis-allow, the attribute writing. In some cases, some attributes can be write only if some conditions are met. If this method returns true, the write_<Attribute_name>() method will be called. Otherwise, an error will be generated for the attribute. This method receives one argument which is an emumeration describing the attribute request type (read or write). For read/write attribute, this method is the same for reading and writing. The input argument value makes the difference.
For our example, it is always possible to write the SetPosition attribute. Therefore, the StepperMotor class only defines a write_SetPosition() method.
1 void StepperMotor::write_SetPosition(Tango::WAttribute &att)
2 {
3 att.get_write_value(sttr_SetPosition_write);
4
5 DEBUG_STREAM << "Attribute SetPosition value = ";
6 DEBUG_STREAM << attr_SetPosition_write << endl;
7
8 position[0] = attr_SetPosition_write;
9 }
10
11 void StepperMotor::write_attr_hardware(vector<long> &attr_list)
12 {
13
14 }
Line 3 : Retrieve new attribute value
Line 5-6 : Send some messages using Tango Logging system
Line 8 : Set the hardware (pretty simple in our case)
Line 11 - 14: The write_attr_hardware() method.
In our case, we don’t have to do anything in the
write_attr_hardware()
method. It is coded here just for educational purpose. When its not needed, this method has a default implementation in the Tango base class and it is not mandatory to declare and defin it in your own Tango class
Retrieving device properties#
Retrieving properties is fairly simple with the use of the database object. Each Tango device is an aggregate with a DbDevice object (see figure 6.1). This has been grouped in a method called get_device_properties(). The classes and methods of the Dbxxx objects are described in the Tango API documentation.
1 void DocDs::get_device_property()
2 {
3 Tango::DbData data;
4 data.push_back(DbDatum("Max"));
5 data.push_back(DbDatum("Min"));
6
7 get_db_device()->get_property(data);
8
9 if (data[0].is_empty()==false)
10 data[0] >> max;
11 if (data[1].is_empty()==false)
12 data[1] >> min;
13 }
Line 4-5 : Two DbDatum (one per property) are stored into a DbData object
Line 7 : Call the database to retrieve properties value
Line 9-10 : If the Max property is defined in the database, extract its value from the DbDatum object and store it in a device data member
Line 11-12 : If the Min property is defined in the database, extract its value from the DbDatum object and store it in a device data member
The remaining methods#
The remaining methods are the dev_state, dev_status, always_executed_hook, dev_read_position and read_Direction() methods. The dev_state method parameters are fixed. It does not receive any input parameter and must return a Tango_DevState data type. The dev_status parameters are also fixed. It does not receive any input parameter and must return a Tango string. The always_executed_hook receives nothing and return nothing. The dev_read_position method input parameter is the motor number as a long and the returned parameter is the motor position also as a long data type. The read_Direction() method is the method for reading the Direction attribute.
1 DevLong StepperMotor::dev_read_position(DevLong axis)
2 {
3
4 if (axis < 0 || axis > AGSM_MAX_MOTORS)
5 {
6 WARNING_STREAM << "Steppermotor::dev_read_position(): axis out of range !";
7 WARNING_STREAM << endl;
8
9 TangoSys_OMemStream o;
10
11 o << "Axis number " << axis << " out of range" << ends;
12 throw_exception("StepperMotor_OutOfRange",
13 o.str(),
14 "StepperMotor::dev_read_position");
15 }
16
17 return position[axis];
18 }
19
20 void always_executed_hook()
21 {
22 INFO_STREAM << "In the always_executed_hook method << endl;
23 }
24
25 Tango_DevState StepperMotor::dev_state()
26 {
27 INFO_STREAM << "In StepperMotor state command" << endl;
28 return DeviceImpl::dev_state();
29 }
30
31 Tango_DevString StepperMotor::dev_status()
32 {
33 INFO_STREAM << "In StepperMotor status command" << endl;
34 return DeviceImpl::dev_status();
35 }
36
37 void read_Direction(Tango::Attribute att)
38 {
39 att.set_value(attr_Direction_read);
40 }
Line 1-18 : The dev_read_position method
Line 6-14 : Throw exception to client if the received axis number is out of range
Line 9 : A TangoSys_OMemStream is used as stream. The TangoSys_OMemStream has been defined in improve portability across platform. For Unix like operating system, it is a ostrtream type. For operating system with a full implementation of the standard library, it is a ostringstream type.
Line 20-23 : The always_executed_hook method. It does nothing. It has been included here only as pedagogic usage.
Line 25-29 : The dev_state method. It does exactly what the default dev_state does. It has been included here only as pedagogic usage
Line 31-35 : The dev_status method. It does exactly what the default dev_status does. It has been included here only as pedagogic usage
Line 37-40 : The read_Direction method. Simply set the Attribute object internal value
Device server under Windows#
Two kind of programs are available under Windows. These kinds of programs are called console application or Windows application. A console application is started from a MS-DOS window and is very similar to classical UNIX program. A Windows application is most of the time not started from a MS-DOS window and is generally a graphical application without standard input/output. Writing a device server in a console application is straight forward following the rules described in the previous sub-chapters. Writing a device server in a Windows application needs some changes detailed in the following sub-chapters.
The Tango device server graphical interface#
Within the Windows operating system, most of the running application has a window user interface. This is also true for the Windows Tango device server. Using or not this interface is up to the device server programmer. The choice is done with an argument to the server_init() method of the Tango::Util class. This interface is pretty simple and is based on three windows which are :
The device server main window
The device server console window
The device server help window
The device server main window#
This window looks like :
Figure 6.7: Tango device server main window#
Four menus are available in this window. The File menu allows the user to exit the device server. The View menu allows you to display/hide the device server console window. The Debug menu allows the user to change the server output verbose level. All the outputs goes to the console window even if it is hidden. The Help menu displays the help window. The device server name is displayed in the window title. The text displayed at the bottom of the window has a default value (the one displayed in this window dump) but may be changed by the device server programmer using the set_main_window_text() method of the Tango::Util class. If used, this method must be called prior to the call of the server_init() method. Refer to cppTango API reference for a complete description of this method.
The console window#
This window looks like :
It simply displays all the logging** message when a console target is used in the device server.
The help window#
This window looks like :
This window displays
The device server name
The Tango library release
The Tango IDL definition release
The device server release. The device server programmer may set this release number using the set_server_version() method of the Tango::Util class. If used, this must be done prior to the call of the server_init() method. If the set_server_version() method is not used, x.y is displays as version number. Refer to cppTango API reference for a complete description of this method.
MFC device server#
There is no main function within a classical MFC program. Most of the time, your application is represented by one instance of a C++ class which inherits from the MFC CWinApp class. This CWinApp class has several methods that you may overload in your application class. For a device server to run correctly, you must overload two methods of the CWinApp class. These methods are the InitInstance() and ExitInstance() methods. The rule of these methods is obvious following their names.
Remember that if the Tango device server graphical user interface is used, you must link your device server with the Tango windows resource file. This is done by adding the Tango resource file to the Project Settings/Link/Input/Object, library modules window in VC++.
The InitInstance method#
The code to be added here is the equivalent of the code written in a classical main() function. Don’t forget to add the tango.h file in the list of included files.
1 BOOL FluidsApp::InitInstance()
2 {
3 AfxEnableControlContainer();
4
5 // Standard initialization
6 // If you are not using these features and wish to reduce the size
7 // of your final executable, you should remove from the following
8 // the specific initialization routines you do not need.
9
10 #ifdef _AFXDLL
11 Enable3dControls(); // Call this when using MFC in a shared DLL
12 #else
13 Enable3dControlsStatic(); // Call this when linking to MFC statically
14 #endif
15 Tango::Util *tg;
16 try
17 {
18
19 tg = Tango::Util::init(m_hInstance,m_nCmdShow);
20
21 tg->server_init(true);
22
23 tg->server_run();
24
25 }
26 catch (bad_alloc)
27 {
28 MessageBox((HWND)NULL,"Memory error","Command line",MB_ICONSTOP);
29 return(FALSE);
30 }
31 catch (Tango::DevFailed &e)
32 {
33 MessageBox((HWND)NULL,,e.errors[0].desc.in(),"Command line",MB_ICONSTOP);
34 return(FALSE);
35 }
36 catch (CORBA::Exception &)
37 {
38 MessageBox((HWND)NULL,"Exception CORBA","Command line",MB_ICONSTOP);
39 return(FALSE);
40 }
41
42 m_pMainWnd = new CWnd;
43 m_pMainWnd->Attach(tg->get_ds_main_window());
44
45 return TRUE;
46 }
Line 19 : Initialise Tango system. This method also analises the argument used in command line.
Line 21 : Create Tango classes requesting the Tango Windows graphical interface to be used
Line 23 : Start Network listener. Note that under NT, this call returns in the contrary of UNIX like operating system.
Line 26-30 : Display a message box in case of memory allocation error and leave method with a return value set to false in order to stop the process
Line 31-35 : Display a message box in case of error during server initialization phase.
Line 36-40 : Display a message box in case of error other than memory allocation. Leave method with a return value set to false in order to stop the process.
Line 37-38 : Create a MFC main window and attach the Tango graphical interface main window to this MFC window.
The ExitInstance method#
This method is called when the application is stopped. For Tango device server, its rule is to destroy the Tango::Util singleton if this one has been correctly constructed.
1 int FluidsApp::ExitInstance()
2 {
3 bool del = true;
4
5 try
6 {
7 Tango::Util *tg = Tango::Util::instance();
8 }
9 catch(Tango::DevFailed)
10 {
11 del = false;
12 }
13
14 if (del == true)
15 delete (Tango::Util::instance());
16
17 return CWinApp::ExitInstance();
18 }
Line 7 : Try to retrieve the Tango::Util singleton. If this one has not been constructed correctly, this call will throw an exception.
Line 9-12 : Catch the exception in case of incomplete Tango::Util singleton construction
Line 14-15 : Delete the Tango::Util singleton. This will unregister the Tango device server from the Tango database.
Line 17 : Execute the ExitInstance method of the CWinApp class.
If you don’t want to use the Tango device server graphical interface, do not pass any parameter to the server_init() method and instead of the code display in lines 37 and 38 in the previous example of the InitInstance() method, use your own code to initialize your own application.
Example of how to build a Windows device server MFC based#
This sub-chapter gives an example of what it is needed to do to build a MFC Windows device server. Rather than being a list of actions to strictly follow, this is some general rules of how using VC++ to build a Tango device server using MFC.
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.
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.
Copy the five files generated by Pogo to the Windows computer and add them to your project
Remove the dialog window files (xxxDlg.cpp and xxxDlg.h), the Resource include file and the resource script file from your project
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.
Enable RTTI in your project settings (see chapter [Compiling NT])
Change your application class:
Add the definition of an ExitInstance method in the declaration file. (xxx.h file)
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)
Replace the InitInstance() method as described in previous sub-chapter. (xx.cpp file)
Add an ExitInstance() method as described in previous sub-chapter (xxx.cpp file)
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
Write a class which inherits from a pre-written Tango class called NTService. This class must have a start method.
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:
The always_executed_hook() method.
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)
A method called read_<pipe_name>(). The aim of this method is to store the pipe data in the pipe object. It has one parameter which is a reference to the Pipe object to be read.
The figure 6.8 is a drawing of these method calls sequencing for our class StepperMotor with one pipe named DynData.
Figure 6.8: Read pipe sequencing#
The class DynDataPipe is a simple class which follow the same skeleton from one Tango class to another. Therefore, this class is generated by the Tango code generator Pogo and the Tango class developper does not have to modify it. The method is_DynData_allowed() is relatively simple and in most cases the default code generated by Pogo is enough. The method read_DynData() is the method on which the Tango class developper has to concentrate on. The following code is one example of these two methods.
1 bool StepperMotor::is_DynData_allowed(Tango::PipeReqType req)
2 {
3 if (get_state() == Tango::ON)
4 return true;
5 else
6 return false;
7 }
8
9 void StepperMotor::read_DynData(Tango::Pipe &pipe)
10 {
11 nb_call++;
12 if (nb_call % 2 == 0)
13 {
14 pipe.set_root_blob_name("BlobCaseEven");
15
16 vector<string> de_names {"EvenFirstDE","EvenSecondDE"};
17 pipe.set_data_elt_names(de_names);
18
19 dl = 666;
20 v_db.clear();
21 v_db.push_back(1.11);
22 v_db.push_back(2.22);
23
24 pipe << dl << v_db;
25 }
26 else
27 {
28 pipe.set_root_blob_name("BlobCaseOdd");
29
30 vector<string> de_names {"OddFirstDE"};
31 pipe.set_data_elt_names(de_names);
32
33 v_str.clear();
34 v_str.push_back("Hola");
35 v_str.push_back("Salut");
36 v_str.push_back("Hi");
37
38 pipe << v_str;
39 }
40 }
The is_DynData_allowed method is defined between lines 1 and 7. It is allowed to read or write the pipe only is the device state is ON. Note that the input parameter req is not used. The parameter allows the user to know the type of request. The data type PipeReqType is one enumeration with two possible values which are READ_REQ and WRITE_REQ.
The read_DynData method is defined between lines 9 and 40. If the number of times this method has been called is even, the pipe contains two data elements. The first one is named EvenFirstDE and its data is a long. The second one is named EvenSecondDE and its data is an array of double. If the number of call is odd, the pipe contains only one data element. Its name is OddFirstDe and its data is an array of strings. Data are inserted into the pipe at lines 24 and 38. The variables nb_call, dl, v_db and v_str are device data member and therefore declare in the .h file. Refer to pipe section in chapter 3 and to the API reference documentation (in Tango WEB pages) to learn more on how you can insert data into a pipe and to know how data are organized within a pipe.
Client writing a pipe#
When a client writes a pipe, the following methods are executed in the Tango class:
The always_executed_hook() method.
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)
A method called write_<pipe_name>(). It has one parameter which is a reference to the WPipe object to be written. The aim of this method is to get the data to be written from the WPipe oject and to write them into the corresponding Tango class objects.
The figure 6.9 is a drawing of these method calls sequencing for our class StepperMotor with one pipe named DynData.
Figure 6.9: Write pipe sequencing#
The class DynDataPipe is a simple class which follow the same skeleton from one Tango class to another. Therefore, this class is generated by the Tango code generator Pogo and the Tango class developper does not have to modify it. The method is_DynData_allowed() is relatively simple and in most cases the default code generated by Pogo is enough. The method write_DynData() is the method on which the Tango class developper has to concentrate on. The following code is one example of the write_DynData() method.
1 void StepperMotor::write_DynData(Tango::WPipe &w_pipe)
2 {
3 string str;
4 vector<float> v_fl;
5
6 w_pipe >> str >> v_fl;
7 .....
8 }
In this example, we know that the pipe will always contain a srting followed by one array of float. On top of that, we are not niterested by the
data element names. Data are extracted from the pipe at line 6 and are available for further use starting at line 7. If the content of the pipe is not a string followed by one array of float, the data extraction line (6) will throw one exception which will be reported to the client who has tried to write the pipe. Refer to pipe section in chapter 3 and to the API reference documentation (in Tango WEB pages) to learn more on how you can insert data into a pipe and to know how data are organized within a pipe.
The default is_allowed method behavior is to always allows the command
URL stands for Uniform Resource Locator
The StepperMotor class inherits from the DeviceImpl class and therefore is a DeviceImpl
The StepperMotorClass inherits from the DeviceClass and therefore is a DeviceClass
It can also be data declared as object data members or memory declared as static
How-Tos#
This section contains a set of useful ‘How tos’ with instructions on how to perform some common tasks with Tango.
Getting started
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
The Debugging and Testing section provides instructions on how to use the Tango Docker containers to test newly developed Tango device servers.
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#
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#
Most Tango packages are available on conda-forge for Linux, macOS and Windows.
Conda packages can be used in production or for development on your machine.
Note
Running the Tango Databaseds requires a mariadb database.
The preferred method to install mariadb is to use your OS package manager. This will come with systemd integration on Linux. For local development, docker can be used.
It is not recommended to use conda for mariadb itself (there is currently no mariadb-server on conda-forge but there is a mysql-server).
You can then run Databaseds by installing the tango-database conda package.
Tip
For local development, you can run PyDatabaseds instead by installing pytango-db, a pure Python implementation of the Tango Database
relying on sqlite. No mariadb needed. This isn’t recommended for production at this point!
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#
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.
tango-admin is not available on Windows
astor on conda-forge is taken by a Python package
Debian#
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.
Installing the packages
sudo apt install libtango-dev tango-db tango-test
You can use the default answers when asked questions during installation.
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.
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.listand adding the line:
deb http://deb.debian.org/debian bookworm-backports mainUpdate the package sources
sudo apt updateInstall 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
Start mariadb:
sudo service mariadb startSet 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>');"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-*
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 allInstall:
sudo cmake --install .
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 Distributionfile. You can usewgetto get the version you require:wget https://gitlab.com/api/v4/projects/24125890/packages/generic/TangoSourceDistribution/X.X.X/tango-X.X.X.tar.gzUnpack:
tar xzvf tango-*.tar.gz cd tango-X.X.X
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=OFFto the above command.Further CMake compilations flags are described in CMake options.
Compile
cmake --build build --parallel $(nproc)where
$(nproc)is the number of processes to use, for example2.Install:
sudo cmake --build build --target installAdd 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>
Start tango-controls database server:
sudo /usr/local/bin/tango startSet the TANGO_HOST variable in
/etc/tangorc:sudo nano /etc/tangorcand add
TANGO_HOST=127.0.0.1:10000
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
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#
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:
Add the EPEL repository:
sudo dnf install -y epel-releaseAdd 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 makecacheInstall and start MariaDB:
sudo dnf install -y mariadb-server mariadb sudo systemctl start mariadb sudo systemctl enable mariadb
Run mysql_secure_installation script:
sudo mysql_secure_installationInstall the tango Databaseds
sudo dnf install -y tango-dbCreate TANGO database:
cd /usr/share/tango-db/ sudo ./create_db.sh
Set up TANGO environment:
Note
You should not use
localhostas your TANGO_HOST. You can set the machine hostname using sudo hostnamectl set-hostname tangoboxsudo nano /etc/tangorcFor example:
TANGO_HOST=tangobox:10000Set up environment variables:
sudo nano /etc/profile.d/tango.shFor example:
. /etc/tangorc export TANGO_HOST
Start and enable TANGO database:
sudo systemctl start tango-db sudo systemctl enable tango-db
Install Starter and TangoTest:
sudo dnf install -y tango-starter tango-testStart and enable Starter:
sudo systemctl start tango-starter sudo systemctl enable tango-starter
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#
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, clickSystem.Click the
Advanced System Settingslink in the left column.In the System Properties window, click on the
Advancedtab, then click theEnvironment Variablesbutton near the bottom of that tab.In the
Environment Variableswindow click theNewbutton.In the field
NamewriteTANGO_HOST.In the field
Valuewrite 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 LineInvoke command:
%TANGO_ROOT%\\bin\\dbconfig.exeNote
This lets you set up two environment variables
MYSQL_USERandMYSQL_PASSWORDused 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 userootcredentials provided upon MariaDB installation if it is your development workstation. For production environment it is suggested to create an additional user withDB Adminprivileges. On Windows you may useMariaDB InstallerfromStartmenu and select the optionReconfigurefor MariaDB Server. Please refer to: https://mariadb.com/kb/en/create-user/Populate database with an initial Tango configuration:
Open
Command LineAdd 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
DataBasedsdevice 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
DataBasedsrunning permanently. You may either add the command above toAutostartor run it as a service.
Make
DataBasedsrun as a serviceNote
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 LineasAdministrator.Change current path to where the
nssmis unpacked or copied, eg.cd "%TANGO_ROOT%\\bin".Invoke
nssm.exe install Tango-DataBaseds. This will open a window where you can define service parameters.In the Application tab provide information as follows (adjust if your installation path is different).
In the Environment tab provide variables with credentials used for accessing the MariaDB, like:
Click
Install Service.Invoke
nssm.exe start Tango-DataBasedsto start the service.Test if everything is ok. Use
Startmenu 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 Serversexecutable:
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 beC:\\DeviceServers\\binwithmkdir c:\\DeviceServers\\binChange to the Tango bin directory with command (
cd "%TANGO_ROOT%\\bin")Copy
TangoTestdevice serverto the newly crated folder:copy TangoTest.exe c:\\DeviceServers\\binAdd entry about the Starter device server you will start on your computer:
Start a tool called
Astor. You may use either WindowsStartmenu or calltango-astor.batIn
Astorwindow select menu&Command --> Add a New HostIn the form that appears provide your
Host nameandDevice Servers PATH.
Accept with
CreateGo back to
Command Line
- Install Starter service:
Invoke
nssm.exe install Tango-Starter.In the Application tab provide information as follows:
Adjust if your installation path is different. In
Argumentsexchangepg-dell-newwith the proper name of your host.In the Environment tab provide TANGO_HOST variable, like:
Click
Install serviceStart the service:
nssm.exe start Tango-Starter.Go back to
Astor.After a while you will see a green led next to your host name:
Run
TangoTestdevice server:You may test the configuration by starting prefigured TangoTest device.
Start
Astorif it is not running.
Double Click on your computer name to open
Control Panel. It opens a window as below:
Click
Start new.In the open window select :
Click
Start Server.In the open window select
Controlled by Astro -> Yes, andStartup Level -> Level 1.
When you click
OKit should start the server. After a while you should see:
- Running your
Device Servers <device server>: You need to copy an executable to the folder configured for
Starter. In our example it isC:\\DeviceServers\\bin.Then use
Astor. After openingControl panelfor your computer (double clicking on a label) and selectionStart New…Select
Create New Serverand follow a wizard.
- Running your
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#
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#
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#
The purpose of the TANGO Box Virtual Machine is to give you a fast, out-of-the-box experience of a working TANGO system. There is a set of shortcuts to all essential TANGO tools on the virtual machine desktop. Together with the user documentation, that allow you to experience the power and elegance of a fully configured TANGO system with all the latest tools first hand. After this “guided tour” of the TANGO system, TANGO Box is an excellent tool to make further explorations on your own, to use it for demonstration purposes, to make studies, proof-of-concepts and even production ready systems.
These images can also be used on cloud providers by converting them to the appropriate format.
TangoBox 10.3#
You may download TangoBox 10.3 from here.
There is currently no up-to-date documentation.
TangoBox 9.3#
You may download TangoBox 9.3 from here.
Please read Tango Controls demo VM’s documentation.
See also a release note and README.
Minimum Requirements#
2vCPU
2GB (preferred 4GB) RAM
30GB of disk space
TangoBox 9.3#
TangoBox is a VM image running Tango Controls system and its various tools. It is intended to be used for demonstration and training. It also simulates distributed deployment by using Docker.
Download#
The latest version of the TangoBox 9.3 can be downloaded from here.
What is installed#
Below there is list of provide packages/features. Please note that some of them are installed as docker container and maybe switched off (stopped) and requires to be switched on for being explored, see Switching containers on and off
Tango 9.3.3
Tango Access Control
PyTango 9.3.3
Taurus 4.5.1
QTango
ITango
Docker
Linac system simulation (as docker container tangobox-sim)
HDB/TDB, SNAP DS (as docker container tangobox-archiving)
HDB++ (as docker container tangobox-hdbpp)
JupyTango (as docker container tangobox-jupytango)
SerialLine, Modbus and PyPLC device server (as docker container tangobox-com)
mTango + restAPI , Tango WebApp (as docker container tangobox-web)
E-giga (as docker container tangobox-egiga)
PyCharm
First steps#
First of all you have to download latest release of VirtualBox. It can be downloaded from www.virtualbox.org . Simply install it and start the program.
TangoBox is released as an .ova package so it can be easily imported.
Select import and choose downloaded TangoBox file
If you want, you can change VM’s configuration (i.e graphics, RAM). It is highly recommended to increase default RAM size
A virtual machine settings window.#
You may change number of CPUs and increase RAM size. Memory size has major impact on VM performance.
Wait for VirtualBox to import machine
After importing the VM image to VirtualBox you may start it.
Username is:
tango-csPassword is:
tango
tango-cs user has sudo privileges, so he may invoke commands as superuser with command sudo.
You may explore the Tango Controls feature by clicking related shortcuts on the Desktop.
Note
Plese note that some shortcuts are related to features running on containers. Please start related container first. See the following section.
Switching containers on and off#
Some of the features of Tango are provided inside pre-build docker containers. These can be switched on and off by starting or stopping related containers. Containers behave similar to virtual machines with they own network cards and operating system stack, however, lacking full separations.
To start a container, open terminal and invoke docker start {container-name}. For example, to star a linac simulation use the following statement:
docker start tangobox-sim
To stop a container, open terminal and invoke docker stop {container-name}. For example, to stop a linac simulation use the following statement:
docker stop tangobox-sim
To see which containers are running please, call docker ps
Deployment structure#
Network#
Containers are created withing their own subnet: 172.18.0.0/16. The network is called tango_nw
The subnet was created with the following docker command:
docker network create --driver=bridge --subnet=172.18.0.0/16 --opt com.docker.network.bridge.enable_icc=true \
--opt com.docker.network.bridge.host_binding_ipv4="0.0.0.0" --opt com.docker.network.bridge.mtu=1500 \
--opt com.docker.network.bridge.enable_ip_masquerade=true tango_nw
Containers are assigned static IPs. List of the IPs assignment maybe seen in /etc/hosts. Use command
cat /etc/hosts to see its contents.
Containers and images dependency#
Each container is based on its image. All images are already build but, if neccessary, Dockerfiles are stored in home/Dockerfiles
directory. Below is the list of all containers and corresponding images:
Container |
Image |
Remarks |
|---|---|---|
tangobox-com |
registry/tangobox-com |
- |
tangobox-sim |
registry/tangobox-sim |
- |
tangobox-archiving |
registry/tangobox-archive |
- |
tangobox-hdbpp |
registry/tangobox-hdbpp |
- |
tangobox-web |
registry/tangobox-web |
- |
tangobox-egiga |
registry/tangobox-egiga |
- |
tangobox-jupytango |
registry/tangobox-jupytango |
- |
- |
registry/tangobox-base |
Base container |
- |
ubuntu |
Ubuntu image to build others |
Currently images are stored in registry.gitlab.com/s2innovation/tangobox-docker registry.
Some device servers may be stopped when launching containers. It is so to get better performance (high cpu and ram usage). To control and start/stop particular DS according to your needs, use Tango Manager (Astor) to it.
Example applications#
Modbus simulation#
To simulate Modbus, we suggest to use ModbusPal. To do so, use ModbusPal (simulation) desktop shortcut. Once it is started, declare new modbus slave by clicking ADD. Choose 1 and name it 10.0.2.15. Now click “eye” button in modbus slaves section and add at least 10 holding registers. You can change their values according to your needs.
After that, use RUN button to start simulation. Values in registers can be changed by clicking “eye” button in Modbus slaves section.
Keep in mind that in ModbusPal, registers are counted from 1 while on DS from 0!
To monitor changes, use ATKPanel started from Jive. Both ModbusComposer and PyPLC have two attributes configured:
ModbusComposer: Temperature uses 4th; Pressure uses 5th register in ModbusPal
PyPLC: Voltage uses 6th; Flow uses 7th register in ModbusPal
View on a ModbusComposer device and configured ModbusPal simulator.#
JupyTango#
JupyTango is a Jupyter featuring Tango related kernels. With JupyterLab you may interact and do scripting for Tango through a web browser.
Browser window with JupyTango in action#
In case you want to try it, here’s the procedure:
start jupyterlab (it is started by default):
docker start tangobox-jupytangoopen a new browser window and go to
http://tangobox-jupytango:8888/labenjoy!
Here are the JupyTango additions to itango:
Plotting a tango attribute
Syntax:
pta [options] <tab for device selection> + <tab for attribute selection>
Supported options:
-wor--width: plot width in pixels-hor--height: plot height in pixels
Monitoring a tango attribute: Syntax:
tm [options] + <tab for device selection> + <tab for attribute selection>
Supported options:
-wor--width: plot width in pixels-hor--height: plot height in pixels-por--period: plot refresh period in [0.1, 5] seconds - defaults to 1s-dor--depth: scalar attribute history depth in [1, 3600] seconds - defaults to 900s
You can try to kill the monitored device will the JupyTango monitor is running to see how errors are handled.
JLinac simulation#
To start simulation, you need to run tangobox-sim container (use docker start tangobox-sim to start it
from a terminal). It is also important to make sure that all related device servers are running.
The easiest way to do it is to check it in Astor - a bulb next to tangobox-sim should be green.
JLinac simulation running.#
HDB/TDB/SNAP Archiving (Mambo, Bensikin)#
Prior to use HDB/TDB (Mambo) or SNAP (Bensikin) you need to make sure that
the tangobox-archiving container and related device servers are running:
Call docker start tangobox-archive on a terminal.
Start Astor and check if the
tangobox-archivenode is green.
Then, you may start Mambo or Bensikin by clicking icons on the desktop.
Screen of running Mambo#
Please take note of a green bulb of the tangobox-archive node in the Astor window.
HDB++ Archiving#
To use HDB++ and its tools ( HDB Configurator and HDB Viewer) please make sure that
the tangobox-hdbpp container and related device servers are running:
Call docker start tangobox-hdbpp on a terminal.
Start Astor and check if the
tangobox-hdbppnode is green.
Then, you may start HDB Configurator or HDB Viewer by clicking icons on the desktop.
HDB Configurator and HDB Viewer#
E-giga#
E-giga is a web application for archiving data visualization (HDB/TDB and HDB++). The TangoBox deployment uses HDB/TDB.
E-giga in a web browser window#
To use e-giga following conditions must be fulfilled:
tangobox-archiveandtangobox-webcontainers must be started and archiving device servers must be runninguse i.e. Mambo to enable data archiving for HDB database. It is required. If you do not see any attributes in
E-gigait is probably due to archiving being disabled. Check with Astor if the tangobox-archive LED is green and with Mambo if there are any attributes configured to be archivied.
To open browser with E-giga click on the relate desktop icon.
Tango WebApp#
Tango may be available through a web browser. Tango WebApp is a general purpose Tango web application. You may try it
on the TangoBox.
A screenshot of Tango WebApp in a browser#
To play with Tango WebApp make sure that the ‘tangobox-web` container is running
(use docker start tangobox-web to start it from a terminal). Then, you may open a browser with a related
desktop icon. Use username tango-cs and password tango to log-in.
REST API#
Tango Controls specifies REST API interface and provides its reference implementation. For details see REST API documentation
The TangoBox comes with REST API installed.
Related desktop icon opens a web browser pointing to REST API interface. The REST server requires authentication.
User is tango-cs and password is tango.
A web browser window presenting JSON response of the Tango REST server#
If you would like to play with it with other tools (Python, curl) it is avaialabe at the following
address: http://localhost:10001/tango/rest/rc4/hosts/tangobox/10000.
Sardana#
Sardana is a software suite for Supervision, Control and Data Acquisition in scientific installations. I t aims to reduce cost and time of design, development and support of the control and data acquisition systems. For more information about it please refer to Sardana documentation.
SardanaGUI in action#
To play with Sardana you may double-click the SardanaGUI icon on the desktop or run it from a terminal (type SardanaGUI).
Cumbia#
Cumbia is a new library that offers a carefree approach to multi-threaded application design and implementation. Written from scratch, it can be seen as the evolution of the QTango library, because it offers a more flexible and object oriented multi-threaded programming style.
For more details please check Cumbia webpage and source repository.
Cumbia is installed in /usr/local/cumbia-libs. This directory is added to ld’s default search path.
To see an example Cumbia application, please run below command or use desktop shortcut CumbiaClientDemo:
cumbia client sys/tg_test/1/double_scalar
Cumbia demo application#
PANIC/PyAlarm#
PANIC Alarm System is a set of tools (api, Tango device server, user interface) that provides: Periodic evaluation of a set of conditions, Notification (email, sms, pop-up, speakers), Notification (email, sms, pop-up, speakers), Keep a log of what happened. (files, Tango Snapshots), Taking automated actions (Tango commands / attributes), Tools for configuration/visualization.
The Panic package contains the python AlarmAPI for managing the PyAlarm device servers from a client application or a python shell. The panic module is used by PyAlarm, Panic Toolbar and Panic GUI.
To launch PANIC GUI, use desktop shortcut PANIC.
PANIC GUI application#
SNAPshot (Archiving) installation and configuration#
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:
download and extract the *ArchivingRoot* package,
add the executable flag to the scripts located in
bin/linux,add the executable flat to the scripts located in
device/linux,replace the
shinterpreter withbashin shebang in scripts located inbin/linuxand indevice/linux. The scripts are not POSIX compliant,run SQL script
db/create-SNAPDB-InnoDB.sqlto create the database and users,set
ARCHIVING_ROOTenvironment variable to point to the directory with the contents of the ArchivingRoot package.
Configuration#
Configuration steps:
- 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
- configure SnapManager class properties:
DbUser: snapmanager,
DbPassword: snapmanager,
- configure SnapExtractor device properties:
DbUser: snapbrowser,
DbPassword: snapbrowser,
- 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#
In this section we will guide you step-by-step to help you getting started with Tango-Controls.
First steps#
Some first steps might include:
Reading through the Overview section will provide you with the basic information on Tango Controls. It will help you understand some of the key concepts behind Tango Controls.
Start with either a preconfigured virtual machine or try installing a basic Tango controls set up on your own computer - see trying Tango Controls .
Installing Tango Controls on your own system following instructions from the Installation guides .
Start connecting your devices to Tango Controls :
browse the Device Classes Catalogue to find device servers for your equipment
or read Write your first C++ TANGO device class and follow a guide How to write your device class if your device is not yet supported by any existing device server.
Writing your first client following instruction from Write your first Tango client.
Learn how to use some of the tools provided in Tango:
Explore the content of the Tango Controls web page
Specific how to guides#
How to try Tango Controls#
There are several ways to try the Tango Controls System.
For a first quick look, you can download and run the TangoBox Virtual Machine. Alternatively, you can go ahead and install Tango on your system - see the installation section for a full set of instructions on how to do this on your chosen operating system.
It may also be worth looking at configuring the Tango components to run as services using systemd to automate the start up, please see Use Tango with systemd integration.
Assuming you have installed and configured Tango you can begin to use it.
Play with Tango Controls#
The Tango ecosystem provides a lot of management applications and frameworks to visualize the data. This section provides a quick overview of a basic use case for Tango Controls.
If you have set up a tango-starter systemd service then it will automatically add the new host, however this can also be done manually using Astor. This application is used to configure the Control System and its components. It also provides a quick view of the statuses of all device servers in the Tango Database. To add a new host manually using Astor see: Add a new controlled host.
TangoTest this is a device class that provides all types of attributes available in Tango Devices and so can be used for testing purposes. Astor can be used to start this device server. After opening the control panel for the specific host, you can start a new device server, e.g.:
Tango Starter also needs to be running. Further information is available on starting a new Tango device server in Astor.
We can the open the AtkPanel from the Jive application and view the attributes, properties, and all configuration settings for the selected device.
Open the Jive application by typing the command jive in a console. Then select the
Monitor Device option from the right-click menu on that Tango Device, e.g.:
Users can use the AtkPanel to execute commands on the selected TangoTest device. Some useful commands one might try issuing for this device are:
SwitchStates- changes the state of the device (e.g. from RUN to FAULT or FAULT to RUN)DevType- this is a DevType command exampleState- return the state of the deviceCrashFromX- simulate a crash of the device
The attributes shown in the AtkPanel are mostly real-time values. If instead a user wants to view how the attribute value changes they can use the Taurus framework widgets.
TangoTest has an attribute that is generated using a trigonometric functions, so it is easy to check if the device is working correctly.
Below is the TaurusTrend view of the double_scalar_rww TangoTest attribute:
To run TaurusTrend uses a command:
taurus trend sys/tg_test/1/double_scalar_rww
Taurus also has a custom device panel (similar to the AtkPanel) which can be started with:
taurus device sys/tg_test/1/double_scalar_rww
The Tango Archiving System can be used to store the attribute value changes long term.
Use the end-user applications#
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#
While reading this how-to please refer to manuals of Jive and Astor tools.
Starting device servers with Jive#
In Jive select Tools > Server Wizard from the menu.
In the wizard, fill the server and the instance name and click Next. The server name should be the same as the server executable name, and instance name can be any name you provide.
Wizard - step 1 - device server instance#
The wizard will now wait until you start the device server, which you can do from the command line. Device servers usually take the instance name as an argument. For this example you can use the command
TangoTest test2where test2 is the instance name. When done click Next.
Wizard - step 2 - starting the device server#
In the next steps you will be asked to configure the devices that your device server instance will host. For this you need to pick a class and name for your device. A list of supported classes is provided by the device server, and the name of the device can be any name unique in the system following the format
<domain>/<family>/<member>.
Wizard - step 3 - device class#
Wizard - step 4 - device name#
Next, you can configure properties for the newly created devices.
Wizard - step 5 - device properties#
After that you can create another device of the same class by clicking New Device or pick another class by clicking New Class. You can also finish the configuration and exit the wizard with the Finish button.
Wizard - step 6 - finish#
After the configuration, the device server must be restarted to load and start configured devices.
Wizard - step 7 - device server restart#
Starting device servers with Astor#
Note
To configure device servers with Astor, you will need Starter installed and running on the host you want to start your device server on. Device server should be available in paths configured for the Starter.
In Astor go to the host control panel and click Start New button in the top left corner. If you want to just start an existing device server instance, pick it from the list and click Start Server.
Server list in Astor#
If you want to add new server, click Create New Server. This will open the device installation wizard from Jive. Astor will handle starting the device server for you in step 2 of the wizard.
At the end of the wizard, you will be asked to select the startup level of the new server.
Startup level configuration#
Other options#
You can also use the Tango Admin utility to register servers from scripts or the command line. There are also some device servers (e.g. Sardana) that can register themselves upon starting.
Configure properties on a new device#
To set-up a new device you need to know about all the device properties and their values which must be configured to make the device work. You need to have a description on the property which should indicate clearly its use. Also you need to know about a specified default value.
When creating the device interface with Pogo a description and a default value can be entered for every device property. This information is used by the device installation wizard (available with Jive) to guide you through the configuration.
When creating a new server start the wizard from the Tools menu -> Server Wizard. It allows you to create a new device and to initialise it property by property. For every property the description is displayed and the default value can be viewed. To use the wizard on an already existing device you can right click on the device and choose Device Wizard. You will be guided again through all the properties of the device. At the end the device can be re-started when necessary. Because the wizard is part of Jive, you can test the device configuration immediately.
Deployment#
This section provides a set of common how-to tasks relating to the deployment of Tango.
Transfer events using the multicast protocol#
This feature is available starting with Tango 8.1. Transferring events using a multicast protocol means delivering the events to a group of clients simultaneously in a single transmission from the event source. Tango, through ZMQ, uses the OpenPGM multicating protocol. This is one implementation of the PGM protocol defined by the RFC 3208 (Reliable multicasting protocol). Nevertheless, the default event communication mode is unicast and propagating events via multicasting requires some specific configuration.
Configuring events to use multicast transport#
Before using multicasting transport for event(s), you have to choose which address and port have to be used. In an IP V4 network, only a limited set of addresses are associated with multicasting. These are the IP V4 addresses between 224.0.1.0 and 238.255.255.255.
Once the address is selected, you have to choose a port number. Together
with the event name, these are the two minimal bits of information
which need to be provided to Tango to get multicast
transport. This configuration is done using the MulticastEvent free
property associated to the CtrlSystem object.
In the above screenshot of the Jive tool, the change event on the
state attribute of the dev/test/11 device has to be transferred
using multicasting with the address 226.20.21.22 and the port number
2222. The exact definition of this CtrlSystem/MulticastEvent property
for one event propagated using multicast is
1 CtrlSystem->MulticastEvent: Multicast address,
2 port number,
3 [rate in Mbit/sec],
4 [ivl in seconds],
5 event name
Rate and Ivl are optional properties. In case several events have to be transferred using multicasting, simply extend the MulicastEvent property with the configuration parameters related to the other events. There is only one MulticastEvent property per Tango control system. The underlying multicast protocol (PGM) is rate limited. This means that it limits its network bandwidth usage to a user defined value. The optional third configuration parameter is the maximum rate (in Mbit/sec) that the protocol will use to transfer this event. Because PGM is a reliable protocol, data has to be buffered for re-transmission in case a receiver signals some lost data. The optional forth configuration parameter specify the maximum amount of time (in seconds) that a receiver can be absent for a multicast group before unrecoverable data loss will occur. Exercise care when setting large recovery interval as the data needed for recovery will be held in memory. For example, a 60 seconds (1 minute) recovery interval at a data rate of 1 Gbit/sec requires a 7 GBytes in-memory buffer. When any of these two optional parameters are not set, the default value (defined in next sub-chapter) are used. Here is another example of events using a multicasting configuration
In this example, there are 5 events which are transmitted using multicasting:
Event change for attribute state on device dev/test/11 which uses multicasting address 226.20.21.22 and port number 2222
Event periodic for attribute state on device dev/test/10 which uses multicasting address 226.20.21.22 and port number 3333
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.
Event change for attribute event_change_tst on device dev/test/12 which uses multicasting address 226.20.21.22 and port number 2233
Event archive for attribute event_change_tst on device dev/tomasz/3 which uses multicasting address 226.20.21.22 and port number 2234
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#
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
Line |
Explanation |
|---|---|
1-3 |
Comment lines start with the |
4 |
Blank lines are skipped |
5-7 |
Device definition. <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 |
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 |
12-15 & 15-16 |
Device property (array) |
18 |
When a device string property contains special characters (spaces), the
|
22-35 |
Device attribute property definition. The syntax is <device name>/<attribute name>-><property name>: <property value>
Allowed characters after the |
38-39 |
Class property definition. The syntax is CLASS/<class name>-><property name>: <property value>
Allowed characters after the |
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 |
45 |
Free properties. The syntax is FREE/<object name>-><property name>: <property value>
Although |
Use the Starter device#
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:
systemd
follow instructions for systemd integration .
NSSM (Windows)
follow NSSM configuration instructions in the installation guide for Windows .
Further references#
Run a device server with the File Database#
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#
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#
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:
At the command line with the
-dlistoption: 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 theclass_factory()method. In the device name list, the device name separator is the comma character.
At the device pattern implementation level: in the class inherited from the
Tango::DeviceClassclass via reimplemntation of the methoddevice_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/2This command line starts the device server with two devices named id11/motor/1 and id11/motor/2
StepperMotor et -nodbThis 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 -nodbThis commands starts a device server with two devices named sr/cav-tuner/1 and sr/cav-tuner/2
StepperMotor et -nodb -dlist id12/motor/1Starts 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.
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:
Users with defined rights
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_inoutcall 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:
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.
At the device level: You define on which device(s) request are allowed using the device name. Device families can be specified using wildcards in the device name like domain/family/*
The control system is doing the following checks when a client try to access a device:
Get the user’s name
Get the host IP address
If rights are defined at the host level for this specific user and this IP address then give the user temporary write access to the control system
If nothing is specified for this specific user on this host, give the user temporary access rights equal to the host access rights of the All User user.
If the temporary right given to the user is write access to the control system then check what rights are defined at the device level:
If there is a right defined for the device to be accessed (or for the device family), give user that defined right
Otherwise if no right is defined at the device level for this user then check:
If rights are defined for the All Users user for this device, give this right to the user
Otherwise, give the user read Access only for this device
Otherwise the default access right will be read Access
Then, when the client tries to access the device, the following algorithm is used:
If right is read access
If the call is a write type call, refuse the call
If the call is a command execution
If the command is one of the command defined in the allowed commands for the device class, send the call
Else, refuse the call
All these checks are done during the DeviceProxy instance constructor
except those related to the device class allowed commands which are
checked during the command_inout call.
To simplify the rights management, give the All Users user host access right to all hosts (.*.*.*) and read access to all devices (/*/*). With such a set-up for this user, each new user without any rights defined in the control access will have only read access to all devices on the control system from any hosts. Then, on request, gives write access to specific user on specific host (or family) and on specific device (or family).
The access rights are managed using the Tango Astor tool which provides a graphical interface to grant/revoke user rights and to define device class allowed commands set. The following screenshot shows an example of the Astor window:
In this example, the user taurel has write access to the device sr/d-ct/1 and to all devices belonging to the domain fe but only from the host pcantares. They have read access to all other devices but always only from the host pcantares. The user verdier has write access to the device sys/dev/01 from any host on the network 160.103.5 and Read Access to all the remaining devices from the same network. All the other users have read access only from any host.
Running a Tango control system with the controlled access#
All the users rights are stored in two tables of the Tango database. A dedicated device server called TangoAccessControl accesses these tables without using the classical Tango database server. This TangoAccessControl device server must be configured with only one device. The property Services belonging to the free object CtrlSystem is used to run a Tango control system with controlled access. This property is an array of strings with each string describing the services running in the control system. For controlled access, the service name is AccessControl. The service instance name has to be defined as tango. The device name associated with this service must be the name of the TangoAccessControl server device. For instance, if the TangoAccessControl device server device is named sys/access_control/1, one element of the Services property of the CtrlSystem object has to be set to: AccessControl/tango:sys/access_control/1.
If the service is defined but without a valid device name corresponding
to the TangoAccessControl device server, all users from any host will
have write access (simulating a Tango control system without controlled
access). Note that this device server connects to the MariaDB database and
therefore may need the MariaDB connection related environment variables
MYSQL_USER and MYSQL_PASSWORD - see database environment variables.
Even if a controlled access system is running, it is possible to by-pass
it if, in the environment of the client application, the environment
variable SUPER_TANGO is defined to true. If for one reason or another,
the controlled access server is defined but not accessible, the device
right checked at that time will be read access.
Import multiple device classes to the Catalogue#
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#
Clone the import utility with git:
git clone https://gitlab.com/tango-controls/dsc.git` cd dsc-import
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/'
Configure to run:
In the settings.py file, configure the
REMOTE_REPO_HOSTandREMOTE_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.)
Run the command:
python dsc_import_utility.py
Add the
--csv-fileoption 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_UPDATEis True the catalogue is updatedFor 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#
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#
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#
Here you will find recipies on how to develop with Tango Controls.
Some common task are given below:
Create your first Device class#
This How-to explains how to create your first Device class regardless of the language used.
Prerequisite: This how-to assumes that you have a Tango installed.
Start Pogo code generator: Now you can create a new class:
Click on
and then New.
Add the following required information for your class:
The Device Class identification information (shown in the left panel below)
The class name, language and description (shown in the right panel below)
Once completed, you will see an empty Pogo interface:
You can add Properties, Commands and Attributes by double-clicking on each one. Below is an example after having defined some of these:
Generate your files from File -> Generate and press OK in the window.
Choose your output path and the files you want to create. For example, on linux OS in C++, the minimum set of files that need to be created are: an XMI file, Code files and a Makefile. See the example below:
You will now see the Pogo generated files in your folder:
Pogo has creates skeleton files with your Properties, Commands and Attributes.
Next you will need to develop your device. Information on how to do this can be found in the language specific how-to sections: C++, Java or Python
Finally, to compile and run your class see the section on how to start a device server.
Write your first Tango client#
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#
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.
Python: PyTango API documentation
Warning
CORBA events using notfid are deprecated and about to be removed, see this cppTango issue.
For non-standard events a single call exists for pushing the data to the CORBA Notification Service (omniNotify). Clients who are subscribed to this event have to know what data type is in the DeviceAttribute and unpack it accordingly.
To push non-standard events, the following api call is available to all device servers:
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#
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#
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#
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#
This quick tutorial will guide you to some fundamental TANGO concepts and how to do the first steps.
Step 1: Create a device class#
Start by creating a new device class using the Pogo code generator named TangoQuickStart. See how to create a device class.
Note that in order to compile the class properly, you will also need to add the packaging. Into Pogo, go to File -> Export Package and check all the headers:
Pogo will then create a new folder with the packages.
Once you have created a skeleton and added the headers, you can add your own code into the class and compile it. Further information on how to implement a C++ device class is available here.
Step 2: Compile the device class#
To compile the files, go to the folder that you choose to store your class and execute the following command:
cd packaging
./autogen.sh
./configure --prefix=$HOME/packaging
make
make install
If everything works, you will see the files in the src directory. In this example we used Pogo to create a class named TangoQuickStart and so we will see the following files:
Step 3: Register the device#
Open Jive and go to Edit -> Create Server
Fill out the form as follow:
The Server field must contain the ServerName (i.e. the name of the Device Server) and the instance. The Class field must contain the correct class name (in this case TangoQuickStart) and in the Device field you can add one or more devices following the naming convention: domain/family/member.
Finally click Register server.
Step 4: Start the device#
In order to start the device using the command line navigate to the directory where you compiled your C++ class, e.g.
cd packaging/src/
and use the command with the following syntax:
<TangoClassName> <instance>
Where <TangoClassName> is the name of the class that you created (in this example TangoQuickStart) and the instance is the name of the instance register in the Tango database (in this example test). So for this example the command would be:
TangoQuickStart test
If everything is ok, the following message appears:
Ready to accept request
E.g.
Step 5: explore the device#
When finish, you can explore your device using Jive.
Write your first C++ TANGO device class#
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 typeThe command
DevString: handles Tango stringsDevArray: receives and returns an array containing the simple Tango data typeDevStrArray: does not receive any data but which returns an array of stringsDevStruct: 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
StrAttrA readable attribute of the
Tango::DevLongtype calledLongRdAttr. This attribute is linked with the following writable attributeA writable attribute also of the
Tango::DevLongtype calledLongWrAttr.
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_hardwarein the example below)One read method for each readable attribute (e.g.
read_LongRdAttr, etc in the example below) andOne 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#
Sometimes, it could be useful to write your own process event handling loop. For instance, this feature can be
used in a device server process where the ORB is only one of several components that must perform event
handling. A device server with a graphical user interface for example must allow the GUI to handle windowing events
in addition to allowing the ORB to handle incoming requests. These types of device servers therefore perform
non-blocking event handling. They turn the main thread of control over each of the various event-handling
sub-systems while not allowing any of them to block for significant amount of time. The Tango::Util class
has a method called server_set_event_loop() to deal with such a case. This method has only one argument
which is a function pointer. This function does not receive any argument and returns a boolean. If this
boolean is true, the device server process exits. The device server core will call this function in a loop
without any sleeping time between the call. It is therefore the user’s responsibility to implement in this
function some kind of sleeping mechanism in order not to make this loop too CPU consuming. The code of this
function is executed by the device server main thread.
The following piece of code is an example of how this feature can be used.
1 bool my_event_loop()
2 {
3 some_sleeping_time();
4
5 bool ret = handle_gui_events();
6
7 // DS will exit when returning true
8 return ret;
9 }
10
11 int main(int argc,char *argv[])
12 {
13 Tango::Util *tg;
14 try
15 {
16 // Initialise the device server
17 //----------------------------------------
18 tg = Tango::Util::init(argc, argv);
19
20 tg->set_polling_threads_pool_size(5);
21
22 // Create the device server singleton
23 // which will create everything
24 //----------------------------------------
25 tg->server_init(false);
26
27 tg->server_set_event_loop(my_event_loop);
28
29 // Run the endless loop
30 //----------------------------------------
31 TANGO_LOG << "Ready to accept request" << endl;
32 tg->server_run();
33 }
34 catch (std::bad_alloc&)
35 {
36 ...
The device server main event loop is set at line 27 before the call to the Util::server_run() method. The function used as server loop is defined between lines 1 and 9.
How to create inheritance link between Tango classes#
This howto explains how to create a new Tango device class which inherits from an already existing one.
We use the usual definition of inheritance from object oriented programming:
The high level class supports all the commands/attributes defined in the low level class on top of its own (if any),
The device(s) available for the external world are an instances of the high level class.
Warning
Once modified, Pogo will not be able to understand the high level Tango class and therefore can’t be used for further changes in this class
The example case#
Let’s say that we already have a Tango class called LowLevel with
one command called LowLevelCmd and one attribute called
LowLevelAttr. We want to create a new Tango class called HighLevel
with its own command called HighLevelCmd and its own attribute called
HighLevelAttr. Both classes have been generated using Pogo. The
following is a description of what has to be modified in the code
generated by Pogo for the HighLevel Tango class in order to create the
inheritance link.
The HighLevelClass.h file#
In this file, we will modify the HighLevelClass declaration in order to inherit from the LowLevelClass
class instead of TANGO_BASE_CLASS but we first need to add an extra include file at the file beginning:
1#ifndef _HIGHLEVELCLASS_H
2#define _HIGHLEVELCLASS_H
3
4#include <tango.h>
5#include <HighLevel.h>
6
7#include <LowLevelClass.h>
Then, we change the HighLevelClass inheritance declaration:
1//
2// The HighLevelClass singleton definition
3//
4
5class
6#ifdef WIN32
7 __declspec(dllexport)
8#endif
9HighLevelClass : public LowLevel_ns::LowLevelClass
10{
11public:
12// properties member data
13
14// add your own data members here
15//------------------------------------
The HighLevelClass.cpp file#
In this file, we have to modify:
The HighLevelClass constructor
The HighLevelClass command_factory() method
The HighLevelClass attribute_factory() method
1//+----------------------------------------------------------------------------
2//
3// method : HighLevelClass::HighLevelClass(std::string &s)
4//
5// description : constructor for the HighLevelClass
6//
7// in : - s : The class name
8//
9//-----------------------------------------------------------------------------
10HighLevelClass::HighLevelClass(std::string &s)
11: LowLevel_ns::LowLevelClass(s)
12{
13 TANGO_LOG_INFO << "Entering HighLevelClass constructor" << std::endl;
14 set_default_property();
15 get_class_property();
16 write_class_property();
17
18 TANGO_LOG_INFO << "Leaving HighLevelClass constructor" << std::endl;
19}
Then, the changes in the command_factory() method which needs to call the LowLevelClass command_factory() method:
1//+----------------------------------------------------------------------------
2//
3// method : HighLevelClass::command_factory
4//
5// description : Create the command object(s) and store them in the
6// command list
7//
8//-----------------------------------------------------------------------------
9void HighLevelClass::command_factory()
10{
11 LowLevel_ns::LowLevelClass::command_factory();
12
13 command_list.push_back(new HighLevelCmdClass("HighLevelCmd",
14 Tango::DEV_VOID, Tango::DEV_VOID,
15 "",
16 "",
17 Tango::OPERATOR));
18
19 // add polling if any
20 for(size_t i = 0 ; i < command_list.size(); i++)
21 {
22 // ...
23 }
24}
Finally, the changes in the attribute_factory() method which needs to call the LowLevelClass attribute_factory() method:
1//+----------------------------------------------------------------------------
2// Method: HighLevelClass::attribute_factory(std::vector<Tango::Attr *> &att_list)
3//-----------------------------------------------------------------------------
4void HighLevelClass::attribute_factory(std::vector<Tango::Attr *> &att_list)
5{
6 LowLevel_ns::LowLevelClass::attribute_factory(att_list);
7
8 // Attribute : HighLevelAttr
9 HighLevelAttrAttrib *high_level_attr = new HighLevelAttrAttrib();
10 // ...
11 att_list.push_back(high_level_attr);
12}
The HighLevel.h file#
This file has to be modified in order to:
Change the
HighLevelclass inheritance fromTANGO_BASE_CLASStoLowLevel_ns::LowLevelAdd a new data member in the
HighLevelclass in order to correctly implement the deviceInitcommand (a boolean is enough)Modify the class destructor for a correct management of the device
Initcommand
First, we have to add a new include file:
1#ifndef _HIGHLEVEL_H
2#define _HIGHLEVEL_H
3
4#include <tango.h>
5#include <LowLevel.h>
Then, the change in the HighLevel class inheritance:
1class HighLevel: public LowLevel_ns::LowLevel
2{
3public :
4 // Add your own data members here
5 //-----------------------------------------
The addition of the new data member at the end of the HighLevel class declaration:
1protected :
2 // Add your own data members here
3 //-----------------------------------------
4 bool device_constructed{false};
5}
And finally, the change in the HighLevel class destructor:
1/**
2 * The object desctructor.
3 */
4 ~HighLevel()
5 {
6 delete_device();
7 }
The HighLevel.cpp file#
In this file, we have to modify
The HighLevel class constructors to reflect the change in its inheritance and to initialize the new data member
device_constructedThe HighLevel class
delete_device()andinit_device()to correctly handle the deviceInitcommandThe HighLevel class
always_executed_hook()andread_attr_hardware()methods in order that they call the correspondingLowLevelclass method
Let’s start with the changes in the HighLevel class constructors:
1//+----------------------------------------------------------------------------
2//
3// method : HighLevel::HighLevel(string &s)
4//
5// description : constructor for HighLevel
6//
7// in : - cl : Pointer to the DeviceClass object
8// - s : Device name
9// - d : Description
10//
11//-----------------------------------------------------------------------------
12HighLevel::HighLevel(Tango::DeviceClass *cl, std::string &s)
13 :LowLevel_ns::LowLevel(cl, s.c_str())
14{
15 init_device();
16 device_constructed = true;
17}
18
19HighLevel::HighLevel(Tango::DeviceClass *cl, const char *s)
20 :LowLevel_ns::LowLevel(cl, s)
21{
22 init_device();
23 device_constructed = true;
24}
25
26HighLevel::HighLevel(Tango::DeviceClass *cl, const char *s, const char *d)
27 :LowLevel_ns::LowLevel(cl, s, d)
28{
29 init_device();
30 device_constructed = true;
31}
Now, the modified HighLevel class init_device() and delete_device() methods:
1//+----------------------------------------------------------------------------
2//
3// method : HighLevel::delete_device()
4//
5// description : will be called at device destruction or at init command.
6//
7//-----------------------------------------------------------------------------
8void HighLevel::delete_device()
9{
10 TANGO_LOG_INFO << "HighLevel::delete_device()" << std::endl;
11
12 // Delete device's allocated object
13
14 if(device_constructed)
15 {
16 LowLevel_ns::LowLevel::delete_device();
17 }
18}
19
20//+----------------------------------------------------------------------------
21//
22// method : HighLevel::init_device()
23//
24// description : will be called at device initialization.
25//
26//-----------------------------------------------------------------------------
27void HighLevel::init_device()
28{
29 if(device_constructed)
30 {
31 LowLevel_ns::LowLevel::init_device();
32 }
33
34 TANGO_LOG_INFO << "HighLevel::HighLevel() create device " << device_name << std::endl;
35 // ...
And finally, the HighLevel class always_executed_hook() and read_attr_hardware() methods:
1//+----------------------------------------------------------------------------
2//
3// method : HighLevel::always_executed_hook()
4//
5// description : method always executed before any command is executed
6//
7//-----------------------------------------------------------------------------
8void HighLevel::always_executed_hook()
9{
10 LowLevel_ns::LowLevel::always_executed_hook();
11 TANGO_LOG_INFO << "HighLevel::always_executed_hook()" << std::endl;
12 // ...
13}
14
15//+----------------------------------------------------------------------------
16//
17// method : HighLevel::read_attr_hardware
18//
19// description : Hardware acquisition for attributes.
20//
21//-----------------------------------------------------------------------------
22void HighLevel::read_attr_hardware(std::vector<long> &attr_list)
23{
24 LowLevel_ns::LowLevel::read_attr_hardware(attr_list);
25 TANGO_DEBUG_LOG << "HighLevel::read_attr_hardware(std::vector<long> &attr_list) entering... " << std::endl;
26 // ...
27}
Don’t forget to also modify CMakeLists.txt in order to link the three
LowLevel Tango class object files (Lowlevel.o, LowLevelClass.o and
LowLevelStateMachine.o) to your executable.
The HighLevel class can be registered and started with the usual approach by
Jive or tango_admin.
Conclusion#
With these relatively simple changes in the HighLevel class, we now have a device instance of a Tango class which “inherits” from another Tango class. The drawback of this method is that once the file has been modified, Pogo will not be able to understand the HighLevel class any more and should not be used for further changes in this class!
With a couple of virtual methods, it is also possible in the HighLevel
class to overwrite a command or an attribute defined in the Lowlevel class.
Use C++ std::vector to set attributes#
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#
A non-fixed number of attributes is common while working with Input/Output devices with a variable number of channels (PLCs, DaqBoards, Timers/Counters), when depending of the operation mode more or less scalar or spectrum attributes are be needed.
Generating your class with Pogo#
DynAttr with three types of attributes:
StaticAttr: A scalar, Tango::DevShort, READ attribute,
LongDynAttr: A scalar, Tango::DevLong, READ_WRITE attribute,
DoubleDynAttr: A scalar, Tango::DevDouble, READ_WRITE attribute.
Each device belonging to this class has one StaticAttr and a dynamic
list of LongDynAttr or DoubleDynAttr attributes. This list of dynamic
attributes is defined with a device property named DynAttrList.
Therefore, using Pogo, we define a Tango class with three scalar
attributes with the definition given above. We also create the
DynAttrList property for defining dynamic attribute. This is a vector of
strings with a couple of strings for each attribute. The first string is
the dynamic attribute type (LongDynAttr or DoubleDynAttr) and the second
string is the attribute name.
Note
The dynamic attributes have a light green background color on the main Pogo window.
Dynamic attribute registration#
To register device dynamic attributes, you need to:
call the
Tango::DeviceImpl::add_attribute()method for each device dynamic attributecreate the data used with the attribute (for the Attribute::set_value() method)
Pogo generates method(s) named DynAttr::add_$<DynAttrName>_dynamic_attribute(std::string att_name) which do
this job for you. You have to call these methods according to your needs in another Pogo generated method
named DynAttr::add_dynamic_attributes() which is executed at device creation time.
In our example, in this method we have to:
analyze the content of the device DynAttrList property and create the necessary attributes using the helper method also generated by Pogo
The code of the DynAttr::add_dynamic_attributes() method looks like
1void DynAttr::add_dynamic_attributes()
2{
3 // Example to add dynamic attribute:
4 // Copy inside the following protected area to create instance(s) at startup.
5 // add_LongDynAttr_dynamic_attribute("MyLongDynAttrAttribute");
6 // add_DoubleDynAttr_dynamic_attribute("MyDoubleDynAttrAttribute");
7
8 /*----- PROTECTED REGION ID(DynAttr::add_dynamic_attributes) ENABLED START -----*/
9 /* clang-format on */
10
11 const auto len = dynAttrList.size();
12
13 if(len == 0)
14 {
15 return;
16 }
17
18 if((len % 2) != 0)
19 {
20 TANGO_THROW_EXCEPTION("DynAttrInvalidSetup", "Expected a multiple of two entries in dynAttrList");
21 }
22
23 for(size_t i = 0; i < len; i += 2)
24 {
25 if(dynAttrList[i] == "LongDynAttr")
26 {
27 add_LongDynAttr_dynamic_attribute(dynAttrList[i + 1]);
28 }
29 else if(dynAttrList[i] == "DoubleDynAttr")
30 {
31 add_DoubleDynAttr_dynamic_attribute(dynAttrList[i + 1]);
32 }
33 else
34 {
35 TANGO_THROW_EXCEPTION("DynAttrInvalidSetup", "Unexpected dynamic attribute name");
36 }
37 }
38
39 /* clang-format off */
40 /*----- PROTECTED REGION END -----*/ // DynAttr::add_dynamic_attributes
41}
Note
The data associated with all LongDynAttr dynamic attributes are initialized to 0 and the data associated to all DoubleDynAttr dynamic attributes are initialized with 0.0
The definition of the DoubleDynAttr attribute is simply to return when read, the last value which has been written. The code for the DoubleDynAttr reading/writing is the following
1void DynAttr::read_DoubleDynAttr(Tango::Attribute &attr)
2{
3 DEBUG_STREAM << "DynAttr::read_DoubleDynAttr(Tango::Attribute &attr) entering... " << std::endl;
4 Tango::DevDouble *att_value = get_DoubleDynAttr_data_ptr(attr.get_name());
5 /*----- PROTECTED REGION ID(DynAttr::read_DoubleDynAttr) ENABLED START -----*/
6 /* clang-format on */
7
8 attr.set_value(att_value);
9
10 /* clang-format off */
11 /*----- PROTECTED REGION END -----*/ // DynAttr::read_DoubleDynAttr
12}
13
14void DynAttr::write_DoubleDynAttr(Tango::WAttribute &attr)
15{
16 DEBUG_STREAM << "DynAttr::write_DoubleDynAttr(Tango::WAttribute &attr) entering... " << std::endl;
17 // Retrieve write value
18 Tango::DevDouble w_val;
19 attr.get_write_value(w_val);
20 /*----- PROTECTED REGION ID(DynAttr::write_DoubleDynAttr) ENABLED START -----*/
21 /* clang-format on */
22
23 auto *att_value = get_DoubleDynAttr_data_ptr(attr.get_name());
24 *att_value = w_val;
25
26 /* clang-format off */
27 /*----- PROTECTED REGION END -----*/ // DynAttr::write_DoubleDynAttr
28}
The code of the read method in it’s Pogo generated part retrieves a
pointer to the data associated with this attribute with the helper
method named DynAttr::get_$<DynAttrName>_data_ptr(std::string att_name).
The user code simply passes this pointer to the Tango Attribute::set_value() method.
The user code of the write method also uses the Pogo generated helper method to get the attribute data pointer and set this data to the value sent by the caller.
The definition of the LongDynAttr is a bit more sophisticated. For one device of this Tango class, we have several dynamic attributes of this LongDynAttr type. According to which attribute is read or written, we have to call different method accessing the hardware.
The code for reading/writing the LongDynAttr attribute is given below:
1void DynAttr::read_LongDynAttr(Tango::Attribute &attr)
2{
3 DEBUG_STREAM << "DynAttr::read_LongDynAttr(Tango::Attribute &attr) entering... " << endl;
4 Tango::DevLong *att_value = get_LongDynAttr_data_ptr(attr.get_name());
5
6 /*----- PROTECTED REGION ID(DynAttr::read_LongDynAttr) ENABLED START -----*/
7
8 std::string &att_name = attr.get_name();
9 if(att_name == dynAttrList[1])
10 {
11 // Access hardware for channel 1 which is the first attribute in the list
12 *att_value = read_hardware_channel1();
13 }
14 else if(att_name == dynAttrList[3])
15 {
16 // Access hardware for channel 2 which is the second attribute in the list
17 *att_value = read_hardware_channel2();
18 }
19 else
20 {
21 TANGO_THROW_EXCEPTION("DynAttrInvalidSetup", "Unexpected dynamic attribute name");
22 }
23
24 // Set the attribute value
25 attr.set_value(att_value);
26
27 /*----- PROTECTED REGION END -----*/ // DynAttr::read_LongDynAttr
28}
29
30void DynAttr::write_LongDynAttr(Tango::WAttribute &attr)
31{
32 DEBUG_STREAM << "DynAttr::write_LongDynAttr(Tango::Attribute &attr) entering... " << endl;
33
34 // Retrieve write value
35 Tango::DevLong w_val;
36 attr.get_write_value(w_val);
37
38 /*----- PROTECTED REGION ID(DynAttr::write_LongDynAttr) ENABLED START -----*/
39
40 std::string &att_name = attr.get_name();
41 if(att_name == dynAttrList[1])
42 {
43 // Access hardware for channel 1 which is the first attribute in the list
44 write_hardware_channel1(w_val);
45 }
46 else if(att_name == dynAttrList[3])
47 {
48 // Access hardware for channel 2 which is the second attribute in the list
49 write_hardware_channel2(w_val);
50 }
51 else
52 {
53 TANGO_THROW_EXCEPTION("DynAttrInvalidSetup", "Unexpected dynamic attribute name");
54 }
55
56 /*----- PROTECTED REGION END -----*/ // DynAttr::write_LongDynAttr
57}
Running the server#
A Tango device server process hosting this DynAttr class has been defined in the database with two device. The dynamic attributes for these two devices are:
As shown by the Pogo screen-shot in the beginning of this HowTo, the Tango class also defines a static attribute for each device named StaticAttr. Running the device server and opening TestDevice panels on each device displays device attribute list:
This method fully supports restarting device(s) or server using the device server process admin device.
Handle Tango string attributes in 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:
the pointer to the memory where the string is stored (
char *)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:
the vector is initialized somewhere in the Tango class
the vector is declared as a device data member (in MyDev.h header file)
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#
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#
Enumerated attributes are supported using the data type DevEnum.
This data type is not a real C++ enumeration because:
The enumerated value allways start with 0
Values are consecutive
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:
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.
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#
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:
The wind speed at the top of the ski-lift
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#
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 theinit_deviceit cannot be caught by the Tango DeviceServer programmer.If in the
init_devicemethod 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#
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 |
|
DevShort |
|
DevEnum |
enumeration (only for attribute / See chapter on advanced features) |
DevLong |
|
DevLong64 |
|
DevFloat |
|
DevDouble |
|
DevString |
|
DevEncoded |
|
DevUChar |
|
DevUShort |
|
DevULong |
|
DevULong64 |
|
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:
The polling mode
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:
The pull callback mode
The push callback mode
In the pull callback mode, the callback is triggered if the server answer is arrived when the client decide it by calling a synchronization method (The client pull-out the answer). In push mode, the callback is executed as soon as the reply arrives in a separate thread (The server pushes the answer to the client).
Synchronous model#
Synchronous access to Tango device are provided using the DeviceProxy or AttributeProxy class. For the DeviceProxy class, the main synchronous call methods are :
command_inout() to execute a Tango device command
read_attribute() or read_attributes() to read a Tango device attribute(s)
write_attribute() or write_attributes() to write a Tango device attribute(s)
write_read_attribute() or write_read_attributes() to write then read Tango device attribute(s)
read_pipe() to read a Tango device pipe
write_pipe() to write a Tango device pipe
write_read_pipe() to write then read Tango device pipe
For commands, data are sent/received to/from device using the DeviceData class. For attributes, data are sent/received to/from device attribute using the DeviceAttribute class. For pipes, data are sent/receive to/from device pipe using the DevicePipe and DevicePipeBlob classes.
In some cases, only attributes provided by a Tango device are interesting for the application. You can use the AttributeProxy class. Its main synchronous methods are :
read() to read the attribute value
write() to write the attribute value
write_read() to write then read the attribute value
Data are transmitted using the DeviceAttribute class.
Asynchronous model#
Asynchronous access to Tango device are provided using DeviceProxy or AttributeProxy, CallBack and ApiUtil classes methods. The main asynchronous call methods and used classes are :
To execute a command on a device
DeviceProxy::command_inout_asynch() and DeviceProxy::command_inout_reply() in polling model.
DeviceProxy::command_inout_asynch(), DeviceProxy::get_asynch_replies() and CallBack class in callback pull model
DeviceProxy::command_inout_asynch(), ApiUtil::set_asynch_cb_sub_model() and CallBack class in callback push model
To read a device attribute
DeviceProxy::read_attribute_asynch() and DeviceProxy::read_attribute_reply() in polling model
DeviceProxy::read_attribute_asynch(), DeviceProxy::get_asynch_replies() and CallBack class in callback pull model.
DeviceProxy::read_attribute_asynch(), ApiUtil::set_asynch_cb_sub_model() and CallBack class in callback push model
To write a device attribute
DeviceProxy::write_attribute_asynch() in polling model
DeviceProxy::write_attribute_asynch() and CallBack class in callback pull model
DeviceProxy::write_attribute_asynch(), ApiUtil::set_asynch_cb_sub_model() and CallBack class in callback push model
For commands, data are send/received to/from device using the DeviceData class. For attributes, data are send/received to/from device attribute using the DeviceAttribute class. It is also possible to generate asynchronous request(s) using the AttributeProxy class following the same schema than above. Methods to use are :
read_asynch() and read_reply() to asynchronously read the attribute value
write_asynch() and write_reply() to asynchronously write the attribute value
Events#
Introduction#
For an introduction to events, see Events.
Application Programmer’s Interface#
How to set up and use the TANGO events ? The interfaces described here are intended as user-friendly interfaces to the underlying CORBA calls. The interface is modeled after the asynchronous command_inout() interface to maintain coherency. The event system supports push callback model as well as the pull callback model.
The two event reception modes are:
Push callback model : On event reception a callback method gets immediately executed.
Pull callback model : The event will be buffered in the client until the client is ready to receive the event data. The client triggers the execution of the callback method.
The event reception buffer in the pull callback model, is implemented as a round-robin buffer. The client can choose the size when subscribing for the event. This way the client can set up different ways to receive events.
Event reception buffer size = 1 : The client is interested only in the value of the last event received. All other events that have been received since the last reading are discarded.
Event reception buffer size > 1 : The client has chosen to keep an event history of a given size. When more events arrive since the last reading, older events will be discarded.
Event reception buffer size = ALL_EVENTS : The client buffers all received events. The buffer size is unlimited and only restricted by the available memory for the client.
Configuring events#
Please refer to the Event explanation part for more details on how to configure the change, periodic and archive events.
C++ Clients#
This is the interface for clients who want to receive events. The main action of the client is to subscribe and unsubscribe to events. Once the client has subscribed to one or more events the events are received in a separate thread by the client.
Two reception modes are possible:
On event reception a callbacks method gets immediately executed.
The event will be buffered until the client until the client is ready to receive the event data.
The mode to be used has to be chosen when subscribing for the event.
Subscribing to events#
The client call to subscribe to an event is named DeviceProxy::subscribe_event() . During the event subscription the client has to choose the event reception mode to use.
Push model:
1int DeviceProxy::subscribe_event(
2 const string &attribute,
3 Tango::EventType event,
4 Tango::CallBack *callback,
5 bool stateless = false);
The client implements a callback method which is triggered when the event is received. Note that this callback method will be executed by a thread started by the underlying ORB. This thread is not the application main thread.
Pull model:
1int DeviceProxy::subscribe_event(
2 const string &attribute,
3 Tango::EventType event,
4 int event_queue_size,
5 bool stateless = false);
The client chooses the size of the round robin event reception buffer. Arriving events will be buffered until the client uses DeviceProxy::get_events() to extract the event data. For Tango releases before 8, a similar call with one extra parameter for event filtering is also available.
On top of the user filter defined by the filters parameter, basic filtering is done based on the reason specified and the event type. For example when reading the state and the reason specified is change the event will be fired only when the state changes. Events consist of an attribute name and the event reason. A standard set of reasons are implemented by the system, additional device specific reasons can be implemented by device servers programmers.
The stateless flag = false indicates that the event subscription will only succeed when the given attribute is known and available in the Tango system. Setting stateless = true will make the subscription succeed, even if an attribute of this name was never known. The real event subscription will happen when the given attribute will be available in the Tango system.
Note that in this model, the callback method will be executed by the thread doing the DeviceProxy::get_events() call.
The CallBack class#
In C++, the client has to implement a class inheriting from the Tango CallBack class and pass this to the DeviceProxy::subscribe_event() method. The CallBack class is the same class as the one proposed for the TANGO asynchronous call. This is as follows for events :
1class MyCallback : public Tango::CallBack
2{
3 .
4 .
5 .
6 public:
7 void push_event(Tango::EventData *);
8 void push_event(Tango::AttrConfEventData *);
9 void push_event(Tango::DataReadyEventData *);
10 void push_event(Tango::DevIntrChangeEventData *);
11 void push_event(Tango::PipeEventData *);
12}
where EventData is defined as follows :
1class EventData
2{
3 DeviceProxy *device;
4 string attr_name;
5 string event;
6 DeviceAttribute *attr_value;
7 bool err;
8 DevErrorList errors;
9}
AttrConfEventData is defined as follows :
1class AttrConfEventData
2{
3 DeviceProxy *device;
4 string attr_name;
5 string event;
6 AttributeInfoEx *attr_conf;
7 bool err;
8 DevErrorList errors;
9}
DataReadyEventData is defined as follows :
1class DataReadyEventData
2{
3 DeviceProxy *device;
4 string attr_name;
5 string event;
6 int attr_data_type;
7 int ctr;
8 bool err;
9 DevErrorList errors;
10}
DevIntrChangeEventData is defined as follows :
1class DevIntrChangeEventData
2{
3 DeviceProxy device;
4 string event;
5 string device_name;
6 CommandInfoList cmd_list;
7 AttributeInfoListEx att_list;
8 bool dev_started;
9 bool err;
10 DevErrorList errors;
11}
and PipeEventData is defined as follows :
1class PipeEventData
2{
3 DeviceProxy *device;
4 string pipe_name;
5 string event;
6 DevicePipe *pipe_value;
7 bool err;
8 DevErrorList errors;
9}
In push model, there are some cases (same callback used for events coming from different devices hosted in device server process running on different hosts) where the callback method could be executed concurently by different threads started by the ORB. The user has to code his callback method in a thread safe manner.
Unsubscribing from an event#
Unsubscribe a client from receiving the event specified by event_id is done by calling the DeviceProxy::unsubscribe_event() method :
1void DeviceProxy::unsubscribe_event(int event_id);
Extract buffered event data#
When the pull model was chosen during the event subscription, the received event data can be extracted with DeviceProxy::get_events(). Two possibilities are available for data extraction. Either a callback method can be executed for every event in the buffer when using
1int DeviceProxy::get_events(
2 int event_id,
3 CallBack *cb);
Or all the event data can be directly extracted as EventDataList, AttrConfEventDataList , DataReadyEventDataList, DevIntrChangeEventDataList or PipeEventDataList when using
1int DeviceProxy::get_events(
2 int event_id,
3 EventDataList &event_list);
4
5int DeviceProxy::get_events(
6 int event_id,
7 AttrConfEventDataList &event_list);
8
9int DeviceProxy::get_events(
10 int event_id,
11 DataReadyEventDataList &event_list);
12
13int DeviceProxy::get_events(
14 int event_id,
15 DevIntrChangeEventDataList &event_list);
16
17int DeviceProxy::get_events(
18 int event_id,
19 PipeEventDataList &event_list);
The event data lists are vectors of EventData, AttrConfEventData, DataReadyEventData or PipeEventData pointers with special destructor and clean-up methods to ease the memory handling.
1class EventDataList:public vector<EventData *>
2class AttrConfEventDataList:public vector<AttrConfEventData *>
3class DataReadyEventDataList:public vector<DataReadyEventData *>
4class DevIntrChangeEventDataList:public vector<DevIntrChangeEventData *>
5class PipeEventDataList:public vector<PipeEventData *>
Example#
Here is a typical code example of a client to register and receive events. First, you have to define a callback method as follows:
1class DoubleEventCallBack : public Tango::CallBack
2{
3 void push_event(Tango::EventData*);
4};
5
6
7void DoubleEventCallBack::push_event(Tango::EventData *myevent)
8{
9 Tango::DevVarDoubleArray *double_value;
10 try
11 {
12 cout << "DoubleEventCallBack::push_event(): called attribute "
13 << myevent->attr_name
14 << " event "
15 << myevent->event
16 << " (err="
17 << myevent->err
18 << ")" << endl;
19
20
21 if (!myevent->err)
22 {
23 *(myevent->attr_value) >> double_value;
24 cout << "double value "
25 << (*double_value)[0]
26 << endl;
27 delete double_value;
28 }
29 }
30 catch (...)
31 {
32 cout << "DoubleEventCallBack::push_event(): could not extract data !\n";
33 }
34}
Note
If the event is an error event (myevent->err == true), the attr_value field in myevent EventData object will be empty (null pointer). The same applies for some other kinds of events. For example, attr_conf field in AttrConfEventData object will be a null pointer in case of error event (if the err field is true). As a consequence, the device server programmer should always check the err field before trying to extract the EventData::attr_value or AttrConfEventData::attr_conf fields associated to the event.
Then the main code must subscribe to the event and choose the push or the pull model for event reception.
Push model:
1DoubleEventCallBack *double_callback = new DoubleEventCallBack;
2
3Tango::DeviceProxy *mydevice = new Tango::DeviceProxy("my/device/1");
4
5int event_id;
6const string attr_name("current");
7event_id = mydevice->subscribe_event(attr_name,
8 Tango::CHANGE_EVENT,
9 double_callback);
10cout << "event_client() id = " << event_id << endl;
11
12// The callback methods are executed by the Tango event reception thread.
13// The main thread is not concerned of event reception.
14// Whatch out with synchronisation and data access in a multi threaded environment!
15
16sleep(1000); // wait for events
17
18mydevice->unsubscribe_event(event_id);
Pull model:
1DoubleEventCallBack *double_callback = new DoubleEventCallBack;
2int event_queue_size = 100; // keep the last 100 events
3
4Tango::DeviceProxy *mydevice = new Tango::DeviceProxy("my/device/1");
5
6int event_id;
7const string attr_name("current");
8event_id = mydevice->subscribe_event(attr_name,
9 Tango::CHANGE_EVENT,
10 event_queue_size);
11cout << "event_client() id = " << event_id << endl;
12
13// Check every 3 seconds whether new events have arrived and trigger the callback method
14// for the new events.
15
16for (int i=0; i < 100; i++)
17{
18 sleep (3);
19
20 // Read the stored event data from the queue and call the callback method for every event.
21 mydevice->get_events(event_id, double_callback);
22}
23
24event_test->unsubscribe_event(event_id);
Group#
A Tango Group provides the user with a single point of control for a collection of devices. By analogy, one could see a Tango Group as a proxy for a collection of devices. For instance, the Tango Group API supplies a command_inout() method to execute the same command on all the elements of a group.
A Tango Group is also a hierarchical object. In other words, it is possible to build a group of both groups and individual devices. This feature allows creating logical views of the control system - each view representing a hierarchical family of devices or a sub-system.
In this chapter, we will use the term hierarchy to refer to a group and its sub-groups. The term Group designates to the local set of devices attached to a specific Group.
Getting started with Tango group#
The quickest way of getting started is to study an example…
Imagine we are vacuum engineers who need to monitor and control hundreds of gauges distributed over the 16 cells of a large-scale instrument. Each cell contains several penning and pirani gauges. It also contains one strange gauge. Our main requirement is to be able to control the whole set of gauges, a family of gauges located into a particular cell (e.g. all the penning gauges of the 6th cell) or a single gauge (e.g. the strange gauge of the 7th cell). Using a Tango Group, such features are quite straightforward to obtain.
Reading the description of the problem, the device hierarchy becomes obvious. Our gauges group will have the following structure:
1-> gauges
2 | -> cell-01
3 | |-> inst-c01/vac-gauge/strange
4 | |-> penning
5 | | |-> inst-c01/vac-gauge/penning-01
6 | | |-> inst-c01/vac-gauge/penning-02
7 | | |- ...
8 | | |-> inst-c01/vac-gauge/penning-xx
9 | |-> pirani
10 | |-> inst-c01/vac-gauge/pirani-01
11 | |-> ...
12 | |-> inst-c01/vac-gauge/pirani-xx
13 | -> cell-02
14 | |-> inst-c02/vac-gauge/strange
15 | |-> penning
16 | | |-> inst-c02/vac-gauge/penning-01
17 | | |-> ...
18 | |
19 | |-> pirani
20 | | |-> ...
21 | -> cell-03
22 | |-> ...
23 | | -> ...
In the C++, such a hierarchy can be build as follows (basic version):
1//- step0: create the root group
2Tango::Group *gauges = new Tango::Group("gauges");
3
4
5//- step1: create a group for the n-th cell
6Tango::Group *cell = new Tango::Group("cell-01");
7
8
9//- step2: make the cell a sub-group of the root group
10gauges->add(cell);
11
12
13//- step3: create a "penning" group
14Tango::Group *gauge_family = new Tango::Group("penning");
15
16
17//- step4: add all penning gauges located into the cell (note the wildcard)
18gauge_family->add("inst-c01/vac-gauge/penning*");
19
20
21//- step5: add the penning gauges to the cell
22cell->add(gauge_family);
23
24
25//- step6: create a "pirani" group
26gauge_family = new Tango::Group("pirani");
27
28
29//- step7: add all pirani gauges located into the cell (note the wildcard)
30gauge_family->add("inst-c01/vac-gauge/pirani*");
31
32
33//- step8: add the pirani gauges to the cell
34cell->add(gauge_family);
35
36
37//- step9: add the "strange" gauge to the cell
38cell->add("inst-c01/vac-gauge/strange");
39
40
41//- repeat step 1 to 9 for the remaining cells
42cell = new Tango::Group("cell-02");
43...
Important note: There is no particular order to create the hierarchy. However, the insertion order of the devices is conserved throughout the lifecycle of the Group and cannot be changed. That way, the Group implementation can guarantee the order in which results are returned (see below).
Keeping a reference to the root group is enough to manage the whole hierarchy (i.e. there no need to keep trace of the sub-groups or individual devices). The Group interface provides methods to retrieve a sub-group or an individual device.
Be aware that a C++ group allways gets the ownership of its children and deletes them when it is itself deleted. Therefore, never try to delete a Group (respectively a DeviceProxy) returned by a call to Tango::Group::get_group() (respectively to Tango::Group::get_device()). Use the Tango::Group::remove() method instead (see the Tango Group class API documentation for details).
We can now perform any action on any element of our gauges group. For instance, let’s ping the whole hierarchy to be sure that all devices are alive.
1//- ping the whole hierarchy
2if (gauges->ping() == true)
3{
4 std::cout << "all devices alive" << std::endl;
5}
6else
7{
8 std::cout << "at least one dead/busy/locked/... device" << std::endl;
9}
Enabling and disabling group members#
Devices belonging to a group can be temporarily excluded from all operations performed on the group using the Group::disable and Group::enable calls.
Device name passed to the disable (enable) methods can contain
wildcards (*).
Note that only the first matching device will be disabled (enabled).
The search algorithm is breadth-first search. All group elements are searched
(in the insertion order) for a match before descending recursively to
sub-groups. Recursive search can be disabled with the forward flag
(see a section dedicated to the forwarding ).
During group operations like attribute read or command calls, entries for disabled elements will be included in the result set, however they will not have any value and will be marked as disabled (GroupReply::group_element_enabled will be false).
Note that if exceptions are enabled , any attempt
to access the result (e.g. via get_data()) from a disabled device will raise Tango::DevFailed.
Otherwise an empty value will be returned.
Below is an example using the gauges group:
1// will disable: inst-c01/vac-gauge/penning-01
2gauges->disable("inst-c01/*/penn");
3
4// will disable nothing
5const bool forwarded = true;
6gauges->disable("inst-c01/vac-gauge/pirani-01", not forwarded);
7
8// will disable: inst-c01/vac-gauge/pirani-01
9gauge_family->disable("inst-c01/vac-gauge/pirani-01");
10
11// will enable: inst-c01/vac-gauge/penning-01
12gauges->enable("inst-c01/*");
13
14auto states = gauges->command_inout("State");
15for (auto& state : states)
16{
17 if (state.group_element_enabled())
18 {
19 // it's safe to access the value
20 std::cout << state.dev_name() << ": " << state.get_data() << "\n";
21 }
22 else
23 {
24 std::cout << state.dev_name() << ": is disabled\n";
25 }
26}
Forward or not forward?#
Since a Tango Group is a hierarchical object, any action performed on a group can be forwarded to its sub-groups. Most of the methods in the Group interface have a so-called forward option controlling this propagation. When set to false, the action is only performed on the local set of devices. Otherwise, the action is also forwarded to the sub-groups, in other words, propagated along the hierarchy. In C++ , the forward option defaults to true (thanks to the C++ default argument value). There is no such mechanism in Java and the forward option must be systematically specified.
Executing a command#
As a proxy for a collection of devices, the Tango Group provides an interface similar to the DeviceProxy’s. For the execution of a command, the Group interface contains several implementations of the command_inout method. Both synchronous and asynchronous forms are supported.
Obtaining command results#
Command results are returned using a Tango::GroupCmdReplyList. This is nothing but a vector containing a Tango::GroupCmdReply for each device in the group. The Tango::GroupCmdReply contains the actual data (i.e. the Tango::DeviceData). By inheritance, it may also contain any error occurred during the execution of the command (in which case the data is invalid).
We previously indicated that the Tango Group implementation guarantees that the command results are returned in the order in which its elements were attached to the group. For instance, if g1 is a group containing three devices attached in the following order:
1g1->add("my/device/01");
2g1->add("my/device/03");
3g1->add("my/device/02");
the results of
1Tango::GroupCmdReplyList crl = g1->command_inout("Status");
will be organized as follows:
crl[0]
contains the status of my/device/01
crl[1]
contains the status of my/device/03
crl[2]
contains the status of my/device/02
Things get more complicated if sub-groups are added between devices.
1g2->add("my/device/04");
2g2->add("my/device/05");
3
4
5g4->add("my/device/08");
6g4->add("my/device/09");
7
8
9g3->add("my/device/06");
10g3->add(g4);
11g3->add("my/device/07");
12
13
14g1->add("my/device/01");
15g1->add(g2);
16g1->add("my/device/03");
17g1->add(g3);
18g1->add("my/device/02");
The result order in the Tango::GroupCmdReplyList depends on the value of the forward option. If set to true, the results will be organized as follows:
1Tango::GroupCmdReplyList crl = g1->command_inout("Status", true);
crl[0]
contains the status of my/device/01 which belongs to g1
crl[1]
contains the status of my/device/04 which belongs to g1.g2
crl[2]
contains the status of my/device/05 which belongs to g1.g2
crl[3]
contains the status of my/device/03 which belongs to g1
crl[4]
contains the status of my/device/06 which belongs to g1.g3
crl[5]
contains the status of my/device/08 which belongs to g1.g3.g4
crl[6]
contains the status of my/device/09 which belongs to g1.g3.g
crl[7]
contains the status of my/device/07 which belongs to g1.g3
crl[8]
contains the status of my/device/02 which belongs to g1
If the forward option is set to false, the results are:
1Tango::GroupCmdReplyList crl = g1->command_inout("Status", false);
crl[0]
contains the status of my/device/01 which belongs to g
crl[1]
contains the status of my/device/03 which belongs to g1
crl[2]
contains the status of my/device/02 which belongs to g1
The Tango::GroupCmdReply contains some public members allowing the identification of both the device (Tango::GroupCmdReply::dev_name) and the command (Tango::GroupCmdReply::obj_name). It means that, depending of your application, you can associate a response with its source using its position in the response list or using the Tango::GroupCmdReply::dev_name member.
Case 1: a command, no argument#
As an example, we execute the Status command on the whole hierarchy synchronously.
1Tango::GroupCmdReplyList crl = gauges->command_inout("Status");
As a first step in the results processing, it could be interesting to check value returned by the has_failed() method of the GroupCmdReplyList. If it is set to true, it means that at least one error occurred during the execution of the command (i.e. at least one device gave error).
1if (crl.has_failed())
2{
3 cout << "at least one error occurred" << endl;
4}
5else
6{
7 cout << "no error " << endl;
8}
Now, we have to process each individual response in the list.
A few words on error handling and data extraction#
Depending of the application and/or the developer’s programming habits, each individual error can be handle by the C++ (or Java) exception mechanism or using the dedicated has_failed() method. The GroupReply class - which is the mother class of both GroupCmdReply and GroupAttrReply - contains a static method to enable (or disable) exceptions called enable_exception(). By default, exceptions are disabled. The following example is proposed with both exceptions enable and disable.
In C++, data can be extracted directly from an individual reply. The GroupCmdReply interface contains a template operator >> allowing the extraction of any supported Tango type (in fact the actual data extraction is delegated to DeviceData::operator >>). One dedicated extract method is also provided in order to extract DevVarLongStringArray and DevVarDoubleStringArray types to std::vectors.
Error and data handling C++ example:
1//-------------------------------------------------------
2//- synch. group command example with exception enabled
3//-------------------------------------------------------
4//- enable exceptions and save current mode
5bool last_mode = GroupReply::enable_exception(true);
6//- process each response in the list ...
7for (int r = 0; r < crl.size(); r++)
8{
9//- enter a try/catch block
10 try
11 {
12//- try to extract the data from the r-th reply
13//- suppose data contains a double
14 double ans;
15 crl[r] >> ans;
16 cout << crl[r].dev_name()
17 << "::"
18 << crl[r].obj_name()
19 << " returned "
20 << ans
21 << endl;
22 }
23 catch (const DevFailed& df)
24 {
25//- DevFailed caught while trying to extract the data from reply
26 for (int err = 0; err < df.errors.length(); err++)
27 {
28 cout << "error: " << df.errors[err].desc.in() << endl;
29 }
30//- alternatively, one can use crl[r].get_err_stack() see below
31 }
32 catch (...)
33 {
34 cout << "unknown exception caught";
35 }
36}
37//- restore last exception mode (if needed)
38GroupReply::enable_exception(last_mode);
39//- Clear the response list (if reused later in the code)
40crl.reset();
41
42
43//-------------------------------------------------------
44//- synch. group command example with exception disabled
45//-------------------------------------------------------
46//- disable exceptions and save current mode bool
47last_mode = GroupReply::enable_exception(false);
48//- process each response in the list ...
49for (int r = 0; r < crl.size(); r++)
50{
51//- did the r-th device give error?
52 if (crl[r].has_failed() == true)
53 {
54//- printout error description
55 cout << "an error occurred while executing "
56 << crl[r].obj_name()
57 << " on "
58 << crl[r].dev_name() << endl;
59//- dump error stack
60 const DevErrorList& el = crl[r].get_err_stack();
61 for (int err = 0; err < el.size(); err++)
62 {
63 cout << el[err].desc.in();
64 }
65 }
66 else
67 {
68//- no error (suppose data contains a double)
69 double ans;
70 bool result = crl[r] >> ans;
71 if (result == false)
72 {
73 cout << "could not extract double from "
74 << crl[r].dev_name()
75 << " reply"
76 << endl;
77 }
78 else
79 {
80 cout << crl[r].dev_name()
81 << "::"
82 << crl[r].obj_name()
83 << " returned "
84 << ans
85 << endl;
86 }
87 }
88}
89//- restore last exception mode (if needed)
90GroupReply::enable_exception(last_mode);
91//- Clear the response list (if reused later in the code)
92crl.reset();
Now execute the same command asynchronously. C++ example:
1//-------------------------------------------------------
2//- asynch. group command example (C++ example)
3//-------------------------------------------------------
4long request_id = gauges->command_inout_asynch("Status");
5//- do some work
6do_some_work();
7
8
9//- get results
10crl = gauges->command_inout_reply(request_id);
11//- process responses as previously describe in the synch. implementation
12for (int r = 0; r < crl.size(); r++)
13{
14//- data processing and error handling goes here
15//- copy/paste code from previous example
16. . .
17}
18//- clear the response list (if reused later in the code)
19crl.reset();
Case 2: a command, one argument#
Here, we give an example in which the same input argument is applied to all devices in the group (or its sub-groups).
In C++:
1//- the argument value
2double d = 0.1;
3//- insert it into the TANGO generic container for command: DeviceData
4Tango::DeviceData dd;
5dd << d;
6//- execute the command: Dev_Void SetDummyFactor (Dev_Double)
7Tango::GroupCmdReplyList crl = gauges->command_inout("SetDummyFactor", dd);
Since the SetDummyFactor command does not return any value, the individual replies (i.e. the GroupCmdReply) do not contain any data. However, we have to check their has_failed() method returned value to be sure that the command completed successfully on each device (acknowledgement). Note that in such a case, exceptions are useless since we never try to extract data from the replies.
In C++ we should have something like:
1//- no need to process the results if no error occurred (Dev_Void command)
2if (crl.has_failed())
3{
4//- at least one error occurred
5 for (int r = 0; r < crl.size(); r++)
6 {
7//- handle errors here (see previous C++ examples)
8 }
9}
10//- clear the response list (if reused later in the code)
11crl.reset();
See case 1 for an example of asynchronous command.
Case 3: a command, several arguments#
Here, we give an example in which a specific input argument is applied to each device in the hierarchy. In order to use this form of command_inout, the user must have an a priori and perfect knowledge of the devices order in the hierarchy. In such a case, command arguments are passed in an array (with one entry for each device in the hierarchy).
The C++ implementation provides a template method which accepts a std::vector of C++ type for command argument. This allows passing any kind of data using a single method.
The size of this vector must equal the number of device in the hierarchy (respectively the number of device in the group) if the forward option is set to true (respectively set to false). Otherwise, an exception is thrown.
The first item in the vector is applied to the first device in the hierarchy, the second to the second device in the hierarchy, and so on…That’s why the user must have a perfect knowledge of the devices order in the hierarchy.
Assuming that gauges are ordered by name, the SetDummyFactor command can be executed on group cell-01 (and its sub-groups) as follows:
Remember, cell-01 has the following internal structure:
1-> gauges
2 | -> cell-01
3 | |-> inst-c01/vac-gauge/strange
4 | |-> penning
5 | | |-> inst-c01/vac-gauge/penning-01
6 | | |-> inst-c01/vac-gauge/penning-02
7 | | |-> ...
8 | | |-> inst-c01/vac-gauge/penning-xx
9 | |-> pirani
10 | |-> inst-c01/vac-gauge/pirani-01
11 | |-> ...
12 | |-> inst-c01/vac-gauge/pirani-xx
Passing a specific argument to each device in C++:
1//- get a reference to the target group
2Tango::Group *g = gauges->get_group("cell-01");
3//- get number of device in the hierarchy (starting at cell-01)
4long n_dev = g->get_size(true);
5//- Build argin list
6std::vector<double> argins(n_dev);
7//- argument for inst-c01/vac-gauge/strange
8argins[0] = 0.0;
9//- argument for inst-c01/vac-gauge/penning-01
10argins[1] = 0.1;
11//- argument for inst-c01/vac-gauge/penning-02
12argins[2] = 0.2;
13//- argument for remaining devices in cell-01.penning
14. . .
15//- argument for devices in cell-01.pirani
16. . .
17//- the reply list
18Tango::GroupCmdReplyList crl;
19//- enter a try/catch block (see below)
20try
21{
22//- execute the command
23 crl = g->command_inout("SetDummyFactor", argins, true);
24 if (crl.has_failed())
25 {
26//- error handling goes here (see case 1)
27 }
28}
29catch (const DevFailed& df)
30{
31//- see below
32}
33crl.reset();
If we want to execute the command locally on cell-01 (i.e. not on its sub-groups), we should write the following C++ code:
1//- get a reference to the target group
2Tango::Group *g = gauges->get_group("cell-01");
3//- get number of device in the group (starting at cell-01)
4long n_dev = g->get_size(false);
5//- Build argin list
6std::vector<double> argins(n_dev);
7//- argins for inst-c01/vac-gauge/penning-01
8argins[0] = 0.1;
9//- argins for inst-c01/vac-gauge/penning-02
10argins[1] = 0.2;
11//- argins for remaining devices in cell-01.penning
12. . .
13//- the reply list
14Tango::GroupCmdReplyList crl;
15//- enter a try/catch block (see below)
16try
17{
18//- execute the command
19 crl = g->command_inout("SetDummyFactor", argins, false);
20 if (crl.has_failed())
21 {
22//- error handling goes here (see case 1)
23 }
24}
25catch (const DevFailed& df)
26{
27//- see below
28}
29crl.reset();
Note: if we want to execute the command locally on cell-01 (i.e. not on its sub-groups), we should write the following code:
1//- get a reference to the target group
2Group g = gauges.get_group("cell-01");
3//- get pre-build arguments list for the group (starting@cell-01)
4DeviceData[] argins = g.get_command_specific_argument_list(false);
5//- argins for inst-c01/vac-gauge/penning-01
6argins[0].insert(0.1);
7//- argins for inst-c01/vac-gauge/penning-02
8argins[1].insert(0.2);
9//- argins for remaining devices in cell-01.penning
10. . .
11//- the reply list
12GroupCmdReplyList crl;
13//- enter a try/catch block (see below)
14try
15{
16//- execute the command
17 crl = g.command_inout("SetDummyFactor", argins, false, false);
18 if (crl.has_failed())
19 {
20//- error handling goes here (see case 1)
21 }
22}
23catch (DevFailed d)
24{
25//- see below
26}
This form of command_inout (the one that accepts an array of value as its input argument), may throw an exception before executing the command if the number of elements in the input array does not match the number of individual devices in the group or in the hierarchy (depending on the forward option).
An asynchronous version of this method is also available. See case 1 for an example of asynchronous command.
Reading attribute(s)#
In order to read attribute(s), the Group interface contains several implementations of the read_attribute() and read_attributes() methods. Both synchronous and asynchronous forms are supported. Reading several attributes is very similar to reading a single attribute. Simply replace the std::string used for attribute name by a vector of std::string with one element for each attribute name. In case of read_attributes() call, the order of attribute value returned in the GroupAttrReplyList is all attributes for first element in the group followed by all attributes for the second group element and so on.
Obtaining attribute values#
Attribute values are returned using a GroupAttrReplyList. This is nothing but an array containing a GroupAttrReply for each device in the group. The GroupAttrReply contains the actual data (i.e. the DeviceAttribute). By inheritance, it may also contain any error occurred during the execution of the command (in which case the data is invalid).
Here again, the Tango Group implementation guarantees that the attribute values are returned in the order in which its elements were attached to the group. See Obtaining command results for details.
The GroupAttrReply contains some public methods allowing the identification of both the device (GroupAttrReply::dev_name) and the attribute (GroupAttrReply::obj_name). It means that, depending of your application, you can associate a response with its source using its position in the response list or using the Tango::GroupAttrReply::dev_name member.
A few words on error handling and data extraction#
Here again, depending of the application and/or the developer’s programming habits, each individual error can be handle by the C++ exception mechanism or using the dedicated has_failed() method. The GroupReply class - which is the mother class of both GroupCmdReply and GroupAttrReply - contains a static method to enable (or disable) exceptions called enable_exception(). By default, exceptions are disabled. The following example is proposed with both exceptions enable and disable.
In C++, data can be extracted directly from an individual reply. The GroupAttrReply interface contains a template operator>> allowing the extraction of any supported Tango type (in fact the actual data extraction is delegated to DeviceAttribute::operator>>).
Reading an attribute is very similar to executing a command.
Reading an attribute in C++:
1//-----------------------------------------------------------------
2//- synch. read "vacuum" attribute on each device in the hierarchy
3//- with exceptions enabled - C++ example
4//-----------------------------------------------------------------
5//- enable exceptions and save current mode
6bool last_mode = GroupReply::enable_exception(true);
7//- read attribute
8Tango::GroupAttrReplyList arl = gauges->read_attribute("vacuum");
9//- for each response in the list ...
10for (int r = 0; r < arl.size(); r++)
11{
12//- enter a try/catch block
13 try
14 {
15//- try to extract the data from the r-th reply
16//- suppose data contains a double
17 double ans;
18 arl[r] >> ans;
19 cout << arl[r].dev_name()
20 << "::"
21 << arl[r].obj_name()
22 << " value is "
23 << ans << endl;
24 }
25 catch (const DevFailed& df)
26 {
27//- DevFailed caught while trying to extract the data from reply
28 for (int err = 0; err < df.errors.length(); err++)
29 {
30 cout << "error: " << df.errors[err].desc.in() << endl;
31 }
32//- alternatively, one can use arl[r].get_err_stack() see below
33 }
34 catch (...)
35 {
36 cout << "unknown exception caught";
37 }
38}
39//- restore last exception mode (if needed)
40GroupReply::enable_exception(last_mode);
41//- clear the reply list (if reused later in the code)
42arl.reset();
In C++, an asynchronous version of the previous example could be:
1//- read the attribute asynchronously
2long request_id = gauges->read_attribute_asynch("vacuum");
3//- do some work
4do_some_work();
5
6
7//- get results
8Tango::GroupAttrReplyList arl = gauges->read_attribute_reply(request_id);
9//- process replies as previously described in the synch. implementation
10for (int r = 0; r < arl.size(); r++)
11{
12//- data processing and/or error handling goes here
13...
14}
15//- clear the reply list (if reused later in the code)
16arl.reset();
Writing an attribute#
The Group interface contains several implementations of the write_attribute() method. Both synchronous and asynchronous forms are supported. However, writing more than one attribute at a time is not supported.
Obtaining acknowledgement#
Acknowledgements are returned using a GroupReplyList. This is nothing but an array containing a GroupReply for each device in the group. The GroupReply may contain any error occurred during the execution of the command. The return value of the has_failed() method indicates whether an error occurred or not. If this flag is set to true, the GroupReply::get_err_stack() method gives error details.
Here again, the Tango Group implementation guarantees that the attribute values are returned in the order in which its elements were attached to the group. See Obtaining command results for details.
The GroupReply contains some public members allowing the identification of both the device (GroupReply::dev_name) and the attribute (GroupReply::obj_name). It means that, depending of your application, you can associate a response with its source using its position in the response list or using
Case 1: one value for all devices#
Here, we give an example in which the same attribute value is written on all devices in the group (or its sub-groups). Exceptions are supposed to be disabled.
Writing an attribute in C++:
1//-----------------------------------------------------------------
2//- synch. write "dummy" attribute on each device in the hierarchy
3//-----------------------------------------------------------------
4//- assume each device support a "dummy" writable attribute
5//- insert the value to be written into a generic container
6Tango::DeviceAttribute value(std::string("dummy"), 3.14159);
7//- write the attribute
8Tango::GroupReplyList rl = gauges->write_attribute(value);
9//- any error?
10if (rl.has_failed() == false)
11{
12 cout << "no error" << endl;
13}
14else
15{
16 cout << "at least one error occurred" << endl;
17//- for each response in the list ...
18 for (int r = 0; r < rl.size(); r++)
19 {
20//- did the r-th device give error?
21 if (rl[r].has_failed() == true)
22 {
23//- printout error description
24 cout << "an error occurred while reading "
25 << rl[r].obj_name()
26 << " on "
27 << rl[r].dev_name()
28 << endl;
29//- dump error stack
30 const DevErrorList& el = rl[r].get_err_stack();
31 for (int err = 0; err < el.size(); err++)
32 {
33 cout << el[err].desc.in();
34 }
35 }
36 }
37}
38//- clear the reply list (if reused later in the code)
39rl.reset();
Here is a C++ asynchronous version:
1//- insert the value to be written into a generic container
2Tango::DeviceAttribute value(std::string("dummy"), 3.14159);
3//- write the attribute asynchronously
4long request_id = gauges.write_attribute_asynch(value);
5//- do some work
6do_some_work();
7
8
9//- get results
10Tango::GroupReplyList rl = gauges->write_attribute_reply(request_id);
11//- process replies as previously describe in the synch. implementation ...
Case 2: a specific value per device#
Here, we give an example in which a specific attribute value is applied to each device in the hierarchy. In order to use this form of write_attribute(), the user must have an a priori and perfect knowledge of the devices order in the hierarchy.
The C++ implementation provides a template method which accepts a std::vector of C++ type for command argument. This allows passing any kind of data using a single method.
The size of this vector must equal the number of device in the hierarchy (respectively the number of device in the group) if the forward option is set to true (respectively set to false). Otherwise, an exception is thrown.
The first item in the vector is applied to the first device in the group, the second to the second device in the group, and so on…That’s why the user must have a perfect knowledge of the devices order in the group.
Assuming that gauges are ordered by name, the dummy attribute can be written as follows on group cell-01 (and its sub-groups) as follows:
Remember, cell-01 has the following internal structure:
1-> gauges
2 | -> cell-01
3 | |-> inst-c01/vac-gauge/strange
4 | |-> penning
5 | | |-> inst-c01/vac-gauge/penning-01
6 | | |-> inst-c01/vac-gauge/penning-02
7 | | |-> ...
8 | | |-> inst-c01/vac-gauge/penning-xx
9 | |-> pirani
10 | |-> inst-c01/vac-gauge/pirani-01
11 | |-> ...
12 | |-> inst-c01/vac-gauge/pirani-xx
C++ version:
1//- get a reference to the target group
2Tango::Group *g = gauges->get_group("cell-01");
3//- get number of device in the hierarchy (starting at cell-01)
4long n_dev = g->get_size(true);
5//- Build value list
6std::vector<double> values(n_dev);
7//- value for inst-c01/vac-gauge/strange
8values[0] = 3.14159;
9//- value for inst-c01/vac-gauge/penning-01
10values[1] = 2 * 3.14159;
11//- value for inst-c01/vac-gauge/penning-02
12values[2] = 3 * 3.14159;
13//- value for remaining devices in cell-01.penning
14. . .
15//- value for devices in cell-01.pirani
16. . .
17//- the reply list
18Tango::GroupReplyList rl;
19//- enter a try/catch block (see below)
20try
21{
22//- write the "dummy" attribute
23 rl = g->write_attribute("dummy", values, true);
24 if (rl.has_failed())
25 {
26//- error handling (see previous cases)
27 }
28}
29catch (const DevFailed& df)
30{
31//- see below
32}
33rl.reset();
Note: if we want to execute the command locally on cell-01 (i.e. not on its sub-groups), we should write the following code
1//- get a reference to the target group
2Tango::Group *g = gauges->get_group("cell-01");
3//- get number of device in the group
4long n_dev = g->get_size(false);
5//- Build value list
6std::vector<double> values(n_dev);
7//- value for inst-c01/vac-gauge/penning-01
8values[0] = 2 * 3.14159;
9//- value for inst-c01/vac-gauge/penning-02
10values[1] = 3 * 3.14159;
11//- value for remaining devices in cell-01.penning
12. . .
13//- the reply list
14Tango::GroupReplyList rl;
15//- enter a try/catch block (see below)
16try
17{
18//- write the "dummy" attribute
19 rl = g->write_attribute("dummy", values, false);
20 if (rl.has_failed())
21 {
22//- error handling (see previous cases)
23 }
24}
25catch (const DevFailed& df)
26{
27//- see below
28}
29rl.reset();
This form of write_attribute() (the one that accepts an array of value as its input argument), may throw an exception before executing the command if the number of elements in the input array does not match the number of individual devices in the group or in the hierarchy (depending on the forward option).
An asynchronous version of this method is also available.
Reading/Writing device pipe#
Reading or writing device pipe is made possible using DeviceProxy class methods. To read a pipe, you have to use the method read_pipe(). To write a pipe, use the write_pipe() method. A method write_read_pipe() is also provided in case you need to write then read a pipe in a non-interuptible way. All these calls generate synchronous request and support only reading or writing a single pipe at a time. Those pipe related DeviceProxy class methods (read_pipe, write_pipe,…) use DevicePipe class instances. A DevicePipe instance is nothing more than a string for the pipe name and a DevicePipeBlob instance called the root blob. In a DevicePipeBlob instance, you have:
The blob name
One array of DataElement. Each instance of this DataElement class has:
A name
A value which can be either
Scalar or array of any basic Tango type
Another DevicePipeBlob
Therefore, this is a recursive data structure and you may have DevicePipeBlob in DevicePipeBlob. There is no limit on the depth of this recursivity even if it is not recommended to have a too large depth. The following figure summarizes DevicePipe data structure
Figure 4.1: DevicePipe data structure#
Many methods to insert/extract data into/from a DevicePipe are available. In the DevicePipe class, these methods simply forward their action to the DevicePipe root blob. The same methods are available in the DevicePipeBlob in case you need to use the recursivity provided by this data structure.
Reading a pipe#
When you read a pipe, you have to extract data received from the pipe. Because data transferred through a pipe can change at any moment, two differents cases are possible:
The client has a prior knowledge of what should be transferred through the pipe
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:
The data element name (a C++ string): name
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)#
Developing your first Java TANGO client#
Developing your first Java TANGO device class#
In this section we describe how one can start developing Tango device server using Java.
Three methods will be described:
Using jtango-maven-archetype
Using POGO
Starting from scratch
Prerequisites
Java >1.7
Maven >3
Tango-Controls environment (Tango Database aka Tango host is deployed)
Using jtango-maven-archetype#
Perhaps the simplest way to start to develop your first Tango device server in Java is to use jtango-maven-archetype.
Maven is an Apache project and it is widely used in Java development nowdays. More information can be found in the Internet. Here we just name main features of Maven:
First of all Maven is a build system, i.e. it automatizes the build process of the project. As Maven is a plugin platform various plugins are used to achieve the desired result e.g. define compilation target (aka javac -target 1.8) or package the project into a single executable jar.
Secondly Maven automatically manages dependencies (required versions are being automatically downloaded from so called Maven central repository from the Internet).
Finally Maven provides a way to generate skeleton projects. This section is based on this feature.
So to start execute the following command:
$> mvn archetype:generate \
-DarchetypeGroupId=org.tango-controls \
-DarchetypeArtifactId=jtango-maven-archetype \
-DarchetypeVersion=1.4
This command generates skeleton project using special Maven artifact that defines the template of the project. While generating new project you have to define several properties:
groupId – target project’s groupId. Typically it is reversed domain name of the company e.g. com.company
artifactId – target project’s artifactId. This is can be considered as the name of the target executable. This value must follow java class naming conventions e.g. MyDevice
version – target project version. Simply the first version of the project e.g. 1.0-SNAPSHOT
package – Java package for newly generated class. Typically can be left as default i.e. groupId
license – name of the license under which the project is distributed e.g. LGPL-3, GPL, MIT etc
organization – name of the organization that maintains the project e.g. Company
organization-url – organization’s URL e.g. http://www.company.com
author-name – name of the author/maintainer e.g. JoeDoe
author-email – author/maintainer’s email e.g. joe.doe@company.com
facility – facility at which project is being developed e.g. DESY, ESRF, etc
platform – Windows, MacOS, Unix/Linux etc. Typically Java projects will have All in this property
family – as in POGO. Typically Java high level projects will have SoftwareSystem in this property
bus – bus to the device (underlying hardware) e.g. Serial. For Java this might be NA if there is no real hardware associated with this Tango server.
jtango-version – a version of JTango dependency or LATEST if you are know what are you doing.
Latest version of JTango is
The following output indicates that project has been successfully generated:
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
Now you can goto to the project folder and build it:
$>cd MyDevice
$>mvn package
$>java -jar target/MyDevice-1.0-SNAPSHOT.jar development
Assuming that Tango-Controls environment is set up properly (TODO ref) and MyDevice/development (TODO ref) server is defined in the Tango Database the later command will start the device server.
Now using your favorite IDE open the newly generated project and develop your JTango server. Please read more in JTango documentation.
Using POGO#
See POGO documentation.
Starting from scratch#
Example Java device and detailed documentation can be found in the JTango documentation.
Integrate Java Tango servers with Astor#
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:
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
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)#
Developing Python TANGO device class#
How to PyTango#
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#
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:#
Tango Java Archiving, ArchivingRoot from sourceforge,
Taurus (optional)
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#
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#
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
DatabaseDSDevice Serverska-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#
One wants to test a Device Server using the full tango stack. This test must not be related to any environment and be performed together with other tests. It is possible to start the tango server without database or with a File Database for testing. These are the default approaches in Python or C++ and that would work for Java as well. The below solution in Java is yet another approach.
Below is how one can integrate it into JUnit framework:
1 public class ServerTest {
2
3 public static final String NO_DB_GIOP_PORT = "12345";//any random non-occupied port
4 public static final String NO_DB_INSTANCE = "test_server";
5 public static final String NO_DB_DEVICE_NAME = "test/junit/" +NO_DB_INSTANCE;
6 //our server will run in a dedicated thread
7 private final ExecutorService server = Executors.newSingleThreadExecutor(new ThreadFactory() {
8 @Override
9 public Thread newThread(Runnable r) {
10 Thread thread = new Thread(r);
11 thread.setName("My fancy Tango server");
12 thread.setDaemon(true);
13 return thread;
14 }
15 });
16
17 @Before
18 public void before() throws InterruptedException {
19 System.setProperty("OAPort", NO_DB_GIOP_PORT);
20
21 final CountDownLatch latch = new CountDownLatch(1);
22 server.submit(new Runnable() {
23 @Override
24 public void run() {
25 ServerManager.getInstance().start(new String[]{NO_DB_INSTANCE, "-nodb", "-dlist", NO_DB_DEVICE_NAME}, TestServer.class);//TestServer - is our Tango server we want to test against
26 latch.countDown();
27 }
28 });
29 //make sure server has been started before leaving method
30 latch.await();
31 }
32
33 @After
34 public void after() {
35 server.shutdownNow();
36 }
37
38
39 @Test
40 public void testGetClientId() throws Exception {
41 TangoProxy proxy = TangoProxies.newDeviceProxyWrapper("tango://localhost:" + NO_DB_GIOP_PORT + "/" + NO_DB_DEVICE_NAME + "#dbase=no");
42
43 assertSame(DeviceState.ON,proxy.readAttribute("State"));
44 }
45 }
It is also possible to use 3rd party Tango server to test against. Suppose our Tango server uses other Tango server to perform its tasks (Data aggregation, state monitoring etc). In this case 3rd party binary can be added to the project. This binary can be then launched from within JUnit test:
1 //this test cases uses precompiled TangoTest stored in {PRJ_ROOT}/exec/tango/<win64|debian> folder
2 public class TangoTestProxyWrapperTest {
3 public static final String TANGO_DEV_NAME = "test/local/0";
4 public static final int TANGO_PORT = 16547;
5 public static final String TEST_TANGO = "tango://localhost:" + TANGO_PORT + "/" + TANGO_DEV_NAME + "#dbase=no";
6 public static final String X64 = "x64";
7 public static final String LINUX = "linux";
8 public static final String WINDOWS_7 = "windows 7";
9 public static final String AMD64 = "amd64";
10
11 private static Process PRC;
12
13 @BeforeClass
14 public static void beforeClass() throws Exception {
15 String crtDir = System.getProperty("user.dir");
16 //TODO define executable according to current OS
17 String os = System.getProperty("os.name");
18 String arch = System.getProperty("os.arch");
19 StringBuilder bld = new StringBuilder(crtDir);
20 //TODO other platforms or rely on the environmet
21 if (LINUX.equalsIgnoreCase(os) && AMD64.equals(arch))
22 bld.append("/exec/tango/debian/").append("TangoTest");
23 else if (WINDOWS_7.equalsIgnoreCase(os) && AMD64.equals(arch))
24 bld.append("\\exec\\tango\\win64\\").append("TangoTest");
25 else
26 throw new RuntimeException(String.format("Unsupported platform: name=%s arch=%s", os, arch));
27
28 PRC = new ProcessBuilder(bld.toString(), "test", "-ORBendPoint", "giop:tcp::" + TANGO_PORT, "-nodb", "-dlist", TANGO_DEV_NAME)
29 .start();
30
31 //drain slave's out stream
32 new Thread(new Runnable() {
33 @Override
34 public void run() {
35 char bite;
36 try {
37 while ((bite = (char) PRC.getInputStream().read()) > -1) {
38 System.out.print(bite);
39 }
40 } catch (IOException ignore) {
41 }
42 }
43 }).start();
44
45 //drains slave's err stream
46 new Thread(new Runnable() {
47 @Override
48 public void run() {
49 char bite;
50 try {
51 while ((bite = (char) PRC.getErrorStream().read()) > -1) {
52 System.err.print(bite);
53 }
54 } catch (IOException ignore) {
55 }
56 }
57 }).start();
58 }
59
60 @AfterClass
61 public static void afterClass() throws Exception {
62 PRC.destroy();
63 }
64
65 //this test directly writes/reads to/from TangoTest double_scalar_w
66 @Test
67 public void testWriteReadAttribute_Double() throws Exception {
68 TangoProxy instance = TangoProxies.newDeviceProxyWrapper(TEST_TANGO);
69
70 instance.writeAttribute("double_scalar_w", 0.1984D);
71
72 double result = instance.<Double>readAttribute("double_scalar_w");
73
74 assertEquals(0.1984D, result);
75 }
76
77 //in other test case one can create instance of his own server (see previous code snippet)
78
79 }
Contributing#
Instructions on how to contribute to the main projects within Tango can be found under this section. This include contributing to:
The Tango IDL: the CORBA IDL file
cppTango: the C++ library for Tango
pyTango: the Python bindings for Tango
jTango: the Java implementation of the Tango Kernel
Tango Documentation: this documentation of the Tango project
Tango IDL#
cppTango#
For details on how to contribute to cppTango please see the contributing page on the GitLab project.
PyTango#
For information on how to contribute to PyTango, see the project’s How to Contribute guide.
JTango#
Tango Documentation#
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/mainbranch.
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#
Fork the tango-doc repository:
From the tango-controls/tango-doc click Fork in the top right corner.
Clone your forked repository to work on it locally:
git clone git@gitlab.com:<user>/tango-doc.git
The
originremote will point to your forked repository.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 to9.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
Edit the appropriate file (or create it if it doesn’t exist).
Make sure that the file appears in the relevant toc-tree (in the
index.mdfile or in the mastersource/index.md).Check if your changes have built correctly:
make clean html
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#
Add modifications to a commit list. For example:
git add source/How-To/contributing/docs.md git add source/How-To/contributing/contributing.md
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.
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 the9.5.2, you need to callgit rebase 9.5.2
Pushing (to the GitLab repository)#
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)#
Go to your branch on your forked repository.
Click Create merge request.
Check the
Fromandintolocations.Fromshould be the branch on your forked repository andintoshould be into the upstream repositorytango-doc:mainbranch.Provide a relevant comment and check that the
CommitsandChangesare as expected.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.
Add the project as a subproject of tango-controls on Read the Docs:
From the Read the Docs project: Project > Admin > Subproject > Add subproject.
Configure the subproject mapping in the parent tango-controls project.
In the
src/conf.pyadd to theintersphinx_mappingdefinition:intersphinx_mapping = { 'subproj_name': ('https://<subproject_name>.readthedocs.io/en/latest/', None) }
Configure the mapping in the subproject to be able to link to the parent tango-controls project
In the subproject
src/conf.pyensure theintersphinxextension is specified:extensions = [ ... 'sphinx.ext.intersphinx', ]
In the subproject
src/conf.pyadd to theintersphinx_mappingdefinition:intersphinx_mapping = { 'tango-controls': ('https://tango-controls.readthedocs.io/en/latest/', None) }
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)
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#
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#
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”#
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#
Overview#
LogViewer is an application for displaying logs pushed by the devices to the Tango logging system. You can fetch logs from different Tango devices and filter them by several properties to find what you need.
LogViewer 2.0.1 main window#
1 - Menu bar
2 - Filter settings
3 - Clear button - clears the logs window
4 - Pause button
5 - Device tree
6 - Logs
7 - History of actions
Tango Logging#
Tango provides a logging service for devices. It is configured by several device properties, and support sending logs to various targets, such as files, log consumer devices or the console. For more information you can check out more details about logging service .
Adding devices#
To add logs from device to the viewer you need to right click on the device in the device tree and click add. Then you need to set which level logs from that device should be visible. You can do this with Set Logging Level options in the device context menu.
Add logs from device#
Alternatively, you can add device with log level already set with Add/Set Logging Level context menu option.
Add and set logging level#
The new device logs will then be displayed in LogViewer window.
Add colocated devices#
The Add Colocated option adds all devices, running on the same
device server instance, as logging sources. For example, if you have a
TangoTest/test instance with sys/tg_test/1 and
sys/tg_test/2 devices, choosing Add Colocated on any of
these devices will add both of them. The same applies for
Remove Colocated and Set Logging Level (colocated)
options.
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#
The Tango Database can be maintained using a command-line interface with the tango_admin tool.
The following features are available:
Ping the tango database server
Check if a device/device server is defined
Create/Delete a server
Create a property
List server/devices
Unexport devices
Tango Admin help#
tango_admin --help
Output:
Usage:
--help Prints this help
--ping-database [max_time (s)] Ping database
--check-device <dev> Check if the device is defined in DB
--check-device-exported <dev> Check if the device is exported in DB
--add-server <exec/inst> <class> <dev list (comma separated)> Add a server in DB
--delete-server <exec/inst> [--with-properties] Delete a server from DB
--check-server <exec/inst> Check if a device server is defined in DB
--server-list Display list of server names
--server-instance-list <exec> Display list of server instances for the given server name
--add-property <dev> <prop_name> <prop_value (comma separated for array)> Add a device property in DB
--delete-property <dev> <prop_name> Delete a device property from DB
--tac-enabled Check if the TAC (Tango Access Control) is enabled
--ping-device <dev> [max_time (s)] Check if the device is running
--ping-network [max_time (s)] [-v] Ping network
--unexport-device <dev> Unexport a device from DB. USE WITH CARE.
This is the output of version 1.24.
Examples#
Adding a device server#
# For python this is the device server source file name without `.py` extension,
# for C++ this is the executable name
executable=fancyMotor
class=fancyMotorClass
instance=1
device=vacuum/innerCircle/valve1
tango_admin --add-server "${executable}/${instance}" $class $device
Adding a device server property#
device=vacuum/innerCircle/valve1
tango_admin --add-property $device RemotePort 127.0.0.1
Checking if a device server is alive#
tango_admin --check-device sys/database/2
Checking if a device server is exported#
tango_admin --check-device-exported sys/tg_test/1
JDraw#
See Jdraw documentation project.
ITango#
ITango is a PyTango CLI based on IPython. It is designed to be used as an IPython profile.
See ITango documentation.
Taurus (Python GUI library)#
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#
Pogo is the TANGO code generator written in Java swing.
This graphical interface allows to define a TANGO class model.
This class model will be saved in a .xmi file.
- From this TANGO class model Pogo is able to generate:
a device server in C++, Java or Python.
An html documentation based on information entered during class model creation.
The code generation part is based on EMF (Eclipse Model Framework) associated with Xtext and Xtend classes.
Requirement: This tool needs java 1.7 or higher to be able to run.
Source:
project: tango-controls/pogo
download: https://repo1.maven.org/maven2/org/tango-controls/Pogo/
Bensikin User Manual#
Authors:
R.Girardot, M.Ounsy, A.Buteau, M.Thiam - Soleil
Introduction#
This document is an end-user guide to using the Bensikin application, and a brief developer-oriented presentation of the application’s architecture.
Application’s context: Contexts and Snapshots#
A snapshot is, as said in the name, a “picture” of a list of equipment’s “settings” (more precisely of their Tango attributes values) taken at a precise instant.
A snapshot is based on a context, which is a group of Tango devices and their attributes that must be part of the snapshots. A context is described by meta-data (author, description, etc.), so that users know which context is used what for.
A typical use case of a snapshot is to memorize a particular configuration of a set of equipment’s, to be able in the future to reset them to the values of this snapshot (for example, reposition all Insertion devices to their parking position after a beam loss).
Application’s description and goals#
Application’s goals#
Bensikin allows the user to define contexts and take snapshots. Snapshots can be saved as files and modified.
Bensikin is ready for multi-user use, which has for consequence the need to define accounts. An account is a way to map a user with a working directory. An important consequence of this is that 2 different users must not use the same working directory, or you may encounter application misbehaviors. An account has a name and a path to a working directory.
Bensikin is thus naturally divided (both in functionalities and display) in three parts:
- The account part, which is an introduction to the rest of the
application
The context part
The snapshot part
A first look to Bensikin#
The Bensikin Account Manager is here to manage accounts, which means:
Creating a new account
Deleting an existing account
Launching application with an account chosen in a list
Figure 1: Bensikin Account Manager#
The Context Control Panel is where user can manage contexts, which means creating, loading and modifying contexts, and launching snapshots based on the defined contexts.
The Snapshot Control Panel is where user can manage snapshots, which means saving snapshots in files, loading snapshots from database and files, temporary modify snapshots attributes values and set equipment with defined snapshots (with or without modifying snapshots) or a subpart of them.
The application’s logs panel displays the application information and error messages (like database interaction, encountered problems, etc.)
The Menu and the Tool bar are for actions shortcuts and application’s options.
Figure 2: Bensikin main panel#
Account Manager#
The Figure 1: Bensikin Account Manager presents the account Manager Interface, on application start. With this manager, you can create a new account, or delete or use an existing one.
To quit the application, simply click on
button.
Existing accounts are listed in the account Selection Combo Box, which
you can reload by clicking on
button (if you think that someone
could have modified it by creating a new account or deleting an existing
one, for example).
Creating a new account#
To create a new account, click on the button
(at the bottom
left of the panel). A new dialog will appear, as following.
Figure 3: Creating a new account#
In this new dialog, you will have to enter the name of your new
account and the path of the application working directory for this
account. If you prefer, you can browse for the path by clicking on
the
button. Then, a classic browsing dialog will be
displayed, in which you can choose the directory. When both fields
(Name and Path) are fulfilled, click on
button to
validate your new account, which will be automatically added in the
list of existing accounts. If you click on
button, you go back to the first dialog, as presented in
Figure 1: Bensikin Account Manager ,
and nothing is done.
Deleting an existing Account#
To delete an existing account, first select the account in the account selection combo box, as following:
Figure 4: Account selection#
When the account is selected, click on Delete button to delete it. If you do it, you won’t be able to use this account any more (and no other user either), because the account is definitely removed from list. The account deletion doesn’t involve the corresponding directory (neither its content) deletion.
If you want to see your account path, you can check Show account path.
Figure 5: Show account path#
Launching application with an existing account#
To launch application with an existing account, first select the account in the account selection combo box, as presented in Figure 4: Account selection .
Then, click on Ok button, and you will reach the application main panel configured with this account (the account name is displayed in frame title).
Contexts Management#
This section describes how to control contexts with Bensikin. A context is a list of attributes for which you can make a snapshot. A context has an ID and a creation date, both defined by the database. A context also has a name, an author, a reason and a description. The reason usually describes why the context was created (example: because of an incident or in order to set some equipment), whereas the description is here to have an idea of what kind of attributes you will find in this context.
Contexts are managed in the context control panel:
Figure 6: Context control panel#
Creating a new context#
To create a new context, click on the new icon in toolbar
(
), or choose option to make a new context from File menu
or Contexts menu:

You also are ready to make a new context at application first start
or by clicking on the reset icon (
):
Figure 7: Application first start#
The difference between the reset icon(
) and the new
icon (
) is, that the “reset” icon will clear every panel,
whereas the “new” icon will only clear the snapshot list and the
Context Details sub panel.
Classic way (tree)#
The tree on the left side of the Context Details sub panel allows you to check for available attributes. The one on the right side represents your context attributes.
To add attributes in your context browse the left tree, select
attributes (represented by the icon
), and click on the
green arrow
to transfer them to the right tree.
To remove attributes from your context, select them in the right
tree and click on the red cross
.
Finally, fill the context Meta data (Name, Author, Reason and
Description) in the corresponding fields (Note that filling the
fields activates the register button
).
Then, you can save your context in database by clicking on the
register button
.
Doing so will deactivate the register button and activate the
launch snapshot button
.
You can save your context in a file using the save
icon
.
Alternate way (table)#
To select this alternate way, go to tools menu and select options.
Then select the context tab and click on the table radio button.
Figure 8: Option –context tab#
Click on the ok button. The context panel now has the “table selection mode”.
Figure 9: Bensikin with context table selection mode (new context)#
Attribute selection and automatic attributes adding:
Choose a Domain. This refreshes the list of possible Device classes for this Domain.
Choose a Device class. This refreshes the list of possible Attributes for this Domain and Device class.
Choose an Attribute and press OK:
All Attributes with the selected name AND belonging to any Device of the selected Class and Domain are added to the current Context’s list of attributes.
All new attributes are light red until the Context is registered.
Line level sub-selection of loaded attributes:
Each attributes are initially checked, but this check can be removed by the user. When the user clicks on validate, all unchecked attributes will be removed from the current Context.
Click All to select all lines
Click None to select no lines
Highlight lines in the list (CTRL and SHIFT are usable), then click Reverse highlighted to reverse the checked/unchecked status of all highlighted lines.
As for the classic way, you will have to fill the Meta data fields
and register your context in database by clicking on the register
button 
Modifying an existing context#
As a matter of fact, you can’t really “modify” a context. What you can do is to create a new context with its information (attributes and Meta data) based on another one.
The very difference is in alternate mode, where former attributes are in white and new ones in light red:
Figure 10: Bensikin with context table selection mode (modified context)#
The “register” button changed a little too: its text is Register this context instead of “Register this new context”, as you can see on the figure above.
Loading a context#
There are 2 ways to load a context:
Load it from the database
Load it from a file
In both cases, loading a context will apply a quick filter on the snapshot list, so you can see the snapshots about this context that have been created this day (the day when you load the context).
Loading a context from database#
In the Contexts menu, choose load then select DB:

A dialog will then appear to allow you to filter the list of contexts in database following different criteria:
Figure 11: Data base Context filter dialog#
Select no criterion to search for all contexts present in database.
Click on the
button to apply the filter. The list of
corresponding contexts will then appear in the Context List sub
panel, as shown in Figure 6: Context control panel . Double click
on a context in table to load it and see its details in the Context
Details sub panel (See Figure 6: Context control panel ).
If there are too many contexts in the list, you can remove some
contexts from list (not from database) by selecting them in list and
clicking on the cross on the top right corner of the list:

Loading a context from file#
In the Contexts menu, choose load then select File, or in File menu choose load then select Context:

A classic file browser will appear. Search for your “.ctx” file and select it to load the corresponding context in the Context Details sub panel (See Figure 6: Context control panel ).
Printing a context#
Once you have context ready, click on the print icon (
)
and select context:

The classic print dialog will then appear. Validate your print configuration to print an xml representation of your context.
User can also print context by pressing the button 
Saving a context#
Once you have context ready, click on the save icon (
)
and select context:

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

Then, the behavior is “Word-like”. This means that if this is the first time you save this context, you will see the classic file browser to choose where to save your context, with file name. However, else, it will automatically save in the corresponding file. If you want to save in another file, you have to go to File menu, select Save As and click on Context or go to Contexts menu and click on Save As:

Snapshot Management#
This section describes how to control snapshots with Bensikin. A Snapshot is a view of your equipment at a precise date, view based on a context. A Snapshot has an ID, a creation date (Time), and a comment to describe it (which can be left empty).
Snapshots are managed in the snapshot control panel:
Figure 12: Snapshot control panel#
Creating a new snapshot#
To create a new snapshot, first select a valid context in the
context control panel (see Figure 6: Context control panel ). Then
click on the button
. The corresponding snapshot is added
in the list of snapshots in the Snapshot List sub panel.
Loading a snapshot#
There are 2 ways to load a snapshot:
Load it from the database
Load it from a file
Loading a snapshot from database#
Loading a snapshot from database consists in adding this snapshot in the list of snapshots in the Snapshot List sub panel.
As you can see in Figure 12: Snapshot control panel ,
the Snapshot List sub panel allows you to filter snapshots from database to find
the snapshot you want to load. However, have in mind that this
filter is “context dependent”, which means that the snapshots which
will appear in the list by clicking on the filter button
(
) are the one that correspond to your filter criteria
AND the selected context in the Context Control Panel. If the
filter is cleared (which you can obtain by clicking on the
button
), you will search for all the snapshots in
database that correspond to the selected context.
Loading a snapshot from file#
In the Snapshots menu, choose load then select File, or in File menu choose load then select Snapshot:

A classic file browser will appear. Search for your “.snap” file and select it to load the corresponding snapshot in the Snapshot Details sub panel (See Figure 12: Snapshot control panel )
Editing a snapshot#
To edit a snapshot, double click on the snapshot you want to edit in
the snapshot list (in the Snapshot List sub panel). This will open a
new tab about this snapshot in the Snapshot Details sub panel, tab
named by this snapshot ID. If you load a snapshot from file, the
name of the tab is the name of the file. To differentiate snapshots
loaded from file and the ones loaded from database, the snapshot
loaded from file tabs have the icon
.
Setting equipment with a snapshot#
A snapshot allows you to set equipment with its attributes write
values. You can choose which attributes will set equipment, and
which not, by selecting or unselecting the corresponding check box
in the column Can Set Equipment
(See Figure 12: Snapshot control panel ).
By default, every attribute is selected. If you unselect
some attributes, an icon
will appear in tab title to
notify you that these attributes will not set equipments. You can
quick select/unselect all the attributes by clicking on All
and None buttons. When you are ready to set equipment with the
selected write values, click on the button
.
You can also modify the write value before setting equipment by
editing it in the table. If you do so, the value becomes red and an
icon appears to warn you about the fact that you made
modifications in this snapshot (these modifications will not be
saved in database, they are just here to set equipment).
Figure 13: Modified snapshot#
Snapshot comparison#
There are 2 ways to compare snapshots:
Compare a snapshot with another one:
To do so, select a tab in Snapshot Details sub panel (Figure 12: Snapshot control panel ). Click on button
. You will
see the tab title of this attribute appear in the field
“1st snapshot”. Select another tab and click again on
button to put this attribute tab title in the field
“2nd snapshot”. Click then on
button to see the
comparison between these 2 snapshots.If user wants to see only the first line of comparison, he must check filter

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

Figure 14: Snapshot comparison - full table#
To print this comparison table, click on Print button.
Compare a snapshot with current state:
To compare a snapshot with current state, set this snapshot as “1st snapshot”, as explained above, and leave the “2nd snapshot” empty. Note that once the “1st snapshot is selected, you only can update the “2nd snapshot or clear the comparison selection. To do so, click on the button
. What is hidden behind this is a creation of a
snapshot, named “BENSIKIN_AUTOMATIC_SNAPSHOT”, and you compare
this snapshot with your snapshot. Have in mind that this automatic
snapshot is registered in database. So, in the comparison table, the
current state will appear as the second snapshot with the name
“Current state” (red block in the comparison table).
Snapshot Details copy#
As you can see in Figure 12: Snapshot control panel ,
snapshots are detailed in a table. You can copy this table to clipboard as a
text-CSV formatted table by clicking on
or
button. If you want to see this text result and may be filter it
(like removing lines), click on
button. You will see the
text appear in a dialog.
Figure 15: Snapshot edit clipboard dialog#
Modifying a snapshot comment#
Once your snapshot details are loaded, click on
button to
modify its comment (and save it in database or file).
Printing a snapshot#
Once you have context ready, click on the print icon (
)
and select snapshot:

The classic print dialog will then appear. Validate your print configuration to print an xml representation of your snapshot.
Saving a snapshot#
Once you have context ready, click on the save icon (
)
and select snapshot:

You can also go to menu Context and click on Save, or go to menu File -> Save -> Snapshot.
Then, the behavior is “Word-like”. This means that if this is the first time you save this snapshot, you will see the classic file browser to choose where to save your snapshot, with file name. However, if not, it will automatically save in the corresponding file. If you want to save in another file, you have to go to File menu, select Save As and click on Snapshot, or go to Snapshots menu and click on Save As.

Favorites#
Bensikin manages a list of favorite context, so you can quickly switch to anyone of them. Those favorites are saved at application shutdown and loaded on startup.
Adding a context to favorites#
To add a context to your favorites, have your context ready by creating or loading it. Then go to Favorites menu and click on Add selected context.

Switching to a context in favorites#
To switch to a context in favorites, which means to load it from favorites, go to “Favorites” menu, select “contexts”, and click on the context you want to load.

Options#
Bensikin manages global options. Those options are saved at application shutdown, and loaded on startup. The Options menu is located in the Menu bar: Tools -> Options.
Application’s history save/load Options#
Define whether Bensikin has a history, i.e. a persistent state when closed/reopened.
If yes is checked, a XML History file will be saved in Bensikin’s workspace, and on next startup the current Context and Snapshot will be loaded.
Figure 16: Save option#
Snapshot Options#
These are the Bensikin Snapshot Options:
Figure 17: Snapshot options#
In the Comment Panel, you can choose to automatically set or not a value to a new snapshot comment. This means, when you click on
button, the newly created snapshot will or will not have a
pre-defined comment.In the Comparison Panel, you can choose which columns you want to show/hide for every block in the Snapshot Comparison table. You can choose to show/hide the Difference block too (See Figure 14: Snapshot comparison - full table )
In the Export Panel, you can choose the column separator for your text-CSV formatted tables (See Figure 15: Snapshot edit clipboard dialog ), and which columns to export.
Context Options#
Context options allow you to select which way you want to edit your contexts, see Figure 8: Option –context tab and the Creating a new snapshot section.
Print Options#
The Print option allows you to print text or table in the Snapshots or in the Contexts.
Figure 18: Print option#
When you check
, you adapt the size of your print to the
size of your page.
When you check
, you cut the length of your print on
several parts and the width of your print takes the width of your
page.
When you check
, the length and the width of your print
are cut on several parts.
The Bensikin toolbar#
The toolbar is located under the menu bar, and consists mainly of a set of shortcuts to often used functionalities.
Figure 19: Bensikin toolbar#
is a shortcut to creating a new Context
is a shortcut to saving the selected Context/Snapshot into
a Context/Snapshot file
is a shortcut to doing a saving all opened Contexts and
Snapshots
is a shortcut to printing the xml representation of the
current Context/Snapshot
is a shortcut to removing all opened Contexts and
Snapshots from display
Taranta#
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.
Tango REST API#
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 resourcePUT- update a resourcePOST- create a new resourceDELETE- remove a resource
The Tango REST API exports Tango hosts; Tango devices; Tango device attributes, commands and pipes. So it follows Tango Device Server Model.
For example, one can request Tango Host using Tango REST API:
GET tango/rest/rc4/hosts/tango_host/10000
Here one uses the Tango REST API implementation related to version RC4 as indicated by the
tango/rest/rc4 part of the example URL. The hosts part will export the Tango Host accessible through
this Tango REST API implementation and the tango_host/10000 assumes that the TANGO_HOST address is set to tango_host:10000.
In the following example a client makes a request for a device attribute:
GET tango/rest/rc4/hosts/tango_host/10000/devices/sys/tg_test/1/attributes/double_scalar/value
Again here tango/rest/rc4 relates to Tango REST API version RC4 implementation; hosts/tango_host/10000 relates to TANGO_HOST=tango_host:10000; the devices/sys/tg_test/1 relates to the device tango://tango_host:10000/sys/tg_test/1 exported through the Tango REST API and finally attributes/double_scalar/value relates to the value of the attribute.
This third example shows how a client can execute a command on a device:
PUT tango/rest/rc4/hosts/tango_host/10000/devices/sys/tg_test/1/commands/DevDouble?v=3.14
Here the client executes tango://tango_host:10000/sys/tg_test/1/DevDouble with the input arg=3.14.
Tango REST API implementations#
Since the Tango REST API itself is only a specification one needs an actual implementation running somewhere.
Known implementations are listed in the Tango REST API readme.
Please refer to the corresponding implementation documentation on how to install and use it.
In a nutshell download the latest fat jar distribution and start the server via java -DTANGO_HOST=localhost:10000 -jar mtangorest.server.jar -nodb -dlist sys/rest/0/
One can test the installation by attempting to read an attribute value from a device. Typically there is a TangoTest server running on the Tango host, whihc can be accesses through a browser at:
http://localhost:10001/tango/rest/rc4/hosts/localhost/10000/devices/sys/tg_test/1/attributes/double_scalar/value
showing:
{
"name":"double_scalar",
"value":179.04696279859678,
"quality":"ATTR_VALID",
"timestamp":1493918496122
}
Deployment#
As Tango REST exports Tango via http to the internet the usual question is how to protect Tango from the unwanted activity?
The deployment of the Tango REST API can be made quite safe. Usually one wants to put the Tango REST API server behind a reverse proxy and restrict its access to a single Tango Host. A reverse proxy can also allow connections only via https.
As every request via REST API must be validated against Tango Access Control this adds an extra layer of security.
Below is a deployment scheme of REST API at ESRF. In this installation the REST API exports readonly forwarded attributes and is accessible via a secured http connection.
Every request passes through [HAProxy]https://www.haproxy.org/ configured to use the https protocol for a secure connection. On its backend HAproxy communicates with the Tango REST server, which in turn can access only the Tango host where a device of class ForwardComposer is defined. This device provides read only access to the MStatus Tango device with status information about the storage ring at ESRF.
In addition the Tango REST API can be integrated with authentication and authorisation services like [Kerberos]https://web.mit.edu/kerberos/#what_is.
Finally, the Tango REST API implementation should use Tango Access Control to validate every request made from the internet.
Further steps#
Install Tango REST API server locally or using docker.
Develop your REST client or use a 3rd party framework.
Deploy everything on the local network or in the cloud.
References#
Reference#
RFC#
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#
Tango Controls offers a rich ecosystem for developers and users alike.
Since Tango is a also a developer’s framework there are many libraries and tools for existing devices and clients. Refer to the overview for developers for more information.
Tango is designed to run small and large systems. To support managing large installations a number of administration tools are provided.
All control systems need to be able to archive data so that one can look at past data when needed. Tango comes with a number of archiving solutions and the archiving overview is our starting point for them.
In addition to the Python, C++ and Java APIs for Tango, a number of other languages and tools can be used together with Tango. Please have a look at our bindings overview where we list the currently known ones.
Here is the official list of the active Tango Ecosystem software:
Software |
Maintainer / Institute |
Description |
|---|---|---|
Taranta |
Yimeng Li / MAX IV Laboratory |
Web Front End to Tango with no-code Dashboard, synoptic, … |
Matteo Canzari / INAF |
||
Pogo |
Damien Lacoste / ESRF |
Code generator for tango device server, to help get started |
when writing device servers |
||
TangoDatabase |
Thomas Braun / byte physics |
The database server using MariaDB/MySQL |
Graziano Scalamera / Elettra |
||
TangoTest |
Thomas Braun / byte physics |
Famous device server for testing |
cppTango |
Reynald Bourtembourg / ESRF |
C++ implementation of Tango |
Damien Lacoste / ESRF |
||
Thomas Juerges / SKAO |
||
Thomas Braun / byte physics |
||
Thomas Ives / OSL |
||
PyTango |
Anton Joubert / MAX IV Laboratory |
Python binding for Tango |
Yury Matveyev / DESY |
||
ITango |
Benjamin Bertrand / MAX IV Laboratory |
IPython extension for PyTango |
dsconfig |
Johan Forsberg / MAX IV Laboratory |
Tango configuration management tools |
Benjamin Bertrand / MAX IV Laboratory |
||
tango-admin |
Thomas Braun / byte physics |
Commandline tool for various tango admin tasks |
Thomas Juerges / SKAO |
||
TangoSourceDistribution |
Thomas Braun / byte physics |
Integrated package of various tango tools |
ATK |
Jean-Luc Pons / ESRF |
Java Application Toolkit |
Jive |
Jean-Luc Pons / ESRF |
Tango Database Browser GUI |
Starter |
Nicolas Tappret / ESRF |
device server to start/stop Tango device servers |
Astor |
Nicolas Tappret / ESRF |
Graphical Tango control system administration tool |
ATKPanel |
Jean-Luc Pons / ESRF |
Generic Java GUI to interact with a Tango device |
Nicolas Tappret / ESRF |
||
jdraw |
Jean-Luc Pons / ESRF |
Synoptic drawing tool (part of ATK project) |
Tango Access Control |
Nicolas Tappret / ESRF |
Access control device server for Tango |
C Language binding |
Jens Meyer / ESRF |
Tango C binding |
Taurus |
Oriol Vallcorba / ALBA |
Python Qt GUI framework based on PyTango for device |
Benjamin Bertrand / MAX IV Laboratory |
monitoring and control |
|
Arturo Hoffstadt / ESO |
||
Sardana |
Oriol Vallcorba / ALBA |
Experiment control toolkit based on PyTango and Taurus for |
Johan Forsberg / MAX IV Laboratory |
device orchestration and data acquisition |
|
Teresa Nuñez / DESY |
||
Michal Piekarski / SOLARIS |
||
fandango |
Sergi Rubio Manrique / ALBA |
Python Device Servers and CLI utilities library |
panic |
Sergi Rubio Manrique / ALBA |
Alarm System based on PyTango (Device Server and Qt GUI) |
HDB++ |
Damien Lacoste / ESRF |
High Performance Archiving System for Tango |
Reynald Bourtembourg / ESRF |
||
Graziano Scalamera / Elettra |
||
Sergi Rubio Manrique / ALBA |
||
Johan Forsberg / MAX IV Laboratory |
||
pyhdbpp |
Sergi Rubio Manrique / ALBA |
python client for hdb++ (backend agnostic) |
Jose Ramos / ALBA |
||
Damien Lacoste / ESRF |
Tango Core: C++#
cppTango API reference#
Please refer to: C++ API documentation generated by Doxygen.
Tango Core: Python#
PyTango - a Python binding to Tango#
Python is a commonly used programming language in the scientific community, due to its many advantages (the most important of those is probably simplicity of its syntax). Tango Controls also supports it in a form of a pybind11-based binding to the cppTango implementation.
In “pythonic” terms, it is a package available at PyPI that exposes the complete Tango API (both the client and the server parts of it) as well as provides a framework for unit-testing your device servers.
Note
You should use a PyTango version that has the same major and minor version numbers as cppTango that you have. So if you have cppTango version X.Y.Z, you should have PyTango version X.Y.V (where V might equal Z, but its not required).
PyTango usage#
For instructions on how to use PyTango, the Python binding for Tango Controls, please refer to the PyTango documentation
PyTango API reference#
Please refer to PyTango API documentation
Tango Core: Java#
JTango usage#
For instructions on how to use JTango, the Java binding for Tango Controls, please refer to the JTango documentation.
JTango API reference#
Bindings#
C Language#
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#
Client API for Igor Pro
- Release 3.0.0 for Igor Pro 7.x
Runs on Windows x64 [no official support for x86 any more]
Binary distribution for 7.x for Windows x64
- Release 2.5.0 for Igor Pro 6.x
Runs on Windows x86 & x64
Binary distribution for 6.x for Windows x86
- Source code available on GitLab
compiling this binding requires the WaveMetrics’ XOP Toolkit
LabVIEW#
Client and server API for LabVIEW
Runs on Windows and Linux
Provides 32 and 64 bits support on both platforms
- Release 3.0.0 for LabVIEW 2015
Binary distribution for Windows [LabVIEW 32 bits]
- Release 3.0.0 for LabVIEW 2014
Binary distribution for LabView 2014 on Windows [LabVIEW 64 bits]
Binary distribution for Linux [LabVIEW 32 bits]
- Patch for release 3.0.0
requires LabVIEW >= 2014
this Vi library contains some bug fixes, simply replace the original one in the /vis directory
Source code available on GitLab
Matlab & Octave#
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#
The API specification is discussed in Tango REST API.
Overview#
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#
The most important information Dependencies, Connection of models and views and etc. you will find following this link.
Glossary#
- 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
SerialLineclass 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
DServerdevice 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_configoperation 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/tangorcfor unix-like OSes and in${TANGO_ROOT}/tangorcfor Windows. Or for the current user only in~/.tangorcfor 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#
This chapter is only part of the TANGO device server reference guide. To get reference documentation about the:
C++ library classes, see cppTango API reference.
Java classes, see JTango API reference.
Python classes, see PyTango API reference.
Device parameter#
A black box, a device description field, a device state and status are associated with each TANGO device.
The device black box#
The device black box is managed as a circular buffer. It is possible to tune the buffer depth via a device property. This property name is
device name->blackbox_depth
A default value is hard-coded to 50 if the property is not defined. This black box depth property is retrieved from the Tango property database during the device creation phase.
The device description field#
There are two ways to initialise the device description field.
At device creation time. Some constructors of the DeviceImpl class supports this field as parameter. If these constructor are not used, the device description field is set to a default value which is A Tango device.
With a property. A description field defines with this method overrides a device description defined at construction time. The property name is
device name->description
The device state and status#
Some constructors of the DeviceImpl class allows the initialisation of device state and/or status or device creation time. If these fields are not defined, a default value is applied. The default state is Tango::UNKOWN, the default status is Not Initialised.
The device polling#
Seven device properties allow the polling tunning. These properties are described in the following table
Property name |
property rule |
default value |
|---|---|---|
poll_ring_depth |
Polling buffer depth |
10 |
cmd_poll_ring_depth |
Cmd polling buffer depth |
|
attr_poll_ring_depth |
Attr polling buffer depth |
|
poll_old_factor |
Data too old factor |
4 |
min_poll_period |
Minimun polling period |
|
cmd_min_poll_period |
Min. polling period for cmd |
|
attr_min_poll_period |
Min. polling period for attr |
The rule of the poll_ring_depth property is obvious. It defines the polling ring depth for all the device polled command(s) and attribute(s). Nevertheless, when filling the polling buffer via the fill_cmd_polling_buffer() (or fill_attr_polling_buffer()) method, it could be helpfull to define specific polling ring depth for a command (or an attribute). This is the rule of the cmd_poll_ring_depth and attr_poll_ring_depth properties. For each polled object with specific polling depth (command or attribute), the syntax of this property is the object name followed by the ring depth (ie State,20,Status,15). If one of these properties is defined, for the specific command or attribute, it will overwrite the value set by the poll_ring_depth property. The poll_old_factor property allows the user to tune how long the data recorded in the polling buffer are valid. Each time some data are read from the polling buffer, a check is done between the date when the data were recorded in the polling buffer and the date when the user request these data. If the interval is greater than the object polling period multiply by the value of the poll_old_factor factory, an exception is returned to the caller. These two properties are defined at device level and therefore, it is not possible to tune this parameter for each polled object (command or attribute). The last 3 properties are dedicated to define a polling period minimum threshold. The property min_poll_period defines in (mS) a device minimum polling period. Property cmd_min_poll_period defines (in mS) a minimum polling period for a specific command. The syntax of this property is the command name followed by the minimum polling period (ie MyCmd,400). Property attr_min_poll_period defines (in mS) a minimum polling period for a specific attribute. The syntax of this property is the attribute name followed by the minimum polling period (ie MyAttr,600). These two properties has a higher priority than the min_poll_period property. By default these three properties are not defined mening that there is no minimun polling period.
Four other properties are used by the Tango core classes to manage the polling thread. These properties are :
polled_cmd to memorize the name of the device polled command
polled_attr to memorize the name of the device polled attribute
non_auto_polled_cmd to memorize the name of the command which shoule not be polled automatically at the first request
non_auto_polled_attr to memorize the name of the attribute which should not be polled automatically at the first request
You don’t have to change these properties values by yourself. They are automatically created/modified/deleted by Tango core classes.
The device logging#
The Tango Logging Service (TLS) uses device properties to control device logging at startup (static configuration). These properties are described in the following table
Property name |
property rule |
default value |
|---|---|---|
logging_level |
Initial device logging level |
WARNING |
current_logging_level |
Logging level applied to the current session |
No default |
logging_target |
Initial device logging target |
No default |
current_logging_larget |
Logging target applied to the current session |
No default |
logging_rft |
Logging rolling file threshold |
20 Mega bytes |
logging_path |
Logging file path |
/tmp/tango-<logging name> or C:/tango-<logging name> (Windows) |
The logging_level property controls the initial logging level of a device. Its set of possible values is: OFF, FATAL, ERROR, WARNING, INFO or DEBUG. This property is overwritten by the verbose command line option (-v).
The logging_target property is a multi-valued property containing the initial target list. Each entry must have the following format: target_type::target_name (where target_type is one of the supported target types and target_name, the name of the target). Supported target types are: console, file and device. For a device target, target_name must contain the name of a log consumer device (as defined in [sec:Tango-log-consumer]). For a file target, target_name is the name of the file to log to. If omitted the device’s name is used to build the file name (domain_family_member.log). Finally, target_name is ignored in the case of a console target. The TLS does not report any error occurred while trying to setup the initial targets.
Logging_target property example : logging_target = [ console, file, <file::/home/me/mydevice.log> , device::tmp/log/1 In this case, the device will automatically logs to the standard output, to its default file (which is something like domain_family_member.log), to a file named mydevice.log and located in /home/me. Finally, the device logs are also sent to a log consumer device named tmp/log/1. Currently, the target do not take into account uppercase characters, so the logging_target property “file::/Home/He/MyDevice.log” will write into “file::/home/me/mydevice.log”
The current_logging_target and current_logging_level properties behave as read-write properties which are set when device is started, respectively from logging_target and logging_level property values. Thoses properties are not stored into Tango database like other properties so they will only be available through the device, and only until it is stopped. If one of those properties is modified during the device running, the (last) value modification will be taken into account, and only until the next device stop.
The logging_rft property specifies the rolling file threshold (rft), of the device’s file targets. This threshold is expressed in Kb. When the size of a log file reaches the so-called rolling-file-threshold (rft), it is backuped as current_log_file_name + _1 and a new current_log_file_name is opened. Obviously, there is only one backup file at a time (i.e. any existing backup is destroyed before the current log file is backuped). The default threshold is 20 Mb, the minimum is 500 Kb and the maximum is 1000 Mb.
The logging_path property overwrites the TANGO_LOG_PATH environment variable. This property can only be applied to a DServer class device and has no effect on other devices.
Note: setting logging properties into the device will give access to logging of the device itself, while setting logging_level properties to DEBUG in dserver admin associated device will give access to cppTango library logging. This will not work with other logging level.
Device attribute#
Attribute are configured with two kind of parameters: Parameters hard-coded in source code and modifiable parameters
Hard-coded device attribute parameters#
Seven attribute parameters are defined at attribute creation time in the Tango class source code. Obviously, these parameters are not modifiable except with a new source code compilation. These parameters are
Parameter name |
Parameter description |
|---|---|
name |
Attribute name |
data_type |
Attribute data type |
data_format |
Attribute data format |
writable |
Attribute read/write type |
max_dim_x |
Maximum X dimension |
max_dim_y |
Maximum Y dimension |
writable_attr_name |
Associated write attribute |
level |
Attribute display level |
root_attr_name |
Root attribute name |
The Attribute data type#
Thirteen data types are supported. These data types are
Tango::DevBoolean
Tango::DevShort
Tango::DevLong
Tango::DevLong64
Tango::DevFloat
Tango::DevDouble
Tango::DevUChar
Tango::DevUShort
Tango::DevULong
Tango::DevULong64
Tango::DevString
Tango::DevState
Tango::DevEncoded
The attribute data format#
Three data format are supported for attribute
Format |
Description |
|---|---|
Tango::SCALAR |
The attribute value is a single number |
Tango::SPECTRUM |
The attribute value is a one dimension number |
Tango::IMAGE |
The attribute value is a two dimension number |
The max_dim_x and max_dim_y parameters#
These two parameters defined the maximum size for attributes of the SPECTRUM and IMAGE data format.
data format |
max_dim_x |
max_dim_y |
|---|---|---|
Tango::SCALAR |
1 |
0 |
Tango::SPECTRUM |
User Defined |
0 |
Tango::IMAGE |
User Defined |
User Defined |
For attribute of the Tango::IMAGE data format, all the data are also returned in a one dimension array. The first array is value[0],[0], array element X is value[0],[X-1], array element X+1 is value[1][0] and so forth.
The attribute read/write type#
Tango supports four kind of read/write attribute which are :
Tango::READ for read only attribute
Tango::WRITE for writable attribute
Tango::READ_WRITE for attribute which can be read and write
Tango::READ_WITH_WRITE for a readable attribute associated to a writable attribute (For a power supply device, the current really generated is not the wanted current. To handle this, two attributes are defined which are generated_current and wanted_current. The wanted_current is a Tango::WRITE attribute. When the generated_current attribute is read, it is very convenient to also get the wanted_current attribute. This is exactly what the Tango::READ_WITH_WRITE attribute is doing)
When read, attribute values are always returned within an array even for scalar attribute. The length of this array and the meaning of its elements is detailed in the following table for scalar attribute.
Name |
Array length |
Array[0] |
Array[1] |
|---|---|---|---|
Tango::READ |
1 |
Read value |
|
Tango::WRITE |
1 |
Last write value |
|
Tango::READ_WRITE |
2 |
Read value |
Last write value |
Tango::READ_WITH_WRITE |
2 |
Read value |
Associated attribute last write value |
When a spectrum or image attribute is read, it is possible to code the device class in order to send only some part of the attribute data (For instance only a Region Of Interest for an image) but never more than what is defined by the attribute configuration parameters max_dim_x and max_dim_y. The number of data sent is also transferred with the data and is named dim_x and dim_y. When a spectrum or image attribute is written, it is also possible to send only some of the attribute data but always less than max_dim_x for spectrum and max_dim_x * max_dim_y for image. The following table describe how data are returned for spectrum attribute. dim_x is the data size sent by the server when the attribute is read and dim_x_w is the data size used during the last attribute write call.
Name |
Array length |
Array[0->dim_x-1] |
Array[dim_x-> dim_x + dim_x_w -1] |
|
|---|---|---|---|---|
Tango::READ |
dim_x |
Read values |
||
Tango::WRITE |
dim_x_w |
Last write values |
||
Tango::READ_WRITE |
dim_x + dim_x_w |
Read value |
Last write values |
|
Tango::READ_WITH_WRITE |
dim_x + dim_x_w |
Read value |
Associated attributelast write values |
The following table describe how data are returned for image attribute. dim_r is the data size sent by the server when the attribute is read (dim_x * dim_y) and dim_w is the data size used during the last attribute write call (dim_x_w * dim_y_w).
Name |
Array length |
Array[0->dim_r-1] |
Array[dim_r->dim_r + dim_w -1] |
|---|---|---|---|
Tango::READ |
dim_r |
Read values |
|
Tango::WRITE |
dim_w |
Last write values |
|
Tango::READ_WRITE |
dim_r + dim_w |
Read value |
Last write values |
Tango::READ_WITH_WRITE |
dim_r + dim_w |
Read value |
Associated attributelast write values |
Until a write operation has been performed, the last write value is initialized to 0 for scalar attribute of the numeriacal type, to Not Initialised for scalar string attribute and to true for scalar boolean attribute. For spectrum or image attribute, the last write value is initialized to an array of one element set to 0 for numerical type, to an array of one element set to true for boolean attribute and to an array of one element set to Not initialized for string attribute
The associated write attribute parameter#
This parameter has a meaning only for attribute with a Tango::READ_WITH_WRITE read/write type. This is the name of the associated write attribute.
The attribute display level parameter#
This parameter is only an help for graphical application. It is a C++ enumeration starting at 0. The code associated with each attribute display level is defined in the following table (Tango::DispLevel).
name |
Value |
|---|---|
Tango::OPERATOR |
0 |
Tango::EXPERT |
1 |
This parameter allows a graphical application to support two types of operation :
An operator mode for day to day operation
An expert mode when tuning is necessary
According to this parameter, a graphical application knows if the attribute is for the operator mode or for the expert mode.
The root attribute name parameter#
In case the attribute is a forwarded one, this parameter is the name of the associated root attribute. In case of classical attribute, this string is set to Not specified.
Modifiable attribute parameters#
Each attribute has a configuration set of 20 modifiable parameters. These can be grouped in three different purposes:
General purpose parameters
Alarm related parameters
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:
The Database
If nothing in database, from the Tango class default
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 :
At class level
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 |
|
|
|
|---|---|---|---|
standard_unit |
|
||
min_value |
|
|
|
max_value |
|
|
|
rel_change |
|
|
|
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:
The Database
If nothing in database, from the Tango class default
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 :
At class level
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.
The library set a default value of 1000 for both buffers (client and device server side)
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.
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::ApiUtilUsing 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.
Their black-box is also destroyed and re-built
CORBA#
Below are the 10 important things you should know about CORBA. More detail is provided below for those that are interested.
You don’t need to know CORBA to work with TANGO
CORBA is the acronym for Common Object Request Broker Architecture and it is a standard defined by the Object Management Group (OMG)
CORBA enables communication between software written in different languages and running on different computers
CORBA applications are composed of many objects; objects are running software that provides functionalities and that can represent something in the real world
Every object has a type which is defined with a language called IDL (Interface Definition Language)
An object has an interface and an implementation: this is the essence of CORBA because it allows interoperability.
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.
CORBA is based on a Remote Procedure Call model
The TANGO Device is a CORBA Object
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.
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:
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.
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
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…)
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:
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
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)
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 | |
+---------------+--------------------------+------+-----+---------+-------+
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';
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:
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
move an attribute from a device server to another one
keep trace of which attribute is assigned to which
start/stop the archiving of an attribute at runtime
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.
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.
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';
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#
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++#
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#
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.
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):
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.
We show you how to implement Tango Devices, Tango clients and other Tango-related software.
Here we provide solutions to specific problems that you might encounter on the road with Tango Controls.
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.
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.
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.
