GFclient instantiates but camera is not yet running

This commit is contained in:
gac-x05la
2024-07-26 12:17:50 +02:00
committed by mohacsi_i
parent 780e64d81d
commit 51cfae82c4
4 changed files with 120 additions and 67 deletions

View File

@@ -223,15 +223,15 @@ class GigaFrostCamera(PSIDetectorBase):
infoBusyFlag = Component(EpicsSignalRO, "BUSY_STAT", auto_monitor=True)
infoSyncFlag = Component(EpicsSignalRO, "SYNC_FLAG", auto_monitor=True)
cmdSyncHw = Component(EpicsSignal, "SYNC_SWHW.PROC", put_complete=True)
cmdStartCamera = Component(EpicsSignal, "START_CAM", put_complete=True)
cmdSetParam = Component(EpicsSignal, "SET_PARAM.PROC", put_complete=True)
cmdSyncHw = Component(EpicsSignal, "SYNC_SWHW.PROC", put_complete=True, kind=Kind.omitted)
cmdStartCamera = Component(EpicsSignal, "START_CAM", put_complete=True, kind=Kind.omitted)
cmdSetParam = Component(EpicsSignal, "SET_PARAM.PROC", put_complete=True, kind=Kind.omitted)
# UDP header
cfgUdpNumPorts = Component(EpicsSignal, "PORTS", put_complete=True, kind=Kind.config)
cfgUdpNumFrames = Component(EpicsSignal, "FRAMENUM", put_complete=True, kind=Kind.config)
cfgUdpHtOffset = Component(EpicsSignal, "HT_OFFSET", put_complete=True, kind=Kind.config)
cmdWriteService = Component(EpicsSignal, "WRITE_SRV.PROC", put_complete=True)
cmdWriteService = Component(EpicsSignal, "WRITE_SRV.PROC", put_complete=True, kind=Kind.omitted)
# Standard camera configs
cfgExposure = Component(EpicsSignal, "EXPOSURE", put_complete=True, auto_monitor=True, kind=Kind.config)
@@ -244,7 +244,7 @@ class GigaFrostCamera(PSIDetectorBase):
# Software signals
cmdSoftEnable = Component(EpicsSignal, "SOFT_ENABLE", put_complete=True)
cmdSoftTrigger = Component(EpicsSignal, "SOFT_TRIG.PROC", put_complete=True)
cmdSoftTrigger = Component(EpicsSignal, "SOFT_TRIG.PROC", put_complete=True, kind=Kind.omitted)
cmdSoftExposure = Component(EpicsSignal, "SOFT_EXP", put_complete=True)
# Trigger configuration PVs
@@ -349,15 +349,15 @@ class GigaFrostCamera(PSIDetectorBase):
)
# HW settings as read only
cfgSyncFlag = Component(EpicsSignalRO, "PIXRATE", auto_monitor=True)
cfgTrigDelay = Component(EpicsSignalRO, "TRIG_DELAY", auto_monitor=True)
cfgSyncoutDelay = Component(EpicsSignalRO, "SYNCOUT_DLY", auto_monitor=True)
cfgOutputPolarity0 = Component(EpicsSignalRO, "BNC0_RBV", auto_monitor=True)
cfgOutputPolarity1 = Component(EpicsSignalRO, "BNC1_RBV", auto_monitor=True)
cfgOutputPolarity2 = Component(EpicsSignalRO, "BNC2_RBV", auto_monitor=True)
cfgOutputPolarity3 = Component(EpicsSignalRO, "BNC3_RBV", auto_monitor=True)
cfgInputPolarity1 = Component(EpicsSignalRO, "BNC4_RBV", auto_monitor=True)
cfgInputPolarity2 = Component(EpicsSignalRO, "BNC5_RBV", auto_monitor=True)
cfgSyncFlag = Component(EpicsSignalRO, "PIXRATE", auto_monitor=True, kind=Kind.config)
cfgTrigDelay = Component(EpicsSignalRO, "TRIG_DELAY", auto_monitor=True, kind=Kind.config)
cfgSyncoutDelay = Component(EpicsSignalRO, "SYNCOUT_DLY", auto_monitor=True, kind=Kind.config)
cfgOutputPolarity0 = Component(EpicsSignalRO, "BNC0_RBV", auto_monitor=True, kind=Kind.config)
cfgOutputPolarity1 = Component(EpicsSignalRO, "BNC1_RBV", auto_monitor=True, kind=Kind.config)
cfgOutputPolarity2 = Component(EpicsSignalRO, "BNC2_RBV", auto_monitor=True, kind=Kind.config)
cfgOutputPolarity3 = Component(EpicsSignalRO, "BNC3_RBV", auto_monitor=True, kind=Kind.config)
cfgInputPolarity1 = Component(EpicsSignalRO, "BNC4_RBV", auto_monitor=True, kind=Kind.config)
cfgInputPolarity2 = Component(EpicsSignalRO, "BNC5_RBV", auto_monitor=True, kind=Kind.config)
infoBoardTemp = Component(EpicsSignalRO, "T_BOARD", auto_monitor=True)
USER_ACCESS = ["exposure_mode", "fix_nframes_mode", "trigger_mode", "enable_mode"]
@@ -413,6 +413,7 @@ class GigaFrostCamera(PSIDetectorBase):
def configure(
self,
d: dict=None,
nimages=10,
exposure=0.2,
period=1.0,
@@ -452,8 +453,7 @@ class GigaFrostCamera(PSIDetectorBase):
* 5: Apply the full linearity correction
"""
# If Bluesky style configure
if isinstance(nimages, dict):
d = nimages.copy()
if d is not None:
nimages = d.get('nimages', 10)
exposure = d.get('exposure', exposure)
period = d.get('period', period)

View File

@@ -217,6 +217,13 @@ class GigaFrostClient(PSIDetectorBase):
Backend url address necessary to set up the camera's udp header.
(default: http://xbl-daq-23:8080)
Usage:
----------
gf = GigaFrostClient(
"X02DA-CAM-GF2:", name="gf2", backend_url="http://xbl-daq-28:8080", auto_soft_enable=True,
daq_ws_url="ws://xbl-daq-29:8080", daq_rest_url="http://xbl-daq-29:5000"
)
Bugs:
----------
FRAMERATE : Ignored in soft trigger mode, period becomes 2xexposure time
@@ -224,7 +231,7 @@ class GigaFrostClient(PSIDetectorBase):
# pylint: disable=too-many-instance-attributes
cam = Component(GigaFrostCamera, prefix="X02DA-CAM-GF2:", name="cam")
#daq = Component(StdDaqWsClient, "")
daq = Component(StdDaqWsClient, name="daq")
def __init__(
self,
@@ -233,20 +240,19 @@ class GigaFrostClient(PSIDetectorBase):
name,
auto_soft_enable=False,
backend_url=const.BE999_DAFL_CLIENT,
daq_ws_url = "ws://localhost:8080",
daq_rest_url = "http://localhost:5000",
kind=None,
read_attrs=None,
configuration_attrs=None,
parent=None,
**kwargs,
):
self.__class__.__dict__["cam"].kwargs.update({'backend_url':"http://xbl-daq-28:8080",})
self.__class__.__dict__["cam"].kwargs.update({'auto_soft_enable': True})
self.__class__.__dict__["cam"].kwargs['backend_url'] = backend_url
self.__class__.__dict__["cam"].kwargs['auto_soft_enable'] = auto_soft_enable
self.__class__.__dict__["daq"].kwargs['ws_url'] = daq_ws_url
self.__class__.__dict__["daq"].kwargs['rest_url'] = daq_rest_url
#self.__class__.__dict__["daq"].__class__.__dict__["cfg"].kwargs['rest_url'] = daq_rest_url
super().__init__(
prefix=prefix,
@@ -299,11 +305,11 @@ 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.cfg.cfg_image_pixel_height.get()
px_daq_w = self.daq.cfg.cfg_image_pixel_width.get()
px_gf_h = self.cam.cfgRoiX.get()
px_gf_y = self.cam.cfgRoiY.get()
px_gf_w = self.cam.cfgRoiY.get()
if px_daq_h != px_gf_h or px_daq_w != px_gf_w:
raise RuntimeError(f"Different image size configured on GF and the DAQ")
@@ -315,6 +321,7 @@ class GigaFrostClient(PSIDetectorBase):
# Automatically connect to MicroSAXS testbench if directly invoked
if __name__ == "__main__":
gf = GigaFrostClient(
"X02DA-CAM-GF2:", name="gf2", backend_url="http://xbl-daq-28:8080", auto_soft_enable=True
"X02DA-CAM-GF2:", name="gf2", backend_url="http://xbl-daq-28:8080", auto_soft_enable=True,
daq_ws_url="ws://xbl-daq-29:8080", daq_rest_url="http://xbl-daq-29:5000"
)
gf.wait_for_connection()

View File

@@ -14,12 +14,12 @@ from ophyd import Device, Signal, Component, Kind
import requests
class StdDaqRestConfig(Device):
class StdDaqRestClient(Device):
"""Wrapper class around the new StdDaq REST interface.
This was meant to replace the websocket inteface that replaced the
documented python client. We can finally read configuration through
standard HTTP requests, although the secondary server is ot reachable
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.
"""
# pylint: disable=too-many-instance-attributes
@@ -39,12 +39,13 @@ class StdDaqRestConfig(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, url: str = "http://xbl-daq-29:5000", parent: Device = None, **kwargs
self, *args, rest_url: str = "http://localhost:5000", parent: Device = None, **kwargs
) -> None:
super().__init__(*args, parent=parent, **kwargs)
self._url_base = url
self._url_base = rest_url
# Connect ro the DAQ and initialize values
self.read_daq_config()
@@ -57,12 +58,31 @@ class StdDaqRestConfig(Device):
raise ConnectionError(f"[{self.name}] Error {r.status_code}:\t{r.text}")
cfg = r.json()
for key, val in cfg.items():
if isinstance(val, (int, float, str)):
getattr(self, "cfg_"+key).set(val).wait()
self.cfg_detector_name.set(cfg['detector_name']).wait()
self.cfg_detector_type.set(cfg['detector_type']).wait()
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_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()
self.cfg_max_number_of_forwarders_spawned.set(cfg['max_number_of_forwarders_spawned']).wait()
self.cfg_use_all_forwarders.set(cfg['use_all_forwarders']).wait()
self.cfg_module_sync_queue_size.set(cfg['module_sync_queue_size']).wait()
self.cfg_module_positions.set(cfg['module_positions']).wait()
self._config_read = True
return cfg
def write_daq_config(self):
"""Write configuration ased on current PV values. Some fields might be
unchangeable.
"""
if not self._config_read:
raise RuntimeError("Pleae read config before editing")
config = {
'detector_name': str(self.cfg_detector_name.get()),
'detector_type': str(self.cfg_detector_type.get()),
@@ -85,28 +105,30 @@ class StdDaqRestConfig(Device):
if r.status_code != 200:
raise ConnectionError(f"[{self.name}] Error {r.status_code}:\t{r.text}")
def read(self):
self.read_daq_config()
def stage(self) -> list:
"""Read the current configuration from the DAQ
"""Stage op: Read the current configuration from the DAQ
"""
self.read_daq_config()
return super().stage()
def unstage(self):
"""Read the current configuration from the DAQ
"""Unstage op: Read the current configuration from the DAQ
"""
self.read_daq_config()
return super().unstage()
def stop(self):
"""Read the current configuration from the DAQ
"""Stop op: Read the current configuration from the DAQ
"""
self.unstage()
# Automatically connect to MicroSAXS testbench if directly invoked
if __name__ == "__main__":
daqcfg = StdDaqRestConfig(name="daqcfg", url="http://xbl-daq-29:5000")
daqcfg = StdDaqRestClient(name="daqcfg", rest_url="http://xbl-daq-29:5000")
daqcfg.wait_for_connection()

View File

@@ -14,33 +14,49 @@ from ophyd import Device, Signal, Component, Kind
from websockets.sync.client import connect
from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError
try:
from stddaq_rest import StdDaqRestClient
except ModuleNotFoundError:
from tomcat_bec.devices.gigafrost.stddaq_rest import StdDaqRestClient
class StdDaqWsClient(Device):
"""Wrapper class around the StdDaq websocket interface.
"""StdDaq API
This was meant to replace the documented python client. We cannot read
or change the current configuration through this interface.
A bit more about the Standard DAQ configuration:
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.
The standard DAQ configuration is a single JSON file locally autodeployed
to the DAQ servers (as root!!!). Previously there was a service to offer
a REST API to write this file, but since there's no frontend group, this
is no longer available.
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")
"""
# pylint: disable=too-many-instance-attributes
# Status attributes
status = Component(Signal, value="unknown", kind=Kind.hinted)
n_images = Component(Signal, value=10000, kind=Kind.config)
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)
cfg = Component(StdDaqRestClient, kind=Kind.config)
def __init__(
self, *args, url: str = "ws://localhost:8080", parent: Device = None, **kwargs
self, *args, ws_url: str = "ws://localhost:8080", rest_url="http://localhost:5000", parent: Device = None, **kwargs
) -> None:
self.__class__.__dict__['cfg'].kwargs['rest_url'] = rest_url
super().__init__(*args, parent=parent, **kwargs)
self.status._metadata["write_access"] = False
self._ws_url = url
self._ws_url = ws_url
self._mon = None
# Connect ro the DAQ
@@ -58,13 +74,17 @@ class StdDaqWsClient(Device):
sleep(5)
self._client = connect(self._ws_url)
def __del__(self):
"""Try to close the socket"""
self._client.close_socket()
def monitor(self):
"""Attach monitoring to the DAQ"""
self._client = connect(self._ws_url)
self._mon = Thread(target=self.poll, daemon=True)
self._mon.start()
def configure(self, n_images: int = None, file_path: str = None) -> tuple:
def configure(self, d: dict=None, n_total: int = None, file_path: str = None) -> tuple:
"""Set the standard DAQ parameters for the next run
Note that full reconfiguration is not possible with the websocket
@@ -73,13 +93,13 @@ class StdDaqWsClient(Device):
Example:
----------
std.configure(n_images=10000, file_path="/data/test/raw")
std.configure(n_total=10000, file_path="/data/test/raw")
Parameters
----------
n_images : int, optional
Number of images to be taken during each scan. Set to -1 for an
unlimited number of images (limited by the ringbuffer size and
n_total : int, optional
Total number of images to be taken during each scan. Set to -1 for
an unlimited number of images (limited by the ringbuffer size and
backend speed). (default = 10000)
file_path : string, optional
Save file path. (default = '/gpfs/test/test-beamline')
@@ -87,13 +107,12 @@ class StdDaqWsClient(Device):
"""
old_config = self.read_configuration()
# If Bluesky style configure
if isinstance(n_images, dict):
d = n_images.copy()
n_images = d.get('n_images', None)
if d is not None:
n_total = d.get('n_total', None)
file_path = d.get('file_path', None)
if n_images is not None:
self.n_images.set(int(n_images))
if n_total is not None:
self.n_total.set(int(n_total))
if file_path is not None:
self.output_file.set(str(file_path))
@@ -108,9 +127,9 @@ class StdDaqWsClient(Device):
not, we can't query if not running.
"""
file_path = self.file_path.get()
n_image = self.n_images.get()
n_total = self.n_total.get()
message = {"command": "start", "path": file_path, "n_image": n_image}
message = {"command": "start", "path": file_path, "n_image": n_total}
reply = self.message(message)
reply = json.loads(reply)
@@ -186,7 +205,12 @@ class StdDaqWsClient(Device):
self._mon = None
class StdDaqClient(StdDaqWsClient):
"""Just an alias"""
pass
# Automatically connect to MicroSAXS testbench if directly invoked
if __name__ == "__main__":
daq = StdDaqWsClient(name="daq", url="ws://xbl-daq-29:8080")
daq = StdDaqWsClient(name="daq", ws_url="ws://xbl-daq-29:8080", rest_url="http://xbl-daq-29:5000")
daq.wait_for_connection()