A package for creating Ophyd and Ophyd-async devices from configuration files.
Instead of instantiating devices directly in python, Guarneri reads a configuration file and creates/connects the devices for you. This provides the following benefits:
- Beamline configuration is in a human-readable configuration file (e.g. TOML).
- Other tools can modify the configuration file if needed.
- Devices can be connected in parallel (faster).
- Missing devices are handled gracefully.
- Devices can be simulated/mocked by changing a single value in the config file.
Let's say you have some ophyd and ophyd-async devices defined
(with type hints) in a file called devices.py
. This is not
specific to guarneri, just regular Ophyd.
from ophyd_async.epics import epics_signal_rw
from ophyd_async.core import AsyncDevice
from ophyd import Device, Component
from guarneri import Instrument
class MyDevice(Device):
description = Component(".DESC")
class MyAsyncDevice(AsyncDevice):
def __init__(self, prefix: str, name: str = ""):
self.description = epics_signal_rw(str, f"{prefix}.DESC")
super().__init__(name=name)
def area_detector_factory(hdf: bool=True):
# Create devices here using the arguments
area_detector = ...
return area_detector
Instead of instantiating these in a python startup script, Guarneri
will let you create devices from a TOML configuration file. First
we create a TOML file (e.g. instrument.toml
) with the necessary parameters, these map
directly onto the arguments of the device's __init__()
, or the
arguments of a factory that returns a device.
[my_device.device1]
prefix = '255id:'
[async_device.device3]
prefix = '255id:'
[area_detector.sim_det]
hdf = true
Then in your beamline startup code, create a Guarneri instrument and load the config files.
from io import StringIO
from devices import MyDevice, MyAsyncDevice, area_detector_factory
# Prepare the instrument device
instrument = Instrument({
"my_device": MyDevice,
"async_device": MyAsyncDevice,
"area_detector": area_detector_factory,
})
# Create the devices from the TOML configuration file
instrument.load_config_files("instrument.toml")
# Optionally connect all the devices
await instrument.connect_devices()
# Now use the devices for science!
instrument.devices['device_1'].description.get()
The first argument to guarneri.Instrument.__init__()
is a mapping
of TOML section names to device classes. Guarneri then introspects the
device or factory to decide which TOML keys and types are valid. In
the above example, the heading [my_device.device1]
will create an
instance of MyDevice()
with the name "device1"
and prefix
"255id:"
. This is equivalent to MyDevice(prefix="255id:",
name="device1")
.
Happi has a similar goal to Guarneri, but with a different
scope. While Happi is meant for facility-level configuration (e.g.
LCLS), Guarneri is aimed at individual beamlines at a synchrotron.
Happi uses HappiItem
classes with ItemInfo
objects to describe the devices definitions, while Guarneri uses the
device classes themselves. Happi provides a python client for adding
and modifying the devices, while Guarneri uses human-readable
configuration files.
Which one is better? Depends on what you're trying to do. If you want a flexible and scalable system that shares devices across a facility, try Happi. If you want a way to get devices running quickly on your beamline before users show up, try Guarneri.
Sphinx-generated documentation for this project can be found here: https://spc-group.github.io/guarneri/
Describe the project requirements (i.e. Python version, packages and how to install them)
The following will download the package and load it into the python environment.
git clone https://github.com/spc-group/guarneri
pip install guarneri
For development of guarneri, install as an editable project with all development dependencies using:
pip install -e ".[dev]"
$ pip install -e . $ pytest -vv