Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft PR for the NYX compliance changes #360

Draft
wants to merge 27 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4345590
gui split, fixes required for raster
Jun 21, 2023
3c1c0d1
Fixes epic address list error
mskinner5278 Feb 3, 2023
743ee58
Fixes an error in the multiSampleGripper definition for denso_robot
mskinner5278 Feb 8, 2023
95a5f4e
Fixes double-gripper logic, breaks mounting of the same sample onto i…
mskinner5278 Feb 8, 2023
d65e7c2
adds backend for parkRobot
Jun 21, 2023
f4dffc0
replaces old raster flyer with NYX raster flyer
mskinner5278 Mar 8, 2023
1cc4afc
Added configuration for skipping the epics detector init
mskinner5278 May 4, 2023
71091c7
daq_macros changes for code compliance merging
Jun 21, 2023
f38f820
start_bs compliance merge changes
Jun 21, 2023
2972b53
Compliance merge for GUI split
Jun 21, 2023
46217fb
temporarily disabling loop center button
mskinner5278 Jun 21, 2023
98122b3
nyxtools overlay update
mskinner5278 Jun 21, 2023
cb948d9
using bps.sleep(...) in plan, thanks to tacaswell
mskinner5278 Jul 13, 2023
de1c2e9
more changes based on #314 review
mskinner5278 Jul 13, 2023
7553c0b
removed useless line preservation
mskinner5278 Jul 13, 2023
5644371
now using setPvDesc alias
mskinner5278 Jul 13, 2023
b4e90f4
Update daq_macros.py
mskinner5278 Jul 14, 2023
2e9678b
removes /var/log/dama/ log writing
mskinner5278 Jun 21, 2023
4cea6f9
missing imports
mskinner5278 Jun 21, 2023
ce3b0dd
fixed bad package declaration
mskinner5278 Jun 21, 2023
0774515
remove /var/log/dama logging
mskinner5278 Jun 21, 2023
ef8ae08
changes to meet compliance testing
mskinner5278 Jun 22, 2023
5637df5
removed commented code
mskinner5278 Jul 14, 2023
37beeef
t
mskinner5278 Jul 14, 2023
c90f302
remove overlay from pythonpath
mskinner5278 Jul 14, 2023
86caa96
Hiding the launch directory
mskinner5278 Jul 18, 2023
541437d
Fixed missing detector arming in standard collection
mskinner5278 Jul 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/lsdcServer_nyx.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
export PROJDIR=/nsls2/software/mx/daq/
export CONFIGDIR=${PROJDIR}bnlpx_config/
export LSDCHOME=${PROJDIR}lsdc_nyx
export EPICS_CA_AUTO_ADDR_LIST=NO
#export EPICS_CA_AUTO_ADDR_LIST=NO
export PYTHONPATH=".:${CONFIGDIR}:/usr/lib64/edna-mx/mxv1/src:/usr/lib64/edna-mx/kernel/src:${LSDCHOME}:${PROJDIR}/RobotControlLib"
export PATH=/usr/local/bin:/usr/bin:/bin:${PROJDIR}/software/bin:/opt/ccp4/bin
source ${CONFIGDIR}daq_env_nyx.txt
Expand Down
3 changes: 2 additions & 1 deletion config_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class RasterStatus(Enum):
PINS_PER_PUCK = 16

DETECTOR_OBJECT_TYPE_LSDC = "lsdc" # using det_lib
DETECTOR_OBJECT_TYPE_NO_INIT = "no init" # skip epics detector init
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any reason why you couldn't use DETECTOR_OBJECT_TYPE_OPHYD?

DETECTOR_OBJECT_TYPE_OPHYD = "ophyd" # instantiated in start_bs, using Bluesky scans
DETECTOR_OBJECT_TYPE = "detectorObjectType"

