local_contact_panel.py: added config for autoamtion, currently just the sleep between mount and wait.
This commit is contained in:
@@ -41,6 +41,7 @@ from aare.common.beamline import MXBeamline, cfg_get
|
||||
from aare.common.logger_config import setup_logger
|
||||
|
||||
from aare.common.exception_handler import BeamlineBusyException
|
||||
from aare.daq.config_model import LocalContactConfigModel
|
||||
|
||||
#TODO WHAT SHOULD THIS BE? This should be in the YAMl file it is beamline specific
|
||||
ABR_POS_MOUNT = AerotechCoordinate(
|
||||
@@ -1263,6 +1264,44 @@ class BeamlineConfig:
|
||||
"tell_hint": tell_hint,
|
||||
}
|
||||
|
||||
def get_local_contact_config(self) -> LocalContactConfigModel:
|
||||
default = LocalContactConfigModel()
|
||||
|
||||
try:
|
||||
redis_key = f"{self.__bl}:local_contact_config"
|
||||
raw_value = self.__client.get(redis_key)
|
||||
|
||||
if raw_value in (None, "", b""):
|
||||
return default
|
||||
|
||||
if isinstance(raw_value, bytes):
|
||||
raw_value = raw_value.decode("utf-8")
|
||||
|
||||
return LocalContactConfigModel.model_validate_json(str(raw_value))
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to read Local Contact config from Redis: {e}")
|
||||
return default
|
||||
|
||||
def set_local_contact_config(self, config: LocalContactConfigModel | dict) -> LocalContactConfigModel:
|
||||
validated = LocalContactConfigModel.model_validate(config)
|
||||
try:
|
||||
redis_key = f"{self.__bl}:local_contact_config"
|
||||
self.__client.set(redis_key, validated.model_dump_json())
|
||||
logger.info(f"Saved Local Contact config to Redis: {redis_key}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to write Local Contact config to Redis: {e}")
|
||||
raise
|
||||
|
||||
return validated
|
||||
|
||||
@property
|
||||
def local_contact_config(self) -> LocalContactConfigModel:
|
||||
return self.get_local_contact_config()
|
||||
|
||||
@local_contact_config.setter
|
||||
def local_contact_config(self, value: LocalContactConfigModel | dict) -> None:
|
||||
self.set_local_contact_config(value)
|
||||
|
||||
if __name__ == "__main__":
|
||||
from aare.common.beamline import mx_beamline
|
||||
cfg = BeamlineConfig(bl=mx_beamline())
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
class LocalContactConfigModel(BaseModel):
|
||||
mount_to_center_sleep_s: float = Field(
|
||||
default=0.0,
|
||||
ge=0.0,
|
||||
le=120.0,
|
||||
description="Delay after mount and before loop centering, in seconds.",
|
||||
)
|
||||
+17
-1
@@ -69,7 +69,7 @@ from aare.common.exception_handler import (
|
||||
BeamlineBusyException,
|
||||
AutoRasterSampleSkipped
|
||||
)
|
||||
|
||||
from aare.daq.config_model import LocalContactConfigModel
|
||||
from aare.daq.operations.face_detection import FaceDetectionContext, FaceDetectionService, FaceDetectionResult
|
||||
from aare.daq.operations.face_detection.models import (
|
||||
FaceDetectionDependencies,
|
||||
@@ -2749,6 +2749,16 @@ class AareDAQ:
|
||||
logger.info(f"mounting done at {time.perf_counter() - start}")
|
||||
|
||||
self._mark_progress_running(progress, WorkflowStateKind.LOOP_CENTRE, "Centering sample")
|
||||
|
||||
local_contact_config = self.get_local_contact_config()
|
||||
mount_to_center_sleep_s = float(local_contact_config.mount_to_center_sleep_s)
|
||||
|
||||
if mount_to_center_sleep_s > 0:
|
||||
logger.info(
|
||||
f"Sleeping {mount_to_center_sleep_s:.2f}s between mount and loop centering"
|
||||
)
|
||||
time.sleep(mount_to_center_sleep_s)
|
||||
|
||||
centered = False
|
||||
try:
|
||||
centered = self._execute_loop_centering(sample)
|
||||
@@ -3541,3 +3551,9 @@ class AareDAQ:
|
||||
self.__set_state(BeamlineStateEnum.SampleAlignment)
|
||||
self.__cfg.state_busy = False
|
||||
raise
|
||||
|
||||
def get_local_contact_config(self) -> LocalContactConfigModel:
|
||||
return self.__cfg.get_local_contact_config()
|
||||
|
||||
def set_local_contact_config(self, config: LocalContactConfigModel) -> LocalContactConfigModel:
|
||||
return self.__cfg.set_local_contact_config(config)
|
||||
|
||||
@@ -19,7 +19,7 @@ from PySide6.QtWidgets import (
|
||||
QTabWidget,
|
||||
QTextEdit,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
QWidget, QDoubleSpinBox,
|
||||
)
|
||||
|
||||
from aare.common.logger_config import setup_logger
|
||||
@@ -29,6 +29,7 @@ from aare.gui.threads.daq_worker import DAQWorker
|
||||
from aare.gui.widgets.local_contact_status_widget import LocalContactStatusWidget
|
||||
from aare.gui.widgets.text_list_dialog import TextListDialog
|
||||
from aare.gui.widgets.title_label import TitleLabel
|
||||
from aare.gui.widgets.number_line_edit import NumberLineEdit
|
||||
|
||||
logger = setup_logger("aareGUI")
|
||||
|
||||
@@ -40,6 +41,7 @@ class LocalContactPanel(QFrame):
|
||||
TAB_BEC = "BEC"
|
||||
TAB_HARDWARE = "Hardware"
|
||||
TAB_DETECTOR = "Detector"
|
||||
TAB_CONFIG = "Config"
|
||||
|
||||
DEVICE_TITLES = {
|
||||
"bec": "BEC",
|
||||
@@ -60,6 +62,8 @@ class LocalContactPanel(QFrame):
|
||||
self._status_widgets: list[LocalContactStatusWidget] = []
|
||||
self._bec_macros_dialog: TextListDialog | None = None
|
||||
self._bec_devices_dialog: TextListDialog | None = None
|
||||
self._local_contact_config_payload: dict = {}
|
||||
self._mount_to_center_sleep_s = QDoubleSpinBox(self)
|
||||
|
||||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
self.setFrameShadow(QFrame.Shadow.Raised)
|
||||
@@ -130,10 +134,13 @@ class LocalContactPanel(QFrame):
|
||||
self._tabs.addTab(self._build_bec_tab(), self.TAB_BEC)
|
||||
self._tabs.addTab(self._build_hardware_tab(), self.TAB_HARDWARE)
|
||||
self._tabs.addTab(self._build_detector_tab(), self.TAB_DETECTOR)
|
||||
self._tabs.addTab(self._build_config_tab(), self.TAB_CONFIG)
|
||||
|
||||
self._daq.local_contact_simulation_state_loaded.connect(self._apply_simulation_state)
|
||||
self._daq.local_contact_device_state_loaded.connect(self._apply_device_state)
|
||||
self._daq.local_contact_links_loaded.connect(self._apply_links)
|
||||
self._daq.local_contact_config_loaded.connect(self._apply_local_contact_config)
|
||||
self._daq.local_contact_config_saved.connect(self._apply_local_contact_config)
|
||||
self._daq.local_contact_transfer_error.connect(self._show_local_contact_transfer_error)
|
||||
self._daq.bec_user_macros_loaded.connect(self._show_bec_user_macros)
|
||||
self._daq.bec_devices_loaded.connect(self._show_bec_devices)
|
||||
@@ -452,6 +459,58 @@ class LocalContactPanel(QFrame):
|
||||
layout.addStretch(1)
|
||||
return tab
|
||||
|
||||
def _build_config_tab(self) -> QWidget:
|
||||
tab = QWidget(self)
|
||||
layout = QVBoxLayout(tab)
|
||||
layout.setContentsMargins(6, 6, 6, 6)
|
||||
layout.setSpacing(8)
|
||||
|
||||
description = QLabel(
|
||||
"Staff-only tuning values used by automation and recovery flows.",
|
||||
tab,
|
||||
)
|
||||
description.setWordWrap(True)
|
||||
layout.addWidget(description)
|
||||
|
||||
form_box = QGroupBox("Automation timing", tab)
|
||||
form_layout = QGridLayout(form_box)
|
||||
form_layout.setContentsMargins(10, 12, 10, 10)
|
||||
form_layout.setHorizontalSpacing(8)
|
||||
form_layout.setVerticalSpacing(8)
|
||||
|
||||
label = QLabel("Mount → center delay (s)", form_box)
|
||||
self._mount_to_center_sleep_s = QDoubleSpinBox(form_box)
|
||||
self._mount_to_center_sleep_s.setRange(0.0, 120.0)
|
||||
self._mount_to_center_sleep_s.setDecimals(1)
|
||||
self._mount_to_center_sleep_s.setSingleStep(0.5)
|
||||
self._mount_to_center_sleep_s.setSuffix(" s")
|
||||
self._mount_to_center_sleep_s.setToolTip(
|
||||
"Delay after sample mounting and before loop centering starts."
|
||||
)
|
||||
|
||||
form_layout.addWidget(label, 0, 0)
|
||||
form_layout.addWidget(self._mount_to_center_sleep_s, 0, 1)
|
||||
|
||||
button_row = QWidget(tab)
|
||||
button_layout = QHBoxLayout(button_row)
|
||||
button_layout.setContentsMargins(0, 0, 0, 0)
|
||||
button_layout.setSpacing(8)
|
||||
|
||||
save_button = QPushButton("Save config", button_row)
|
||||
save_button.clicked.connect(self._save_local_contact_config)
|
||||
|
||||
reload_button = QPushButton("Reload config", button_row)
|
||||
reload_button.clicked.connect(self._daq.load_local_contact_config)
|
||||
|
||||
button_layout.addWidget(save_button)
|
||||
button_layout.addWidget(reload_button)
|
||||
button_layout.addStretch(1)
|
||||
|
||||
layout.addWidget(form_box)
|
||||
layout.addWidget(button_row)
|
||||
layout.addStretch(1)
|
||||
return tab
|
||||
|
||||
def _build_section(self, title: str, widgets: list[QWidget]) -> QWidget:
|
||||
box = QGroupBox(title, self)
|
||||
box_layout = QVBoxLayout(box)
|
||||
@@ -611,6 +670,23 @@ class LocalContactPanel(QFrame):
|
||||
logger.info(message)
|
||||
callback()
|
||||
|
||||
@Slot(dict)
|
||||
def _apply_local_contact_config(self, payload: dict) -> None:
|
||||
self._local_contact_config_payload = payload or {}
|
||||
|
||||
self._mount_to_center_sleep_s.blockSignals(True)
|
||||
self._mount_to_center_sleep_s.setValue(
|
||||
float(self._local_contact_config_payload.get("mount_to_center_sleep_s", 0.0))
|
||||
)
|
||||
self._mount_to_center_sleep_s.blockSignals(False)
|
||||
|
||||
@Slot()
|
||||
def _save_local_contact_config(self) -> None:
|
||||
payload = {
|
||||
"mount_to_center_sleep_s": float(self._mount_to_center_sleep_s.value()),
|
||||
}
|
||||
self._daq.set_local_contact_config(payload)
|
||||
|
||||
|
||||
class LocalContactDialog(QDialog):
|
||||
def __init__(self, *, daq: DAQWorker, parent=None):
|
||||
|
||||
@@ -86,6 +86,8 @@ class DAQWorker(QObject):
|
||||
local_contact_simulation_state_loaded = Signal(dict)
|
||||
local_contact_device_state_loaded = Signal(dict)
|
||||
local_contact_links_loaded = Signal(dict)
|
||||
local_contact_config_loaded = Signal(dict)
|
||||
local_contact_config_saved = Signal(dict)
|
||||
local_contact_transfer_error = Signal(str)
|
||||
|
||||
polled_devices_status = Signal(str, bool) # (message, is_error)
|
||||
@@ -1543,6 +1545,62 @@ class DAQWorker(QObject):
|
||||
)
|
||||
self._disable_local_contact_metadata_polling(message)
|
||||
|
||||
@Slot()
|
||||
def load_local_contact_config(self):
|
||||
if self.__base_url is None:
|
||||
self.local_contact_config_loaded.emit({"mount_to_center_sleep_s": 0.0})
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/config"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
reply = self.__net_manager.get(request)
|
||||
reply.finished.connect(lambda: self._handle_local_contact_config_response(reply))
|
||||
|
||||
def _handle_local_contact_config_response(self, reply: QNetworkReply):
|
||||
try:
|
||||
response_data = self.handle_response(reply)
|
||||
payload = json.loads(response_data) if response_data else {}
|
||||
if not isinstance(payload, dict):
|
||||
raise RuntimeError("Invalid local contact config payload")
|
||||
self.local_contact_config_loaded.emit(payload)
|
||||
except Exception as e:
|
||||
message = (
|
||||
"Error transferring information from DAQ while loading Local Contact config.\n\n"
|
||||
f"{e}"
|
||||
)
|
||||
logger.error(message)
|
||||
self.local_contact_transfer_error.emit(message)
|
||||
|
||||
@Slot(dict)
|
||||
def set_local_contact_config(self, payload: dict):
|
||||
if self.__base_url is None:
|
||||
self.local_contact_config_saved.emit(payload)
|
||||
return
|
||||
|
||||
request = QNetworkRequest(QUrl(f"{self.__base_url}/local_contact/config"))
|
||||
request.setRawHeader(b"Authorization", f"Bearer {self.__token}".encode("utf-8"))
|
||||
request.setRawHeader(b"Content-Type", b"application/json")
|
||||
body = QByteArray(json.dumps(payload).encode("utf-8"))
|
||||
reply = self.__net_manager.put(request, body)
|
||||
reply.finished.connect(lambda: self._handle_set_local_contact_config_response(reply))
|
||||
|
||||
def _handle_set_local_contact_config_response(self, reply: QNetworkReply):
|
||||
try:
|
||||
response_data = self.handle_response(reply)
|
||||
payload = json.loads(response_data) if response_data else {}
|
||||
if not isinstance(payload, dict):
|
||||
raise RuntimeError("Invalid local contact config response")
|
||||
self.local_contact_config_saved.emit(payload)
|
||||
self.local_contact_config_loaded.emit(payload)
|
||||
self.status_message.emit("Local Contact config saved.", False)
|
||||
except Exception as e:
|
||||
message = (
|
||||
"Error transferring information from DAQ while saving Local Contact config.\n\n"
|
||||
f"{e}"
|
||||
)
|
||||
logger.error(message)
|
||||
self.local_contact_transfer_error.emit(message)
|
||||
|
||||
@Slot(str, bool)
|
||||
def set_local_contact_simulation(self, device: str, enabled: bool):
|
||||
self.generic_post(f"local_contact/simulate/{device}?enabled={str(enabled).lower()}")
|
||||
|
||||
Reference in New Issue
Block a user