refactor(mcs-card): fix mcs card integration at the beamline
This commit is contained in:
@@ -28,12 +28,16 @@ DELAY CHANNELS:
|
|||||||
- e = d
|
- e = d
|
||||||
- f = e + 1us (short pulse to OR gate for MCS triggering)
|
- f = e + 1us (short pulse to OR gate for MCS triggering)
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
from ophyd import DeviceStatus, StatusBase
|
from ophyd import DeviceStatus, StatusBase
|
||||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||||
|
from ophyd_devices import CompareStatus, TransitionStatus
|
||||||
|
|
||||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
|
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
|
||||||
CHANNELREFERENCE,
|
CHANNELREFERENCE,
|
||||||
@@ -44,6 +48,12 @@ from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import
|
|||||||
ChannelConfig,
|
ChannelConfig,
|
||||||
DelayGeneratorCSAXS,
|
DelayGeneratorCSAXS,
|
||||||
)
|
)
|
||||||
|
from csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs import READYTOREAD, ACQUIRING
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
from bec_lib.devicemanager import DeviceManagerBase, ScanInfo
|
||||||
|
from csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs import MCSCardCSAXS
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
@@ -83,6 +93,16 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
|||||||
It is operated in standard mode, not burst mode and will trigger the EXT/EN of DDG2 (channel ab).
|
It is operated in standard mode, not burst mode and will trigger the EXT/EN of DDG2 (channel ab).
|
||||||
It is responsible for opening the shutter (channel cd) and sending an extra trigger to an or gate for the MCS card (channel ef).
|
It is responsible for opening the shutter (channel cd) and sending an extra trigger to an or gate for the MCS card (channel ef).
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, name: str,
|
||||||
|
prefix: str = "",
|
||||||
|
scan_info: ScanInfo | None = None,
|
||||||
|
device_manager: DeviceManagerBase | None = None,
|
||||||
|
**kwargs):
|
||||||
|
"""
|
||||||
|
Initialize the MCSCardCSAXS with the given arguments and keyword arguments.
|
||||||
|
"""
|
||||||
|
super().__init__(name=name, prefix=prefix, scan_info=scan_info,device_manager=device_manager, **kwargs)
|
||||||
|
self.device_manager=device_manager
|
||||||
|
|
||||||
# pylint: disable=attribute-defined-outside-init
|
# pylint: disable=attribute-defined-outside-init
|
||||||
def on_connected(self) -> None:
|
def on_connected(self) -> None:
|
||||||
@@ -127,6 +147,22 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
|||||||
If we don't then subsequent triggers may reach the DDG too early, and will be ignored. To
|
If we don't then subsequent triggers may reach the DDG too early, and will be ignored. To
|
||||||
avoid this, we've added the option to specify a delay via add_delay, default here is 50ms.
|
avoid this, we've added the option to specify a delay via add_delay, default here is 50ms.
|
||||||
"""
|
"""
|
||||||
|
mcs = self.device_manager.devices.get('mcs', None)
|
||||||
|
if mcs is None:
|
||||||
|
logger.info(f"Did not find mcs card in current session")
|
||||||
|
else:
|
||||||
|
mcs :MCSCardCSAXS
|
||||||
|
status_ready_read = CompareStatus(mcs.ready_to_read, READYTOREAD.DONE)
|
||||||
|
mcs.stop_all.put(1)
|
||||||
|
status_acquiring = TransitionStatus(mcs.acquiring, [ACQUIRING.DONE, ACQUIRING.ACQUIRING])
|
||||||
|
self.cancel_on_stop(status_ready_read)
|
||||||
|
self.cancel_on_stop(status_acquiring)
|
||||||
|
status_ready_read.wait(2)
|
||||||
|
|
||||||
|
mcs.ready_to_read.put(READYTOREAD.PROCESSING)
|
||||||
|
mcs.erase_start.put(1)
|
||||||
|
status_acquiring.wait(timeout=2)# 2 s wait for mcs card to start should be more than enough..
|
||||||
|
|
||||||
st = StatusBase()
|
st = StatusBase()
|
||||||
self.cancel_on_stop(st)
|
self.cancel_on_stop(st)
|
||||||
self.trigger_shot.put(1, use_complete=True)
|
self.trigger_shot.put(1, use_complete=True)
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from __future__ import annotations
|
|||||||
import enum
|
import enum
|
||||||
|
|
||||||
from ophyd import Component as Cpt
|
from ophyd import Component as Cpt
|
||||||
from ophyd import DynamicDeviceComponent, EpicsSignal, EpicsSignalRO, Kind
|
from ophyd import DynamicDeviceComponent, EpicsSignal, EpicsSignalRO, Kind, Device
|
||||||
|
|
||||||
|
|
||||||
class CHANNELADVANCE(int, enum.Enum):
|
class CHANNELADVANCE(int, enum.Enum):
|
||||||
@@ -147,13 +147,13 @@ def _create_mca_channels(num_channels: int) -> dict[str, tuple]:
|
|||||||
for i in range(1, num_channels + 1):
|
for i in range(1, num_channels + 1):
|
||||||
mcs_channels[f"mca{i}"] = (
|
mcs_channels[f"mca{i}"] = (
|
||||||
EpicsSignalRO,
|
EpicsSignalRO,
|
||||||
f"MCA{i}",
|
f'mca{i}.VAL',
|
||||||
{"kind": Kind.omitted, "auto_monitor": True, "doc": f"MCA channel {i}."},
|
{"kind": Kind.omitted, "auto_monitor": True, "doc": f"MCA channel {i}."},
|
||||||
)
|
)
|
||||||
return mcs_channels
|
return mcs_channels
|
||||||
|
|
||||||
|
|
||||||
class MCSCard:
|
class MCSCard(Device):
|
||||||
"""
|
"""
|
||||||
Ophyd implementation for the interface to the SIS3801/SIS3820 multichannel scaler (MCS) cards via EPICS.
|
Ophyd implementation for the interface to the SIS3801/SIS3820 multichannel scaler (MCS) cards via EPICS.
|
||||||
|
|
||||||
@@ -209,13 +209,14 @@ class MCSCard:
|
|||||||
)
|
)
|
||||||
read_all = Cpt(
|
read_all = Cpt(
|
||||||
EpicsSignal,
|
EpicsSignal,
|
||||||
"ReadAll",
|
"DoReadAll.VAL",
|
||||||
kind=Kind.omitted,
|
kind=Kind.omitted,
|
||||||
doc="Forces a read of all mca or waveform records from the hardware. This record can be set to periodically process to update the records during acquisition. Note that even if this record has SCAN=Passive the mca or waveform records will always process once when acquisition completes.",
|
doc="Forces a read of all mca or waveform records from the hardware. This record can be set to periodically process to update the records during acquisition. Note that even if this record has SCAN=Passive the mca or waveform records will always process once when acquisition completes.",
|
||||||
)
|
)
|
||||||
|
read_mode = Cpt(EpicsSignal, "ReadAll.SCAN", kind=Kind.omitted, doc="Readout mode for transferring data from FIFO buffer to mca EPICS scalars.")
|
||||||
num_use_all = Cpt(
|
num_use_all = Cpt(
|
||||||
EpicsSignal,
|
EpicsSignal,
|
||||||
"NUseAll",
|
"NuseAll",
|
||||||
kind=Kind.omitted,
|
kind=Kind.omitted,
|
||||||
doc="The number of channels to use for the mca or waveform records. Acquisition will automatically stop when the number of channel advances reaches this value.",
|
doc="The number of channels to use for the mca or waveform records. Acquisition will automatically stop when the number of channel advances reaches this value.",
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
"""Module for the MCSCard CSAXS implementation."""
|
"""Module for the MCSCard CSAXS implementation."""
|
||||||
|
from __future__ import annotations
|
||||||
from ophyd import Component as Cpt
|
from typing import TYPE_CHECKING
|
||||||
from ophyd_devices import CompareStatus, ProgressSignal
|
from ophyd import Component as Cpt, Signal, Kind, EpicsSignalRO
|
||||||
|
from ophyd_devices import CompareStatus, ProgressSignal, TransitionStatus
|
||||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||||
|
|
||||||
|
from threading import RLock
|
||||||
|
import enum
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
|
||||||
from csaxs_bec.devices.epics.mcs_card.mcs_card import (
|
from csaxs_bec.devices.epics.mcs_card.mcs_card import (
|
||||||
ACQUIREMODE,
|
ACQUIREMODE,
|
||||||
ACQUIRING,
|
ACQUIRING,
|
||||||
@@ -16,6 +21,15 @@ from csaxs_bec.devices.epics.mcs_card.mcs_card import (
|
|||||||
MCSCard,
|
MCSCard,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
from bec_lib.devicemanager import DeviceManagerBase, ScanInfo
|
||||||
|
|
||||||
|
logger =bec_logger.logger
|
||||||
|
|
||||||
|
class READYTOREAD(int, enum.Enum):
|
||||||
|
|
||||||
|
PROCESSING = 0
|
||||||
|
DONE = 1
|
||||||
|
|
||||||
class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
||||||
"""
|
"""
|
||||||
@@ -23,19 +37,40 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
|||||||
The basic functionality is inherited from the MCSCard class.
|
The basic functionality is inherited from the MCSCard class.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
progress: ProgressSignal = Cpt(ProgressSignal, "progress")
|
ready_to_read = Cpt(Signal, name='ready_to_read', kind=Kind.omitted, doc='Signal that indicates if mcs card is ready to be read from after triggers. 0 not ready, 1 ready')
|
||||||
|
progress: ProgressSignal = Cpt(ProgressSignal, name="progress")
|
||||||
|
count_time = Cpt(Signal, name="count_time", kind=Kind.normal)
|
||||||
|
bpm_1 = Cpt(Signal, name='bpm_1', kind=Kind.normal)
|
||||||
|
bpm_2 = Cpt(Signal, name='bpm_2', kind=Kind.normal)
|
||||||
|
bpm_3 = Cpt(Signal, name='bpm_3', kind=Kind.normal)
|
||||||
|
bpm_4 = Cpt(Signal, name='bpm_4', kind=Kind.normal)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, name: str,
|
||||||
|
prefix: str = "",
|
||||||
|
scan_info: ScanInfo | None = None,
|
||||||
|
device_manager: DeviceManagerBase | None = None,
|
||||||
|
**kwargs):
|
||||||
"""
|
"""
|
||||||
Initialize the MCSCardCSAXS with the given arguments and keyword arguments.
|
Initialize the MCSCardCSAXS with the given arguments and keyword arguments.
|
||||||
"""
|
"""
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(name=name, prefix=prefix, scan_info=scan_info,device_manager=device_manager, **kwargs)
|
||||||
self._pv_timeout = 2
|
self._pv_timeout = 2
|
||||||
|
self._rlock = RLock()
|
||||||
|
self.counter_mapping = {f"{self.counters.name}_mca1" : 'bpm_1',
|
||||||
|
f"{self.counters.name}_mca2" : 'bpm_2',
|
||||||
|
f"{self.counters.name}_mca3" : 'bpm_3',
|
||||||
|
f"{self.counters.name}_mca4" : 'bpm_4',
|
||||||
|
f"{self.counters.name}_mca5" : 'count_time'
|
||||||
|
}
|
||||||
|
self.counter_updated = []
|
||||||
|
|
||||||
def on_connected(self):
|
def on_connected(self):
|
||||||
"""
|
"""
|
||||||
Called when the device is connected.
|
Called when the device is connected.
|
||||||
"""
|
"""
|
||||||
|
# Make sure card is not running
|
||||||
|
self.stop_all.put(1)
|
||||||
|
|
||||||
self.channel_advance.set(CHANNELADVANCE.EXTERNAL).wait(timeout=self._pv_timeout)
|
self.channel_advance.set(CHANNELADVANCE.EXTERNAL).wait(timeout=self._pv_timeout)
|
||||||
self.channel_advance.set(CHANNEL1SOURCE.EXTERNAL).wait(
|
self.channel_advance.set(CHANNEL1SOURCE.EXTERNAL).wait(
|
||||||
timeout=self._pv_timeout
|
timeout=self._pv_timeout
|
||||||
@@ -53,17 +88,40 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
|||||||
) # To be checked and tested!
|
) # To be checked and tested!
|
||||||
self.count_on_start.set(0).wait(timeout=self._pv_timeout)
|
self.count_on_start.set(0).wait(timeout=self._pv_timeout)
|
||||||
|
|
||||||
|
# Set appropriate read mode
|
||||||
|
self.read_mode.set(READMODE.PASSIVE).wait(timeout=self._pv_timeout)
|
||||||
|
|
||||||
# Subscribe the progress signal
|
# Subscribe the progress signal
|
||||||
self.current_channel.subscribe(self._progress_update, run=False)
|
self.current_channel.subscribe(self._progress_update, run=False)
|
||||||
|
|
||||||
# Set appropriate read mode
|
# Subscribe to the mca updates
|
||||||
self.read_mode.set(READMODE.PASSIVE).wait(timeout=self._pv_timeout)
|
for name in self.counters.component_names:
|
||||||
|
sig:EpicsSignalRO = getattr(self.counters, name)
|
||||||
|
sig.subscribe(self._on_counter_update, run=False)
|
||||||
|
|
||||||
|
def _on_counter_update(self, value, **kwargs) -> None:
|
||||||
|
with self._rlock:
|
||||||
|
signal = kwargs.get("obj", None)
|
||||||
|
if signal is None:
|
||||||
|
logger.info(f"Called without 'obj' in kwargs: {kwargs}")
|
||||||
|
return
|
||||||
|
mapped_signal_name = self.counter_mapping.get(signal.name, None)
|
||||||
|
if mapped_signal_name is None:
|
||||||
|
logger.info(f"Received update from unmapped signal {signal.name}")
|
||||||
|
return
|
||||||
|
sig = getattr(self, mapped_signal_name)
|
||||||
|
sig.put(value)
|
||||||
|
self.counter_updated.append(signal.name)
|
||||||
|
received_all_updates = (set(self.counter_updated) == set(self.counter_mapping.keys()))
|
||||||
|
logger.info(f"Value for received_all_updates {received_all_updates}")
|
||||||
|
if received_all_updates:
|
||||||
|
self.ready_to_read.put(1) # Reset happens from DDG class!
|
||||||
|
self.counter_updated.clear()
|
||||||
|
|
||||||
def _progress_update(self, value, **kwargs) -> None:
|
def _progress_update(self, value, **kwargs) -> None:
|
||||||
"""Callback for progress updates from ophyd subscription on current_channel."""
|
"""Callback for progress updates from ophyd subscription on current_channel."""
|
||||||
frames_per_trigger = self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
frames_per_trigger = self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
||||||
channel_advance = frames_per_trigger + 1
|
self.progress.put(value=value, max_value=frames_per_trigger, done=bool(value == frames_per_trigger))
|
||||||
self.progress.put(value, max_value=channel_advance, done=bool(value == channel_advance))
|
|
||||||
|
|
||||||
def on_stage(self) -> None:
|
def on_stage(self) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -78,13 +136,22 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
|||||||
"""
|
"""
|
||||||
Called when the device is unstaged.
|
Called when the device is unstaged.
|
||||||
"""
|
"""
|
||||||
|
self.stop_all.put(1)
|
||||||
|
self.ready_to_read.put(READYTOREAD.DONE)
|
||||||
self.erase_all.set(0).wait(timeout=self._pv_timeout)
|
self.erase_all.set(0).wait(timeout=self._pv_timeout)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def on_trigger(self) -> None:
|
||||||
|
status = TransitionStatus(self.ready_to_read, stric=True, transitions=[READYTOREAD.PROCESSING, READYTOREAD.DONE])
|
||||||
|
self.cancel_on_stop(status)
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
def on_pre_scan(self) -> None:
|
def on_pre_scan(self) -> None:
|
||||||
"""
|
"""
|
||||||
Called before the scan starts.
|
Called before the scan starts.
|
||||||
"""
|
"""
|
||||||
self.erase_start.put(1)
|
|
||||||
|
|
||||||
def on_complete(self) -> CompareStatus:
|
def on_complete(self) -> CompareStatus:
|
||||||
"""On scan completion."""
|
"""On scan completion."""
|
||||||
@@ -98,5 +165,6 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
|||||||
Called when the scan is stopped.
|
Called when the scan is stopped.
|
||||||
"""
|
"""
|
||||||
self.stop_all.put(1)
|
self.stop_all.put(1)
|
||||||
|
self.ready_to_read.put(READYTOREAD.DONE)
|
||||||
# Reset the progress signal
|
# Reset the progress signal
|
||||||
# self.progress.put(0, done=True)
|
# self.progress.put(0, done=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user