Merge branch 'xtreme_devices_update' into 'master'

Xtreme devices update

See merge request bec/ophyd_devices!19
This commit is contained in:
2023-05-09 16:43:16 +00:00
5 changed files with 393 additions and 17 deletions

View File

@ -102,14 +102,46 @@ signals:
onFailure: retry
status: {enabled: true}
manipulator:
description: 'Sample Manipulator'
deviceClass: X07MASampleManipulator
deviceConfig: {name: manipulator, prefix: 'X07MA-ES1-MAG:'}
sample_hor:
description: 'Horizontal sample position'
deviceClass: EpicsMotor
deviceConfig: {name: sample_hor, prefix: 'X07MA-ES1-AI:TRZS'}
acquisitionConfig: {acquisitionGroup: monitor, readoutPriority: baseline, schedule: sync}
onFailure: retry
status: {enabled: true}
sample_vert:
description: 'Horizontal sample position'
deviceClass: EpicsMotor
deviceConfig: {name: sample_vert, prefix: 'X07MA-ES1-AI:TRY1'}
acquisitionConfig: {acquisitionGroup: monitor, readoutPriority: baseline, schedule: sync}
onFailure: retry
status: {enabled: true}
sample_rot:
description: 'Horizontal sample position'
deviceClass: EpicsMotor
deviceConfig: {name: sample_rot, prefix: 'X07MA-ES1-AI:ROY1'}
acquisitionConfig: {acquisitionGroup: monitor, readoutPriority: baseline, schedule: sync}
onFailure: retry
status: {enabled: true}
harmonic:
description: 'ID harmonic'
deviceClass: EpicsSignal
deviceConfig: {name: harmonic, prefix: 'X07MA-ID:HARMONIC'}
acquisitionConfig: {acquisitionGroup: monitor, readoutPriority: baseline, schedule: sync}
onFailure: retry
status: {enabled: true}
# manipulator:
# description: 'Sample Manipulator'
# deviceClass: X07MASampleManipulator
# deviceConfig: {name: manipulator, prefix: 'X07MA-ES1-MAG:'}
# acquisitionConfig: {acquisitionGroup: monitor, readoutPriority: baseline, schedule: sync}
# onFailure: retry
# status: {enabled: true}
temperature:
description: 'Temperature controller'
deviceClass: X07MATemperatureController
@ -118,10 +150,10 @@ temperature:
onFailure: retry
status: {enabled: true}
TControl:
tcontrol:
description: 'Automatic temperature control'
deviceClass: X07MAAutoTemperatureControl
deviceConfig: {name: 'TControl', prefix: 'X07MA-ES1-TEMP:'}
deviceConfig: {name: 'tcontrol', prefix: 'X07MA-ES1-TEMP:'}
acquisitionConfig: {acquisitionGroup: monitor, readoutPriority: baseline, schedule: sync}
onFailure: retry
status: {enabled: true}

View File

@ -1,19 +1,19 @@
"""
ophyd device classes for X07MA beamline
"""
from collections import OrderedDict
import time
import traceback
from collections import OrderedDict
from typing import Any
from ophyd import Component as Cpt
from ophyd import FormattedComponent as FCpt
from ophyd import Device, EpicsSignal, EpicsSignalRO, Kind, PVPositioner, EpicsMotor
from ophyd.flyers import FlyerInterface
from ophyd.status import DeviceStatus, SubscriptionStatus
from ophyd.pv_positioner import PVPositionerComparator
import traceback
from bec_utils import bec_logger
from ophyd import Component as Cpt
from ophyd import Device, EpicsMotor, EpicsSignal, EpicsSignalRO
from ophyd import FormattedComponent as FCpt
from ophyd import Kind, PVPositioner
from ophyd.flyers import FlyerInterface
from ophyd.pv_positioner import PVPositionerComparator
from ophyd.status import DeviceStatus, SubscriptionStatus
logger = bec_logger.logger

View File

@ -1 +1,2 @@
from .sim import SynAxisMonitor, SynAxisOPAAS, SynFlyer, SynSignalRO, SynSLSDetector
from .sim_xtreme import SynXtremeOtf

View File

