local_contact_panel.py: added config for autoamtion, currently just the sleep between mount and wait.

This commit is contained in:
2026-07-03 11:28:00 +02:00
parent ce1f939e63
commit 6a90a01dbe
5 changed files with 200 additions and 2 deletions
+39
View File
@@ -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())
+9
View File
@@ -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
View File
@@ -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)
+77 -1
View File
@@ -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):
+58
View File
@@ -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()}")