Works with BEC v3
This commit is contained in:
@@ -71,7 +71,7 @@ es1_ismc:
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
|
||||
es1_tasks:
|
||||
@@ -157,7 +157,7 @@ gfdaq:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: true
|
||||
softwareTrigger: false
|
||||
|
||||
daq_stream0:
|
||||
description: stdDAQ preview (2 every 555)
|
||||
|
||||
@@ -20,6 +20,10 @@ logger = bec_logger.logger
|
||||
|
||||
|
||||
class AerotechDriveDataCollectionMixin(CustomDeviceMixin):
|
||||
"""Configuration and staging
|
||||
|
||||
NOTE: scripted scans start drive data collection internally
|
||||
"""
|
||||
# parent : aa1Tasks
|
||||
def on_stage(self) -> None:
|
||||
"""Configuration and staging"""
|
||||
@@ -48,10 +52,10 @@ class AerotechDriveDataCollectionMixin(CustomDeviceMixin):
|
||||
logger.warning(f"[{self.parent.name}] Configuring with:\n{d}")
|
||||
self.parent.configure(d=d)
|
||||
|
||||
# Stage the DDC distance module
|
||||
# Stage the data collection if not in internally launced mode
|
||||
# NOTE: Scripted scans start acquiring from the scrits
|
||||
if self.parent.scaninfo.scan_type not in ("script", "scripted"):
|
||||
self.parent._switch.set("Start", settle_time=0.2).wait()
|
||||
self.parent.bluestage()
|
||||
|
||||
def on_unstage(self):
|
||||
"""Standard bluesky unstage"""
|
||||
@@ -127,6 +131,10 @@ class aa1AxisDriveDataCollection(PSIDeviceBase):
|
||||
new = self.read_configuration()
|
||||
return (old, new)
|
||||
|
||||
def bluestage(self) -> None:
|
||||
"""Bluesky-style stage"""
|
||||
self._switch.set("Start", settle_time=0.2).wait()
|
||||
|
||||
def reset(self):
|
||||
"""Reset incremental readback"""
|
||||
self._switch.set("ResetRB", settle_time=0.1).wait()
|
||||
|
||||
@@ -21,11 +21,8 @@ class AerotechPsoDistanceMixin(CustomDeviceMixin):
|
||||
when not in use. I.e. this method is not expected to be called when
|
||||
PSO is not needed or when it'd conflict with other devices.
|
||||
"""
|
||||
# logger.warning(self.parent.scaninfo.scan_msg.info['kwargs'].keys())
|
||||
|
||||
|
||||
# Fish out configuration from scaninfo
|
||||
# NOTE: Scans don't have to fully configure the device, but it is expected that it's
|
||||
# Fish out configuration from scaninfo (does not need to be full configuration)
|
||||
scanparam = self.parent.scaninfo.scan_msg.info
|
||||
alias = self.parent.parent.name if self.parent.parent is not None else self.parent.name
|
||||
logger.warning(f"[{alias}] Scan parameters:\n{scanparam}")
|
||||
@@ -49,14 +46,7 @@ class AerotechPsoDistanceMixin(CustomDeviceMixin):
|
||||
self.parent.configure(d=d)
|
||||
|
||||
# Stage the PSO distance module
|
||||
if isinstance(self.parent._distance_value, (np.ndarray, list, tuple)):
|
||||
self.dstArrayRearm.set(1).wait()
|
||||
# Wait for polling
|
||||
sleep(0.5)
|
||||
# Start monitoring the counters if distance is valid
|
||||
if self.parent.dstDistanceVal.get() > 0:
|
||||
self.parent.dstEventsEna.set("On").wait()
|
||||
self.parent.dstCounterEna.set("On").wait()
|
||||
self.parent.bluestage()
|
||||
|
||||
def on_unstage(self):
|
||||
"""Standard bluesky unstage"""
|
||||
@@ -286,6 +276,19 @@ class aa1AxisPsoDistance(aa1AxisPsoBase):
|
||||
new = self.read_configuration()
|
||||
logger.info(f"[{self.name}] PSO configured to {pso_wavemode} mode")
|
||||
return (old, new)
|
||||
|
||||
def bluestage(self) -> None:
|
||||
"""Bluesky style stage"""
|
||||
# Stage the PSO distance module and zero counter
|
||||
if isinstance(self._distance_value, (np.ndarray, list, tuple)):
|
||||
self.dstArrayRearm.set(1).wait()
|
||||
# Wait for polling
|
||||
sleep(0.5)
|
||||
# Start monitoring the counters if distance is valid
|
||||
if self.dstDistanceVal.get() > 0:
|
||||
self.dstEventsEna.set("On").wait()
|
||||
self.dstCounterEna.set("On").wait()
|
||||
|
||||
|
||||
# # ########################################################################
|
||||
# # Bluesky flyer interface
|
||||
|
||||
@@ -91,6 +91,8 @@ program
|
||||
Dwell($fSettlingTimeSec)
|
||||
PsoEventGenerateSingle($axis)
|
||||
Dwell($fExposureTimeSec)
|
||||
PsoEventGenerateSingle($axis)
|
||||
|
||||
$iglobal[1] = $ii
|
||||
end
|
||||
|
||||
|
||||
@@ -57,20 +57,7 @@ class AerotechTasksMixin(CustomDeviceMixin):
|
||||
self.parent.configure(d=d)
|
||||
|
||||
# The actual staging
|
||||
settle_time = 0.2
|
||||
if self.parent._is_configured:
|
||||
if self.parent._text_to_execute is not None:
|
||||
status = self.parent._execute.set(self.parent._text_to_execute, settle_time=settle_time)
|
||||
else:
|
||||
status = self.parent.switch.set("Run", settle_time=settle_time)
|
||||
else:
|
||||
status = DeviceStatus(self.parent)
|
||||
status.set_finished()
|
||||
if settle_time is not None and settle_time > 0:
|
||||
sleep(settle_time)
|
||||
if self.parent._failure.value:
|
||||
raise RuntimeError("Failed to kick off task, please check the Aerotech IOC")
|
||||
return status
|
||||
self.parent.bluestage()
|
||||
|
||||
def on_unstage(self):
|
||||
"""Stop the currently selected task"""
|
||||
@@ -205,6 +192,25 @@ class aa1Tasks(PSIDeviceBase):
|
||||
new = self.read_configuration()
|
||||
return (old, new)
|
||||
|
||||
def bluestage(self) -> None:
|
||||
"""Bluesky style stage"""
|
||||
settle_time = 0.2
|
||||
if self._is_configured:
|
||||
if self.taskIndex.get() in (0, 1):
|
||||
logger.warning(f"[{self.name}] Woah, launching AeroScript on a system task. Daring today are we?")
|
||||
if self._text_to_execute is not None:
|
||||
status = self._execute.set(self._text_to_execute, settle_time=settle_time)
|
||||
else:
|
||||
status = self.switch.set("Run", settle_time=settle_time)
|
||||
else:
|
||||
status = DeviceStatus(self)
|
||||
status.set_finished()
|
||||
if settle_time is not None and settle_time > 0:
|
||||
sleep(settle_time)
|
||||
if self._failure.value:
|
||||
raise RuntimeError("Failed to kick off task, please check the Aerotech IOC")
|
||||
return status
|
||||
|
||||
##########################################################################
|
||||
# Bluesky flyer interface
|
||||
def complete(self) -> DeviceStatus:
|
||||
|
||||
@@ -58,7 +58,7 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
if len(d) > 0:
|
||||
# Stop if current status is not idle
|
||||
if self.parent.state() != "idle":
|
||||
self.parent.safestop()
|
||||
self.parent.surestop()
|
||||
# Configure new run (will restart the stdDAQ)
|
||||
logger.warning(f"[{self.parent.name}] Configuring with:\n{d}")
|
||||
self.parent.configure(d=d)
|
||||
@@ -74,7 +74,7 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
if reply is not None:
|
||||
reply = json.loads(reply)
|
||||
self.parent.status.set(reply["status"], force=True).wait()
|
||||
logger.info(f"[{self.parent.name}] Start DAQ reply: {reply['status']}")
|
||||
logger.info(f"[{self.parent.name}] Start DAQ reply: {reply}")
|
||||
# Give it more time to reconfigure
|
||||
if reply["status"] in ("rejected"):
|
||||
# FIXME: running exposure is a nogo
|
||||
@@ -99,19 +99,12 @@ class StdDaqMixin(CustomDeviceMixin):
|
||||
def on_unstage(self):
|
||||
""" Stop a running acquisition and close connection
|
||||
"""
|
||||
try:
|
||||
self.parent.message({"command": "stop_all"}, wait_reply=False)
|
||||
except (RuntimeError, TypeError):
|
||||
# The poller thread locks recv raising a RuntimeError
|
||||
pass
|
||||
finally:
|
||||
logger.debug(f"[{self.parent.name}] Deleting socket after unstage")
|
||||
self.parent._wsclient = None
|
||||
self.parent.surestop()
|
||||
|
||||
def on_stop(self):
|
||||
""" Stop a running acquisition and close connection
|
||||
"""
|
||||
self.parent.unstage()
|
||||
self.parent.surestop()
|
||||
|
||||
def poll(self) -> None:
|
||||
""" Monitor status messages while connection is open. This will block the reply monitoring
|
||||
@@ -151,7 +144,7 @@ class StdDaqClient(PSIDeviceBase):
|
||||
"""
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
custom_prepare_cls = StdDaqMixin
|
||||
USER_ACCESS = ["set_daq_config", "get_daq_config", "safestop", "restart", "connect", "message", "state"]
|
||||
USER_ACCESS = ["set_daq_config", "get_daq_config", "surestop", "nuke", "connect", "message", "state"]
|
||||
_wsclient = None
|
||||
|
||||
# Status attributes
|
||||
@@ -223,7 +216,7 @@ class StdDaqClient(PSIDeviceBase):
|
||||
"The stdDAQ websocket interface refused connection 5 times.")
|
||||
logger.debug(f"[{self.name}] Connected to DAQ after {num_retry} tries")
|
||||
|
||||
def message(self, message: dict, timeout=1, wait_reply=True) -> None | str:
|
||||
def message(self, message: dict, timeout=1, wait_reply=True, client=None) -> None | str:
|
||||
"""Send a message to the StdDAQ and receive a reply
|
||||
|
||||
Note: finishing acquisition means StdDAQ will close connection, so
|
||||
@@ -301,7 +294,7 @@ class StdDaqClient(PSIDeviceBase):
|
||||
if cfg['image_pixel_height'] != self.cfg_pixel_height.get() or cfg['image_pixel_width'] != self.cfg_pixel_width.get():
|
||||
# Stop if current status is not idle
|
||||
if self.state() != "idle":
|
||||
self.safestop()
|
||||
self.surestop()
|
||||
|
||||
# Stop running acquisition
|
||||
self.unstage()
|
||||
@@ -348,7 +341,7 @@ class StdDaqClient(PSIDeviceBase):
|
||||
raise ConnectionError(f"[{self.name}] Error {r.status_code}:\t{r.text}")
|
||||
return r.json()
|
||||
|
||||
def restart(self):
|
||||
def nuke(self):
|
||||
""" Reconfigures the stdDAQ to restart the services
|
||||
"""
|
||||
cfg = self.get_daq_config()
|
||||
@@ -363,35 +356,43 @@ class StdDaqClient(PSIDeviceBase):
|
||||
r = json.loads(r)
|
||||
return r['status']
|
||||
|
||||
def safestop(self, timeout=5):
|
||||
def surestop(self, timeout=5):
|
||||
""" Stops a running acquisition
|
||||
|
||||
REST reconfiguration restarts with systemd and can corrupt currently written files.
|
||||
"""
|
||||
# Retries to steal connection from poller
|
||||
state = self.state()
|
||||
for rr in range(5):
|
||||
try:
|
||||
r = self.message({"command": "stop_all"})
|
||||
if r is not None:
|
||||
r = json.loads(r)
|
||||
logger.warning(f"[{self.name}] Stop-all command {r['status']}")
|
||||
else:
|
||||
# NOTE: stdDAQ just closes connection if idle
|
||||
logger.warning(f"[{self.name}] Stop-all command unknown")
|
||||
finally:
|
||||
sleep(0.2)
|
||||
client = connect(self.ws_url.get())
|
||||
msg = json.dumps({"command": "stop_all"})
|
||||
client.send(msg)
|
||||
reply = client.recv(timeout=1)
|
||||
reply = json.loads(reply)
|
||||
logger.warning(reply)
|
||||
# if r['status'] == 'success':
|
||||
# break
|
||||
|
||||
sleep(0.5)
|
||||
|
||||
client = connect(self.ws_url.get())
|
||||
msg = json.dumps({"command": "status"})
|
||||
client.send(msg)
|
||||
reply = client.recv(timeout=1)
|
||||
reply = json.loads(reply)
|
||||
|
||||
if reply['status'] in ['idle', 'stoped']:
|
||||
logger.warning(f"[{self.name}] Stop-all command finished in {reply['status']}")
|
||||
return
|
||||
|
||||
# If stop_all didn't stop, nuke the whole thing
|
||||
logger.error(f"[{self.name}] Stop-all command did not finish in time, nuking the stdDAQ")
|
||||
self.nuke()
|
||||
sleep(timeout)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Wait until status is back to idle
|
||||
if timeout > 0.2:
|
||||
wait_time = 0
|
||||
while wait_time < timeout/5:
|
||||
state = self.state()
|
||||
if state is not None:
|
||||
if state in ['idle', 'stoped']:
|
||||
return
|
||||
sleep(0.2)
|
||||
wait_time += 0.2
|
||||
|
||||
|
||||
# Automatically connect to microXAS testbench if directly invoked
|
||||
|
||||
@@ -28,8 +28,7 @@ class TomcatStepScan(ScanBase):
|
||||
|
||||
Example class for simple BEC-based step scan using the low-level API. It's just a standard
|
||||
'line_scan' with the only difference that overrides burst behavior to use camera burst instead
|
||||
of software triggers.
|
||||
|
||||
of individual software triggers.
|
||||
|
||||
NOTE: As decided by Tomcat, the scans should not manage the scope of devices
|
||||
- All enabled devices are expected to be configured for acquisition by the end of stage
|
||||
@@ -98,13 +97,13 @@ class TomcatStepScan(ScanBase):
|
||||
yield from self._move_scan_motors_and_wait(pos)
|
||||
time.sleep(self.settling_time)
|
||||
|
||||
# yield from self.stubs.trigger(min_wait=trigger_time)
|
||||
yield from self.stubs.trigger(group='trigger', point_id=self.point_id)
|
||||
|
||||
trigger_time = self.exp_time * self.burst_at_each_point
|
||||
time.sleep(trigger_time)
|
||||
yield from self.stubs.trigger(min_wait=trigger_time)
|
||||
# yield from self.stubs.trigger(group='trigger', point_id=self.point_id)
|
||||
# time.sleep(trigger_time)
|
||||
|
||||
yield from self.stubs.read(group="monitored", point_id=self.point_id, wait_group=None)
|
||||
yield from self.stubs.read(group="monitored", point_id=self.point_id)
|
||||
# yield from self.stubs.read(group="monitored", point_id=self.point_id, wait_group=None)
|
||||
self.point_id += 1
|
||||
|
||||
|
||||
@@ -195,15 +194,14 @@ class TomcatSnapNStep(AsyncFlyScanBase):
|
||||
def render_file(self, filename, filesubs):
|
||||
"""Prepare action: render AeroScript file"""
|
||||
# Load the test file
|
||||
if filename is not None:
|
||||
filename = os.path.join(os.path.dirname(__file__), "../devices/aerotech/" + filename)
|
||||
logger.info(f"Attempting to load file {filename}")
|
||||
with open(filename) as f:
|
||||
templatetext = f.read()
|
||||
filename = os.path.join(os.path.dirname(__file__), "../devices/aerotech/" + filename)
|
||||
logger.info(f"Attempting to load file {filename}")
|
||||
with open(filename) as f:
|
||||
templatetext = f.read()
|
||||
|
||||
# Substitute jinja template
|
||||
tm = jinja2.Template(templatetext)
|
||||
filetext = tm.render(scan=filesubs)
|
||||
# Substitute jinja template
|
||||
tm = jinja2.Template(templatetext)
|
||||
filetext = tm.render(scan=filesubs)
|
||||
return filetext
|
||||
|
||||
def scan_core(self):
|
||||
@@ -215,7 +213,7 @@ class TomcatSnapNStep(AsyncFlyScanBase):
|
||||
# FIXME: this will swallow errors
|
||||
# yield from self.stubs.complete(device="es1_tasks")
|
||||
st = yield from self.stubs.send_rpc_and_wait("es1_tasks", "complete")
|
||||
st.wait()
|
||||
# st.wait()
|
||||
task_states = yield from self.stubs.send_rpc_and_wait("es1_tasks", "taskStates.get")
|
||||
if task_states[4] == 8:
|
||||
raise RuntimeError(f"Task {4} finished in ERROR state")
|
||||
@@ -346,7 +344,7 @@ class TomcatSimpleSequence(AsyncFlyScanBase):
|
||||
# FIXME: this will swallow errors
|
||||
# yield from self.stubs.complete(device="es1_tasks")
|
||||
st = yield from self.stubs.send_rpc_and_wait("es1_tasks", "complete")
|
||||
st.wait()
|
||||
# st.wait()
|
||||
task_states = yield from self.stubs.send_rpc_and_wait("es1_tasks", "taskStates.get")
|
||||
if task_states[4] == 8:
|
||||
raise RuntimeError(f"Task {4} finished in ERROR state")
|
||||
|
||||
Reference in New Issue
Block a user