ITango

ITango is a PyTango CLI based on IPython. It is designed to be used as an IPython profile.

It is available since PyTango 7.1.2 and has been moved to a separate project since PyTango 9.2.0.

You can start ITango by typing on the command line:

$ itango

or the equivalent:

$ ipython --profile=tango

and you should get something like this:

_images/itango00.png

Features

ITango works like a normal python console, but it gives you in addition a nice set of features from IPython like:

  • proper (bash-like) command completion

  • automatic expansion of python variables, functions, types

  • command history (with up/down arrow keys, %hist command)

  • help system ( object? syntax, help(object))

  • persistently store your favorite variables

  • color modes

For a complete list checkout the IPython web page.

Plus an additional set of Tango specific features:

  • automatic import of Tango objects to the console namespace (tango module, DeviceProxy (=Device), Database, Group and AttributeProxy (=Attribute))

  • device name completion

  • attribute name completion

  • automatic tango object member completion

  • list tango devices, classes, servers

  • customized tango error message

  • tango error introspection

  • switch database

  • refresh database

  • list tango devices, classes

  • store favorite tango objects

  • store favorite tango devices

  • tango color modes

Check the Highlights to see how to put these feature to good use :-)

Highlights

Tab completion

ITango exports many tango specific objects to the console namespace. These include:

  • the tango module itself

    ITango [1]: tango
    Result [1]: <module 'tango' from ...>
    
  • The DeviceProxy (=Device), AttributeProxy (=Attribute), Database and Group classes

     1ITango [1]: De<tab>
     2DeprecationWarning            Device       DeviceProxy
     3
     4ITango [2]: Device
     5Result [2]: <class 'tango._tango.DeviceProxy'>
     6
     7ITango [3]: Device("sys/tg_test/1")
     8Result [3]: DeviceProxy(sys/tg_test/1)
     9
    10ITango [4]: Datab<tab>
    11
    12ITango [4]: Database
    13
    14ITango [4]: Att<tab>
    15Attribute       AttributeError  AttributeProxy
    
  • The Tango Database object to which the itango session is currently connected

    ITango [1]: db
    Result [1]: Database(homer, 10000)
    

Device name completion

ITango knows the complete list of device names (including alias) for the current tango database. This means that when you try to create a new Device, by pressing <tab> you can see a context sensitive list of devices.

