DAQ: added loco panel and commands such as tell dry to GUI and DAQ
This commit is contained in:
+34
-1
@@ -1316,12 +1316,45 @@ class AareDAQ:
|
||||
logger.error(f"Failed to park and dry: {e}")
|
||||
raise
|
||||
|
||||
def blower_control(self):
|
||||
def tell_dry(self):
|
||||
self.__cfg.try_set_busy(timeout=360)
|
||||
try:
|
||||
self.__devs.tell.check_enable_motion()
|
||||
self.__devs.tell.wait_not_busy()
|
||||
self.__devs.tell.dry(wait=True)
|
||||
self.__cfg.state_busy = False
|
||||
except Exception as e:
|
||||
self.__cfg.state_busy = False
|
||||
logger.error(f"Failed to dry TELL: {e}")
|
||||
raise
|
||||
|
||||
def tell_toggle_blower(self):
|
||||
try:
|
||||
self.__devs.tell.toggle_blower()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to turn off blower: {e}")
|
||||
|
||||
def initialise_smargon(self):
|
||||
self.__cfg.try_set_busy(timeout=360)
|
||||
try:
|
||||
self.__devs.smargon_initialize()
|
||||
self.__cfg.state_busy = False
|
||||
except Exception as e:
|
||||
self.__cfg.state_busy = False
|
||||
logger.error(f"Failed to initialise Smargon: {e}")
|
||||
raise
|
||||
|
||||
def initialise_detector(self):
|
||||
self.__cfg.try_set_busy(timeout=360)
|
||||
try:
|
||||
self.__jfjoch.initialize()
|
||||
self.__cfg.state_busy = False
|
||||
except Exception as e:
|
||||
self.__cfg.state_busy = False
|
||||
logger.error(f"Failed to initialise detector: {e}")
|
||||
raise
|
||||
|
||||
|
||||
def __magnet_position_sensor_check(self, timeout: float = 1.0, repeat: bool = True):
|
||||
#TODO check this works, add beamstop z controls and test.
|
||||
|
||||
|
||||
+77
-1
@@ -492,6 +492,45 @@ async def anneal(time_s: float, token: str = Depends(oauth2_scheme)):
|
||||
daq.anneal(time_s)
|
||||
return "OK"
|
||||
|
||||
@app.post("/smargon/initialize")
|
||||
async def initialise_smargon(token: str = Depends(oauth2_scheme)) -> dict:
|
||||
"""
|
||||
Initialise Smargon. Staff only.
|
||||
|
||||
Args:
|
||||
token: OAuth2 access token.
|
||||
|
||||
Returns:
|
||||
Dictionary with status and message.
|
||||
"""
|
||||
data = auth.parse_token(token)
|
||||
auth.check_jwt_staff_only(data)
|
||||
daq.initialise_smargon()
|
||||
return {
|
||||
"ok": True,
|
||||
"message": "Smargon initialised.",
|
||||
}
|
||||
|
||||
|
||||
@app.post("/detector/initialize")
|
||||
async def initialise_detector(token: str = Depends(oauth2_scheme)) -> dict:
|
||||
"""
|
||||
Initialise the detector. Staff only.
|
||||
|
||||
Args:
|
||||
token: OAuth2 access token.
|
||||
|
||||
Returns:
|
||||
Dictionary with status and message.
|
||||
"""
|
||||
data = auth.parse_token(token)
|
||||
auth.check_jwt_staff_only(data)
|
||||
daq.initialise_detector()
|
||||
return {
|
||||
"ok": True,
|
||||
"message": "Detector initialised.",
|
||||
}
|
||||
|
||||
@app.post("/beamline/goto_abr_meas_pos")
|
||||
async def goto_abr_meas_pos(token: str = Depends(oauth2_scheme)):
|
||||
"""
|
||||
@@ -699,7 +738,7 @@ async def sample(token: str = Depends(oauth2_scheme)) -> SampleShortInfo:
|
||||
)
|
||||
|
||||
|
||||
@app.post("/sample/park_and_dry")
|
||||
@app.post("/tell/park_and_dry")
|
||||
async def park_and_dry(token: str = Depends(oauth2_scheme)):
|
||||
"""
|
||||
Execute the 'park and dry' procedure for the sample changer (TELL).
|
||||
@@ -717,6 +756,43 @@ async def park_and_dry(token: str = Depends(oauth2_scheme)):
|
||||
"message": "TELL has been dried and parked",
|
||||
}
|
||||
|
||||
@app.post("/tell/dry")
|
||||
async def tell_dry(token: str = Depends(oauth2_scheme)) -> dict:
|
||||
"""
|
||||
Execute a TELL dry cycle. Staff only.
|
||||
|
||||
Args:
|
||||
token: OAuth2 access token.
|
||||
|
||||
Returns:
|
||||
Dictionary with status and message.
|
||||
"""
|
||||
data = auth.parse_token(token)
|
||||
auth.check_jwt_staff_only(data)
|
||||
daq.tell_dry()
|
||||
return {
|
||||
"ok": True,
|
||||
"message": "TELL dry cycle completed.",
|
||||
}
|
||||
|
||||
@app.post("/tell/toggle_blower")
|
||||
async def tell_toggle_blower(token: str = Depends(oauth2_scheme)) -> dict:
|
||||
"""
|
||||
Toggle the TELL blower. Staff only.
|
||||
|
||||
Args:
|
||||
token: OAuth2 access token.
|
||||
|
||||
Returns:
|
||||
Dictionary with status and message.
|
||||
"""
|
||||
data = auth.parse_token(token)
|
||||
auth.check_jwt_staff_only(data)
|
||||
daq.blower_control()
|
||||
return {
|
||||
"ok": True,
|
||||
"message": "TELL blower toggled.",
|
||||
}
|
||||
|
||||
@app.post("/sample/mount")
|
||||
async def mount(dbid: int, token: str = Depends(oauth2_scheme), reference: bool = False):
|
||||
|
||||
@@ -26,6 +26,7 @@ from aare.gui.panels.beamline_state_panel import BeamlineStatePanel
|
||||
from aare.gui.panels.data_collection_settings import DataCollectionSettings
|
||||
from aare.gui.panels.developer_help_dialog import DeveloperHelpDialog
|
||||
from aare.gui.panels.beamline_recovery_panel import BeamlineRecoveryDialog
|
||||
from aare.gui.panels.local_contact_panel import LocalContactDialog
|
||||
from aare.gui.panels.manual_sample_panel import ManualSamplePanel
|
||||
from aare.gui.panels.prediction_metrics_panel import PredictionMetricsPanel
|
||||
from aare.gui.panels.reference_tools_panel import ReferenceToolsPanel
|
||||
@@ -87,6 +88,7 @@ class MainWindow(QMainWindow):
|
||||
self._automation_critical_banner_active = False
|
||||
self._dev_help_dialog = None
|
||||
self._beamline_recovery_dialog = None
|
||||
self._local_contact_dialog = None
|
||||
self._controls_help_dialog = None
|
||||
self._cleanup_done = False
|
||||
self._default_window_state = None
|
||||
@@ -943,6 +945,10 @@ class MainWindow(QMainWindow):
|
||||
beamline_recovery_action.triggered.connect(self.show_beamline_recovery)
|
||||
help_menu.addAction(beamline_recovery_action)
|
||||
|
||||
local_contact_action = QAction("Local Contact", self)
|
||||
local_contact_action.triggered.connect(self.show_local_contact)
|
||||
help_menu.addAction(local_contact_action)
|
||||
|
||||
help_menu.addSeparator()
|
||||
|
||||
start_text_tutorial_action = QAction("Start Tutorial (Text)", self)
|
||||
@@ -1157,7 +1163,9 @@ class MainWindow(QMainWindow):
|
||||
f"Details:\n{message}"
|
||||
),
|
||||
)
|
||||
self.show_beamline_recovery()
|
||||
self.show_local_contact("Detector")
|
||||
else:
|
||||
self.show_local_contact("Recovery")
|
||||
else:
|
||||
if is_detector_failure:
|
||||
QMessageBox.critical(
|
||||
@@ -1195,6 +1203,19 @@ class MainWindow(QMainWindow):
|
||||
self._beamline_recovery_dialog.raise_()
|
||||
self._beamline_recovery_dialog.activateWindow()
|
||||
|
||||
def show_local_contact(self, tab_name: str = "Recovery") -> None:
|
||||
if not bool(getattr(self.__decoded_token, "staff", False)):
|
||||
return
|
||||
if self._local_contact_dialog is None:
|
||||
self._local_contact_dialog = LocalContactDialog(
|
||||
daq=self.daq,
|
||||
parent=self,
|
||||
)
|
||||
self._local_contact_dialog.set_active_tab(tab_name)
|
||||
self._local_contact_dialog.show()
|
||||
self._local_contact_dialog.raise_()
|
||||
self._local_contact_dialog.activateWindow()
|
||||
|
||||
@Slot(str)
|
||||
def show_sample_missing_dialog(self, msg: str):
|
||||
if self.job_list_panel.is_running():
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from PySide6.QtCore import Slot
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QFrame,
|
||||
QGridLayout,
|
||||
QInputDialog,
|
||||
QLabel,
|
||||
QPushButton,
|
||||
QTabWidget,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from aare.common.logger_config import setup_logger
|
||||
from aare.gui.panels.beamline_recovery_panel import RecoveryPanel
|
||||
from aare.gui.threads.daq_worker import DAQWorker
|
||||
from aare.gui.widgets.title_label import TitleLabel
|
||||
|
||||
logger = setup_logger("aareGUI")
|
||||
|
||||
|
||||
class LocalContactPanel(QFrame):
|
||||
def __init__(self, *, daq: DAQWorker, parent=None):
|
||||
super().__init__(parent)
|
||||
self._daq = daq
|
||||
|
||||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
self.setFrameShadow(QFrame.Shadow.Raised)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(8)
|
||||
|
||||
layout.addWidget(TitleLabel("Local Contact", parent=self))
|
||||
|
||||
self._info_label = QLabel(
|
||||
"Staff tools for beamline recovery and local-contact operations.\n"
|
||||
"Some buttons are placeholders until backend pathways are connected.",
|
||||
self,
|
||||
)
|
||||
self._info_label.setWordWrap(True)
|
||||
layout.addWidget(self._info_label)
|
||||
|
||||
self._tabs = QTabWidget(self)
|
||||
layout.addWidget(self._tabs, 1)
|
||||
|
||||
self._tabs.addTab(self._build_recovery_tab(), "Recovery")
|
||||
self._tabs.addTab(self._build_tell_tab(), self.TAB_TELL)
|
||||
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)
|
||||
|
||||
def set_active_tab(self, tab_name: str) -> None:
|
||||
for index in range(self._tabs.count()):
|
||||
if self._tabs.tabText(index).strip().lower() == str(tab_name).strip().lower():
|
||||
self._tabs.setCurrentIndex(index)
|
||||
return
|
||||
|
||||
logger.warning(f"Unknown Local Contact tab requested: {tab_name}")
|
||||
|
||||
def _build_recovery_tab(self) -> QWidget:
|
||||
tab = QWidget(self)
|
||||
layout = QVBoxLayout(tab)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
self._recovery_panel = RecoveryPanel(daq=self._daq, parent=tab)
|
||||
layout.addWidget(self._recovery_panel)
|
||||
|
||||
return tab
|
||||
|
||||
def _build_tell_tab(self) -> QWidget:
|
||||
tab = QWidget(self)
|
||||
layout = QGridLayout(tab)
|
||||
|
||||
row = 0
|
||||
layout.addWidget(QLabel("TELL operations", tab), row, 0, 1, 2)
|
||||
row += 1
|
||||
|
||||
layout.addWidget(
|
||||
self._make_button(
|
||||
"Unmount",
|
||||
self._daq.unmount,
|
||||
"Requesting sample unmount.",
|
||||
),
|
||||
row,
|
||||
0,
|
||||
)
|
||||
layout.addWidget(
|
||||
self._make_button(
|
||||
"Dry",
|
||||
self._daq.tell_dry,
|
||||
"Requesting TELL dry.",
|
||||
),
|
||||
row,
|
||||
1,
|
||||
)
|
||||
row += 1
|
||||
|
||||
layout.addWidget(
|
||||
self._make_button(
|
||||
"Park and dry",
|
||||
self._daq.park_and_dry,
|
||||
"Requesting park and dry.",
|
||||
),
|
||||
row,
|
||||
0,
|
||||
)
|
||||
layout.addWidget(
|
||||
self._make_button(
|
||||
"Toggle blower",
|
||||
self._daq.tell_toggle_blower,
|
||||
"Toggling blower.",
|
||||
),
|
||||
row,
|
||||
1,
|
||||
)
|
||||
row += 1
|
||||
|
||||
layout.addWidget(
|
||||
self._make_button(
|
||||
"Anneal",
|
||||
self._anneal_from_dialog,
|
||||
),
|
||||
row,
|
||||
0,
|
||||
)
|
||||
|
||||
layout.setRowStretch(row + 1, 1)
|
||||
return tab
|
||||
|
||||
def _build_bec_tab(self) -> QWidget:
|
||||
tab = QWidget(self)
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
label = QLabel(
|
||||
"BEC tools.\n"
|
||||
"For now this tab only exposes loading user macros.",
|
||||
tab,
|
||||
)
|
||||
label.setWordWrap(True)
|
||||
layout.addWidget(label)
|
||||
|
||||
layout.addWidget(
|
||||
self._make_button(
|
||||
"Load user macros",
|
||||
self._daq.bec_load_user_macros,
|
||||
"Loading BEC user macros.",
|
||||
)
|
||||
)
|
||||
layout.addStretch(1)
|
||||
|
||||
return tab
|
||||
|
||||
def _build_hardware_tab(self) -> QWidget:
|
||||
tab = QWidget(self)
|
||||
layout = QVBoxLayout(tab)
|
||||
|
||||
label = QLabel(
|
||||
"Hardware initialisation tools.\n"
|
||||
"These are placeholders for future Smargon and Aerotech initialisation support.",
|
||||
tab,
|
||||
)
|
||||
label.setWordWrap(True)
|
||||
layout.addWidget(label)
|
||||
|
||||
layout.addWidget(
|
||||
self._make_button(
|
||||
"Initialise Smargon",
|
||||
self._daq.initialise_smargon,
|
||||
"Initialising Smargon.",
|
||||
)
|
||||
)
|
||||
layout.addWidget(
|
||||
self._make_button(
|
||||
"Initialise Aerotech",
|
||||
self._daq.initialise_aerotech,
|
||||
"Initialising Aerotech.",
|
||||
)
|
||||
)
|
||||
layout.addStretch(1)
|
||||
|
||||
return tab
|
||||
|
||||
def _build_detector_tab(self) -> QWidget:
|
||||
tab = QWidget(self)
|
||||
layout = QGridLayout(tab)
|
||||
|
||||
row = 0
|
||||
layout.addWidget(QLabel("Detector operations", tab), row, 0, 1, 2)
|
||||
row += 1
|
||||
|
||||
layout.addWidget(
|
||||
self._make_button(
|
||||
"Cancel current state",
|
||||
self._daq.cancel,
|
||||
"Cancelling current detector/scan state.",
|
||||
),
|
||||
row,
|
||||
0,
|
||||
)
|
||||
layout.addWidget(
|
||||
self._make_button(
|
||||
"Take pedestal",
|
||||
self._daq.detector_take_pedestal,
|
||||
"Requesting detector pedestal.",
|
||||
),
|
||||
row,
|
||||
1,
|
||||
)
|
||||
row += 1
|
||||
|
||||
layout.addWidget(
|
||||
self._make_button(
|
||||
"Initialise detector",
|
||||
self._daq.initialise_detector,
|
||||
"Initialising detector.",
|
||||
),
|
||||
row,
|
||||
0,
|
||||
)
|
||||
|
||||
layout.setRowStretch(row + 1, 1)
|
||||
return tab
|
||||
|
||||
def _make_button(
|
||||
self,
|
||||
text: str,
|
||||
callback: Callable[[], None],
|
||||
log_message: str | None = None,
|
||||
) -> QPushButton:
|
||||
button = QPushButton(text, self)
|
||||
if log_message is None:
|
||||
button.clicked.connect(callback)
|
||||
else:
|
||||
button.clicked.connect(lambda: self._run_logged_action(log_message, callback))
|
||||
return button
|
||||
|
||||
@Slot()
|
||||
def _anneal_from_dialog(self) -> None:
|
||||
seconds, ok = QInputDialog.getDouble(
|
||||
self,
|
||||
"Anneal",
|
||||
"Anneal time (s):",
|
||||
1.0,
|
||||
0.1,
|
||||
60.0,
|
||||
1,
|
||||
)
|
||||
if not ok:
|
||||
return
|
||||
self._daq.anneal(seconds)
|
||||
|
||||
@Slot()
|
||||
def _run_logged_action(self, message: str, callback: Callable[[], None]) -> None:
|
||||
logger.info(message)
|
||||
callback()
|
||||
|
||||
|
||||
class LocalContactDialog(QDialog):
|
||||
def __init__(self, *, daq: DAQWorker, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Local Contact")
|
||||
self.setMinimumSize(760, 560)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(12, 12, 12, 12)
|
||||
layout.setSpacing(8)
|
||||
|
||||
self._panel = LocalContactPanel(daq=daq, parent=self)
|
||||
layout.addWidget(self._panel, 1)
|
||||
|
||||
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close, parent=self)
|
||||
buttons.rejected.connect(self.reject)
|
||||
buttons.accepted.connect(self.accept)
|
||||
layout.addWidget(buttons)
|
||||
|
||||
def set_active_tab(self, tab_name: str) -> None:
|
||||
self._panel.set_active_tab(tab_name)
|
||||
@@ -596,6 +596,11 @@ class DAQWorker(QObject):
|
||||
logger.error(f"Recovery action failed: {e}")
|
||||
self.http_error.emit(str(e))
|
||||
|
||||
def _emit_placeholder_local_contact_action(self, action_name: str) -> None:
|
||||
message = f"{action_name}: This hasn't been connected yet."
|
||||
logger.info(message)
|
||||
self.status_message.emit(message, False)
|
||||
|
||||
def generic_post(self, url: str, body: str = ""):
|
||||
"""
|
||||
Send a generic HTTP POST request to the server.
|
||||
@@ -1224,6 +1229,43 @@ class DAQWorker(QObject):
|
||||
reply = self.__net_manager.post(request, QByteArray(b""))
|
||||
reply.finished.connect(lambda: self._handle_sample_resync_response(reply))
|
||||
|
||||
@Slot(float)
|
||||
def anneal(self, time_s: float):
|
||||
self.generic_post(f"beamline/anneal?time_s={time_s:.1f}")
|
||||
|
||||
#TODO combine dry and park and dry
|
||||
@Slot()
|
||||
def park_and_dry(self):
|
||||
self.generic_post("tell/park_and_dry")
|
||||
|
||||
@Slot()
|
||||
def tell_dry(self):
|
||||
self.generic_post("tell/dry")
|
||||
|
||||
@Slot()
|
||||
def tell_toggle_blower(self):
|
||||
self.generic_post("tell/toggle_blower")
|
||||
|
||||
@Slot()
|
||||
def bec_load_user_macros(self):
|
||||
self._emit_placeholder_local_contact_action("BEC load user macros")
|
||||
|
||||
@Slot()
|
||||
def initialise_smargon(self):
|
||||
self.generic_post("smargon/initialize")
|
||||
|
||||
@Slot()
|
||||
def initialise_aerotech(self):
|
||||
self._emit_placeholder_local_contact_action("Initialise Aerotech")
|
||||
|
||||
@Slot()
|
||||
def detector_take_pedestal(self):
|
||||
self._emit_placeholder_local_contact_action("Detector take pedestal")
|
||||
|
||||
@Slot()
|
||||
def initialise_detector(self):
|
||||
self.generic_post("detector/initialize")
|
||||
|
||||
@Slot()
|
||||
def unmount(self):
|
||||
self.generic_post("sample/unmount")
|
||||
@@ -1232,10 +1274,6 @@ class DAQWorker(QObject):
|
||||
def mount(self, s: SampleShortInfo, reference: bool = False):
|
||||
self.generic_post(f"sample/mount?dbid={s.db_id}&reference={reference}")
|
||||
|
||||
@Slot()
|
||||
def park_and_dry(self):
|
||||
self.generic_post("sample/park_and_dry")
|
||||
|
||||
@Slot(SampleShortInfo)
|
||||
def sample_manual(self, s: SampleShortInfo):
|
||||
self.generic_post(f"sample/manual", s.model_dump_json())
|
||||
|
||||
Reference in New Issue
Block a user