ITANGO(1)

πŸ“¦
version 0.3.0 Β· 2 versions
, in ubuntu resolute, ubuntu stonking

✨ NAME ✨

okay so bestie, the header literally just says itango and then itango again β€” iconic, no notes πŸ’… so here's the whole vibe:

ITango (https://pypi.python.org/pypi/itango/) is a PyTango CLI built on top of IPython (http://ipython.org/). It's designed to be used as an IPython profile, fr fr.

It's been a thing since PyTango 7.1.2, and it got moved out into its own separate project as of PyTango 9.2.0 πŸ¦‹

You start ITango by typing this on the command line:

$ itango

or, same energy:

$ ipython --profile=tango

and you should get something like this: [image] 🌸


πŸŽ€ FEATURES πŸŽ€

ITango works like a normal python console, but it hands you an extra little goodie bag of features from IPython, like:

  • proper (bash-like) command completion
  • automatic expansion of python variables, functions, types
  • command history (up/down arrow keys, plus the %hist command)
  • a help system (the object? syntax, and help(object))
  • persistently store your favorite variables
  • color modes

For the complete list, go check out the IPython web page (http://ipython.org/).

PLUS a whole additional set of Tango (http://www.tango-controls.org/) specific features, and this is the part that really eats:

  • automatic import of Tango objects to the console namespace (the 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 out the Highlights to see how to put all of this to good use :-)


🌈 HIGHLIGHTS 🌈

πŸ¦‹ Tab completion πŸ¦‹

ITango exports a bunch of tango specific objects right into 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
ITango [1]: De<tab>
DeprecationWarning            Device       DeviceProxy

ITango [2]: Device
Result [2]: <class 'tango._tango.DeviceProxy'>

ITango [3]: Device("sys/tg_test/1")
Result [3]: DeviceProxy(sys/tg_test/1)

ITango [4]: Datab<tab>

ITango [4]: Database

ITango [4]: Att<tab>
Attribute       AttributeError  AttributeProxy
  • the Tango Database object that your itango session is currently connected to
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. Which means when you're trying to create a new Device, hitting <tab> shows you a context sensitive list of devices. Obsessed.

ITango [1]: test = Device("<tab>
Display all 3654 possibilities? (y or n) n

ITango [1]: test = Device("sys<tab>
sys/access_control/1  sys/database/2        sys/tautest/1         sys/tg_test/1

ITango [2]: test = Device("sys/tg_test/1")

🌸 Attribute name completion 🌸

ITango can inspect the list of attributes β€” as long as the device server for the device where that attribute lives is actually running, bestie.

ITango [1]: short_scalar = Attribute("sys<tab>
sys/access_control/1/  sys/database/2/        sys/tautest/1/         sys/tg_test/1/

ITango [1]: short_scalar = Attribute("sys/tg_test/1/<tab>
sys/tg_test/1/State                sys/tg_test/1/no_value
sys/tg_test/1/Status               sys/tg_test/1/short_image
sys/tg_test/1/ampli                sys/tg_test/1/short_image_ro
sys/tg_test/1/boolean_image        sys/tg_test/1/short_scalar
sys/tg_test/1/boolean_image_ro     sys/tg_test/1/short_scalar_ro
sys/tg_test/1/boolean_scalar       sys/tg_test/1/short_scalar_rww
sys/tg_test/1/boolean_spectrum     sys/tg_test/1/short_scalar_w
sys/tg_test/1/boolean_spectrum_ro  sys/tg_test/1/short_spectrum
sys/tg_test/1/double_image         sys/tg_test/1/short_spectrum_ro
sys/tg_test/1/double_image_ro      sys/tg_test/1/string_image
sys/tg_test/1/double_scalar        sys/tg_test/1/string_image_ro
...

ITango [1]: short_scalar = Attribute("sys/tg_test/1/short_scalar")

ITango [29]: print test.read()
DeviceAttribute[
data_format = tango._tango.AttrDataFormat.SCALAR
  dim_x = 1
  dim_y = 0
has_failed = False
is_empty = False
   name = 'short_scalar'
nb_read = 1
nb_written = 1
quality = tango._tango.AttrQuality.ATTR_VALID
r_dimension = AttributeDimension(dim_x = 1, dim_y = 0)
   time = TimeVal(tv_nsec = 0, tv_sec = 1279723723, tv_usec = 905598)
   type = tango._tango.CmdArgType.DevShort
  value = 47
w_dim_x = 1
w_dim_y = 0
w_dimension = AttributeDimension(dim_x = 1, dim_y = 0)
w_value = 0]

🧚 Automatic tango object member completion 🧚

When you create a new tango object (say, a device), itango can dynamically figure out which members that device has β€” including tango commands and attributes, if the device is currently running πŸ”₯

ITango [1]: test = Device("sys/tg_test/1")

ITango [2]: test.<tab>
Display all 240 possibilities? (y or n)
...
test.DevVoid                            test.get_access_control
test.Init                               test.get_asynch_replies
test.State                              test.get_attribute_config
test.Status                             test.get_attribute_config_ex
test.SwitchStates                       test.get_attribute_list
...

ITango [2]: test.short_<tab>
test.short_image        test.short_scalar       test.short_scalar_rww   test.short_spectrum
test.short_image_ro     test.short_scalar_ro    test.short_scalar_w     test.short_spectrum_ro

ITango [2]: test.short_scalar        # old style: test.read_attribute("short_scalar").value
Result [2]: 252

ITango [3]: test.Dev<tab>
test.DevBoolean               test.DevUShort                test.DevVarShortArray
test.DevDouble                test.DevVarCharArray          test.DevVarStringArray
test.DevFloat                 test.DevVarDoubleArray        test.DevVarULongArray
test.DevLong                  test.DevVarDoubleStringArray  test.DevVarUShortArray
test.DevShort                 test.DevVarFloatArray         test.DevVoid
test.DevString                test.DevVarLongArray
test.DevULong                 test.DevVarLongStringArray

ITango [3]: test.DevDouble(56.433)  # old style: test.command_inout("DevDouble").
Result [3]: 56.433

πŸ‘‘ Tango classes as DeviceProxy πŸ‘‘

ITango exports every known tango class as a python alias to DeviceProxy. So if you wanna create a device of a class you already know (say, Libera, for example) you can literally just do:

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

And the big slay here is that the tango device name completion is sensitive to the type of device you're trying to create. Meaning: if you're mid-way through typing a device name and you press <tab>, only devices of the tango class 'Libera' will pop up as possible completions. main character behavior πŸ’…

ITango [1]: bpm1 = Libera("<tab>
BO01/DI/BPM-01  BO01/DI/BPM-09  BO02/DI/BPM-06  BO03/DI/BPM-03  BO03/DI/BPM-11  BO04/DI/BPM-08
BO01/DI/BPM-02  BO01/DI/BPM-10  BO02/DI/BPM-07  BO03/DI/BPM-04  BO04/DI/BPM-01  BO04/DI/BPM-09
BO01/DI/BPM-03  BO01/DI/BPM-11  BO02/DI/BPM-08  BO03/DI/BPM-05  BO04/DI/BPM-02  BO04/DI/BPM-10
BO01/DI/BPM-04  BO02/DI/BPM-01  BO02/DI/BPM-09  BO03/DI/BPM-06  BO04/DI/BPM-03  BO04/DI/BPM-11
BO01/DI/BPM-05  BO02/DI/BPM-02  BO02/DI/BPM-10  BO03/DI/BPM-07  BO04/DI/BPM-04
BO01/DI/BPM-06  BO02/DI/BPM-03  BO02/DI/BPM-11  BO03/DI/BPM-08  BO04/DI/BPM-05
BO01/DI/BPM-07  BO02/DI/BPM-04  BO03/DI/BPM-01  BO03/DI/BPM-09  BO04/DI/BPM-06
BO01/DI/BPM-08  BO02/DI/BPM-05  BO03/DI/BPM-02  BO03/DI/BPM-10  BO04/DI/BPM-07

ITango [1]: bpm1 = Libera("BO01<tab>
BO01/DI/BPM-01  BO01/DI/BPM-03  BO01/DI/BPM-05  BO01/DI/BPM-07  BO01/DI/BPM-09  BO01/DI/BPM-11
BO01/DI/BPM-02  BO01/DI/BPM-04  BO01/DI/BPM-06  BO01/DI/BPM-08  BO01/DI/BPM-10

ITango [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 and then hitting Enter will show you a customized representation of the object instead of the usual repr(): [image]

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

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

db.put_class_property("Libera", dict(__icon="libera.png"))

# if you placed your image in a directory different than itango.resource
# then, instead you have to specify the absolute directory

db.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 (and it takes precedence over the class property value, if one is defined):

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

⭐ List tango devices, classes, servers ⭐

ITango gives you a set of magic functions (that's ipython lingo, iykyk) that let you check the list of tango devices, classes and servers that are registered in the current database.

          ITango [1]: lsdev
                                            Device                     Alias                    Server                Class
          ---------------------------------------- ------------------------- ------------------------- --------------------
                        expchan/BL99_Dummy0DCtrl/1                  BL99_0D1                 Pool/BL99      ZeroDExpChannel
                            simulator/bl98/motor08                                      Simulator/BL98            SimuMotor
                        expchan/BL99_Dummy0DCtrl/3                  BL99_0D3                 Pool/BL99      ZeroDExpChannel
                        expchan/BL99_Dummy0DCtrl/2                  BL99_0D2                 Pool/BL99      ZeroDExpChannel
                        expchan/BL99_Dummy0DCtrl/5                  BL99_0D5                 Pool/BL99      ZeroDExpChannel
                        expchan/BL99_Dummy0DCtrl/4                  BL99_0D4                 Pool/BL99      ZeroDExpChannel
                        expchan/BL99_Dummy0DCtrl/7                  BL99_0D7                 Pool/BL99      ZeroDExpChannel
                        expchan/BL99_Dummy0DCtrl/6                  BL99_0D6                 Pool/BL99      ZeroDExpChannel
                            simulator/bl98/motor01                                      Simulator/BL98            SimuMotor
                            simulator/bl98/motor02                                      Simulator/BL98            SimuMotor
                            simulator/bl98/motor03                                      Simulator/BL98            SimuMotor
             mg/BL99/_mg_macserv_26065_-1320158352                                           Pool/BL99           MotorGroup
                            simulator/bl98/motor05                                      Simulator/BL98            SimuMotor
                            simulator/bl98/motor06                                      Simulator/BL98            SimuMotor
                            simulator/bl98/motor07                                      Simulator/BL98            SimuMotor
                          simulator/BL98/motctrl01                                      Simulator/BL98        SimuMotorCtrl
                        expchan/BL99_Simu0DCtrl1/1                  BL99_0D8                 Pool/BL99      ZeroDExpChannel
                       expchan/BL99_UxTimerCtrl1/1                BL99_Timer                 Pool/BL99         CTExpChannel
          ...

          ITango [1]: lsdevclass
          SimuCoTiCtrl                   TangoAccessControl             ZeroDExpChannel
          Door                           Motor                          DataBase
          MotorGroup                     IORegister                     SimuMotorCtrl
          TangoTest                      MacroServer                    TauTest
          SimuMotor                      SimuCounterEx                  MeasurementGroup
          Pool                           CTExpChannel

          ITango [1]: lsserv
          MacroServer/BL99               MacroServer/BL98               Pool/V2
          Pool/BL99                      Pool/BL98                      TangoTest/test
          Pool/tcoutinho                 Simulator/BL98
          TangoAccessControl/1           TauTest/tautest                DataBaseds/2
          MacroServer/tcoutinho          Simulator/BL99

Customized tango error message and introspection</p> πŸ’…

ITango (<https://pypi.python.org/pypi/itango/>) is out here catching tango exceptions for you whenever you do tango stuff β€” like, say, writing an attribute with a value that's outside the allowed limits β€” and it's gonna try to show it in a summarized, user-friendly way. No ugly wall of text, we don't do that here. And if you need the full tea on the last tango error, bestie, just hit the magic command tango_error. 🫢

ITango [1]: test = Device("sys/tg_test/1")

ITango [2]: test.no_value
API_AttrValueNotSet : Read value for attribute no_value has not been updated
For more detailed information type: tango_error

ITango [3]: tango_error
Last tango error:
DevFailed[
DevError[
    desc = 'Read value for attribute no_value has not been updated'
  origin = 'Device_3Impl::read_attributes_no_except'
  reason = 'API_AttrValueNotSet'
severity = tango._tango.ErrSeverity.ERR]
DevError[
    desc = 'Failed to read_attribute on device sys/tg_test/1, attribute no_value'
  origin = 'DeviceProxy::read_attribute()'
  reason = 'API_AttributeFailed'
severity = tango._tango.ErrSeverity.ERR]]

Switching database πŸ¦‹

You can switch database simply by executing the switchdb <host> [<port>] magic command. Iconic, honestly.

ITango [1]: switchdb

Must give new database name in format <host>[:<port>].
<port> is optional. If not given it defaults to 10000.

Examples:
switchdb homer:10005
switchdb homer 10005
switchdb homer

ITango [2]: db
Database(homer, 10000)

ITango [3]: switchdb bart       # by default port is 10000

ITango [4]: db
Database(bart, 10000)

ITango [5]: switchdb lisa 10005  # you can use spaces between host and port

ITango [6]: db
Database(lisa, 10005)

ITango [7]: switchdb marge:10005   # or the traditional ':'

ITango [8]: db
Database(marge, 10005)

Refreshing the database ✨

So, when itango starts up β€” or when the database gets switched β€” a query goes out to the tango Database device server, and it hands back all the necessary data. That data gets stored locally in a itango cache, which is literally what powers all the nice features. 🌸

But here's the thing: if the Database server changes in some way (say, a new device server gets registered), your local database cache is not consistent anymore with the tango database. So itango gives you a magic command called refreshdb that lets you reread all tango information straight from the database. Vibe check passed.

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. Which means β€” plot twist β€” they can be used by the IPython (<http://ipython.org/>) store magic command (type store? on the itango console to get the info on how to use this command, bestie). You can, for example, assign your favorite devices to local python variables and then store these for the next time you startup IPython (<http://ipython.org/>) with the itango profile. Obsessed. πŸ’–

ITango [1]: theta = Motor("BL99_M1")  # notice how we used tango alias

ITango [2]: store theta
Stored 'theta' (DeviceProxy)

ITango [3]: Ctrl+D

(IPython session is closed and started again...)

ITango [1]: store -r # in some versions of IPython you may need to do this ...

ITango [1]: print theta
DeviceProxy(motor/bl99/1)

Adding itango to your own ipython profile πŸ”₯

Adding itango to the ipython default profile πŸ’–

Okay so let's say you find itango so useful that every single time you start ipython, you want itango features loaded by default. Main character energy. 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.
import itango

def main():

    # uncomment if you want to get ipython -p sh behaviour
    # without having to use command line switches
    # import ipy_profile_sh
    itango.init_ipython(ip, console=False)
  1. 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:

ITango [1]: import IPython.utils.path

ITango [2]: IPython.utils.path.get_ipython_dir()
<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:

config = get_config()
i_shell_app = config.InteractiveShellApp
extensions = getattr(i_shell_app, 'extensions', [])
extensions.append('itango')
i_shell_app.extensions = extensions

for more information on how to configure IPython >= 0.11 please check the IPython configuration (<http://ipython.org/ipython-doc/dev/config/ipython.html#configuring-the-ipython-command-line-application>)

And now, every time you start ipython:

ipython

itango features will also be loaded. Periodt.

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've been working with IPython (<http://ipython.org/>) 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 the 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've created a profile called nuclear, and therefore you have a file called $HOME/.ipython/ipy_profile_nuclear.py with the following contents:

import os
import IPython.ipapi

def main():
    ip = IPython.ipapi.get()

    o = ip.options
    o.banner = "Springfield nuclear powerplant CLI\n\nWelcome Homer Simpson"
    o.colors = "Linux"
    o.prompt_in1 = "Mr. Burns owns you [\\#]: "

main()

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

import os
import IPython.ipapi
import itango

def main():
    ip = IPython.ipapi.get()
    itango.init_ipython(ip, console=False)

    o = ip.options
    o.banner = "Springfield nuclear powerplant CLI\n\nMr. Burns owns you!"
    o.colors = "Linux"
    o.prompt_in1 = "The Simpsons [\\#]: "

main()

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


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:

          import os
          import IPython.ipapi
          import IPython.genutils
          import IPython.ColorANSI
          import itango
          import StringIO

          def magic_liberas(ip, p=''):
              """Lists all known Libera devices."""
              data = itango.get_device_map()
              s = StringIO.StringIO()
              cols = 30, 15, 20
              l = "%{0}s %{1}s %{2}s".format(*cols)
              print >>s, l % ("Device", "Alias", "Server")
              print >>s, l % (cols[0]*"-", cols[1]*"-", cols[2]*"-")
              for d, v in data.items():
                  if v[2] != 'Libera': continue
                  print >>s, l % (d, v[0], v[1])
              s.seek(0)
              IPython.genutils.page(s.read())

          def main():
              ip = IPython.ipapi.get()

              itango.init_ipython(ip)

              o = ip.options

              Colors = IPython.ColorANSI.TermColors
              c = dict(Colors.__dict__)

              o.banner += "\n{Brown}Welcome to Orbit analysis{Normal}\n".format(**c)

              o.prompt_in1 = "Orbit [\\#]: "
              o.colors = "BlueTango"

              ip.expose_magic("liberas", magic_liberas)

              db = ip.user_ns.get('db')
              dev_class_dict = itango.get_class_map()

              if not dev_class_dict.has_key("Libera"):
                  return

              for libera in dev_class_dict['Libera']:
                  domain, family, member = libera.split("/")
                  var_name = domain + "_" + member
                  var_name = var_name.replace("-","_")
                  ip.to_user_ns( { var_name : tango.DeviceProxy(libera) } )

          main()

Then start your CLI with:

$ ipython --profile=orbit

and you will have something like this [image] β€” yes bestie, a literal screenshot moment, iconic πŸ’–


πŸ›°οΈ Advanced event monitoring πŸ›°οΈ

okay so this is the part where itango lowkey becomes a surveillance queen πŸ‘€ 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:

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

πŸ‘‘ Author πŸ‘‘

Tango Controls

2016, Tango Controls


okay that's the whole page, we did it πŸ’… every last byte accounted for, no crumbs left behind. this is ITANGO(1), dated Feb 06, 2026 β€” go forth and monitor something, bestie πŸ¦‹βœ¨

πŸ“¦ where this page comes from