# Quick tour

This quick tour will guide you through the first steps on using PyTango.

## Fundamental TANGO concepts

Before you begin there are some fundamental TANGO concepts you should be aware of.

Tango consists basically of a set of devices running somewhere on the network.

A device is identified by a unique case insensitive name in the format <domain>/<family>/<member>. Examples: LAB-01/PowerSupply/01, ID21/OpticsHutch/energy.

Each device has a series of attributes, pipes, properties and commands.

An attribute is identified by a name in a device. It has a value that can be read. Some attributes can also be changed (read-write attributes). Each attribute has a well known, fixed data type.

A pipe is a kind of attribute. Unlike attributes, the pipe data type is strucured (in the sence of C struct) and it is dynamic.

A property is identified by a name in a device. Usually, devices properties are used to provide a way to configure a device.

A command is also identified by a name. A command may or not receive a parameter and may or not return a value when it is executed.

Any device has at least a State and Status attributes and State, Status and Init commands. Reading the State or Status attributes has the same effect as executing the State or Status commands.

Each device as an associated TANGO Class. Most of the times the TANGO class has the same name as the object oriented programming class which implements it but that is not mandatory.

TANGO devices live inside a operating system process called TANGO Device Server. This server acts as a container of devices. A device server can host multiple devices of multiple TANGO classes. Devices are, therefore, only accessible when the corresponding TANGO Device Server is running.

A special TANGO device server called the TANGO Database Server will act as a naming service between TANGO servers and clients. This server has a known address where it can be reached. The machines that run TANGO Device Servers and/or TANGO clients, should export an environment variable called `TANGO_HOST` that points to the TANGO Database server address. Example: `TANGO_HOST=homer.lab.eu:10000`

## Minimum setup

This chapter assumes you have already installed PyTango.

To explore PyTango you should have a running Tango system. If you are working in a facility/institute that uses Tango, this has probably already been prepared for you. You need to ask your facility/institute tango contact for the `TANGO_HOST` variable where Tango system is running.

If you are working in an isolate machine you first need to make sure the Tango system is installed and running (see tango how to).

Most examples here connect to a device called sys/tg_test/1 that runs in a TANGO server called TangoTest with the instance name test. This server comes with the TANGO installation. The TANGO installation also registers the test instance. All you have to do is start the TangoTest server on a console:

    $ TangoTest test
    Ready to accept request

Note

if you receive a message saying that the server is already running, it just means that somebody has already started the test server so you don’t need to do anything.

## Client

Finally you can get your hands dirty. The first thing to do is start a python console and import the `tango` module. The following example shows how to create a proxy to an existing TANGO device, how to read and write attributes and execute commands from a python console:

    >>> import tango

    >>> # create a device object
    >>> test_device = tango.DeviceProxy("sys/tg_test/1")

    >>> # every device has a state and status which can be checked with:
    >>> print(test_device.state())
    RUNNING

    >>> print(test_device.status())
    The device is in RUNNING state.

    >>> # this device has an attribute called "long_scalar". Let's see which value it has...
    >>> data = test_device.read_attribute("long_scalar")

    >>> # ...PyTango provides a shortcut to do the same:
    >>> data = test_device["long_scalar"]

    >>> # the result of reading an attribute is a DeviceAttribute python object.
    >>> # It has a member called "value" which contains the value of the attribute
    >>> data.value
    136

    >>> # Check the complete DeviceAttribute members:
    >>> print(data)
    DeviceAttribute[
    data_format = SCALAR
          dim_x = 1
          dim_y = 0
     has_failed = False
       is_empty = False
           name = 'long_scalar'
        nb_read = 1
     nb_written = 1
        quality = ATTR_VALID
    r_dimension = AttributeDimension(dim_x = 1, dim_y = 0)
           time = TimeVal(tv_nsec = 0, tv_sec = 1399450183, tv_usec = 323990)
           type = DevLong
          value = 136
        w_dim_x = 1
        w_dim_y = 0
    w_dimension = AttributeDimension(dim_x = 1, dim_y = 0)
        w_value = 0]

    >>> # PyTango provides a handy pythonic shortcut to read the attribute value:
    >>> test_device.long_scalar
    136

    >>> # Setting an attribute value is equally easy:
    >>> test_device.write_attribute("long_scalar", 8776)

    >>> # ... and a handy shortcut to do the same exists as well:
    >>> test_device.long_scalar = 8776

    >>> # TangoTest has a command called "DevDouble" which receives a number
    >>> # as parameter and returns the same number as a result. Let's
    >>> # execute this command:
    >>> test_device.command_inout("DevDouble", 45.67)
    45.67

    >>> # PyTango provides a handy shortcut: it exports commands as device methods:
    >>> test_device.DevDouble(45.67)
    45.67

    >>> # Introspection: check the list of attributes:
    >>> test_device.get_attribute_list()
    ['ampli', 'boolean_scalar', 'double_scalar', '...', 'State', 'Status']

    >>>

