Seems to be working configuration
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()}'")
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user