1ITango [1]: test = Device("<tab>
2Display all 3654 possibilities? (y or n) n
3
4ITango [1]: test = Device("sys<tab>
5sys/access_control/1  sys/database/2        sys/tautest/1         sys/tg_test/1
6
7ITango [2]: test = Device("sys/tg_test/1")

Attribute name completion

ITango can inspect the list of attributes in case the device server for the device where the attribute resides is running.

 1ITango [1]: short_scalar = Attribute("sys<tab>
 2sys/access_control/1/  sys/database/2/        sys/tautest/1/         sys/tg_test/1/
 3
 4ITango [1]: short_scalar = Attribute("sys/tg_test/1/<tab>
 5sys/tg_test/1/State                sys/tg_test/1/no_value
 6sys/tg_test/1/Status               sys/tg_test/1/short_image
 7sys/tg_test/1/ampli                sys/tg_test/1/short_image_ro
 8sys/tg_test/1/boolean_image        sys/tg_test/1/short_scalar
 9sys/tg_test/1/boolean_image_ro     sys/tg_test/1/short_scalar_ro
10sys/tg_test/1/boolean_scalar       sys/tg_test/1/short_scalar_rww
11sys/tg_test/1/boolean_spectrum     sys/tg_test/1/short_scalar_w
12sys/tg_test/1/boolean_spectrum_ro  sys/tg_test/1/short_spectrum
13sys/tg_test/1/double_image         sys/tg_test/1/short_spectrum_ro
14sys/tg_test/1/double_image_ro      sys/tg_test/1/string_image
15sys/tg_test/1/double_scalar        sys/tg_test/1/string_image_ro
16...
17
18ITango [1]: short_scalar = Attribute("sys/tg_test/1/short_scalar")
19
20ITango [29]: print test.read()
21DeviceAttribute[
22data_format = tango._tango.AttrDataFormat.SCALAR
23  dim_x = 1
24  dim_y = 0
25has_failed = False
26is_empty = False
27   name = 'short_scalar'
28nb_read = 1
29nb_written = 1
30quality = tango._tango.AttrQuality.ATTR_VALID
31r_dimension = AttributeDimension(dim_x = 1, dim_y = 0)
32   time = TimeVal(tv_nsec = 0, tv_sec = 1279723723, tv_usec = 905598)
33   type = tango._tango.CmdArgType.DevShort
34  value = 47
35w_dim_x = 1
36w_dim_y = 0
37w_dimension = AttributeDimension(dim_x = 1, dim_y = 0)
38w_value = 0]

Automatic tango object member completion

When you create a new tango object, (ex.: a device), itango is able to find out dynamically which are the members of this device (including tango commands and attributes if the device is currently running)

 1ITango [1]: test = Device("sys/tg_test/1")
 2
 3ITango [2]: test.<tab>
 4Display all 240 possibilities? (y or n)
 5...
 6test.DevVoid                            test.get_access_control
 7test.Init                               test.get_asynch_replies
 8test.State                              test.get_attribute_config
 9test.Status                             test.get_attribute_config_ex
10test.SwitchStates                       test.get_attribute_list
11...
12
13ITango [2]: test.short_<tab>
14test.short_image        test.short_scalar       test.short_scalar_rww   test.short_spectrum
15test.short_image_ro     test.short_scalar_ro    test.short_scalar_w     test.short_spectrum_ro
16
17ITango [2]: test.short_scalar        # old style: test.read_attribute("short_scalar").value
18Result [2]: 252
19
20ITango [3]: test.Dev<tab>
21test.DevBoolean               test.DevUShort                test.DevVarShortArray
22test.DevDouble                test.DevVarCharArray          test.DevVarStringArray
23test.DevFloat                 test.DevVarDoubleArray        test.DevVarULongArray
24test.DevLong                  test.DevVarDoubleStringArray  test.DevVarUShortArray
25test.DevShort                 test.DevVarFloatArray         test.DevVoid
26test.DevString                test.DevVarLongArray
27test.DevULong                 test.DevVarLongStringArray
28
29ITango [3]: test.DevDouble(56.433)  # old style: test.command_inout("DevDouble").
30Result [3]: 56.433

Tango classes as DeviceProxy

ITango exports all known tango classes as python alias to DeviceProxy. This way, if you want to create a device of class which you already know (say, Libera, for example) you can do:

ITango [1]: lib01 = Libera("BO01/DI/BPM-01")

One great advantage is that the tango device name completion is sensitive to the type of device you want to create. This means that if you are in the middle of writing a device name and you press the <tab> key, only devices of the tango class ‘Libera’ will show up as possible completions.

 1ITango [1]: bpm1 = Libera("<tab>
 2BO01/DI/BPM-01  BO01/DI/BPM-09  BO02/DI/BPM-06  BO03/DI/BPM-03  BO03/DI/BPM-11  BO04/DI/BPM-08
 3BO01/DI/BPM-02  BO01/DI/BPM-10  BO02/DI/BPM-07  BO03/DI/BPM-04  BO04/DI/BPM-01  BO04/DI/BPM-09
 4BO01/DI/BPM-03  BO01/DI/BPM-11  BO02/DI/BPM-08  BO03/DI/BPM-05  BO04/DI/BPM-02  BO04/DI/BPM-10
 5BO01/DI/BPM-04  BO02/DI/BPM-01  BO02/DI/BPM-09  BO03/DI/BPM-06  BO04/DI/BPM-03  BO04/DI/BPM-11
 6BO01/DI/BPM-05  BO02/DI/BPM-02  BO02/DI/BPM-10  BO03/DI/BPM-07  BO04/DI/BPM-04
 7BO01/DI/BPM-06  BO02/DI/BPM-03  BO02/DI/BPM-11  BO03/DI/BPM-08  BO04/DI/BPM-05
 8BO01/DI/BPM-07  BO02/DI/BPM-04  BO03/DI/BPM-01  BO03/DI/BPM-09  BO04/DI/BPM-06
 9BO01/DI/BPM-08  BO02/DI/BPM-05  BO03/DI/BPM-02  BO03/DI/BPM-10  BO04/DI/BPM-07
10
11ITango [1]: bpm1 = Libera("BO01<tab>
12BO01/DI/BPM-01  BO01/DI/BPM-03  BO01/DI/BPM-05  BO01/DI/BPM-07  BO01/DI/BPM-09  BO01/DI/BPM-11
13BO01/DI/BPM-02  BO01/DI/BPM-04  BO01/DI/BPM-06  BO01/DI/BPM-08  BO01/DI/BPM-10
14
15ITango [1]: bpm1 = Libera("BO01/DI/BPM-01")

Customized device representation

When you use ipython >= 0.11 with a Qt console frontend:

$ itango qtconsole

typing a variable containing a tango device object followend by Enter will present you with a customized representation of the object instead of the usual repr() :

_images/itango06.png

You can customize the icon that itango displays for a specific device. The first thing to do is to copy the image file into itango.resource installation directory (if you don’t have permissions to do so, copy the image into a directory of your choosing and make sure it is accessible from itango).

If you want to use the image for all devices of a certain tango class, just add a new tango class property called __icon. You can do it with jive or, of course, with itango itself:

1db.put_class_property("Libera", dict(__icon="libera.png"))
2
3# if you placed your image in a directory different than itango.resource
4# then, instead you have to specify the absolute directory
5
6db.put_class_property("Libera", dict(__icon="/home/homer/.config/itango/libera.png"))

If you need different images for different devices of the same class, you can specify an __icon property at the device level (which takes precedence over the class property value, if defined):

db.put_device_property("BO01/DI/BPM-01", dict(__icon="libera2.png"))

List tango devices, classes, servers

ITango provides a set of magic functions (ipython lingo) that allow you to check for the list tango devices, classes and servers which are registered in the current database.

 1ITango [1]: lsdev
 2                                  Device                     Alias                    Server                Class
 3---------------------------------------- ------------------------- ------------------------- --------------------
 4              expchan/BL99_Dummy0DCtrl/1                  BL99_0D1                 Pool/BL99      ZeroDExpChannel
 5                  simulator/bl98/motor08                                      Simulator/BL98            SimuMotor
 6              expchan/BL99_Dummy0DCtrl/3                  BL99_0D3                 Pool/BL99      ZeroDExpChannel
 7              expchan/BL99_Dummy0DCtrl/2                  BL99_0D2                 Pool/BL99      ZeroDExpChannel
 8              expchan/BL99_Dummy0DCtrl/5                  BL99_0D5                 Pool/BL99      ZeroDExpChannel
 9              expchan/BL99_Dummy0DCtrl/4                  BL99_0D4                 Pool/BL99      ZeroDExpChannel
10              expchan/BL99_Dummy0DCtrl/7                  BL99_0D7                 Pool/BL99      ZeroDExpChannel
11              expchan/BL99_Dummy0DCtrl/6                  BL99_0D6                 Pool/BL99      ZeroDExpChannel
12                  simulator/bl98/motor01                                      Simulator/BL98            SimuMotor
13                  simulator/bl98/motor02                                      Simulator/BL98            SimuMotor
14                  simulator/bl98/motor03                                      Simulator/BL98            SimuMotor
15   mg/BL99/_mg_macserv_26065_-1320158352                                           Pool/BL99           MotorGroup
16                  simulator/bl98/motor05                                      Simulator/BL98            SimuMotor
17                  simulator/bl98/motor06                                      Simulator/BL98            SimuMotor
18                  simulator/bl98/motor07                                      Simulator/BL98            SimuMotor
19                simulator/BL98/motctrl01                                      Simulator/BL98        SimuMotorCtrl
20              expchan/BL99_Simu0DCtrl1/1                  BL99_0D8                 Pool/BL99      ZeroDExpChannel
21             expchan/BL99_UxTimerCtrl1/1                BL99_Timer                 Pool/BL99         CTExpChannel
22...
23
24ITango [1]: lsdevclass
25SimuCoTiCtrl                   TangoAccessControl             ZeroDExpChannel
26Door                           Motor                          DataBase
27MotorGroup                     IORegister                     SimuMotorCtrl
28TangoTest                      MacroServer                    TauTest
29SimuMotor                      SimuCounterEx                  MeasurementGroup
30Pool                           CTExpChannel
31
32ITango [1]: lsserv
33MacroServer/BL99               MacroServer/BL98               Pool/V2
34Pool/BL99                      Pool/BL98                      TangoTest/test
35Pool/tcoutinho                 Simulator/BL98
36TangoAccessControl/1           TauTest/tautest                DataBaseds/2
37MacroServer/tcoutinho          Simulator/BL99

Customized tango error message and introspection

ITango intercepts tango exceptions that occur when you do tango operations (ex.: write an attribute with a value outside the allowed limits) and tries to display it in a summarized, user friendly way. If you need more detailed information about the last tango error, you can use the magic command ‘tango_error’.

 1ITango [1]: test = Device("sys/tg_test/1")
 2
 3ITango [2]: test.no_value
 4API_AttrValueNotSet : Read value for attribute no_value has not been updated
 5For more detailed information type: tango_error
 6
 7ITango [3]: tango_error
 8Last tango error:
 9DevFailed[
10DevError[
11    desc = 'Read value for attribute no_value has not been updated'
12  origin = 'Device_3Impl::read_attributes_no_except'
13  reason = 'API_AttrValueNotSet'
14severity = tango._tango.ErrSeverity.ERR]
15DevError[
16    desc = 'Failed to read_attribute on device sys/tg_test/1, attribute no_value'
17  origin = 'DeviceProxy::read_attribute()'
18  reason = 'API_AttributeFailed'
19severity = tango._tango.ErrSeverity.ERR]]

Switching database

You can switch database simply by executing the ‘switchdb <host> [<port>]’ magic command.

 1ITango [1]: switchdb
 2
 3Must give new database name in format <host>[:<port>].
 4<port> is optional. If not given it defaults to 10000.
 5
 6Examples:
 7switchdb homer:10005
 8switchdb homer 10005
 9switchdb homer
10
11ITango [2]: db
12Database(homer, 10000)
13
14ITango [3]: switchdb bart       # by default port is 10000
15
16ITango [4]: db
17Database(bart, 10000)
18
19ITango [5]: switchdb lisa 10005  # you can use spaces between host and port
20
21ITango [6]: db
22Database(lisa, 10005)
23
24ITango [7]: switchdb marge:10005   # or the traditional ':'
25
26ITango [8]: db
27Database(marge, 10005)

Refreshing the database

When itango starts up or when the database is switched, a query is made to the tango Database device server which provides all necessary data. This data is stored locally in a itango cache which is used to provide all the nice features. If the Database server is changed in some way (ex: a new device server is registered), the local database cache is not consistent anymore with the tango database. Therefore, itango provides a magic command ‘refreshdb’ that allows you to reread all tango information from the database.

ITango [1]: refreshdb

Storing your favorite tango objects for later usage

Note

This feature is not available if you have installed IPython 0.11!

Since version 7.1.2, DeviceProxy, AttributeProxy and Database became pickable. This means that they can be used by the IPython ‘store’ magic command (type ‘store?’ on the itango console to get information on how to use this command). You can, for example, assign your favorite devices in local python variables and then store these for the next time you startup IPython with itango profile.

 1ITango [1]: theta = Motor("BL99_M1")  # notice how we used tango alias
 2
 3ITango [2]: store theta
 4Stored 'theta' (DeviceProxy)
 5
 6ITango [3]: Ctrl+D
 7
 8(IPython session is closed and started again...)
 9
10ITango [1]: store -r # in some versions of IPython you may need to do this ...
11
12ITango [1]: print theta
13DeviceProxy(motor/bl99/1)

Adding itango to your own ipython profile

Adding itango to the ipython default profile

Let’s assume that you find itango so useful that each time you start ipython, you want itango features to be loaded by default. The way to do this is by editing your default ipython configuration file:

  1. On IPython <= 0.10

    $HOME/.ipython/ipy_user_conf.py and add the lines 1 and 7.

    Note

    The code shown below is a small part of your $HOME/.ipython/ipy_user_conf.py. It is shown here only the relevant part for this example.

    1import itango
    2
    3def main():
    4
    5    # uncomment if you want to get ipython -p sh behaviour
    6    # without having to use command line switches
    7    # import ipy_profile_sh
    8    itango.init_ipython(ip, console=False)
    
  2. On IPython > 0.10

    First you have to check which is the configuration directory being used by IPython. For this, in an IPython console type:

    1ITango [1]: import IPython.utils.path
    2
    3ITango [2]: IPython.utils.path.get_ipython_dir()
    4<IPYTHON_DIR>
    

    now edit <IPYTHON_DIR>/profile_default/ipython_config.py and add the following line at the end to add itango configuration:

    load_subconfig('ipython_config.py', profile='tango')
    

    Alternatively, you could also load itango as an IPython extension:

    1config = get_config()
    2i_shell_app = config.InteractiveShellApp
    3extensions = getattr(i_shell_app, 'extensions', [])
    4extensions.append('itango')
    5i_shell_app.extensions = extensions
    

    for more information on how to configure IPython >= 0.11 please check the IPython configuration

And now, every time you start ipython:

ipython

itango features will also be loaded.

In [1]: db
Out[1]: Database(homer, 10000)

Adding itango to an existing customized profile

Note

This chapter has a pending update. The contents only apply to IPython <= 0.10.

If you have been working with IPython before and have already defined a customized personal profile, you can extend your profile with itango features without breaking your existing options. The trick is to initialize itango extension with a parameter that tells itango to maintain the existing options (like colors, command line and initial banner).

So, for example, let’s say you have created a profile called nuclear, and therefore you have a file called $HOME/.ipython/ipy_profile_nuclear.py with the following contents:

 1import os
 2import IPython.ipapi
 3
 4def main():
 5    ip = IPython.ipapi.get()
 6
 7    o = ip.options
 8    o.banner = "Springfield nuclear powerplant CLI\n\nWelcome Homer Simpson"
 9    o.colors = "Linux"
10    o.prompt_in1 = "Mr. Burns owns you [\\#]: "
11
12main()

In order to have itango features available to this profile you simply need to add two lines of code (lines 3 and 7):

 1import os
 2import IPython.ipapi
 3import itango
 4
 5def main():
 6    ip = IPython.ipapi.get()
 7    itango.init_ipython(ip, console=False)
 8
 9    o = ip.options
10    o.banner = "Springfield nuclear powerplant CLI\n\nMr. Burns owns you!"
11    o.colors = "Linux"
12    o.prompt_in1 = "The Simpsons [\\#]: "
13
14main()

This will load the itango features into your profile while preserving your profile’s console options (like colors, command line and initial banner).

Creating a profile that extends itango profile

Note

This chapter has a pending update. The contents only apply to IPython <= 0.10.

It is also possible to create a profile that includes all itango features and at the same time adds new ones. Let’s suppose that you want to create a customized profile called ‘orbit’ that automatically exports devices of class ‘Libera’ for the booster accelerator (assuming you are working on a synchrotron like institute ;-). Here is the code for the $HOME/.ipython/ipy_profile_orbit.py:

 1import os
 2import IPython.ipapi
 3import IPython.genutils
 4import IPython.ColorANSI
 5import itango
 6import StringIO
 7
 8def magic_liberas(ip, p=''):
 9    """Lists all known Libera devices."""
10    data = itango.get_device_map()
11    s = StringIO.StringIO()
12    cols = 30, 15, 20
13    l = "%{0}s %{1}s %{2}s".format(*cols)
14    print >>s, l % ("Device", "Alias", "Server")
15    print >>s, l % (cols[0]*"-", cols[1]*"-", cols[2]*"-")
16    for d, v in data.items():
17        if v[2] != 'Libera': continue
18        print >>s, l % (d, v[0], v[1])
19    s.seek(0)
20    IPython.genutils.page(s.read())
21
22def main():
23    ip = IPython.ipapi.get()
24
25    itango.init_ipython(ip)
26
27    o = ip.options
28
29    Colors = IPython.ColorANSI.TermColors
30    c = dict(Colors.__dict__)
31
32    o.banner += "\n{Brown}Welcome to Orbit analysis{Normal}\n".format(**c)
33
34    o.prompt_in1 = "Orbit [\\#]: "
35    o.colors = "BlueTango"
36
37    ip.expose_magic("liberas", magic_liberas)
38
39    db = ip.user_ns.get('db')
40    dev_class_dict = itango.get_class_map()
41
42    if not dev_class_dict.has_key("Libera"):
43        return
44
45    for libera in dev_class_dict['Libera']:
46        domain, family, member = libera.split("/")
47        var_name = domain + "_" + member
48        var_name = var_name.replace("-","_")
49        ip.to_user_ns( { var_name : tango.DeviceProxy(libera) } )
50
51main()

Then start your CLI with:

$ ipython --profile=orbit

and you will have something like this

_images/itango02.png

Advanced event monitoring

With itango it is possible to monitor change events triggered by any tango attribute which has events enabled.

To start monitoring the change events of an attribute:

ITango [1]: mon -a BL99_M1/Position
'BL99_M1/Position' is now being monitored. Type 'mon' to see all events

To list all events that have been intercepted:

 1ITango [2]: mon
 2  ID           Device    Attribute            Value       Quality             Time
 3---- ---------------- ------------ ---------------- ------------- ----------------
 4   0     motor/bl99/1        state               ON    ATTR_VALID  17:11:08.026472
 5   1     motor/bl99/1     position            190.0    ATTR_VALID  17:11:20.691112
 6   2     motor/bl99/1        state           MOVING    ATTR_VALID  17:12:11.858985
 7   3     motor/bl99/1     position    188.954072857 ATTR_CHANGING  17:12:11.987817
 8   4     motor/bl99/1     position    186.045533882 ATTR_CHANGING  17:12:12.124448
 9   5     motor/bl99/1     position    181.295838155 ATTR_CHANGING  17:12:12.260884
10   6     motor/bl99/1     position     174.55354729 ATTR_CHANGING  17:12:12.400036
11   7     motor/bl99/1     position     166.08870515 ATTR_CHANGING  17:12:12.536387
12   8     motor/bl99/1     position     155.77528943 ATTR_CHANGING  17:12:12.672846
13   9     motor/bl99/1     position    143.358230136 ATTR_CHANGING  17:12:12.811878
14  10     motor/bl99/1     position    131.476140017 ATTR_CHANGING  17:12:12.950391
15  11     motor/bl99/1     position    121.555421781 ATTR_CHANGING  17:12:13.087970
16  12     motor/bl99/1     position    113.457930987 ATTR_CHANGING  17:12:13.226531
17  13     motor/bl99/1     position    107.319423091 ATTR_CHANGING  17:12:13.363559
18  14     motor/bl99/1     position    102.928229946 ATTR_CHANGING  17:12:13.505102
19  15     motor/bl99/1     position    100.584726495 ATTR_CHANGING  17:12:13.640794
20  16     motor/bl99/1     position            100.0    ATTR_ALARM  17:12:13.738136
21  17     motor/bl99/1        state            ALARM    ATTR_VALID  17:12:13.743481
22
23ITango [3]: mon -l mot.* state
24  ID           Device    Attribute            Value       Quality             Time
25---- ---------------- ------------ ---------------- ------------- ----------------
26   0     motor/bl99/1        state               ON    ATTR_VALID  17:11:08.026472
27   2     motor/bl99/1        state           MOVING    ATTR_VALID  17:12:11.858985
28  17     motor/bl99/1        state            ALARM    ATTR_VALID  17:12:13.743481

To stop monitoring the attribute:

ITango [1]: mon -d BL99_M1/Position
Stopped monitoring 'BL99_M1/Position'

Note

Type ‘mon?’ to see detailed information about this magic command