This is just the tip of the iceberg. Check the `DeviceProxy` for the complete API.

PyTango used to come with an integrated IPython based console called ITango, now moved to a separate project. It provides helpers to simplify console usage. You can use this console instead of the traditional python console. Be aware, though, that many of the tricks you can do in an ITango console cannot be done in a python program.

## Server

Since PyTango 8.1 it has become much easier to program a Tango device server. PyTango provides some helpers that allow developers to simplify the programming of a Tango device server.

Before creating a server you need to decide:

1. The Tango Class name of your device (example: PowerSupply). In our example we will use the same name as the python class name.
2. The list of attributes of the device, their data type, access (read-only vs read-write), data_format (scalar, 1D, 2D)
3. The list of commands, their parameters and their result

In our example we will write a fake power supply device server. There will be a class called PowerSupply which will have attributes:

- voltage (scalar, read-only, numeric)
- current (scalar, read_write, numeric, expert mode)
- noise (2D, read-only, numeric)

pipes:

- info (read-only)

commands:

- TurnOn (argument: None, result: None)
- TurnOff (argument: None, result: None)
- Ramp (param: scalar, numeric; result: bool)

properties:

- host (string representing the host name of the actual power supply)
- port (port number in the host with default value = 9788)

Here is the code for the `power_supply.py`

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    """Demo power supply tango device server"""

    import time
    import numpy

    from tango import AttrQuality, AttrWriteType, DispLevel, DevState, DebugIt
    from tango.server import Device, attribute, command, pipe, device_property

    class PowerSupply(Device):

        voltage = attribute(label="Voltage", dtype=float,
                            display_level=DispLevel.OPERATOR,
                            access=AttrWriteType.READ,
                            unit="V", format="8.4f",
                            doc="the power supply voltage")

        current = attribute(label="Current", dtype=float,
                            display_level=DispLevel.EXPERT,
                            access=AttrWriteType.READ_WRITE,
                            unit="A", format="8.4f",
                            min_value=0.0, max_value=8.5,
                            min_alarm=0.1, max_alarm=8.4,
                            min_warning=0.5, max_warning=8.0,
                            fget="get_current",
                            fset="set_current",
                            doc="the power supply current")

        noise = attribute(label="Noise",
                          dtype=((int,),),
                          max_dim_x=1024, max_dim_y=1024)

        info = pipe(label='Info')

        host = device_property(dtype=str)
        port = device_property(dtype=int, default_value=9788)

        def init_device(self):
            Device.init_device(self)
            self.__current = 0.0
            self.set_state(DevState.STANDBY)

        def read_voltage(self):
            self.info_stream("read_voltage(%s, %d)", self.host, self.port)
            return 9.99, time.time(), AttrQuality.ATTR_WARNING

        def get_current(self):
            return self.__current

        def set_current(self, current):
            # should set the power supply current
            self.__current = current

        def read_info(self):
            return 'Information', dict(manufacturer='Tango',
                                       model='PS2000',
                                       version_number=123)

        @DebugIt()
        def read_noise(self):
            return numpy.random.random_integers(1000, size=(100, 100))

        @command
        def TurnOn(self):
            # turn on the actual power supply here
            self.set_state(DevState.ON)

        @command
        def TurnOff(self):
            # turn off the actual power supply here
            self.set_state(DevState.OFF)

        @command(dtype_in=float, doc_in="Ramp target current",
                 dtype_out=bool, doc_out="True if ramping went well, "
                 "False otherwise")
        def Ramp(self, target_current):
            # should do the ramping
            return True

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

Check the high level server API for the complete reference API. The write a server how to can help as well.

Before running this brand new server we need to register it in the Tango system. You can do it with Jive (Jive->Edit->Create server):

… or in a python script:

    >>> import tango

    >>> dev_info = tango.DbDevInfo()
    >>> dev_info.server = "PowerSupply/test"
    >>> dev_info._class = "PowerSupply"
    >>> dev_info.name = "test/power_supply/1"

    >>> db = tango.Database()
    >>> db.add_device(dev_info)

After, you can run the server on a console with:

    $ python power_supply.py test
    Ready to accept request

Now you can access it from a python console:

    >>> import tango

    >>> power_supply = tango.DeviceProxy("test/power_supply/1")
    >>> power_supply.state()
    STANDBY

    >>> power_supply.current = 2.3

    >>> power_supply.current
    2.3

    >>> power_supply.PowerOn()
    >>> power_supply.Ramp(2.1)
    True

    >>> power_supply.state()
    ON

Note

In this example, the name of the server and the name of the tango class are the same: PowerSupply. This pattern is enforced by the `run_server()` method. However, it is possible to run several tango classes in the same server. In this case, the server name would typically be the name of server file. See the `run()` function for further information.