From b3e0a64bf1e7c74c669dfca85a91359f1096bbf4 Mon Sep 17 00:00:00 2001 From: gac-x05la Date: Mon, 28 Apr 2025 17:37:17 +0200 Subject: [PATCH] Found out stdDAQ filename problem --- .../device_configs/microxas_test_bed.yaml | 72 +++++++++---------- .../devices/gigafrost/gigafrostcamera.py | 7 +- tomcat_bec/devices/gigafrost/pcoedgecamera.py | 41 ++++++++--- .../devices/gigafrost/std_daq_client.py | 19 +++-- tomcat_bec/scans/simple_scans.py | 0 5 files changed, 85 insertions(+), 54 deletions(-) create mode 100644 tomcat_bec/scans/simple_scans.py diff --git a/tomcat_bec/device_configs/microxas_test_bed.yaml b/tomcat_bec/device_configs/microxas_test_bed.yaml index 9bb6db8..96905cb 100644 --- a/tomcat_bec/device_configs/microxas_test_bed.yaml +++ b/tomcat_bec/device_configs/microxas_test_bed.yaml @@ -102,7 +102,7 @@ es1_psod: onFailure: buffer readOnly: false readoutPriority: monitored - softwareTrigger: true + softwareTrigger: false es1_ddaq: @@ -133,22 +133,22 @@ es1_ddaq: # softwareTrigger: true -gfcam: - description: GigaFrost camera client - deviceClass: tomcat_bec.devices.GigaFrostCamera - deviceConfig: - prefix: 'X02DA-CAM-GF2:' - backend_url: 'http://sls-daq-001:8080' - auto_soft_enable: true - deviceTags: - - camera - - trigger - - gfcam - enabled: true - onFailure: buffer - readOnly: false - readoutPriority: monitored - softwareTrigger: true +# gfcam: +# description: GigaFrost camera client +# deviceClass: tomcat_bec.devices.GigaFrostCamera +# deviceConfig: +# prefix: 'X02DA-CAM-GF2:' +# backend_url: 'http://sls-daq-001:8080' +# auto_soft_enable: true +# deviceTags: +# - camera +# - trigger +# - gfcam +# enabled: true +# onFailure: buffer +# readOnly: false +# readoutPriority: monitored +# softwareTrigger: true # gfcam: @@ -202,30 +202,11 @@ gfcam: # softwareTrigger: false -pcocam: - description: PCO.edge camera client - deviceClass: tomcat_bec.devices.PcoEdge5M - deviceConfig: - prefix: 'X02DA-CCDCAM2:' - deviceTags: - - camera - - trigger - - pcocam - enabled: true - onFailure: buffer - readOnly: false - readoutPriority: monitored - softwareTrigger: true - - # pcocam: # description: PCO.edge camera client # deviceClass: tomcat_bec.devices.PcoEdge5M # deviceConfig: # prefix: 'X02DA-CCDCAM2:' -# std_daq_live: 'tcp://129.129.95.111:20010' -# std_daq_ws: 'ws://129.129.95.111:8081' -# std_daq_rest: 'http://129.129.95.111:5010' # deviceTags: # - camera # - trigger @@ -236,6 +217,25 @@ pcocam: # readoutPriority: monitored # softwareTrigger: true + +pcocam: + description: PCO.edge camera client + deviceClass: tomcat_bec.devices.PcoEdge5M + deviceConfig: + prefix: 'X02DA-CCDCAM2:' + std_daq_live: 'tcp://129.129.95.111:20010' + std_daq_ws: 'ws://129.129.95.111:8081' + std_daq_rest: 'http://129.129.95.111:5010' + deviceTags: + - camera + - trigger + - pcocam + enabled: true + onFailure: buffer + readOnly: false + readoutPriority: monitored + softwareTrigger: true + # pcodaq: # description: GigaFrost stdDAQ client # deviceClass: tomcat_bec.devices.StdDaqClient diff --git a/tomcat_bec/devices/gigafrost/gigafrostcamera.py b/tomcat_bec/devices/gigafrost/gigafrostcamera.py index 7be4e01..1f7b99c 100644 --- a/tomcat_bec/devices/gigafrost/gigafrostcamera.py +++ b/tomcat_bec/devices/gigafrost/gigafrostcamera.py @@ -199,7 +199,7 @@ class GigaFrostCamera(PSIDeviceBase, GigaFrostBase): supplied signal. Use external enable instead, that works! """ - if acq_mode == "default": + if acq_mode in ["default", "step"]: # NOTE: Software trigger via softEnable (actually works) # Trigger parameters self.fix_nframes_mode = "start" @@ -630,14 +630,11 @@ class GigaFrostCamera(PSIDeviceBase, GigaFrostBase): and self.trigger_mode == "auto" and self.enable_mode == "soft" ): - t_start = time() # BEC teststand operation mode: posedge of SoftEnable if Started self.soft_enable.set(0).wait() self.soft_enable.set(1).wait() - logger.info(f"Elapsed: {time()-t_start}") - - if self.acquire_block.get(): + if self.acquire_block.get() or self.backend is None: wait_time = 0.2 + 0.001 * self.num_exposures.value * max( self.acquire_time.value, self.acquire_period.value ) diff --git a/tomcat_bec/devices/gigafrost/pcoedgecamera.py b/tomcat_bec/devices/gigafrost/pcoedgecamera.py index 7653bdd..f8461c2 100644 --- a/tomcat_bec/devices/gigafrost/pcoedgecamera.py +++ b/tomcat_bec/devices/gigafrost/pcoedgecamera.py @@ -59,7 +59,6 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase): USER_ACCESS = [ "complete", "backend", - # "acq_done", "live_preview", "arm", "disarm", @@ -125,6 +124,8 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase): *'FIFO buffer' for continous streaming data_format : str Usually set to 'ZEROMQ' + acq_mode : str + Store mode and data format according to preconfigured settings """ if self.state not in ("IDLE"): raise RuntimeError(f"Can't change configuration from state {self.state}") @@ -149,6 +150,10 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase): if "data_format" in d: self.file_format.set(d["data_format"]).wait() + # If a pre-configured acquisition mode is specified, set it + if "acq_mode" in d: + self.set_acquisition_mode(d["acq_mode"]) + # State machine # Initial: BUSY and SET both low # 0. Write 1 to SET_PARAM @@ -165,6 +170,19 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase): self.set_param.set(1).wait() status.wait() + def set_acquisition_mode(self, acq_mode): + """Set acquisition mode + + Utility function to quickly select between pre-configured and tested + acquisition modes. + """ + if acq_mode in ["default", "step"]: + # NOTE: Trigger duration requires a consumer + self.bufferStoreMode.set("Recorder").wait() + # self.file_format.set("ZEROMQ").wait() + else: + raise RuntimeError(f"Unsupported acquisition mode: {acq_mode}") + def arm(self): """Bluesky style stage: arm the detector""" logger.warning("Staging PCO") @@ -206,6 +224,7 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase): def _on_preview_update(self, img: np.ndarray, header: dict): """Send preview stream and update frame index counter""" + # FIXME: There's also a recorded images counter provided by the stdDAQ writer self.num_images_counter.put(header["frame"], force=True) self._run_subs(sub_type=self.SUB_DEVICE_MONITOR_2D, obj=self, value=img) @@ -251,8 +270,6 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase): } d = {} - if "exp_burst" in scan_args and scan_args["exp_burst"] is not None: - d["exposure_num_burst"] = scan_args["exp_burst"] if "image_width" in scan_args and scan_args["image_width"] is not None: d["image_width"] = scan_args["image_width"] if "image_height" in scan_args and scan_args["image_height"] is not None: @@ -261,10 +278,16 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase): d["exposure_time_ms"] = scan_args["exp_time"] if "exp_period" in scan_args and scan_args["exp_period"] is not None: d["exposure_period_ms"] = scan_args["exp_period"] - # if 'exp_burst' in scan_args and scan_args['exp_burst'] is not None: - # d['exposure_num_burst'] = scan_args['exp_burst'] - # if 'acq_mode' in scan_args and scan_args['acq_mode'] is not None: - # d['acq_mode'] = scan_args['acq_mode'] + if 'exp_burst' in scan_args and scan_args['exp_burst'] is not None: + d['exposure_num_burst'] = scan_args['exp_burst'] + if "acq_time" in scan_args and scan_args["acq_time"] is not None: + d["exposure_time_ms"] = scan_args["acq_time"] + if "acq_period" in scan_args and scan_args["acq_period"] is not None: + d["exposure_period_ms"] = scan_args["acq_period"] + if 'acq_burst' in scan_args and scan_args['acq_burst'] is not None: + d['exposure_num_burst'] = scan_args['acq_burst'] + if 'acq_mode' in scan_args and scan_args['acq_mode'] is not None: + d['acq_mode'] = scan_args['acq_mode'] # elif self.scaninfo.scan_type == "step": # d['acq_mode'] = "default" if "pco_store_mode" in scan_args and scan_args["pco_store_mode"] is not None: @@ -305,6 +328,7 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase): def on_pre_scan(self) -> DeviceStatus | None: """Called right before the scan starts on all devices automatically.""" + logger.warning("Called op_prescan on PCO camera") # First start the stdDAQ if self.backend is not None: self.backend.start( @@ -371,8 +395,7 @@ class PcoEdge5M(PSIDeviceBase, PcoEdgeBase): def on_complete(self) -> DeviceStatus | None: """Called to inquire if a device has completed a scans.""" - # return self.acq_done() - return None + return self.acq_done() def on_kickoff(self) -> DeviceStatus | None: """Start data transfer diff --git a/tomcat_bec/devices/gigafrost/std_daq_client.py b/tomcat_bec/devices/gigafrost/std_daq_client.py index f5b8caf..56cca38 100644 --- a/tomcat_bec/devices/gigafrost/std_daq_client.py +++ b/tomcat_bec/devices/gigafrost/std_daq_client.py @@ -48,9 +48,10 @@ class StdDaqStatus(str, enum.Enum): class StdDaqClient: - USER_ACCESS = ["status", "start", "stop", "get_config", "set_config", "reset", "_status"] + USER_ACCESS = ["status", "count", "start", "stop", "get_config", "set_config", "reset", "_status"] _ws_client: ws.ClientConnection | None = None + _count: int = 0 _status: StdDaqStatus = StdDaqStatus.UNDEFINED _status_timestamp: float | None = None _ws_recv_mutex = threading.Lock() @@ -81,6 +82,11 @@ class StdDaqClient: """ return self._status + @property + def count(self) -> int: + """ Get the recorded frame count""" + return self._count + def add_status_callback( self, status: DeviceStatus, success: list[StdDaqStatus], error: list[StdDaqStatus] ): @@ -113,15 +119,17 @@ class StdDaqClient: # Ensure connection self.wait_for_connection() - logger.info(f"Starting StdDaq backend. Current status: {self.status}") status = StatusBase() - self.add_status_callback(status, success=["waiting_for_first_image"], error=["rejected"]) + # NOTE: CREATING_FILE --> IDLE is a known error, the exact cause is unknown, nut might be botched overwrite protection + # Changing file_prefix often solves the problem, but still allows overwrites + self.add_status_callback(status, success=["waiting_for_first_image"], error=["rejected", "idle"]) message = { "command": "start", "path": file_path, "file_prefix": file_prefix, "n_image": num_images, } + logger.info(f"Starting StdDaq backend. Current status: {self.status}. Message: {message}") self._ws_client.send(json.dumps(message)) if wait: status.wait(timeout=timeout) @@ -327,7 +335,10 @@ class StdDaqClient: continue msg = json.loads(msg) if self._status != msg["status"]: - logger.info(f"stdDAQ state transition by: {msg}") + logger.warning(f"stdDAQ state transition: {self._status} --> {msg}") + if msg["status"] == "recording": + self._count = msg.get("count", 0) + # Update status and run callbacks self._status = msg["status"] self._status_timestamp = msg_timestamp self._run_status_callbacks() diff --git a/tomcat_bec/scans/simple_scans.py b/tomcat_bec/scans/simple_scans.py new file mode 100644 index 0000000..e69de29