mirror of
https://github.com/bec-project/ophyd_devices.git
synced 2026-02-20 17:28:42 +01:00
fix(pandabox): simplify enum states
This commit is contained in:
@@ -32,7 +32,7 @@ import pandablocks.commands as pbc
|
|||||||
from bec_lib import bec_logger
|
from bec_lib import bec_logger
|
||||||
from ophyd.status import WaitTimeoutError
|
from ophyd.status import WaitTimeoutError
|
||||||
from pandablocks.blocking import BlockingClient
|
from pandablocks.blocking import BlockingClient
|
||||||
from pandablocks.responses import EndData, FrameData, ReadyData, StartData
|
from pandablocks.responses import Data, EndData, FrameData, ReadyData, StartData
|
||||||
|
|
||||||
from ophyd_devices import PSIDeviceBase, StatusBase
|
from ophyd_devices import PSIDeviceBase, StatusBase
|
||||||
|
|
||||||
@@ -108,12 +108,11 @@ def load_layout_from_file_to_panda(host: str, file_path: str) -> None:
|
|||||||
########################
|
########################
|
||||||
|
|
||||||
|
|
||||||
class PandaDataEvents(StrEnum):
|
class PandaState(StrEnum):
|
||||||
"""
|
"""
|
||||||
Events from the PandaBox data stream. The events READY, START, FRAME, END correspond to
|
States from the PandaBox data stream. The state READY, START, FRAME, END correspond to
|
||||||
actual data frames received from the PandaBox. The DISARMED event is used to indicate
|
actual data frames received from the PandaBox. DISARMED indicates that the PandaBox
|
||||||
that the PandaBox has been disarmed, either after a complete data acquisition or after
|
has been disarmed and is no longer acquiring data.
|
||||||
an interrupted acquisition.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
READY = "ready"
|
READY = "ready"
|
||||||
@@ -122,14 +121,19 @@ class PandaDataEvents(StrEnum):
|
|||||||
END = "end"
|
END = "end"
|
||||||
DISARMED = "disarmed"
|
DISARMED = "disarmed"
|
||||||
|
|
||||||
|
def describe(self) -> str:
|
||||||
|
"""Return a human-readable description of the event."""
|
||||||
|
descriptions = {
|
||||||
|
PandaState.READY: "PandaBox is ready for data acquisition.",
|
||||||
|
PandaState.START: "PandaBox has started data acquisition.",
|
||||||
|
PandaState.FRAME: "PandaBox has sent a frame of data.",
|
||||||
|
PandaState.END: "PandaBox has ended data acquisition.",
|
||||||
|
PandaState.DISARMED: "PandaBox is disarmed and not acquiring data. This event is not triggered by a data frame from the PandaBox.",
|
||||||
|
}
|
||||||
|
return descriptions.get(self, "Unknown PandaBox data event.")
|
||||||
|
|
||||||
LITERAL_PANDA_DATA_EVENTS: TypeAlias = Union[
|
|
||||||
PandaDataEvents.READY.value,
|
|
||||||
PandaDataEvents.START.value,
|
|
||||||
PandaDataEvents.FRAME.value,
|
|
||||||
PandaDataEvents.END.value,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
LITERAL_PANDA_COMMANDS: TypeAlias = Union[
|
LITERAL_PANDA_COMMANDS: TypeAlias = Union[
|
||||||
pbc.Raw,
|
pbc.Raw,
|
||||||
pbc.Arm,
|
pbc.Arm,
|
||||||
@@ -139,7 +143,6 @@ LITERAL_PANDA_COMMANDS: TypeAlias = Union[
|
|||||||
pbc.GetFieldInfo,
|
pbc.GetFieldInfo,
|
||||||
pbc.GetPcapBitsLabels,
|
pbc.GetPcapBitsLabels,
|
||||||
]
|
]
|
||||||
|
|
||||||
LITERAL_PANDA_DATA: TypeAlias = Union[ReadyData, StartData, FrameData, EndData]
|
LITERAL_PANDA_DATA: TypeAlias = Union[ReadyData, StartData, FrameData, EndData]
|
||||||
|
|
||||||
|
|
||||||
@@ -151,12 +154,7 @@ class PandaBox(PSIDeviceBase):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
USER_ACCESS = [
|
USER_ACCESS = ["send_raw", "add_status_callback", "remove_status_callback", "get_panda_state"]
|
||||||
"send_raw",
|
|
||||||
"add_status_callback",
|
|
||||||
"remove_status_callback",
|
|
||||||
"get_panda_data_state",
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -172,7 +170,7 @@ class PandaBox(PSIDeviceBase):
|
|||||||
|
|
||||||
# Lock
|
# Lock
|
||||||
self._lock = threading.RLock()
|
self._lock = threading.RLock()
|
||||||
self._panda_data_state: PandaDataEvents | str = PandaDataEvents.DISARMED.value
|
self._panda_state: PandaState | str = PandaState.DISARMED.value
|
||||||
|
|
||||||
# Status callback management
|
# Status callback management
|
||||||
self._status_callbacks: dict[uuid.UUID, dict[str, Any]] = {}
|
self._status_callbacks: dict[uuid.UUID, dict[str, Any]] = {}
|
||||||
@@ -215,25 +213,25 @@ class PandaBox(PSIDeviceBase):
|
|||||||
def add_status_callback(
|
def add_status_callback(
|
||||||
self,
|
self,
|
||||||
status: StatusBase,
|
status: StatusBase,
|
||||||
success: list[PandaDataEvents],
|
success: list[PandaState],
|
||||||
failure: list[PandaDataEvents],
|
failure: list[PandaState],
|
||||||
check_directly: bool = True,
|
check_directly: bool = True,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
This methods registers a status callback to the data receiving loop that will resolve
|
This methods registers a status callback to the data receiving loop that will resolve
|
||||||
if the PandaBox receives specific data events. It is used to allow asynchronous resolution
|
if the PandaBox receives specific data events. It is used to allow asynchronous resolution
|
||||||
of status objects based on PandaBox events. Per default, the callback checks the current
|
of status objects based on PandaBox events. Per default, the callback checks the current
|
||||||
panda_data_state directly to see if the status can be resolved immediately. This is useful
|
panda_state directly to see if the status can be resolved immediately. This is useful
|
||||||
when the status is created after the PandaBox has already sent some data events. However, this
|
when the status is created after the PandaBox has already sent some data events. However, this
|
||||||
can also be disabled by setting check_directly to False.
|
can also be disabled by setting check_directly to False.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
status (StatusBase): The status object to register the callback for.
|
status (StatusBase): The status object to register the callback for.
|
||||||
success (list[PandaDataEvents]): The list of PandaBox data events that will resolve
|
success (list[PandaState]): The list of PandaBox data events that will resolve
|
||||||
the status as successful.
|
the status as successful.
|
||||||
failure (list[PandaDataEvents]): The list of PandaBox data events that will resolve
|
failure (list[PandaState]): The list of PandaBox data events that will resolve
|
||||||
the status as failed.
|
the status as failed.
|
||||||
check_directly (bool): Whether to check the current panda_data_state directly
|
check_directly (bool): Whether to check the current panda_state directly
|
||||||
to resolve the status immediately. Defaults to True.
|
to resolve the status immediately. Defaults to True.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -242,7 +240,7 @@ class PandaBox(PSIDeviceBase):
|
|||||||
"""
|
"""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
if check_directly:
|
if check_directly:
|
||||||
current_state = self.panda_data_state
|
current_state = self.panda_state
|
||||||
if current_state in success and not status.done:
|
if current_state in success and not status.done:
|
||||||
status.set_finished()
|
status.set_finished()
|
||||||
return ""
|
return ""
|
||||||
@@ -275,7 +273,7 @@ class PandaBox(PSIDeviceBase):
|
|||||||
def add_data_callback(
|
def add_data_callback(
|
||||||
self,
|
self,
|
||||||
callback: Callable[[LITERAL_PANDA_DATA], None],
|
callback: Callable[[LITERAL_PANDA_DATA], None],
|
||||||
data_type: LITERAL_PANDA_DATA_EVENTS = PandaDataEvents.FRAME.value,
|
data_type: PandaState = PandaState.FRAME.value,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Register a data callback to be called whenever new data is received from the PandaBox.
|
Register a data callback to be called whenever new data is received from the PandaBox.
|
||||||
@@ -304,28 +302,28 @@ class PandaBox(PSIDeviceBase):
|
|||||||
with self._lock:
|
with self._lock:
|
||||||
self._data_callbacks.pop(cb_id, None)
|
self._data_callbacks.pop(cb_id, None)
|
||||||
|
|
||||||
def get_panda_data_state(self) -> str:
|
def get_panda_state(self) -> str:
|
||||||
"""Get current panda data state."""
|
"""Get current panda data state."""
|
||||||
return self.panda_data_state
|
return self.panda_state
|
||||||
|
|
||||||
#########################
|
#########################
|
||||||
### State management ###
|
### State management ###
|
||||||
#########################
|
#########################
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def panda_data_state(self) -> str:
|
def panda_state(self) -> str:
|
||||||
"""Get the current state of the data acquisition on the PandaBox."""
|
"""Get the current state of the data acquisition on the PandaBox."""
|
||||||
return (
|
return (
|
||||||
self._panda_data_state.value
|
self._panda_state.value
|
||||||
if isinstance(self._panda_data_state, PandaDataEvents)
|
if isinstance(self._panda_state, PandaState)
|
||||||
else self._panda_data_state
|
else self._panda_state
|
||||||
)
|
)
|
||||||
|
|
||||||
@panda_data_state.setter
|
@panda_state.setter
|
||||||
def panda_data_state(self, value: PandaDataEvents | str) -> None:
|
def panda_state(self, value: PandaState | str) -> None:
|
||||||
"""Set the current state of the data acquisition on the PandaBox."""
|
"""Set the current state of the data acquisition on the PandaBox."""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
self._panda_data_state = value
|
self._panda_state = value
|
||||||
|
|
||||||
################################
|
################################
|
||||||
### Data readout management ###
|
### Data readout management ###
|
||||||
@@ -353,7 +351,7 @@ class PandaBox(PSIDeviceBase):
|
|||||||
- FrameData: Contains a frame of data acquired from the PandaBox.
|
- FrameData: Contains a frame of data acquired from the PandaBox.
|
||||||
- EndData: Indicates the end of a data acquisition.
|
- EndData: Indicates the end of a data acquisition.
|
||||||
|
|
||||||
Upon receiving each type of data message, the panda_data_state is updated accordingly,
|
Upon receiving each type of data message, the panda_state is updated accordingly,
|
||||||
and any registered callbacks for that event are executed. This allows to handle callbacks
|
and any registered callbacks for that event are executed. This allows to handle callbacks
|
||||||
for each stage of the data acquisition process. For example, a child class could add a
|
for each stage of the data acquisition process. For example, a child class could add a
|
||||||
status callback to resolve during a specific stage of the data acquisition based on an
|
status callback to resolve during a specific stage of the data acquisition based on an
|
||||||
@@ -370,20 +368,20 @@ class PandaBox(PSIDeviceBase):
|
|||||||
try:
|
try:
|
||||||
for data in client.data(scaled=False):
|
for data in client.data(scaled=False):
|
||||||
if isinstance(data, ReadyData):
|
if isinstance(data, ReadyData):
|
||||||
self._run_status_callbacks(PandaDataEvents.READY)
|
self._run_status_callbacks(PandaState.READY)
|
||||||
self._run_data_callbacks(data, PandaDataEvents.READY.value)
|
self._run_data_callbacks(data, PandaState.READY.value)
|
||||||
|
|
||||||
elif isinstance(data, StartData):
|
elif isinstance(data, StartData):
|
||||||
self._run_status_callbacks(PandaDataEvents.START)
|
self._run_status_callbacks(PandaState.START)
|
||||||
self._run_data_callbacks(data, PandaDataEvents.START.value)
|
self._run_data_callbacks(data, PandaState.START.value)
|
||||||
|
|
||||||
elif isinstance(data, FrameData):
|
elif isinstance(data, FrameData):
|
||||||
self._run_status_callbacks(PandaDataEvents.FRAME)
|
self._run_status_callbacks(PandaState.FRAME)
|
||||||
self._run_data_callbacks(data, PandaDataEvents.FRAME.value)
|
self._run_data_callbacks(data, PandaState.FRAME.value)
|
||||||
|
|
||||||
elif isinstance(data, EndData):
|
elif isinstance(data, EndData):
|
||||||
self._run_status_callbacks(PandaDataEvents.END)
|
self._run_status_callbacks(PandaState.END)
|
||||||
self._run_data_callbacks(data, PandaDataEvents.END.value)
|
self._run_data_callbacks(data, PandaState.END.value)
|
||||||
break # Exit data readout loop
|
break # Exit data readout loop
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
@@ -396,11 +394,19 @@ class PandaBox(PSIDeviceBase):
|
|||||||
# expected safe state of the data receiving loop from the PandaBox and was added
|
# expected safe state of the data receiving loop from the PandaBox and was added
|
||||||
# in addition to the existing READY, START, FRAME, END events created from the existing
|
# in addition to the existing READY, START, FRAME, END events created from the existing
|
||||||
# PandaBox data messages.
|
# PandaBox data messages.
|
||||||
client.send(self._disarm()) # Ensure we disarm at the end
|
|
||||||
self.data_thread_run_event.clear()
|
|
||||||
self._run_status_callbacks(PandaDataEvents.DISARMED)
|
|
||||||
|
|
||||||
def _run_status_callbacks(self, event: PandaDataEvents) -> None:
|
client.send(self._disarm()) # Ensure we disarm at the end
|
||||||
|
|
||||||
|
self.data_thread_run_event.clear() # Stop data readout loop
|
||||||
|
|
||||||
|
self._run_status_callbacks(PandaState.DISARMED) # Run DISARMED status callbacks
|
||||||
|
|
||||||
|
# As DISARMED is not triggered by a data message, we manually run data callbacks for it here
|
||||||
|
# and run it with an empty Data() object following the base class for data message responses
|
||||||
|
# of the pandablocks library.
|
||||||
|
self._run_data_callbacks(Data(), PandaState.DISARMED.value)
|
||||||
|
|
||||||
|
def _run_status_callbacks(self, event: PandaState) -> None:
|
||||||
"""
|
"""
|
||||||
Run registered status callbacks for a given PandaBox data event.
|
Run registered status callbacks for a given PandaBox data event.
|
||||||
These callbacks are used to resolve status objects that are registered
|
These callbacks are used to resolve status objects that are registered
|
||||||
@@ -411,16 +417,16 @@ class PandaBox(PSIDeviceBase):
|
|||||||
NOTE : Status callbacks are removed once they are resolved (either success or failure).
|
NOTE : Status callbacks are removed once they are resolved (either success or failure).
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
event (PandaDataEvents): The PandaBox data event that occurred.
|
event (PandaState): The PandaBox data event that occurred.
|
||||||
data (LITERAL_PANDA_DATA): The data associated with the event.
|
data (LITERAL_PANDA_DATA): The data associated with the event.
|
||||||
"""
|
"""
|
||||||
self.panda_data_state = event
|
self.panda_state = event
|
||||||
with self._lock:
|
with self._lock:
|
||||||
callbacks_to_remove = []
|
callbacks_to_remove = []
|
||||||
for cb_id, cb_info in self._status_callbacks.items():
|
for cb_id, cb_info in self._status_callbacks.items():
|
||||||
status: StatusBase = cb_info["status"]
|
status: StatusBase = cb_info["status"]
|
||||||
success_events: list[PandaDataEvents] = cb_info["success"]
|
success_events: list[PandaState] = cb_info["success"]
|
||||||
failure_events: list[PandaDataEvents] = cb_info["failure"]
|
failure_events: list[PandaState] = cb_info["failure"]
|
||||||
|
|
||||||
if event in success_events and not status.done:
|
if event in success_events and not status.done:
|
||||||
status.set_finished()
|
status.set_finished()
|
||||||
@@ -436,9 +442,7 @@ class PandaBox(PSIDeviceBase):
|
|||||||
for cb_id in callbacks_to_remove:
|
for cb_id in callbacks_to_remove:
|
||||||
self._status_callbacks.pop(cb_id, None)
|
self._status_callbacks.pop(cb_id, None)
|
||||||
|
|
||||||
def _run_data_callbacks(
|
def _run_data_callbacks(self, data: LITERAL_PANDA_DATA, event_type: PandaState) -> None:
|
||||||
self, data: LITERAL_PANDA_DATA, event_type: LITERAL_PANDA_DATA_EVENTS
|
|
||||||
) -> None:
|
|
||||||
"""
|
"""
|
||||||
Placeholder method to run data callbacks for received PandaBox data.
|
Placeholder method to run data callbacks for received PandaBox data.
|
||||||
Child classes can override this method to implement custom behavior
|
Child classes can override this method to implement custom behavior
|
||||||
@@ -448,13 +452,13 @@ class PandaBox(PSIDeviceBase):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
data (LITERAL_PANDA_DATA): The data received from the PandaBox.
|
data (LITERAL_PANDA_DATA): The data received from the PandaBox.
|
||||||
event_type (LITERAL_PANDA_DATA_EVENTS): The type of data received. This can be
|
event_type (PandaState): The type of data received. This can be
|
||||||
"ready", "start", "frame", or "end".
|
"ready", "start", "frame", or "end".
|
||||||
"""
|
"""
|
||||||
with self._lock:
|
with self._lock:
|
||||||
for cb_info in self._data_callbacks.values():
|
for cb_info in self._data_callbacks.values():
|
||||||
callback: Callable[[LITERAL_PANDA_DATA], None] = cb_info["callback"]
|
callback: Callable[[LITERAL_PANDA_DATA], None] = cb_info["callback"]
|
||||||
cb_data_type: LITERAL_PANDA_DATA_EVENTS = cb_info["data_type"]
|
cb_data_type: PandaState = cb_info["data_type"]
|
||||||
if cb_data_type == event_type:
|
if cb_data_type == event_type:
|
||||||
callback(data)
|
callback(data)
|
||||||
|
|
||||||
@@ -518,7 +522,7 @@ class PandaBox(PSIDeviceBase):
|
|||||||
"""
|
"""
|
||||||
# First make sure that the data readout loop is not running
|
# First make sure that the data readout loop is not running
|
||||||
status = StatusBase(obj=self)
|
status = StatusBase(obj=self)
|
||||||
self.add_status_callback(status=status, success=[PandaDataEvents.DISARMED], failure=[])
|
self.add_status_callback(status=status, success=[PandaState.DISARMED], failure=[])
|
||||||
try:
|
try:
|
||||||
status.wait(timeout=3)
|
status.wait(timeout=3)
|
||||||
except WaitTimeoutError:
|
except WaitTimeoutError:
|
||||||
@@ -542,8 +546,8 @@ class PandaBox(PSIDeviceBase):
|
|||||||
status_ready_data_received = StatusBase(obj=self)
|
status_ready_data_received = StatusBase(obj=self)
|
||||||
self.add_status_callback(
|
self.add_status_callback(
|
||||||
status=status_ready_data_received,
|
status=status_ready_data_received,
|
||||||
success=[PandaDataEvents.READY],
|
success=[PandaState.READY],
|
||||||
failure=[PandaDataEvents.FRAME, PandaDataEvents.END],
|
failure=[PandaState.FRAME, PandaState.END],
|
||||||
)
|
)
|
||||||
status_ready_data_received.add_callback(self._pre_scan_status_callback)
|
status_ready_data_received.add_callback(self._pre_scan_status_callback)
|
||||||
if status:
|
if status:
|
||||||
|
|||||||
Reference in New Issue
Block a user