diff --git a/tomcat_bec/devices/gigafrost/stddaq_client.py b/tomcat_bec/devices/gigafrost/stddaq_client.py index ffe35ad..437acbd 100644 --- a/tomcat_bec/devices/gigafrost/stddaq_client.py +++ b/tomcat_bec/devices/gigafrost/stddaq_client.py @@ -9,7 +9,7 @@ Created on Thu Jun 27 17:28:43 2024 import json from time import sleep from threading import Thread -from ophyd import Device, Signal, Component, Kind, DeviceStatus +from ophyd import Device, Signal, Component, Kind, DeviceStatus, Staged from websockets.sync.client import connect from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError @@ -74,6 +74,10 @@ class StdDaqClient(Device): self._client = None self.connect() + def __del__(self) -> None: + self._client.close() + return super().__del__() + def connect(self): """Connect to the StdDAQ's websockets interface @@ -130,13 +134,34 @@ class StdDaqClient(Device): self.output_file.set(str(d['file_path'])) # Configure DAQ if 'pixel_width' in d or 'pixel_height' in d: - self.stop() - sleep(1) + # Safe stop before configure (see 'reset') + self.reset() self.config.configure(d) new_config = self.read_configuration() return (old_config, new_config) + + def reset(self): + """ + + The current stdDAQ refuses connection if another session is running. This is safety so + we don't accidentally kill a running exposure. But this also means that we have to wait + until a dead session dies of timeout. + + NOTE: REST reconfiguration restarts with systemd and can corrupt currently written files. + """ + try: + if self._client is not None: + self._client.close() + self._client = connect(self._ws_url) + msg = json.dumps({"command": "stop"}) + self._client.send(msg) + except (ConnectionClosedError, ConnectionClosedOK, ConnectionRefusedError): + pass + self._staged = Staged.no + sleep(1) + def stage(self) -> list: """Start a new run with the standard DAQ @@ -145,7 +170,6 @@ class StdDaqClient(Device): not, we can't query if not running. """ if self._staged: - self.unstage() self._client.close() file_path = self.file_path.get() @@ -188,6 +212,7 @@ class StdDaqClient(Device): self.message(message, wait_reply=False) except RuntimeError: pass + self._client.close() return super().unstage() def kickoff(self) -> DeviceStatus: diff --git a/tomcat_bec/devices/gigafrost/stddaq_rest.py b/tomcat_bec/devices/gigafrost/stddaq_rest.py index 0cca770..d8abfb4 100644 --- a/tomcat_bec/devices/gigafrost/stddaq_rest.py +++ b/tomcat_bec/devices/gigafrost/stddaq_rest.py @@ -38,7 +38,7 @@ class StdDaqRestClient(Device): """ # pylint: disable=too-many-instance-attributes - USER_ACCESS = ["write_daq_config"] + USER_ACCESS = ["set_daq_config", "get_daq_config", "reset"] # Status attributes rest_url = Component(Signal, kind=Kind.config) @@ -74,14 +74,15 @@ class StdDaqRestClient(Device): """ r = requests.get( self.rest_url.get() + '/api/config/get', - params={'config_file': "/etc/std_daq/configs/gf1.json", 'user': "ioc"}, + params={'user': "ioc"}, + # params={'config_file': "/etc/std_daq/configs/gf.json", 'user': "ioc"}, timeout=2 ) if r.status_code != 200: raise ConnectionError(f"[{self.name}] Error {r.status_code}:\t{r.text}") return r.json() - def read_daq_config(self) -> None: + def read_daq_config(self) -> dict: """Extract the current configuration from the JSON file """ cfg = self.get_daq_config() @@ -122,13 +123,10 @@ class StdDaqRestClient(Device): config = orig.update(config) return config - def write_daq_config(self): + def set_daq_config(self, config): """Write configuration ased on current PV values. Some fields might be unchangeable. """ - orig = self.get_daq_config() - config = self._build_config(orig) - params = {"user": "ioc"} r = requests.post( self.rest_url.get() + '/api/config/set', @@ -139,7 +137,15 @@ class StdDaqRestClient(Device): ) if r.status_code != 200: raise ConnectionError(f"[{self.name}] Error {r.status_code}:\t{r.text}") - return r + return r.json() + + def write_daq_config(self): + """Write configuration ased on current PV values. Some fields might be + unchangeable. + """ + orig = self.get_daq_config() + config = self._build_config(orig) + return self.set_daq_config(config) def configure(self, d: dict = None): """Configure the next scan with the GigaFRoST camera @@ -179,6 +185,11 @@ class StdDaqRestClient(Device): new = self.read_configuration() return old, new + def reset(self): + """ Reconfigures the stdDAQ to restart the services""" + cfg = self.get_daq_config() + self.set_daq_config(cfg) + def read(self): self.read_daq_config() return super().read() diff --git a/tomcat_bec/scans/tutorial_fly_scan.py b/tomcat_bec/scans/tutorial_fly_scan.py index ead9ae4..92a9b1b 100644 --- a/tomcat_bec/scans/tutorial_fly_scan.py +++ b/tomcat_bec/scans/tutorial_fly_scan.py @@ -7,6 +7,8 @@ from bec_server.scan_server.scans import Acquire, AsyncFlyScanBase class AcquireDark(Acquire): scan_name = "acquire_dark" + required_kwargs = ["num"] + gui_config = {"Acquisition parameters": ["num"]} def __init__(self, num: int, **kwargs): """ @@ -35,6 +37,8 @@ class AcquireDark(Acquire): class AcquireFlat(Acquire): scan_name = "acquire_flat" + required_kwargs = ["num"] + gui_config = {"Acquisition parameters": ["num"]} def __init__(self, num: int, out_position: float, **kwargs): """ @@ -69,6 +73,8 @@ class AcquireFlat(Acquire): class TutorialFlyScanContLine(AsyncFlyScanBase): scan_name = "tutorial_cont_line_fly_scan" + required_kwargs = ["motor"] + gui_config = {"Acquisition parameters": ["motor"]} def __init__( self,