@ -7,7 +7,7 @@ from typing import List
import numpy as np
from bec_utils import BECMessage, MessageEndpoints, bec_logger
from ophyd import Component as Cpt
from ophyd import Device, DeviceStatus, PositionerBase, Signal
from ophyd import Device, DeviceStatus, OphydObject, PositionerBase, Signal
from ophyd.sim import _ReadbackSignal, _SetpointSignal
from ophyd.utils import LimitError, ReadOnlyError
@ -438,7 +438,109 @@ class SynFlyer(Device, PositionerBase):
flyer = threading.Thread(target=produce_data, args=(self, metadata))
flyer.start()
# time.sleep(0.01)
class SynController(OphydObject):
def on(self):
pass
def off(self):
pass
class SynFlyerLamNI(Device, PositionerBase):
def __init__(
self,
*,
name,
readback_func=None,
value=0,
delay=0,
speed=1,
update_frequency=2,
precision=3,
parent=None,
labels=None,
kind=None,
device_manager=None,
**kwargs,
):
if readback_func is None:
def readback_func(x):
return x
sentinel = object()
loop = kwargs.pop("loop", sentinel)
if loop is not sentinel:
warnings.warn(
f"{self.__class__} no longer takes a loop as input. "
"Your input will be ignored and may raise in the future",
stacklevel=2,
)
self.sim_state = {}
self._readback_func = readback_func
self.delay = delay
self.precision = precision
self.tolerance = kwargs.pop("tolerance", 0.5)
self.device_manager = device_manager
# initialize values
self.sim_state["readback"] = readback_func(value)
self.sim_state["readback_ts"] = ttime.time()
super().__init__(name=name, parent=parent, labels=labels, kind=kind, **kwargs)
self.controller = SynController(name="SynController")
def kickoff(self, metadata, num_pos, positions, exp_time: float = 0):
positions = np.asarray(positions)
def produce_data(device, metadata):
buffer_time = 0.2
elapsed_time = 0
bundle = BECMessage.BundleMessage()
for ii in range(num_pos):
bundle.append(
BECMessage.DeviceMessage(
signals={
"syn_flyer_lamni": {
"flyer_samx": {"value": positions[ii, 0], "timestamp": 0},
"flyer_samy": {"value": positions[ii, 1], "timestamp": 0},
}
},
metadata={"pointID": ii, **metadata},
).dumps()
)
ttime.sleep(exp_time)
elapsed_time += exp_time
if elapsed_time > buffer_time:
elapsed_time = 0
device.device_manager.producer.send(
MessageEndpoints.device_read(device.name), bundle.dumps()
)
bundle = BECMessage.BundleMessage()
device.device_manager.producer.set_and_publish(
MessageEndpoints.device_status(device.name),
BECMessage.DeviceStatusMessage(
device=device.name,
status=1,
metadata={"pointID": ii, **metadata},
).dumps(),
)
device.device_manager.producer.send(
MessageEndpoints.device_read(device.name), bundle.dumps()
)
device.device_manager.producer.set_and_publish(
MessageEndpoints.device_status(device.name),
BECMessage.DeviceStatusMessage(
device=device.name,
status=0,
metadata={"pointID": num_pos, **metadata},
).dumps(),
)
print("done")
flyer = threading.Thread(target=produce_data, args=(self, metadata))
flyer.start()
class SynAxisOPAAS(Device, PositionerBase):

View File

