Implementations of select EPICS-compatible records in caproto.
- Alive record
- APS BSS
- Labjack
- Manager
- Motor
pip install caproto-apps
The AliveGroup provides equivalent functionality to the EPICS alive record and is compatible with existing alive daemons.
It is intended to be added to an existing PVGroup
using caproto's
SubGroup
wrapper:
from caprotoapps import AliveGroup
class MyIOC(PVGroup):
alive = SubGroup(
AliveGroup,
prefix="alive",
remote_host="xapps2.xray.aps.anl.gov",
remote_port=5678, # Optional, 5678 is the default port
ioc_name="my_ioc", # If omitted, will use the parent IOC prefix
)
if __name__ == "__main__":
# Start the IOC as normal for caproto
...
An alive daemon can request environmental variables from the alive
group. Many of the default environmental variables in the EPICS alive
record are specific to EPICS (e.g. EPICS_BASE
) and so are not
included as default environmental variables in this AliveGroup
.
Presently, the default environmental variables are:
- ENGINEER
- LOCATION
- GROUP
- STY
- PREFIX
Neither caproto nor caproto-apps sets the value of these environmental variables, so it is left up to the launcher for the parent IOC (e.g. in a systemd unit). Additional default environmental variables can be added (or replaced) by subclassing the AliveGroup:
from caprotoapps.alive import AliveGroup, envvar_default_property
class MyAliveGroup(AliveGroup):
# Replace ``ENGINEER`` with ``SCIENTIST``
evd1 = envvar_default_property(1, "SCIENTIST")
# Add a new variable, "STATUS"
evd6 = envvar_default_property(1, "STATUS")
An interface to the APS beamline scheduling system. If you're looking for the EPICS version, try: apsbss.
This PVGroup tries to mimic the EPICS version, with a few exceptions:
- The proposal and ESAF fields are automatically updated whenever the
${P}proposal:id
and${P}esaf:id
fields are changed.
- The
id
fields are strings instead of integers. - There are PVs for the list of principals investigators:
${P}proposal:userPIs
and${P}esaf:userPIs
.
To allow the BSS group to access the scheduling database, you must
provide it with the host and port for the REST API, then add it as a
SubGroup to your IOC. See examples/apsbss_ioc.py
for a complete
example.
from caprotoapps import ApsBssGroup
class MyIOC(PVGroup):
...
bss = SubGroup(ApsBssGroup, prefix="bss:", dm_host="https://example.org:11236")
Then set ${P}bss:esaf:cycle
and ${P}bss:proposal:beamline
to
the corresponding cycle (e.g. "2023-1") and beamline (e.g. "25-ID-C").
To update the proposal and ESAF fields, set ${P}:bss:proposal:id
and ${P}:bss:esaf:id
respectively.
There are a set of PVGroup
objects for the T-series data
acquisition devices from the LabJack company. They are designed to
mimic the LabJack EPICS module, and operate in a similar
manner.
Note
Caproto has labjack-ljm as a dependency, which supports LabJack T-series devices. However, labjack-ljm requires that LJM be installed separately from the python support. Without this library installed, the LabJack IOC will not function. See the LJM user's guide for more information.
There is a group for each supported device, which is currently:
- LabJackT4
though there are plans to support more in the future.
To add support for a LabJack device, include the following in an IOC:
from caprotoapps import LabJackT4
class MyIOC(PVGroup):
t4_1 = SubGroup(LabJackT4, prefix="T4_1:", identifier="labjack01")
t4_2 = SubGroup(LabJackT4, prefix="T4_2:", identifier="labjack02")
t4_sim = SubGroup(LabJackT4, prefix="T4_3:", identifier="-2")
if __name__ == "__main__":
# Start your IOC as usual
...
For a complete example, see examples/labjack_ioc.py
.
identifier can be any valid LJM identifier to distinguish a device:
- The hostname of a network-connected device (see note)
- The IP address of a network-connected device
- The USB port of a USB-connected device
- The serial number of a connected device
- The name of a connected device
- "-2" to use the simulated device
- "ANY" to use the first device found (not recommended)
Note
Hostnames are not supported by LJM, so caprotoapps will first try to resolve the identifier as a hostname, and if that fails will use the identifier as provided.
The ManagerGroup
allows for remote management of other
IOCs. Currently the only supported style is that of APS beamline
controls group. To allow control of an IOC, specify the path to the
startup script using the script parameter.
from caproto.server import SubGroup
from caprotoapps import ManagerGroup
class MyIOC(PVGroup):
ioc_manager = SubGroup(ManagerGroup,
script="/path/to/script.sh")
If the script can be reached on another machine via SSH, then the
following pattern can also be used, provided that passwordless login
is set up (i.e. using ssh-keygen
):
class MyIOC(PVGroup):
ioc_manager = SubGroup(ManagerGroup,
script="myuser@myhost:/path/to/script.sh")
```
**Note:** The *console* PV is currently not implemented.
It is possible to limit which IOCs can be started or stopped via an IOC ManagerGroup using the allow_start and allow_stop parameters during initialization:
class MyIOC(PVGroup):
mission_critical_manager = SubGroup(ManagerGroup,
allow_start=True,
allow_stop=False)
The status PVs startable and stoppable are read-only indicators of whether the IOC can be controlled via this ManagerGroup. Re-starting an IOC requires both allow_start and allow_stop to be true.
Caproto-apps has a base class that can be used for individual motors. It contains simple functionality for common motor features, similar to the EPICS motor record.
The MotorFieldsBase class contains all the basic functionality for a motor record. Support for certain motor types can be added in through custom data type classes. This is necessary so that motor-specific parameters can be passed in, such as axis in the following example:
from caproto.server import PVGroup, pvproperty, PvpropertyDouble
from caprotoapps import MotorFieldsBase
class CustomMotor(PvpropertyDouble):
axis: int
def __init__(self, axis: int, *args, **kwargs):
self.axis = axis
super().__init__(*args, **kwargs)
async def do_move(self, value: float, speed: float):
"""This function gets executing when the motor should actually move."""
print(f"Moving {self.axis=} at {value=} at {speed=} steps/sec.")
class MotorIOC(PVGroup):
"""An IOC showing motor devices."""
m1 = pvproperty(name="m1", axis=1, record="motor_base", value=0.0, dtype=CustomMotor, precision=4)
m2 = pvproperty(name="m2", axis=2, record="motor_base", value=0.0, dtype=CustomMotor, precision=2)
Only some features have been implemented. Kindly submit an issue for missing features that you want to use.
- Calibration
- Fully supported, though not all other fields properly change their behavior in response to the SET field.
- Command Buttons
- Not implemented
- Resolution
- MRES is used to calculate steps from the dial value. Remaining fields are not used.
- Motion
- VELO is used as the speed when actually moving the motor. Remaining fields are not used.
- Links
- Not used
- Limits
- Soft limits are enforced, and the limits respond to the SET field. The parent pvproperty's upper_ctrl_limit and lower_ctrl_limit properties are independent of the record limit fields.
- Drive
- VAL, DVAL, and RVAL all update one another. If RVAL is changed, the motor will move. RLV and SYNC are not used.
- Readback
- RBV, DRBV, and RRBV all update in 0.1 sec periods. DMOV and MOVN update when the motor is moving. The remaining fields are unused.
- Servo
- Not used.
- Alarm
- Not used.
- Miscellaneous
- PREC is tied to the parent pvproperty's precision metadata. Changing PREC updates the precision of the remaining floating-point fields.
- Private
- Not used.
To install caproto-apps for development, first clone the github repository:
git clone https://github.com/canismarko/caproto-apps.git
Then run tests with pytest
pytest
(venv) $ python -m build
(venv) $ twine check dist/*
(venv) $ twine upload -r testpypi dist/*
(venv) $ twine upload dist/*