Many more scans

This commit is contained in:
gac-x05la
2024-10-18 16:10:29 +02:00
parent 5d3550d0fd
commit 7118b2da2b
8 changed files with 107 additions and 75 deletions

View File

@@ -14,27 +14,35 @@ as a special beamline console with the AWI software stack and is administered by
services are set up such that the *gac-x05la* account can administer all services.
As any beamline console it's part of the *sls-bl-x05la* beamline network, so you have to go through
the SSH gateway by:
'''
```
ssh -J x05la-gw gac-x05la@x05la-bec-001
'''
```
PSI's virtual terminal service *nx-term.psi.ch* offers a convenient way to have a linux desktop
from your browser.
### Monitoring BEC services
Once logged in to the *x05la-bec-001* host, you can attach to the console of the BEC services by:
'''
```
tmux attach -t bec
'''
```
You can restart services by clicking on their sub-terminal and pressing Ctrl + C (sometimes twice).
This is needed whenever you edit the scan or device files.
### Activating the BEC environment
In order to start the BEC console interface, you first need to activate the corresponding venv.
This will also start the BEC GUI in a separate process and window:
```
source /data/test/x05la-test-bec/bec_deployment/bev_venv/bin/activate
bec
```
### The BEC deployment
The BEC is deployed to a beamline specific network mounted folder that contains the main
repositories for the current installation, you can check it by:
'''
```
[gac-x05la@x05la-bec-001 ~]$ ls -ltr /data/test/x05la-test-bec/bec_deployment/
total 4
drwxr-xr-x 7 gac-x05la unx-sls 4096 Aug 20 15:10 ophyd_devices
@@ -45,7 +53,7 @@ drwxr-xr-x 6 gac-x05la unx-sls 4096 Aug 20 15:11 bec_venv
drwxr-xr-x 15 gac-x05la unx-sls 4096 Aug 29 13:06 bec
drwxr-xr-x 7 gac-x05la unx-sls 4096 Sep 3 13:28 tomcat_bec
drwxr-xr-x 2 gac-x05la unx-sls 4096 Sep 6 11:22 logs
'''
```
A short summary of the folder structure:
- bec_venv : The actual installation directory of the BEC framework
- bec : Git repo for the BEC framework
@@ -55,18 +63,10 @@ A short summary of the folder structure:
In short: You should use the bec_venv and only edit the tomcat_bec folder. And this is easily done
in Visual Studio Code, a really handy IDE if you have a fast connection:
'''
```
code &
'''
```
### The BEC environment
So in order to start the BEC console interface, you first need to activate the venv. This will
also start the BEC GUI in a separate process and window:
'''
source /data/test/x05la-test-bec/bec_deployment/bev_venv/bin/activate
bec
'''
### The Tomcat BEC repository
@@ -75,7 +75,7 @@ The Tomcat BEC repository is a collection of Tomcat specific ophyd devices, scan
device configurations. It tries to enforce a standard structure across beamlines and it's
**maintenance is the responsibility of the beamline.** I.e. it should be handed over from
controls while still keeping the basic structure:
'''
```
[gac-x05la@x05la-bec-001 ~]$ ls -ltr /data/test/x05la-test-bec/bec_deployment/tomcat_bec/tomcat_bec/
total 6
-rw-r--r-- 1 gac-x05la unx-sls 0 Jan 1 1970 __init__.py
@@ -90,30 +90,30 @@ drwxr-xr-x 3 gac-x05la unx-sls 4096 Sep 3 13:28 device_configs
drwxr-xr-x 7 gac-x05la unx-sls 4096 Sep 5 18:08 devices
drwxr-xr-x 3 gac-x05la unx-sls 4096 Sep 16 12:30 scans
-rw-r--r-- 1 gac-x05la unx-nogroup 3383 Oct 1 16:08 Readme.md
'''
```
From the list of folders the most important ones are:
- device_configs : Ophyd device datbase as YAML file
- devices : Ophyd device repository (i.e. Aerotech, Gigafrost)
- scans : Tomcat specific base scans in the low-level API
The official location of the beamline repository is:
'''
```
https://gitlab.psi.ch/bec/tomcat_bec
'''
```
### Configuration and basic diagnostic in BEC
Once you're at the CLI it is good practice to check the available devices:
'''
```
dev.show_all()
'''
```
If the previous two configuration attempts were unsuccesfull the bec_device_server will start
without any devices. A single missing or mistyped IOC will cause a configuration failure, so once
you fixed you configs you can reload your YAML config file:
'''
```
bec.config.update_session_with_file("/data/test/x05la-test-bec/bec_deployment/tomcat_bec/tomcat_bec/device_configs/microxas_test_bed.yaml")
'''
```
If a single, unnecesary device doesn't come up , just comment it out.
@@ -136,25 +136,24 @@ recently updated event model. Redundant staging raises an exception.
Since seeing is believing, it's often a good idea to bring up the panels and get some visual
feedback. You can start the standard CaQtDM panels for the Aerotech and GigaFrost from any
beamline console running the contros stack by:
'''
```
caqtdm -macro "CAM=X02DA-CAM-GF2" X_X02DA_GIGAFROST_camControl_user.ui
caqtdm -macro "P=X02DA-ES1-SMP1" aeroauto1_ControllerOverview.ui
'''
```
The std-DAQ doesn't have control panels, but the BEC can display the hard-coded preview streams.
The *daq_stream0* preview forwards displays 2 frames out of every 555 images. The second stream
is too fast for the bec_device_server and is thus disabled.
The *daq_stream1* preview forwards displays 1 frames at a maximum of 5 Hz.
So we just need to add the correct widgets to the GUI:
'''
```
gui.add_dock('tomolive')
gui.panels['tomolive'].add_widget('BECFigure').image('daq_stream0')
'''
gui.panels['tomolive'].add_widget('BECFigure').image('daq_stream1')
```
### Short run with the GigaFrost and std-DAQ
This is a short, recorded test run with 2016x2016 frames at 100 Hz with the GigaFrost. The current
GigaFrost ophyd device includes the std-DAQ client, but they will probably be separated.
'''
```
# Configure
d = {'ntotal': 1000000, 'nimages': 555, 'exposure': 5, 'period': 10}
dev.gfclient.configure(d)
@@ -167,14 +166,14 @@ def.gfclient.trigger()
# Unstage
def.gfclient.unstage()
dev.daq_stream0.unstage()
'''
```
## Reseting the Automation1 iSMC
There's some electrical disturbance at SLS that can bring the system to a problematic state. A workaround is to remotely reset the iSMC:
'''
```
dev.es1_ismc.reset()
'''
```
FIXME: There is a known desync in PSO inputs upon iSMC restart that requires restarting the IOC.
@@ -185,9 +184,9 @@ FIXME: There is a known desync in PSO inputs upon iSMC restart that requires res
As I was unsure about the various triggering modes of the Gigafrost, I only prepared a simple
step scanning implementation that is compatible with the standard bluesky step scanning interface
using soft-triggers
'''
```
scans.simplestep(range=(-20, 160), steps=18, exp_time=2, exp_burst=220)
'''
```
### Templated script scans

View File

@@ -95,7 +95,6 @@ es1_psod:
prefix: 'X02DA-ES1-SMP1:ROTY:PSO:'
deviceTags:
- es1
- trigger
enabled: true
onFailure: buffer
readOnly: false

View File

@@ -985,6 +985,15 @@ class aa1AxisDriveDataCollection(Device):
super().unstage()
print(f"Recorded samples: {self.nsamples_rbv.value}")
def kickoff(self) -> DeviceStatus:
if not self._staged:
self.stage()
#status = DeviceStatus(self, done=True, success=True, settle_time=0.1)
status = DeviceStatus(self)
status.set_finished()
return status
def complete(self, settle_time=0.1) -> DeviceStatus:
"""DDC just reads back whatever is available in the buffers"""
sleep(settle_time)

View File

@@ -106,7 +106,7 @@ class GigaFrostClient(PSIDetectorBase):
"""
# pylint: disable=too-many-instance-attributes
custom_prepare_cls = GigaFrostClientMixin
USER_ACCESS = [""]
USER_ACCESS = ["kickoff"]
cam = Component(gfcam.GigaFrostCamera, prefix="X02DA-CAM-GF2:", name="cam")
daq = Component(stddaq.StdDaqClient, name="daq")
@@ -182,6 +182,11 @@ class GigaFrostClient(PSIDetectorBase):
return super().stage()
def kickoff(self) -> DeviceStatus:
if not self._staged:
self.stage()
return DeviceStatus(self, done=True, success=True, settle_time=0.1)
# def trigger(self) -> DeviceStatus:
# """ Triggers the current device and all sub-devices, i.e. the camera.
# """

View File

@@ -9,7 +9,7 @@ Created on Thu Jun 27 17:28:43 2024
import json
from time import sleep
from threading import Thread
from ophyd import Device, Signal, Component, Kind
from ophyd import Device, Signal, Component, Kind, DeviceStatus
from websockets.sync.client import connect
from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError
@@ -43,6 +43,7 @@ class StdDaqClient(Device):
'''
"""
# pylint: disable=too-many-instance-attributes
USER_ACCESS=["kickoff"]
# Status attributes
url = Component(Signal, kind=Kind.config)
@@ -189,6 +190,10 @@ class StdDaqClient(Device):
pass
return super().unstage()
def kickoff(self) -> DeviceStatus:
""" The DAQ was not meant to quickly toggle"""
return DeviceStatus(self, done=True, success=True, settle_time=0.1)
def stop(self, *, success=False):
""" Stop a running acquisition

View File

@@ -12,7 +12,7 @@ from time import sleep, time
from threading import Thread
import zmq
import numpy as np
from ophyd import Device, Signal, Component, Kind
from ophyd import Device, Signal, Component, Kind, DeviceStatus
from ophyd_devices.interfaces.base_classes.psi_detector_base import (
CustomDetectorMixin,
PSIDetectorBase,
@@ -134,6 +134,7 @@ class StdDaqPreviewDetector(PSIDetectorBase):
cam_widget = gui.add_dock('cam_dock1').add_widget('BECFigure').image('daq_stream1')
"""
# Subscriptions for plotting image
USER_ACCESS=["kickoff"]
SUB_MONITOR = "device_monitor_2d"
_default_sub = SUB_MONITOR
@@ -179,6 +180,9 @@ class StdDaqPreviewDetector(PSIDetectorBase):
sleep(1)
self._socket.connect(self.url.get())
def kickoff(self) -> DeviceStatus:
""" The DAQ was not meant to be toggled"""
return DeviceStatus(self, done=True, success=True, settle_time=0.1)
# Automatically connect to MicroSAXS testbench if directly invoked
if __name__ == "__main__":

View File

@@ -157,12 +157,14 @@ class TemplatedScanBase(AsyncFlyScanBase):
# Collect
if self.daqname is not None and self.daqmode=="collect":
print("TOMCAT Collecting scripted scan results (from EPICS)")
positions = yield from self.stubs.send_rpc_and_wait(self.daqname, "collect")
logger.info(f"Finished scan with collected positions: {positions}")
def cleanup(self):
"""Set scan progress to 1 to finish the scan"""
self.num_pos = 1
print("TOMCAT Officially finshed the scan")
return super().cleanup()
@@ -520,10 +522,10 @@ class SequenceScanBase(TemplatedScanBase):
# Call super() to do the substitutions
yield from super().prepare_positions()
def unstage(self):
""" Wait for DAQ before unstaging"""
time.sleep(1)
yield from super().unstage()
# def unstage(self):
# """ Wait for DAQ before unstaging"""
# time.sleep(1)
# yield from super().unstage()

View File

@@ -71,7 +71,7 @@ def demosequencescan(scan_start, gate_high, gate_low, repeats=1, repmode="PosNeg
scans.sequencescan(scan_start, gate_high, gate_low, exp_time=exp_time, exp_frames=exp_frames, repeats=repeats, repmode=repmode)
def becsequencescan(scan_start, gate_high, gate_low, repeats=1, repmode="PosNeg",
def becsequencescan(start, gate_high, gate_low, repeats=1, repmode="PosNeg",
exp_time=0.005, exp_frames=180, sync='pso'):
""" Demo sequence scan with GigaFrost
@@ -106,15 +106,19 @@ def becsequencescan(scan_start, gate_high, gate_low, repeats=1, repmode="PosNeg"
if scan_repmode in ("POS", "NEG"):
scan_range = repeats *(gate_high + gate_low)
if scan_repmode=="POS":
scan_end = scan_start + scan_range + scan_accdistance
scan_start = start - scan_accdistance
scan_end = start + scan_range + scan_accdistance
if scan_repmode=="NEG":
scan_end = scan_start - scan_range - scan_accdistance
scan_start = start + scan_accdistance
scan_end = start - scan_range - scan_accdistance
elif scan_repmode in ("POSNEG", "NEGPOS"):
scan_range = gate_high + gate_low
if scan_repmode=="POSNEG":
scan_end = scan_start + scan_range + scan_accdistance
scan_start = start - scan_accdistance
scan_end = start + scan_range + scan_accdistance
if scan_repmode=="NEGPOS":
scan_end = scan_start - scan_range - scan_accdistance
scan_start = start + scan_accdistance
scan_end = start - scan_range - scan_accdistance
else:
raise RuntimeError(f"Unsupported repetition mode: {repmode}")
@@ -165,35 +169,40 @@ def becsequencescan(scan_start, gate_high, gate_low, repeats=1, repmode="PosNeg"
dev.es1_ddaq.configure(daqcfg)
dev.es1_psod.configure(psocfg)
# Stage all devices
dev.gfclient.stage()
dev.es1_ddaq.stage()
dev.es1_psod.stage()
dev.daq_stream1.stage()
try:
# Stage all devices
dev.gfclient.stage()
dev.es1_ddaq.stage()
dev.es1_psod.stage()
dev.daq_stream1.stage()
# Kick off devices
dev.gfclient.kickoff()
dev.es1_ddaq.kickoff()
dev.es1_psod.kickoff()
print("Handing over to 'scans.line_scan'")
# Kick off devices
dev.gfclient.kickoff()
dev.es1_ddaq.kickoff()
dev.es1_psod.kickoff()
if scan_repmode in ["POS", "NEG"]:
dev.es1_psod.prepare(psoBoundsPos)
dev.es1_roty.move(scan_end).wait()
elif scan_repmode in ["POSNEG", "NEGPOS"]:
for ii in range(scan_repnum):
if ii%2==0:
print("Manual motor scan")
if scan_repmode in ["POS", "NEG"]:
dev.es1_psod.prepare(psoBoundsPos)
dev.es1_roty.move(scan_end).wait()
if ii%2==1:
dev.es1_psod.prepare(psoBoundsNeg)
dev.es1_roty.move(scan_start).wait()
# Stage all devices
dev.gfclient.unstage()
dev.es1_ddaq.unstage()
dev.es1_psod.unstage()
dev.daq_stream1.unstage()
elif scan_repmode in ["POSNEG", "NEGPOS"]:
for ii in range(scan_repnum):
if ii%2==0:
dev.es1_psod.prepare(psoBoundsPos)
# FIXME : Temporary trigger
dev.gfclient.trigger()
dev.es1_roty.move(scan_end).wait()
if ii%2==1:
dev.es1_psod.prepare(psoBoundsNeg)
# FIXME : Temporary trigger
dev.gfclient.trigger()
dev.es1_roty.move(scan_start).wait()
finally:
# Stage all devices
dev.gfclient.unstage()
dev.es1_ddaq.unstage()
dev.es1_psod.unstage()
dev.daq_stream1.unstage()
# Move back to start
dev.es1_roty.move(scan_start).wait()