Skip to content

AustralianSynchrotron/as-ophyd-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ophyd-epics-api

The ophyd-api is used in production on most beamlines at the Australian Synchrotron. This github version is just the latest commit, with some small edits for GitHub. The active development is current done in an on prem repository. Note that it depends on some internal libraries that are yet to be released to GitHub, so this code is more to demonstrate our approach, than be a directly useable system. There is a strong dependence on how ophyd devices are instantiated by our beamline libraries. We are more than happy to discuss this with interested parties.

about

This project is built off the work previously done on the SAXS/WAXS beamline

This is a fastapi server that provides websocket based subscription over ophyd device components that are suitable to be monitored (components that are low level type of components (like EPICS signals)).

It is designed to be provided with a python library that contains the relevant ophyd device instances.

how to use

At a basic high level, you provide it with a python library (typically a beamline library) by way of running it in a virtual environment or a system that has the relevant library in its path. You then provide it with the library to use via a launch option, and then you can subscribe to the low level attributes (typically corresponding to a single EPICS PV) of any of the OPHYD device instances provided by the library, via GET and POST requests and a websocket connection to this API.

Note about properly describing ophyd instances

This API works with some standard base epics types of ophyd devices (like EpicsMotor, EpicsSignal, EpicsSignalBase) out of the box.

If you supply your own custom device types, then you may find you run into difficulties when querying for that device type through this API.

For example, if the following describes your setup:

  • this API is accessible at localhost:8080
  • beamline library called xas_beamline_library accessible in its venv
  • started with the command option --library xas_beamline_library
  • xas_beamline_library has a custom ophyd device defined at xas_beamline_library.setup.beamline_builder.devices.dcm

then you should be able to query the dcm device through this api via a GET request to: http://localhost:8080/api/v1.0/devices/setup.beamline_builder.devices.dcm?describe=true

However you find that the fields reported don't report the proper values, then the reason is due to the code at @app.get(f"{base_url}/devices/{{device}}") in the ophyd_api.py

You may be tempted to add in your own isinstance check,

There is a plugin option available to you instead:

You can supply another argument to the startup command, --describer or -d and this value should be similar to the --library value. A string that would be value to the importlib.import_module() call when running in the API's venv.

so for example if you had a module in your beamline library called describer_plugin.py then you could pass --describer xas_beamline_library.describer_plugin and then if your file describer_plugin.py contains code similar to the below, it will work:

from xas_beamline_library.devices.xas_dcm import MotorDCM

def describe(device_cpt, description):
    """to be a valid plugin it must be named describe and take two args
    and raise NotImplementedError if it does not want to supply an updated
    description dict."""
    if isinstance(device_cpt, MotorDCM):
        my_description = dict(description)
        # following three lines add to the description dict, need to be
        # values that will readily parse to JSON.
        my_description["write_pv"] = device_cpt.setpoint.get()
        my_description["lower_disp_limit"] = device_cpt.energy.lo_lim.get()
        my_description["upper_disp_limit"] = device_cpt.energy.hi_lim.get()
        return my_description
    else:
        raise NotImplementedError("I was made to process components of type "
                                  "MotorDCM as defined by the "
                                  "xas-beamline-library")

plugin interface:

The interface for the plugins supported by this API is that they contain a describe function which takes a device component instance as the first arg and a description dict as the second arg. the function is then free to create a new description dict filling in whatever values for whatever keys it wants, but if you expect it to work with some of the react ophyd components that might be making use of this API then you should adhere to the key name conventions.

The describe function should additionally raise a NotImplementedError if it does not know or want to supply an updated description dict.

plugin handling behaviour

multiple plugin modules can be specified at the command line, simply repeat the --describer or -d option multiple times.

plugins will be given a chance to modify the description dict in the order in which they are supplied.

An xas beamline library example walk-through

Using the XAS beamline's DCM bragg angle (which indicates energy being output) as an example to display in the browser.

