Seems to be working configuration

This commit is contained in:
gac-x05la
2024-08-29 16:34:23 +02:00
committed by mohacsi_i
parent ae958c010e
commit 0c8c60cfe3
9 changed files with 120 additions and 117 deletions

View File

@@ -1,7 +1,7 @@
eyex:
readoutPriority: baseline
description: X-ray eye translation x
deviceClass: ophyd.EpicsMotor
description: X-ray eye axis X
deviceClass: tomcat_bec.devices.psimotor.EpicsMotorMR
deviceConfig:
prefix: MTEST-X05LA-ES2-XRAYEYE:M1
deviceTags:
@@ -12,8 +12,8 @@ eyex:
softwareTrigger: false
eyey:
readoutPriority: baseline
description: X-ray eye translation y
deviceClass: ophyd.EpicsMotor
description: X-ray eye axis Y
deviceClass: tomcat_bec.devices.psimotor.EpicsMotorMR
deviceConfig:
prefix: MTEST-X05LA-ES2-XRAYEYE:M2
deviceTags:
@@ -24,8 +24,8 @@ eyey:
softwareTrigger: false
eyez:
readoutPriority: baseline
description: X-ray eye translation z
deviceClass: ophyd.EpicsMotor
description: X-ray eye axis Z
deviceClass: tomcat_bec.devices.psimotor.EpicsMotorEC
deviceConfig:
prefix: MTEST-X05LA-ES2-XRAYEYE:M3
deviceTags:
@@ -93,7 +93,7 @@ femto_mean_curr:
# softwareTrigger: true
gfclient:
description: GigaFrost camera controls
description: GigaFrost camera client
deviceClass: tomcat_bec.devices.GigaFrostClient
deviceConfig:
prefix: 'X02DA-CAM-GF2:'
@@ -110,8 +110,8 @@ gfclient:
softwareTrigger: true
daq_stream0:
description: Standard DAQ preview stream 2 frames every 1000 image
deviceClass: tomcat_bec.devices.StdDaqPreview
description: stdDAQ preview (2 every 555)
deviceClass: tomcat_bec.devices.StdDaqPreviewDetector
deviceConfig:
url: 'tcp://129.129.95.38:20002'
deviceTags:
@@ -123,8 +123,8 @@ daq_stream0:
softwareTrigger: false
daq_stream1:
description: Standard DAQ preview stream 4 frames at 10 Hz
deviceClass: tomcat_bec.devices.StdDaqPreview
description: stdDAQ preview (4 at 10 Hz)
deviceClass: tomcat_bec.devices.StdDaqPreviewDetector
deviceConfig:
url: 'tcp://129.129.95.38:20001'
deviceTags:
@@ -134,18 +134,3 @@ daq_stream1:
readOnly: false
readoutPriority: monitored
softwareTrigger: false
daq_stream2:
description: Standard DAQ preview stream from first server
deviceClass: tomcat_bec.devices.StdDaqPreview
deviceConfig:
url: 'tcp://129.129.95.40:20001'
deviceTags:
- std-daq
enabled: true
onFailure: buffer
readOnly: false
readoutPriority: monitored
softwareTrigger: false

View File

@@ -12,4 +12,4 @@ from .grashopper_tomcat import GrashopperTOMCAT
from .psimotor import EpicsMotorMR, EpicsMotorEC
from .gigafrost.gigafrostclient import GigaFrostClient
from .gigafrost.stddaq_preview import StdDaqPreview
from .gigafrost.stddaq_preview import StdDaqPreview, StdDaqPreviewDetector

View File

@@ -149,8 +149,14 @@ class GigaFrostCameraMixin(CustomDetectorMixin):
It must be safe to assume that the device is ready for the scan
to start immediately once this function is finished.
"""
# Either an acquisition is running or it's already done
if self.parent.infoBusyFlag.value:
raise RuntimeError("Camera is already busy, unstage it first!")
logger.warn("Camera is already busy, unstage it first!")
self.parent.unstage()
sleep(0.5)
# Sync if out of sync
if self.parent.infoSyncFlag.value == 0:
self.parent.cmdSyncHw.set(1).wait()
# Switch to acquiring
self.parent.cmdStartCamera.set(1).wait()
self.parent.state.put(const.GfStatus.ACQUIRING, force=True)

View File

@@ -42,7 +42,8 @@ class GigaFrostClientMixin(CustomDetectorMixin):
to start immediately once this function is finished.
"""
# Gigafrost can finish a run without explicit unstaging
self.parent._staged = Staged.no
if self.parent._staged:
self.parent.unstage()
#self.parent.daq.stage()
#self.parent.cam.stage()

