/ophyd-tango

Experimental ophyd–tango integration

Primary LanguagePython

Experimental Ophyd–Tango Integration

What is this?

Ophyd endeavors to provide a hardware abstraction that is control-layer agonostic. So far this has been validated on:

  • Channel Access (two different I/O libraries, pyepics and caproto)
  • One-off socket protocols specific to particular hardwarwe
  • Simualated hardware in Python

It has not yet been validated on a second fully-featured control layer such as Tango. The goal of this project is to see what it would take to get ophyd working with Tango, and to iron out any EPICS-specific assumptions that may have leaked into ophyd's design despite the authors' best efforts.

This experiment is in its very early stages, currently just an hour's worth of hacking.

Prerequisites

This repo includes a docker-compose.yaml file obtained from the Tango documentaiton. You need docker-compose to run it. Install, e.g.

apt install docker-compose

or

brew install docker-compose

Start Tango Test Database

git clone github.com/bluesky/ophyd-tango
cd ophyd-tango
docker-compose up --build

Install client software

conda create -n pytango -c tango-controls python=3.7 pytango ipython
pip install ophyd  # I used pip to avoid conda-forge vs defaults conflicts.

Overview of Design So Far

  • TangoAttribute wraps a tango.AttributeProxy in the bluesky interface. An attribute has one value (could be an array) just like a PV does.
  • TangoDevice wraps a tango.DeviceProxy. Like ophyd.Device, a TangoDevice has an internal structure. Unlike ophyd.Device, the structure is encoded by the server and set up on the client at connection time, not encoded in the class definition. So it seems there will just be one TangoDevice, used directly, rather than many subclasses tuned to specific hardware. It will probably not use ophyd.Component internally; rather it will set up many AttributeProxy instances at __init__ time.
  • One could use ophyd.Device and ophyd.Component to build larger Devices out of TangoDevice and/or TangoAttribute.
  • One could image convenience functions to query a tango database for all its devices and automatically build corresponding TangoDevice instances on demand.

Run ophyd example

First, set this environment variable so that tango.Database() can find the database running in Docker.

export TANGO_HOST=127.0.0.1:10000

Start IPython and run ophyd_tango.py in the user namespace. This perform boilerplate bluesky setup and defines tango_attr, an object that satisfies a subset of the bluesky interface and connects to the double_scalar Tango Attribute from the test database running in Docker.

ipython -i ophyd_tango.py

Execute a simple bluesky plan like so:

In [1]: RE(count([tango_attr], 10, 1), LiveTable(['double_scalar']))
+-----------+------------+---------------+
|   seq_num |       time | double_scalar |
+-----------+------------+---------------+
|         1 | 15:49:12.2 |      -204.450 |
|         2 | 15:49:13.2 |      -207.108 |
|         3 | 15:49:14.2 |      -207.108 |
|         4 | 15:49:15.2 |      -209.702 |
|         5 | 15:49:16.2 |      -209.702 |
|         6 | 15:49:17.2 |      -212.233 |
|         7 | 15:49:18.2 |      -212.233 |
|         8 | 15:49:19.2 |      -214.699 |
|         9 | 15:49:20.3 |      -214.699 |
|        10 | 15:49:21.3 |      -217.100 |
+-----------+------------+---------------+
generator count ['20a661f7'] (scan num: 1)
Out[1]: ('20a661f7-665c-412f-b4a0-78f69aaced52',)

Notes

  • Next step is to handle set() and subscribe(). Tango's subscription semantics seem more sophisiticated than those in Channel Access. Ophyd's interface may need to be extended to fully utilize it.
  • See first_steps.md for a log of some basic tango usage, including subscriptions.

Extra

Follow up on this experimentation. Now TangoAttribute can be used with ophyd.Device

# Get tango device name from instanciation
class DeviceAttrNames(Device):
    dou = Cpt(TangoAttribute, "/double_scalar")
    flo = Cpt(TangoAttribute, "/float_scalar")

# Hardcode full tango name (an ophyd device can read from two tango devices
class DeviceFullNames(Device):
    dou = Cpt(TangoAttribute, "sys/tg_test/1/double_scalar")
    flo = Cpt(TangoAttribute, "sys/tg_test/1/float_scalar")

device_attr_names = DeviceAttrNames("sys/tg_test/1", name="some_name")
device_full_names = DeviceFullNames("", name="another_name")

A tango.DeviceProxy object can also be used like an ophyd.Device

device_tango = TangoDevice("sys/tg_test/1", read_attrs=["ampli", "double_scalar"])

By default, read reads all the tango attribute. It can be filtered with read_attrs

All those object can be used with basic BlueSky:

RE(count([tango_attr], 10, 1), LiveTable(['double_scalar']))
RE(count([tango_attr_names], 10, 1), LiveTable(['double_scalar']))
RE(count([device_full_names], 10, 1), LiveTable(['double_scalar']))
RE(count([device_tango], 10, 1), LiveTable(['double_scalar']))