Expand Down Expand Up @@ -118,4 +119,4 @@ class RasterStatus(Enum):
EMBL_SERVER_PV_BASE = {
"amx": "XF:17IDB-ES:AMX{EMBL}",
"fmx": "XF:17IDC-ES:FMX{EMBL}"
}
}
4 changes: 2 additions & 2 deletions daq_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,8 @@ def mountSample(sampID):
setPvDesc("robotZWorkPos",getPvDesc("robotZMountPos"))
setPvDesc("robotOmegaWorkPos",90.0)
logger.info("done setting work pos")
if (currentMountedSampleID != ""): #then unmount what's there
if (sampID!=currentMountedSampleID and not robot_lib.multiSampleGripper()):
if (currentMountedSampleID != "" and not robot_lib.multiSampleGripper()): #then unmount what's there
if (sampID!=currentMountedSampleID):
puckPos = mountedSampleDict["puckPos"]
pinPos = mountedSampleDict["pinPos"]
if robot_lib.unmountRobotSample(gov_robot, puckPos,pinPos,currentMountedSampleID):
Expand Down
143 changes: 106 additions & 37 deletions daq_macros.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,21 @@
from collections import OrderedDict
from threading import Thread
from config_params import *
from ophyd.status import SubscriptionStatus
from kafka_producer import send_kafka_message

import gov_lib
import urllib.request
import io

from scans import (zebra_daq_prep, setup_zebra_vector_scan,
setup_zebra_vector_scan_for_raster,
setup_vector_program)
import bluesky.plan_stubs as bps
import bluesky.plans as bp
from bluesky.preprocessors import finalize_wrapper
from bluesky.log import config_bluesky_logging
config_bluesky_logging(level='INFO')
from fmx_annealer import govStatusGet, govStateSet, fmxAnnealer, amxAnnealer # for using annealer specific to FMX and AMX

try:
Expand Down Expand Up @@ -224,7 +229,7 @@ def changeImageCenterHighMag(x,y,czoom):
def autoRasterLoop(currentRequest):
global autoRasterFlag