View File

@@ -167,8 +167,13 @@ class StdDaqPreviewMixin(CustomDetectorMixin):
Parent class: CustomDetectorMixin
"""
_mon = None
def on_stage(self):
"""Start listening for preview data stream"""
if self._mon is not None:
self.parent.unstage()
sleep(0.5)
self.parent.connect()
self._stop_polling = False
self._mon = Thread(target=self.poll, daemon=True)
@@ -176,8 +181,13 @@ class StdDaqPreviewMixin(CustomDetectorMixin):
def on_unstage(self):
"""Stop a running preview"""
self._stop_polling = True
if self._mon is not None:
self._stop_polling = True
# Might hang on recv_multipart
self._mon.join(timeout=1)
# So also disconnect the socket
self.parent._socket.disconnect()
def on_stop(self):
"""Stop a running preview"""
self.on_unstage()
@@ -191,27 +201,39 @@ class StdDaqPreviewMixin(CustomDetectorMixin):
try:
# Exit loop and finish monitoring
if self._stop_polling:
logger.info(f"[{self.parent.name}]\tDetaching monitor")
break
# pylint: disable=no-member
meta, data = self.parent._socket.recv_multipart(flags=zmq.NOBLOCK)
header = json.loads(meta)
if header["type"]=="uint16":
image = np.frombuffer(data, dtype=np.uint16)
image = image.reshape(header['shape'])
r = self.parent._socket.recv_multipart(flags=zmq.NOBLOCK)
if len(r)==2:
meta, data = r
else:
sleep(0.1)
continue
# Update image and update subscribers
t_curr = time()
t_elapsed = t_curr - t_last
if t_elapsed > self.parent.throttle.get():
header = json.loads(meta)
if header["type"]=="uint16":
image = np.frombuffer(data, dtype=np.uint16)
if image.size != np.prod(header['shape']):
err = f"Unexpected array size of {image.size} for header: {header}"
raise ValueError(err)
image = image.reshape(header['shape'])
# Update image and update subscribers
self.parent.frame.put(header['frame'], force=True)
self.parent.image_shape.put(header['shape'], force=True)
self.parent.image.put(image, force=True)
self.parent._run_subs(sub_type=self.parent.SUB_MONITOR, value=image)
t_last=t_curr
name = self.parent.name
nfo = f"[{name}]\tFrameNo: {header['frame']}\tMean: {np.mean(image)}"
logger.info(nfo)
logger.info(
f"[{self.parent.name}] Updated frame {header['frame']}\t"
f"Shape: {header['shape']}\tMean: {np.mean(image):.3f}"
)
except ValueError:
# Happens when ZMQ partially delivers the multipart message
pass

View File

@@ -70,8 +70,8 @@ class StdDaqRestClient(Device):
except Exception as ex:
logger.error(f"Failed to connect to the StdDAQ REST API\n{ex}")
def read_daq_config(self) -> dict:
"""Read the current configuration from the JSON file
def get_daq_config(self) -> dict:
"""Read the current configuration from the DAQ
"""
r = requests.get(
self.rest_url.get() + '/api/config/get',
@@ -80,8 +80,12 @@ class StdDaqRestClient(Device):
)
if r.status_code != 200:
raise ConnectionError(f"[{self.name}] Error {r.status_code}:\t{r.text}")
return r.json()
cfg = r.json()
def read_daq_config(self) -> None:
"""Extract the current configuration from the JSON file
"""
cfg = self.get_daq_config()
self.cfg_detector_name.set(cfg['detector_name']).wait()
self.cfg_detector_type.set(cfg['detector_type']).wait()
@@ -98,9 +102,8 @@ class StdDaqRestClient(Device):
#self.cfg_module_positions.set(cfg['module_positions']).wait()
self._config_read = True
return r
def _build_config(self) -> dict:
def _build_config(self, orig) -> dict:
config = {
'detector_name': str(self.cfg_detector_name.get()),
'detector_type': str(self.cfg_detector_type.get()),
@@ -110,13 +113,15 @@ class StdDaqRestClient(Device):
'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",
'log_level': "info",
'submodule_info': {},
'max_number_of_forwarders_spawned': int(self.cfg_max_number_of_forwarders.get()),
'use_all_forwarders': bool(self.cfg_use_all_forwarders.get()),
'module_sync_queue_size': int(self.cfg_module_sync_queue_size.get()),
'module_positions': {}
'module_positions': {},
'number_of_writers': 14
}
config = orig.update(config)
return config
def write_daq_config(self):
@@ -126,7 +131,8 @@ class StdDaqRestClient(Device):
if not self._config_read:
raise RuntimeError("Pleae read config before editing")
config = self._build_config()
orig = self.get_daq_config()
config = self._build_config(orig)
#params = {"user": "ioc", "config_file": "/etc/std_daq/configs/gf1.json"}
params = {"user": "ioc"}
@@ -153,7 +159,7 @@ class StdDaqRestClient(Device):
"""
# Reads the current config
old = self.read_configuration()
self.read_daq_config()
# If Bluesky style configure
if d is not None:
# Only reconfigure if we're instructed

View File

@@ -144,19 +144,24 @@ class StdDaqClient(Device):
by calling unstage. So it might start from an already running state or
not, we can't query if not running.
"""
if self._staged:
self.unstage()
self._client.close()
file_path = self.file_path.get()
n_total = self.n_total.get()
message = {"command": "start", "path": file_path, "n_image": n_total}
reply = self.message(message)
reply = json.loads(reply)
if reply["status"] in ("creating_file"):
self.status.put(reply["status"], force=True)
elif reply["status"] in ("rejected"):
raise RuntimeError(
f"Start StdDAQ command rejected (might be already running): {reply['reason']}"
)
if reply is not None:
reply = json.loads(reply)
if reply["status"] in ("creating_file"):
self.status.put(reply["status"], force=True)
elif reply["status"] in ("rejected"):
raise RuntimeError(
f"Start StdDAQ command rejected (might be already running): {reply['reason']}"
)
self._mon = Thread(target=self.poll, daemon=True)
self._mon.start()

