diff --git a/tomcat_bec/device_configs/microxas_test_bed.yaml b/tomcat_bec/device_configs/microxas_test_bed.yaml index 3a4bf14..f29c7c0 100644 --- a/tomcat_bec/device_configs/microxas_test_bed.yaml +++ b/tomcat_bec/device_configs/microxas_test_bed.yaml @@ -128,7 +128,7 @@ daq_stream0: description: Standard DAQ preview stream 2 frames every 1000 image deviceClass: tomcat_bec.devices.gigafrost.stddaq_preview.StdDaqPreview deviceConfig: - url: 'tcp://129.129.95.38:20000' + url: 'tcp://129.129.95.38:20002' deviceTags: - std-daq enabled: true @@ -150,3 +150,17 @@ daq_stream1: readoutPriority: monitored softwareTrigger: false +daq_stream2: + description: Standard DAQ preview stream from first server + deviceClass: tomcat_bec.devices.gigafrost.stddaq_preview.StdDaqPreview + deviceConfig: + url: 'tcp://129.129.95.40:20001' + deviceTags: + - std-daq + enabled: true + onFailure: buffer + readOnly: false + readoutPriority: monitored + softwareTrigger: false + + diff --git a/tomcat_bec/devices/gigafrost/gigafrostclient.py b/tomcat_bec/devices/gigafrost/gigafrostclient.py index a53f6d2..5898f97 100644 --- a/tomcat_bec/devices/gigafrost/gigafrostclient.py +++ b/tomcat_bec/devices/gigafrost/gigafrostclient.py @@ -198,8 +198,8 @@ class GigaFrostClient(PSIDetectorBase): return old, new def stage(self): - px_daq_h = self.daq.config.cfg_image_pixel_height.get() - px_daq_w = self.daq.config.cfg_image_pixel_width.get() + px_daq_h = self.daq.config.cfg_pixel_height.get() + px_daq_w = self.daq.config.cfg_pixel_width.get() px_gf_w = self.cam.cfgRoiX.get() px_gf_h = self.cam.cfgRoiY.get() diff --git a/tomcat_bec/devices/gigafrost/stddaq_preview.py b/tomcat_bec/devices/gigafrost/stddaq_preview.py index 832a6a3..4b11978 100644 --- a/tomcat_bec/devices/gigafrost/stddaq_preview.py +++ b/tomcat_bec/devices/gigafrost/stddaq_preview.py @@ -43,7 +43,7 @@ class StdDaqPreview(Device): # pylint: disable=too-many-instance-attributes # Subscriptions for plotting image - SUB_MONITOR = "monitor" + SUB_MONITOR = "device_monitor_2d" _default_sub = SUB_MONITOR # Status attributes @@ -229,7 +229,7 @@ class StdDaqPreviewDetector(PSIDetectorBase): cam_widget = gui.add_dock('cam_dock1').add_widget('BECFigure').image('daq_stream1') """ # Subscriptions for plotting image - SUB_MONITOR = "monitor" + SUB_MONITOR = "device_monitor_2d" _default_sub = SUB_MONITOR custom_prepare_cls = StdDaqPreviewMixin diff --git a/tomcat_bec/devices/gigafrost/stddaq_rest.py b/tomcat_bec/devices/gigafrost/stddaq_rest.py index b00b9ae..7f21e94 100644 --- a/tomcat_bec/devices/gigafrost/stddaq_rest.py +++ b/tomcat_bec/devices/gigafrost/stddaq_rest.py @@ -10,7 +10,6 @@ from time import sleep from ophyd import Device, Signal, Component, Kind import requests - try: from bec_lib import bec_logger logger = bec_logger.logger @@ -19,25 +18,37 @@ except ModuleNotFoundError: logger = logging.getLogger("GfCam") - class StdDaqRestClient(Device): """Wrapper class around the new StdDaq REST interface. - This was meant to replace or extend the websocket inteface that replaced - the documented python client. We can finally read configuration through - standard HTTP requests, although the secondary server is not reachable - at the time. + This was meant to extend the websocket inteface that replaced the documented + python client. It is used as a part of the StdDaqClient aggregator class. + Good to know that the stdDAQ restarts all services after reconfiguration. + + The standard DAQ configuration is a single JSON file locally autodeployed + to the DAQ servers (as root!!!). It can only be written through the REST API + via standard HTTP requests. The DAQ might be distributed across several servers, + we'll only interface with the primary REST interface will synchronize with + all secondary REST servers. In the past this was a source of problems. + + Example: + ''' + daqcfg = StdDaqRestClient(name="daqcfg", rest_url="http://xbl-daq-29:5000") + ''' """ # pylint: disable=too-many-instance-attributes + USER_ACCESS = ["write_daq_config"] + _config_read = False + # Status attributes rest_url = Component(Signal, kind=Kind.config) cfg_detector_name = Component(Signal, kind=Kind.config) cfg_detector_type = Component(Signal, kind=Kind.config) cfg_n_modules = Component(Signal, kind=Kind.config) cfg_bit_depth = Component(Signal, kind=Kind.config) - cfg_image_pixel_height = Component(Signal, kind=Kind.config) - cfg_image_pixel_width = Component(Signal, kind=Kind.config) + cfg_pixel_height = Component(Signal, kind=Kind.config) + cfg_pixel_width = Component(Signal, kind=Kind.config) cfg_start_udp_port = Component(Signal, kind=Kind.config) cfg_writer_user_id = Component(Signal, kind=Kind.config) cfg_submodule_info = Component(Signal, kind=Kind.config) @@ -46,8 +57,6 @@ class StdDaqRestClient(Device): cfg_module_sync_queue_size = Component(Signal, kind=Kind.config) cfg_module_positions = Component(Signal, kind=Kind.config) - _config_read = False - def __init__( self, *args, rest_url: str = "http://localhost:5000", parent: Device = None, **kwargs ) -> None: @@ -58,7 +67,7 @@ class StdDaqRestClient(Device): # Connect ro the DAQ and initialize values self.read_daq_config() - def read_daq_config(self): + def read_daq_config(self) -> dict: """Read the current configuration from the JSON file """ r = requests.get( @@ -75,8 +84,8 @@ class StdDaqRestClient(Device): self.cfg_n_modules.set(cfg['n_modules']).wait() self.cfg_bit_depth.set(cfg['bit_depth']).wait() - self.cfg_image_pixel_height.set(cfg['image_pixel_height']).wait() - self.cfg_image_pixel_width.set(cfg['image_pixel_width']).wait() + self.cfg_pixel_height.set(cfg['image_pixel_height']).wait() + self.cfg_pixel_width.set(cfg['image_pixel_width']).wait() self.cfg_start_udp_port.set(cfg['start_udp_port']).wait() self.cfg_writer_user_id.set(cfg['writer_user_id']).wait() #self.cfg_submodule_info.set(cfg['submodule_info']).wait() @@ -88,14 +97,14 @@ class StdDaqRestClient(Device): self._config_read = True return r - def _build_config(self): + def _build_config(self) -> dict: config = { 'detector_name': str(self.cfg_detector_name.get()), 'detector_type': str(self.cfg_detector_type.get()), 'n_modules': int(self.cfg_n_modules.get()), 'bit_depth': int(self.cfg_bit_depth.get()), - 'image_pixel_height': int(self.cfg_image_pixel_height.get()), - 'image_pixel_width': int(self.cfg_image_pixel_width.get()), + 'image_pixel_height': int(self.cfg_pixel_height.get()), + 'image_pixel_width': int(self.cfg_pixel_width.get()), 'start_udp_port': int(self.cfg_start_udp_port.get()), 'writer_user_id': int(self.cfg_writer_user_id.get()), 'log_level': "debug", @@ -139,6 +148,7 @@ class StdDaqRestClient(Device): pixel_height : int, optional Image size in the y-direction [pixels] (default = 2016) """ + # Reads the current config old = self.read_configuration() # If Bluesky style configure @@ -150,13 +160,14 @@ class StdDaqRestClient(Device): pixel_width = d.get('image_width', pixel_width) pixel_height = d.get('image_height', pixel_height) - self.cfg_image_pixel_height.set(pixel_height).wait() - self.cfg_image_pixel_width.set(pixel_width).wait() + if self.cfg_pixel_height.get()!=pixel_height or self.cfg_pixel_width.get() != pixel_width: + self.cfg_pixel_height.set(pixel_height).wait() + self.cfg_pixel_width.set(pixel_width).wait() - self.write_daq_config() - logger.info(f"[{self.name}] Reconfigured the StdDAQ") - # No feedback on restart, we just sleep - sleep(3) + self.write_daq_config() + logger.info(f"[{self.name}] Reconfigured the StdDAQ") + # No feedback on restart, we just sleep + sleep(3) new = self.read_configuration() return old, new @@ -175,14 +186,12 @@ class StdDaqRestClient(Device): self.read_daq_config() return super().stage() - def unstage(self): """Unstage op: Read the current configuration from the DAQ """ self.read_daq_config() return super().unstage() - def stop(self, success=False): """Stop op: Read the current configuration from the DAQ """ diff --git a/tomcat_bec/devices/gigafrost/stddaq_ws.py b/tomcat_bec/devices/gigafrost/stddaq_ws.py index 78ea0e7..0d8fcb4 100644 --- a/tomcat_bec/devices/gigafrost/stddaq_ws.py +++ b/tomcat_bec/devices/gigafrost/stddaq_ws.py @@ -19,25 +19,28 @@ except ModuleNotFoundError: from tomcat_bec.devices.gigafrost.stddaq_rest import StdDaqRestClient - - class StdDaqClient(Device): """StdDaq API - This class combines the new websocket and REST interfaces that were meant - to replace the documented python client. The websocket interface starts - and stops the acquisition and provides status, while the REST interface - can read and write the configuration. + This class combines the new websocket and REST interfaces of the stdDAQ that + were meant to replace the documented python client. The websocket interface + starts and stops the acquisition and provides status, while the REST + interface can read and write the configuration. The DAQ needs to restart + all services to reconfigure with a new config. + + The websocket provides status updates about a running acquisition but the + interface breaks connection at the end of the run. The standard DAQ configuration is a single JSON file locally autodeployed - to the DAQ servers (as root!!!). It can only be written through a primary - REST API that is semi-supported, as there's no frontend group. The DAQ - might be distributed across several servers, meaning that the primary REST - interface will try to synchronize with secondary REST servers, but this - might fail, yielding a flawed configuration. - - daq = StdDaqWsClient(name="daq", ws_url="ws://xbl-daq-29:8080", rest_url="http://xbl-daq-29:5000") + to the DAQ servers (as root!!!). It can only be written through a REST API + that is semi-supported. The DAQ might be distributed across several servers, + we'll only interface with the primary REST interface will synchronize with + all secondary REST servers. In the past this was a source of problems. + Example: + ''' + daq = StdDaqClient(name="daq", ws_url="ws://xbl-daq-29:8080", rest_url="http://xbl-daq-29:5000") + ''' """ # pylint: disable=too-many-instance-attributes @@ -45,7 +48,7 @@ class StdDaqClient(Device): status = Component(Signal, value="unknown", kind=Kind.normal) n_total = Component(Signal, value=10000, kind=Kind.config) file_path = Component(Signal, value="/gpfs/test/test-beamline", kind=Kind.config) - + # Configuration config = Component(StdDaqRestClient, kind=Kind.config) def __init__( @@ -70,8 +73,8 @@ class StdDaqClient(Device): def connect(self): """Connect to te StDAQs websockets interface - StdDAQ may reject connection for a few seconds, so if it fails, wait - a bit and try to connect again. + StdDAQ may reject connection for a few seconds after restart, + so if it fails, wait a bit and try to connect again. """ num_retry = 0 while num_retry < 5: @@ -113,7 +116,6 @@ class StdDaqClient(Device): backend speed). (default = 10000) file_path : string, optional Save file path. (default = '/gpfs/test/test-beamline') - """ old_config = self.read_configuration() @@ -157,7 +159,6 @@ class StdDaqClient(Device): self._mon = Thread(target=self.poll, daemon=True) self._mon.start() - sleep(3) return super().stage() def unstage(self): @@ -208,9 +209,13 @@ class StdDaqClient(Device): return reply def poll(self): - """Monitor status messages until connection is open""" + """Monitor status messages until connection is open + + This will block the reply monitoring to calling unstage() might throw. + Status updates are sent every 1 seconds + """ try: - sleep(0.1) + sleep(1.2) for msg in self._client: try: message = json.loads(msg) @@ -220,6 +225,8 @@ class StdDaqClient(Device): except Exception as ex: print(ex) return + except (ConnectionClosedError, ConnectionClosedOK): + return finally: self._mon = None