Example #1
0
    def set(self, mode, config=None):
        """
        (Re)-initialize all devices based on given mode
        simulated devices <-- debug
        actual devices    <-- dryrun, production
        """
        if mode.lower() not in ['debug', 'dryrun', 'production']:
            raise ValueError(f"Unknown mode: {mode}")
        else:
            self._mode = mode
        
        global A_shutter
        global suspend_A_shutter
        global tomostage
        global preci, samX, ksamX, ksamZ, samY 
        global psofly
        global det

        # re-init all tomo related devices
        A_shutter = get_shutter(self._mode)
        tomostage = get_motors(self._mode) 
        preci     = tomostage.preci              
        samX      = tomostage.samX               
        ksamX     = tomostage.ksamX
        ksamZ     = tomostage.ksamZ        
        samY      = tomostage.samY               
        psofly    = get_fly_motor(self._mode)
        det       = get_detector(self._mode)

        # some quick sanity check production mode
        import apstools.devices as APS_devices
        aps = APS_devices.ApsMachineParametersDevice(name="APS")
        suspend_A_shutter = SuspendFloor(A_shutter.pss_state, 1)
        """
Example #2
0
    def __init__(self, mode='debug'):
        self.RE = bluesky.RunEngine({})
        self.db = databroker.Broker.named("mongodb_config")
        self.RE.subscribe(self.db.insert)
        self.RE.subscribe(BestEffortCallback())

        self._mode = mode
        from apstools.devices import ApsMachineParametersDevice
        self._aps = ApsMachineParametersDevice(name="APS")
        self.shutter = Experiment.get_shutter(mode)
        self.suspend_shutter = SuspendFloor(self.shutter.pss_state, 1)

        # monitor APS current
        self.suspend_APS_current = SuspendFloor(self._aps.current,
                                                2,
                                                resume_thresh=10)
        self.RE.install_suspender(self.suspend_APS_current)
Example #3
0
def imprint_row(*args, events=1, min_mj=0.5):
    """
    Run a single row of the imprint scan

    Parameters
    ----------
    args: passed to bluesky.plans.scan

    events: int
        The number of events to allow at each stopping point in the scan

    min_mj, float, optional
        The minimum energy the scan should continue to execute scans

    Example
    -------
    .. code::

        # Run a scan from -1, 1 in 10 steps with 40 shots at each step
        RE(imprint_row(pi_x, -1, 1, 10, events=40))

        # Run a scan with two motors from -1, 1 and 12, 20, in five steps with
        # 10 shots at each step. Pause if we have less than 1 mJ in the GDET
        RE(imprint_row(pi_x, -1, 1,
                       cxi.kb2.hl, 12, 20, 5,
                       events=10, min_mj=1))
    """
    # Create a new suspender
    suspender = SuspendFloor(beam_stats.mj_avg, min_mj)
    # Install the new suspender
    yield from bps.install_suspender(suspender)
    # Execute a scan first configuring the Sequencer and DAQ
    try:
        yield from sequencer_mode(daq, sequencer, events, sequence_wait=.25)
        yield from bp.scan([daq, sequencer], *args)
    # Always ensure we remove the suspender
    finally:
        yield from bps.remove_suspender(suspender)
Example #4
0
def get_shutter(mode='debug'):
    """
    return
        simulated shutter <-- dryrun, debug
        acutal shutter    <-- production
    """
    import apstools.devices as APS_devices
    aps = APS_devices.ApsMachineParametersDevice(name="APS")

    if mode.lower() in ['debug', 'dryrun']:
        A_shutter = APS_devices.SimulatedApsPssShutterWithStatus(name="A_shutter")
    elif mode.lower() == 'production':
        A_shutter = APS_devices.ApsPssShutterWithStatus(
            _devices['A_shutter'][0],
            _devices['A_shutter'][1],
            name="A_shutter",
        )
        suspend_APS_current = SuspendFloor(aps.current, 2, resume_thresh=10)
        RE.install_suspender(suspend_APS_current)
    else:
        raise ValueError(f"🙉: invalide mode, {mode}")

    return A_shutter
Example #5
0
# be placed in this file. You may instead wish to make a copy of this file in
# the user's data directory, and use that as a working copy.
################################################################################



from ophyd import EpicsSignal
from bluesky.suspenders import SuspendFloor, SuspendCeil


if True:
    # Define suspenders to hold data collection if x-ray
    # beam is not available.
    
    ring_current = EpicsSignal('SR:OPS-BI{DCCT:1}I:Real-I')
    sus = SuspendFloor(ring_current, 100, resume_thresh=101)
    RE.install_suspender(sus)

    #absorber_pos = EpicsSignal( 'XF:11BMB-ES{SM:1-Ax:ArmR}Mtr.RBV')
    #sus_abs_low = SuspendFloor(absorber_pos, -56, resume_thresh=-55)
    #sus_abs_hi = SuspendCeil(absorber_pos, -54, resume_thresh=-55)
    #RE.install_suspender(sus_abs_low)
    #RE.install_suspender(sus_abs_hi)

    #RE.clear_suspenders()


if False:
    # The following shortcuts can be used for unit conversions. For instance,
    # for a motor operating in 'mm' units, one could instead do:
    #     sam.xr( 10*um )
Example #6
0
print(__file__)
from bluesky.suspenders import (SuspendBoolHigh, SuspendBoolLow, SuspendFloor,
                                SuspendCeil, SuspendInBand, SuspendOutBand)

fe_shut_suspender = SuspendBoolHigh(EpicsSignalRO(shutter_fe.status.pvname),
                                    sleep=10 * 60)
ph_shut_suspender = SuspendBoolHigh(EpicsSignalRO(shutter_ph.status.pvname),
                                    sleep=10 * 60)

# suspender for beamline current is mA
beam_current_suspender = SuspendFloor(nsls_ii.beam_current,
                                      suspend_thresh=300,
                                      sleep=10 * 60)
suspenders = [
    fe_shut_suspender,
    ph_shut_suspender,
    beam_current_suspender,
]
''' Some help on suspenders /bluesky
# how to add a suspender:
# Method 1:
# RE.install_suspender(fe_shut_suspender)
# Method 2 (in the plan):
# RE(bpp.suspend_wrapper(myplan(), [suspenders]))




# general bluesky info
# blue sky plans (mostly) reside here:
# general plans
import bluesky.plans as bp
from bluesky.suspenders import SuspendFloor, SuspendBoolLow, SuspendBoolHigh, SuspendCeil

print(f'Loading {__file__}')

# Count on XBPM2 suspender
susp_xbpm2_sum = SuspendFloor(xbpm2.sumY, 0.3, resume_thresh=0.8)
RE.install_suspender(susp_xbpm2_sum)

# Ring current suspender
susp_beam = SuspendFloor(ring.current, 100, resume_thresh=350)
RE.install_suspender(susp_beam)

# Front end shutter suspender
susp_smi_shutter = SuspendFloor(smi_shutter_enable, 0.1, resume_thresh=0.9)
RE.install_suspender(susp_smi_shutter)

# to clear suspenders RE.clear_suspenders()
Example #8
0
def set_beamdump_suspender(xrun,
                           suspend_thres=None,
                           resume_thres=None,
                           wait_time=None,
                           clear=True):
    """helper function to set suspender based on ring_current

    Parameters
    ----------
    xrun : instance of RunEngine
        the run engine instance suspender will be installed
    suspend_thres : float, optional
        suspend if ring current value falls below this threshold. ring
        current value is read out from ring current signal when
        set_beamdump_suspender function is executed. default is the
        larger value between 50 mA or 50% of ring current
    resume_thres : float, optional
        resume if ring current value falls below this threshold. ring
        current value is read out from ring current signal when
        set_beamdump_suspender function is executed. default is the
        larger value among 50 mA or 80% of current ring current
    wait_time : float, optional
        wait time in seconds after the resume condition is met. default
        is 1200s (20 mins)
    clear : bool, optional
        option on whether to clear all the existing suspender(s).
        default is True (only newly added suspender will be applied)
    """
    signal = xpd_configuration.get("ring_current", None)
    if signal is None:
        # edge case, attribute is accidentally removed
        raise RuntimeError("no ring current signal is found in "
                           "current configuration, please reach out to "
                           "local contact for more help.")
    signal_val = signal.get()
    default_suspend_thres = 50
    default_resume_thres = 50
    if suspend_thres is None:
        suspend_thres = max(default_suspend_thres, 0.5 * signal_val)
    if resume_thres is None:
        resume_thres = max(default_resume_thres, 0.8 * signal_val)
    if wait_time is None:
        wait_time = 1200
    if suspend_thres <= 50:
        warnings.warn(
            "suspender set when beam current is low.\n"
            "For the best operation, run:\n"
            ">>> {}\n"
            "when beam current is at its full value."
            "To interrogate suspenders have"
            " been installed, please run :\n"
            ">>> {}\n".format("set_suspender(xrun)", "xrun.suspenders"),
            UserWarning,
        )
    sus = SuspendFloor(signal,
                       suspend_thres,
                       resume_thresh=resume_thres,
                       sleep=wait_time)
    if clear:
        xrun.clear_suspenders()
    xrun.install_suspender(sus)
    print("INFO: suspender on signal {}, with suspend threshold {} and "
          "resume threshold={}, wait time ={}s has been installed.\n".format(
              signal.name, suspend_thres, resume_thres, wait_time))
Example #9
0
# Shutter
from bluesky.suspenders import SuspendFloor
print("Setting up Shutter")
if in_production:
    # define the real shutter used at 6BMA@APS
    # NOTE:
    #   this requires connection to the hardware, otherwise a connection error will be raised

    A_shutter = APS_devices.ApsPssShutterWithStatus(
        "6bmb1:rShtrA:",
        "PA:06BM:STA_A_FES_OPEN_PL",
        name="A_shutter",
    )
    A_shutter.pss_state
    # no scans until A_shutter is open
    suspend_A_shutter = SuspendFloor(A_shutter.pss_state, 1)
    # NOTE:
    # since tomo scan take dark field images with shutter colosed, the
    # suspender installation for A_shutter is located in the plan for
    # granular control.

    # no scans if aps.current is too low
    suspend_APS_current = SuspendFloor(aps.current, 2, resume_thresh=10)
    RE.install_suspender(suspend_APS_current)

else:
    # for testing during dark time (no beam, shutter closed by APS)
    A_shutter = APS_devices.SimulatedApsPssShutterWithStatus(name="A_shutter")
    suspend_A_shutter = SuspendFloor(A_shutter.pss_state, 1)
    print("---use simulated shutter---")
from bluesky.suspenders import SuspendFloor, SuspendBoolHigh

ring_suspender = SuspendFloor(ring_curr,
                              190,
                              resume_thresh=200,
                              sleep=120,
                              post_plan=beamline_align_v2)

#shutterb_suspender = SuspendBoolHigh(EpicsSignalRO(shutterb.status.pvname), sleep=5, post_plan=beamline_align_v2)

# Is this the right PV???
#fe_shut_suspender = SuspendBoolHigh(EpicsSignal('XF:02ID-PPS{Sh:FE}Enbl-Sts'), sleep=20*60)

## It needs:
## RE.install_suspender(test_shutsusp)
## RE.remove_suspender(test_shutsusp)

RE.install_suspender(ring_suspender)
#RE.install_suspender(shutterb_suspender)

print("")
print(
    "You can safely ignore the 'SuspendOutBand' warning - this is a known issue that is fixed in a newer version."
)
Example #11
0
from bluesky.suspenders import (SuspendFloor, SuspendCeil, SuspendBoolHigh,
                                SuspendBoolLow)
import bluesky.plans as bp
import bluesky.plan_stubs as bps
import bluesky.preprocessors as bpp


def shuttergenerator(shutter, value):
    return (yield from bpp.rewindable_wrapper(bps.mv(shutter, value), False))


# Ring current suspender
susp_rc = SuspendFloor(ring_current,
                       140,
                       resume_thresh=160,
                       sleep=10 * 60,
                       pre_plan=list(shuttergenerator(shut_b, 'Close')),
                       post_plan=list(shuttergenerator(shut_b, 'Open')))

# Cryo cooler suspender
susp_cryo = SuspendCeil(cryo_v19,
                        0.8,
                        resume_thresh=0.2,
                        sleep=15 * 60,
                        pre_plan=list(shuttergenerator(shut_b, 'Close')),
                        post_plan=list(shuttergenerator(shut_b, 'Open')))

# Shutter status suspender
susp_shut_fe = SuspendBoolHigh(EpicsSignalRO(shut_fe.status.pvname), sleep=10)
susp_shut_a = SuspendBoolHigh(EpicsSignalRO(shut_a.status.pvname), sleep=10)
susp_shut_b = SuspendBoolHigh(EpicsSignalRO(shut_b.status.pvname), sleep=10)
Example #12
0
class glbl():
    beamline_host_name = BEAMLINE_HOST_NAME
    base = BASE_DIR
    home = HOME_DIR
    _export_tar_dir = _EXPORT_TAR_DIR
    xpdconfig = BLCONFIG_DIR
    import_dir = IMPORT_DIR
    config_base = CONFIG_BASE
    tiff_base = TIFF_BASE
    usrScript_dir = USERSCRIPT_DIR
    yaml_dir = YAML_DIR
    allfolders = ALL_FOLDERS
    archive_dir = USER_BACKUP_DIR
    dk_yaml = DARK_YAML_NAME
    dk_window = DARK_WINDOW
    frame_acq_time = FRAME_ACQUIRE_TIME
    auto_dark = True
    owner = OWNER
    beamline_id = BEAMLINE_ID
    group = GROUP
    _allowed_scanplan_type = ALLOWED_SCANPLAN_TYPE

    # logic to assign correct objects depends on simulation or real experiment
    if not simulation:
        from bluesky.run_engine import RunEngine
        from bluesky.register_mds import register_mds
        # import real object as other names to avoid possible self-referencing later
        from bluesky import Msg as msg
        from bluesky.plans import Count as count
        from bluesky.plans import AbsScanPlan as absScanPlan
        from databroker import DataBroker
        from databroker import get_images as getImages
        from databroker import get_events as getEvents
        from bluesky.callbacks import LiveTable as livetable
        from bluesky.callbacks.broker import verify_files_saved as verifyFiles
        from ophyd import EpicsSignalRO, EpicsSignal
        from bluesky.suspenders import SuspendFloor
        ring_current = EpicsSignalRO('SR:OPS-BI{DCCT:1}I:Real-I',
                                     name='ring_current')
        xpdRE = RunEngine()
        xpdRE.md['owner'] = owner
        xpdRE.md['beamline_id'] = beamline_id
        xpdRE.md['group'] = group
        register_mds(xpdRE)
        beamdump_sus = SuspendFloor(ring_current,
                                    ring_current.get() * 0.9,
                                    resume_thresh=ring_current.get() * 0.9,
                                    sleep=1200)
        #xpdRE.install_suspender(beamdump_sus) # don't enable it untill beam is back
        # real imports
        Msg = msg
        Count = count
        db = DataBroker
        LiveTable = livetable
        get_events = getEvents
        get_images = getImages
        AbsScanPlan = absScanPlan
        verify_files_saved = verifyFiles
        # real collection objects
        area_det = None
        temp_controller = None
        shutter = None

    else:
        simulation = True
        ARCHIVE_BASE_DIR = os.path.join(BASE_DIR, 'userSimulationArchive')
        # mock imports
        Msg = MagicMock()
        Count = MagicMock()
        AbsScanPlan = MagicMock()
        db = MagicMock()
        get_events = MagicMock()
        get_images = MagicMock()
        LiveTable = mock_livetable
        verify_files_saved = MagicMock()
        # mock collection objects
        xpdRE = MagicMock()
        temp_controller = MagicMock()
        shutter = mock_shutter()
        area_det = MagicMock()
        area_det.cam = MagicMock()
        area_det.cam.acquire_time = MagicMock()
        area_det.cam.acquire_time.put = MagicMock(return_value=0.1)
        area_det.cam.acquire_time.get = MagicMock(return_value=0.1)
        area_det.number_of_sets = MagicMock()
        area_det.number_of_sets.put = MagicMock(return_value=1)
        print('==== Simulation being created in current directory:{} ===='.
              format(BASE_DIR))
Example #13
0
from bluesky.suspenders import SuspendFloor, SuspendBoolHigh, SuspendBoolLow

from BMM import user_ns as user_ns_module

user_ns = vars(user_ns_module)

from BMM.user_ns.detectors import quadem1
from BMM.user_ns.metadata import ring
from BMM.user_ns.instruments import bmps, idps

#RE.clear_suspenders()
all_BMM_suspenders = list()

## ----------------------------------------------------------------------------------
## suspend when I0 drops below 0.1 nA (not in use)
suspender_I0 = SuspendFloor(quadem1.I0, 0.1, resume_thresh=1, sleep=5)
#all_BMM_suspenders.append(suspender_I0)

## ----------------------------------------------------------------------------------
## suspend upon beam dump, resume 30 seconds after hitting 90% of fill target
try:
    if ring.filltarget.connected is True and ring.filltarget.get() > 20:
        suspender_ring_current = SuspendFloor(ring.current,
                                              10,
                                              resume_thresh=0.9 *
                                              ring.filltarget.get(),
                                              sleep=60)
        all_BMM_suspenders.append(suspender_ring_current)
except Exception as e:
    print(f'failed to create ring current suspender: {e}')
    pass
Example #14
0
""" 
suspender.py