logger.info('entering autoRasterLoop')
gov_status = gov_lib.setGovRobot(gov_robot, 'SA')
if not gov_status.success:
return 0
Expand Down Expand Up @@ -823,7 +828,7 @@ def runDozorThread(directory,
"""
global rasterRowResultsList,processedRasterRowCount

time.sleep(0.5) #allow for file writing
time.sleep(1.0) #allow for file writing

node = getNodeName("spot", rowIndex, 8)

Expand Down Expand Up @@ -1834,9 +1839,12 @@ def snakeRasterBluesky(rasterReqID, grain=""):
if (daq_utils.beamline == "fmx"):
setPvDesc("sampleProtect",0)
setPvDesc("vectorGo", 0) #set to 0 to allow easier camonitoring vectorGo

govStatus = gov_lib.setGovRobot(gov_robot, "DA")
if govStatus.exception():
logger.error(f"Problem during start-of-raster governor move, aborting! exception: {govStatus.exception()}")
return
data_directory_name, filePrefix, file_number_start, dataFilePrefix, exptimePerCell, img_width_per_cell, wave, detDist, rasterDef, stepsize, omega, rasterStartX, rasterStartY, rasterStartZ, omegaRad, rowCount, numsteps, totalImages, rows = params_from_raster_req_id(rasterReqID)
rasterRowResultsList = [{} for i in range(0,rowCount)]
rasterRowResultsList = [{} for i in range(0,rowCount)]
processedRasterRowCount = 0
rasterEncoderMap = {}

Expand All @@ -1853,41 +1861,39 @@ def snakeRasterBluesky(rasterReqID, grain=""):
parentRequest = db_lib.getRequestByID(parentReqID)
parentReqObj = parentRequest["request_obj"]
parentReqProtocol = parentReqObj["protocol"]
detDist = parentReqObj["detDist"]
detDist = parentReqObj["detDist"]

rasterFilePrefix = dataFilePrefix + "_Raster"
total_exposure_time = exptimePerCell*totalImages
detDist /= 1000 # TODO find a way to standardize detector distance settings

raster_flyer.configure_detector(file_prefix=rasterFilePrefix, data_directory_name=data_directory_name)
if raster_flyer.detector.cam.armed.get() == 1:
daq_lib.gui_message('Detector is in armed state from previous collection! Stopping detector, but the user '
'should check the most recent collection to determine if it was successful. Cancelling'
'this collection, retry when ready.')
raster_flyer.detector.cam.acquire.put(0)
logger.warning("Detector was in the armed state prior to this attempted collection.")
return 0
start_time = time.time()
arm_status = raster_flyer.detector_arm(angle_start=omega, img_width=img_width_per_cell, total_num_images=totalImages, exposure_period_per_image=exptimePerCell, file_prefix=rasterFilePrefix,
raster_flyer.detector_arm(angle_start=omega, img_width=img_width_per_cell, total_num_images=totalImages, exposure_period_per_image=exptimePerCell, file_prefix=rasterFilePrefix,
data_directory_name=data_directory_name, file_number_start=file_number_start, x_beam=xbeam, y_beam=ybeam, wavelength=wave, det_distance_m=detDist,
num_images_per_file=numsteps)
govStatus = gov_lib.setGovRobot(gov_robot, "DA")
arm_status.wait()
logger.info(f"Governor move to DA and synchronous arming took {time.time() - start_time} seconds.")
if govStatus.exception():
logger.error(f"Problem during start-of-raster governor move, aborting! exception: {govStatus.exception()}")
return
raster_flyer.configure_detector(file_prefix=rasterFilePrefix, data_directory_name=data_directory_name)
raster_flyer.detector.stage()
num_images_per_file=numsteps) # rasterDef['numCells']) TODO: try to get all images in one file
#raster_flyer.detector.stage()
procFlag = int(getBlConfig("rasterProcessFlag"))
spotFindThreadList = []
yield from bps.mv(samplexyz.omega, (omega-1)) # attempting to over-compensate omega movement
for row_index, row in enumerate(rows): # since we have vectors in rastering, don't move between each row
xMotAbsoluteMove, xEnd, yMotAbsoluteMove, yEnd, zMotAbsoluteMove, zEnd = raster_positions(row, stepsize, omegaRad, rasterStartX, rasterStartY, rasterStartZ, row_index)
vector = {'x': (xMotAbsoluteMove, xEnd), 'y': (yMotAbsoluteMove, yEnd), 'z': (zMotAbsoluteMove, zEnd)}
logger.info(f'starting new row: {row_index}')
zMotAbsoluteMove, zEnd, yMotAbsoluteMove, yEnd, xMotAbsoluteMove, xEnd = raster_positions(row, stepsize, omegaRad+90, rasterStartZ*1000, rasterStartY*1000, rasterStartX*1000, row_index)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these factors of 1000 cannot stay. can't you refine or make alternative PVs for sample centering-related PVs that would enable you to keep the current values and the same names of the objects to be moved?

vector = {'x': (xMotAbsoluteMove/1000, xEnd/1000), 'y': (yMotAbsoluteMove/1000, yEnd/1000), 'z': (zMotAbsoluteMove/1000, zEnd/1000)}
yield from bps.mv(samplexyz.x, xMotAbsoluteMove/1000, samplexyz.y, yMotAbsoluteMove/1000, samplexyz.z, zMotAbsoluteMove/1000, samplexyz.omega, omega-0.05)
yield from zebraDaqRasterBluesky(raster_flyer, omega, numsteps, img_width_per_cell * numsteps, img_width_per_cell, exptimePerCell, rasterFilePrefix,
data_directory_name, file_number_start, row_index, vector)
raster_flyer.zebra.reset.put(1) # reset after every row to make sure it is clear for the next row
time.sleep(0.2) # necessary for reliable row processing - see comment in commit 6793f4
#raster_flyer.zebra.reset.put(1) # reset after every row to make sure it is clear for the next row
yield from bps.sleep(0.3) # necessary for reliable row processing - see comment in commit 6793f4

# processing
if (procFlag):
if (procFlag):
if (daq_utils.detector_id == "EIGER-16"):
seqNum = int(raster_flyer.detector.file.sequence_id.get())
else:
Expand All @@ -1909,6 +1915,7 @@ def snakeRasterBluesky(rasterReqID, grain=""):
initiate transitions here allows for GUI sample/heat map image to update
after moving to known position"""
logger.debug(f'lastOnSample(): {lastOnSample()} autoRasterFlag: {autoRasterFlag}')
yield from bps.sleep(3) #waiting for detector to not lose row
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would be a major problem for the other beamlines, where this sleep isn't necessary

if (lastOnSample() and not autoRasterFlag):
govStatus = gov_lib.setGovRobot(gov_robot, 'SA', wait=False)
if govStatus.exception():
Expand All @@ -1932,7 +1939,7 @@ def snakeRasterBluesky(rasterReqID, grain=""):
#data acquisition is finished, now processing and sample positioning
if not procFlag: # no, no processing. just move to raster start
#must go to known position to account for windup dist.
logger.info("moving to raster start")
logger.info(f" no processing! moving to raster start: {rasterStartX} {rasterStartY} {rasterStartZ} {omega}")
yield from bps.mv(samplexyz.x, rasterStartX)
yield from bps.mv(samplexyz.y, rasterStartY)
yield from bps.mv(samplexyz.z, rasterStartZ)
Expand All @@ -1941,25 +1948,37 @@ def snakeRasterBluesky(rasterReqID, grain=""):

else: # yes, do row processing
if daq_lib.abort_flag != 1:
print("processing rows")
[thread.join(timeout=120) for thread in spotFindThreadList]
else:
logger.info("raster aborted, do not wait for spotfind threads")
logger.info(str(processedRasterRowCount) + "/" + str(rowCount))
logger.info(str(processedRasterRowCount) + "/" + str(rowCount))
rasterResult = generateGridMap(rasterRequest)

logger.info(f'protocol = {reqObj["protocol"]}')
if (reqObj["protocol"] == "multiCol" or parentReqProtocol == "multiColQ"):
if (parentReqProtocol == "multiColQ"):
if (parentReqProtocol == "multiColQ"):
multiColThreshold = parentReqObj["diffCutoff"]
else:
multiColThreshold = reqObj["diffCutoff"]
gotoMaxRaster(rasterResult,multiColThreshold=multiColThreshold)
multiColThreshold = reqObj["diffCutoff"]
# gotoMaxRaster(rasterResult,multiColThreshold=multiColThreshold)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

calls to other code can't just be removed. make a config variable or something that enables you to make NYX work with your code while AMX/FMX use the gotoMaxRaster() function

logger.info(f"moving to raster start: {rasterStartX} {rasterStartY} {rasterStartZ} {omega}")
yield from bps.mv(samplexyz.x, rasterStartX)
yield from bps.mv(samplexyz.y, rasterStartY)
yield from bps.mv(samplexyz.z, rasterStartZ)
yield from bps.mv(samplexyz.omega, omega)
else:
try:
# go to start omega for faster heat map display
gotoMaxRaster(rasterResult,omega=omega)
# gotoMaxRaster(rasterResult,omega=omega)
logger.info(f"moving to raster start: {rasterStartX} {rasterStartY} {rasterStartZ} {omega}")
yield from bps.mv(samplexyz.x, rasterStartX)
yield from bps.mv(samplexyz.y, rasterStartY)
yield from bps.mv(samplexyz.z, rasterStartZ)
yield from bps.mv(samplexyz.omega, omega)
except ValueError:
#must go to known position to account for windup dist.
logger.info(f"moving to raster start: {rasterStartX} {rasterStartY} {rasterStartZ} {omega}")
logger.info("moving to raster start")
yield from bps.mv(samplexyz.x, rasterStartX)
yield from bps.mv(samplexyz.y, rasterStartY)
Expand All @@ -1984,7 +2003,7 @@ def snakeRasterBluesky(rasterReqID, grain=""):
rasterRequest["request_obj"]["rasterDef"]["status"] = (
RasterStatus.READY_FOR_SNAPSHOT.value
)
db_lib.updateRequest(rasterRequest)
db_lib.updateRequest(rasterRequest)
db_lib.updatePriority(rasterRequestID,-1)

#ensure gov transitions have completed successfully
Expand Down Expand Up @@ -2269,11 +2288,11 @@ def gotoMaxRaster(rasterResult,multiColThreshold=-1,**kwargs):
logger.info("goto " + str(x) + " " + str(y) + " " + str(z))

if 'omega' in kwargs:
beamline_lib.mvaDescriptor("sampleX",x,
"sampleY",y,
"sampleZ",z,
beamline_lib.mvaDescriptor("sampleX",x/1000,
"sampleY",y/1000,
"sampleZ",z/1000,
"omega",kwargs['omega'])
else: beamline_lib.mvaDescriptor("sampleX",x,"sampleY",y,"sampleZ",z)
else: beamline_lib.mvaDescriptor("sampleX",x/1000,"sampleY",y/1000,"sampleZ",z/1000)

if (autoVectorFlag): #if we found a hotspot, then look again at cellResults for coarse vector start and end
xminColumn = [] #these are the "line rasters" of the ends of threshold points determined by the first pass on the raster results
Expand Down Expand Up @@ -3210,12 +3229,20 @@ def clean_up_files(pic_prefix, output_file):

def loop_center_xrec():
global face_on
print('entering daq_macros.loop_center_xrec')

daq_lib.abort_flag = 0
pic_prefix = "findloop"
output_file = 'xrec_result.txt'
print('clean up files')
clean_up_files(pic_prefix, output_file)
zebraCamDaq(0,360,40,.4,pic_prefix,getBlConfig("visitDirectory"),0)
#TODO: if daq_utils.beamline=='nyx':
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sure that the AMX/FMX code works for them

print('post clean')
xrec_no_zebra(0)
print('post no zebra')
#else:
# zebraCamDaq(0,360,40,.4,pic_prefix,os.getcwd(),0)
#zebraCamDaq(0,360,40,.4,pic_prefix,getBlConfig("visitDirectory"),0)
comm_s = f'xrec {os.environ["CONFIGDIR"]}/xrec_360_40Fast.txt {output_file}'
logger.info(comm_s)
try:
Expand Down Expand Up @@ -3268,7 +3295,37 @@ def loop_center_xrec():
#now try to get the loopshape starting from here
return 1


def xrec_no_zebra(angle_start):
print(f'xrec_no_zebra{angle_start}')
beamline_lib.mvaDescriptor("omega", angle_start)
#yield from bps.mv(samplexyz.omega, angle_start)
for omega_target in range (angle_start, angle_start+360, 40):
#yield from bps.mv(samplexyz.omega, omega_target)
beamline_lib.mvaDescriptor("omega", omega_target)
logger.info(f'taking image at {omega_target}')
timeout = 5
# change image mode to single(0)
setPvDesc("lowMagImMode",0)
# start camera
setPvDesc("lowMagAcquire",1)
try:
with urllib.request.urlopen(daq_utils.lowMagCamURL, timeout=timeout) as response:
logger.info("xnz: read")
image_data = response.read()
logger.info("xnz: read")
with open(getBlConfig("visitDirectory")+"findloop_"+str(omega_target//40)+".jpg", "wb") as filename:
logger.info("xnz: write file")
filename.write(image_data)
logger.info("xnz: write file")
except urllib.error.URLError as e:
print("Error:", e)
logger.info("xnz: sleep")
time.sleep(1)
# change image mode to continuous(2)
setPvDesc("lowMagImMode",2)
# start camera
setPvDesc("lowMagAcquire",1)


def zebraCamDaq(angle_start,scanWidth,imgWidth,exposurePeriodPerImage,filePrefix,data_directory_name,file_number_start,scanEncoder=3): #scan encoder 0=x, 1=y,2=z,3=omega
#careful - there's total exposure time, exposure period, exposure time
Expand Down Expand Up @@ -3385,8 +3442,20 @@ def zebraDaqBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, exposur
'change_state':changeState, 'transmission':vector_params["transmission"],
'data_path':data_path}
start_time = time.time()
arm_status = flyer.detector_arm(**required_parameters)
flyer.detector_arm(**required_parameters)

def armed_callback(value, old_value, **kwargs):
if old_value == 0 and value == 1:
return True
return False

arm_status = SubscriptionStatus(flyer.detector.cam.armed, armed_callback, run=False)

flyer.detector.cam.acquire.put(1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this acquire be done after the detector is armed?


govStatus = gov_lib.setGovRobot(gov_robot, "DA")
time.sleep(0.5)
govStatus.wait()
arm_status.wait()
logger.info(f"Governor move to DA and synchronous arming took {time.time()-start_time} seconds.")
if govStatus.exception():
Expand Down Expand Up @@ -3441,7 +3510,7 @@ def zebraDaqRasterBluesky(flyer, angle_start, num_images, scanWidth, imgWidth, e
row_index=row_index, transmission=1, protocol="raster")
yield from bp.fly([raster_flyer])

logger.info("vector Done")
logger.info(f"vector Done, zebra arm status: {raster_flyer.zebra.pc.arm.output}")
logger.info("zebraDaqRasterBluesky Done")

def zebraDaq(vector_program,angle_start,scanWidth,imgWidth,exposurePeriodPerImage,filePrefix,data_directory_name,file_number_start,scanEncoder=3,changeState=True): #scan encoder 0=x, 1=y,2=z,3=omega
Expand Down
3 changes: 0 additions & 3 deletions daq_main2.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,9 @@
logging.getLogger('ophyd').setLevel(logging.WARN)
logging.getLogger('caproto').setLevel(logging.WARN)
handler1 = handlers.RotatingFileHandler('lsdcServerLog.txt', maxBytes=5000000, backupCount=100)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, differences cannot be completely removed.

handler2 = handlers.RotatingFileHandler('/var/log/dama/%slsdcServerLog.txt' % os.environ['BEAMLINE_ID'], maxBytes=5000000, backupCount=100)
myformat = logging.Formatter('%(asctime)s %(name)-8s %(levelname)-8s %(message)s')
handler1.setFormatter(myformat)
handler2.setFormatter(myformat)
logger.addHandler(handler1)
logger.addHandler(handler2)

perform_server_checks()
setBlConfig("visitDirectory", os.getcwd())
Expand Down
4 changes: 2 additions & 2 deletions daq_main_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ def pybass_init():

daq_utils.init_environment()
daq_lib.init_var_channels()
#if getBlConfig(config_params.DETECTOR_OBJECT_TYPE) == config_params.DETECTOR_OBJECT_TYPE_LSDC:
det_lib.init_detector()
if getBlConfig(config_params.DETECTOR_OBJECT_TYPE) != config_params.DETECTOR_OBJECT_TYPE_NO_INIT:
det_lib.init_detector()
daq_lib.message_string_pv = beamline_support.pvCreate(daq_utils.beamlineComm + "message_string")
daq_lib.gui_popup_message_string_pv = beamline_support.pvCreate(daq_utils.beamlineComm + "gui_popup_message_string")
beamline_lib.read_db()
Expand Down
8 changes: 7 additions & 1 deletion denso_robot.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ class OphydRobot:
def __init__(self, robot):
self.robot = robot

def parkRobot(self):
try:
self.robot.parkRobot()
except Exception as e:
logger.error(f'Failed to park robot: {e}')

def warmupGripper(self):
try:
logger.info('drying gripper')
Expand Down Expand Up @@ -83,7 +89,7 @@ def unmount(self, gov_robot, puck_pos: int, pin_pos: int, samp_id: str):
def finish(self):
...

def multiSampleGripper():
def multiSampleGripper(self):
return True

def check_sample_mounted(self, mount, puck_pos, pin_pos): # is the correct sample present/absent as expected during a mount/unmount?
Expand Down
Loading