View File

@@ -9,7 +9,9 @@ detailed interface for motors using the new ECMC-based motion systems at PSI.
import warnings
from ophyd import Component, EpicsMotor, EpicsSignal, EpicsSignalRO, Kind
from ophyd.status import MoveStatus
from ophyd.status import DeviceStatus, MoveStatus
from ophyd.utils.errors import UnknownStatusFailure
from ophyd.utils.epics_pvs import AlarmSeverity
class SpmgStates:
@@ -21,6 +23,7 @@ class SpmgStates:
GO = 3
class EpicsMotorMR(EpicsMotor):
""" Extended EPICS Motor class
@@ -28,39 +31,16 @@ class EpicsMotorMR(EpicsMotor):
It extends EpicsMotor base class to provide some simple status checks
before movement.
"""
tolerated_alarm = AlarmSeverity.INVALID
SUB_PROGRESS = "progress"
motor_deadband = Component(
EpicsSignalRO, ".RDBD", auto_monitor=True, kind=Kind.config)
motor_mode = Component(
EpicsSignal, ".SPMG", auto_monitor=True, put_complete=True, kind=Kind.omitted)
_start_position = None
_target_position = None
# pylint: disable=too-many-arguments
def __init__(
self,
prefix="",
*,
name,
kind=None,
read_attrs=None,
configuration_attrs=None,
parent=None,
**kwargs,
):
super().__init__(
prefix=prefix,
name=name,
kind=kind,
read_attrs=read_attrs,
configuration_attrs=configuration_attrs,
parent=parent,
**kwargs,
)
self.subscribe(self._progress_update, run=False)
motor_status = Component(
EpicsSignal, ".STAT", auto_monitor=True, kind=Kind.omitted)
motor_enable = Component(
EpicsSignal, ".CNEN", auto_monitor=True, kind=Kind.omitted)
def move(self, position, wait=True, **kwargs) -> MoveStatus:
""" Extended move function with a few sanity checks
@@ -70,29 +50,25 @@ class EpicsMotorMR(EpicsMotor):
# Reset SPMG before move
spmg = self.motor_mode.get()
if spmg != SpmgStates.GO:
self.motor_mode.put(SpmgStates.GO).wait()
#Warni if trying to move beyond an active limit
if self.high_limit_switch and position > self.position:
warnings.warn("Attempting to move above active HLS", RuntimeWarning)
if self.low_limit_switch and position < self.position:
warnings.warn("Attempting to move below active LLS", RuntimeWarning)
self.motor_mode.set(SpmgStates.GO).wait()
# Warn if EPIC motorRecord claims an error
status = self.motor_status.get()
if status:
warnings.warn(f"EPICS MotorRecord is in alarm state {status}, ophyd will raise", RuntimeWarning)
# Warni if trying to move beyond an active limit
# if self.high_limit_switch and position > self.position:
# warnings.warn("Attempting to move above active HLS", RuntimeWarning)
# if self.low_limit_switch and position < self.position:
# warnings.warn("Attempting to move below active LLS", RuntimeWarning)
self._start_position = self.position
self._target_position = position
try:
status = super().move(position, wait, **kwargs)
return status
except UnknownStatusFailure:
status = DeviceStatus(self)
status.set_finished()
return status
return super().move(position, wait, **kwargs)
def _progress_update(self, value, **kwargs) -> None:
"""Progress update on the current movement"""
if (self._start_position is None) or (self._target_position is None) or (not self.moving):
self._run_subs(sub_type=self.SUB_PROGRESS, value=1, max_value=1, done=1)
return
progress = abs(
(value - self._start_position) / (self._target_position - self._start_position)
)
self._run_subs(
sub_type=self.SUB_PROGRESS, value=progress, max_value=1, done=self.moving)
class EpicsMotorEC(EpicsMotorMR):
@@ -101,7 +77,6 @@ class EpicsMotorEC(EpicsMotorMR):
Special motor class to provide additional functionality for ECMC based motors.
It exposes additional diagnostic fields and includes basic error management.
"""
USER_ACCESS = ['reset']
enable_readback = Component(EpicsSignalRO, "-EnaAct", auto_monitor=True, kind=Kind.normal)
enable = Component(
@@ -113,7 +88,7 @@ class EpicsMotorEC(EpicsMotorMR):
#high_interlock = Component(EpicsSignalRO, "-SumIlockFwd", auto_monitor=True, kind=Kind.normal)
#low_interlock = Component(EpicsSignalRO, "-SumIlockBwd", auto_monitor=True, kind=Kind.normal)
ecmc_status = Component(EpicsSignalRO, "-Status", auto_monitor=True, kind=Kind.normal)
#ecmc_status = Component(EpicsSignalRO, "-Status", auto_monitor=True, kind=Kind.normal)
error = Component(EpicsSignalRO, "-ErrId", auto_monitor=True, kind=Kind.normal)
error_msg = Component(EpicsSignalRO, "-MsgTxt", auto_monitor=True, kind=Kind.normal)
error_reset = Component(EpicsSignal, "-ErrRst", put_complete=True, kind=Kind.omitted)
@@ -123,7 +98,7 @@ class EpicsMotorEC(EpicsMotorMR):
Note that the default EpicsMotor only supports the 'GO' movement mode.
"""
# Reset SPMG before move
# Check ECMC error status before move
error = self.error.get()
if error:
raise RuntimeError(f"Motor is in error state with message: '{self.error_msg.get()}'")
@@ -146,4 +121,4 @@ class EpicsMotorEC(EpicsMotorMR):
self.error_reset.set(1, settle_time=0.1).wait()
# Check if it disappeared
if self.error.get():
raise RuntimeError(f"Failed to reset axis error: '{self.error_msg.get()}'")
raise RuntimeError(f"Failed to reset axis, error still present: '{self.error_msg.get()}'")

View File

@@ -1,5 +1,4 @@
import time
import numpy as np
from bec_lib import bec_logger
@@ -87,12 +86,16 @@ class GigaFrostStepScan(AsyncFlyScanBase):
yield from self.stubs.pre_scan()
def stage(self):
yield from self.stubs.send_rpc_and_wait(
"gf2", "configure", {"nimages": self.scan_exp_b, "exposure": self.scan_exp_t, "period": self.scan_exp_p, "roix": 480, "roiy": 128}
)
yield from self.stubs.send_rpc_and_wait(
"daq", "configure", {"n_images": self.scan_steps * self.scan_exp_b}
)
d= {
"ntotal": self.scan_steps * self.scan_exp_b,
"nimages": self.scan_exp_b,
"exposure": self.scan_exp_t,
"period": self.scan_exp_p,
"pixel_width": 480,
"pixel_height": 128
}
yield from self.stubs.send_rpc_and_wait("gfclient", "configure", d)
# For god, NO!
yield from super().stage()
def scan_core(self):
@@ -101,7 +104,7 @@ class GigaFrostStepScan(AsyncFlyScanBase):
print(f"Point: {ii}")
st = yield from self.stubs.send_rpc_and_wait(self.scan_motors[0], "move", self.positions[ii])
st.wait()
st = yield from self.stubs.send_rpc_and_wait("gf2", "trigger")
st = yield from self.stubs.send_rpc_and_wait("gfclient", "trigger")
st.wait()
self.pointID += 1
time.sleep(0.2)