callbacks that suspend operation (beam low, )
"""

from ..framework import RE
from ..devices.misc_devices import I1
from ..plans import tablev_scan

from bluesky.suspenders import SuspendFloor

susp_tablev = SuspendFloor(I1, 
                            suspend_thresh=0.1,
                            resume_thresh=0.5,
                            sleep=20
                            tripped_message='I1 dropped below threshold',
                            pre_plan=tablev_scan)

RE.install_suspender(tablev_suspender)
Example #15
0
from bluesky.suspenders import (SuspendBoolHigh, SuspendBoolLow, SuspendFloor,
                                SuspendCeil, SuspendInBand, SuspendOutBand)

from ophyd import EpicsSignal
from .startup import RE

ring_suspender = SuspendFloor(EpicsSignal('XF:23ID-SR{}I-I'),
                              250,
                              sleep=3 * 60)
#ring_suspender = SuspendFloor(EpicsSignal('XF:23ID-SR{}I-I'), 250, sleep=3)
fe_shut_suspender = SuspendBoolHigh(EpicsSignal('XF:23ID-PPS{Sh:FE}Pos-Sts'),
                                    sleep=10 * 60)
#fe_shut_suspender = SuspendBoolHigh(EpicsSignal('XF:23ID-PPS{Sh:FE}Pos-Sts'), sleep=3)
ps1_shut_suspender = SuspendBoolHigh(EpicsSignal('XF:23IDA-PPS:1{PSh}Pos-Sts'),
                                     sleep=5 * 60)
#ps1_shut_suspender =  SuspendBoolHigh(EpicsSignal('XF:23IDA-PPS:1{PSh}Pos-Sts'),sleep=3)

RE.install_suspender(ring_suspender)
RE.install_suspender(fe_shut_suspender)
RE.install_suspender(ps1_shut_suspender)
from bluesky.suspenders import (SuspendFloor, SuspendBoolHigh, SuspendBoolLow)


# Here are some conditions that will cause scans to pause automatically:
# - when the beam current goes below a certain threshold
susp_current = SuspendFloor(beamline_status.beam_current,
                            suspend_thresh=150.0,
                            resume_thresh=240.0,
                            tripped_message='beam current too low',
                            )

# - when the shutter is closed
susp_shutter = SuspendBoolLow(beamline_status.shutter_status,
                              tripped_message='shutter not open',
                              )

# - if the beamline isn't enabled
susp_enabled = SuspendBoolLow(beamline_status.beamline_enabled,
                              tripped_message='beamline is not enabled',
                              )

susp_cryo_cooler = SuspendBoolHigh(beamline_status.cryo_filling,
                                   sleep=60*10,
                                   tripped_message='Cyrocooler is refilling')

# NOTE: to enable or disable the suspenders, (un)comment the following:
# Install all suspenders:
#RE.install_suspender(susp_current)
#RE.install_suspender(susp_shutter)
#RE.install_suspender(susp_enabled)
#RE.install_suspender(susp_cryo_cooler)
Example #17
0
from bluesky.suspenders import SuspendFloor

ring_suspender = SuspendFloor(ring_curr, 290, resume_thresh=300, sleep=120)
# Is this the right PV???
#fe_shut_suspender = SuspendBoolHigh(EpicsSignal('XF:02ID-PPS{Sh:FE}Enbl-Sts'), sleep=20*60)

## It needs:
## RE.install_suspender(test_shutsusp)
## RE.remove_suspender(test_shutsusp)

RE.install_suspender(ring_suspender)
#RE.install_suspender(fe_shut_suspender)

print("")
print(
    "You can safely ignore the 'SuspendOutBand' warning - this is a known issue that is fixed in a newer version."
)
Example #18
0
def test_suspenders_stress(RE):
    """
    Run scan with tons of inconvenient suspenders
    """
    sig = Signal(name="dull signal")

    def pre_plan(*args, **kwargs):
        yield from null()
        logger.debug("starting suspender")

    def post_plan(*args, **kwargs):
        yield from null()
        logger.debug("releasing suspender")

    suspenders = [
        SuspendFloor(sig, i, sleep=10, pre_plan=pre_plan, post_plan=post_plan)
        for i in range(10)
    ]
    for s in suspenders:
        RE.install_suspender(s)
    mot = SlowSoftPositioner(n_steps=1000,
                             delay=0.001,
                             position=0,
                             name='test_mot')

    def sig_sequence(sig):
        sig.put(15)
        time.sleep(1)
        sig.put(9)
        logger.debug('expect suspend soon')
        time.sleep(1)
        sig.put(8)
        logger.debug('expect second suspend layer')
        time.sleep(1)
        sig.put(14)
        logger.debug('expect resume after 1 second')
        time.sleep(2)
        sig.put(2)
        logger.debug('expect many layered suspend')
        time.sleep(1)
        sig.put(15)
        logger.debug('expect resume after 1 second')
        time.sleep(2)
        sig.put(-10)
        logger.debug('expect to confuse the scan now')
        sig.put(10)
        sig.put(3)
        sig.put(5)
        sig.put(456)
        sig.put(0)
        sig.put(23)
        sig.put(0)
        sig.put(15)
        logger.debug('hopefully it suspended and is now waiting to resume')
        # 3 suspends and 3 resumes should add 6s to the scan

    @run_decorator()
    def dull_scan(mot, count, sig=None, sleep_time=0):
        if sig:
            thread = threading.Thread(target=sig_sequence, args=(sig, ))
            thread.start()
        for i in range(count):
            yield from checkpoint()
            try:
                yield from mv(mot, i)
            except:
                pass
            # make every step take 1s extra
            yield from sleep(sleep_time)
            yield from checkpoint()
            yield from create()
            yield from read(mot)
            yield from save()
            yield from checkpoint()

    out = []
    coll = collector("test_mot", out)
    RE.subscribe('event', coll)

    base_start = time.time()
    RE(dull_scan(mot, 10, sleep_time=1))
    base_elapsed = time.time() - base_start

    susp_start = time.time()
    RE(dull_scan(mot, 10, sig=sig, sleep_time=1))
    susp_elapsed = time.time() - susp_start

    assert susp_elapsed - base_elapsed > 6
Example #19
0
from ophyd import EpicsSignal
from bluesky.suspenders import SuspendFloor
from bluesky.suspenders import SuspendCeil
#from bluesky.suspenders import SuspendBoolLow

BEAM_RECOVER_TIME = 30  #Time in seconds
BEAM_THRES = 300
beam_current = EpicsSignal('SR:OPS-BI{DCCT:1}I:Real-I')

beam_current_sus = SuspendFloor(beam_current,
                                BEAM_THRES,
                                sleep=BEAM_RECOVER_TIME)


def install_beam_suspender():
    RE.install_suspender(beam_current_sus)


def uninstall_beam_suspender():
    RE.remove_suspender(beam_current_sus)


bpm_xpos = EpicsSignal('XF:16IDB-CT{Best}:BPM0:PosX_Mean')
BPM_X_THRES_HIGH = 20
BPM_X_THRES_LOW = -20

BPM_RECOVER_TIME = 5

bpm_ypos = EpicsSignal('XF:16IDB-CT{Best}:BPM0:PosY_Mean')
BPM_Y_THRES_HIGH = 20
BPM_Y_THRES_LOW = -20
Example #20
0
from bluesky.suspenders import SuspendFloor, SuspendBoolHigh, SuspendBoolLow
from IPython import get_ipython

user_ns = get_ipython().user_ns

#RE.clear_suspenders()
all_BMM_suspenders = list()

## ----------------------------------------------------------------------------------
## suspend when I0 drops below 0.1 nA (not in use)
suspender_I0 = SuspendFloor(user_ns['quadem1'].I0,
                            0.1,
                            resume_thresh=1,
                            sleep=5)
#all_BMM_suspenders.append(suspender_I0)

## ----------------------------------------------------------------------------------
## suspend upon beam dump, resume 30 seconds after hitting 90% of fill target
try:
    if user_ns['ring'].filltarget.get() > 20:
        suspender_ring_current = SuspendFloor(user_ns['ring'].current,
                                              10,
                                              resume_thresh=0.9 *
                                              user_ns['ring'].filltarget.get(),
                                              sleep=60)
        all_BMM_suspenders.append(suspender_ring_current)
except:
    pass

## ----------------------------------------------------------------------------------
## suspend if the BM photon shutter closes, resume 5 seconds after opening