First check that you are able to read the value without this API. First try to read the value using a plain caget. This will prove that your computer is able to 'speak' to the EPICS network in question.

The command to read would be:

caget SR12ID01DCM01:ENERGY_RBV

If that works then you know you can safely move on to the next part.

Next check that the beamline library you want to use is configured correctly and is working.

Get yourself a python virtual environment that have the beamline library installed in.

Open a python REPL interpretor and then try to import the beamline library.

The commands we'll use is:

from xas_beamline_library.queueserver.qs_config import *
dcm.bragg.user_readback.get()

You can see that the above corresponds to these lines of the code.

if the above works then we can move onto launching this ophyd-api:

The command to use will be:

python ophyd_api.py --library xas_beamline_library --port 8080 --log-level debug

Note that the value provided for --library is the value that would be valid as an import statement in the code or in the REPL. So for example if you look at that line we ran previously:

from xas_beamline_library.queueserver.qs_config import *

The --library value needs to match that. This value is different to the name of the library in the python packaging index (which for xas beamline library is xas-beamline-library (the value you would use if you do pip install or poetry add))

Now the ophyd-api should be up and running on port 8080 and configured to search through the xas-beamline-library for ophyd devices when we later use a client to connect and subscribe to that device.

Now we're ready to try and connect to it with a react app.

First you will need to add the necessary files to your react app to allow websocket communication with the ophyd api.

Then the value you would want to use for an ophyd status field component's "device" prop would be "queueserver.qs_config.dcm.bragg.user_readback" because remember that when we confirmed it just in the python REPL we used this command:

from xas_beamline_library.queueserver.qs_config import *
dcm.bragg.user_readback.get()

The first part (xas_beamline_library) was already provided to the ophyd-api via the --library option when we launched it. But in the python repl we had to go into the queueserver module and then the qs_config submodule to pull out the instance named dcm and then get its bragg attribute and then get the bragg attribute's user_readback attribute which corresponded to the PV that we were actually interested in.

So that's why we use the string "queueserver.qs_config.dcm.bragg.user_readback" because that points the ophyd-api all the way down into the actual low-level ophyd component that maps to an EPICS PV that we can subscribe to ("monitor" in EPICS speak).

A temporary mock ioc example walk-through

For a slightly different example, we can still test the functionality of the ophyd-api even if we don't have access to the 'real' EPICS network, by using a mock IOC powered by caproto, defining a "beamline-library" to wrap one of its exposed PVs and then seeing its value change when we subscribe to it.

For convenience there is such an example is provided in this repo in the directory ophyd_epics_api/test_local_ioc, which is based on the confluence page on caproto. The python script called mock_ioc.py is the mock IOC obviously, and the devices.py python script is the beamline-library we'll use (technically it will see the entire test_local_ioc as the beamline library, but when we supply device addresses for it to use we will start with devices.).

To run it, in a separate terminal run the mock_ioc.py file with simply:

python mock_ioc.py

You now have an IOC running on your machine with a few PVs

Next you'll start the ophyd-api with the following launch command:

EPICS_CA_ADDR_LIST=127.0.0.1 python ophyd_api.py --library test_local_ioc --port 8080 --log-level debug

The EPICS_CA_ADDR_LIST environment variable is required to tell it (or rather, the pyepics that ophyd uses under the hood) to look for ioc's on your localhost, and to use the library test_local_ioc as the beamline library to look for ophyd devices in. This works because that's the name of the folder (python package) in the same directory as the ophyd_api.py script (so import test_local_ioc is valid python for the ophyd_api script to execute by virtue of the current directory being one of the places that the import action traverses in looking for packages to import).

Next here is an example of the device prop value that you would then use in a suitable react app would be:

"devices.mock_record.ai0"

to test it quickly without the use of a full react application you can use the provided postman scripts.

About

RESTful and Websocket Ophyd Device connection.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors