Epics Signal

In this notebook you will:

  • Connect to some simulated hardware using EpicsSignal
  • Explore the EpicsSignal interface.

Recommend Prerequisites:

Simulated Hardware

Below, we will connect to EPICS IOC(s) controlling simulated hardware in lieu of actual motors, detectors. The IOCs should already be running in the background. Run this command to verify that they are running: it should produce output with RUNNING on each line. In the event of a problem, edit this command to replace status with restart all and run again.

In [ ]:
!supervisorctl -c supervisor/supervisord.conf status

Hello, EpicsSignal

An EpicsSignal is ophyd's representation of a single EPICS channel or a pair of channels, one readable and one writeable.

In [ ]:
from ophyd import EpicsSignal
In [ ]:
a = EpicsSignal('simple:A', name='a')
In [ ]:
a
In [ ]:
a.name  # human-friendly label, which will be encoded in 'documents' emitted by bluesky
In [ ]:
a.wait_for_connection()
In [ ]:
a.connected
In [ ]:
a.get()
In [ ]:
a.put(3)
a.get()
In [ ]:
a.read()
In [ ]:
a.describe()

Exercise

Instaniate an EpicsSignal that is connect to some made-up PV that does not exist, as in:

broken = EpicsSignal('THIS_IS_NOT_A_THING', name='broken')

What does broken.connected do? What about broken.wait_for_connection()? And broken.read()?

In [ ]:
 
In [ ]:
 
In [ ]:
 
In [ ]:
 

Read-only

The EpicsSignalRO represents a read-only signal. It can't be put to.

In [ ]:
from ophyd import EpicsSignalRO

x = EpicsSignalRO('random_walk:x', name='x')
In [ ]:
x.get()

Try putting a value to x It will raise a ReadOnlyError.

In [ ]:
 

A read–write pair

Sometimes the readback and setpoint are different PVs. We can group them into one EpicsSignal.

In [ ]:
temp = EpicsSignal('thermo:I', write_pv='thermo:SP', name='temp')
temp
In [ ]:
temp.get()
In [ ]:
temp.put(105)
In [ ]:
temp.get()

This IOC simulates the oscillations of a temperature controller and will take some time to settle to the desired value. Executing the cell above several times will return varied values. This illustrates the importance of tracking "done"-ness, which we will address in the tutorial on Devices.

More on status later.

Subscribe

The actions on an EPICS channel are:

  • read (get)
  • write (put)
  • monitor (subscribe, "event add")

To subscribe is to say, "Send me updates asynchronously whenever the value changes." To process these changes, we write a function that will be called each time a new value arrives and register than function with the Signal.

In [ ]:
x = EpicsSignal('random_walk:x', name='x')

def callback(value, old_value, **kwargs):
    print(f"Value changed from {old_value} to {value}.")
    
token = x.subscribe(callback)
In [ ]:
token  # We can use this to unsubscribe.
In [ ]:
x.unsubscribe(token)

Exercise

Define and subscribe a callback that prints "+" when the value changes in the positive direction and "-" when it changes in the negative direction.

In [ ]:
 
In [ ]:
%load solutions/callback_print_sign.py

set is put like with a way to know when the action is complete.

put is the low-level method that actually communicates with hardware. set is a higher-level method that calls put and then tracks when the action initiated by put has completed, either by using Channel Access "put completion" or by polling the signal on a background thread.

In [ ]:
temp.tolerance = 0.05
In [ ]:
status = temp.set(273)
status.add_callback(lambda *args, **kwargs: print('done!'))
# Wait several seconds and then 'done!' will be printed by a background thread.

More about status and exactly what is happening here in the notebook on Device.

Accessing the PV name for debugging

When we have many signals in play, it can be useful to as a Signal which PV it is connected to (or attempting to connect to).

In [ ]:
a.pvname  # PV name we gave above

If ophyd is failing to connect, we can try to isolate the problem by using another Channel Access client like caget or caproto-get.

In [ ]:
!caproto-get 'simple:A'

We can add verbose output to learn more about which server this is from, etc.

In [ ]:
!caproto-get -v 'simple:A'
In [ ]:
!caproto-get -vvv 'simple:A'

So many names

In summary, we have:

  • The pvname, an address for machines
  • The name, a label for humans and downstream analysis code that wants 'temperature' not 'PV:asdfoijefopefpoaewaopivjapoefijaeftep'.
  • The name of the variable in Python, the name we use in the code. There could be multiple of these pointing to the same object, as in a = b = EpicsSignal(...).