@ -0,0 +1,241 @@
import threading
import time
import numpy as np
from ophyd import Component as Cpt
from ophyd import Device, Kind, Signal
from ophyd.flyers import FlyerInterface
from ophyd.ophydobj import Kind
from ophyd.status import DeviceStatus, SubscriptionStatus
from ophyd.utils import ReadOnlyError
class SynSetpoint(Signal):
def __init__(
self,
name,
*,
value=0,
dtype=float,
timestamp=None,
parent=None,
labels=None,
kind=Kind.hinted,
tolerance=None,
rtolerance=None,
metadata=None,
cl=None,
attr_name="",
auto_monitor=False,
):
self._dtype = dtype
if self._dtype == str and value == 0:
value = ""
super().__init__(
name=name,
value=value,
timestamp=timestamp,
parent=parent,
labels=labels,
kind=kind,
tolerance=tolerance,
rtolerance=rtolerance,
metadata=metadata,
cl=cl,
attr_name=attr_name,
)
def put(self, value, *, timestamp=None, force=False):
old_val = self._readback
self._readback = self._dtype(value)
self._run_subs(
sub_type="value",
old_value=old_val,
value=self._readback,
timestamp=time.time(),
)
def get(self):
return self._readback
def describe(self):
res = super().describe()
# There should be only one key here, but for the sake of generality....
for k in res:
res[k]["precision"] = self.parent.precision
return res
class SynData(Signal):
_default_sub = "value"
def __init__(
self,
*,
name,
value=0,
timestamp=None,
parent=None,
labels=None,
kind=Kind.hinted,
tolerance=None,
rtolerance=None,
metadata=None,
cl=None,
attr_name="",
auto_monitor=False,
):
super().__init__(
name=name,
value=value,
timestamp=timestamp,
parent=parent,
labels=labels,
kind=kind,
tolerance=tolerance,
rtolerance=rtolerance,
metadata=metadata,
cl=cl,
attr_name=attr_name,
)
self._reset_data()
def _reset_data(self):
self._readback = np.array([])
def get(self):
return self._readback
def append(self, val: float):
self._readback = np.append(self._readback, val)
def describe(self):
res = super().describe()
# There should be only one key here, but for the sake of
# generality....
for k in res:
res[k]["precision"] = self.parent.precision
return res
@property
def timestamp(self):
"""Timestamp of the readback value"""
return time.time()
def put(self, value, *, timestamp=None, force=False):
raise ReadOnlyError("The signal {} is readonly.".format(self.name))
def set(self, value, *, timestamp=None, force=False):
raise ReadOnlyError("The signal {} is readonly.".format(self.name))
class SynXtremeOtf(FlyerInterface, Device):
"""
PGM on-the-fly scan
"""
SUB_VALUE = "value"
SUB_FLYER = "flyer"
_default_sub = SUB_VALUE
e1 = Cpt(SynSetpoint, kind=Kind.config)
e2 = Cpt(SynSetpoint, kind=Kind.config)
time = Cpt(SynSetpoint, kind=Kind.config)
folder = Cpt(SynSetpoint, dtype=str, value="", kind=Kind.config)
file = Cpt(SynSetpoint, dtype=str, value="", kind=Kind.config)
acquire = Cpt(SynSetpoint, auto_monitor=True)
edata = Cpt(SynData, kind=Kind.hinted, auto_monitor=True)
data = Cpt(SynData, kind=Kind.hinted, auto_monitor=True)
idata = Cpt(SynData, kind=Kind.hinted, auto_monitor=True)
fdata = Cpt(SynData, kind=Kind.hinted, auto_monitor=True)
count = Cpt(SynData, kind=Kind.omitted, auto_monitor=True)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._start_time = 0
self.acquire.subscribe(self._update_status, run=False)
self.count.subscribe(self._update_data, run=False)
self._data_event = threading.Event()
self.precision = 3
def kickoff(self):
self._start_time = time.time()
self.acquire.put(1)
status = DeviceStatus(self)
status.set_finished()
return status
def complete(self):
def check_value(*, old_value, value, **kwargs):
return old_value == 1 and value == 0
status = SubscriptionStatus(self.acquire, check_value, event_type=self.acquire.SUB_VALUE)
return status
def collect(self):
data = {"time": self._start_time, "data": {}, "timestamps": {}}
for attr in ("edata", "data", "idata", "fdata"):
obj = getattr(self, attr)
data["data"][obj.name] = obj.get()
data["timestamps"][obj.name] = obj.timestamp
return data
def describe_collect(self):
desc = {}
for attr in ("edata", "data", "idata", "fdata"):
desc.update(getattr(self, attr).describe())
return desc
def _update_status(self, *, old_value, value, **kwargs):
if old_value == 1 and value == 0:
self._done_acquiring()
return
if old_value == 0 and value == 1:
threading.Thread(target=self._start_acquiring, daemon=True).start()
def _reset_data(self):
for entry in ("edata", "data", "idata", "fdata"):
getattr(self, entry)._reset_data()
self.count._readback = 0
self._data_event.clear()
def _populate_data(self):
self._reset_data()
while not self._data_event.is_set():
for entry in ("edata", "data", "idata", "fdata"):
getattr(self, entry).append(np.random.rand())
self.count._readback = len(self.edata.get())
self.count._run_subs(
sub_type="value",
old_value=self.count._readback - 1,
value=self.count._readback,
timestamp=time.time(),
)
time.sleep(0.2)
self._data_event.clear()
def _start_acquiring(self):
threading.Thread(target=self._populate_data, daemon=True).start()
timeout_event = threading.Event()
flag = timeout_event.wait(self.time.get())
if not flag:
self._data_event.set()
self.acquire.put(0)
def _update_data(self, value, **kwargs):
if value == 0:
return
data = self.collect()
self._run_subs(sub_type=self.SUB_FLYER, value=data)
if __name__ == "__main__":
obj = SynXtremeOtf(name="otf")
status = obj.time.set(4)
status.wait()
status = obj.kickoff()
status.wait()
while obj.acquire.get():
time.sleep(0.2)
print("done")