GFclient instantiates but camera is not yet running
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user