# -*- mode: python; tab-width: 4; indent-tabs-mode: nil -*-
from gpstime import gpstime
from datetime import datetime
import os
import json
import math
import time
import cdsutils as cdu
import switch_SDF_source_files as switch_SDF
import lscparams
import ISC_GEN_STATES
import ISC_library
import susconst
import cdslib
import numpy as np
from timeout_utils import call_with_timeout
from guardian import GuardState, GuardStateDecorator, NodeManager

##################################################
# NODES
##################################################

nodes = NodeManager(['ISC_DRMI',
                     'ALS_COMM',
                     'ALS_DIFF',
                     'ALS_YARM',
                     'ALS_XARM',
                     'ALIGN_IFO',
                     'OMC_LOCK',
                     'IMC_LOCK',
                     'SEI_BS',
                     'LASER_PWR',
                     'TCS_ITMX_CO2_PWR',
                     'TCS_ITMY_CO2_PWR',
                     'SUS_PI',
                     'PSL_FSS',
                     'FAST_SHUTTER',
                     'VIOLIN_DAMPING',
                     'SQZ_MANAGER',
                     'AWG_LINES',
                     'INIT_ALIGN',
                     'TMS_SERVO',
                     'NOISE_CLEAN',
                     'CAMERA_SERVO',
                     'THERMALIZATION'
                     ])

##################################################
# GLOBALS:
#################################################
##################################################
# STATES: INIT / DOWN
##################################################
nominal = 'NOMINAL_LOW_NOISE'

class INIT(GuardState):
    request = True

    def main(self):
        log('Initializing subordinate nodes:')
        nodes.set_managed()
        return True
        #return 'PREP_DC_READOUT_TRANSITION'
        
class IDLE(GuardState):

    def run(self):
        return True

class LOCKLOSS(GuardState):
    index = 2
    request = False
    redirect = False

    def main(self):
        nodes['PSL_FSS'] = 'READY_FOR_MC_LOCK'
    def run(self):
        return 'DOWN'

class LOCKLOSS_DRMI(GuardState):
    index = 3
    request = False
    redirect = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    def run(self):
        return True

class LOCKLOSS_PRMI(GuardState):
    index = 4
    request = False
    redirect = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    def run(self):
        return True

class INITIAL_ALIGNMENT(GuardState):
    index = 7
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    def main(self):
        # Release IMC_LOCK and PSL_FSS so they wont stall
        nodes.release(nodes=['IMC_LOCK'])
        nodes.release(nodes=['PSL_FSS'])
        ezca['SYS-MOTION_X_SHUTTER_A_OPEN'] = 1 # Open X and Y ALS shutters TVo 20181111
        ezca['SYS-MOTION_Y_SHUTTER_B_OPEN'] = 1
        nodes['TCS_ITMX_CO2_PWR'] = 'NO_OUTPUT'
        nodes['TCS_ITMY_CO2_PWR'] = 'NO_OUTPUT'
        nodes['INIT_ALIGN'] = 'INIT'
        #nodes['ALS_XARM'] = 'UNLOCKED'
        #nodes['ALS_YARM'] = 'UNLOCKED'
        #nodes['ALIGN_IFO'] = 'SET_SUS_FOR_ALS_FPMI'
        self.counter = 0
        self.timer['set_request'] = 2

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    # TJ Jun 25 2019 - Removed because it will unstall ALIGN_IFO with the ISC_LOCK last request,
    # not the INIT_ALIGN last request.
    #@ISC_library.unstall_nodes(nodes)
    def run(self):
        # Added the timer above because it would move too fast
        if self.timer['set_request'] and self.counter == 0:
            nodes['INIT_ALIGN'] = 'INIT_ALIGN_COMPLETE'
            self.counter +=1
        elif self.counter == 1 and nodes['INIT_ALIGN'].arrived and nodes['INIT_ALIGN'].done:
            return True
        #if nodes['ALIGN_IFO'].arrived and self.counter == 0:
        #    # Set the nodes that are IA to AUTO
        #    # ALIGN_IFO will grab and manage them in its own state (not done from ISC_LOCK)
        #    for node in IA_node_list:
        #        ezca['GRD-{}_MODE'.format(node)] = 'AUTO'
        #    self.counter +=1
        #if self.counter == 1:
        #    return True

class MANUAL_INITIAL_ALIGNMENT(GuardState):
    index = 8
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    def main(self):
        # Release IMC_LOCK and PSL_FSS so they wont stall
        nodes.release(nodes=['IMC_LOCK'])
        nodes.release(nodes=['PSL_FSS'])
        nodes.release(nodes=['INIT_ALIGN'])
        nodes.release(nodes=['ALIGN_IFO'])
        ezca['SYS-MOTION_X_SHUTTER_A_OPEN'] = 1 # Open X and Y ALS shutters TVo 20181111
        ezca['SYS-MOTION_Y_SHUTTER_B_OPEN'] = 1
        nodes['TCS_ITMX_CO2_PWR'] = 'NO_OUTPUT'
        nodes['TCS_ITMY_CO2_PWR'] = 'NO_OUTPUT'
        nodes['INIT_ALIGN'] = 'INIT'
        #nodes['ALS_XARM'] = 'UNLOCKED'
        #nodes['ALS_YARM'] = 'UNLOCKED'
        #nodes['ALIGN_IFO'] = 'SET_SUS_FOR_ALS_FPMI'
        self.counter = 0
        self.timer['set_request'] = 2

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    # TJ Jun 25 2019 - Removed because it will unstall ALIGN_IFO with the ISC_LOCK last request,
    # not the INIT_ALIGN last request.
    #@ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.counter == 1:
            return True


class DOWN(GuardState):
    index = 10
    goto = False
    redirect = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):

        # This state is for things that must be done immediately.
        # Things that are not time-sensitive should go in PREP_FOR_LOCKING

        # Turn off the TM violin, bounce, roll mode damping (violin and bounce mode should be triggered in LSC model)
        for optic in ['ITMX','ETMY','ETMX','ITMY']:
            for mode in range(1,41):
                ezca.get_LIGOFilter('SUS-%s_L2_DAMP_MODE%s'%(optic,mode)).ramp_gain(0, ramp_time=0.2,wait=False)
            for mode in ['V','R']:
                ezca.get_LIGOFilter('SUS-%s_M0_DARM_DAMP_%s'%(optic,mode)).ramp_gain(0, ramp_time=0, wait=False)

        #request other guardians to go to the right state, this takes about 0.2 seconds
        #the guardians which need to go down quickly are listed first
        nodes['ALS_COMM']         = 'DOWN'  #SED is trying a test, after we do initial alignment ALS_COMM is already in DOWN, so the code doesn't get re-run.  This way it will be rerun
        if lscparams.ALS_ESD == 'Y':
            nodes['ALS_DIFF_ETMY_ESD'] = 'DOWN'
        else:
            nodes['ALS_DIFF']     = 'DOWN'
        nodes['ISC_DRMI']         = 'DOWN'
        nodes['OMC_LOCK']         = 'DOWN'
        nodes['IMC_LOCK']         = 'LOCKED'
        nodes['ALS_XARM']         = 'UNLOCKED'
        nodes['ALS_YARM']         = 'UNLOCKED'
        nodes['LASER_PWR']        = 'POWER_2W'
        nodes['PSL_FSS']          = 'READY_FOR_MC_LOCK'
        nodes['TCS_ITMX_CO2_PWR'] = 'NO_OUTPUT'
        nodes['TCS_ITMY_CO2_PWR'] = 'NO_OUTPUT'
        nodes['FAST_SHUTTER']     = 'DOWN'
        nodes['VIOLIN_DAMPING']   = 'TURN_OFF_DAMPING_ALL'
        nodes['AWG_LINES']        = 'IDLE'
        nodes['TMS_SERVO']        = 'TMS_SERVO_OFF'
        nodes['NOISE_CLEAN']      = 'DOWN'
        nodes['CAMERA_SERVO']     = 'DOWN'
        nodes['SUS_PI']           = 'IFO_DOWN'
        if not nodes['SQZ_MANAGER'].state == 'NO_SQUEEZING':
            nodes['SQZ_MANAGER'] = 'DOWN'

        #turn off arm asc loops
        for arm_loop in ['DHARD', 'CHARD', 'DSOFT', 'CSOFT']:
            for py in ['P', 'Y']:
                ezca.get_LIGOFilter('ASC-{}_{}'.format(arm_loop, py)).switch_off('INPUT', 'OFFSET', wait=False)
                ezca.get_LIGOFilter('ASC-{}_{}'.format(arm_loop, py)).ramp_gain(0, ramp_time=1, wait=False)

        # disable feedforward
        ezca['LSC-MICHFF_GAIN'] = 0
        ezca['LSC-PRCLFF_GAIN'] = 0
        ezca['LSC-SRCLFF1_GAIN'] = 0
        ezca['LSC-SRCLFF2_GAIN'] = 0
        ezca['LSC-CPSFF_GAIN']=0
        # EMC adding HAM1 FF to disable list
        ezca['HPI-HAM1_TTL4C_FF_OUTSW'] = 0

        #ramp down LSC feedback (some of these are only relevant to locklosses from some states)
        ezca.get_LIGOFilter('LSC-REFLBIAS').switch('FMALL', 'OFF', wait=False)
        ezca.get_LIGOFilter('LSC-REFLBIAS').switch('FM9', 'FM3', 'ON', wait=False)
        ezca['ALS-C_REFL_DC_BIAS_GAIN'] = 0

        #ramp down LSC filters in sus that have integrators (only DARM, IMC_LOCK does MC2 and ISC_DRMI does DRMI)
        ezca['SUS-ETMX_L1_LOCK_L_GAIN']=0
        ezca['SUS-ETMY_L1_LOCK_L_GAIN']=0

        #DC centering, DRMI ASC, and OMC ASC are done by DRMI guardian
        # ETM, ITM, TMS top mass relief off
        for dof in ['P', 'Y']:
            for optic in ['ETMX', 'ETMY', 'ITMX', 'ITMY']:
                ezca['SUS-{}_M0_LOCK_{}_GAIN'.format(optic, dof)] = 0
                ezca.get_LIGOFilter('SUS-{}_M0_LOCK_{}'.format(optic, dof)).switch_off('INPUT')
            for optic in ['TMSX', 'TMSY']:
                ezca['SUS-{}_M1_LOCK_{}_GAIN'.format(optic,dof)] = 0
                ezca.get_LIGOFilter('SUS-{}_M1_LOCK_{}'.format(optic, dof)).switch_off('INPUT')

        #now clear history of QUAD + TMS top masses, arm asc loops
        ezca['ASC-CHARD_P_B_GAIN']=0
        ezca['ASC-CHARD_Y_B_GAIN']=0

        for dof in ['P', 'Y']:
            for arm_loop in ['DHARD', 'CHARD', 'DSOFT', 'CSOFT']:
                ezca['ASC-{}_{}_RSET'.format(arm_loop, dof)] = 2
                ezca['ASC-RPC_{}_{}_GAIN'.format(arm_loop, dof)] = 0
            for optic in ['ETMX', 'ETMY', 'ITMX', 'ITMY']:
                ezca['SUS-{}_M0_LOCK_{}_RSET'.format(optic, dof)] =2
            for optic in ['TMSX', 'TMSY']:
                ezca['SUS-{}_M1_LOCK_{}_RSET'.format(optic, dof)] = 2

        # Clear history of OMs, RMs for DC centering
        for dof in ['P', 'Y']:
            for optic in ['RM1','RM2','OM1','OM2']:
                ezca['SUS-{}_M1_LOCK_{}_RSET'.format(optic, dof)] = 2

        # turn off ISIFF
        for tm in ['ETMX', 'ETMY', 'ITMX', 'ITMY']:
            ezca['SUS-%s_M0_ISIFF_L2P_TRAMP'%tm]=10
            ezca['SUS-%s_M0_ISIFF_L2P_GAIN'%tm]=0

        # turn of squeezer ASC
        ezca['SQZ-ASC_WFS_SWITCH'] = 0
        for py in ['P', 'Y']:
            for ii in range(1,11):
                ezca['SQZ-ASC_POS_{}_RSET'.format(py)] = 0
                ezca['SQZ-ASC_ANG_{}_RSET'.format(py)] = 0
        # turn off ads ditherings
        for py in ['PIT', 'YAW']:
            for ii in range(1,11):
                ezca['ASC-ADS_{}{}_DOF_GAIN'.format(py, ii)] = 0
                ezca['ASC-ADS_{}{}_OSC_CLKGAIN'.format(py, ii)] = 0

        for py in ['PIT', 'YAW']:
            for ii in range(1,11):
                ezca['ASC-ADS_{}{}_DOF_RSET'.format(py, ii)] = 2

        # turn off SRCL RETUNE Offset
        ezca.switch('LSC-SRCL1', 'OFFSET', 'OFF')

        # Reset HWS averaging time
        #commented out because channels were unavailable March 4 2022
        #ezca['TCS-ITMX_HWS_AVERAGE_DURATION'] = 20
        #ezca['TCS-ITMY_HWS_AVERAGE_DURATION'] = 20
        #ezca['TCS-ETMX_HWS_AVERAGE_DURATION'] = 20
        #ezca['TCS-ETMY_HWS_AVERAGE_DURATION'] = 20

        # turn off PRCL UGF servo and reset history
        ezca['LSC-PRCL_UGF_OSC_TRAMP'] = 2
        ezca['LSC-PRCL_UGF_OSC_CLKGAIN'] = 0
        ezca['LSC-PRCL_UGF_MASTER'] = 0
        ezca.switch('LSC-PRCL_UGF_SERVO', 'INPUT', 'OFF')
        ezca['LSC-PRCL_UGF_SERVO_RSET'] = 2


    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        # Jump straight to rest of setup stuff
        return 'PREP_FOR_LOCKING'


class PREP_FOR_LOCKING(GuardState):
    index = 9
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):

        nodes['OMC_LOCK']='OFF_RESONANCE'
        nodes['SEI_BS']='FULLY_ISOLATED_NO_ST2_BOOST' # 'ISOLATED_DAMPED' # JCD,JimW 24Mar2021s
        nodes['ALIGN_IFO'] = 'SET_SUS_FOR_ALS_FPMI'
        nodes['INIT_ALIGN'] = 'IDLE'
        nodes['THERMALIZATION'] = 'IDLE'
        #nodes['TCS_ITMY_CO2_PWR'] = 'NOM_CENTRAL_POWER' #central CO2 20230203 GLM # commented out by EMC same day after changing IY ring heater

        # Turn off intensity, frequency, and 9 MHz intensity lines
        ezca['LSC-OUTPUT_MTRX_11_9'] = 0
        ezca['LSC-MOD_RF9_AM_EXCITATIONEN'] = 0
        ezca['LSC-MOD_RF45_AM_EXCITATIONEN'] = 0
        ezca['LSC-REFL_SERVO_COMEXCEN'] = 0
        ezca['PSL-ISS_SECONDLOOP_EXCITATION_GAIN'] = 0
        # Switch back to safe SDF source files
        switch_SDF.set_source_files('safe')

        # Dump the SDF diffs to a log file
        #start_dsd = ezca['DAQ-DC0_GPS']
        #dsd = ISC_library.DUMP_SDF_DIFFS()
        #dsd.run()
        #end_dsd = ezca['DAQ-DC0_GPS']
        #log(f'It took {(start_dsd-end_dsd)} seconds to run the DUMP diffs function')
        ezca['VID-CAM16_EXP'] = lscparams.camera_exp['ASAIR']

        # Turn ALS corner station VCOs back on so we can lock
        ezca['ALS-C_DIFF_VCO_POWERDISABLE'] = 0 # 0 = on = not disabled.
        ezca['ALS-C_DIFF_FDD_POWERDISABLE'] = 0
        ezca['ALS-C_COMM_VCO_POWERDISABLE'] = 0 # 0 = on = not disabled.
        ezca['ALS-C_COMM_FDD_POWERDISABLE'] = 0


        #turn off CHARD BLENDING  (does this really need to happen here? seems like it could be later)
        ezca['ASC-CHARD_P_B_GAIN']=0
        ezca['ASC-CHARD_Y_B_GAIN']=0
        ezca.get_LIGOFilter('ASC-CHARD_Y_A').only_on('INPUT', 'OUTPUT', 'DECIMATION')
        ezca.get_LIGOFilter('ASC-CHARD_P_A').only_on('INPUT', 'OUTPUT', 'DECIMATION')

        # Zero ASC inmtrx for clean error signal StripTool
        for dof in ['DHARD', 'CHARD', 'DSOFT_A', 'DSOFT_B','CSOFT_A', 'CSOFT_B', 'MICH', 'PRC1', 'PRC2', 'SRC1', 'SRC2','INP1']:
            for py in ['PIT','YAW']:
                ISC_library.asc_intrix[py].put(dof,[],0) # zero rows of input matrix

        # LSC settings
        ISC_library.intrix['REFLBIAS', 'TR_CARM'] = 0
        ISC_library.intrix['REFLBIAS', 'TR_REFL9'] = 0
        ISC_library.intrix.load()

        ISC_library.intrix_OMCAS45['DARM', 'ASAIR_A45Q'] = 0
        ISC_library.intrix_OMCAS45['DARM', 'OMCDC'] = 0
        ISC_library.intrix_OMCAS45['CARM', 'OMCDC'] = 0
        ezca['LSC-ARM_INPUT_MTRX_TRAMP'] = 0
        ISC_library.intrix_OMCAS45.load()

        ezca['LSC-DARM_TRIG_THRESH_ON']  = lscparams.thresh['DARM']['ON']
        ezca['LSC-DARM_TRIG_THRESH_OFF'] = lscparams.thresh['DARM']['OFF']
        ISC_library.trigrix.put([],'POP_A_DC',0)
        ISC_library.trigrix.put([],'POPAIR_A_DC',0)

        # Turn off any oscillators to LSC
        ezca['LSC-LOCKIN_1_OSC_CLKGAIN'] = 0
        ezca['LSC-LOCKIN_2_OSC_CLKGAIN'] = 0
        ezca['LSC-LOCKIN_3_OSC_CLKGAIN'] = 0

        # Turn off any oscillators in ASC, and clear their history
#        for py in ['PIT', 'YAW']:
#            for ii in range(1,11):
#                ezca['ASC-ADS_{}{}_DOF_GAIN'.format(py, ii)] = 0
#                ezca['ASC-ADS_{}{}_OSC_CLKGAIN'.format(py, ii)] = 0
#
#        for py in ['PIT', 'YAW']:
#            for ii in range(1,11):
#                ezca['ASC-ADS_{}{}_DOF_RSET'.format(py, ii)] = 2

        #  LSC ITMs off (in case of lockloss while transitioning to or from ETMX control)
        ISC_library.outrix.put('ITMX', [], 0)
        ISC_library.darmcarm_outrix.put('ITMX', [], 0)
        ISC_library.outrix.put('ITMY', [], 0)
        ISC_library.darmcarm_outrix.put('ITMY', [], 0)

        #so we don't have annoying flashes on strip tool
        ezca['LSC-PR_GAIN_GAIN']=0
        ezca['LSC-REFL_A_LP_GAIN'] = 0

        # Unhold the offset of TRX
        ezca.get_LIGOFilter('LSC-TR_X_NORM').turn_off('HOLD')

        # Remove the normalization for DARM
        ezca['LSC-POW_NORM_MTRX_1_17'] = 0
        ezca['LSC-POW_NORM_MTRX_1_18'] = 0
        ezca['LSC-DARM1_OFFSET'] = 0

        # Verify LSC POP A RF45 whitening gain is set correctly. The filters should be set by SDF revert
        for iq in ['I', 'Q']:
            ezca.get_LIGOFilter(f'LSC-POP_A_RF45_{iq}').only_on('INPUT', 'OFFSET', 'FM1', 'FM5', 'OUTPUT',
                                                                'DECIMATION')
        ezca['LSC-POP_A_RF45_WHITEN_GAIN'] = 15

        # SUSPENSIONS settings
        # PR2
        ezca.get_LIGOFilter('SUS-PR2_M3_ISCINF_P').switch_off('HOLD') # Clear the hold
        ezca.get_LIGOFilter('SUS-PR2_M3_ISCINF_Y').switch_off('HOLD') # Clear the hold
        ezca['SUS-PR2_M3_ISCINF_L_GAIN'] = 0 # Turn off feedback from MICH

        # PR3
        ezca.switch('SUS-PR3_M1_DRIVEALIGN_P2P', 'OFFSET', 'OFF') # turn off wire heating compenstation

        # SR2
        ezca['SUS-SR2_M3_ISCINF_L_GAIN'] = 0 # Turn off feedback from MICH

        # SR3
        #nodes['SR3_CAGE_SERVO']='CAGE_SERVO_RUNNING' # Make sure it's running before we try to lock

        # Set ETMX bias to full, for locking # JCD 1Mar2023
        ezca['SUS-ETMX_L3_LOCK_BIAS_OFFSET'] = -8.9 # doesn't really matter sign, just needs to be full bias
        
        # Check for BIAS flip on ETMX
        if ezca['SUS-ETMX_L3_LOCK_BIAS_OFFSET'] < 0:
            biasSign = -1
        else:
            biasSign = 1
        ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_GAIN'] = abs(ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_GAIN'])*-1*biasSign # positive if sign is neg
        ezca['SUS-ETMX_L3_DRIVEALIGN_L2P_GAIN'] = abs(ezca['SUS-ETMX_L3_DRIVEALIGN_L2P_GAIN'])*-1*biasSign # positive if sign is neg
        ezca['SUS-ETMX_L3_DRIVEALIGN_L2Y_GAIN'] = abs(ezca['SUS-ETMX_L3_DRIVEALIGN_L2Y_GAIN'])*-1*biasSign # positive if sign is neg
        ezca['SUS-ETMX_L3_ESDOUTF_LIN_FORCE_COEFF'] = abs(ezca['SUS-ETMX_L3_ESDOUTF_LIN_FORCE_COEFF'])*biasSign # neg if sign is neg
        ezca['SUS-ETMX_L3_LOCK_BIAS_GAIN'] = 1
        # Make sure ETMX ESD is on
        if abs(ezca['IOP-SUSAUX_EX_MADC4_EPICS_CH0']) < 100:
            log('Toggling EX ESD activation state')
            ezca['SUS-ETMX_BIO_L3_RESET'] = 1

        # ETMY
        if ezca['SUS-ETMY_L3_LOCK_BIAS_OFFSET'] < 0:
            biasSign = -1
        else:
            biasSign = 1
        ezca['SUS-ETMY_L3_DRIVEALIGN_L2L_GAIN']     = abs(ezca['SUS-ETMY_L3_DRIVEALIGN_L2L_GAIN'])*biasSign # neg if sign is neg
        ezca['SUS-ETMY_L3_ESDOUTF_LIN_FORCE_COEFF'] = abs(ezca['SUS-ETMY_L3_ESDOUTF_LIN_FORCE_COEFF'])*biasSign # neg if sign is neg
        #ezca['SUS-ETMY_L3_LOCK_BIAS_GAIN'] = -1 # Opposite of in-lock, to de-charge when not in use.

        # Make sure ETMY ESD is on
        if abs(ezca['IOP-SUSAUX_EY_MADC4_EPICS_CH1']) < 1000:
            log('Toggling EY ESD activation state')
            ezca['ISC-END_Y_BO_3'] = 0  # Changed name 2Oct2018 to match new beckhoff convention JCD
            time.sleep(0.1)
            ezca['ISC-END_Y_BO_3'] = 1

        # All Test Masses
        ezca['SUS-ETMX_L1_LOCK_P_RSET'] = 2
        ezca['SUS-ETMX_L1_LOCK_Y_RSET'] = 2
        ezca['SUS-ETMY_L1_LOCK_P_RSET'] = 2
        ezca['SUS-ETMY_L1_LOCK_Y_RSET'] = 2
        ezca['SUS-ETMY_L3_LOCK_L_RSET'] = 2
        ezca['SUS-ETMY_L2_LOCK_L_RSET'] = 2
        ezca['SUS-ETMY_L1_LOCK_L_RSET'] = 2
        time.sleep(0.1)

        # Set A2L to their centered values.
        for py in ['P2L','Y2L']:
            for optic in ['ITMX','ITMY','ETMX', 'ETMY']:
                ezca['SUS-{}_L2_DRIVEALIGN_{}_SPOT_GAIN'.format(optic,py)] = lscparams.a2l_gains['CENTER'][py][optic]  #A2L_SPOT is temporarily A2L as of 2/23/22
                # ezca['SUS-{}_L2_DRIVEALIGN_{}_GAIN'.format(optic,py)] = lscparams.a2l_gains['CENTER'][py][optic] # CRC replaced again 3/9/22


        # Moved from ISC_DRMI. 10Dec2019 JCD, JSK
        for sus in ['BS']:
            for coil in ['UR','UL','LR','LL']:
                ezca['SUS-BS_BIO_M2_%s_STATEREQ'%coil] = 2

        for py in ['P', 'Y']:
            ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_%s'%py).switch_on('FM7') # appeared that the 28.45 Hz mode moved to 28.75 Hz; new notches for it.
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_%s'%py).switch_on('FM7')
            ezca.get_LIGOFilter('SUS-ITMX_L3_LOCK_%s'%py).switch_on('FM7')
            ezca.get_LIGOFilter('SUS-ITMY_L3_LOCK_%s'%py).switch_on('FM7')

        # Reset history for violin damping
        for optic in ['ITMX','ETMY','ETMX','ITMY']:
            for mode in range(1,41):
                ezca['SUS-%s_L2_DAMP_MODE%s_RSET'%(optic,mode)] = 2
            for mode in ['V','R']:
                ezca['SUS-%s_M0_DARM_DAMP_%s_RSET'%(optic,mode)] = 2

        #unpark ALS VCOs #mov ed to COMM and DIFF guardians
        ezca['ALS-C_DIFF_PLL_PRESET'] = 'No'
        #ezca['ALS-C_COMM_PLL_PRESET'] = 'No'
        #ezca['ALS-C_COMM_VCO_CONTROLS_ENABLE'] = 1

        # open shutters
        ezca['SYS-MOTION_C_SHUTTER_D_OPEN'] = 1 # ISCT1 ALS Cavity Beam shutter
        ezca['SYS-MOTION_C_SHUTTER_C_OPEN'] = 1 # ISCT1 PSL green beam shutter
        ezca['SYS-MOTION_C_SHUTTER_E_OPEN'] = 1 # ISCT1 REFL beam (for REFL air)
        ezca['SYS-MOTION_C_SHUTTER_F_OPEN'] = 1 # POP air?

        #check if REFL, POP or AS AIR B DIVs are closed, open them
        if ezca['SYS-MOTION_C_BDIV_A_POSITION'] == 1:
            ezca['SYS-MOTION_C_BDIV_A_OPEN'] =1
        if ezca['SYS-MOTION_C_BDIV_D_POSITION'] == 1:
            ezca['SYS-MOTION_C_BDIV_D_OPEN'] =1
            ezca['VID-CAM18_EXP'] = 1800*2
        if ezca['SYS-MOTION_C_BDIV_B_POSITION'] == 1:
            ezca['SYS-MOTION_C_BDIV_B_OPEN'] =1


        # PHOTODIODE SETTINGS
        for white in [1,2,3]:
            ezca['LSC-REFLAIR_A_RF9_WHITEN_SET_%s'%white] = 0
            ezca['LSC-REFLAIR_A_RF9_AWHITEN_SET%s'%white] = 0

        # Restore whitening gains on QPDs, and adjust accordingly
        for ii in ['X', 'Y']:
            for jj in ['A', 'B']:
                ezca['ASC-{}_TR_{}_WHITEN_GAIN'.format(ii, jj)] = 18 # dB
                ezca['ASC-{}_TR_{}_NSUM_GAIN'.format(ii, jj)] = 1
            ezca['LSC-{}_TIDAL_REDTRIG_THRESH_ON'.format(ii)]=30
            ezca['LSC-{}_TIDAL_REDTRIG_THRESH_OFF'.format(ii)]=10
            # reset calibration of circulating power for new whitening gain.
            ezca.switch('ASC-{}_PWR_CIRC'.format(ii), 'FM1', 'OFF') # turned this off due to recalibration of the arms kW
            # ezca.switch('ASC-{}_PWR_CIRC'.format(ii), 'FM2', 'ON')
        for jj in ['A', 'B']:
            ezca['ASC-POP_{}_WHITEN_GAIN'.format(jj)] = 12 # dB



        # Reset PD gains after modulation depth reduction
        for pd in lscparams.pd_gain['LSC']:
            for iq in ['I','Q']:
                for freq in lscparams.pd_gain['LSC'][pd]:
                    if freq in ['9','45']:
                        ezca['LSC-%s_A_RF%s_%s_GAIN'%(pd,freq,iq)] = lscparams.pd_gain['LSC'][pd][freq]
                    elif freq in ['18','90']:
                        ezca['LSC-%s_B_RF%s_%s_GAIN'%(pd,freq,iq)] = lscparams.pd_gain['LSC'][pd][freq]

        for pd in lscparams.pd_gain['ASC']:
            for iq in ['I','Q']:
                for seg in [1,2,3,4]:
                    for freq in lscparams.pd_gain['ASC'][pd]:
                        if freq == 'X':
                            ezca['ASC-POP_X_RF_%s%s_GAIN'%(iq,seg)] = lscparams.pd_gain['ASC'][pd][freq]
                        else:
                            ezca['ASC-%s_A_RF%s_%s%s_GAIN'%(pd,freq,iq,seg)] = lscparams.pd_gain['ASC'][pd][freq]
                            ezca['ASC-%s_B_RF%s_%s%s_GAIN'%(pd,freq,iq,seg)] = lscparams.pd_gain['ASC'][pd][freq]

        for dof in ['INP1','INP2','MICH','PRC1','PRC2','SRC1','SRC2','CSOFT','DSOFT']:
            for py in ['P','Y']:
                ezca['ASC-%s_%s_SMOOTH_ENABLE'%(dof,py)] = 0


        ezca['LSC-MOD_RF9_AM_RFSET']  = lscparams.mod_depth['9']
        ezca['LSC-MOD_RF45_AM_RFSET'] = lscparams.mod_depth['45']

        # temporarily turn down BRS-X damping on threshold
        #ezca['ISI-GND_BRS_ETMX_HIGHTHRESHOLD'] = 1000

        ezca.get_LIGOFilter('SUS-MC2_M1_DRIVEALIGN_L2L').switch_off('OFFSET') # Ensure offset off, in case left on from kicking IMC

        # Clear history on DARM BLRMS channels
        for blrms in [1,2,3,4,5]:
            ezca['OAF-RANGE_RBP_%s_GAIN'%blrms] = 0
            ezca['OAF-RANGE_RLP_%s_RSET'%blrms] = 2

        # Reset TMS offsets for misaligning
        xys = ['X', 'Y']
        pityaws = ['PIT', 'YAW']
        for xy in xys:
            for pityaw in pityaws:
                py = pityaw[0]
                test_FM = ezca.get_LIGOFilter('SUS-TMS{xy}_M1_TEST_{py}'.format(xy=xy, py=py))
                test_FM.turn_off('OFFSET')

        for xy in ['X', 'Y']:
            for py in ['P', 'Y']:
                ezca['SUS-TMS{}_M1_TEST_{}_OFFSET'.format(xy, py)] = \
                    susconst.misalign_values['TMS{}'.format(xy)][py]
                ezca['SUS-TMS{}_M1_TEST_{}_TRAMP'.format(xy, py)] = 2.0

        #turn off calibration lines on ETMs so that they don't cause lockloss when the bias is ramped to 0 with the linearization on
        for g in ['CLK','SIN','COS']:
            ezca['SUS-ETMX_L3_CAL_LINE_%sGAIN'%(g)] = 0
            ezca['SUS-ETMX_L2_CAL_LINE_%sGAIN'%(g)] = 0
            ezca['SUS-ETMX_L1_CAL_LINE_%sGAIN'%(g)] = 0
            ezca['CAL-CS_TDEP_SUS_LINE3_COMPARISON_OSC_%sGAIN'%(g)] = 0
            ezca['CAL-CS_TDEP_SUS_LINE2_COMPARISON_OSC_%sGAIN'%(g)] = 0
            ezca['CAL-CS_TDEP_SUS_LINE1_COMPARISON_OSC_%sGAIN'%(g)] = 0
        
        ezca['CAL-PCALX_OSC_SUM_ON'] = 0
        ezca['CAL-PCALY_OSC_SUM_ON'] = 0
        
        ezca['LSC-DARMOSC_SUM_ON'] = 0
                
        self.timer['wait'] = 1 # give burt time to finish
        self.done_flag = False
    '''
    # This is all of references to SUS quads that were in this state that will 
    # be restored in the SDF_REVERT state.
    
        # ETMX
        for coil in ['UR', 'UL', 'LR', 'LL']:
            ezca['SUS-ETMX_L3_ESDOUTF_{}_LIN_BYPASS_SW'.format(coil)] = 0 # Want ESD to be linearized, so set = 0
            ezca['SUS-ETMX_BIO_L3_{}_VOLTAGE_SW'.format(coil)] = 1
            ezca['SUS-ETMX_BIO_L3_{}_HVDISCONNECT_SW'.format(coil)] = 1
            ezca['SUS-ETMX_BIO_L3_{}_STATEREQ'.format(coil)] = 1

        # set all ETMX output switches
        ezca['SUS-ETMX_L1_LOCK_OUTSW_L'] = 1
        ezca['SUS-ETMX_L1_LOCK_OUTSW_P'] = 0
        ezca['SUS-ETMX_L1_LOCK_OUTSW_Y'] = 0
        ezca['SUS-ETMX_L2_LOCK_OUTSW_L'] = 0
        ezca['SUS-ETMX_L2_LOCK_OUTSW_P'] = 1
        ezca['SUS-ETMX_L2_LOCK_OUTSW_Y'] = 1
        ezca['SUS-ETMX_L3_LOCK_OUTSW_L'] = 1
        ezca['SUS-ETMX_L3_LOCK_OUTSW_P'] = 0
        ezca['SUS-ETMX_L3_LOCK_OUTSW_Y'] = 0

        #ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').switch('INPUT', 'OFF', wait=False)
        ezca['SUS-ETMY_L3_LOCK_L_GAIN'] = 0
        ezca['SUS-ITMX_L3_LOCK_L_GAIN'] = 0

        #do_list = []
        for sus in ['ETMX','ETMY','ITMX','ITMY']:
            coilburtpath = '/opt/rtcds/userapps/release/isc/h1/scripts/sus/'
            ezca.burtwb(coilburtpath+ sus.lower() + '_l2_out_normal.snap') # Put test mass eul2osem matrices back, just in case
            for coil in ['UR', 'UL', 'LR', 'LL']:
                ezca['SUS-%s_BIO_L2_%s_STATEREQ'%(sus,coil)] = 2 # Reset quad suspension noise states


            for py in ['P','Y']:
                ezca['SUS-%s_M0_LOCK_%s_GAIN'%(sus,py)]=0
                ezca.get_LIGOFilter('SUS-%s_L2_LOCK_%s'%(sus,py)).switch_on('FM2') # make sure the violing band stops are on
                if py == 'Y':
                    ezca.get_LIGOFilter('SUS-%s_L2_OLDAMP_Y'%(sus)).switch_off('INPUT')

        for sus in ['ETMX','ETMY','ITMX','ITMY']:
            for py in ['P', 'Y']:
                if py=='P':
                    ezca.get_LIGOFilter('SUS-%s_M0_LOCK_%s'%(sus,py)).only_on('FM3','FM4','FM5', 'FM7', 'FM8', 'OUTPUT','DECIMATION')
                    ezca.get_LIGOFilter('SUS-%s_M0_LOCK_%s'%(sus,py)).only_on('FM3','FM4','FM5', 'FM7', 'FM8', 'OUTPUT','DECIMATION')

                else:
                    ezca.get_LIGOFilter('SUS-%s_M0_LOCK_%s'%(sus,py)).only_on('FM3','FM4','FM5', 'FM8', 'OUTPUT','DECIMATION')
                    ezca.get_LIGOFilter('SUS-%s_M0_LOCK_%s'%(sus,py)).only_on('FM3','FM4','FM5', 'FM8', 'OUTPUT','DECIMATION')
        
        # Reset violin and bounce mode damping filters JCD 8Nov2018
        for optic in ['ITMX','ETMY','ETMX','ITMY']:
            for mode in range(1,41):
                ezca.get_LIGOFilter('SUS-%s_L2_DAMP_MODE%s'%(optic,mode)).switch_off('INPUT')
        time.sleep(0.1)

    '''
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):

        if not ISC_library.is_locked('IMC') or ezca['PSL-ROTATIONSTAGE_RS_BUSY']:
            notify('MC is not locked or rotation stage moving')
            return

        # ezca['LSC-TR_X_QPD_B_SUM_OFFSET'] = -0.05 # Updated 15Mar2021 SED, removed by CRC 02Mar2022
        # ezca['LSC-TR_Y_QPD_B_SUM_OFFSET'] = -0.15 # Updated 15Mar2021 SED, removed by CRC 02Mar2022

        if self.timer['wait'] and not self.done_flag:
            # load all individually switched eul2osem matrices also just incase something didn't finish or model reboots
            for opt in ['BS_M2','MC1_M3', 'MC2_M3', 'MC3_M3', 'PRM_M3', 'PR2_M3', 'SRM_M3', 'SR2_M3','ITMX_L2','ITMY_L2','ETMX_L2','ETMY_L2']:
                ezca['SUS-%s_EUL2OSEM_LOAD_MATRIX'%opt] = 1
            log('almost done')

            # Make sure Bias offset is turned on!  JCD 19Feb2019
            ezca.get_LIGOFilter('SUS-ETM%s_L3_LOCK_BIAS'%lscparams.ALS_ESD).switch_on('OFFSET')

            if lscparams.ALS_ESD == 'Y': # Make sure ETMY ESD is on
                if abs(ezca['SUS-ETMY_L3_ESDAMON_DC_INMON']) < 20000:
                    log('ETMY bias not at expected value') # ETMY ESD is not ready
                    notify('check ETM bias')
                    if -17000 < ezca['SUS-ETMY_L3_ESDAMON_DC_INMON'] < -15000: # Check if railed negative
                        notify('ETMY ESD is potentially railed!')
                    elif abs(ezca['SUS-ETMY_L3_ESDAMON_DC_INMON']) < 1000: # In case requested DC bias completely turned off
                        if abs( ezca['SUS-ETMY_L3_LOCK_BIAS_OFFSET'] ) < 5.0:
                            log('SUS-ETMY_L3_LOCK_BIAS_OFFSET too small. Check the requested epics value')
                            self.timer['wait'] = 2
                        else:
                            log('ETMY ESD Driver is OFF. Hitting RESET to turn it ON.')
                            ezca['SUS-ETMY_BIO_L3_RESET'] = 1
                            log('ETMY ESD Driver is being reset. Sleep for 2 sec.')
                            self.timer['wait'] = 2  # wait two seconds for the microcontroller to take action
                    return # Exit and re-execute run to make check again
            else:  # Make sure ETMX ESD is on
                if abs(ezca['SUS-ETMX_L3_ESDAMON_DC_INMON']) < 20000: # ETMX ESD is not ready
                    notify('check ETM bias')
                    log('ETMX bias not at expected value')
                    if -17000 < ezca['SUS-ETMX_L3_ESDAMON_DC_INMON'] < -15000: # Check if railed negative
                        notify('ETMX ESD is potentially railed!')
                    elif abs(ezca['SUS-ETMX_L3_ESDAMON_DC_INMON']) < 1000: # In case requested DC bias completely turned off
                        if abs( ezca['SUS-ETMX_L3_LOCK_BIAS_OFFSET'] ) < 5.0:
                            log('SUS-ETMX_L3_LOCK_BIAS_OFFSET too small. Check the requested epics value')
                            self.timer['wait'] = 2
                        else:
                            log('ETMX ESD Driver is OFF. Hitting RESET to turn it ON.')
                            ezca['SUS-ETMX_BIO_L3_RESET'] = 1
                            log('ETMX ESD Driver is being reset. Sleep for 2 sec.')
                            self.timer['wait'] = 2 # wait two seconds for the microcontroller to take action
                    return # Exit and re-execute run to make check again

            # Ensure IFO trigger is overridden and ready, so we can acquire
            ISC_library.trigrix.put('IFO_trig',[],0)
            ISC_library.trigrix.put('IFO_trig','POP_A_DC',1) # CRC changed from POPAIR A DC
            ezca['LSC-IFO_TRIG_THRESH_ON'] = -1000000
            ezca['LSC-IFO_TRIG_THRESH_OFF'] = -1000000

            ezca.switch('SUS-TMSX_M1_TEST_P', 'OFFSET', 'OFF') #Turn off temporary TMS offset
            ezca['ASC-SRC1_P_SMOOTH_OFFSET_ENABLE'] = 0 #make sure src1 offset is off

            notify('Done Prepping')
            self.done_flag = True


        # TJ 2022-07-28 Added the flag when we changed from forcing this state to READY
        # It was still running bits while waiting here
        if self.done_flag:
            return True

'''
class CREATE_MONITOR_SNAP(GuardState):
    """Create/update the .snap file without the monitored channels
    from the safe.snap that is linked in the target burt area for that
    model. Write the file to the burtfile userapps area. There shoudl
    already be a symlink created for this file (you have to create 
    as the controls user).
    """
    request = False
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # First check that there are no pending changes in the models
        # that we would revert. If so, go to the check sdf state and wait
        self.fem_objs = [cdslib.CDSFrontEnd(model[0], model[1]) for model in lscparams.sdf_revert_models]
        for fem in self.fem_objs:
            if fem.SDF_TABLE_LOCK:
                log('Pending changes on {}, manually verify'.format(fem.name))
                return 'CHECK_SDF'

        header = """\
--- Start BURT header
Time:      {}
Login ID: controls ()
Eff  UID: 1001
Group ID: 1001
Keywords:
Comments:
Type:     Absolute
Directory: {}
Req File: autoBurt.req
--- End BURT header"""
        uburt_area = '/opt/rtcds/userapps/release/{}/h1/burtfiles/'
        target_burt = '/opt/rtcds/lho/h1/target/h1{model}/h1{model}epics/burt/'
        safe_file = target_burt + 'safe.snap'
        monitored_file = 'h1{}_safe_monitored.snap'

        for model in lscparams.sdf_revert_models:
            mod, dcuid, uapp = model
            # If its a beckhoff model, file names need to be appended
            if dcuid > 1023:
                mod = mod + 'sdf'
            # Sort the monitored channels in the safe.snap
            with open(safe_file.format(model=mod), 'r') as f:
                snap_full = f.readlines()
            #FIXME: handle better
            snap_no_header = snap_full[11:]
            snap_monitored = []
            for line in snap_no_header:
                # channel, 1, value, monitor
                if line.split()[3] != '0':
                    snap_monitored.append(line)

            header_now = header.format(time.strftime('%c'), target_burt.format(model=mod))
            file_text = header_now + ''.join(snap_monitored)

            # The PSL snaps are under their own directories in uapps, awkwardly check here
            if mod[:3] == 'psl':
                uburt_path = uburt_area.format(uapp) + '{}/'.format(mod[3:]) + monitored_file.format(mod)
            else:
                uburt_path = uburt_area.format(uapp) + monitored_file.format(mod)
            with open(uburt_path, 'w') as f:
                f.writelines(file_text)
                log('Created file: {}'.format(uburt_area.format(uapp) + monitored_file.format(mod)))
        self.timer['wait_for_file_creation'] = 3
        
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.timer['wait_for_file_creation']:
            return True
        else:
            return False
'''
        
class SDF_REVERT(GuardState):
    """Revert any changes that are currently in SDF for select models


    The file must contain the '.snap' extension, and must exist (or be a
    soft link) in the model burt directory:

    /opt/rtcds/<site>/<ifo>/target/<model>/<model>epics/burt

    A file in the above directory may be specified by name only, with or
    without the .snap extension (although the file must ultimately have a
    .snap extension to be loaded by the front end).  File may contain any
    subset of channels in the front end.  Channels not belonging to the
    specified front end are ignored.
    """
    index = 6
    request = False
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        #self.snap_file_dir = '/opt/rtcds/lho/h1/target/h1{}/h1{}epics/burt/'
        # First check that there are no pending changes in the models
        # that we would revert. If so, go to the check sdf state and wait
        self.fem_objs = [cdslib.CDSFrontEnd(model[0], model[1]) for model in lscparams.sdf_revert_models]
        for fem in self.fem_objs:
            if fem.SDF_TABLE_LOCK:
                log('Pending changes on {}, manually verify'.format(fem.name))
                return 'CHECK_SDF'

        self.snap_file = 'safe'
        for fem in self.fem_objs:
            diff_cnt = fem.SDF_DIFF_CNT
            log('SDF diff count for {}: {}'.format(fem.name, diff_cnt))
            # Currently leaving confirm to False, and we check in run
            fem.sdf_load(self.snap_file, loadto='monitored', confirm=False)
            log('Loaded {}.snap for {} into epics'.format(self.snap_file, fem.name))
        self.timer['wait_for_revert'] = 3
        self.epics_complete = False
        self.all_complete = False

    def run(self):
        if self.timer['wait_for_revert'] and not self.epics_complete and not self.all_complete:
            # Check that the tables are actually loaded
            for fem in self.fem_objs:
                sdf_msg = ezca['FEC-{}_SDF_MSG_STR'.format(fem.DCUID)]
                if sdf_msg:
                    notify(sdf_msg)
                if fem.sdf_get_loaded_edb() != fem.sdf_get_request():
                    notify('Waiting for SDF file to load')
                    return False
            self.epics_complete = True
        elif self.timer['wait_for_revert'] and self.epics_complete and not self.all_complete:
            ### Nov 15, 2022 TJS - A bug was found in SDF when the grd writes these safe_monitored to epics
            # the table, while showing that it's the safe.snap loaded, will actually write to the
            # safe_monitored.snap when someone goes to accept a change. This had people thinking that they
            # were saving changes only to find their changes reverted. The short term solution is to reload
            # the table with the safe.snap to doubly make sure that the safe.snap is being saved.
            ### This may have been fixed on Feb 14 2023 with the new RCG 5.1 but still have not tested,
            # so Ill leave this in for now

            log('Reloading the tables to their safe.snaps (alog65732)')
            for fem in self.fem_objs:
                fem.sdf_load('safe', loadto='table', confirm=False)
                log('Loaded {}.snap for {} into table'.format(self.snap_file, fem.name))
            # I dont think we need to do a full check again
            self.all_complete = True
            return True
        elif self.all_complete:
            # This is just for when we stay in this state
            return True
        else:
            return False

class CHECK_SDF(GuardState):
    """A stopping point to check SDF differences

    """
    index = 5
    request = True
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.fem_objs = [cdslib.CDSFrontEnd(model[0], model[1]) for model in lscparams.sdf_revert_models]

    def run(self):
        for fem in self.fem_objs:
            if fem.SDF_TABLE_LOCK:
                notify('{} ({}) has pending changes'.format(fem.name, fem.DCUID))
                # We want to hold here if there are pending changes
                return False
            elif fem.SDF_DIFF_CNT > 0:
                notify('{} diff count: {}'.format(fem.name, fem.SDF_DIFF_CNT))
        return True

    
class READY(GuardState):
    index = 11
    request = False
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        if not nodes['SQZ_MANAGER'].state == 'NO_SQUEEZING':
            nodes['SQZ_MANAGER'] = 'FDS_READY_IFO'

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        
        
        
        # Check if ETMY oplev stdev is low as proxy for all optics quiet
        oplev_stdev = call_with_timeout(cdu.avg, 1, ['SUS-ETMX_L3_OPLEV_PIT_OUT_DQ', 'SUS-ETMX_L3_OPLEV_YAW_OUT_DQ'], True)
        if ((oplev_stdev[0][1] <= 0.5) and (oplev_stdev[1][1] <= 0.5)):
            # Check if arm tidal has bled off
            if (ezca['LSC-X_ARM_CTRL_OUTPUT']==0
                and ezca['LSC-Y_ARM_CTRL_OUTPUT']==0
                and ezca['LSC-X_COMM_CTRL_OUTPUT']==0
                and ezca['LSC-Y_COMM_CTRL_OUTPUT']==0
                and nodes['IMC_LOCK'].arrived
                and nodes['IMC_LOCK'].done):
                log(f"nodes['IMC_LOCK'].state = {nodes['IMC_LOCK'].state}")
                return True
            else:
                if not(ezca['LSC-X_ARM_CTRL_OUTPUT']==0
                    and ezca['LSC-Y_ARM_CTRL_OUTPUT']==0
                    and ezca['LSC-X_COMM_CTRL_OUTPUT']==0
                    and ezca['LSC-Y_COMM_CTRL_OUTPUT']==0):
                    log('waiting for arm tidal')
        else:
            notify('waiting for oplevs to settle')


##################################################
# STATES: ALS and arms green
##################################################

class LOCKING_ARMS_GREEN(GuardState):
    index = 12
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.final_arm_req = 'ETM_TMS_WFS_OFFLOADED' # changed from Locked slow w gr wfs pum
        self.locked_states_list = ['ETM_TMS_WFS_OFFLOADED', 'OFFLOAD_ETM_TMS_WFS', 'LOCKED_SLOW_WFS_ETM_TMS']
        ezca['SYS-MOTION_X_SHUTTER_A_OPEN'] = 1 #moved here for team hartmann GLM 2018/09/06
        ezca['SYS-MOTION_Y_SHUTTER_B_OPEN'] = 1
        nodes['PSL_FSS']   = 'IDLE'
        nodes['ALS_XARM']  = self.final_arm_req
        nodes['ALS_YARM']  = self.final_arm_req
        nodes['ALIGN_IFO'] = 'SET_SUS_FOR_ALS_FPMI'
        # Set timer before increased flashes starts
        incflashes_time = 120
        self.timer['x_not_locked'] = incflashes_time
        self.timer['y_not_locked'] = incflashes_time
        self.timer['x_locked_but_no_wfs'] = 0
        self.timer['y_locked_but_no_wfs'] = 0
        # 0 = initialize, 1 = timer set, 2 = IF requested (this is gross)
        self.no_wfs_if_attempt = {arm: 0 for arm in ['X', 'Y']}
        self.iflashes = {arm: 0 for arm in ['X', 'Y']}
        self.change_pol = None
        
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        # If we've requested manual (no increase flashes), return true so we can get there
        if ezca.read('GRD-ISC_LOCK_TARGET', as_string=True)=='GREEN_ARMS_MANUAL':
            return True

        if nodes['ALS_YARM'].state == 'ETM_TMS_WFS_OFFLOADED' \
           and nodes['ALS_YARM'].done \
           and nodes['ALS_XARM'].state ==  'ETM_TMS_WFS_OFFLOADED' \
           and nodes['ALS_XARM'].done:
                return True
        else:
            notify('Waiting for arms nodes')
        # Removing unless we think that its important info  - TJ 4Feb2020
        '''
        elif not cdu.avg(2,'ALS-C_TRX_A_LF_OUT_DQ', True)[1] <= lscparams.thresh['ARMs_Green']['QUIET']:
            log('X arm green trans too noisy')
        elif not cdu.avg(2,'ALS-C_TRY_A_LF_OUT_DQ', True)[1] <= lscparams.thresh['ARMs_Green']['QUIET']:
            log('Y arm green trans too noisy')
        elif ezca['ALS-C_TRX_A_LF_OUTMON'] < lscparams.thresh['ARMs_Green']['LOCKED']:
            log('x arm trans too low')
        elif ezca['ALS-C_TRY_A_LF_OUTMON'] < lscparams.thresh['ARMs_Green']['LOCKED']:
            log('Y arm trans too low')
        '''
        # Separately
        arms = ['X', 'Y']
        for arm in arms:
            if ezca['ALS-{}_FIBR_LOCK_FIBER_POLARIZATIONPERCENT'.format(arm)] > 20 and self.change_pol == None:
                log('The ALS polarization percentage is greater than 20 in {} arm, trying CHANGE_POL state'.format(arm))
                nodes['ALS_{}ARM'.format(arm)] = 'CHANGE_POL'
                # Set the other arm to UNLOCKED to avoid it going through the polarization state, since the controller will affect both arms
                if arm == arms[0]:
                    nodes['ALS_{}ARM'.format(arms[1])] = 'UNLOCKED'
                else:
                    nodes['ALS_{}ARM'.format(arms[0])] = 'UNLOCKED'
                self.change_pol = arm
                    
            if self.change_pol is not None:
                if self.change_pol == arm:
                    if nodes['ALS_{}ARM'.format(arm)].done:
                        nodes['ALS_XARM'] = self.final_arm_req
                        nodes['ALS_YARM'] = self.final_arm_req
                        # Reset IF timers
                        self.timer['x_not_locked'] = 120
                        self.timer['y_not_locked'] = 120
                        self.change_pol = None
                    return False
                else:
                    continue

            if self.timer['{}_not_locked'.format(arm.lower())]:
                # Make sure that it isnt already locked, or hasnt tried Scan_alignment (wasIncFlashes) 2x
                if nodes['ALS_{}ARM'.format(arm)].state not in self.locked_states_list \
                   and nodes['ALS_{}ARM'.format(arm)].request != 'SCAN_ALGINMENT' \
                   and self.iflashes[arm] < 2:
                    # First verify that SEI is OK
                    if ezca[f'GRD-SEI_ETM{arm}_STATE_S'] != 'FULLY_ISOLATED' or \
                         ezca[f'GRD-SEI_ITM{arm}_STATE_S'] != 'FULLY_ISOLATED':
                        notify('Arm SEI not nominal, no I.F. until resolved')
                        return False
                    # This should know to jump on its own
                    #nodes['ALS_{}ARM'.format(arm)] = 'INCREASE_FLASHES'
                    nodes['ALS_{}ARM'.format(arm)] = 'SCAN_ALIGNMENT'
                    self.iflashes[arm] += 1
                    log('Trying Increase Flashes {}arm ({}x)'.format(arm, self.iflashes[arm]))
                    # Need this here so it has time to see the state change
                    time.sleep(0.5)
                #elif nodes['ALS_{}ARM'.format(arm)].state == 'INCREASE_FLASHES' \
                elif nodes['ALS_{}ARM'.format(arm)].state == 'SCAN_ALIGNMENT' \
                     and nodes['ALS_{}ARM'.format(arm)].done:
                    nodes['ALS_{}ARM'.format(arm)] = self.final_arm_req
                    # Give it some time to try to lock with this alignment
                    self.timer['{}_not_locked'.format(arm.lower())] = 90
                ## We are locked but not with enough power for WFS to engage, do IF.
                # This isn't a good test. It could be going between locking and locked,
                # while still satisfying these conditions. BUT it should need to go to
                # IF anyway at that point. If we find that that isnt true, then we will
                # need to add in a check to see if the power is stable between the condition.
                elif nodes['ALS_{}ARM'.format(arm)].state == 'LOCKED_SLOW_WFS_ETM_TMS' \
                     and 0.2 < ezca['ALS-{}_TR_A_LF_OUT16'.format(arm)] < 0.6 \
                     and self.timer['{}_locked_but_no_wfs'.format(arm.lower())] \
                     and self.no_wfs_if_attempt[arm] == 1:
                    # First verify that SEI is OK
                    if ezca[f'GRD-SEI_ETM{arm}_STATE_S'] != 'FULLY_ISOLATED' or \
                         ezca[f'GRD-SEI_ITM{arm}_STATE_S'] != 'FULLY_ISOLATED':
                        notify('Arm SEI not nominal, no I.F. until resolved')
                        return False
                    #nodes['ALS_{}ARM'.format(arm)] = 'INCREASE_FLASHES'
                    nodes['ALS_{}ARM'.format(arm)] = 'SCAN_ALIGNMENT'
                    log('The power seems to be too low for the WFS to catch, trying IF')
                    self.iflashes[arm] += 1
                    self.no_wfs_if_attempt[arm] = 2
                # Set up timer for previous conditional
                elif nodes['ALS_{}ARM'.format(arm)].state == 'LOCKED_SLOW_WFS_ETM_TMS' \
                     and 0.2 < ezca['ALS-{}_TR_A_LF_OUT16'.format(arm)] < 0.6 \
                     and self.timer['{}_locked_but_no_wfs'.format(arm.lower())] \
                     and self.no_wfs_if_attempt[arm] == 0:
                    self.timer['{}_locked_but_no_wfs'.format(arm.lower())] = 60
                    self.no_wfs_if_attempt[arm] = 1
                # Hands up, give up, your problem
                elif nodes['ALS_{}ARM'.format(arm)].request == self.final_arm_req \
                     and nodes['ALS_{}ARM'.format(arm)].state not in self.locked_states_list \
                     and self.iflashes[arm] >= 2:
                    notify('Find {} arm by hand?'.format(arm))



class GREEN_ARMS_MANUAL(GuardState):
    index = 13
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # A state that doesn't try increase flashes, so that we can work on alignment by hand, if needed
        self.final_arm_req = 'ETM_TMS_WFS_OFFLOADED' 
        self.locked_states_list = ['ETM_TMS_WFS_OFFLOADED', 'OFFLOAD_ETM_TMS_WFS', 'LOCKED_SLOW_WFS_ETM_TMS']
        ezca['SYS-MOTION_X_SHUTTER_A_OPEN'] = 1 
        ezca['SYS-MOTION_Y_SHUTTER_B_OPEN'] = 1
        nodes['PSL_FSS']   = 'IDLE'
        nodes['ALS_XARM']  = self.final_arm_req
        nodes['ALS_YARM']  = self.final_arm_req
        nodes['ALIGN_IFO'] = 'SET_SUS_FOR_ALS_FPMI'

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        # If we feel done, return True to get back to main path (to locking_arms_green), which
        #    will check that the arms got locked and offloaded
        # SED commented this out March 2022, and added a path to the graph that I think should allow us to choose either GREEN_ARMS_MANUAL or LOCKING_ARMS_GREEN, and choose a default version by placing a weight on the transition. 
        #if not(ezca.read('GRD-ISC_LOCK_TARGET', as_string=True)=='GREEN_ARMS_MANUAL'):
        #   return True

        # If we actually get both arms locked, return true to indicate that
        if nodes['ALS_YARM'].state == self.final_arm_req \
           and nodes['ALS_YARM'].done \
           and nodes['ALS_XARM'].state ==  self.final_arm_req \
           and nodes['ALS_XARM'].done:
                return True



            
class LOCKING_ALS(GuardState):
    index = 15
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        #self.DIFFcounter = 1
        #self.COMMcounter = 1
        self.counter = 1
        self.timer['pause'] = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        #even when we use ETMY ESD, the UIM is still using ETMX so we still don't change the way we manage tidal
        if self.counter == 1:
            if abs(ezca['ALS-C_DIFF_PLL_CTRLMON']) <= 5:
                ezca['ALS-C_COMM_PLL_INEN'] = 0
                nodes['ALS_XARM'] = 'NO_SLOW_W_GR_WFS'
                nodes['ALS_DIFF'] = 'LOCKED'
                self.counter +=1
            else:
                # nodes['ALS_XARM'] = 'LOCKED_SLOW_W_GR_WFS_PUM'
                nodes['ALS_XARM'] = 'LOCKED_SLOW_WFS_ETM_TMS' # CRC changed for the time being since we don't have green cameras
                notify('waiting for DIFF PLL to come into range')

        if self.counter == 2 and nodes['ALS_DIFF'].arrived and nodes['ALS_DIFF'].done:
            ezca['ALS-C_COMM_PLL_INEN'] = 1
            self.counter +=1
        if self.counter == 3 and ezca['ALS-C_COMM_PLLSLOW_STATUS']:
            nodes['ALS_COMM'] = 'LOCKED'
            self.counter +=1
        if self.counter == 4 and ((nodes['ALS_COMM'].state == 'HANDOFF_PART1') or (nodes['ALS_COMM'].state == 'HANDOFF_PART2') or (nodes['ALS_COMM'].state == 'HANDOFF_PART3') or (nodes['ALS_COMM'].state == 'LOCKED')):
            #this used to wait for COMM to be in a state between HANDOFF part 1 and LOCKED< SED doesn't think that's needed.
            nodes['ALS_YARM'] = 'NO_SLOW_W_GR_WFS'
            self.counter += 1
        if nodes['ALS_COMM'].arrived and nodes['ALS_COMM'].done and nodes['ALS_DIFF'].arrived and nodes['ALS_DIFF'].done and self.counter ==5:
            nodes['ALS_XARM'] = 'TRANSITION'
            nodes['ALS_YARM'] = 'TRANSITION'
            self.counter += 1
        if self.counter >= 6:
            return True


class FIND_IR(GuardState):
    index = 16
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        nodes['ALS_COMM'] = 'IR_FOUND'
        self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        log(self.counter)
        if nodes['ALS_COMM'].arrived and self.counter == 1:
            if lscparams.ALS_ESD == 'Y':
                nodes['ALS_DIFF_ETMY_ESD'] = 'IR_FOUND'
                self.counter += 1
            else:
                nodes['ALS_DIFF'] = 'IR_FOUND'
                self.counter += 1
        # Give it a second try
        if self.counter == 2 and nodes['ALS_DIFF'].state == 'NO_IR_FOUND':
            nodes['ALS_DIFF'] = 'FIND_IR'
            self.counter +=1
        elif self.counter == 3:
            nodes['ALS_DIFF'] = 'IR_FOUND'
            self.counter += 1
        elif nodes['ALS_DIFF'].arrived and self.counter >= 2:
            return True

class CHECK_IR(GuardState):
    index = 17
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.timer['pause'] = 5

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if (self.timer['pause']
                    and ezca['LSC-TR_X_NORM_OUTPUT'] > 0.2
                    and ezca['LSC-TR_Y_NORM_OUTPUT'] > 0.2
                    and ezca['ALS-C_COMM_VCO_CONTROLS_ENABLE'] == 1):

            return True

        elif self.timer['pause']:
            if ezca['LSC-TR_X_NORM_OUTPUT'] < 0.2:
                notify('IR not really found (COMM)')
            else:
                notify('IR not really found (DIFF)')



class ARMS_OFF_RESONANCE(GuardState):
    index = 18
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # Just in case someone had to find IR by hand, move the offset to the set freq.
        curFreqSet  = ezca['ALS-C_COMM_VCO_CONTROLS_SETFREQUENCY']
        foundOffset = ezca['ALS-C_COMM_VCO_CONTROLS_SETFREQUENCYOFFSET']
        ezca['ALS-C_COMM_VCO_CONTROLS_SETFREQUENCY'] = curFreqSet + foundOffset
        # Then set the freq offset to move the arms off resonance
        ezca['ALS-C_COMM_VCO_CONTROLS_SETFREQUENCYOFFSET'] = -2000
        self.timer['wait'] = 2

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.timer['wait']:
            return True


##################################################
# STATES: DRMI, PRMI, MICH
##################################################

class CHECK_MICH_FRINGES(GuardState):
    index = 48
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        nodes['ALIGN_IFO'] = 'MICH_BRIGHT_OFFLOADED_ALS'
        # Added to avoid conflict with ALIGN_IFO and ISC_DRMI setting the LSC-MICH1 FM mask --TJ Sep 2, 2022
        nodes['ISC_DRMI'] = 'DOWN'
        self.counter = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if nodes['ALIGN_IFO'].arrived and nodes['ALIGN_IFO'].done:
            return True

class MICH_OFFLOADED(GuardState):
    index = 49
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        for py in ['P', 'Y']:
            for dof in ['DC3', 'DC4']:
                ezca.get_LIGOFilter('ASC-%s_%s'%(dof, py)).switch_off('INPUT')
        ezca.get_LIGOFilter('LSC-MICH1').ramp_gain(0, ramp_time=0.5, wait=False)
        # Special request to go to input power for MICH quickly
        ezca['GRD-LASER_PWR_REQUEST'] = 'POWER_2W'


    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if ezca['GRD-LASER_PWR_STATE'] == 'POWER_2W':
            return True


class ACQUIRE_PRMI(GuardState):
    index = 50
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    def main(self):
        nodes['ALIGN_IFO'] = 'SET_SUS_FOR_PRMI_W_ALS'

        ezca['LSC-MICH_TRIG_THRESH_ON'] = lscparams.thresh['PRMIsb_MICH']['ON']
        ezca['LSC-MICH_TRIG_THRESH_OFF'] = lscparams.thresh['PRMIsb_MICH']['OFF']

        ezca['VID-CAM16_EXP'] = lscparams.camera_exp['ASAIR']
        self.timer['check_mich_fringes_wait'] = 600 # if PRMI won't lock within 6 minutes, do check mich fringes (back to short timer 28Mar2023 RWS)
        self.counter = 1
        self.timer['PRMI_POPAIR_check'] = 60

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    def run(self):
        if nodes['ISC_DRMI'].STALLED: # CRC (alog 45617)
            nodes['ISC_DRMI'].revive()

        # Just in case someone changes their mind and wants to lock DRMI
        #    instead of PRMI (or requests LOCK_PRMI instead of PRMI_LOCKED when they intend to lock PRMI)
        #if ezca.read('GRD-ISC_LOCK_TARGET', as_string=True)=='ALIGN_RECYCLING_MIRRORS':
        #    return 'ALIGN_RECYCLING_MIRRORS'
        # If you need to misalign the PRM to check MICH fringes
        elif ezca.read('GRD-ISC_LOCK_TARGET', as_string=True)=='CHECK_MICH_FRINGES':
            return True
        elif nodes['ALIGN_IFO'].arrived and self.counter == 1:
            #SED RWS think this step is unnecessary, could be removed
            nodes['ISC_DRMI'] = 'PREP_DRMI'
            self.counter += 1
        elif nodes['ISC_DRMI'].arrived and self.counter == 2:
            nodes['ISC_DRMI'] = 'PRMI_LOCKED'
            self.counter += 1
        elif nodes['ISC_DRMI'].arrived and nodes['ISC_DRMI'].done and ISC_library.is_locked('PRMI') and self.counter == 3:
            #added done check here to avoid leaving this state when PRMI is locking but not surviving more than a second
            return True
        #commented out March 9 2022 because MICH bright is giving us bad alignment
        elif self.timer['check_mich_fringes_wait'] and not ISC_library.is_locked('PRMI') and lscparams.auto_MICH_fringes:
            return 'CHECK_MICH_FRINGES'
        elif self.timer['PRMI_POPAIR_check'] and not ISC_library.is_locked('PRMI'):
            # This wrapper should handle bad nds data grabs
            popdata_prmi = call_with_timeout(cdu.getdata, 'LSC-POPAIR_B_RF90_I_ERR_DQ', -60)
            # This conditional handles None data returned
            if popdata_prmi:
                if popdata_prmi.data.max() < 20: #was 20, changed it to 5 for first lock for o4b commish 
                    log('no POPAIR RF90 flashes above 20, going to CHECK MICH FRINGES')
                    return 'CHECK_MICH_FRINGES'
                else:
                    self.timer['PRMI_POPAIR_check'] = 60


class PRMI_LOCKED(GuardState):
    index = 51
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    def main(self):
        log('ISC_LOCK PRMI_LOCKED reached')

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    def run(self):
        if not ISC_library.is_locked('PRMI'):
            log('debug doesnot think PRMI is locked')
            return 'ACQUIRE_PRMI'
        else:
            return True

class PRMI_ASC(GuardState):
    index = 52
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'PRMI'])
    @nodes.checker()
    def main(self):
        nodes['ISC_DRMI'] = 'PRMI_ASC_OFFLOADED'

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'PRMI'])
    @nodes.checker()
    def run(self):
        if nodes['ISC_DRMI'].arrived and nodes['ISC_DRMI'].done:
            return True


class PRMI_TO_DRMI_TRANSITION(GuardState):
    index = 55
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    def main(self):
        nodes['ISC_DRMI'] = 'PREP_PRMI_TO_DRMI'
        nodes['ALIGN_IFO'] = 'SET_SUS_FOR_FULL_LOCK'
        ezca['VID-CAM16_EXP'] = lscparams.camera_exp['ASAIR']
        self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    def run(self):
        if nodes['ALIGN_IFO'].arrived and nodes['ALIGN_IFO'].done and self.counter == 1:
            self.timer['wait'] = 0.2
            self.counter += 1
            notify('waiting for SRM swing')
        if self.counter == 2:
            if self.timer['wait']:
                nodes['ISC_DRMI'] = 'PREP_DRMI_ASC'
                self.timer['wait'] = 1
                self.counter += 1
        if self.counter == 3:
            if not ISC_library.is_locked('PRMI'):
                log('PRMI not locked')
                return 'ACQUIRE_DRMI_1F' # Jnote: should we be going to PREP_DRMI instead?
            elif self.timer['wait']:
                if nodes['ISC_DRMI'].arrived:
                    return True


class ALIGN_RECYCLING_MIRRORS(GuardState):
    index = 99
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    def main(self):
        nodes['ALIGN_IFO'] = 'SET_SUS_FOR_FULL_LOCK'
        self.timer['wait_for_sus_settle'] = 10

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    def run(self):
        if not nodes['ALIGN_IFO'].arrived:
            self.timer['wait_for_sus_settle'] = 10
            notify('SUS Guardians working')
        else:
            if self.timer['wait_for_sus_settle']:
                notify('waiting for sus to settle')
                return True


class ACQUIRE_DRMI_1F(GuardState):
    index = 101
    request = False
    #commented out decorators as a test for strange guardian behavoir where it jumps from this states to PRE_DRMI_ASC even though DRMI is not locked

    #@ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    #@nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        #SED comment, I don't understand the point of these first 4 lines, the DRMI request will be overwritten
        nodes['ISC_DRMI']  = 'PREP_DRMI'
        time.sleep(1)
        ezca['LSC-MICH_TRIG_THRESH_ON'] = lscparams.thresh['DRMI_MICH_als']['ON']
        ezca['LSC-MICH_TRIG_THRESH_OFF'] = lscparams.thresh['DRMI_MICH_als']['OFF']

        ezca['ALS-C_COMM_VCO_CONTROLS_ENABLE'] = 1
        # ezca['ALS-C_COMM_VCO_CONTROLS_SETFREQUENCYOFFSET'] = -1000 # RWS - this is already done in prev state
        ezca['ALS-C_REFL_DC_BIAS_GAIN'] = 0
        nodes['ALIGN_IFO'] = 'SET_SUS_FOR_FULL_LOCK'
        nodes['ISC_DRMI']  = 'DRMI_LOCKED'
        self.counter = 0
        ezca['VID-CAM16_EXP'] = lscparams.camera_exp['ASAIR']
        self.timer['try_PRMI'] = 600 # Was 600, reduce to 300 24Oct2019 JCD
        self.timer['DRMI_POPAIR_check'] = 180 # was -60, changed to -120 on 03/06/24, then changed to 180 on Sept 26 2024

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if ISC_library.is_locked('DRMI'):#added this line to prevent the situation where DRMI is locked, but the DRMI guardian has not yet arrived and we unlock DRMI to do PRMI
            if nodes['ISC_DRMI'].arrived:
                log(self.counter)
                if self.counter == 0:
                    self.counter += 1
                    nodes['ISC_DRMI']  = 'PREP_DRMI_ASC'
                elif self.counter == 1:
                    return True
        elif nodes['ISC_DRMI'].STALLED: # CRC (alog 45617)
            nodes['ISC_DRMI'].revive()
        elif ezca.read('GRD-ISC_LOCK_TARGET', as_string=True) == 'ACQUIRE_PRMI':  #SED moved these outside the counter because it seemed we were stuck in the situation where the counter was 1 but DRMI wasn't locked, and we couldn't request PRMI
            return True
        elif ezca.read('GRD-ISC_LOCK_TARGET', as_string=True) == 'CHECK_MICH_FRINGES':
            return True
        elif self.timer['try_PRMI'] and lscparams.auto_PRMI:
            return 'ACQUIRE_PRMI'
        elif self.timer['DRMI_POPAIR_check'] and lscparams.auto_PRMI:
            #popdata_drmi = cdu.getdata('LSC-POPAIR_B_RF18_I_ERR_DQ', -60)
            # This wrapper should handle bad nds data grabs
            popdata_drmi = call_with_timeout(cdu.getdata, 'LSC-POPAIR_B_RF18_I_ERR_DQ', -120) # was -60, changed to -120 on 03/06/24
            # This conditional handles None data returned
            
            if popdata_drmi:
                if popdata_drmi.data.max() < 80:  # Was 95, lowered to 80 on 03/06/24
                    log('POP 18 max is:')
                    log(popdata_drmi.data.max())
                    log('no POPAIR RF18 flashes above 80, going to PRMI')
                    return 'ACQUIRE_PRMI'
                else:
                    self.timer['DRMI_POPAIR_check'] = 60


        '''if self.counter == 0:
            if nodes['ISC_DRMI'].arrived:
                self.counter += 1
                nodes['ISC_DRMI']  = 'PREP_DRMI_ASC'
            elif nodes['ISC_DRMI'].STALLED: # CRC (alog 45617)
                nodes['ISC_DRMI'].revive()
            elif ezca.read('GRD-ISC_LOCK_TARGET', as_string=True) == 'ACQUIRE_PRMI':
                return True
            elif ezca.read('GRD-ISC_LOCK_TARGET', as_string=True) == 'CHECK_MICH_FRINGES':
                return True
        if self.counter == 1:
            if nodes['ISC_DRMI'].arrived:
                return True'''

class DRMI_LOCKED_PREP_ASC(GuardState):
    index = 102
    request = False

    # just a place for chillin' after locking DRMI
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        log('DRMI LOCKED achieved')

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        return True

class ENGAGE_DRMI_ASC(GuardState):
    index = 103
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        nodes['ISC_DRMI'] = 'DRMI_1F_LOCKED_W_ASC'

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if nodes['ISC_DRMI'].arrived:
            return True

class TURN_ON_BS_STAGE2(GuardState):
    index = 104
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    def main(self):
        nodes['SEI_BS'] = 'FULLY_ISOLATED'
        # ezca['ISI-GND_BRS_ETMX_HIGHTHRESHOLD'] = 2000 # BRSX crashed, so this isn't working. 18Oct2018 JCD

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    def run(self):
        if nodes['SEI_BS'].arrived:
            return True


class TRANSITION_DRMI_TO_3F(GuardState):
    index = 105
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        nodes['ISC_DRMI'] = 'DRMI_3F_LOCKED'

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if nodes['ISC_DRMI'].arrived:
            return True
        else:
            log('Not arrived')


class DRMI_LOCKED_CHECK_ASC(GuardState):
    index = 110
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.wfs2OffloadList = ['INP1', 'PRC1', 'PRC2', 'MICH', 'SRC1', 'SRC2']
        self.wfsTolerancePit = [5,400, 200, 600, 400, 200]
        self.wfsToleranceYaw = [5,400, 200, 600, 400, 200]
        self.timer['DRMI alignment checkpoint timer'] = 0.1

        ezca['LSC-PR_GAIN_GAIN'] = 1
        ezca['LSC-REFL_A_LP_GAIN'] = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if (self.timer['DRMI alignment checkpoint timer']
            and ISC_library.asc_convergence_checker(self.wfs2OffloadList, self.wfsTolerancePit, self.wfsToleranceYaw)):
            #putting an offset into AS 36 Q before we change the MICH ASC control from 45 to 36
                self.ASA_36Q_PIT_INMON = call_with_timeout(cdu.avg, -10, 'ASC-AS_A_RF36_Q_PIT_INMON')
                self.ASA_36Q_YAW_INMON = call_with_timeout(cdu.avg, -10, 'ASC-AS_A_RF36_Q_YAW_INMON')
                ezca['ASC-AS_A_RF36_Q_PIT_OFFSET'] = -1.*self.ASA_36Q_PIT_INMON
                ezca['ASC-AS_A_RF36_Q_YAW_OFFSET'] = -1.*self.ASA_36Q_YAW_INMON
                ezca.get_LIGOFilter('ASC-AS_A_RF36_Q_PIT').switch_on('OFFSET')
                ezca.get_LIGOFilter('ASC-AS_A_RF36_Q_YAW').switch_on('OFFSET')
                return True

class OFFLOAD_DRMI_ASC(GuardState):
    index = 111
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.using_MICH_ASC = True
        if ezca['ASC-MICH_P_GAIN'] == 0:
            self.using_MICH_ASC = False
        if self.using_MICH_ASC:
            ezca.get_LIGOFilter('ASC-MICH_P').ramp_gain(0, ramp_time=2, wait=False)
            ezca.get_LIGOFilter('ASC-MICH_Y').ramp_gain(0, ramp_time=2, wait=False)

            time.sleep(2)

            ezca.get_LIGOFilter('ASC-MICH_P').switch_off('INPUT')
            ezca.get_LIGOFilter('ASC-MICH_Y').switch_off('INPUT')

            # Try to reduce the transient when transitioning to AS36
            ezca['ASC-MICH_P_SMOOTH_LIMIT'] = 500
            ezca['ASC-MICH_Y_SMOOTH_LIMIT'] = 500
            ezca['ASC-MICH_P_SMOOTH_ENABLE'] = 0 #1, CRC Turned off Dec 16 2018, alog
            ezca['ASC-MICH_Y_SMOOTH_ENABLE'] = 0 #1

        self.wfsConvergeFlag = True
        self.wfs2OffloadList = ['INP1', 'PRC1', 'PRC2', 'SRC1', 'SRC2', 'MICH']
        self.wfsTolerancePit = [5, 400, 200, 400, 200, 600]
        self.wfsToleranceYaw = [5, 400, 200, 400, 200, 600]

        # Arm the lockloss trigger earlier

        #ezca['LSC-TRIG_MTRX_10_12'] = 1 # ensure IFO_TRIG is on POPAIR_A_DC Jenne, Adrian 26Sept2019
        #ezca['LSC-TRIG_MTRX_10_2']  = 0 # stop using POP_18_I

        # CRC, next two lines should do the same as the above two lines
        ISC_library.trigrix.put('IFO_trig','POP_A_DC',1)
        ISC_library.trigrix.put('IFO_trig','POPAIR_B_RF18_I',0)
        ezca['LSC-IFO_TRIG_THRESH_OFF'] = lscparams.thresh['LOCKLOSS_CHECK_DRMI']['OFF']
        ezca['LSC-IFO_TRIG_THRESH_ON']  = lscparams.thresh['LOCKLOSS_CHECK_DRMI']['ON']

        self.counter = 1
        self.timer['wait'] = 5

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        log(self.counter)
        if self.counter == 1 and self.timer['wait']:
            if self.using_MICH_ASC:
                #commenting out because of new work on 72 MHz work (SED, see alog 43259)
                ISC_library.asc_intrix['PIT'].put('MICH', [], 0)
                ISC_library.asc_intrix['YAW'].put('MICH', [], 0)
                ISC_library.asc_intrix['PIT']['MICH', 'AS_A_RF36_Q'] = 1.0 #1.3 CRC #1.5 HY 09/10/18 reducing the gain to 0.8 Hz UGF
                ISC_library.asc_intrix['YAW']['MICH', 'AS_A_RF36_Q'] = 1.0 #1.3 CRC #1.5
                time.sleep(0.2)
                # HY put BS 72 here
                #ISC_library.asc_intrix['PIT']['MICH', 'AS_A_RF72_Q'] = 1500
                #ISC_library.asc_intrix['PIT']['MICH', 'AS_B_RF72_Q'] = 0
                #ISC_library.asc_intrix['YAW']['MICH', 'AS_A_RF72_Q'] = 0
                #ISC_library.asc_intrix['YAW']['MICH', 'AS_B_RF72_Q'] = 750


                ezca.get_LIGOFilter('ASC-MICH_P').switch_off('FM2')
                ezca.get_LIGOFilter('ASC-MICH_Y').switch_off('FM2')
                ezca.get_LIGOFilter('ASC-MICH_P').switch_on('FM1')
                ezca.get_LIGOFilter('ASC-MICH_Y').switch_on('FM1')
                
                # EMC added to switch controllers
                ezca.get_LIGOFilter('ASC-MICH_P').switch_off('FM8', 'FM10')
                ezca.get_LIGOFilter('ASC-MICH_Y').switch_off('FM7', 'FM10')
                ezca.get_LIGOFilter('ASC-MICH_P').switch_on('FM7')
                ezca.get_LIGOFilter('ASC-MICH_Y').switch_on('FM5')

                ezca['ASC-MICH_P_RSET'] = 2
                ezca['ASC-MICH_Y_RSET'] = 2
                time.sleep(0.1)

                ezca.get_LIGOFilter('ASC-MICH_P').switch_on('INPUT')
                ezca.get_LIGOFilter('ASC-MICH_Y').switch_on('INPUT')

                ezca.get_LIGOFilter('ASC-MICH_P').ramp_gain(lscparams.asc_gains['MICH']['P']['ENGAGE_DRMI_ASC'], ramp_time=20, wait=False)
                ezca.get_LIGOFilter('ASC-MICH_Y').ramp_gain(lscparams.asc_gains['MICH']['Y']['ENGAGE_DRMI_ASC'], ramp_time=20, wait=False)
                self.timer['wait'] = ezca['ASC-MICH_P_TRAMP']

            self.counter += 1


        if self.counter == 2 and self.timer['wait']:
            if self.using_MICH_ASC:
                ezca.get_LIGOFilter('ASC-MICH_P').switch_off('FM1')
                ezca.get_LIGOFilter('ASC-MICH_Y').switch_off('FM1')
                ezca.get_LIGOFilter('ASC-MICH_P').switch_on('FM2')
                ezca.get_LIGOFilter('ASC-MICH_Y').switch_on('FM2')
            self.counter +=1

        if self.counter == 3 and self.timer['wait']:
            if ISC_library.asc_convergence_checker(self.wfs2OffloadList, self.wfsTolerancePit, self.wfsToleranceYaw):
                self.counter += 1
                notify('Offloading DRMI ASC')
            elif self.counter == 0:
                notify('Waiting for DRMI asc to converge')
                self.timer['wait'] = 1 # So that the log doesn't fill up with a zillion lines about covergence

        if self.counter == 4:

            self.turnOffSusList = ['PRM_M1','PR2_M1','PR3_M1','SRM_M1','SR2_M1']
            self.turnOffWfsList = ['INP1', 'PRC1', 'PRC2','SRC1','SRC2']
            self.clearWfsList   = ['INP1', 'PRC1', 'PRC2','SRC1','SRC2']
            for py in ['P','Y']:
                for sus in self.turnOffSusList:
                    ezca.get_LIGOFilter('SUS-%s_LOCK_%s'%(sus,py)).switch_off('INPUT', wait=False)
                for wfs in self.turnOffWfsList:
                    ezca.get_LIGOFilter('ASC-%s_%s'%(wfs,py)).switch_off('INPUT', wait=False)
            self.timer['wait'] = 1
            self.counter += 1

        if self.counter == 5 and self.timer['wait']:
            # does ASC offloading
            nodes['ISC_DRMI'] = 'DRMI_3F_W_ASC_READY'
            self.counter += 1

        if nodes['ISC_DRMI'].arrived and self.counter == 6:
            self.counter += 1
        else:
            notify('Waiting for DRMI ASC')

        # shouldn't need these resets, should be taken care of in offload_many in ISC_GEN_STATES
        if self.counter == 7 and self.timer['wait']:
            for wfs in self.clearWfsList:
                ezca.get_LIGOFilter('ASC-%s_P'%wfs).ramp_gain(0, ramp_time=1, wait=False)
                ezca.get_LIGOFilter('ASC-%s_Y'%wfs).ramp_gain(0, ramp_time=1, wait=False)
            self.timer['wait'] = 1.5
            self.counter += 1

        if self.counter == 8 and self.timer['wait']:
            for wfs in self.clearWfsList:
                ezca['ASC-%s_P_RSET'%wfs] = 2
                ezca['ASC-%s_Y_RSET'%wfs] = 2
            self.counter += 1
            time.sleep(0.1)

        if self.counter == 9 and nodes['ISC_DRMI'].arrived:
            self.counter += 1

        if self.counter == 10 and nodes['ISC_DRMI'].arrived:
            ezca.switch('LSC-MCL', 'FM3', 'ON')
            self.counter += 1

        if self.counter == 11:
            if nodes['SEI_BS'].arrived:
                self.counter+=1
            else:
                notify('waiting for BS ISI')

        if self.counter == 12:
            return True


class CHECK_AS_SHUTTERS(GuardState):
    index = 120
    request = False
    #this state is intended to make sure that our fast shutter is working correctly, it is very important for equipment safety.  Any edits to this state need to be reviewed, please bring it up for discussion if you have some reason you want to edit this.  
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.useMICH = True
        if ezca['ASC-MICH_P_GAIN'] == 0:
            self.useMICH = False
        if self.useMICH:
            ezca.get_LIGOFilter('ASC-MICH_P').ramp_gain(0, ramp_time=2, wait=True)
            ezca.get_LIGOFilter('ASC-MICH_Y').ramp_gain(0, ramp_time=2, wait=True)
            time.sleep(2)        # should really move this stuff to the run state so we don't have to sleep before turning off input

        self.turnOffSusList = ['BS_M1']
        self.turnOffWfsList = ['MICH','DC3','DC4']
        for py in ['P','Y']:
            for sus in self.turnOffSusList:
                ezca.get_LIGOFilter('SUS-%s_LOCK_%s'%(sus,py)).switch_off('INPUT', wait=False)
            for servo in self.turnOffWfsList:
                ezca.get_LIGOFilter('ASC-%s_%s'%(servo,py)).switch_off('INPUT', wait=False)


        if self.useMICH:
            ezca.get_LIGOFilter('ASC-MICH_P').switch_off('FM2')
            ezca.get_LIGOFilter('ASC-MICH_Y').switch_off('FM2')
            ezca.get_LIGOFilter('ASC-MICH_P').switch_on('FM1')
            ezca.get_LIGOFilter('ASC-MICH_Y').switch_on('FM1')

            ezca['ASC-MICH_P_RSET'] = 2
            ezca['ASC-MICH_Y_RSET'] = 2
            time.sleep(0.1)

        self.TestNeeded = False
        self.timer['wait'] = 10
        self.counter = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        # First, check that the LOCKLOSS_SHUTTER_CHECK node saw the shutter fire
        # during the last lock loss.
        if ezca['GRD-LOCKLOSS_SHUTTER_CHECK_STATE_S'] != 'LOW_ARM_POWER':
            notify('Check that fast shutter triggered on last lockloss')
            log('LOCKLOSS_SHUTTER_CHECK node not reporting correct state, needs manual check.')
            return False
        
        if self.counter==0 and self.timer['wait']:
            nodes['FAST_SHUTTER'] = 'READY'
            self.counter += 1

        if nodes['FAST_SHUTTER'].arrived and self.counter==1:
            if nodes['FAST_SHUTTER'] == 'SHUTTER_FAILURE':
                self.TestNeeded = True
                notify('Shutter Failed!')

            elif nodes['FAST_SHUTTER'] == 'READY':
                log('Turning back on AS ASC')
                for py in ['P','Y']:
                    ezca.get_LIGOFilter('ASC-MICH_%s'%py).switch_on('FM1') # lower gain of mich for reengage
                    for sus in self.turnOffSusList:
                        ezca.get_LIGOFilter('SUS-%s_LOCK_%s'%(sus,py)).switch_on('INPUT', wait=False)
                    for servo in self.turnOffWfsList[1:]:
                        ezca.get_LIGOFilter('ASC-%s_%s'%(servo,py)).switch_on('INPUT', wait=False)
                self.timer['wait'] = 2
                self.counter +=1

        if self.timer['wait'] and self.counter==2:
            # Commenting out MICH ASC for now, CRC 03Mar2022
            if self.useMICH:
                for py in ['P','Y']:
                    ezca.get_LIGOFilter('ASC-MICH_%s'%(py)).switch_on('INPUT', wait=False)
                    self.timer['wait'] = ezca['ASC-MICH_P_TRAMP']
                    ezca.get_LIGOFilter('ASC-MICH_P').ramp_gain(lscparams.asc_gains['MICH']['P']['ENGAGE_DRMI_ASC'], ramp_time=2, wait=False)
                    ezca.get_LIGOFilter('ASC-MICH_Y').ramp_gain(lscparams.asc_gains['MICH']['Y']['ENGAGE_DRMI_ASC'], ramp_time=2, wait=False)
            self.counter += 1

        if self.timer['wait'] and self.counter==3:
            if self.useMICH:
                for py in ['P', 'Y']:
                    ezca.get_LIGOFilter('ASC-MICH_%s'%py).switch_off('FM1')
                self.timer['wait'] = 10
            self.counter +=1
        if self.timer['wait'] and self.counter==4:
            if self.useMICH:
                for py in ['P', 'Y']:
                    ezca.get_LIGOFilter('ASC-MICH_%s'%py).switch_on('FM2')
            self.counter +=1
        if self.counter==5:
            return True


##################################################
# STATES: CARM offset reduction (and DARM to RF)
##################################################

class PREP_TR_CARM(GuardState):
    index = 200
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.NN = 0
        self.myoffsets = [0.0,0.0]
        self.counter = 0
        self.timer['wait'] = 0.2

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        # Formerly prep_tr_qpd_b_sum
        if self.counter == 0:
            # alternative to cdu.avg, since that prevents checking for locklosses
            log(self.myoffsets)
            if self.timer['wait']:
                if self.NN < 20:
                    self.myoffsets[0] = self.myoffsets[0] + ezca['LSC-TR_X_QPD_B_SUM_INMON']
                    self.myoffsets[1] = self.myoffsets[1] + ezca['LSC-TR_Y_QPD_B_SUM_INMON']
                    #self.timer['wait'] = 0.2
                    self.NN += 1
                else:
                    self.myoffsets[0] = self.myoffsets[0] / self.NN
                    self.myoffsets[1] = self.myoffsets[1] / self.NN
                    self.counter += 1

        if self.counter == 1:
            ezca['LSC-TR_X_QPD_B_SUM_OFFSET'] = round(-self.myoffsets[0],3)
            ezca['LSC-TR_Y_QPD_B_SUM_OFFSET'] = round(-self.myoffsets[1],3)
            self.counter += 1
        if self.counter == 2:
            #set things up, but don't turn anything on yet
            # resG in path to MC2
            ezca.switch('LSC-MCL', 'FM3', 'ON')
            #make sure we are not sending anything out to the DAC
            ezca.get_LIGOFilter('ALS-C_REFL_DC_BIAS').ramp_gain(0, ramp_time=0.1, wait=False) # so no signal goes to CARM path yet
            #############################
            #set up digital TR CARM path
            #############################
            # use the QPD
            ezca['LSC-TR_DC_OR_QPD_SUM'] = 1
            # set TR CARM filters
            ezca.switch('LSC-TR_CARM', 'FM2', 'OFF', 'FM1', 'FM8', 'ON')
            # Start TRX and TRY at some offset
            ezca.get_LIGOFilter('LSC-TR_CARM').ramp_offset(-0.5, ramp_time = 3, wait=False) # why ramp? shouldn't be in use
            # Ensure the REFLBIAS filter module is controlled by TR_CARM, not anything else
            ISC_library.intrix.put('REFLBIAS',[],0)
            ISC_library.intrix['REFLBIAS', 'TR_CARM'] = 1.0
            ezca['LSC-PD_DOF_MTRX_TRAMP'] = 1.0
            time.sleep(0.1)
            ISC_library.intrix.load()
            time.sleep(0.1)
            # Set the REFLBIAS filter module:
            #     FM3 = 1 Hz pole, 40 Hz zero (a boost to ensure sqrt(TRX+TRY) has a
            #         stable crossover with ALS COMM)
            #     FM9 = 1.6 Hz pole, 40 Hz zero (to imitate the COMM VCO response)
            ezca.switch('LSC-REFLBIAS', 'FMALL', 'OFF')
            ezca['LSC-REFLBIAS_RSET'] = 2
            ezca.switch('LSC-REFLBIAS', 'FM9','FM3', 'ON')
            ezca['LSC-REFLBIAS_GAIN'] =  lscparams.carm_offset['gains']['LSC_REFLBIAS_PREP_TR_CARM']
            # turn off filter used in digital to analog carm transition
            ezca.switch('ALS-C_REFL_DC_BIAS','FM1','OFF')
            ezca['ALS-C_REFL_DC_BIAS_TRAMP'] = 2
            #############################
            ### set up the analog path
            #############################
            #summing juntion should be using REFL DC BIAS not in VAC refl for SUM A
            ezca['LSC-REFL_SUM_A_IN1GAIN'] = lscparams.LSC_REFL_SUM_A_IN1GAIN_PREP_TR_CARM # dB
            ezca['LSC-REFL_SUM_A_IN1EN'] = 1
            ezca['LSC-REFL_SUM_A_IN2EN'] = 0
            # Engage the sqrt(TRX+TRY) path into CM board
            ezca['LSC-REFL_SERVO_IN1GAIN'] = lscparams.LSC_REFL_SERVO_IN1GAIN_PREP_TR_CARM
            ezca['LSC-REFL_SERVO_IN1EN'] = 1
            self.counter +=1
        if self.counter ==3:
            '''#expirimental idea: try triggering REFLBIAS
            ISC_library.trigrix['REFLBIAS','TRX'] = 1
            ezca['LSC-REFLBIAS_TRIG_THRESH_ON'] = 0.08
            ezca['LSC-REFLBIAS_TRIG_THRESH_OFF'] = 0.01'''
            #bring arms back so that transition to TR_CARM can work
            ezca['ALS-C_COMM_VCO_CONTROLS_SETFREQUENCYOFFSET'] = lscparams.carm_offset['TR_CARM_offsets']['als_c_comm_vco_freq']
            time.sleep(0.2)
            self.counter +=1
        if self.counter == 4:
            if abs(ezca['ALS-C_COMM_VCO_CONTROLS_DIFFFREQUENCY']) < 10:
                return True


class START_TR_CARM(GuardState):
    """
    Configure the LSC so that the CARM sensor can be transitioned from ALS COMM
    to sqrt(TRX+TRY).

    In the LSC model, the sqrt(TRX+TRY) path currently uses three cascaded filter
    banks: LSC-TR_CARM, LSC-REFLBIAS, and LSC-REFL_DC_BIAS. After these filter
    banks, the signal becomes analog (via the LSC-EXTRA channels) and is
    sent into the common-mode board.

    """
    index = 201
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # do some things in case you missed IR and are manually repeating this state.
        ezca.switch('LSC-MCL', 'FM3', 'ON')
        #unboost ALS comm so we can add a TR CARM signal at low frequency
        ezca['ALS-C_COMM_PLL_BOOST'] = 0
        self.timer['wait'] = 2 # to ensure all these things are done
        self.counter = 0


    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.timer['wait']:
            if self.counter == 0:
                #if ezca['LSC-REFLBIAS_TRIG_MON'] :
                #turn of frequency servo
                ezca['ALS-C_COMM_VCO_CONTROLS_ENABLE'] = 0
                ezca['ALS-C_REFL_DC_BIAS_GAIN'] = lscparams.ALS_C_REFL_DC_BIAS_GAIN_START_TR_CARM
                self.timer['wait'] = 2
                self.counter +=1
            elif self.counter == 1:
                #engage an integrator
                ezca.switch('LSC-REFLBIAS', 'FM6', 'ON')
                # read the REFL_DC value and store it for later use in an epics record
                # this is something Stefan did to make the last steps of the CARM offset reduction more robust,
                # it records the REFL power level before we do CARM offset reduction to have a reference. see alog 44075 for details.
                self.avg_REFLDC = call_with_timeout(cdu.avg, 2, 'LSC-REFL_A_LF_OUTPUT')
                ezca.switch('LSC-XARM', 'OFFSET', 'OFF')
                ezca['LSC-XARM_OFFSET'] = self.avg_REFLDC
                return True


class CARM_TO_TR(GuardState):
    """
    Hand off the CARM sensor from ALS COMM to sqrt(TRX+TRY).

    """
    index = 202
    request = False
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN', 'DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.counter = 0
        self.servo_on = True
        self.Failure = False
        #check if PREP_TR_CARM failed
        if ezca['LSC-TR_X_NORM_OUTPUT'] < 0.03:
            notify('NO IR in arms!!!')
            ezca.switch('LSC-MCL', 'FM3', 'OFF')
            ezca.switch('LSC-REFLBIAS', 'FM4',  'FM6','OFF')
            time.sleep(0.2)
            ezca['ALS-C_REFL_DC_BIAS_GAIN'] = 0
            #wait for the gain to ramp to zero
            time.sleep(3)
            ezca.switch('ALS-C_REFL_DC_BIAS','INPUT','ON') # Ensure input is on, after gain set to zero
            ezca['LSC-REFLBIAS_RSET'] = 2
            ezca['ALS-C_COMM_VCO_CONTROLS_ENABLE'] = 1
            self.timer['noIRtimer']=10
            self.Failure = True
            self.wfsConvergeFlag = True
        else:
            # Was -0.8, but not enough light on TRY QPD, so making -1.2 JCD 2019-July-31
            ezca['LSC-TR_CARM_OFFSET'] = lscparams.carm_offset['TR_CARM_offsets']['carm_to_tr']
            self.timer['pause']=ezca['LSC-TR_CARM_TRAMP']

        self.timer['wait'] = 2
        self.counter = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.Failure:
            if self.timer['noIRtimer']:
                #return 'PREP_TR_CARM'
                #just sit here if PREP TR CARM has failed
                notify('NO IR in arms!!!')
                return False
        elif self.timer['pause']:
            log(self.counter)
            if self.counter==0:
                ezca.switch('LSC-REFLBIAS', 'FM4', 'ON')  #this was commented out and put in after the analog gain reduction and FM10.  We seem to have some locklosses where the TR_CARM loop is oscillating around 90 Hz in these steps, so it seems like having this back here would help.  SED Sept 10 2019, searching for old alogs, there are notes about this in 44481, 36686
                self.timer['pause']=ezca['ALS-C_REFL_DC_BIAS_TRAMP']
                self.counter+=1
            elif ezca['LSC-REFL_SERVO_IN2GAIN'] > 0 and self.counter > 0:
                # Ramp down the ALS GAIN  into common mode servo
                ezca['LSC-REFL_SERVO_IN2GAIN'] -= 1
                #self.counter = 0
                time.sleep(0.2)
            elif ezca['LSC-REFL_SERVO_IN2GAIN'] > -31:
                # Ramp down the ALS GAIN  into common mode servo
                ezca['LSC-REFL_SERVO_IN2GAIN'] -= 1
                #self.counter = 0
                time.sleep(0.01)
            elif ezca['LSC-REFL_SERVO_IN2GAIN'] <= -31 and self.counter == 1:
                time.sleep(1)
                ezca['LSC-REFL_SERVO_IN2EN'] = 0
                ezca.switch('LSC-REFLBIAS','FM10', 'ON')
                time.sleep(3)
                self.counter +=1
            if self.counter == 2:
                #ezca.switch('LSC-REFLBIAS', 'FM4', 'ON')
                return True


class CARM_150_PICOMETERS(GuardState):
    """
    Move the CARM offset to a nominal value of 150 pm.

    This is implemented by changing the set point of sqrt(TRX+TRY), so we are not
    guaranteed that the offset is actually 150 pm.

    """
    index = 203
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        ezca.get_LIGOFilter('LSC-TR_CARM').ramp_offset(-1, ramp_time=3, wait=False)
        ezca.get_LIGOFilter('ALS-C_REFL_DC_BIAS').ramp_gain(lscparams.carm_offset['gains']['ALS-C_REFLBIAS_150PM'], ramp_time=10, wait=False)
        self.timer['CARMramp'] = ezca['LSC-TR_CARM_TRAMP']
        self.counter = 1

        # Set up servo which changes diff offset to bring AS 45 Q toward 0
        servo_gain = lscparams.carm_offset['gains']['ALS_C_DIFF_PLL_servo'] #was 0.004 20190409
        control_chan = 'ALS-C_DIFF_PLL_CTRL_OFFSET'
        readback_chan = 'ASC-AS_A_RF45_Q_SUM_NORM'
        self.servo = cdu.Servo(ezca, control_chan, readback=readback_chan,
                               gain=servo_gain)

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC', 'XARM_GREEN', 'YARM_GREEN','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        self.servo.step() # do every time, just in case

        if self.counter == 1 and self.timer['CARMramp']:
            #the arm build up for the transition to RF DARM should be about 0.5% of the full power build up
            ezca.get_LIGOFilter('LSC-TR_CARM').ramp_offset(lscparams.carm_offset['TR_CARM_offsets']['RF_DARM'], ramp_time=3, wait=False)#changed from -3.7 Feb 15 2019
            self.timer['CARMramp'] = ezca['LSC-TR_CARM_TRAMP']
            self.counter += 1

        if self.counter == 2 and self.timer['CARMramp']:
            return True


class DARM_TO_RF(GuardState):
    """
    Transition the DARM sensor from ALS DIFF to AS45Q.

    """
    index = 304
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # Hand off DIFF to AS45Q by ramping the input matrix
        ISC_library.intrix_OMCAS45['DARM', 'ASAIR_A45Q'] = lscparams.carm_offset['gains']['RF_DARM_intrix']#-1e-6 #-40 #(was positive with ASAIR, but with AS_A_sum there is a sign flip)
        ISC_library.intrix_OMCAS45['DARM', 'ALS_DIFF'] = 0
        ezca['LSC-ARM_INPUT_MTRX_TRAMP'] = 2
        ezca['LSC-POW_NORM_TRX_SQRT_ENABLE'] = 1
        ezca['LSC-POW_NORM_TRY_SQRT_ENABLE'] = 1  # added 6 Mar 2024 JCD
        self.timer['wait'] = 1
        self.counter = 1
        # Set up servo which changes diff offset to bring AS 45 Q toward 0
        servo_gain = 0.001 #was 0.004 20190304
        control_chan = 'ALS-C_DIFF_PLL_CTRL_OFFSET'
        readback_chan = 'ASC-AS_A_RF45_Q_SUM_NORM'
        self.servo = cdu.Servo(ezca, control_chan, readback=readback_chan,
                               gain=servo_gain)

        # Set the input and output matrices for DHARD feedback (loop input switches still off)
        ISC_library.asc_intrix['PIT']['DHARD', 'AS_A_RF45_Q'] = 1
        ISC_library.asc_intrix['YAW']['DHARD', 'AS_A_RF45_Q'] = 1

        # Set up smooth limiter, so we don't lose lock when engaging DHARD WFS. JCD 22Feb2019
        ezca['ASC-DHARD_P_SMOOTH_TRAMP'] = 3
        ezca['ASC-DHARD_Y_SMOOTH_TRAMP'] = 3
        ezca['ASC-DHARD_P_SMOOTH_LIMIT'] = 600
        ezca['ASC-DHARD_Y_SMOOTH_LIMIT'] = 600
        ezca['ASC-DHARD_P_SMOOTH_ENABLE'] = 1
        ezca['ASC-DHARD_Y_SMOOTH_ENABLE'] = 1


    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        log('checked imc locked')
        # Jnote: need to check AS_A sum value, may not be right normalization for this 1e-5 val
        if (self.counter == 1 and abs(ezca['ASC-AS_A_RF45_Q_SUM_OUT16']) <= 100 and self.timer['wait']): #was <=10 20190304
            ISC_library.intrix_OMCAS45.load()
            log('loaded intrix')
            self.timer['wait'] = 2  #temporarily increased from 1 second March 8 2024
            self.counter += 1
        elif self.counter == 1:
            self.servo.step() # do every time, just in case

        if self.timer['wait'] and self.counter == 2:
            ezca['LSC-POW_NORM_MTRX_1_17'] = 0.37 # Don't use TRX, 6 Mar 2024 JCD
            #ezca['LSC-POW_NORM_MTRX_1_18'] = 0.37 # Use TRY not TRX, 6 Mar 2024 JCD

            # Turn off the SB60 filter
            ezca.switch('LSC-DARM2', 'FM10', 'OFF') # Need stopband to reduce drive RMS (when we had terrible ground loop problems)
            # Turn off the 55 Hz LPF
            ezca.switch('LSC-DARM2', 'FM9', 'OFF')
            self.timer['wait'] = 2
            self.counter += 1

        if self.counter == 3 and self.timer['wait']:
            nodes['ALS_XARM'] = 'LOCKED_RED'
            nodes['ALS_YARM'] = 'LOCKED_RED'
            self.counter += 1

        if self.counter == 4 and nodes['ALS_XARM'].arrived and nodes['ALS_YARM'].arrived:
            #when we were doing the RF DARM transition at lower TR CARM offset (-3.7), we needed to increase DARM gain here then decrease it as soon as we reduced the offset.
            #ezca.get_LIGOFilter('LSC-DARM1').ramp_gain(1000, ramp_time=2, wait=False)
            self.timer['wait'] = ezca['LSC-DARM1_TRAMP']
            self.counter += 1

        if self.counter == 5 and self.timer['wait']:

            return True


class DHARD_WFS(GuardState):
    index = 305
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        #bonus, set up violin mode filters now that we have RF DARM, so that the filter transient has time to settle before we want to engage the filters. 
        #nodes['VIOLIN_DAMPING'] = 'DAMPING_SETTINGS'
        # Set up suspensions: enable the L2 pitch/yaw feedback path
        for sus in ['ETMX', 'ETMY','ITMX', 'ITMY']:
            ezca['SUS-%s_M0_LOCK_P_GAIN'%(sus)] = 0.3
            ezca['SUS-%s_M0_LOCK_Y_GAIN'%(sus)] = 1
            for py in ['P','Y']:
                ezca.switch('SUS-%s_M0_LOCK_%s'%(sus,py),'INPUT','ON')
                ezca['SUS-%s_L2_LOCK_OUTSW_%s'%(sus,py)] = 1

        ezca['ASC-AS_B_RF45_WHITEN_GAIN'] =  3

        # DHARD input and output matrices now set in RF DARM

        # Set the DHARD filter settings
        ezca.get_LIGOFilter('ASC-DHARD_P').only_on('FM6', 'OUTPUT', 'DECIMATION') # moved cntrlHBW to FM6 (put ELP in FM10)
        ezca.get_LIGOFilter('ASC-DHARD_Y').only_on('FM6', 'OUTPUT', 'DECIMATION') # Trying newctrl from alog 66339, JCD 22Dec2022
        self.dhardTramp = 5
        ezca['ASC-DHARD_P_TRAMP'] = self.dhardTramp
        ezca['ASC-DHARD_Y_TRAMP'] = self.dhardTramp
        
        #turn input on with gain = 0 to avoid transients alog71681
        ezca.switch('ASC-DHARD_P', 'INPUT', 'ON')
        ezca.switch('ASC-DHARD_Y', 'INPUT', 'ON')
        
        self.timer['dhardOn'] = 1
        self.timer['wait'] = 1
        self.counter = 1
        self.convergenceCounter = 0


    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        wfsOffloadList  = ['DHARD']
        wfsTolerancePit = [70] #GLM was 200 220902
        wfsToleranceYaw = [70] #GLM was 200 220902

        # Check if the AS port shutters are open
        if ezca['SYS-MOTION_C_SHUTTER_G_STATE'] == 1 or ezca['SYS-MOTION_C_FASTSHUTTER_A_STATE'] == 1:
            notify('Shutter closed!')
            return False

        if self.timer['dhardOn'] and self.counter == 1:
            # Turn the DHARD loops on by ramping up the filter gains
            # SED added factor of two when she commented out ITMs to compensate for the lost actuation

            ezca['ASC-DHARD_P_GAIN'] = lscparams.asc_gains['DHARD']['P']['DHARD_WFS_initial']
            ezca['ASC-DHARD_Y_GAIN'] = lscparams.asc_gains['DHARD']['Y']['DHARD_WFS_initial']
            self.timer['dhardOn'] = self.dhardTramp + 20
            self.counter += 1

        if self.timer['wait'] and self.counter == 2 and self.timer['dhardOn']:
            # wait for convergence, not just a single zero-crossing
            if ISC_library.asc_convergence_checker(wfsOffloadList, wfsTolerancePit, wfsToleranceYaw):
                self.convergenceCounter += 1
                self.timer['wait'] = 1
                log('Looks like we are close to zero - recheck in 1 sec to be sure')
            else:
                notify('waiting for ASC to offload before increasing gain')
                self.timer['wait'] = 1
            if self.convergenceCounter > 2:
                self.counter += 1
                self.convergenceCounter = 0 # reset for next round

        if self.counter == 3:
            # We must be converged now, so turn off smooth input limiter. JCD 22Feb2019
            ezca['ASC-DHARD_P_SMOOTH_ENABLE'] = 0
            ezca['ASC-DHARD_Y_SMOOTH_ENABLE'] = 0
            self.timer['wait'] = ezca['ASC-DHARD_P_SMOOTH_TRAMP']
            self.counter +=1

        if self.counter == 4 and self.timer['wait']:
            ezca['ASC-DHARD_P_GAIN'] = lscparams.asc_gains['DHARD']['P']['DHARD_WFS_final'] #was DHARD_WFS_final 20240306 changed while we regain 04a alignment
            ezca['ASC-DHARD_Y_GAIN'] = lscparams.asc_gains['DHARD']['Y']['DHARD_WFS_final'] #was DHARD_WFS_final 20240306 changed while we regain 04a alignment
            self.counter += 1

        if self.counter == 5:
            return True

##################################################
# STATES: Shutter ALS
##################################################

class PARK_ALS_VCO(GuardState):
    index = 306
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        ezca['ALS-C_COMM_PLL_PRESET'] = 'Low'
        ezca['ALS-C_DIFF_PLL_PRESET'] = 'Low'
        self.timer['wait'] = 5
        self.counter = 1


    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.timer['wait'] and self.counter == 1:
            ezca['ALS-C_COMM_VCO_TUNEOFS'] = 2
            self.timer['wait'] = 0.2
            self.counter += 1

        if self.timer['wait'] and self.counter == 2:
            # Turn ALS corner station VCOs off to reduce whistles.
            ezca['ALS-C_DIFF_VCO_POWERDISABLE'] = 1
            ezca['ALS-C_DIFF_FDD_POWERDISABLE'] = 1
            ezca['ALS-C_COMM_VCO_POWERDISABLE'] = 1 # 1 = do disable = off
            ezca['ALS-C_COMM_FDD_POWERDISABLE'] = 1
            self.counter += 1
            self.timer['wait'] = 0.2

        if self.timer['wait'] and self.counter == 3:
            return True


class SHUTTER_ALS(GuardState):
    index = 307
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.ALS_ESD = 'X'
        nodes['ALS_XARM'] = 'SHUTTERED'
        nodes['ALS_YARM'] = 'SHUTTERED'
        nodes['ALS_COMM'] = 'SHUTTERED'
        if lscparams.ALS_ESD == 'Y':
            nodes['ALS_DIFF_ETMY_ESD'] = 'SHUTTERED'
            self.Diff= nodes.nodes['ALS_DIFF_ETMY_ESD']
        else:
            nodes['ALS_DIFF'] = 'SHUTTERED'
            self.Diff= nodes.nodes['ALS_DIFF']
        self.timer['wait'] = 1
        self.Xarm= nodes.nodes['ALS_XARM']
        self.Yarm= nodes.nodes['ALS_YARM']
        self.Comm= nodes.nodes['ALS_COMM']
        self.shuttersClosed = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if not self.timer['wait']:
            return
        if self.Xarm.arrived and self.Yarm.arrived and self.Comm.arrived and self.Diff.arrived and not self.shuttersClosed:
            ezca['SYS-MOTION_X_SHUTTER_A_CLOSE'] = 1
            ezca['SYS-MOTION_Y_SHUTTER_B_CLOSE'] = 1
            self.shuttersClosed = True
        else:
            notify('nodes not arrived')

        if self.shuttersClosed:
            return True

class IDLE_ALS(GuardState):
    index = 310
    request = False
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        nodes['ALS_DIFF'] = 'IDLE'
        nodes['ALS_COMM'] = 'IDLE'
        return True
    
##################################################
# STATES: CARM TO RESONANCE
##################################################

class CARM_OFFSET_REDUCTION(GuardState):
    index = 308
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # start offset ramp
        ezca.get_LIGOFilter('LSC-TR_CARM').ramp_offset(-7, ramp_time=10, wait=False)

        # read back the REFL_DC value before CARM reduction
        self.PrefFraction= lscparams.carmHO_PrefFraction # from alog 43346, REFL_DC fraction at OFFSET = -40 (arm transmission 800)
        self.OFSideal    = lscparams.carmHO_OFSideal # was-54 # this is the target corresponding to about 85% of arm buildup. Used in step 6
        self.P0 = ezca['LSC-XARM_OFFSET']
        log(['REFL_DC before CARM reduction ',str(self.P0)])

        self.timer['CARM_ramp'] = ezca['LSC-TR_CARM_TRAMP']
        self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):

        if self.counter == 1 and self.timer['CARM_ramp']:

            #this increase in gain doesn't seem necessary or good here? Aug 8th 2018
            #ezca['LSC-SRCL1_GAIN'] = -25
            self.counter += 1

        if self.counter == 2: # was CARM_15PM state
            # set up PR3 wire heating compenstation
            #commenting out since new PR3 baffle hopefully solves this problem
            #ezca.get_LIGOFilter('SUS-PR3_M1_DRIVEALIGN_P2P').ramp_offset(0.25, ramp_time=60, wait=False)
            #ezca.get_LIGOFilter('SUS-PR3_M1_DRIVEALIGN_P2P').switch_on('OFFSET')

            # back to ramping TR_CARM
            ezca.get_LIGOFilter('LSC-TR_CARM').ramp_offset(lscparams.carm_offset['TR_CARM_offsets']['DARM_boost'], ramp_time=5)
            self.timer['CARM_ramp'] = ezca['LSC-TR_CARM_TRAMP']
            self.counter +=1
        if self.counter == 3 and self.timer['CARM_ramp']:
            # set gains for this state
            #ezca.get_LIGOFilter('LSC-DARM1').ramp_gain(400, ramp_time=2)#is 400 already commenting out SED Oct 2019
            if ezca['LSC-REFL_A_LF_NORM_MON']<6.5 and ezca['LSC-TR_X_NORM_INMON']<210.0:
                notify('Warning! low recycing gain!')
            self.counter += 1
        if self.counter == 4 and self.timer['CARM_ramp']: # was CARM_10PM state
            ezca.switch('LSC-DARM1', 'FM4', 'ON')
            time.sleep(1)
            # ramp gain to about 67% of the full power build up before making changes to
            # TR_CARM loops (was -48 before TCS work)
            # note: changing away from -40 will affect the reference REFL_DC power PrefFraction
            ezca.get_LIGOFilter('LSC-TR_CARM').ramp_offset(lscparams.carm_offset['TR_CARM_offsets']['carmH0_OFSref'], ramp_time=10, wait=False)
            ezca['LSC-ARM_INPUT_MTRX_TRAMP'] = 5
            #commented this out when we have low recycling gain because it doesn't
            #   seem necessary, might be if alignment references were set better.
            #ISC_library.intrix_OMCAS45['DARM', 'ASAIR_A45Q'] /=2
            #time.sleep(0.1)
            #ISC_library.intrix_OMCAS45.load()
            #not good with low recycling gain
            ezca['LSC-REFLBIAS_GAIN'] = lscparams.carm_offset['gains']['LSC_REFLBIAS_CARM_OFFSET_REDUCTION']
            self.timer['CARM_ramp'] = ezca['LSC-TR_CARM_TRAMP'] + 1
            self.counter += 1

        if self.counter == 5 and self.timer['CARM_ramp']: # was CARM_5PM state
            ezca.switch('LSC-REFLBIAS', 'FM2', 'ON')
            # now look ar REFL_DC to calculate the correction due to the current effective arm loss.
            #    (alog xxx by Stefan, Danny)
            self.P = call_with_timeout(cdu.avg, 2, 'LSC-REFL_A_LF_OUTPUT')
            log(['REFL_DC at reference offset: ',str(self.P)])
            self.OFSref=ezca['LSC-TR_CARM_OFFSET']
            # calculation
            self.PFraction=self.P/self.P0
            self.OFSactualSquared = 1.0/(  1.0/(self.OFSideal**2) + (self.PrefFraction - self.PFraction ) / (self.OFSref**2)  )
            if (self.OFSactualSquared>55**2) or (self.OFSactualSquared<42**2):  #threshold for worst alignment (effecitvely lowest refl power that is tolerated) lowered from 42 to 39 SED CRC JCD Jan 2 2018
                log(['Error: Calculated arm offset outside limits. Stopping here. '])
                log(['Offset would be ',str(-math.sqrt(abs(self.OFSactualSquared))) ])
                log(['OFSideal  ',str(self.OFSideal) ])
                log(['PrefFraction  ',str(self.PrefFraction) ])
                log(['PFraction  ',str(self.PFraction) ])
                log(['OFSref  ',str(self.OFSref) ])
            else:
                self.OFSactual = -math.sqrt(self.OFSactualSquared)
                log(['Using LSC-TR_CARM_OFFSET of ',str(self.OFSactual),' for last step of CARM reduction.'])
                # done. Continue with handoff
                self.counter +=1
        if self.counter ==6:
            return True

class CARM_5_PICOMETERS(GuardState):
    index = 309
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.timer['CARM_ramp']= ezca['LSC-TR_CARM_TRAMP']
        self.counter = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        # read back the REFL_DC value before CARM reduction
        self.PrefFraction= lscparams.carmHO_PrefFraction # from alog 43346, REFL_DC fraction at OFFSET = -40 (arm transmission 800)
        self.OFSideal    = lscparams.carmHO_OFSideal # was-54 # this is the target corresponding to about 85% of arm buildup. Used in step 6
        self.P0 = ezca['LSC-XARM_OFFSET']
        self.P = call_with_timeout(cdu.avg, 2, 'LSC-REFL_A_LF_OUTPUT')
        log(['REFL_DC at reference offset: ',str(self.P)])
        self.OFSref=ezca['LSC-TR_CARM_OFFSET']
        # calculation
        self.PFraction=self.P/self.P0
        self.OFSactualSquared = 1.0/(  1.0/(self.OFSideal**2) + (self.PrefFraction - self.PFraction ) / (self.OFSref**2)  )
        if self.counter == 0 :
            if (self.OFSactualSquared>55**2) or (self.OFSactualSquared<41**2) :
                log(['Error: Calculated arm offset outside limits. Stopping here. '])
                log(['Offset would be ',str(-math.sqrt(abs(self.OFSactualSquared))) ])
                log(['OFSideal  ',str(self.OFSideal) ])
                log(['PrefFraction  ',str(self.PrefFraction) ])
                log(['PFraction  ',str(self.PFraction) ])
                log(['OFSref  ',str(self.OFSref) ])
            else:
                self.OFSactual = -math.sqrt(self.OFSactualSquared)
                log(['Using LSC-TR_CARM_OFFSET of ',str(self.OFSactual),' for last step of CARM reduction.'])
                # done. Continue with handoff
                #prepare to transition to REFL9 when we are at 85% of the arm build up that we get on resonance.  We need to update this number when the recycling gain changes. GLM +SED changed it from -41 to -48 Sept1st 2018
                # was -54 before TCS work
                # now using self.OFSactual
                ezca.get_LIGOFilter('LSC-TR_CARM').ramp_offset(self.OFSactual, ramp_time=5)
                self.timer['CARM_ramp'] = ezca['LSC-TR_CARM_TRAMP']
                self.counter += 1
        if self.counter == 1 and self.timer['CARM_ramp']:
            #ezca['LSC-TR_CARM_OFFSET'] = -52 #hack that bypasses all the PRG adjustment above, 2/3/23, should definitely be taken out asap
            return True


class CARM_TO_REFL(GuardState):
    index = 409
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # set up TR REFLAIR 9
        ezca.get_LIGOFilter('LSC-TR_REFLAIR9').ramp_gain(lscparams.LSC_TR_REFLAIR9_CARM_TO_REFL, ramp_time=0, wait=False) # gain = -0.8
        self.counter = 1
        self.timer['wait'] = 0.2
        self.myoffset = 0
        self.NN = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.timer['wait']:
            if self.counter == 1:
                # averaging that allows for lockloss checking
                if self.NN < 20:
                    self.myoffset = self.myoffset + ezca['LSC-TR_REFLAIR9_INMON']
                    self.timer['wait'] = 0.2
                    self.NN += 1
                else:
                    self.myoffset = self.myoffset / self.NN
                    ezca['LSC-TR_REFLAIR9_OFFSET'] = round(-1*self.myoffset,3)
                    self.counter += 1

        if self.counter == 2:
            ezca['LSC-REFL_OR_REFLAIR'] = 0.0  # this means use reflair
            ezca.switch('LSC-TR_REFLAIR9','FM1','FM2','FM5','OFFSET','ON')

            # Transition between TRXY and REFLAIR
            ISC_library.intrix['REFLBIAS', 'TR_REFL9'] = 2.0  #put back from 3.0 to 2.0 on 09/10/2018
            ISC_library.intrix['REFLBIAS', 'TR_CARM'] = 0.0  
            ezca['LSC-PD_DOF_MTRX_TRAMP'] = 0.2
            time.sleep(0.1)
            ISC_library.intrix.load()
            self.counter +=1
        if self.counter >= 3:
            return True


class RESONANCE(GuardState):
    """
    Reduce the CARM detuning to 0 pm.
    """
    index = 410
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        ezca.get_LIGOFilter('LSC-TR_REFLAIR9').ramp_offset(0, ramp_time = 5, wait=False)
        #O2 guardian started input matrix at 40, here was reduced to 8.
        #ISC_library.intrix_OMCAS45['DARM', 'ASAIR_A45Q'] /=3
        time.sleep(0.1)
        ISC_library.intrix_OMCAS45.load()
        self.counter = 0
        self.timer['matrix_ramp'] = ezca['LSC-TR_REFLAIR9_TRAMP']

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['IMC','DRMI'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.timer['matrix_ramp']:
            if self.counter == 0:
                # remove the ETM L3 lowpass
                ezca.switch('SUS-ETMX_L3_LOCK_L', 'FM6', 'OFF')
                ezca.switch('SUS-ETMY_L3_LOCK_L', 'FM6', 'OFF')
                ezca.switch('ASC-DHARD_P', 'FM3', 'ON')
                self.timer['matrix_ramp'] = 3
                self.counter +=1
            elif self.counter == 1:
                ezca['ASC-DHARD_P_GAIN'] = lscparams.asc_gains['DHARD']['P']['RESONANCE']
                ezca['ASC-DHARD_Y_GAIN'] = lscparams.asc_gains['DHARD']['Y']['RESONANCE']
                self.timer['matrix_ramp'] = 2
                self.counter += 1

            elif self.counter == 2 and self.timer['matrix_ramp']:
                # HY 08/15/2018 turn on ISIFF
                #for tm in ['ETMX', 'ETMY', 'ITMX', 'ITMY']:
                #    ezca['SUS-%s_M0_ISIFF_L2P_TRAMP'%tm]=10
                #    ezca['SUS-%s_M0_ISIFF_L2P_GAIN'%tm]=-1
                return True


class CARM_TO_ANALOG(GuardState):
    """
    Transition control of CARM from digital normalized REFL AIR 9 I to analog
    unnormalized REFL A 9 I.
    """
    index = 420
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # use reflair instead of refl if this is true
        self.reflair = False
        if self.reflair:
            #turn off ALS input, engage REFLAIR 9 input
            ezca['LSC-REFL_SUM_B_IN1EN'] = 0
            ezca['LSC-REFL_SUM_B_IN2EN'] = 1
            ezca['LSC-REFL_SUM_B_IN2GAIN'] = -32  # Start with low gain, ramp in run state - avoids glitch
            ezca['LSC-REFL_SUM_A_IN2EN'] = 0
        else:
            ezca['LSC-REFL_SUM_A_IN2GAIN'] = -32  # Start with low gain, ramp in run state - avoids glitch
            ezca['LSC-REFL_SUM_A_IN2EN'] = 1

        self.timer['wait'] = 0
        self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.counter == 1 and self.timer['wait']:
            if self.reflair:
                # if we are using REFL air we can't do things in the same way as for
                # refl since we would have to start with the gain slider above the max.
                if ezca['LSC-REFL_SUM_B_IN2GAIN'] < -15+lscparams.reflair_relative_gain:
                    ezca['LSC-REFL_SUM_B_IN2GAIN'] += 1
                else:
                    self.timer['wait'] = 5
                    self.counter += 1
            else:
                if ezca['LSC-REFL_SUM_A_IN2GAIN'] < -15 + 4:  # EMC increased 6 dB addition to 7 dB for additional 20% HAM1 vent
                    ezca['LSC-REFL_SUM_A_IN2GAIN'] += 1
                else:
                    self.timer['wait'] = 5
                    self.counter += 1

        # Add +3 dB to IMC servo
        if self.counter == 2 and self.timer['wait']:
            if ezca['IMC-REFL_SERVO_IN1GAIN'] < 2: 
                ezca['IMC-REFL_SERVO_IN1GAIN'] += 1 # move IMC IN1 Gain from -1 dB to +2 dB, 2021 March 19 alog 58307
                ezca['IMC-REFL_SERVO_IN2GAIN'] += 1 # move IMC IN2 Gain from -12 dB to -9 dB, 2021 March 19
                self.timer['wait'] = 0.1
            else:
                self.counter += 1

        if self.counter == 3 and self.timer['wait']:
            # Switch off a double boost in the digital CARM path.
            ezca.switch('LSC-REFLBIAS','FM2','OFF')
            self.timer['wait'] = 2
            self.counter += 1

        if self.counter == 4 and self.timer['wait']:
            if ezca['LSC-REFL_SUM_A_IN2GAIN'] < 8 + 4: # EMC increased 6 dB addition to 7 dB for additional 20% HAM1 vent
                ezca['LSC-REFL_SUM_A_IN2GAIN'] += 1
                self.timer['wait'] = 0.1
            else:
                #now CARM ugf is 7 kHz, as of March 19 2021
                self.timer['wait'] = 5
                self.counter += 1

        if self.counter == 5 and self.timer['wait']:
            # Now turn on a digital CARM antiboost. Then engage an analog boost
            # (40 Hz pole, 4 kHz zero) common to both digital and analog CARM paths.
            ezca.switch('ALS-C_REFL_DC_BIAS','FM1','ON')
            self.timer['wait'] = 2
            self.counter += 1

        if self.counter == 6 and self.timer['wait']:
            # enable boost with 4kHz zero
            ezca['LSC-REFL_SERVO_COMCOMP'] = 1
            self.timer['wait'] = 2
            self.counter += 1

        if self.counter == 7 and self.timer['wait']:
            ezca.get_LIGOFilter('ALS-C_REFL_DC_BIAS').ramp_gain(0, ramp_time=2, wait=False)
            ezca['LSC-MCL_TRAMP'] = 1
            self.timer['wait'] = 2
            self.counter += 1

            ezca['LSC-REFL_SUM_A_IN1EN'] = 0
            self.counter +=1
            self.timer['wait'] = 0.2

        # Redistribute Gain
        if self.counter == 9 and self.timer['wait']:
            # make sure that the fast gain is 0db, so that we avoid attenuating the
            # signal at the input then amplifying it later
            if ezca['LSC-REFL_SERVO_FASTGAIN'] > 0:
                ezca['LSC-REFL_SERVO_IN1GAIN']  += 1 # from -16 to -9 dB
                ezca['LSC-REFL_SERVO_FASTGAIN'] -= 1 # from 7 to 0 dB
                ezca['LSC-MCL_GAIN'] *= round(10**(-1./20),4) # from 47.6 dB to 40.6 dB
            else:
                self.timer['wait'] = 0.5
                self.counter += 1

        if self.counter == 10 and self.timer['wait']:
            if ezca['IMC-REFL_SERVO_IN2GAIN'] > -18:
                ezca['LSC-REFL_SERVO_IN1GAIN'] += 1 # from -9 to -3 dB
                ezca['IMC-REFL_SERVO_IN2GAIN'] -= 1 # from -12 to -18 dB
                ezca['LSC-MCL_GAIN'] *= round(10**(-1./20),4) # from 40.6 to 34.6 dB
            else:
                self.timer['wait'] = 0.5
                self.counter += 1

        ### INCREASE IMC AND CARM GAIN
        # +3 dB on LSC IN1 gain slider (CRC removed Mar 31 2021)
        if self.counter == 11 and self.timer['wait']:
            # ezca['LSC-REFL_SERVO_IN1GAIN'] += 3 # from -3 to 0 dB
            # self.timer['wait'] = 0.5
            self.counter += 1

        # Turn on IMC Boost 2 and give the IMC +3 dB
        if self.counter == 12 and self.timer['wait']:
            ezca['IMC-REFL_SERVO_IN1GAIN'] += 1 #alog 66518, SED reduced this to put IMC ugf at 55kHz (top of phase bubble) before we power up from -1 to 2 dB
            ezca['IMC-REFL_SERVO_COMBOOST'] = 2 # Boost 2 on
            self.timer['wait'] = 0.5
            self.counter += 1

        # +12 dB on IMC IN2 and MCL GAIN
        if self.counter == 13 and self.timer['wait']:
            if ezca['IMC-REFL_SERVO_IN2GAIN'] < -6: 
                ezca['IMC-REFL_SERVO_IN2GAIN'] += 1 # from -18 to -6 dB
                ezca['LSC-MCL_GAIN'] *= round(10**(1./20),4) # from 34.6 to 47.6 dB
            else:
                self.timer['wait'] = 0.5
                self.counter += 1

        # +16 dB on CMB Fast, -16 dB on IMC IN2
        if self.counter == 14 and self.timer['wait']:
            if ezca['LSC-REFL_SERVO_FASTGAIN'] < 16:
                ezca['IMC-REFL_SERVO_IN2GAIN'] -= 1  # from -6 to -22 dB
                ezca['LSC-REFL_SERVO_FASTGAIN'] += 1 # from   0 to +16 dB
            else:
                self.timer['wait'] = 0.5
                self.counter += 1

        # IMC gain +2 dB (CRC removed Mar 31 2021)
        # Turn on CARM Boost (Removed for now)
        if self.counter == 15 and self.timer['wait']:
            # if ezca['IMC-REFL_SERVO_IN1GAIN'] < 7: 
            #     ezca['IMC-REFL_SERVO_IN1GAIN'] += 1 # move IMC IN1 Gain from +5 dB to +7 dB, 2021 March 19
            #     ezca['IMC-REFL_SERVO_IN2GAIN'] += 1 # move IMC IN2 Gain from -22 dB to -20 dB, 2021 March 19
            # else:  
            #     ezca['LSC-REFL_SERVO_COMBOOST'] = 1
            #     self.timer['wait'] = 0.5
            self.counter += 1

        # CRC 2021 Apr 19 - lowered MC2 crossover gain 
        if self.counter == 16 and self.timer['wait']:
            ezca['LSC-MCL_GAIN'] *= 0.5
            self.counter += 1

        if self.counter == 17:
            return True


##################################################
# STATES: DRMI on POP and DARM offset
##################################################

class DRMI_TO_POP(GuardState):
    """
    Transition control of DRMI from REFLAIR 3f signals to POPAIR 1f signals.

    """
    index = 425
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        ezca.switch('SUS-ETMX_L3_LOCK_L', 'FM2', 'OFF')
        nodes['ISC_DRMI']='IDLE'
        time.sleep(0.1)

        ISC_library.intrix['SRCL', 'REFLAIR_B27I']  = 0
        ISC_library.intrix['SRCL', 'REFLAIR_B135I'] = 0
        ISC_library.intrix['MICH', 'REFLAIR_B135Q'] = 0
        ISC_library.intrix['MICH', 'REFLAIR_B27I'] = 0
        ISC_library.intrix['PRCL', 'REFLAIR_B27I']  = 0
        self.use_POPAIR = False
        if self.use_POPAIR:
            #transition PRCL
            ISC_library.intrix['PRCL', 'POPAIR_A9I']  = lscparams.gain['DRMI_PRCL']['AIR_1F_9']
            #transition MICH
            ISC_library.intrix['MICH', 'POPAIR_A45Q'] = lscparams.gain['DRMI_MICH']['AIR_1F_45']
            #transition SRCL
            ISC_library.intrix['SRCL', 'POPAIR_A45I'] = lscparams.gain['DRMI_SRCL']['AIR_1F_45']
            ISC_library.intrix['SRCL', 'POPAIR_A9I']  = lscparams.gain['DRMI_SRCL']['AIR_1F_9']
        else:
            #transition PRCL
            ISC_library.intrix['PRCL', 'POP_A9I']  = lscparams.gain['DRMI_PRCL']['1F_9']  #these have been adjusted in lscparams for HAM1 vent
            #transition MICH
            ISC_library.intrix['MICH', 'POP_A45Q'] = lscparams.gain['DRMI_MICH']['1F_45']
            #transition SRCL
            ISC_library.intrix['SRCL', 'POP_A45I'] = lscparams.gain['DRMI_SRCL']['1F_45']
            ISC_library.intrix['SRCL', 'POP_A9I']  = lscparams.gain['DRMI_SRCL']['1F_9']


        self.tramp=5
        ezca['LSC-PD_DOF_MTRX_TRAMP'] = self.tramp
        time.sleep(0.2)
        ISC_library.intrix.load()
        self.timer['ramp_drmi']=self.tramp+1


        # Set front-end triggering for LSC to trigger on POP DC
        for dof in ['DARM', 'MICH', 'PRCL', 'SRCL', 'MCL']:
            #before we switch from triggering on POP18 to triggering on POP_A_DC, we override the triggers
            ezca['LSC-{}_TRIG_THRESH_ON'.format(dof)]     = lscparams.thresh['Override']['ON']
            ezca['LSC-{}_TRIG_THRESH_OFF'.format(dof)]    = lscparams.thresh['Override']['OFF']
            ezca['LSC-{}_FM_TRIG_THRESH_ON'.format(dof)]  = lscparams.thresh['Override']['ON']
            ezca['LSC-{}_FM_TRIG_THRESH_OFF'.format(dof)] = lscparams.thresh['Override']['OFF']
        time.sleep(0.2)
        ezca['LSC-TRIG_MTRX_2_2'] = 0
        ezca['LSC-TRIG_MTRX_3_2'] = 0
        ezca['LSC-TRIG_MTRX_4_2'] = 0
        ezca['LSC-TRIG_MTRX_4_8'] = 0
        time.sleep(0.2)
        for num, dof in enumerate(['DARM', 'MICH', 'PRCL', 'SRCL', 'MCL']):
            #ezca['LSC-TRIG_MTRX_{}_17'.format(num+1)] = 1 # Use POPAIR
            ISC_library.trigrix.put(dof,'POP_A_DC',1)

        #ezca['LSC-TRIG_MTRX_10_12'] = 1 # ensure IFO_TRIG is on POPAIR_A_DC. Jenne, Adrian, 26Sept2019
        #ezca['LSC-TRIG_MTRX_10_2']  = 0 # stop using POP_18_I

        # CRC, next two lines should do the same as the above two lines
        ISC_library.trigrix.put('IFO_trig','POP_A_DC', 1)
        ISC_library.trigrix.put('IFO_trig','POPAIR_B_RF18_I', 0)

        time.sleep(0.2)
        for dof in ['DARM', 'MICH', 'PRCL', 'SRCL', 'MCL','IFO']:
            ezca['LSC-{}_TRIG_THRESH_OFF'.format(dof)] = lscparams.thresh['LOCKLOSS_CHECK']['OFF']  #adjusted for HAM1 BS change
            ezca['LSC-{}_TRIG_THRESH_ON'.format(dof)]  = lscparams.thresh['LOCKLOSS_CHECK']['ON']

        # Turn down whitening gains on QPDs,
        # and adjust digital gains accordingly
        for ii in ['X', 'Y']:
            for jj in ['A', 'B']:
                ezca.switch('ASC-{}_TR_{}_NSUM'.format(ii, jj), 'HOLD', 'ON')
                time.sleep(0.1)
                ezca['ASC-{}_TR_{}_WHITEN_GAIN'.format(ii, jj)] = 0 # dB
                ezca['ASC-{}_TR_{}_NSUM_GAIN'.format(ii, jj)] = 7.94  # 
                time.sleep(0.1)
                ezca.switch('ASC-{}_TR_{}_NSUM'.format(ii, jj), 'HOLD', 'OFF')
            ezca['LSC-{}_TIDAL_REDTRIG_THRESH_ON'.format(ii)]=8000
            ezca['LSC-{}_TIDAL_REDTRIG_THRESH_OFF'.format(ii)]=3000
            # reset calibration of circulating power for new whitening gain.
            ezca.switch('ASC-{}_PWR_CIRC'.format(ii), 'FM1', 'ON') # Use this 3dB filter until we recal the kW filter
            # ezca.switch('ASC-{}_PWR_CIRC'.format(ii), 'FM2', 'OFF')
        for jj in ['A', 'B']:
            ezca['ASC-POP_{}_WHITEN_GAIN'.format(jj)] = 0 # dB

        self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):

        if self.counter == 1:
            #August 8th 2018 commented out since it doesn't seem like we have the gain margin for this
            #ezca.get_LIGOFilter('LSC-DARM1').ramp_gain(1400, ramp_time=5, wait=False)

            # HY 10/26/18 turn on a boost in SRCL to suppress the usei
            ezca.get_LIGOFilter('LSC-SRCL1').switch_on('FM3', wait=False)
            self.counter += 1
            self.timer['wait'] = ezca['LSC-DARM1_TRAMP']

        if self.counter == 2 and self.timer['wait']:
            return True


class DARM_OFFSET(GuardState):
    index = 427
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # DARM1_OFFSET at 9.7W needed to be 5e-5 to give ~20mA on DCPD_SUM. 28Jan2019 JCD
        if not lscparams.power_up_on_RF:
            ezca['LSC-DARM1_TRAMP'] = 5
            ezca.switch('LSC-DARM1', 'OFFSET', 'ON')
            power_level = ezca['PSL-POWER_SCALE_OFFSET']
            if abs(power_level-10) < 2.5:
                ezca['LSC-DARM1_OFFSET'] = lscparams.omc_sign * 2e-5
                ezca['OMC-READOUT_X0_OFFSET'] = 28*.73714
            elif abs(power_level-15) < 2.5:
                ezca['LSC-DARM1_OFFSET'] = lscparams.omc_sign * 1.2e-5
                ezca['OMC-READOUT_X0_OFFSET'] = 16.8*.73714
            elif abs(power_level-20) < 5:
                ezca['LSC-DARM1_OFFSET'] = lscparams.omc_sign * 1e-5
                ezca['OMC-READOUT_X0_OFFSET'] = 14*.73714
            else:
                #the one that normally happens
                ezca['LSC-DARM1_OFFSET'] = lscparams.omc_sign * 2e-5
                ezca['OMC-READOUT_X0_OFFSET'] = 28*.73714

            # kludge for low power mode - these settings should give 20mA DCPD SUM
            # "nominal" darm offset is 1e-5, corresponding to X0_OFFSET = 14*.73714=10.32
            # X0_OFFSET needs to be scaled with darm offset
            if power_level < 3.5:
                ezca['LSC-DARM1_OFFSET'] = lscparams.omc_sign * 9e-5
                ezca['OMC-READOUT_X0_OFFSET'] = 42*.73714
        else:
            log('power up on RF flag set, not setting a DARM offset')
        self.timer['wait'] = 0
        self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        # Engage DARM Boosts
        if self.counter ==1:
            #ramping this filter off in the low noise transitions was causing locklosses, so we will try not turning it on. SED/CMC Dec 5 2024
            #ezca.get_LIGOFilter('LSC-DARM1').turn_on('FM1')
            self.timer['wait'] = 1
            self.counter += 1
        elif self.counter == 2 and self.timer['wait']:
            #ezca.get_LIGOFilter('LSC-DARM1').turn_on('FM2')  #SED commented this out since turning it off has caused some locklosses
            self.timer['wait'] = 1
            self.counter += 1
        elif self.counter == 3 and self.timer['wait']:
            ezca.get_LIGOFilter('LSC-DARM1').turn_on('FM10')
            self.timer['wait'] = 3
            self.counter += 1
        nodes['OMC_LOCK'] = 'READY_W_NO_WHITENING'
        return True

class ENGAGE_RF_VIOLINS(GuardState):
    """
    Engage violin damping while DARM is still on RF.

    """
    index = 428
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        nodes['VIOLIN_DAMPING'] = 'DAMPING_ON_RF'

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        return True


##################################################
# STATES: ASC for full lock
##################################################

class PREP_ASC_FOR_FULL_IFO(GuardState):
    index = 429
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):

        #ezca['ASC-SRC1_P_SMOOTH_OFFSET'] = -5       # Offset of -5 in full IFO, with AS_A_72_Q mtrx = 1
        #ezca['ASC-SRC1_P_SMOOTH_OFFSET_ENABLE'] = 1

        # set ASC loop gains to zero so nothing actually gets turned on (everything but MICH and DHARD)
        for py in ['P','Y']:
            for dof in ['INP1','PRC1','PRC2','SRC1','SRC2','CSOFT','DSOFT','CHARD']:
                ezca['ASC-%s_%s_GAIN'%(dof,py)] = 0
        for py in ['PIT','YAW']:
            for dof in ['3','4','5']:
                ezca['ASC-ADS_%s%s_DOF_GAIN'%(py,dof)] = 0

        # POPX centering
        ISC_library.asc_intrix['PIT']['DC6', 'POP_X_DC'] = 1
        ISC_library.asc_intrix['YAW']['DC6', 'POP_X_DC'] = 1
        ezca.get_LIGOFilter('ASC-DC6_P').only_on('INPUT','OUTPUT','DECIMATION')
        ezca.get_LIGOFilter('ASC-DC6_Y').only_on('INPUT','OUTPUT','DECIMATION')

        # make sure the violin band stops are on (otherwise chaos breaks loose!!!!)
        for sus in ['ETMX', 'ETMY', 'ITMX', 'ITMY']:
            for py in ['P','Y']:
                ezca.switch('SUS-%s_L2_LOCK_%s'%(sus,py), 'FM2', 'ON')


        # increase MICH P gain
        ezca.get_LIGOFilter('ASC-MICH_P').ramp_gain(-2.4, ramp_time=5, wait=False)
        
        ISC_library.asc_intrix['PIT'].put('INP1', [], 0)
        ISC_library.asc_intrix['YAW'].put('INP1', [], 0)


        #'''
        ISC_library.asc_intrix['PIT']['INP1','REFL_A_RF45_I']  = 1.74  # multiplied by two after HAM1 vent, added 20% to account for 40% BS in HAM1
        ISC_library.asc_intrix['PIT']['INP1','REFL_B_RF45_I']  = -2.93  # multiplied by two after HAM1 vent added 20% to account for 40% BS in HAM1

        # Updated inmtrx, SED, VS, JCD 21Apr2021
        ISC_library.asc_intrix['YAW']['INP1','REFL_A_RF9_I']  = 0 # was 1.44/1.41 # divided by 3 dB to account for 9Mhz compensation  # multiplied by two after HAM1 vent, added 20% to account for 40% BS in HAM1 
        ISC_library.asc_intrix['YAW']['INP1','REFL_B_RF9_I']  = 0 # was -2.88/1.41 # divided by 3 dB to account for 9Mhz compensation # multiplied by two after HAM1 vent, added 20% to account for 40% BS in HAM1
        ISC_library.asc_intrix['YAW']['INP1','REFL_A_RF45_I'] = 1.6
        ISC_library.asc_intrix['YAW']['INP1','REFL_B_RF45_I'] = -1.6
        #'''

        ISC_library.asc_intrix['PIT'].put('PRC2',[],0)
        ISC_library.asc_intrix['YAW'].put('PRC2',[],0)
        
        # PRC2 on POPX values, keeping for reference
        '''
        ISC_library.asc_intrix['PIT']['PRC2','POP_X_I'] = -0.05
        ISC_library.asc_intrix['YAW']['PRC2','POP_X_I'] = -0.1'''
        
        # PRC2 on REFL vals, tested
        # EMC added these new PRC2 on REFL values 20230301, have been tested at 2W and full power
        ISC_library.asc_intrix['PIT']['PRC2','REFL_A_RF9_I'] = 0.05614
        ISC_library.asc_intrix['PIT']['PRC2','REFL_B_RF9_I'] = 0.03369 
        # EMC updated yaw values with CHARD Y intrix change
        ISC_library.asc_intrix['YAW']['PRC2','REFL_A_RF9_I'] = 0.04554 # was 0.05374 
        ISC_library.asc_intrix['YAW']['PRC2','REFL_A_RF45_I'] = 0 #-0.059 
        ISC_library.asc_intrix['YAW']['PRC2','REFL_B_RF9_I'] = 0.09108 # was 0.10641   
        ISC_library.asc_intrix['YAW']['PRC2','REFL_B_RF45_I'] = 0 # -0.1062

        

        # SRC1
        ISC_library.asc_intrix['PIT'].put('SRC1', [], 0)
        ISC_library.asc_intrix['YAW'].put('SRC1', [], 0)
        ISC_library.asc_intrix['PIT']['SRC1','AS_A_RF72_Q']  = 1
        ISC_library.asc_intrix['PIT']['SRC1','AS_A_RF72_I']  = 0
        ISC_library.asc_intrix['PIT']['SRC1','AS_B_RF72_Q']  = 0
        ISC_library.asc_intrix['PIT']['SRC1','AS_B_RF72_I']  = 0
        ISC_library.asc_intrix['YAW']['SRC1','AS_A_RF72_Q']  = 1
        ISC_library.asc_intrix['YAW']['SRC1','AS_A_RF72_I']  = 0
        ISC_library.asc_intrix['YAW']['SRC1','AS_B_RF72_Q']  = 0
        ISC_library.asc_intrix['YAW']['SRC1','AS_B_RF72_I']  = 0


        #'''
        # CHARD insensitive to PRC2 JCD&VS, 26APr2021
        ISC_library.asc_intrix['PIT']['CHARD', 'REFL_A_RF9_I']  = 0.103 * 2.4/1.41 # divided by 3 dB to account for 9Mhz compensation #factor of 2.4 to compensate for HAM1 BS
        ISC_library.asc_intrix['PIT']['CHARD', 'REFL_A_RF45_I']  = 0.557 * 2.4 #factor of 2.4 to compensate for HAM1 BS
        ISC_library.asc_intrix['PIT']['CHARD', 'REFL_B_RF9_I']  = 0.13613 * 2.4/1.41 # divided by 3 dB to account for 9Mhz compensation #factor of 2.4 to compensate for HAM1 BS
        ISC_library.asc_intrix['PIT']['CHARD', 'REFL_B_RF45_I']  = 0.565 * 2.4 #factor of 2.4 to compensate for HAM1 BS

        # Updated inmtrx, EMC 20230216 after rephasing WFS and correcting seg4 DC gain
        # EMC updated 20230327 to include 9 and 45 sensing
        ISC_library.asc_intrix['YAW']['CHARD', 'REFL_A_RF9_I']  = -0.72 # was 0
        ISC_library.asc_intrix['YAW']['CHARD', 'REFL_A_RF45_I']  =  3.1584 # was 2.632
        ISC_library.asc_intrix['YAW']['CHARD', 'REFL_B_RF9_I']  = -0.72 # was 0
        ISC_library.asc_intrix['YAW']['CHARD', 'REFL_B_RF45_I'] = 6.3168 # was 5.264
        #'''

        # EMC commented out old SOFT intrix values and added new values\
        '''
        ISC_library.asc_intrix['YAW']['DSOFT_A', 'TRX_A']  =  0.684
        ISC_library.asc_intrix['YAW']['DSOFT_A', 'TRX_B']  = -0.308
        ISC_library.asc_intrix['YAW']['DSOFT_A', 'TRY_A']  =  0.646
        ISC_library.asc_intrix['YAW']['DSOFT_A', 'TRY_B']  = -0.420 # old intrix, commmented out by EMC
        '''

        # New intrix EMC
        ISC_library.asc_intrix['YAW']['DSOFT_A', 'TRX_A']  =  -0.0914 # new intrix to reduce hard/soft cross coupling
        ISC_library.asc_intrix['YAW']['DSOFT_A', 'TRX_B']  = 0.1468
        ISC_library.asc_intrix['YAW']['DSOFT_A', 'TRY_A']  =  0.8056
        ISC_library.asc_intrix['YAW']['DSOFT_A', 'TRY_B']  = -1.1537
        
        # EMC commented out old intrix
        '''
        # HY 12/22/18 since we have dither for DC locking point, no need to be insensitive to TMS
        ISC_library.asc_intrix['YAW']['CSOFT_A', 'TRX_A']  =  0.10
        ISC_library.asc_intrix['YAW']['CSOFT_A', 'TRX_B']  = -1.0
        ISC_library.asc_intrix['YAW']['CSOFT_A', 'TRY_A']  = -0.67
        ISC_library.asc_intrix['YAW']['CSOFT_A', 'TRY_B']  =  0.85 # old intrix, commented out by EMC
        '''

        # New intrix EMC
        ISC_library.asc_intrix['YAW']['CSOFT_A', 'TRX_A']  =  0.532
        ISC_library.asc_intrix['YAW']['CSOFT_A', 'TRX_B']  = -0.8432
        ISC_library.asc_intrix['YAW']['CSOFT_A', 'TRY_A']  = -0.728
        ISC_library.asc_intrix['YAW']['CSOFT_A', 'TRY_B']  =  1.0405 # new intrix to reduce hard/soft cross coupling
        
        # EMC commented out old intrix
        '''
        # SOFT blending insens. to DHARD 20190205
        ISC_library.asc_intrix['PIT'].put('CSOFT_A', [], 0)
        ISC_library.asc_intrix['PIT']['CSOFT_A', 'TRX_A']  = -0.067 #was -0.1395, changed 20190205
        ISC_library.asc_intrix['PIT']['CSOFT_A', 'TRX_B']  =  0.155 #was 0.1353, changed 20190205
        ISC_library.asc_intrix['PIT']['CSOFT_A', 'TRY_A']  = -0.0016 #was 0, changed 20190205
        ISC_library.asc_intrix['PIT']['CSOFT_A', 'TRY_B']  = 0.0369 #was 0.0393, changed 20190205# old intrix, commented out by EMC
        '''


        # New intrix EMC
        ISC_library.asc_intrix['PIT'].put('CSOFT_A', [], 0)
        ISC_library.asc_intrix['PIT']['CSOFT_A', 'TRX_A']  = -0.108
        ISC_library.asc_intrix['PIT']['CSOFT_A', 'TRX_B']  = 0.11528
        ISC_library.asc_intrix['PIT']['CSOFT_A', 'TRY_A']  = -0.0413
        ISC_library.asc_intrix['PIT']['CSOFT_A', 'TRY_B']  = 0.07166 # new intrix to reduce hard/soft cross coupling
        
        # EMC commented out old intrix
        '''
        ISC_library.asc_intrix['PIT'].put('DSOFT_B', [], 0)
        ISC_library.asc_intrix['PIT']['DSOFT_A', 'TRX_A']  = -0.067 # was -0.0193, changed 20190205
        ISC_library.asc_intrix['PIT']['DSOFT_A', 'TRX_B']  = 0.155 # was 0.0176, changed 20190205
        ISC_library.asc_intrix['PIT']['DSOFT_A', 'TRY_A']  = 0.0016 # was 0.00335, changed 20190205
        ISC_library.asc_intrix['PIT']['DSOFT_A', 'TRY_B']  = -0.0369 # was -0.08375, changed 20190205 # Fixed sign to match old JCD, MarieK 6Feb2019
        '''


        # New intrix EMC
        ISC_library.asc_intrix['PIT'].put('DSOFT_B', [], 0)
        ISC_library.asc_intrix['PIT']['DSOFT_A', 'TRX_A']  = -0.097
        ISC_library.asc_intrix['PIT']['DSOFT_A', 'TRX_B']  = 0.10316
        ISC_library.asc_intrix['PIT']['DSOFT_A', 'TRY_A']  = 0.03738
        ISC_library.asc_intrix['PIT']['DSOFT_A', 'TRY_B']  = -0.0645 # new intrix to reduce hard/soft cross coupling


        # Ensure CHARD Y A and CHARD P A are on
        ezca['ASC-CHARD_Y_A_GAIN'] = 1.0
        ezca['ASC-CHARD_P_A_GAIN'] = 1.0

        # Set up SOFT blending
        ezca.get_LIGOFilter('ASC-CSOFT_P_A').only_on('INPUT', 'OUTPUT', 'DECIMATION')
        ezca.get_LIGOFilter('ASC-CSOFT_P_B').only_on('INPUT', 'OUTPUT', 'DECIMATION') #20190104 removing FM5, so we don't have to blend CSOFT
        ezca['ASC-CSOFT_P_A_GAIN']=1. #20190205 switching to CSOFT_P_A with new intrix
        ezca['ASC-CSOFT_P_B_GAIN']=0.

        ezca.get_LIGOFilter('ASC-DSOFT_P_A').only_on('INPUT', 'OUTPUT', 'DECIMATION')
        ezca.get_LIGOFilter('ASC-DSOFT_P_B').only_on('INPUT', 'OUTPUT', 'DECIMATION') #20190104 removing FM5, so we don't have to blend DSOFT
        ezca['ASC-DSOFT_P_A_GAIN']= 1. #20190205 switching to CSOFT_P_A with new intrix
        ezca['ASC-DSOFT_P_B_GAIN']= 0.

        #ezca['ASC-CSOFT_Y_B_GAIN'] = 0
        #ezca.get_LIGOFilter('ASC-CSOFT_Y_B').only_on('INPUT', 'FM10', 'OUTPUT', 'DECIMATION')

        # settings for new SOFT engagement strategy using error signal limiters
        #ezca.get_LIGOFilter('ASC-DSOFT_P').only_on('FM1','FM5', 'FM9','FM10','OUTPUT','DECIMATION')
        ezca.get_LIGOFilter('ASC-DSOFT_P').only_on('FM8', 'FM10', 'OUTPUT', 'DECIMATION')  #Using FM10, combined low Q ELP instead of 2 ELPs, EMC 20230131
        ezca.get_LIGOFilter('ASC-DSOFT_Y').only_on('FM1','FM2', 'FM7', 'FM8','OUTPUT','DECIMATION') #EMC changed to FM7 for no pass band ripple LP 20230322
        #ezca.get_LIGOFilter('ASC-CSOFT_P').only_on('FM1','FM5', 'FM9','FM10','OUTPUT','DECIMATION') # High BW did not engage soft loops properly for now back to low BW. Also: dropped gain of FM6 (ctrl2) ny 20dB (now ctrl2g)
        # HBW for CS_P
        ezca.get_LIGOFilter('ASC-CSOFT_P').only_on('FM1', 'FM6', 'FM9', 'OUTPUT', 'DECIMATION') #EMC changed to FM9 for new low Q low passband ripple ELP 20230216
        ezca.get_LIGOFilter('ASC-CSOFT_Y').only_on('FM1', 'FM7', 'FM10', 'OUTPUT','DECIMATION') #EMC changed to FM7 for no pass band ripple LP 20230322
        for dof in ['CSOFT', 'DSOFT']:
            for PY in ['P', 'Y']:
                #this may be unnecessary since we aren't blending the QPDs anymore
                ezca.get_LIGOFilter('ASC-%s_%s_A'%(dof, PY)).switch_on('FM10')
        #turn on AC coupling for Soft P loops
        ezca.get_LIGOFilter('ASC-CSOFT_P_B').switch_on('FM10')
        ezca.get_LIGOFilter('ASC-DSOFT_P_B').switch_on('FM10')

        for py in ['P','Y']:
            for cd in ['C','D']:
                ezca['ASC-%sSOFT_%s_A_LIMIT'%(cd,py)] = 0.02 # These limits do not get turned on, but the values are used for convergence calculations in ENGAGE_SOFT_LOOPS
                ezca['ASC-%sSOFT_%s_SMOOTH_LIMIT'%(cd,py)]  = 0.05
                ezca['ASC-%sSOFT_%s_SMOOTH_ENABLE'%(cd,py)] = 1

        # Other smooth limiters for REFL_POP_WFS
        for py in ['P','Y']:
            for dof in ['PRC2','INP1','CHARD']:   #   ,'SRC1','SRC2']:  # Don't limit SRC loops - they need to move to keep up
                ezca['ASC-%s_%s_SMOOTH_ENABLE'%(dof,py)] = 1

            # Tight limits so everything can come on at once
            ezca['ASC-PRC2_%s_SMOOTH_LIMIT'%(py)]  = 100
            ezca['ASC-INP1_%s_SMOOTH_LIMIT'%(py)]  = 500
            ezca['ASC-CHARD_%s_SMOOTH_LIMIT'%(py)]  = 3000
            #ezca['ASC-SRC1_%s_SMOOTH_LIMIT'%(py)]  = 1
            #ezca['ASC-SRC2_%s_SMOOTH_LIMIT'%(py)]  = 0.05

        # gains of all ASC except DHARD and MICH are zero still, at this point
        ezca.get_LIGOFilter('ASC-CHARD_P').only_on('FM5', 'FM4','FM10', 'OUTPUT', 'DECIMATION')
        ezca.get_LIGOFilter('ASC-CHARD_Y').only_on('FM2', 'OUTPUT', 'DECIMATION') # EMC removed FM10 20230815, FM2 is new control, FM5 old

        ezca.get_LIGOFilter('ASC-PRC2_P').only_on('FM1', 'FM3', 'FM4', 'FM5','OUTPUT', 'DECIMATION') # EMC changed FM7 to FM8, modified plant inversion to prevent ringing 20220816
        ezca.get_LIGOFilter('ASC-PRC2_Y').only_on('FM1', 'FM3', 'FM4', 'FM5', 'OUTPUT', 'DECIMATION')

        ezca.get_LIGOFilter('ASC-SRC1_P').only_on('FM1', 'FM3', 'FM4', 'FM5', 'FM10','OUTPUT', 'DECIMATION')
        ezca.get_LIGOFilter('ASC-SRC1_Y').only_on('FM1', 'FM3', 'FM4', 'FM5', 'FM10','OUTPUT', 'DECIMATION')
        ezca['ASC-SRC1_P_OFFSET'] = lscparams.asc_offset['SRC_AS72_P']
        ezca['ASC-SRC1_Y_OFFSET'] = lscparams.asc_offset['SRC_AS72_Y']

        ezca.get_LIGOFilter('ASC-SRC2_P').only_on('FM1', 'FM3', 'FM4', 'FM6','OUTPUT', 'DECIMATION')
        ezca.get_LIGOFilter('ASC-SRC2_Y').only_on('FM1', 'FM3', 'FM4', 'FM6','OUTPUT', 'DECIMATION')

        #ezca.get_LIGOFilter('ASC-INP1_P').only_on('INPUT', 'OUTPUT', 'DECIMATION', 'FM5', 'FM9')
        #ezca.get_LIGOFilter('ASC-INP1_Y').only_on('INPUT', 'OUTPUT', 'DECIMATION', 'FM4', 'FM5', 'FM10')
        ezca.get_LIGOFilter('ASC-INP1_P').only_on('INPUT', 'OUTPUT', 'DECIMATION', 'FM4', 'FM5', 'FM7') # Gabriele, Elenna 2023-04-27
        ezca.get_LIGOFilter('ASC-INP1_Y').only_on('INPUT', 'OUTPUT', 'DECIMATION', 'FM4', 'FM5', 'FM8')
        
        # switch output matrix for PRC2 to use PR3 (PR2 used for DRMI-only)
        #ezca.get_LIGOFilter('SUS-PR2_M3_ISCINF_P').switch_on('HOLD')
        #ezca.get_LIGOFilter('SUS-PR2_M3_ISCINF_Y').switch_on('HOLD')
        time.sleep(0.1)
        ezca.switch('SUS-PR3_M1_LOCK_P', 'INPUT', 'ON')
        ezca.switch('SUS-PR3_M1_LOCK_Y', 'INPUT', 'ON')

        # ------------------------------------------------------------------------------------------------
        # setup spot position dither
        # instead of locking to a point set by old soft loops,
        # directly engage dither and check the dither convergence
        #set A2L gains to be at the center of the optics... SED August 13 2019
        for py in ['P2L','Y2L']:
            for optic in ['ITMX','ITMY','ETMX', 'ETMY']:
                ezca['SUS-{}_L2_DRIVEALIGN_{}_SPOT_GAIN'.format(optic,py)] = lscparams.a2l_gains['CENTER'][py][optic]  
        ezca['ASC-ADS_GAIN'] = 1 # Make sure this is on, in case it was turned off

        for dof in ['PIT3', 'PIT4', 'PIT5', 'YAW3', 'YAW4', 'YAW5']:
            fdof = 'ASC-ADS_{}'.format(dof)
            ezca[fdof+'_DOF_GAIN'] = 0
            ezca.get_LIGOFilter(fdof+'_DOF').only_on('FM9', 'FM10', 'OUTPUT', 'DECIMATION')
            ezca[fdof+'_DEMOD_I_GAIN'] = 1
            ezca[fdof+'_OSC_CLKGAIN'] = 1600
            ezca.get_LIGOFilter(fdof+'_DEMOD_SIG').only_on('INPUT', 'FM6', 'OUTPUT', 'DECIMATION')

        ezca['ASC-ADS_PIT3_OSC_FREQ'] = 19.653
        ezca.get_LIGOFilter('ASC-ADS_PIT3_DOF').switch_on('FM1')#we need lower gain for DOF3 while the other ASC loops are still at low gain in engage ASC

        ezca['ASC-ADS_PIT4_OSC_FREQ'] = 20.131
        ezca.get_LIGOFilter('ASC-ADS_PIT4_DOF').switch_on('FM8') #20190617 removed fm7, other asc couldnt keep up

        ezca['ASC-ADS_PIT5_OSC_FREQ'] = 20.789
        ezca.get_LIGOFilter('ASC-ADS_PIT5_DOF').switch_on('FM8') #20190617 removed fm7, other asc couldnt keep up

        ezca['ASC-ADS_YAW3_OSC_FREQ'] = 18.37
        ezca.get_LIGOFilter('ASC-ADS_YAW3_DOF').switch_on('FM1')

        ezca['ASC-ADS_YAW4_OSC_FREQ'] = 22.147
        ezca.get_LIGOFilter('ASC-ADS_YAW4_DOF').switch_on('FM8') #20190617 removed fm7, other asc couldnt keep up

        ezca['ASC-ADS_YAW5_OSC_FREQ'] = 21.9
        ezca.get_LIGOFilter('ASC-ADS_YAW5_DOF').switch_on('FM8') #20190617 removed fm7, other asc couldnt keep up

        for py in ['PIT','YAW']:
            for dof in ['3','4','5']:
                ezca['ASC-ADS_%s%s_SMOOTH_ENABLE'%(py,dof)] = 1
                ezca['ASC-ADS_%s%s_SMOOTH_LIMIT'%(py,dof)] = 10 #  JCD 13Nov2019

        self.timer['pause'] = 5
        self.ASC_saturating = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        #if AS_C is saturating because of high violins, we should not engage ASC
        if self.timer['pause']:
            self.ASC_saturating = False
            for seg in [8,9,10,11]:
                if ezca['FEC-19_ADC_OVERFLOW_5_%s'%seg] > 0:
                    self.ASC_saturating = True
                    self.timer['pause'] = 20
        if self.ASC_saturating:
            notify('AS_C saturating, not engaging ASC')
            
        '''prg_thresh = 41
        if ISC_library.prg_ok(prg_thresh):
            return True
        else:
            notify('PRG lower than %s'%prg_thresh)'''
        if self.timer['pause'] and not self.ASC_saturating:
            return True


class ENGAGE_ASC_FOR_FULL_IFO(GuardState):
    index = 430
    request=False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # Turns on DRMI ASC and CHARD (so, everything but SOFT), waits for converge, then engages SOFT


        # Tighter limits for all-at-once engagement
        for py in ['P','Y']:
            #removed INP1 from this list since that is a slower loop and it tends to get pulled away by CHARD SED Oct31 2019
            for dof in ['PRC2','CHARD', 'SRC2']:
                ezca['ASC-%s_%s_SMOOTH_ENABLE'%(dof,py)] = 1

            # Tight limits so everything can come on at once
            ezca['ASC-PRC2_%s_SMOOTH_LIMIT'%(py)]  = 100
            ezca['ASC-INP1_%s_SMOOTH_LIMIT'%(py)]  = 600
            ezca['ASC-CHARD_%s_SMOOTH_LIMIT'%(py)]  = 1000 #2000
            ezca['ASC-SRC1_%s_SMOOTH_LIMIT'%(py)]  = 1
            ezca['ASC-SRC2_%s_SMOOTH_LIMIT'%(py)]  = 0.05

        ezca['ASC-DHARD_P_GAIN'] = lscparams.asc_gains['DHARD']['P']['ENGAGE_ASC_FOR_FULL_IFO'] # set to 6Hz UGF at 2W on 20180924, was -40
        ezca['ASC-DHARD_Y_GAIN'] = lscparams.asc_gains['DHARD']['Y']['ENGAGE_ASC_FOR_FULL_IFO'] # Reduced to -60 from -75 to prevent extra UGF crossing at 17.8Hz.

        ezca.get_LIGOFilter('ASC-INP1_P').ramp_gain(-1, ramp_time=5, wait=False) #Changed from -1 220705 glm
        ezca.switch('ASC-PRC2_P', 'INPUT',  'ON')
        ezca.get_LIGOFilter('ASC-PRC2_P').ramp_gain(250, ramp_time=5, wait=False) # was 500; need to reduce to 250 anyway for LP filters
        ezca.switch('ASC-PRC2_P','FM2', 'ON') # Engage integrator
        ezca.switch('ASC-CHARD_P', 'INPUT', 'ON')
        ezca.get_LIGOFilter('ASC-CHARD_P').ramp_gain(lscparams.asc_gains['CHARD']['P']['ENGAGE_ASC_FOR_FULL_IFO'], ramp_time=15, wait=False)   # Longer ramp
        
        ezca.get_LIGOFilter('ASC-INP1_Y').ramp_gain(-1, ramp_time=5, wait=False) #Changed from -1 220705 glm
        # EMC removed 50 ct offset 20230301 after REFL switch
        #ezca['ASC-PRC2_P_OFFSET'] = 50
        ezca.switch('ASC-PRC2_Y', 'INPUT', 'ON')
        ezca.get_LIGOFilter('ASC-PRC2_Y').ramp_gain(250, ramp_time=5, wait=False) # was 500
        ezca.switch('ASC-PRC2_Y','FM2','ON')

        ezca.switch('ASC-CHARD_Y', 'INPUT', 'ON')
        ezca.get_LIGOFilter('ASC-CHARD_Y').ramp_gain(lscparams.asc_gains['CHARD']['Y']['ENGAGE_ASC_FOR_FULL_IFO_increase'],   ramp_time=15, wait=False)

        ezca.switch('ASC-SRC1_P', 'INPUT', 'ON')
        ezca.get_LIGOFilter('ASC-SRC1_P').ramp_gain(4, ramp_time=5, wait=False)
        #ezca.switch('ASC-SRC1_P', 'OFFSET', 'ON')  # Add in SRC1 offsets to help with DARM_OFFSET locklosses 9 June 2024 JCD. Vals from alog 78332
        ezca.switch('ASC-SRC2_P', 'INPUT', 'ON')
        ezca.get_LIGOFilter('ASC-SRC2_P').ramp_gain(60, ramp_time=5, wait=False)
        ezca.get_LIGOFilter('ASC-SRC2_P').switch_on('FM2')
        ezca.switch('ASC-SRC2_Y', 'INPUT', 'ON')
        ezca.get_LIGOFilter('ASC-SRC2_Y').ramp_gain(25, ramp_time=5, wait=False) # Lower to 25 from 100 22Dec2022 JCD
        ezca.get_LIGOFilter('ASC-SRC2_Y').switch_on('FM2')#SED, added to help keep these error signals controled while ADS comes on August 19th 2019
        ezca.switch('ASC-SRC1_Y', 'INPUT', 'ON')
        ezca.get_LIGOFilter('ASC-SRC1_Y').ramp_gain(4, ramp_time=5, wait=False)
        #ezca.switch('ASC-SRC1_Y', 'OFFSET', 'ON')  # Add in SRC1 offsets to help with DARM_OFFSET locklosses 9 June 2024 JCD. Vals from alog 78332
        
        for dof in ['PIT3', 'YAW3']:
            ezca.get_LIGOFilter('ASC-ADS_%s_DOF'%dof).switch_on('INPUT')

        # turn on PRM dither first if the others are far away, it will bring them closer
        ezca['ASC-ADS_PIT3_DOF_GAIN'] = 1
        ezca['ASC-ADS_YAW3_DOF_GAIN'] = 1

        self.counter = 1
        self.convergenceCounter = 0
        self.timer['wait'] = 20 # max tramp

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        wfsOffloadList  = ['CHARD', 'DHARD', 'SRC1', 'SRC2', 'INP1', 'PRC2']
        wfsTolerancePit = [ 5000,    2000,    1000,   500,    3,      1000] #PRC2 was 600, SRC1 was 2000 lowered 20190120 glm
        wfsToleranceYaw = [ 5000,    2000,    500,   500,    3,      1000]
        ditherError = ['PIT3', 'YAW3'] #test: not turning on other dithers til 3 is has pushed them close to convergence
        ditherTolerance = [2, 2] #JCD Jan2020

        if self.timer['wait'] and self.counter == 1:
            # wait for convergence, not just a single zero-crossing
            if ISC_library.asc_convergence_checker(wfsOffloadList, wfsTolerancePit, wfsToleranceYaw) and ISC_library.dither_convergence_checker(ditherError, ditherTolerance):
                self.convergenceCounter += 1
                self.timer['wait'] = 10
                log('Looks like we are close to zero - recheck in 10 secs to be sure')
            else:
                notify('waiting for ASC to offload before increasing gain')
                self.timer['wait'] = 1 # So that the log doesn't fill up with a zillion lines about covergence
            if self.convergenceCounter > 2:

                for loop in ['INP1','CHARD', 'PRC1','PRC2','SRC1','SRC2']:  #SED moved these soft limiters turning off to here, now we have let the loops come on and converge, there is not reason to choke them anymore.
                    for dof in ['P', 'Y']:
                        ezca['ASC-%s_%s_SMOOTH_ENABLE'%(loop, dof)] = 0
                self.counter += 1
                self.convergenceCounter = 0 # reset for next round

        if self.timer['wait'] and self.counter == 2:
            ezca.switch('ASC-PRC2_P','FM1','OFF')  # Increase PRC2 gain
            ezca.switch('ASC-PRC2_Y','FM1','OFF')  # Increase PRC2 gain

            ### CRC commented out to see if the guardian would 'just work' without SRC ASC Mar 10 2022
            # ezca.get_LIGOFilter('ASC-SRC1_Y').switch_off('FM1') #increase SRC1Y gain.  Would probably also be good to increase gain of SRC1P and SRC2 P+Y (20 dB more gain for SRC1P is too much)
            # ezca.get_LIGOFilter('ASC-SRC1_P').ramp_gain(12,  ramp_time=5, wait=False) # gain incr of a few, rather than 10x

            #turn off the offset in AS36Q which is set when we swtich DRMI BS ASC from 45 to 36.  We used to turn this off in PREP_ASC, but we lost lock in PREP_ASC a couple of times s POP18 dropped, and it looked like it was due to turning off this offset  SED August 23 2019
            ezca['ASC-AS_A_RF36_Q_YAW_TRAMP' ] = 10
            ezca['ASC-AS_A_RF36_Q_PIT_TRAMP' ] = 10
            ezca.get_LIGOFilter('ASC-AS_A_RF36_Q_PIT').switch_off('OFFSET')
            ezca.get_LIGOFilter('ASC-AS_A_RF36_Q_YAW').switch_off('OFFSET')
            ezca['ASC-AS_A_RF36_Q_YAW_OFFSET' ] = 0
            ezca['ASC-AS_A_RF36_Q_PIT_OFFSET' ] = 0

            self.counter += 1
            self.timer['wait'] = ezca['ASC-AS_A_RF36_Q_YAW_TRAMP' ] # wait for the BS offsets to finish ramping off

        # SED thinks that the reason we are waiting in both of these convergence checkers is that we are ramping gains after integrators, instead of waiting why don't we just move the gain ahead of the integrator?
        if self.timer['wait'] and self.counter == 3:
            # wait for convergence, not just a single zero-crossing
            if ISC_library.asc_convergence_checker(wfsOffloadList, wfsTolerancePit, wfsToleranceYaw) and ISC_library.dither_convergence_checker(ditherError, ditherTolerance):
                self.convergenceCounter += 1
                self.timer['wait'] = 1
                log('Looks like we are close to zero - recheck in 1 sec to be sure')
            else:
                notify('waiting for ASC to offload before increasing CHARD gain')
                self.timer['wait'] = 1 # So that the log doesn't fill up with a zillion lines about covergence
            if self.convergenceCounter > 2:
                self.counter += 1
                self.convergenceCounter = 0 # reset for next round

        if self.counter == 4 and self.timer['wait']:
            ezca.switch('ASC-CHARD_P', 'FM1', 'ON')  # was ON 20180921
            ezca.get_LIGOFilter('ASC-CHARD_Y').ramp_gain(lscparams.asc_gains['CHARD']['Y']['ENGAGE_ASC_FOR_FULL_IFO_final'], ramp_time=10, wait=False)

            self.timer['wait'] = ezca['ASC-PRC2_P_TRAMP']
            
            # Add in SRC1 offsets to help with DARM_OFFSET locklosses 9 June 2024 JCD. Vals from alog 78332
            #ezca.switch('ASC-SRC1_P','OFFSET','ON')
            #ezca.switch('ASC-SRC1_Y','OFFSET','ON')
            
            self.counter +=1
        if self.counter == 5 and self.timer['wait']:
            return True

class TMS_SERVO(GuardState):
    index = 433
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        #check if POP PZT is railed
        if not ((abs(ezca['ASC-POP_X_PZT_PIT_OUTMON'])==ezca['ASC-POP_X_PZT_PIT_LIMIT'])
            or  (abs(ezca['ASC-POP_X_PZT_YAW_OUTMON'])==ezca['ASC-POP_X_PZT_YAW_LIMIT'])):
            self.POP_PZT_railed =False
        else:
            self.POP_PZT_railed = True

       # now our TMS servo is a separate guardian
        nodes['TMS_SERVO'] = 'TMS_SERVO_ON'


    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        #check that we haven't railed the POP PZT.  This will give a warning if it has railed.  We have seen the POP PZT be railed as the soft loops come on, because the alignment of PR3 was wrong.  We recovered from this situation by turning the soft loop inputs off, and walking PR3 back to where it used to be according to the top mass sliders.  If the loops have already been engaged when the PZT rails, this code will only notify you of the problem, and prevent you from moving on, it will not attempt to fix the problem.
        if not ((abs(ezca['ASC-POP_X_PZT_PIT_OUTMON'])==ezca['ASC-POP_X_PZT_PIT_LIMIT'])
            or  (abs(ezca['ASC-POP_X_PZT_YAW_OUTMON'])==ezca['ASC-POP_X_PZT_YAW_LIMIT'])):
            self.POP_PZT_railed =False
        else:
            self.POP_PZT_railed = True
        if self.POP_PZT_railed:
            notify('POP X PZT railed!!')
            if (abs(ezca['ASC-POP_X_DC_PIT_OUT16']) > 0.5) or (abs(ezca['ASC-POP_X_DC_YAW_OUT16']) > 0.5):
                log('POP X PZT really railed, stop and fix it')
                return False

        return True


class ENGAGE_SOFT_LOOPS(GuardState):
    index = 435
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.counter = 0
        self.convergenceCounter = 0
        self.convergenceCheck = 0
        self.timer['wait'] = 20 # max tramp


    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        #check that we haven't railed the POP PZT.  This will give a warning if it has railed.  We have seen the POP PZT be railed as the soft loops come on, because the alignment of PR3 was wrong.  We recovered from this situation by turning the soft loop inputs off, and walking PR3 back to where it used to be according to the top mass sliders.  If the loops have already been engaged when the PZT rails, this code will only notify you of the problem, and prevent you from moving on, it will not attempt to fix the problem.
        if not ((abs(ezca['ASC-POP_X_PZT_PIT_OUTMON'])==ezca['ASC-POP_X_PZT_PIT_LIMIT'])
            or  (abs(ezca['ASC-POP_X_PZT_YAW_OUTMON'])==ezca['ASC-POP_X_PZT_YAW_LIMIT'])):
            self.POP_PZT_railed =False
        else:
            self.POP_PZT_railed = True
        if self.POP_PZT_railed:
            notify('POP X PZT railed!!')
            if (abs(ezca['ASC-POP_X_DC_PIT_OUT16']) > 0.5) or (abs(ezca['ASC-POP_X_DC_YAW_OUT16']) > 0.5):
                log('POP X PZT really railed, stop and fix it')
                return False

        # Execute our TMS QPD servos
       # for xy in self.xys:
       #     for pityaw in self.pityaws:
        #        self.servos[xy][pityaw].step()

        doAllSOFTLoops = False # If true, need to set CHARD_Y gain in LownoiseASC back to 0.6. JCD 11Jan2019
        #Do a convergence check when requested, do it twce in a row
        wfsOffloadList  = ['CHARD', 'DHARD', 'SRC1', 'SRC2', 'INP1', 'PRC2']
        wfsTolerancePit = [ 5000,    2000,    2000,   500,    3,      1000]
        wfsToleranceYaw = [ 5000,    2000,    2000,   500,    3,      1000]
        dithOffloadList=['PIT3', 'PIT4', 'PIT5', 'YAW3', 'YAW4', 'YAW5']
        dithTolerance  =[ 6,      6,      6,      6,      6,      6] #loosening convergence, moving stricter checkers to INCREASE_POWER
        if self.convergenceCheck == 1:
            # wait for convergence, not just a single zero-crossing
            if ISC_library.asc_convergence_checker(wfsOffloadList, wfsTolerancePit, wfsToleranceYaw) \
                and ISC_library.dither_convergence_checker(dithOffloadList, dithTolerance):
                self.convergenceCounter += 1
                self.timer['wait'] = 1
                log('Looks like ASC control signals are close to zero - recheck in 1 sec to be sure')
            else:
                notify('waiting for ASC and ADS to converge')
                self.timer['wait'] = 1 # So that the log doesn't fill up with a zillion lines about covergence
            if self.convergenceCounter > 2:
                self.counter += 1
                self.convergenceCounter = 0 # reset for next round
                self.convergenceCheck = 0

        # COUNTER 0: Engage CSOFT, DSOFT
        if self.timer['wait'] and self.counter == 0:
            ezca.switch('ASC-CSOFT_P','INPUT','ON')
            ezca.get_LIGOFilter('ASC-CSOFT_P').ramp_gain(lscparams.asc_gains['CSOFT']['P']['ENGAGE_SOFT_LOOPS'], ramp_time=5, wait=False)
            ezca.switch('ASC-DSOFT_P','INPUT','ON') #glm 20190118 putting dsoft_p back in
            ezca.get_LIGOFilter('ASC-DSOFT_P').ramp_gain(lscparams.asc_gains['DSOFT']['P']['ENGAGE_SOFT_LOOPS'], ramp_time=5, wait=False) #20190120 increasing gain from 15
            #turning up gain on DOF3 can help bring DOF4+5 error signals close to 0
            for ddof in ['PIT3','YAW3']:
                ezca.get_LIGOFilter('ASC-ADS_%s_DOF'%ddof).switch_on('FM7')
            self.counter += 1
            self.timer['wait'] = 10

        # COUNTER 1: Turn on ADS 4 & 5
        if self.timer['wait'] and self.counter == 1:
            ditherError = ['PIT4', 'PIT5', 'YAW4', 'YAW5'] #test: not turning on other dithers til 3 is has pushed them close to convergence
            looseTolerance = [80, 80, 80, 80] #glm reduced from 100 after a lockloss 20190617
            if ISC_library.dither_convergence_checker(ditherError, looseTolerance):
                for dof in ['PIT4', 'PIT5', 'YAW4', 'YAW5']:
                    ezca.get_LIGOFilter('ASC-ADS_%s_DOF'%dof).switch_on('INPUT')
                log('ADS loops are close, turning on PIT4 PIT5 YAW4 YAW5')
                ezca['ASC-ADS_PIT4_DOF_GAIN'] = 1
                ezca['ASC-ADS_PIT5_DOF_GAIN'] = 1
                ezca['ASC-ADS_YAW4_DOF_GAIN'] = 1
                ezca['ASC-ADS_YAW5_DOF_GAIN'] = 1
                self.counter += 1
            else:
                notify('Not turning other dithers on until they dof3 brings dof4+5 errors closer to 0')
                self.timer['wait'] = 1

        # COUNTER 2: Check for ASC and ADS convergence
        if self.timer['wait'] and self.counter == 2:
            self.convergenceCheck = 1

        # COUNTER 3: Do some ASC gain changes that used to happen in MOVE_SPOTS, since we are moving the spot moves to 10W SED Jan 2 2020
        if self.timer['wait'] and self.counter == 3:
            #ezca['ASC-DHARD_P_GAIN'] = lscparams.asc_gains['DHARD']['P']['ENGAGE_SOFT_LOOPS']  # EMC took out 20230215, move UGF down and reduce 9.7 Hz gain peaking#this wasn't a change since gain is already -20
            ezca.switch('ASC-CSOFT_P', 'FM1', 'OFF')
            for ddof in ['PIT4','PIT5','YAW4','YAW5']:  #these are already on
                ezca.get_LIGOFilter('ASC-ADS_%s_DOF'%ddof).switch_on('FM7') #turn those 10dBs back on once we're converged

            # DB HY moved SRC gain reduction here.
            # ezca.get_LIGOFilter('ASC-SRC1_Y').switch_on('FM1')
            # SED temporarily commetned these out August 9 2020
            ### CRC temporarily commented these out Mar 10 2022
            # ezca.get_LIGOFilter('ASC-SRC2_P').ramp_gain(15, ramp_time=4, wait=False)
            # ezca.get_LIGOFilter('ASC-SRC2_Y').ramp_gain(15, ramp_time=4, wait=False)
            self.counter += 1
            self.timer['wait'] = 10 # max tramp

        # COUNTER 4: Check for ASC and ADS convergence
        if self.timer['wait'] and self.counter == 4:
            self.convergenceCheck = 1

        # COUNTER 5: ISI FF and more ASC changes
        if self.timer['wait'] and self.counter == 5:
            # Turn on ISI FF
            #temporarily turning these off March 18 2021, we don't know if this needs to be remeasured with new QUAD.
            #for tm in ['ETMX', 'ETMY', 'ITMX', 'ITMY']:
            #    ezca['SUS-%s_M0_ISIFF_L2P_TRAMP'%tm]=30
            #    ezca['SUS-%s_M0_ISIFF_L2P_GAIN'%tm]=-1
            #SED temporarily commented these out August 9 2020
            #ezca.switch('ASC-SRC2_P', 'FM7', 'ON')
            #ezca.switch('ASC-SRC2_Y', 'FM7', 'ON')
            # EMC and GV updated PRC2 loops, FM5 contains low pass
            #ezca.switch('ASC-PRC2_P', 'FM10', 'ON')
            #ezca.switch('ASC-PRC2_Y', 'FM10', 'ON')
            #waiting until after ADS converge to turn on SRC1Y July 30th 2020  taking this out since SRC1 Y is causing locklosses Aug 1 2020
            #ezca.switch('ASC-SRC1_Y', 'INPUT', 'ON')
            #ezca.get_LIGOFilter('ASC-SRC1_Y').ramp_gain(4, ramp_time=5, wait=False)
            #ezca.switch('ASC-INP1_P', 'FM4', 'ON')
            self.timer['wait']=5
            self.counter += 1

        #we're done here
        if self.counter >=6:
            return True


##################################################
# STATES: Transition to DC Readout
##################################################

class PREP_DC_READOUT_TRANSITION(GuardState):
    """
    Command the OMC guardian to lock the OMC and tune the appropriate offsets to
    enable transition RF to DC readout.

    """
    index = 500
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):

        #hold the offset of TRX
        ezca.get_LIGOFilter('LSC-TR_X_NORM').turn_on('HOLD')
        self.avg_TRX = call_with_timeout(cdu.avg, 2, 'LSC-TR_X_NORM_OUTPUT')
        self.inputMatrix = math.sqrt(self.avg_TRX)*ezca['LSC-POW_NORM_MTRX_1_17']

        ISC_library.intrix_OMCAS45['DARM', 'OMCDC'] = self.inputMatrix
        ISC_library.intrix_OMCAS45['DARM', 'ASAIR_A45Q']= 0

        time.sleep(0.2)
        # turn on the resG which supreses the 1.1 Hz noise from HAM3 bad GS13 in PRCL loop.
        ezca.get_LIGOFilter('LSC-PRCL1').switch_on('FM5')

        self.counter = 1 # initialize
        self.timer['wait'] = 0 # initialize

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    #SED removed unstall nodes here so that if we manually request the OMC to remove whitening this gaurdian won't take over and turn the whitening back on.
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        #log(self.counter)
        if self.counter ==1:
            #this is repeated from DARM_OFFSET
            ezca.get_LIGOFilter('LSC-DARM1').turn_on('FM1')
            self.timer['wait'] = 1
            self.counter += 1
        elif self.counter == 2 and self.timer['wait']:
            #ezca.get_LIGOFilter('LSC-DARM1').turn_on('FM2')
            self.timer['wait'] = 1
            self.counter += 1
        elif self.counter == 3 and self.timer['wait']:
            ezca.get_LIGOFilter('LSC-DARM1').turn_on('FM10')
            self.timer['wait'] = 3
            self.counter += 1
        elif ((nodes['OMC_LOCK'] == 'READY_W_NO_WHITENING' and nodes['OMC_LOCK'].done) or lscparams.power_up_on_RF) \
                and self.counter >= 4:
            if ezca['FEC-179_ADC_OVERFLOW_0_12'] > 0 or ezca['FEC-179_ADC_OVERFLOW_0_13'] > 0:
                # If either DCPD is saturating, stop and wait for help.
                # Changed from OMC-DCPD_B_WINDOW_EXCEED values to the above  --TJS Jan 25 2023
                # Note that the 'EXCEED'  values are smaller than acutally saturating (currently 20k counts, not full 32k counts), so might want 
                #     to check H1:OMC-DCPD_A_WINDOW_MAX instead, and compare that to 30k counts.  JCD 26Aug2022
                notify('DCPDs saturating. Check violins')
            else:
                return True
        elif nodes['OMC_LOCK'] == 'REMOVE_WHITENING':
            notify('waiting for DCPD whitening to switch')
        else:
            log('not ready')
            notify('OMC not ready')
            # Add code for OMC to try again to lock, if it didn't get it the first time. JCD 11Oct2018
            ''' Removing this block for now so OMC doesn't get stuck; will think of better solution - RWS 12Jun2024
            if self.counter == 4 and self.timer['wait'] and (ezca['GRD-OMC_LOCK_STATE_N'] < 199):
                # Adding check on OMC guardian state num, so we don't go to DOWN if we've just found the carrier and we're finishing up.
                nodes['OMC_LOCK'] = 'DOWN'
                self.counter += 1
                self.timer['wait'] = 0.5
            elif self.counter == 5 and self.timer['wait'] and nodes['OMC_LOCK'] == 'DOWN' and nodes['OMC_LOCK'].done:
                nodes['OMC_LOCK'] = 'READY_W_NO_WHITENING'
                self.counter += 1
                self.timer['wait'] = 600 # shouldn't need to request again, but just in case, try again if failed. Should only take ~120s if successful
            elif self.counter == 6 and self.timer['wait']:
                self.counter = 4 # allow state to retry requesting DOWN
            '''




class DARM_TO_DC_READOUT(GuardState):
    """
    Push the load button on the LSC input matrix, thereby transferring control of
    DARM from ASAIR45Q to OMC DC.

    """
    index = 501
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        if lscparams.power_up_on_RF:
            notify('not transitioning to DC readout for power up on RF')
        else:
            ISC_library.intrix_OMCAS45.load()
        self.timer['wait'] = 10 # Make it wait, after OMC is ready for handoff, just in case it needs a moment to settle
        self.counter = 1 

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):

        if self.counter == 1 and self.timer['wait']:
            #ezca['LSC-DARM1_GAIN'] = 400 # was 800, but that gave DARM UGF of 115Hz, 20deg.  Lowering to 400, JCD, SED, GV 5Oct2018
            #self.timer['wait'] = 4
            self.counter += 1

        if self.counter == 2 and self.timer['wait']:
            #these have to be ramped at the same time or you will loose lock
            ezca.get_LIGOFilter('LSC-DARM1').ramp_offset(0, ramp_time=3, wait=False)
            ezca.get_LIGOFilter('LSC-OMC_DC').ramp_offset(0, ramp_time=3, wait=False)
            self.timer['wait'] = 5
            self.counter += 1

        if self.counter == 3 and self.timer['wait']:
            ezca['OMC-READOUT_X0_TRAMP'] = 3
            # set PREF and ERR_GAIN to default reference value
            ezca['OMC-READOUT_PREF_TRAMP'] = 5
            ezca['OMC-READOUT_ERR_TRAMP'] = 5
            ezca['OMC-READOUT_PREF_OFFSET']   = lscparams.dc_readout['PREF']
            ezca['OMC-READOUT_ERR_GAIN']      = lscparams.dc_readout['sign'] * lscparams.dc_readout['ERR_GAIN']
            # copy the gain for the DCPD NULL path.
            ezca['OMC-READOUT_ERR_NULL_GAIN'] = lscparams.dc_readout['sign'] * lscparams.dc_readout['ERR_GAIN']
            # set the DARM offset so that the DCPD Sum is 20mA
            OMCtargetP = lscparams.dc_readout['DCPD_SUM_TARG']
            curPow = ezca['IMC-PWR_IN_OUT16']
            newOfs = math.sqrt(OMCtargetP * ezca['OMC-READOUT_PREF_OFFSET']/curPow
                    - ezca['OMC-READOUT_CD_OFFSET']) * ezca['OMC-READOUT_XF_OFFSET']
            ezca['OMC-READOUT_X0_OFFSET'] = round(newOfs,6)
            self.counter += 1

        if self.counter == 4:
            if ezca['PSL-ISS_LOOP_STATE_OUTPUT'] < 32000:
                theMessage = 'ISS first loop is maybe off?'
                notify(theMessage)
                log(theMessage)
            else:
                return True

class DAMP_BOUNCE(GuardState):
    index = 502
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        #send DARM control to ETMY so it can be used for damping
        ezca['LSC-ARM_OUTPUT_MTRX_2_1'] = -1
        ezca['SUS-ETMY_L3_ISCINF_L_GAIN' ] = 1
        ezca['SUS-ETMY_M0_DARM_DAMP_V_GAIN'] = 0 # was 1 26May2022
        
        

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        return True



class CHECK_VIOLINS_BEFORE_POWERUP(GuardState):
    index = 503
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.timer['wait'] = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        # Check if we're going to saturate our DCPDs when we power up
        # this has now been moved to an ISC Library function
        
        # check if violins are rung up
        if self.timer['wait']:
            if ISC_library.check_for_violins_saturation(power='low'):
                return True
            else:
                self.timer['wait'] = 60
                return False
                    


class DAMP_VIOLINS_FULL_POWER(GuardState):
    """
    Engage violin damping while DARM is still on DC readout.

    """
    index = 566
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        nodes['VIOLIN_DAMPING'] = 'DAMP_VIOLINS_FULL_POWER'
        self.timer['wait'] = 1
        return 

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        return True



##################################################
# STATES: Power up
##################################################

class POWER_10W(GuardState):
    index = 504
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.Soft_loops = ['PIT4', 'PIT5', 'YAW4', 'YAW5']
        self.Soft_thresh = [2,      2,      2,      2]# was [10,      5,      2,      2]

        # Update the HWS to save data at a faster rate
        #commenting out because we could not connect to the channel.  Seems like this is some problem with HWS software?
        # while powering up to see point absorbers
        #ezca['TCS-ITMY_HWS_AVERAGE_DURATION'] = 5
        #ezca['TCS-ITMX_HWS_AVERAGE_DURATION'] = 5
        #ezca['TCS-ETMX_HWS_AVERAGE_DURATION'] = 5
        #ezca['TCS-ETMY_HWS_AVERAGE_DURATION'] = 5

        ezca['OMC-READOUT_X0_TRAMP'] = 0.3

        #ezca['SUS-TMSX_M1_TEST_P_TRAMP'] = 30
        #ezca.switch('SUS-TMSX_M1_TEST_P', 'OFFSET', 'ON') #Temporarily point the TMS so the spot is not falling off the qpd
        #self.timer['wait'] = 30 #wait for the tms
        self.timer['wait'] = 0
        
        # nodes['AWG_LINES'] = 'INJECTING'  # Inj lines before power-up, while working on TCS tuning.

        self.first_step = 5
        if lscparams.input_power['POWER_10W'] > self.first_step:
            nodes['LASER_PWR'] = 'POWER_{}W'.format(self.first_step)
            self.current_request = self.first_step
            ezca['VID-CAM16_EXP'] = lscparams.camera_exp['ASAIR'] * 2. / self.current_request
        else:
            nodes['LASER_PWR'] = 'POWER_{}W'.format(lscparams.input_power['POWER_10W'])
            self.current_request = lscparams.input_power['POWER_10W']
            ezca['VID-CAM16_EXP'] = lscparams.camera_exp['ASAIR'] * 2. / self.current_request

        if lscparams.power_up_on_RF:
            notify('powering up on RF')
            #prepare to scale the DARM offset as we power up on RF if needed
            self.d_offset_start = ezca['LSC-DARM1_OFFSET']
            self.pwr_offset_start = ezca['PSL-POWER_SCALE_OFFSET']

        self.dhardBoostPowerLimit = 9
        if lscparams.input_power['POWER_10W'] <= self.dhardBoostPowerLimit:
            self.dhardBoosted = True
        else:
            self.dhardBoosted = False

        # DO NOT put any ezca commands here that might have connection errors that keep guardian from moving the fringe offset X0.

        self.requestedPowerAcquired = False
        self.power_step = 5 # W
        self.timer['wait'] = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        # At 2 W, the nominal CMB gain should be 22 dB
        # >>> but as of 2018-08-31 it is -16 dB (GV)
        #calc_gain = 20*math.log10(2/ezca['IMC-PWR_IN_OUTMON']) + 22

        # change the DARM offset to follow the current DC light level out of the OMC
        if not (nodes['LASER_PWR'].arrived and nodes['LASER_PWR'].done):
            # If you want to power up on RF, set lscparams.power_up_on_RF
            if lscparams.power_up_on_RF:
                notify('powering up on RF')
                ezca['LSC-DARM1_OFFSET'] = self.d_offset_start*self.pwr_offset_start/ezca['PSL-POWER_SCALE_OFFSET']
            else:
                newOfs = (math.sqrt(max(lscparams.dc_readout['DCPD_SUM_TARG'] *
                    ezca['OMC-READOUT_PREF_OFFSET']/ezca['IMC-PWR_IN_OUTMON'] - ezca['OMC-READOUT_CD_OFFSET'], 0.01)) *
                    ezca['OMC-READOUT_XF_OFFSET'])
                ezca['OMC-READOUT_X0_OFFSET'] = round(newOfs,6)

        if ezca['IMC-PWR_IN_OUT16'] >= self.dhardBoostPowerLimit and not self.dhardBoosted:
            # Reshape Hard loops
            ##### if you need to revert the change made on march 11 2020, uncomment the FM7 line below and comment out the FM2 line.  
            #ezca.switch('ASC-DHARD_P', 'FM7', 'ON')
            ezca.switch('ASC-DHARD_P', 'FM2', 'ON') #SED getting rid of resG in FM7 March 11 2020
            #ezca.switch('ASC-DHARD_Y', 'FM4', 'ON') # SED and EMC removing resgain filter FM5, July 6 2022
            #ezca.switch('ASC-CHARD_Y', 'FM4', 'ON') # HY took it out for now for loop stability 12/23/18
            ezca.switch('ASC-DSOFT_P', 'FM9', 'ON') # a filter to (supress) the 0.47 Hz oscillation
            #ezca.get_LIGOFilter('ASC-CHARD_Y').ramp_gain(240, ramp_time=10, wait=False) # increase by 3 dB, 20230215
            self.timer['wait'] = 10
            
            # no longer necessary as this is in engage_asc
            ''' #change chard y control filter to increase gain at 0.5 Hz, EMC 20221207
            ezca.get_LIGOFilter('ASC-CHARD_Y').ramp_gain(0, ramp_time=5, wait=False)
            time.sleep(5) # not sure if this is the right way to do this
            ezca.switch('ASC-CHARD_Y', 'FM5', 'OFF') 
            ezca.switch('ASC-CHARD_Y', 'FM2', 'ON')
            time.sleep(5)
            ezca.get_LIGOFilter('ASC-CHARD_Y').ramp_gain(175, ramp_time=10, wait=False) # was 125, increased by 3 dB due to 2 Hz mystery'''
            
            self.dhardBoosted = True


        # Only increase power if ISS ready (if it's been requested)
        if nodes['IMC_LOCK'].arrived and nodes['IMC_LOCK'].done and nodes['LASER_PWR'].arrived and nodes['LASER_PWR'].done and self.timer['wait']:

            #ISC_library.adjust_radiation_pressure_compensation()

            dither_loops = ['PIT4', 'PIT5', 'YAW4', 'YAW5']
            dither_thresh = [2,      2,      2,      1]#[10,      10,      10,      1]
            #if not ISC_library.asc_convergence_checker(self.Soft_loops, self.Soft_P_thresh, self.Soft_Y_thresh):
            if not ISC_library.dither_convergence_checker(dither_loops, dither_thresh):
                notify('waiting for soft loops to converge')
                self.timer['wait'] = 1 # So that the log doesn't fill up with a zillion lines about covergence
            elif self.current_request < (lscparams.input_power['POWER_10W'] - self.power_step):
                self.current_request = self.current_request + self.power_step
                nodes['LASER_PWR'] = 'POWER_{}W'.format(self.current_request)
                self.timer['wait'] = 0.3 #60 #0.1
                ezca['VID-CAM16_EXP'] = lscparams.camera_exp['ASAIR'] * 2. / self.current_request
            else:
                nodes['LASER_PWR'] = 'POWER_{}W'.format(lscparams.input_power['POWER_10W'])
                ezca['VID-CAM16_EXP'] = lscparams.camera_exp['ASAIR'] * 2. / lscparams.input_power['POWER_10W']
                self.requestedPowerAcquired = True

        if nodes['LASER_PWR'].done and nodes['LASER_PWR'].arrived and self.requestedPowerAcquired and self.dhardBoosted:
            for ii in ['X', 'Y']:
                ezca['LSC-{}_TIDAL_REDTRIG_THRESH_OFF'.format(ii)]=8000
            # Only get here if at final power, ISS done and hard loops boosted
            return True

class MOVE_SPOTS(GuardState):
    index = 508
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        ##temporarily moved here from ENGAGE_ASC_FOR_FULL_IFO August 3rd 2020
        #ezca.switch('ASC-SRC1_Y', 'INPUT', 'ON')
        #ezca.get_LIGOFilter('ASC-SRC1_Y').ramp_gain(4, ramp_time=5, wait=False)
        # Set up TMS QPD servos to center beam on B QPDs by moving TMS
        '''self.xys = ['X', 'Y']
        self.pityaws = ['PIT', 'YAW']
        servo_gain = 0.1 #0.3 CRC lowered 2021 Mar 29 to see if this helps TMSX yaw 0.3 Hz osc # this works for all servos, converge in ~ 1 minute
        self.servos = {}
        for xy in self.xys:
            self.servos[xy] = {}
            for pityaw in self.pityaws:
                py = pityaw[0] # set py = 'P' or 'Y'

                # JCD 6Feb2020: Assume offsets already on from ENGAGE_SOFT_LOOPS. Don't reset them
                # # Set up servos
                # test_FM = ezca.get_LIGOFilter('SUS-TMS{xy}_M1_TEST_{py}'.format(xy=xy, py=py))
                # if test_FM.is_offset_on():
                #     test_FM.TRAMP.put(30) # set TRAMP to 3 in case offset is ON (should be off)
                #     test_FM.OFFSET.put(0) #  offset should be OFF at this point
                #     time.sleep(30)
                # else:
                #     test_FM.OFFSET.put(0)
                # test_FM.TRAMP.put(0) # set TRAMP to 0 because servo should be immediate
                # test_FM.turn_on('OFFSET')

                # Create the servo on the TMS test filter offset controlled by its B QPD
                control_chan = 'SUS-TMS{xy}_M1_TEST_{py}_OFFSET'.format(xy=xy, py=py)
                readback_chan = 'ASC-{xy}_TR_B_{pityaw}_OUTMON'.format(xy=xy, pityaw=pityaw)
                servo = cdu.Servo(ezca, control_chan, readback=readback_chan, gain=servo_gain)

                self.servos[xy][pityaw] = servo # store the servo in a dictionary'''

        self.counter = 0
        self.convergenceCounter = 0
        self.timer['wait'] = 3 # 120

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        # Execute our TMS QPD servos
        #for xy in self.xys:
         #   for pityaw in self.pityaws:
          #      self.servos[xy][pityaw].step()

        if self.counter == 0 and self.timer['wait']:
            #move spots to final location
            for py in ['P2L','Y2L']:
                for optic in ['ITMX','ITMY','ETMX', 'ETMY']:
                    ezca['SUS-{}_L2_DRIVEALIGN_{}_SPOT_TRAMP'.format(optic,py)] = 120
                    time.sleep(1)
                    ezca['SUS-{}_L2_DRIVEALIGN_{}_SPOT_GAIN'.format(optic,py)] = lscparams.a2l_gains['FULL_POWER'][py][optic] 
                self.timer['wait'] = ezca['SUS-ITMX_L2_DRIVEALIGN_P2L_SPOT_TRAMP']
            self.counter +=1

        doAllSOFTLoops = False # If true, need to set CHARD_Y gain in LownoiseASC back to 0.6. JCD 11Jan2019
        wfsOffloadList  = ['CHARD', 'DHARD', 'SRC1', 'SRC2', 'INP1', 'PRC2']
        wfsTolerancePit = [ 5000,    2000,    2000,   500,    3,      1000]
        wfsToleranceYaw = [ 5000,    2000,    2000,   500,    3,      1000]

        dithOffloadList=['PIT3', 'PIT4', 'PIT5', 'YAW3', 'YAW4', 'YAW5']
        dithTolerance  =[ 1.2,      1,2,      1,2,      1,2,      1.2,      1.2] #EMC loosening convergence, all were 0.6, 20240823 #EMC tightening convergence, all used to be 6, 20230310

        # COUNTER 0: Check for ASC and ADS convergence
        if self.timer['wait'] and self.counter == 1:
            # wait for convergence, not just a single zero-crossing
            if ISC_library.asc_convergence_checker(wfsOffloadList, wfsTolerancePit, wfsToleranceYaw) \
                and ISC_library.dither_convergence_checker(dithOffloadList, dithTolerance):
                self.convergenceCounter += 1
                self.timer['wait'] = 1
                log('Looks like ASC control signals are close to zero - recheck in 1 sec to be sure')
            else:
                notify('waiting for ASC and ADS to converge before increasing ADS gains and reducing SRC ASC gains')
                self.timer['wait'] = 1 # So that the log doesn't fill up with a zillion lines about covergence
            if self.convergenceCounter > 2:
                self.counter += 1
                self.convergenceCounter = 0 # reset for next round

        # COUNTER 1: done!
        if self.timer['wait'] and self.counter == 2:
            return True

class POWER_25W(GuardState):
    index = 506
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.Soft_loops = ['PIT4', 'PIT5', 'YAW4', 'YAW5']
        self.Soft_thresh = [2,      2,      2,      2]# was [10,      5,      2,      2]
        ezca['OMC-READOUT_X0_TRAMP'] = 0.3
        self.timer['wait'] = 0
        #since I split increase power into 2 states, the first request is the same as the final request for POWER_10W
        self.current_request = lscparams.input_power['POWER_10W']
        ezca['VID-CAM16_EXP'] = lscparams.camera_exp['ASAIR'] * 2. / self.current_request

        if lscparams.power_up_on_RF:
            notify('powering up on RF')
            #prepare to scale the DARM offset as we power up on RF if needed
            self.d_offset_start = ezca['LSC-DARM1_OFFSET']
            self.pwr_offset_start = ezca['PSL-POWER_SCALE_OFFSET']

        self.dhardBoostPowerLimit = 9
        if lscparams.input_power['POWER_25W'] <= self.dhardBoostPowerLimit:
            self.dhardBoosted = True
        else:
            self.dhardBoosted = False

        # DO NOT put any ezca commands here that might have connection errors that keep guardian from moving the fringe offset X0.

        self.requestedPowerAcquired = False
        self.power_step = 5 # W
        self.timer['wait'] = 0

        # Power level saved in lscparams
        nodes['TCS_ITMX_CO2_PWR'] = 'NOM_ANNULAR_POWER'
        nodes['TCS_ITMY_CO2_PWR'] = 'NOM_ANNULAR_POWER'

        #make sure that if there is a PRC2 P offset it gets turned off before we power up further
        # this offset is no longer engaged, EMC commenting out this line
        #ezca.switch('ASC-PRC2_P', 'OFFSET', 'OFF')
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
                
        # At 2 W, the nominal CMB gain should be 22 dB
        # >>> but as of 2018-08-31 it is -16 dB (GV)
        #calc_gain = 20*math.log10(2/ezca['IMC-PWR_IN_OUTMON']) + 22

        # change the DARM offset to follow the current DC light level out of the OMC
        if not (nodes['LASER_PWR'].arrived and nodes['LASER_PWR'].done):
            # If you want to power up on RF, set lscparams.power_up_on_RF
            if lscparams.power_up_on_RF:
                notify('powering up on RF')
                ezca['LSC-DARM1_OFFSET'] = self.d_offset_start*self.pwr_offset_start/ezca['PSL-POWER_SCALE_OFFSET']
            else:
                newOfs = (math.sqrt(max(lscparams.dc_readout['DCPD_SUM_TARG'] *
                    ezca['OMC-READOUT_PREF_OFFSET']/ezca['IMC-PWR_IN_OUTMON'] - ezca['OMC-READOUT_CD_OFFSET'], 0.01)) *
                    ezca['OMC-READOUT_XF_OFFSET'])
                ezca['OMC-READOUT_X0_OFFSET'] = round(newOfs,6)

        if ezca['IMC-PWR_IN_OUT16'] >= self.dhardBoostPowerLimit and not self.dhardBoosted:
            # Reshape Hard loops
            #ezca.switch('ASC-DHARD_Y', 'FM4', 'ON') # SED and EMC switching away from resgain filter FM5, July 6 2022
            ezca.switch('ASC-CHARD_P', 'FM9', 'ON')  #April 15 2021, this used to be boost10W (series of resGs), we tried at 25W to switch to simpler boost to help our phase, it worked there so we want to try this for acquisition next time
            #ezca.switch('ASC-CHARD_Y', 'FM4', 'ON') # HY took it out for now for loop stability 12/23/18
            ezca.switch('ASC-DSOFT_P', 'FM9', 'ON') # a filter to (supress) the 0.47 Hz oscillation
            ezca.switch('ASC-CSOFT_P', 'FM3', 'ON') # a filter to prevent osc around 0.5 Hz, added by EMC, FM2 is another option, FM3 is a bigger resG

            # Temp remove while trying power up with new inmtrx in CHARDYB 21Apr2021
            # ezca.switch('ASC-CHARD_Y', 'FM8', 'ON') # EMC removed for loop stability 20220727, old: CRC added in Sheila's zpk for the 0.5 Hz CHARD Y osc FM8
            #ezca['ASC-CHARD_Y_GAIN'] = lscparams.asc_gains['CHARD']['Y']['POWER_25W'] # 40

            ### Add code to turn on SOFT loops
            ezca['ASC-CSOFT_Y_TRAMP'] = 10
            ezca['ASC-DSOFT_Y_TRAMP'] = 10

            ezca['ASC-CSOFT_Y_GAIN'] = 20 #was 10, lowered by EMC from 10 20221207 for new chard y #raised to 20 SED Dec 8
            ezca['ASC-DSOFT_Y_GAIN'] = 30 # was 100, lowered by EMC for new intrix

            ezca['ASC-CSOFT_Y_RSET'] = 2
            ezca['ASC-DSOFT_Y_RSET'] = 2

            ezca.switch('ASC-CSOFT_Y', 'INPUT', 'ON')
            ezca.switch('ASC-DSOFT_Y', 'INPUT', 'ON')

            # Increase ADS Pit gains after move_spots. 28Apr2022 JCD
            ezca['ASC-ADS_PIT3_DOF_GAIN'] = 2
            ezca['ASC-ADS_PIT4_DOF_GAIN'] = 2
            ezca['ASC-ADS_PIT5_DOF_GAIN'] = 2

            self.dhardBoosted = True

        # Only increase power if ISS ready (if it's been requested)
        if nodes['IMC_LOCK'].arrived and nodes['IMC_LOCK'].done and nodes['LASER_PWR'].arrived and nodes['LASER_PWR'].done and self.timer['wait']:

            #ISC_library.adjust_radiation_pressure_compensation()

            dither_loops = ['PIT4', 'PIT5', 'YAW4', 'YAW5']
            dither_thresh = [2,      2,      2,      1]#[10,      10,      10,      1]
            #if not ISC_library.asc_convergence_checker(self.Soft_loops, self.Soft_P_thresh, self.Soft_Y_thresh):
            if not ISC_library.dither_convergence_checker(dither_loops, dither_thresh):
                notify('waiting for soft loops to converge')
                self.timer['wait'] = 1 # So that the log doesn't fill up with a zillion lines about covergence
            elif self.current_request < (lscparams.input_power['POWER_25W'] - self.power_step):
                self.current_request = self.current_request + self.power_step
                nodes['LASER_PWR'] = 'POWER_{}W'.format(self.current_request)
                self.timer['wait'] = 1 #60 #0.1
                ezca['VID-CAM16_EXP'] = lscparams.camera_exp['ASAIR'] * 2. / self.current_request
            else:
                nodes['LASER_PWR'] = 'POWER_{}W'.format(lscparams.input_power['POWER_25W'])
                ezca['VID-CAM16_EXP'] = lscparams.camera_exp['ASAIR'] * 2. / lscparams.input_power['POWER_25W']
                self.requestedPowerAcquired = True

        if nodes['LASER_PWR'].done and nodes['LASER_PWR'].arrived and self.requestedPowerAcquired and self.dhardBoosted:
            for ii in ['X', 'Y']:
                ezca['LSC-{}_TIDAL_REDTRIG_THRESH_OFF'.format(ii)]=8000
            # Only get here if at final power, ISS done and hard loops boosted
            return True

class REDUCE_RF45_MODULATION_DEPTH(GuardState):
    index = 516
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.dBsToReduceBy = 6.0
        self.RampTime = 30 # 30 sec for 3dB
        self.ModReduceStepSize = 0.2

        NumSteps = self.dBsToReduceBy / self.ModReduceStepSize
        self.StepWaitTime = self.RampTime / NumSteps


        # only run through this once, even if state is re-requested!
        if ezca['LSC-MOD_RF45_AM_RFSET'] == lscparams.mod_depth['45']:
            # Increase PD gains for modulation depth reduction compensation
            # (yes, this is a bit of a mess, but it's cleaner in lscparams this way....sorry!)
            do_list = []
            ramp_list = []
            for pd in lscparams.pd_gain['LSC']:
                for iq in ['I','Q']:
                    for freq in lscparams.pd_gain['LSC'][pd]:
                        if freq in ['45']:
                            do_list.append(('LSC-%s_A_RF%s_%s_GAIN'%(pd,freq,iq),
                            round(lscparams.pd_gain['LSC'][pd][freq]*10.0**(self.dBsToReduceBy/20.0),3)))
                            ramp_list.append(('LSC-%s_A_RF%s_%s_TRAMP'%(pd,freq,iq), self.RampTime))
                        elif freq in ['90']:
                            do_list.append(('LSC-%s_B_RF%s_%s_GAIN'%(pd,freq,iq),
                            round(lscparams.pd_gain['LSC'][pd][freq]*10.0**(self.dBsToReduceBy*2/20.0),3)))
                            ramp_list.append(('LSC-%s_B_RF%s_%s_TRAMP'%(pd,freq,iq), self.RampTime))

            for pd in lscparams.pd_gain['ASC']:
                for iq in ['I','Q']:
                    for seg in [1,2,3,4]:
                        for freq in lscparams.pd_gain['ASC'][pd]:
                            if freq == 'X':
                                do_list.append(('ASC-POP_X_RF_%s%s_GAIN'%(iq,seg),
                                round(lscparams.pd_gain['ASC'][pd][freq]*10.0**(self.dBsToReduceBy/20.0),3)))
                                ramp_list.append(('ASC-POP_X_RF_%s%s_TRAMP'%(iq,seg), self.RampTime))
                            elif freq in ['45','36','72']:  # '72' Added 2021May12 GM, JCD and VS
                                do_list.append(('ASC-%s_A_RF%s_%s%s_GAIN'%(pd,freq,iq,seg),
                                round(lscparams.pd_gain['ASC'][pd][freq]*10.0**(self.dBsToReduceBy/20.0),3)))
                                do_list.append(('ASC-%s_B_RF%s_%s%s_GAIN'%(pd,freq,iq,seg),
                                round(lscparams.pd_gain['ASC'][pd][freq]*10.0**(self.dBsToReduceBy/20.0),3)))
                                ramp_list.append(('ASC-%s_A_RF%s_%s%s_TRAMP'%(pd,freq,iq,seg), self.RampTime))
                                ramp_list.append(('ASC-%s_B_RF%s_%s%s_TRAMP'%(pd,freq,iq,seg), self.RampTime))

            for tramp in ramp_list:
                ezca[tramp[0]] = tramp[1]
            for gain in do_list:
                ezca[gain[0]] = gain[1]

        #convergence checking for soft loops
        self.Soft_converged = False
        self.Soft_loops = ['PIT4', 'PIT5', 'YAW4', 'YAW5']
        self.Soft_thresh = [2,      2,      2,      2]

        self.counter = 0
        self.timer['wait'] = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        # only run through this once, even if state is re-requested!
        if ezca['LSC-MOD_RF45_AM_RFSET'] > (lscparams.mod_depth['45'] - self.dBsToReduceBy) and ezca['LSC-MOD_RF45_AM_RFSET'] <= lscparams.mod_depth['45']:
            if self.timer['wait'] and self.counter == 0:
                ezca['LSC-MOD_RF45_AM_RFSET'] -= self.ModReduceStepSize
                self.timer['wait'] = self.StepWaitTime
        else:
            self.counter += 1

        if self.timer['wait'] and self.counter > 0:
            if not ISC_library.dither_convergence_checker(self.Soft_loops, self.Soft_thresh):
                notify('waiting for soft loops to converge')
                self.timer['wait'] = 1 # So that the log doesn't fill up with a zillion lines about covergence
            else:
                log('ASC converged!')
                return True


class CHARD_BLEND(GuardState):
    index = 5170 # was 517. get out of main ladder
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    def main(self):
        #stuff for CHARD blends
        ISC_library.asc_intrix['PIT'].put('CHARD_B', [], 0)
        ISC_library.asc_intrix['YAW'].put('CHARD_B', [], 0)

        ISC_library.asc_intrix['PIT']['CHARD_B', 'TRX_A']  = 1
        ISC_library.asc_intrix['PIT']['CHARD_B', 'TRX_B']  = 1
        ISC_library.asc_intrix['PIT']['CHARD_B', 'TRY_A']  = 2.8
        ISC_library.asc_intrix['PIT']['CHARD_B', 'TRY_B']  = 0
        ISC_library.asc_intrix['YAW']['CHARD_B', 'TRX_A']  = 1
        ISC_library.asc_intrix['YAW']['CHARD_B', 'TRX_B']  = -0.7
        ISC_library.asc_intrix['YAW']['CHARD_B', 'TRY_A']  = -0.5
        ISC_library.asc_intrix['YAW']['CHARD_B', 'TRY_B']  = 0

        ezca.get_LIGOFilter('ASC-CHARD_P_B').only_on('INPUT', 'FM2','FM7', 'OUTPUT', 'DECIMATION')

        ezca.get_LIGOFilter('ASC-CHARD_Y_B').only_on('INPUT', 'FM10', 'OUTPUT', 'DECIMATION') 

        # hard coding that the ramp time is 10 seconds because this should match the ramp time on the blend filters.
        ezca['ASC-CHARD_P_A_TRAMP'] = 10
        ezca['ASC-CHARD_Y_A_TRAMP'] = 10
        ezca['ASC-CHARD_P_B_TRAMP'] = 10
        ezca['ASC-CHARD_Y_B_TRAMP'] = 10
        self.timer['wait']=2
        self.counter = 0


    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.timer['wait']:
            if self.counter == 0:
                #ezca.get_LIGOFilter('ASC-CHARD_Y_A').switch_on('FM6', wait=False) #SED commented out since this wasn't working, APril 13th 2021
                #ezca['ASC-CHARD_Y_A_GAIN'] = 1.35
                #ezca.get_LIGOFilter('ASC-CHARD_Y_A').switch_on('FM4', wait=False)
                #ezca['ASC-CHARD_Y_B_GAIN'] = 1.35 #0.77 #1.4
                ezca.get_LIGOFilter('ASC-CHARD_P_A').switch_on('FM6', wait=False)
                ezca['ASC-CHARD_P_B_GAIN'] = 1
                ''' here you can copy this to undo CHARD BLENDs
                ezca.get_LIGOFilter('ASC-CHARD_Y_A').switch_off('FM6', wait=False)
                ezca['ASC-CHARD_Y_B_GAIN'] = 0
                ezca.get_LIGOFilter('ASC-CHARD_P_A').switch_off('FM6', wait=False)
                ezca['ASC-CHARD_P_B_GAIN'] = 0
                '''
                self.counter += 1
                self.timer['wait'] = 10

            elif self.counter == 1:
                # self.timer['wait'] = 2
                self.counter += 1
            elif self.counter == 2:
                #wait 10 minutes for things to thermalize before increasing power more.  Maybe we can make this shorter but just being conservative for now.
                self.timer['wait'] = 30
                self.counter +=1

            elif self.counter== 3 and self.timer['wait']:
                #ezca.get_LIGOFilter('ASC-INP1_P').ramp_gain(-0.8, ramp_time=5, wait=False)  #commenting out, very small change to gain should probably be in LOWNOISE_ASC if necessary anyway. 
                #ezca.get_LIGOFilter('ASC-INP1_Y').ramp_gain(-0.8, ramp_time=5, wait=False)
                return True

class LOWNOISE_ASC(GuardState):
    index = 522  # Was 519, move to above max pow. JCD 3May2021
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        ezca.switch('ASC-INP1_P', 'FM4', 'ON')
        # reduce the ADS CLK gains SED moved this earlier so that there is time for the error signals to be reduced before we increase the loop gains to compensate
        #SED GLM VS  April 13th, 2021, commenting this out since we lost lock attempting to change ADS
        ezca['ASC-ADS_PIT3_OSC_CLKGAIN'] = 30 #was 300 202010504
        ezca['ASC-ADS_PIT4_OSC_CLKGAIN'] = 30 #was 300 
        ezca['ASC-ADS_PIT5_OSC_CLKGAIN'] = 30 #was 300 
        ezca['ASC-ADS_YAW3_OSC_CLKGAIN'] = 30 #was 300 
        ezca['ASC-ADS_YAW4_OSC_CLKGAIN'] = 30 #was 300 
        ezca['ASC-ADS_YAW5_OSC_CLKGAIN'] = 30 #was 300 

        self.timer['LoopShapeRamp'] = 5
        self.counter = 0
        self.timer['pwr']=0.125

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
            
        if self.counter == 0 and self.timer['LoopShapeRamp']:
            ezca.get_LIGOFilter('ASC-CHARD_Y').ramp_gain(100, ramp_time=10, wait=False) # increased to 100 again on August 3, see below 
            ezca.switch('ASC-CHARD_Y', 'FM3', 'FM8', 'FM9', 'ON') # EMC added 20 Hz LP in FM8, new chard Y high power controller with LP built in, should be engaged with low power controller FM2, more useism with FM3
            # FM9 is the controller to improve low frequnecy noise (August 2, see alog 71927)

            # DHARD P high power controller and LP in one filter
            ezca.switch('ASC-DHARD_P', 'FM4', 'ON')
            #adding a DHARD P boost 78024
            ezca.switch('ASC-DHARD_P', 'FM8', 'ON')
            # Turn on DHARD Y high power and low noise controller
            ezca.switch('ASC-DHARD_Y', 'FM1', 'FM3', 'FM4', 'FM5', 'FM8', 'ON') # FM3 is additional resGs at 0.9 Hz and 0.5 Hz for more low freq boost # FM1 added by Gabriele on July 24, 2023, FM8 from EMC 20230313 for less noise 10-30 Hz
            # boost DSOFT P gain for extra stability at 0.5 Hz
            ezca.get_LIGOFilter('ASC-DSOFT_P').ramp_gain(20, ramp_time=5, wait=False)
            # JCD 3Jan2023: Turn off soft loop smooth limiters now that we're all converged
            for dof in ['CSOFT','DSOFT']:
                for py in ['P','Y']:
                    ezca['ASC-%s_%s_SMOOTH_ENABLE'%(dof,py)] = 0
                    
            self.counter +=1
            self.timer['LoopShapeRamp'] = 10
                    
        if self.counter == 1 and self.timer['LoopShapeRamp']:

            # EMC removed unnecessary commented out code
            self.ADS_gains = {}
            for py in ['PIT','YAW']:
                for dof in ['3', '4', '5']:
                    chan = 'ASC-ADS_%s%s_DOF'%(py,dof)
                    self.ADS_gains[chan] = ezca[chan+'_GAIN']
                    ezca.get_LIGOFilter(chan).ramp_gain(lscparams.ads_camera_gains['ADS_%s%s'%(py,dof)]['gain'],10,wait=False) # EMC increased from 4 to 10 20230307 for convergence for camera servos # increased to 20

            #ezca.get_LIGOFilter('ASC-SRC1_Y').switch_on('OFFSET') #this was needed in O3, but we will hope not to need these things now that we have a new ITMY.  SED GM APril 13 2021
            ezca['ASC-CHARD_P_TRAMP'] = 5 
            # first turn off boost
            ezca.switch('ASC-CHARD_P', 'FM9', 'OFF')
            ezca.switch('ASC-CHARD_P', 'FM3', 'FM8', 'ON')
            ezca['ASC-CHARD_P_GAIN'] = 4.5
            
            self.counter +=1
            self.timer['LoopShapeRamp'] = 10
                    
        if self.counter == 2 and self.timer['LoopShapeRamp']:
            
            # Increase bandwidth of MICH P and MICH Y, also put on new lowpass for MICH P  JCD alog
            # DDB March 17th, yaw was unstable with LP6 and a gain of -1, we reduced gain a bit and switched to ELP15, put low pass switch into DRMI ASC
            #ezca.get_LIGOFilter('ASC-MICH_P').ramp_gain(-1.2, 5, wait=False)
            ezca.get_LIGOFilter('ASC-MICH_Y').ramp_gain(-0.6,   5, wait=False)

            #turn on HAM1 FF to CHARD P
            ezca['HPI-HAM1_TTL4C_FF_OUTSW'] = 1
            # reduce DSOFT Y and CSOFT P gains
            ezca.get_LIGOFilter('ASC-DSOFT_Y').ramp_gain(5, ramp_time=5, wait=False)
            ezca.get_LIGOFilter('ASC-CSOFT_P').ramp_gain(20, ramp_time=5, wait=False)
            ezca.get_LIGOFilter('ASC-DSOFT_P').ramp_gain(5, ramp_time=5, wait=False)
            self.counter +=1
            self.timer['LoopShapeRamp'] = 10
        
        elif self.counter == 3 and self.timer['LoopShapeRamp']:
            # reduce test masses M0 DAMP yaw gains
            ezca['SUS-ITMX_M0_DAMP_Y_TRAMP'] = 10
            ezca['SUS-ETMX_M0_DAMP_Y_TRAMP'] = 10
            ezca['SUS-ITMY_M0_DAMP_Y_TRAMP'] = 10
            ezca['SUS-ETMY_M0_DAMP_Y_TRAMP'] = 10
            ezca['SUS-ITMX_M0_DAMP_Y_GAIN'] = -0.5
            ezca['SUS-ETMX_M0_DAMP_Y_GAIN'] = -0.5
            ezca['SUS-ITMY_M0_DAMP_Y_GAIN'] = -0.5
            ezca['SUS-ETMY_M0_DAMP_Y_GAIN'] = -0.5
            self.counter +=1
            self.timer['LoopShapeRamp'] = 10

        elif self.counter == 4 and self.timer['LoopShapeRamp']:
            for dof in ['P','Y','L','R','T','V']:
                ezca['SUS-SR2_M1_DAMP_%s_GAIN'%(dof)] = -0.2
                ezca['SUS-SR3_M1_DAMP_%s_GAIN'%(dof)] = -0.5                
            ezca['ASC-AS_A_DC_YAW_TRAMP'] = 10
            self.counter += 1
            self.timer['LoopShapeRamp'] = 10
            
        elif self.counter == 5 and self.timer['LoopShapeRamp']:  
            return True


class MAX_POWER(GuardState):
    index = 520
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
    
        self.chard_y_lowered = False

        #convergence checking for soft loops
        self.Soft_loops = ['CSOFT', 'DSOFT']
        self.Soft_P_thresh = [1000, 1000]
        self.Soft_Y_thresh = [1000, 1000]

        ezca['OMC-READOUT_X0_TRAMP'] = 0.3

        if lscparams.power_up_on_RF:
            #prepare to scale the DARM offset as we power up on RF if needed
            self.d_offset_start = ezca['LSC-DARM1_OFFSET']
            self.pwr_offset_start = ezca['PSL-POWER_SCALE_OFFSET']

        ezca['ASC-RPC_CHARD_Y_TRAMP'] = 5
        ezca['ASC-RPC_DHARD_Y_TRAMP'] = 5
        ezca['ASC-RPC_CHARD_P_TRAMP'] = 5
        ezca['ASC-RPC_DHARD_P_TRAMP'] = 5
        ezca['ASC-RPC_CSOFT_P_TRAMP'] = 5
        ezca['ASC-RPC_DSOFT_P_TRAMP'] = 5

        time.sleep(0.1)
        #do we need to sit somewhere half way up for soft loops to converge?
        self.counter=0
        self.timer['wait'] = 0.1
        self.boost_dHard = True
        self.final_boosts = True
        nodes['LASER_PWR'] = 'POWER_25W'
        self.timer['thermal_wait'] = 20  #according to instructions in 46190.  Perhaps we can speed this up, but for now I'm leaveing it as is.
        self.counter = 0
        self.converged = True
        self.current_request = lscparams.input_power['POWER_25W']
        self.power_step = 5 #step the power up in increments of 5W at most
        self.requestedPowerAcquired = False
        
        # boost CSOFT P gain
        ezca.get_LIGOFilter('ASC-CSOFT_P').ramp_gain(25, ramp_time=5, wait=False)
        #ezca.get_LIGOFilter('ASC-DSOFT_P').ramp_gain(20, ramp_time=5, wait=False)
        


    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):

        self.ADS_dofs = [3,4,5]

        if self.timer['thermal_wait']:
            if not self.requestedPowerAcquired:
                if self.current_request < (lscparams.input_power['NLN'] - self.power_step):
                    self.current_request = self.current_request + self.power_step
                    nodes['LASER_PWR'] = 'POWER_{}W'.format(self.current_request)

                    self.timer['thermal_wait'] = 20 # EMC decreased, was 30
                else:
                    nodes['LASER_PWR'] = 'POWER_{}W'.format(lscparams.input_power['NLN'])
                    self.requestedPowerAcquired = True
                    self.timer['thermal_wait'] = 30 # EMC decreased, was 30
            else:
                self.converged = True # reset each time we start the loop
                maxVal = 0
                # If any is not converged, should go to False and disallow going forward.
                #log(ezca['ASC-ADS_YAW4_DEMOD_I_OUT16']/ezca['ASC-ADS_YAW4_OSC_CLKGAIN'])
                for ads in self.ADS_dofs:
                    for py in ['PIT','YAW']:
                        dofVal = abs(ezca['ASC-ADS_%s%s_DEMOD_I_OUT16'%(py,ads)]/ezca['ASC-ADS_%s%s_OSC_CLKGAIN'%(py,ads)])
                        threshVal = 0.002 # EMC increased by a factor of 2 back to 0.002 20240823 # EMC dropped by factor of 2, was 0.002 20230310
                        if (dofVal > threshVal):
                            if dofVal > maxVal:
                                maxVal = dofVal
                            notify('Waiting for ADS converge. Largest value = %s, must become less than %s'%(maxVal, threshVal))
                            self.timer['thermal_wait'] = 5
                            self.converged = False
                if self.converged and nodes['LASER_PWR'].arrived and nodes['LASER_PWR'].done:
                    return True

        # change the DARM offset to follow the current DC light level out of the OMC
        if not (nodes['LASER_PWR'].arrived and nodes['LASER_PWR'].done):
            if lscparams.power_up_on_RF:
                ezca['LSC-DARM1_OFFSET'] = self.d_offset_start*self.pwr_offset_start/ezca['PSL-POWER_SCALE_OFFSET']
            else:
                newOfs = (math.sqrt(max(lscparams.dc_readout['DCPD_SUM_TARG'] *
                    ezca['OMC-READOUT_PREF_OFFSET']/ezca['IMC-PWR_IN_OUTMON'] - ezca['OMC-READOUT_CD_OFFSET'], 0.01)) *
                    ezca['OMC-READOUT_XF_OFFSET'])
                ezca['OMC-READOUT_X0_OFFSET'] = round(newOfs,6)

        #ISC_library.adjust_radiation_pressure_compensation()
        





##################################################
# STATES: Prep for low noise
##################################################



class ADJUST_POWER(GuardState):
    index = 1000
    request = False

    #intended as a state that takes care of loop stability while people change power manually
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.chard_y_lowered = False
        #convergence checking for soft loops
        self.Soft_loops = ['CSOFT', 'DSOFT']
        self.Soft_P_thresh = [1000, 1000]
        self.Soft_Y_thresh = [1000, 1000]

        ezca['OMC-READOUT_X0_TRAMP'] = 0.3

        if lscparams.power_up_on_RF:
            #prepare to scale the DARM offset as we power up on RF if needed
            self.d_offset_start = ezca['LSC-DARM1_OFFSET']
            self.pwr_offset_start = ezca['PSL-POWER_SCALE_OFFSET']

         # ddb comment out 01-04-2023 as RPC doesn't work with new loops
        # ezca['ASC-RPC_CHARD_Y_TRAMP'] = 5
        # ezca['ASC-RPC_DHARD_Y_TRAMP'] = 5
        # ezca['ASC-RPC_CHARD_P_TRAMP'] = 5
        # ezca['ASC-RPC_DHARD_P_TRAMP'] = 5
        # ezca['ASC-RPC_CSOFT_P_TRAMP'] = 5
        # ezca['ASC-RPC_DSOFT_P_TRAMP'] = 5

        time.sleep(0.1)
        #do we need to sit somewhere half way up for soft loops to converge?
        self.counter=0
        self.timer['wait'] = 0.1
        #self.boost_dHard = True
        #self.final_boosts = True
        self.counter = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):

        # change the DARM offset to follow the current DC light level out of the OMC
        if not (nodes['LASER_PWR'].arrived and nodes['LASER_PWR'].done):
            if lscparams.power_up_on_RF:
                ezca['LSC-DARM1_OFFSET'] = self.d_offset_start*self.pwr_offset_start/ezca['PSL-POWER_SCALE_OFFSET']
            else:
                newOfs = (math.sqrt(max(lscparams.dc_readout['DCPD_SUM_TARG'] *
                    ezca['OMC-READOUT_PREF_OFFSET']/ezca['IMC-PWR_IN_OUTMON'] - ezca['OMC-READOUT_CD_OFFSET'], 0.01)) *
                    ezca['OMC-READOUT_XF_OFFSET'])
                ezca['OMC-READOUT_X0_OFFSET'] = round(newOfs,6)

        #ISC_library.adjust_radiation_pressure_compensation() # ddb comment out 01-04-2023 as RPC doesn't work with new loops
        

        return True


class SLOWNOISE_COIL_DRIVERS(GuardState):
    index = 554
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.violins_too_high = False
        for sus in ['ITMX','ITMY','ETMX','ETMY']:
            for mode in range(1,10):
                # was 5, using large number to override check
                if ezca['SUS-'+sus+'_L2_DAMP_MODE'+str(mode)+'_RMSLP_LOG10_OUTMON']>555:
                    self.violins_too_high = True

        turn_off_violins = True #SED changed this from True to false because we are no longer using violin damping this early in the sequence
        if turn_off_violins:
            nodes['VIOLIN_DAMPING'] = 'TURN_OFF_DAMPING_ALL'

        self.all_done = False # initialize

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.violins_too_high:
            notify('Damp violins better')
            #rerun check on violins with stricter threshold
            self.violins_too_high = False
            for sus in ['ITMX','ITMY','ETMX','ETMY']:
                for mode in range(1,10):
                    # was 4.5, using large number to override check
                    if ezca['SUS-'+sus+'_L2_DAMP_MODE'+str(mode)+'_RMSLP_LOG10_OUTMON']>444.5:
                        self.violins_too_high = True
                    else:
                        self.violins_too_high = False
            pass

        if nodes['VIOLIN_DAMPING'].arrived and nodes['VIOLIN_DAMPING'].done and not self.all_done:
            # wait until violin damping is off to start changing coil drivers

            #turn off dither loops
            log('turning dither loop inputs off')
            for dof in ['PIT3', 'PIT4', 'PIT5', 'YAW3', 'YAW4', 'YAW5']:
                ezca.get_LIGOFilter('ASC-ADS_%s_DOF'%dof).switch_off('INPUT')

            eul2osemTramp = 1
            analogExtraSleep = 1
            path = '/opt/rtcds/userapps/release/isc/h1/scripts/sus/'

            # LHO does the three-legged actuation for all optics at once.
            # This is not working for Pre-O4 locking, so we are switching optics one at a time
            # See LHO alog 62132 for generating snap files - JCB (from LLO)
            optics   = ['PRM', 'PR2', 'SRM', 'SR2', 'BS', 'ITMY', 'ITMX', 'ETMY', 'ETMX']
            stage    = ['M3',  'M3',  'M3',  'M3',  'M2', 'L2',   'L2',   'L2',   'L2']
            newState = [ 3,     3,     3,     3,     3,    3,      3,      3,      3]

            opt_stage_state = zip(optics, stage, newState) # tuple of these

            coils  = ['UL', 'UR', 'LL', 'LR']

            # Loop through each optic
            for opt, stg, state in opt_stage_state:
                if ezca['SUS-' + opt + '_BIO_' + stg + '_' + coil + '_STATEREQ'] == state:
                    #if we are already in the final state, don't do anything. 
                    pass
                else:
                    for coil in coils:
                    # Loop through each coil on each optic
                        log('-- Switching ' + opt + ' ' + coil)

                        # first step, set matrix values
                        ezca.burtwb(path + opt.lower() + '_' + stg.lower() +'_out_' + coil.lower() + '.snap')
                        ezca['SUS-' + opt + '_' + stg + '_EUL2OSEM_TRAMP'] = eul2osemTramp   # CRC April 14, 2021
                        time.sleep(0.1)
                        
                        ezca['SUS-' + opt + '_' + stg + '_EUL2OSEM_LOAD_MATRIX'] = 1
                        time.sleep(eul2osemTramp)
                        
                        # second step, clear filters
                        ezca['SUS-' + opt + '_' + stg + '_COILOUTF_' + coil + '_RSET'] = 2
                        time.sleep(analogExtraSleep)

                        # third step, switch coil drivers
                        # go to intermediate state
                        ezca['SUS-' + opt + '_BIO_' + stg + '_' + coil + '_STATEREQ'] = 1
                        time.sleep(0.1)
                        ezca['SUS-' + opt + '_BIO_' + stg + '_' + coil + '_STATEREQ'] = state
                        time.sleep(0.1)

                        # done with this coil...
                                
                    ezca.burtwb(path + opt.lower() + '_' + stg.lower() +'_out_normal.snap')
                    time.sleep(0.1) # for burt to work
                    ezca['SUS-' + opt + '_' + stg + '_EUL2OSEM_LOAD_MATRIX'] = 1
                    time.sleep(eul2osemTramp)
     

            notify('Coil Drivers Done!') # this keeps repeating itself at the end. Bad.



            self.all_done = True

        if self.all_done:
            return True


class LOWNOISE_COIL_DRIVERS(GuardState):
    index = 552 # Was 552. Get out of our main ladder.
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.violins_too_high = False
        for sus in ['ITMX','ITMY','ETMX','ETMY']:
            for mode in range(1,10):
                # was 5, using large number to override check
                if ezca['SUS-'+sus+'_L2_DAMP_MODE'+str(mode)+'_RMSLP_LOG10_OUTMON']>555:
                    self.violins_too_high = True

        turn_off_violins = True
        if turn_off_violins:
            nodes['VIOLIN_DAMPING'] = 'TURN_OFF_DAMPING_ALL'

        self.reset_counter   = 0     # iterations of run state, resets for each coil on each optic
        self.coil_num        = 0     # iterate through coils, reset for each optic
        self.AllDone         = False # initialize

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.violins_too_high:
            notify('Damp violins better')
            #rerun check on violins with stricter threshold
            self.violins_too_high = False
            for sus in ['ITMX','ITMY','ETMX','ETMY']:
                for mode in range(1,10):
                    # was 4.5, using large number to override check
                    if ezca['SUS-'+sus+'_L2_DAMP_MODE'+str(mode)+'_RMSLP_LOG10_OUTMON']>444.5:
                        self.violins_too_high = True
                    else:
                        self.violins_too_high = False
            pass

        if nodes['VIOLIN_DAMPING'].arrived and nodes['VIOLIN_DAMPING'].done and not self.AllDone:
            # wait until violin damping is off to start changing coil drivers

            #turn off dither loops
            log('turning dither loop inputs off')
            for dof in ['PIT3', 'PIT4', 'PIT5', 'YAW3', 'YAW4', 'YAW5']:
                ezca.get_LIGOFilter('ASC-ADS_%s_DOF'%dof).switch_off('INPUT')

            eul2osemTramp = 1
            analogExtraSleep = 1
            path = '/opt/rtcds/userapps/release/isc/h1/scripts/sus/'

            # LLO jumps actuation from xRMs to xR2s, then immediately can change coil driver states
            #   Then, jump opposite, immediately change other coil drivers.
            #   Then, put everything back.  Don't forget factors of 2 in drive strength from optical position
            optics   = ['PRM', 'PR2', 'SRM', 'SR2', 'BS', 'ETMY', 'ETMX', 'ITMY', 'ITMX']
            stage    = ['M3',  'M3',  'M3',  'M3',  'M2', 'L2',   'L2',   'L2',   'L2']
            newState = [ 3,     3,     3,     3,     3,    3,      3,      3,      3]

            opt_stage_state = zip(optics, stage, newState) # tuple of these

            coils  = ['UL', 'UR', 'LL', 'LR']
            if self.coil_num >= len(coils):
                #so we don't get stuck after coil 4
                pass
            else:
                coil  = coils[self.coil_num]

            log(str(self.reset_counter))

            if self.reset_counter == 0:
                # first step, set matrix values
                for opt, stg, state in opt_stage_state:
                    if ezca['SUS-' + opt + '_BIO_' + stg + '_' + coil + '_STATEREQ'] == state:
                        #if we are already in the final state, don't do anything. 
                        continue

                    log('-- Switching ' + opt + ' ' + coil)
                    ezca.burtwb(path + opt.lower() + '_' + stg.lower() +'_out_' + coil.lower() + '.snap')
                    ezca['SUS-' + opt + '_' + stg + '_EUL2OSEM_TRAMP'] = eul2osemTramp   # CRC April 14, 2021
                    time.sleep(0.1)

                    ezca['SUS-' + opt + '_' + stg + '_EUL2OSEM_LOAD_MATRIX'] = 1
                    time.sleep(0.1)

                self.timer['mtrxRamp'] = eul2osemTramp
                self.reset_counter = 1


            elif self.reset_counter == 1 and self.timer['mtrxRamp']:
                # second step, clear filters
                for opt, stg, state in opt_stage_state:
                    if ezca['SUS-' + opt + '_BIO_' + stg + '_' + coil + '_STATEREQ'] == state:
                        #if we are already in the final state, don't do anything. 
                        continue

                    ezca['SUS-' + opt + '_' + stg + '_COILOUTF_' + coil + '_RSET'] = 2
                    time.sleep(0.1)
                    
                self.timer['extraSleep'] = analogExtraSleep
                self.reset_counter = 2


            elif self.reset_counter == 2 and self.timer['extraSleep']:
                # third step, switch coil drivers
                for opt, stg, state in opt_stage_state:
                    if ezca['SUS-' + opt + '_BIO_' + stg + '_' + coil + '_STATEREQ'] == state:
                        #if we are already in the final state, don't do anything. 
                        continue

                    # go to intermediate state
                    ezca['SUS-' + opt + '_BIO_' + stg + '_' + coil + '_STATEREQ'] = 1
                    time.sleep(0.1)

                    # go to final state
                    ezca['SUS-' + opt + '_BIO_' + stg + '_' + coil + '_STATEREQ'] = state
                    time.sleep(0.1)
                self.reset_counter = 3


            elif self.reset_counter == 3:
                # done with this coil...
                self.coil_num += 1
                if self.coil_num < len(coils):
                    # go on to next coil
                    self.reset_counter = 0
                else:
                    # after 4 coils reset optic
                    for opt, stg, state in opt_stage_state:
                        ezca.burtwb(path+ opt.lower() + '_' + stg.lower() +'_out_normal.snap')
                        time.sleep(1) # for burt to work
                        ezca['SUS-' + opt + '_' + stg + '_EUL2OSEM_LOAD_MATRIX'] = 1
                        time.sleep(0.1)

                    self.timer['mtrxRamp'] = eul2osemTramp
                    self.reset_counter = 4
                    
            elif self.reset_counter == 4 and self.timer['mtrxRamp']:
                # only get here after finishing all coils on all optics
                notify('Coil Drivers Done!') # this keeps repeating itself at the end. Bad.


                self.AllDone = True

        if self.AllDone:
            return True

class TRANSITION_FROM_ETMX(GuardState):
    """
    Transition control of DARM from ETMX (with high-voltage ESD driver)
    to ITMX temporarily, then
    set up ETMX for transition back.  Don't actually transition back to ETMX though.

    """
    index   = 557
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # Definition of use_EX_L1L2 moved to lscparams.
        self.use_EY_ESD = True
        ezca['SUS-ITMX_L3_LOCK_BIAS_TRAMP'] = 25
        if self.use_EY_ESD:
            ezca['SUS-ETMY_L3_LOCK_BIAS_TRAMP'] = 0
            ezca['SUS-ETMY_L3_LOCK_BIAS_GAIN'] = 0
            time.sleep(0.1)
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_BIAS').turn_on('OFFSET')
            ezca['SUS-ETMY_L3_LOCK_BIAS_OFFSET'] = -8.9
            time.sleep(0.1)
            ezca['SUS-ETMY_L3_LOCK_BIAS_TRAMP'] = 120


        failureMessage = 'Not transitioning DARM control.'
        if ezca['SUS-ITMX_L3_LOCK_L_OUT16']!=0:
            self.failure = True
            notify('IX control is already on. ' + failureMessage)
        else:
            self.failure = False

        if not self.failure:
            log('Turn on ITMX bias')
            if self.use_EY_ESD:
                log('switching ETMY ESD to high votlage mode')
                if ezca['SUS-ETMY_L3_LOCK_BIAS_OUT16'] == 0:
                    for quad in ['UR', 'UL', 'LR', 'LL']:
                        ezca['SUS-ETMY_L3_ESDOUTF_{}_LIN_BYPASS_SW'.format(quad)] = 0
                        time.sleep(0.5)
                        ezca['SUS-ETMY_BIO_L3_{}_VOLTAGE_SW'.format(quad)] = 1
                        time.sleep(0.1)
                        ezca['SUS-ETMY_BIO_L3_{}_HVDISCONNECT_SW'.format(quad)] = 1
                        time.sleep(0.1)
                        ezca['SUS-ETMY_BIO_L3_{}_STATEREQ'.format(quad)] = 1
                        time.sleep(0.1)
                time.sleep(1)
                ezca['SUS-ETMY_L3_LOCK_BIAS_GAIN'] = 1
            else:           
                ezca['SUS-ITMX_L3_LOCK_BIAS_GAIN'] = 1
            #turn off both inputs
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(0, ramp_time=0, wait=True)
            ezca.get_LIGOFilter('SUS-ITMX_L3_LOCK_L').ramp_gain(0, ramp_time=0, wait=True)
            if self.use_EY_ESD:
                self.timer['ETMswap'] = ezca['SUS-ETMY_L3_LOCK_BIAS_TRAMP']
            else:
                self.timer['ETMswap'] = ezca['SUS-ITMX_L3_LOCK_BIAS_TRAMP']
            self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        self.ADS_dofs = [3,4,5]
        if self.timer['ETMswap']:
            if not self.use_EY_ESD:
                if abs(ezca['SUS-ITMX_L3_ESDAMON_DC_OUTMON']) < 400 and self.counter == 1:  # Check if DC bias is off.
                    notify('IX ESD bias is off. ')
                    self.failure = True
            else:
                if abs(ezca['SUS-ETMY_L3_ESDAMON_DC_OUTMON']) < 400 and self.counter == 1:  # Check if DC bias is off.
                    notify('EY ESD bias is off. ')
                    self.failure = True
        if self.failure:
            notify('IX (or EY) ESD must be off. DARM not transitioned. HELP ME!!!!')
        else:
            if self.timer['ETMswap'] and self.counter == 1:
                log('set up ETMY and ITMX to make the transisition')

                log('Preparing EY +IX for DARM actuation transition...')
                #send it to EY L1+L2, IX L3, not others
                ezca.get_LIGOFilter('SUS-ETMY_M0_LOCK_L').turn_off('INPUT')
                ezca['SUS-ETMY_L1_LOCK_OUTSW_L'] = 1
                ezca['SUS-ETMY_L2_LOCK_OUTSW_L'] = 1
                ezca['SUS-ETMY_L1_LOCK_L_GAIN'] = 1.2#0.60
                ezca['SUS-ETMY_L2_LOCK_L_GAIN'] = 15
                if not self.use_EY_ESD:
                    #set up to use ETMY L1 + ITMX L3
                    ezca['SUS-ETMY_L3_LOCK_OUTSW_L'] = 0
                    ezca.get_LIGOFilter('SUS-ITMX_M0_LOCK_L').turn_off('INPUT')
                    ezca['SUS-ITMX_L1_LOCK_OUTSW_L'] = 0
                    ezca['SUS-ITMX_L2_LOCK_OUTSW_L'] = 0
                    ezca['SUS-ITMX_L3_LOCK_OUTSW_L'] = 1
                elif self.use_EY_ESD:
                    #set up to use ETMY L1 + ITMX L3
                    ezca['SUS-ETMY_L3_LOCK_OUTSW_L'] = 1
                    ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').turn_off('FM5', 'FM8', 'FM9', 'FM10')



                # set up filters
                #ezca.get_LIGOFilter('SUS-ETMY_L1_LOCK_L').only_on('INPUT', 'FM1', 'FM2', 'FM3',
                #        'FM5', 'FM6', 'FM8', 'OUTPUT', 'DECIMATION')
                ezca.get_LIGOFilter('SUS-ETMY_L1_LOCK_L').only_on('INPUT', 'FM4', 'FM10', 'OUTPUT', 'DECIMATION')
                ezca.get_LIGOFilter('SUS-ETMY_L2_LOCK_L').only_on('INPUT', 'FM7', 'OUTPUT', 'DECIMATION')
                #elif not self.use_EY_ESD: # This didn't work, mad about syntax?  Idea was to elif-not the single next line
                ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').only_on('INPUT', 'FM5', 'FM8', 'FM9', 'FM10',
                    'OUTPUT', 'DECIMATION')
                ezca.get_LIGOFilter('SUS-ETMY_L3_ISCINF_L').only_on('INPUT','OUTPUT')
                ezca['SUS-ETMY_L3_ISCINF_L_GAIN'] = 1

                ezca['SUS-ETMY_L2_DRIVEALIGN_L2L_GAIN'] = 1
                ezca.get_LIGOFilter('SUS-ETMY_L2_DRIVEALIGN_L2L').only_on('INPUT', 'FM6', 'FM7', 'OUTPUT', 'DECIMATION')
                if not self.use_EY_ESD:
                    #use ITMX ESD
                    ezca.get_LIGOFilter('SUS-ITMX_L3_DRIVEALIGN_L2L').only_on('INPUT', 'FM4', 'FM5', 'OUTPUT', 'DECIMATION')
                    ezca.get_LIGOFilter('SUS-ITMX_L3_DRIVEALIGN_L2L').ramp_gain(-91, ramp_time=0,wait=False)
                    ezca.get_LIGOFilter('SUS-ITMX_L3_ISCINF_L').ramp_gain(1, ramp_time=0, wait=True)
                else:
                    #use ETMY ESD
                    ezca.get_LIGOFilter('SUS-ETMY_L3_DRIVEALIGN_L2L').only_on('INPUT', 'OUTPUT', 'DECIMATION')

                ## output matrix
                ezca['LSC-ARM_OUTPUT_MTRX_2_1'] = -1
                ezca['LSC-ARM_OUTPUT_MTRX_3_1'] = 1
                
                time.sleep(0.1)


                ## Perform the swap
                log('swaping to ITMX ESD and EY L1+L2') #commented out glm 20190321
                #log('swapping to IX ESD NOT SWAPPING TO EY L1&L2')
                ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(1.25, ramp_time=5, wait=False) #commented out glm 20190321
                ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').ramp_gain(0,    ramp_time=5, wait=False) #
                # ezca.get_LIGOFilter('SUS-ETMX_L3_DRIVEALIGN_L2L').ramp_gain(0,    ramp_time=5, wait=False) #switched from L3_LOCK_L gain glm20190321
                ezca.get_LIGOFilter('SUS-ITMX_L3_LOCK_L').ramp_gain(1,    ramp_time=5, wait=False)
                self.timer['ETMswap'] = ezca['SUS-ETMY_L3_LOCK_L_TRAMP']
                self.counter += 1

            #these steps are to prep for transitioning back to ETMX
            if self.timer['ETMswap'] and self.counter == 2:
                #before setting ETMX upper stages up for control, we need to clear the history in L1 which is left over from DIFF engaging fighting with tidal
                if lscparams.use_EX_L1L2:
                    ezca['SUS-ETMX_L1_LOCK_L_TRAMP'] = 30#self.tramp
                    time.sleep(0.1)
                    ezca['SUS-ETMX_L1_LOCK_L_GAIN'] = 0
                log('ramping down ETMX ESD bias')
                ezca.get_LIGOFilter('SUS-ETMX_L2_LOCK_L').ramp_gain(0, ramp_time=10, wait=False) #commented out glm 20190321
                ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_BIAS').ramp_gain(0, ramp_time=30, wait=False)

                self.timer['ETMswap'] = 31
                self.counter += 1

            if self.timer['ETMswap'] and self.counter == 3:
                if lscparams.use_EX_L1L2:
                    ezca['SUS-ETMX_L1_LOCK_L_RSET']=2
                    ezca['SUS-ETMX_L2_LOCK_L_RSET']=2
                    ezca['SUS-ETMX_L1_LOCK_L_TRAMP'] = 1
                    self.counter += 1
            if self.timer['ETMswap'] and self.counter == 4:
                log('switching ETMX ESD to low votlage mode')
                if ezca['SUS-ETMX_L3_LOCK_BIAS_OUT16'] == 0:
                    for quad in ['UR', 'UL', 'LR', 'LL']:
                        ezca['SUS-ETMX_L3_ESDOUTF_{}_LIN_BYPASS_SW'.format(quad)] = 1
                        time.sleep(0.5)
                        ezca['SUS-ETMX_BIO_L3_{}_VOLTAGE_SW'.format(quad)] = 0
                        time.sleep(0.1)
                        ezca['SUS-ETMX_BIO_L3_{}_HVDISCONNECT_SW'.format(quad)] = 0
                        time.sleep(0.1)
                        ezca['SUS-ETMX_BIO_L3_{}_STATEREQ'.format(quad)] = 2
                        time.sleep(0.1)

                    #make sure the resulting bias is the one that reduces EMI coupling in DARM
                    ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_BIAS').ramp_offset(lscparams.ETMX_GND_MIN_BIAS, ramp_time=30, wait=False)
                    ezca['SUS-ETMX_L3_LOCK_BIAS_GAIN'] = 1 # Just leave this at 1.  Change sign by changing sign of offset
                    ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_GAIN'] = lscparams.ETMX_GND_MIN_DriveAlign_gain # Drivealign gain to match lower bias
                    

                    self.timer['ETMswap'] = ezca['SUS-ETMX_L3_LOCK_BIAS_TRAMP']
                    self.counter += 1


            if self.timer['ETMswap'] and self.counter == 5:
                log('set up ETMX ESD to transition back from ITMX ESD')
                # set up ETMX for transition back
                ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_TRAMP'] = 10
                ezca['SUS-ETMX_L2_LOCK_OUTSW_L'] = 1
                if lscparams.useNewDARM:
                    log("using new DARM")
                    ezca.get_LIGOFilter('SUS-ETMX_L3_DRIVEALIGN_L2L').only_on('INPUT', 'OUTPUT', 'FM7', 'DECIMATION')
                    ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_GAIN'] = lscparams.ETMX_GND_MIN_DriveAlign_gain # Drivealign gain to match lower bias
                    ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').only_on('INPUT','FM5', 'FM8', 'FM9', 'FM10', 'OUTPUT', 'DECIMATION')
                    ezca.get_LIGOFilter('SUS-ETMX_L2_LOCK_L').only_on('INPUT', 'FM1', 'FM6', 'FM10', 'OUTPUT', 'DECIMATION')
                    ezca['SUS-ETMX_L2_LOCK_L_GAIN'] = 30
                    ezca.get_LIGOFilter('SUS-ETMX_L2_DRIVEALIGN_L2L').only_on('INPUT', 'FM5', 'FM7', 'OUTPUT', 'DECIMATION')
                    ezca.get_LIGOFilter('SUS-ETMX_L1_LOCK_L').only_on('INPUT', 'FM2', 'FM6', 'OUTPUT', 'DECIMATION')
                    ezca['SUS-ETMX_L1_LOCK_L_GAIN'] = 1.06
                    #turn off a boost in DARM1  #before May 6 2024 this was turning off FM1 + FM2 (alog 77640)
                    ezca.switch('LSC-DARM1', 'FM1', 'OFF')
                else:
                    log("not using new DARM")
                    #the DARM configuration in early O4
                    ezca.get_LIGOFilter('SUS-ETMX_L3_DRIVEALIGN_L2L').only_on('INPUT','FM4', 'FM5', 'OUTPUT', 'DECIMATION')
                    ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_GAIN'] = lscparams.ETMX_GND_MIN_DriveAlign_gain # Drivealign gain to match lower bias
                    ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').only_on('INPUT','FM5', 'FM8','FM9', 'FM10', 'OUTPUT', 'DECIMATION')
                    if lscparams.use_EX_L1L2:
                        ezca.get_LIGOFilter('SUS-ETMX_L1_LOCK_L').only_on('INPUT','FM10', 'OUTPUT', 'DECIMATION') #removed FM4 BR notches, Feb 27 2019
                        ezca.get_LIGOFilter('SUS-ETMX_L2_LOCK_L').only_on('INPUT', 'FM7', 'OUTPUT', 'DECIMATION') # added boost FM5 Feb 27 2019, removed when L2A filters are off, alog 47982, tried and failed adding boost again May 25 2022
                        ezca.get_LIGOFilter('SUS-ETMX_L2_DRIVEALIGN_L2L').only_on('INPUT', 'OUTPUT', 'DECIMATION','FM6','FM7')
                        #ezca['SUS-ETMX_L2_LOCK_OUTSW_L'] = 1
                        ezca['SUS-ETMX_L1_LOCK_L_GAIN'] = 1.06
                        ezca['SUS-ETMX_L2_LOCK_L_GAIN'] = 23 # CRC, GLM, AE was 23, AE, GLM 20190321 #was 15, SED Feb 27 2019  # trying
                self.timer['ETMswap'] = ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_TRAMP']
                self.counter += 1

            if self.timer['ETMswap'] and self.counter == 6:
                self.converged = True # reset each time we start the loop
                maxVal=0
                # If any is not converged, should go to False and disallow going forward.
                #log(ezca['ASC-ADS_YAW4_DEMOD_I_OUT16']/ezca['ASC-ADS_YAW4_OSC_CLKGAIN'])
                log('ADS convergence check')
                for ads in self.ADS_dofs:
                    for py in ['PIT','YAW']:
                        dofVal = abs(ezca['ASC-ADS_%s%s_DEMOD_I_OUT16'%(py,ads)]/ezca['ASC-ADS_%s%s_OSC_CLKGAIN'%(py,ads)])
                        threshVal = 0.01 #was 0.002 changed GLM 20210416
                        if (dofVal > threshVal):
                            if dofVal > maxVal:
                                maxVal = dofVal
                            self.timer['ETMswap'] = 20
                            self.converged = False


                if self.converged:
                    return True
                else:
                    notify('Waiting for ADS converge. Largest value = %s, must become less than %s'%(round(maxVal,5), threshVal))


class LOWNOISE_ESD_ETMY(GuardState):
    index = 559
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        #check that we haven't already transitioned DARM control, check that bias is on
        ezca['SUS-ETMY_L3_LOCK_BIAS_TRAMP'] = 120

        failureMessage = 'Not transitioning DARM control.'
        if ezca['SUS-ETMY_L3_LOCK_L_OUT16']!=0:
            self.failure = True
            notify('EY control is already on. ' + failureMessage)
        else:
            self.failure = False

        if not self.failure:

            log('set up ETMY to make the transisition')

            log('Preparing EY for DARM actuation transition...')
                #send it to EY L1,L2,L3
            ezca.get_LIGOFilter('SUS-ETMY_M0_LOCK_L').turn_off('INPUT')
            ezca['SUS-ETMY_L1_LOCK_OUTSW_L'] = 1
            ezca['SUS-ETMY_L2_LOCK_OUTSW_L'] = 1
            ezca['SUS-ETMY_L3_LOCK_OUTSW_L'] = 1

            ezca.get_LIGOFilter('SUS-ETMY_L1_LOCK_L').only_on('INPUT', 'FM4','FM10', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L1_LOCK_L_GAIN'] = 1.06

            ezca.get_LIGOFilter('SUS-ETMY_L2_LOCK_L').only_on('INPUT', 'FM7', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L2_LOCK_L_GAIN'] = 23
            ezca.get_LIGOFilter('SUS-ETMY_L2_DRIVEALIGN_L2L').only_on('INPUT', 'FM6', 'FM7','OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L2_DRIVEALIGN_L2L_GAIN'] = 1

            ezca.get_LIGOFilter('SUS-ETMY_L3_ISCINF_L').only_on('INPUT','OUTPUT')
            ezca['SUS-ETMY_L3_ISCINF_L_GAIN'] = 1
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').only_on('INPUT', 'FM5', 'FM8', 'FM9', 'FM10', 'OUTPUT', 'DECIMATION')
            ezca.get_LIGOFilter('SUS-ETMY_L3_DRIVEALIGN_L2L').only_on('INPUT', 'FM4', 'FM5', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L3_DRIVEALIGN_L2L_GAIN']=-34.7

                ## output matrix
            ezca['LSC-ARM_OUTPUT_MTRX_2_1'] = -1
            time.sleep(0.1)


            log('Turn on ETMY bias')
            ezca['SUS-ETMY_L3_LOCK_BIAS_OFFSET'] = 9.3
            ezca['SUS-ETMY_L3_LOCK_BIAS_GAIN'] = -1
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_BIAS').only_on('INPUT', 'OFFSET','FM1','OUTPUT', 'DECIMATION')
            #turn off both inputs
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(0, ramp_time=0, wait=True)
            self.timer['ETMswap'] = ezca['SUS-ETMY_L3_LOCK_BIAS_TRAMP']
            self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        self.ADS_dofs = [3,4,5]
        if self.timer['ETMswap']:
            if abs(ezca['SUS-ETMY_L3_ESDAMON_DC_OUTMON']) < 400 and self.counter == 1:  # Check if DC bias is off.
                notify('EY ESD bias is off. ')
                self.failure = True
        if self.failure:
            notify('EY ESD must be off. DARM not transitioned. HELP ME!!!!')
        else:
            if self.timer['ETMswap'] and self.counter == 1:


                ## Perform the swap
                log('swaping to EY')
                ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(1, ramp_time=5, wait=False) 
                ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').ramp_gain(0,    ramp_time=5, wait=False) 
                self.timer['ETMswap'] = ezca['SUS-ETMY_L3_LOCK_L_TRAMP']
                self.counter += 1
                log(self.counter)
                
            if self.timer['ETMswap'] and self.counter == 2:
                return True

class BACK_TO_ETMX(GuardState):
    index = 561
    request = False
    
    #a state which can be used to transition DARM back to ETMX control if you have been running on ETMY (ie, to have teh EX bias set to 0).  This should work if the last thing that was done was run low noise ESD ETMY. 
    
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        #check that we haven't already transitioned DARM control, check that bias is on
        ezca['SUS-ETMX_L3_LOCK_BIAS_TRAMP'] = 60

        failureMessage = 'Not transitioning DARM control.'
        if ezca['SUS-ETMX_L3_LOCK_L_OUT16']!=0:
            self.failure = True
            notify('EX control is already on. ' + failureMessage)
        else:
            self.failure = False

        if not self.failure:

            log('set up ETMX to make the transisition')

            log('Preparing EX for DARM actuation transition...')
                #send it to EY L1,L2,L3
            ezca.get_LIGOFilter('SUS-ETMX_M0_LOCK_L').turn_off('INPUT')
            ezca['SUS-ETMX_L1_LOCK_OUTSW_L'] = 1
            ezca['SUS-ETMX_L2_LOCK_OUTSW_L'] = 1
            ezca['SUS-ETMX_L3_LOCK_OUTSW_L'] = 1

            ezca.get_LIGOFilter('SUS-ETMX_L1_LOCK_L').only_on('INPUT', 'FM2','FM10', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMX_L1_LOCK_L_GAIN'] = 1.06

            ezca.get_LIGOFilter('SUS-ETMY_L2_LOCK_L').only_on('INPUT', 'FM7', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L2_LOCK_L_GAIN'] = 23
            ezca.get_LIGOFilter('SUS-ETMX_L2_DRIVEALIGN_L2L').only_on('INPUT', 'FM6', 'FM7','OUTPUT', 'DECIMATION')
            ezca['SUS-ETMX_L2_DRIVEALIGN_L2L_GAIN'] = 1

            ezca.get_LIGOFilter('SUS-ETMX_L3_ISCINF_L').only_on('INPUT','OUTPUT')
            ezca['SUS-ETMX_L3_ISCINF_L_GAIN'] = 1
            ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').only_on('INPUT', 'FM5', 'FM8', 'FM9', 'FM10', 'OUTPUT', 'DECIMATION')
            ezca.get_LIGOFilter('SUS-ETMX_L3_DRIVEALIGN_L2L').only_on('INPUT', 'FM4', 'FM5', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_GAIN']= lscparams.ETMX_GND_MIN_DriveAlign_gain

                ## output matrix
            ezca['LSC-ARM_OUTPUT_MTRX_1_1'] = 1
            time.sleep(0.1)


            log('Turn on ETMX bias')
            ezca['SUS-ETMX_L3_LOCK_BIAS_OFFSET'] = lscparams.ETMX_GND_MIN_BIAS
            ezca['SUS-ETMX_L3_LOCK_BIAS_GAIN'] = 1
            ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_BIAS').only_on('INPUT', 'OFFSET','FM1','OUTPUT', 'DECIMATION')
            #turn off both inputs
            ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').ramp_gain(0, ramp_time=0, wait=True)
            self.timer['ETMswap'] = ezca['SUS-ETMX_L3_LOCK_BIAS_TRAMP']
            self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        self.ADS_dofs = [3,4,5]
        if self.timer['ETMswap']:
            if abs(ezca['SUS-ETMX_L3_ESDAMON_DC_OUTMON']) < 400 and self.counter == 1:  # Check if DC bias is off.
                notify('EX ESD bias is off. ')
                self.failure = True
        if self.failure:
            notify('EX ESD must be off. DARM not transitioned. HELP ME!!!!')
        else:
            if self.timer['ETMswap'] and self.counter == 1:

                ## Perform the swap
                log('swaping to EX')
                ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').ramp_gain(1, ramp_time=5, wait=False) 
                ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(0,    ramp_time=5, wait=False) 
                self.timer['ETMswap'] = ezca['SUS-ETMX_L3_LOCK_L_TRAMP']
                self.counter += 1
                log(self.counter)
                
            if self.timer['ETMswap'] and self.counter == 2:
                return True



class LOWNOISE_ESD_ETMX(GuardState):
    index   = 558
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # Transition back to lownoise ESD on ETMX
        # These were the last few steps of old LOWNOISE_ESD_ETMX
        etmx_ramp = 20 # tested 2 seconds, was a failure, original value 10

        log('switching ESD back to ETMX')
        ezca.get_LIGOFilter('SUS-ITMX_L3_LOCK_L').ramp_gain(0,    ramp_time = etmx_ramp, wait=False)
        ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').ramp_gain(1,    ramp_time = etmx_ramp, wait=False)
        if lscparams.use_EX_L1L2 and lscparams.useNewDARM:
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(0, ramp_time = etmx_ramp, wait=False)
        self.timer['ETMswap'] = etmx_ramp

        self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):


        if self.timer['ETMswap'] and self.counter == 1:
            log('turning on LP to avoid fast glitches saturating ESD')
            ezca.switch('LSC-DARM1', 'FM9', 'ON')
            #if lscparams.useNewDARM:  #this was turned off in TRANSITION from ETMX until May 6 2024, alog 77640
            #    #ezca.switch('LSC-DARM1', 'FM2', 'OFF')
            ezca.switch('SUS-ETMX_L1_LOCK_L','FM2', 'ON')# add boost to L1 to relieve L2 at low frequencies SED MSB JSK Sept 9 2019
            log('Turning off unused elements in LSC outmtrx')
            ISC_library.darmcarm_outrix.put('ITMX', [], 0) # Turn off DARM to ITMX and ETMY output
            
            #set ETMY bias to the one that minimizes ground noise coupling. 
            ezca['SUS-ETMY_L3_LOCK_BIAS_TRAMP'] = 120
            time.sleep(0.1)
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_BIAS').only_on('INPUT', 'OFFSET','FM1','OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L3_LOCK_BIAS_OFFSET'] = lscparams.ETMY_GND_MIN_BIAS

            
            #log('Turning off ITMX bias') #added GM 20181029
            ezca.get_LIGOFilter('SUS-ITMX_L3_LOCK_BIAS').ramp_gain(0, ramp_time=120, wait=False) # Again want IX bias off. 31Mar2023 JCD
            # lower the EDS bias
            #ezca['SUS-ETMX_L3_LOCK_BIAS_TRAMP']      = 5
            #ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_TRAMP'] = 5
            #ezca['SUS-ETMX_L3_LOCK_BIAS_GAIN']       = 0.5
            #ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_GAIN']  = -80
            self.counter +=1

        if self.counter == 2:
            log('turning dither loop inputs back on')
            for dof in ['PIT3', 'PIT4', 'PIT5', 'YAW3', 'YAW4', 'YAW5']:
                ezca.get_LIGOFilter('ASC-ADS_%s_DOF'%dof).switch_on('INPUT')
            return True




class LOWNOISE_LENGTH_CONTROL(GuardState):
    """
    Add feedforward of MICH and SRCL control signals onto the test masses.

    """
    index = 560
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        #SQZ prep, SED commenting this out since it's confusing if people are trying to adjust temperature
        #if not nodes['SQZ_MANAGER'].state == 'NO_SQUEEZING':
        #    if ezca['GRD-SQZ_MANAGER_STATE_N'] < 51:
        #        nodes['SQZ_MANAGER'] = 'FDS_READY_IFO'
                
        nodes['NOISE_CLEAN'] = 'SUBTRACTING_NOISE'

        # We don't have phase to lower the MICH UGF.  Leaving UGF at 8 H. JCD 21Mar2019
        #ezca['LSC-MICH1_GAIN'] = 2.5 # RXA - measured Oct 2018; set UGF to  7 Hz

        #engage quad violin notches in MICH filter before we feed mich to the quads.  Putting them in the LSC filter bank means we don't mess up the feedforward phase.
        ezca.get_LIGOFilter('LSC-MICH2').turn_on('FM6', 'FM7')
        ezca.get_LIGOFilter('LSC-MICH2').turn_off('FM10')

        #engage 32Hz and 40Hz notches in MICH filter to reject injecting these peaks in MICH and PRCL. See LHO ALOG 68050. Koji Arai 20Mar2023
        ezca.get_LIGOFilter('LSC-MICH2').turn_on('FM9')
        ezca.get_LIGOFilter('LSC-MICH1').turn_on('FM8') # alog 76631

        # Prep FF filters
        ezca.get_LIGOFilter('LSC-MICHFF').ramp_gain(0,  ramp_time=3, wait=False)
        ezca.get_LIGOFilter('LSC-SRCLFF1').ramp_gain(0, ramp_time=3, wait=False)
        ezca.get_LIGOFilter('LSC-SRCLFF2').ramp_gain(0, ramp_time=3, wait=False)
        ezca.get_LIGOFilter('LSC-PRCLFF').ramp_gain(0,  ramp_time=3, wait=False)

        ezca.get_LIGOFilter('LSC-MICHFF').only_on('INPUT', 'FM6', 'FM10', 'OUTPUT', 'DECIMATION') # new FF April 04 2024, CMC
        ezca.get_LIGOFilter('LSC-SRCLFF1').only_on('INPUT', 'FM8', 'FM10', 'OUTPUT', 'DECIMATION') # new FF Sept 5, EMC fixed
        ezca.get_LIGOFilter('LSC-SRCLFF2').only_on('INPUT','OUTPUT', 'DECIMATION')
        ezca.get_LIGOFilter('LSC-PRCLFF').only_on('INPUT', 'FM7', 'FM10', 'OUTPUT', 'DECIMATION')  # July 11 2024, CMC


        # Set MICH and SRCL and PRCL FF matrix elements for EY-only feeback, to PUM
        ISC_library.outrix['ETMX','MICHFF'] =  0#1
        ISC_library.outrix['ETMY','MICHFF'] = -1
        ISC_library.outrix['ITMX','MICHFF'] =  0
        ISC_library.outrix['ITMY','MICHFF'] =  0
        ISC_library.outrix['ETMX','SRCLFF'] =  0#1
        ISC_library.outrix['ETMY','SRCLFF'] = -1
        ISC_library.outrix['ITMX','SRCLFF'] =  0
        ISC_library.outrix['ITMY','SRCLFF'] =  0
        ISC_library.outrix['ETMX','PRCLFF'] =  0#1
        ISC_library.outrix['ETMY','PRCLFF'] = -1
        
        # Turn off DARM-> EY
        ISC_library.darmcarm_outrix['ETMY', 'DARM'] = 0
        
        # Now that *nothing* is going to EY (no darm, and FF still off), set up EY to go through L3 to L2, but not to L1:
        ezca['SUS-ETMY_L3_LOCK_L_GAIN'] = 1
        ezca['SUS-ETMY_L1_LOCK_L_GAIN'] = 0

        #prep to turn on SRCL OFFSET
        ezca['LSC-SRCL1_OFFSET'] = 0
        ezca['LSC-SRCL1_TRAMP'] = 5
        
        # prep to turn on PRCL offset
        ezca['LSC-PRCL1_OFFSET'] = 0
        ezca['LSC-PRCL1_TRAMP'] = 10
        
        #ezca.switch('LSC-PRCL1', 'OFFSET', 'ON')  #commented out SED Sept 23rd 2024
        
        # set FF tramps to 10 seconds, hoping to avoid 102 Hz ring up
        ezca['LSC-MICHFF_TRAMP'] = 3
        ezca['LSC-SRCLFF1_TRAMP'] = 3

        self.timer['wait'] = 10
        self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):

        if self.counter == 1 and self.timer['wait']:

            #in O 3 we adjusted the gains used for SRCL and MICH here to compensate for dropping sideband build ups at higher powers.  With the ITMY replacement, we aren't seeing as much drop in sideband powers, so we have removed the if statement setting gains for different powers. 
            ezca['LSC-SRCL1_GAIN'] = -7.5 # -10.6 for 15Hz UGF at 47W,  JCD&GM 3May2021; -15 for 15Hz UGF at 47 W thermalized, JCD&VS 5May2021
            ezca['LSC-PRCL1_GAIN'] = 10.0 # was 6, increased so that after thermalization at 60W, it'll be about 30 Hz UGF 21June2023
            ezca['LSC-MICH1_GAIN'] = 2.0 # alog 76631

            ezca.get_LIGOFilter('LSC-SRCL2').switch('FM6', 'FM7', 'ON') # 
            #turn DARM RG on
            ezca.switch('LSC-DARM1', 'FM3', 'ON')
            ezca.switch('LSC-DARM2', 'FM2', 'FM8', 'ON') # FM8 ECGV is low freq boost to reduce DARM RMS and FM2 is 3Hz boost
            ezca.switch('LSC-SRCL1', 'OFFSET', 'ON')

            # turn on new PRCL controller [GV 2023-04-19 https://alog.ligo-wa.caltech.edu/aLOG/index.php?callRep=68817]
            ezca.switch('LSC-PRCL1', 'FM1', 'ON')  # Dcntrl
            ezca.switch('LSC-PRCL2', 'FM10', 'ON') # res3.4
            
            # increase PRCL gain by 1.5
            ezca['LSC-PRCL2_TRAMP'] = 3 # 3 second ramp
            ezca['LSC-PRCL2_GAIN'] = 1.0 # changed to 1.7 from 1.5. 6Jun2023 JCD, alog 70160
            
            # request THERMALIZATION guardian to THERMALIZED to adjust PRCL gain over 6 hours (!!!)
            # nodes['THERMALIZATION'] = 'SERVO_PRCL_GAIN' # commented out 21 June 2023 while commish 60W operation            

            self.timer['wait'] = 10 # Changed back to 1
            self.counter += 1



        if self.counter == 2 and self.timer['wait']:

            ezca['LSC-MICHFF_GAIN']  = lscparams.gain['MICHFF']
            ezca['LSC-SRCLFF1_GAIN'] = lscparams.gain['SRCLFF1'] * lscparams.dc_readout['sign']
            #ezca['LSC-SRCLFF2_GAIN'] = lscparams.gain['SRCLFF2'] * lscparams.dc_readout['sign']
            ezca['LSC-PRCLFF_GAIN']  = lscparams.gain['PRCLFF']

            self.counter += 1
            self.timer['wait'] = 1
            


        if self.counter >= 3 and self.timer['wait']:
            # Install SRCL Offset
            ezca['LSC-SRCL1_OFFSET']  = lscparams.offset['SRCL_RETUNE']
            #ezca['LSC-PRCL1_OFFSET'] = -58 # EMC, alog 79989  #commented out SED Srpt 24 2024
            return True
            
            
class TURN_ON_CALIBRATION_LINES(GuardState):
    index = 562
    request = False
    
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        for g in ['CLK','SIN','COS']:
            ezca['SUS-ETMX_L3_CAL_LINE_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L3']
            ezca['SUS-ETMX_L2_CAL_LINE_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L2']
            ezca['SUS-ETMX_L1_CAL_LINE_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L1']
            ezca['CAL-CS_TDEP_SUS_LINE3_COMPARISON_OSC_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L3']
            ezca['CAL-CS_TDEP_SUS_LINE2_COMPARISON_OSC_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L2']
            ezca['CAL-CS_TDEP_SUS_LINE1_COMPARISON_OSC_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L1']
        
        ezca['CAL-PCALX_OSC_SUM_ON'] = 1
        ezca['CAL-PCALY_OSC_SUM_ON'] = 1
        
        ezca['LSC-DARMOSC_SUM_ON'] = 1
        
        return True


class INCREASE_DARM_OFFSET(GuardState):
    index= 563
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        ezca['OMC-READOUT_X0_TRAMP'] = 5
        # Set DARM offset such that we have 40mA on OMC once we're at full power
        newOfs = (math.sqrt(max(lscparams.dc_readout['DCPD_SUM_NLN'] *
            ezca['OMC-READOUT_PREF_OFFSET']/ezca['IMC-PWR_IN_OUTMON'] - ezca['OMC-READOUT_CD_OFFSET'], 0.01)) *
            ezca['OMC-READOUT_XF_OFFSET'])
        ezca['OMC-READOUT_X0_OFFSET'] = round(newOfs,6)
    
        self.timer['wait'] = ezca['OMC-READOUT_X0_TRAMP']

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.timer['wait']:
            # Put the tramp back
            ezca['OMC-READOUT_X0_TRAMP'] = 1
            return True


class OMC_WHITENING(GuardState):
    index= 591
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.timer['wait'] = 0
        self.omc_nominal = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):

        if self.timer['wait'] and not self.omc_nominal:
            # Check violins are okay before turning on whitening
            if ISC_library.check_for_violins_saturation(power='high'):
                nodes['OMC_LOCK'] = 'READY_FOR_HANDOFF'
                self.timer['wait'] = 5
                self.omc_nominal = True
            else:
                notify('Violins too high for whitening, waiting until damped')
                self.timer['wait'] = 60
                return False

        elif self.timer['wait'] and self.omc_nominal:
            if nodes['OMC_LOCK'].OK:
                return True
            else:
                notify('Waiting for OMC_LOCK')

        else:
            return False
            

class REDUCE_RF9_MODULATION_DEPTH(GuardState):
    index = 510
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):

        self.reduce9MHzdBm = 3.0
        # only run through this once, even if state is re-requested!
        if ezca['LSC-MOD_RF9_AM_RFSET'] == lscparams.mod_depth['9']:
            # Increase PD gains for modulation depth reduction compensation
            # (yes, this is a bit of a mess, but it's cleaner in lscparams this way....sorry!)
            do_list = []
            ramp_list = []
            for pd in lscparams.pd_gain['LSC']:
                for iq in ['I','Q']:
                    for freq in lscparams.pd_gain['LSC'][pd]:
                        if freq in ['9']:
                            do_list.append(('LSC-%s_A_RF%s_%s_GAIN'%(pd,freq,iq),
                            round(lscparams.pd_gain['LSC'][pd][freq]*10.0**(self.reduce9MHzdBm/20.0),3)))
                            ramp_list.append(('LSC-%s_A_RF%s_%s_TRAMP'%(pd,freq,iq), 30))
                        elif freq in ['18']:
                            do_list.append(('LSC-%s_B_RF%s_%s_GAIN'%(pd,freq,iq),
                            round(lscparams.pd_gain['LSC'][pd][freq]*10.0**(2*self.reduce9MHzdBm/20.0),3)))
                            ramp_list.append(('LSC-%s_B_RF%s_%s_TRAMP'%(pd,freq,iq), 30))

            for pd in lscparams.pd_gain['ASC']:
                for iq in ['I','Q']:
                    for seg in [1,2,3,4]:
                        for freq in lscparams.pd_gain['ASC'][pd]:
                            if freq in ['9', '36']: # EMC added 9 MHz compensation 9 Jan 2023
                                do_list.append(('ASC-%s_A_RF%s_%s%s_GAIN'%(pd,freq,iq,seg),
                                round(lscparams.pd_gain['ASC'][pd][freq]*10.0**(self.reduce9MHzdBm/20.0),3)))
                                do_list.append(('ASC-%s_B_RF%s_%s%s_GAIN'%(pd,freq,iq,seg),
                                round(lscparams.pd_gain['ASC'][pd][freq]*10.0**(self.reduce9MHzdBm/20.0),3)))
                                ramp_list.append(('ASC-%s_A_RF%s_%s%s_TRAMP'%(pd,freq,iq,seg), 30))
                                ramp_list.append(('ASC-%s_B_RF%s_%s%s_TRAMP'%(pd,freq,iq,seg), 30))

            for tramp in ramp_list:
                ezca[tramp[0]] = tramp[1]
            for gain in do_list:
                ezca[gain[0]] = gain[1]
            self.timer['wait'] = 0
            self.counter = 1

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if ezca['LSC-MOD_RF9_AM_RFSET'] > (lscparams.mod_depth['9'] - self.reduce9MHzdBm) and ezca['LSC-MOD_RF9_AM_RFSET'] <= lscparams.mod_depth['9']:
            if self.timer['wait'] and self.counter == 1:
                ezca['LSC-MOD_RF9_AM_RFSET'] -= 1
                ezca['LSC-REFL_SERVO_IN1GAIN'] += 1
                self.timer['wait'] = 2
        else:
            self.counter += 1

        if ezca['LSC-MOD_RF9_AM_RFSET'] == (lscparams.mod_depth['9'] - self.reduce9MHzdBm) and self.counter >= 2:
            return True

class LASER_NOISE_SUPPRESSION(GuardState):
    index = 575
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        nodes['IMC_LOCK'] = 'ISS_ON' 
        
        # prepare to add in REFL B sensor
        ezca['LSC-REFL_SUM_B_IN2EN'] = 1
        ezca['LSC-REFL_SUM_B_IN1EN'] = 0

        # Set REFL B sumnode gain to be equal to REFL A sumnode gain, whatever that is (+15 dB as of August 2, 2022)
        ezca['LSC-REFL_SUM_B_IN2GAIN'] = ezca['LSC-REFL_SUM_A_IN2GAIN']
        ezca['LSC-REFL_SERVO_IN2GAIN'] = -32

        # make a note of the current gain for REFL A (on CM input gain slider)
        self.REFLA_gain = ezca['LSC-REFL_SERVO_IN1GAIN']
        self.timer['wait'] = 1
        self.counter = 1

        return

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.counter == 1 and self.timer['wait'] and nodes['IMC_LOCK'].arrived and nodes['IMC_LOCK'].done:            
            # add in REFL B sensor
            ezca['LSC-REFL_SERVO_IN2EN'] = 1
            self.counter += 1
            self.timer['wait'] = 0.3

        if self.counter == 2 and self.timer['wait']:
            # ramp REFL B to same gain as REFL A, at this point the CARM loop gain is a bit lower than we want it to end up so we can increase the loop gain a bit this way
            if ezca['LSC-REFL_SERVO_IN2GAIN'] < self.REFLA_gain:
                ezca['LSC-REFL_SERVO_IN2GAIN'] += 1
                self.timer['wait'] = 0.1
            else:
                self.counter += 1
                self.timer['wait'] = 1.0

        # CRC 20191030: Adding IMC gain taken from CARM_TO_ANALOG back in here.  This is apparently necessary to avoid CARM gain increases below causing a lockloss. alog 52793
        if self.counter == 3 and self.timer['wait']:
            if ezca['IMC-REFL_SERVO_IN1GAIN'] < 2:
                ezca['IMC-REFL_SERVO_IN1GAIN'] += 1
            else:
                self.timer['wait'] = 1.0
                self.counter += 1

        if self.counter == 4 and self.timer['wait']:
            #CRC increased this to 12dB on Aril 23rd/24th, SED added in REFL B on Arpirl 25th, so to maintain the same UGF with the summed sensors, we want 6dB of gain on both in1 and in2
            # Leave gains at 6dB for 60W alog73836, 30Oct2023
            # reducing CARM gain after O4 break, alog 76751
            if ezca['LSC-REFL_SERVO_IN1GAIN'] < 3:
                ezca['LSC-REFL_SERVO_IN1GAIN'] += 1
                ezca['LSC-REFL_SERVO_IN2GAIN'] += 1
                self.timer['wait'] = 1.0
            else:
                self.timer['wait'] = 1.0
                self.counter += 1
                self.gain_increase_counter = 0
        if self.counter ==5 and self.timer['wait']:
            if self.gain_increase_counter <7:  #icrease the imc fast gain by this many dB
                #redistribute gain in IMC servo so that we don't saturate splitmon in earth quakes, JW DS SED
                #setting this to not change the gain sliders October 9, because Camilla saw that the IMC locklosses that we think are due to laser glitches started happening on the day we did this gain redistribution. alog 80561
                #turned back on Dec4 2024 jw
                ezca['IMC-REFL_SERVO_IN1GAIN'] -= 1
                ezca['IMC-REFL_SERVO_IN2GAIN'] -= 1
                ezca['IMC-REFL_SERVO_FASTGAIN'] += 1
                time.sleep(0.1)
                self.gain_increase_counter +=1
            else:
                self.counter +=1
        
        if self.counter >= 6 and self.timer['wait']:
            return True
            
class TURN_ON_SUS_DAC_DITHERS(GuardState):
    index = 5760 # Suggested to be 576, but for now remove from main ladder.
    request = True
    
    # Initial version of state by JSK 2022-Nov-01
    # Edited by EMC 2023 Jan 13
    
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # Dither Parameters (could go in lscparams.py)
        #twentyBitDACs = [['ETMX',['L3','L2','L1']],
         #                ['ETMY',['L3','L2','L1']],
        #  #               ['ITMX',['L3','L2','L1']],
        #   #              ['ITMY',['L3','L2','L1']],
        #    #             ['BS',['M2']],
        #     #            ['SRM',['M3','M2']],
        #      #           ['SR2',['M3','M2']]]
        twentyBitDACs = [['ETMX',['L2']],
                         ['ETMY',['L2']],
                         ['ITMX',['L2']],
                         ['ITMY',['L2']]]
                         
        pringleDrive = [1.0,-1.0,-1.0,1.0]
        dacDitherFreq = 6997.73 # Hz Delibrately NOT either exactly 7000, nor L1 value of 7000.1
        dacDitherAmp = 60000 # EMC:use 600 for L3, other stages untested, JSK had this at 60000, but that is too much for L3 DAC # 18-bit DAC counts, just upstream of OUTF banks, assuming lowest noise coil driver state
        dacDitherRamp = 2.0 # sec
        
        quadrantStrs = ['{}'.format(i) for i in range(1,5)]
        
        # Set up dithers
        for iOptic, thisOptic in enumerate(twentyBitDACs):
            for iStage, thisStage in enumerate(thisOptic[1]):
                # Set LKIN2OSEM and LKIN2ESD Matrices to drive in PRINGLE
                for iQuadrant, thisQuadrant in enumerate(quadrantStrs):
                    if thisStage == 'L3':
                        ezca.write('SUS-{}_{}_LKIN2ESD_{}_1'.format(thisOptic[0],thisStage,thisQuadrant),pringleDrive[iQuadrant])
                    else:
                        ezca.write('SUS-{}_{}_LKIN2OSEM_{}_1'.format(thisOptic[0],thisStage,thisQuadrant),pringleDrive[iQuadrant])
                    
                # Turn on LKIN2OSEM matrix input switch
                ezca.write('SUS-{}_{}_LKIN_EXC_SW'.format(thisOptic[0],thisStage),1.0)
                
            # Set PITCH lock-in oscillator Frequency and TRAMP time
            ezca.write('SUS-{}_LKIN_P_OSC_FREQ'.format(thisOptic[0]),dacDitherFreq) 
            ezca.write('SUS-{}_LKIN_P_OSC_TRAMP'.format(thisOptic[0]),dacDitherRamp)
                        
        # Turn ON dithers by setting non-zero amplitude of CLKGAIN
        for iOptic, thisOptic in enumerate(twentyBitDACs):
            ezca.write('SUS-{}_LKIN_P_OSC_CLKGAIN'.format(thisOptic[0]),dacDitherAmp)
        
        # Wait 10% longer than the ramp time to conclude
        self.timer['rampwait'] = dacDitherRamp + 0.1*dacDitherRamp   
                
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if not self.timer['rampwait']:
            return False
        else:
            return True
   
   
class ADS_TO_CAMERAS(GuardState):
    index = 578
    request = False
    
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        # When we're ready, this should take us to CAMERA_SERVO_ON
        nodes['CAMERA_SERVO'] = 'CAMERA_SERVO_ON'   # Don't use cameras until we see what's wrong, 27Mar2023 JCD
        log('camera servo guardian state')
        ezca.switch('ASC-AS_A_RF36_Q_YAW', 'OFFSET', 'ON')  # should have started OFF when relocking.
        # ezca.get_LIGOFilter('ASC-AS_A_RF36_Q_YAW').ramp_offset(35000, ramp_time = 30, wait=False) # Remove offset, 19July2023 JCD
        self.counter = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)    
    def run(self):
        if nodes['CAMERA_SERVO'].arrived and nodes['CAMERA_SERVO'].done and self.counter == 0:
            # Set A2L to their centered values.
            for py in ['P2L','Y2L']:
                for optic in ['ITMX','ITMY','ETMX', 'ETMY']:
                    ezca['SUS-{}_L2_DRIVEALIGN_{}_SPOT_GAIN'.format(optic,py)] = lscparams.a2l_gains['FINAL'][py][optic]
            return True
        
   
class INJECT_SQUEEZING(GuardState):
    index = 580
    request = False

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        self.sqz_nom = ezca['GRD-SQZ_MANAGER_NOMINAL']
        if nodes['SQZ_MANAGER'].state == 'NO_SQUEEZING':
            self.squeeze = False
            log('SQZ manager set to NO_SQUEEZING, moving on with this state.')
            nodes['SQZ_MANAGER'] = 'NO_SQUEEZING'
        else:
            self.squeeze = True
            nodes['SQZ_MANAGER'] = self.sqz_nom
        self.timer['CheckSqz'] = 90 # How long to wait before checking and rerequesting the squeezer
        nodes['SUS_PI'] = 'PI_DAMPING' 
        
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if not self.squeeze:
            notify('SQZ manager is set to NO_SQUEEZING, moving on. Check SQZ_MANAGER status.')
            return True
        else:
            if nodes['SQZ_MANAGER'].arrived:
                return True
            elif self.timer['CheckSqz']:
                nodes['SQZ_MANAGER'] = 'FDS_READY_IFO'
                time.sleep(1)
                nodes['SQZ_MANAGER'] = self.sqz_nom
                notify('Not yet squeezing, rerequesting and moving on. Check SQZ_MANAGER status.')
                return True
            

class CLOSE_BEAM_DIVERTERS(GuardState):
    index = 590
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        #check if REFL, POP or AS AIR B DIVs are open, close them
        if ezca['SYS-MOTION_C_BDIV_A_POSITION'] == 0:
            log('Closing REFL beam diverter')
            ezca['SYS-MOTION_C_BDIV_A_CLOSE'] = 1
        if ezca['SYS-MOTION_C_BDIV_D_POSITION'] == 0:
            log('Closing AS beam diverter')
            ezca['SYS-MOTION_C_BDIV_D_CLOSE'] = 1
            ezca['VID-CAM18_EXP'] = 18000*2
        # EMC commented out POP for RH tests
        if ezca['SYS-MOTION_C_BDIV_B_POSITION'] == 0:
            log('Closing POP beam diverter')
            ezca['SYS-MOTION_C_BDIV_B_CLOSE'] = 1
        return True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        return True

            
NOMINAL_LOW_NOISE = ISC_GEN_STATES.gen_NOMINAL_LOW_NOISE(nodes, switch_SDF)
NOMINAL_LOW_NOISE.index = 600

class SDF_TO_SAFE(GuardState):
        request = False
        index = 605

        @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
        @ISC_library.assert_dof_locked_gen(['Full_IFO'])
        @nodes.checker()
        @ISC_library.unstall_nodes(nodes)
        def main(self):
            # Switch all SDF source files to OBSERVE
            switch_SDF.set_source_files('safe')

        @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
        @ISC_library.assert_dof_locked_gen(['Full_IFO'])
        @nodes.checker()
        @ISC_library.unstall_nodes(nodes)
        def run(self):
            return True


class NLN_CAL_MEAS(GuardState):
    index = 700
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    #@ISC_library.unstall_nodes(nodes)
    def main(self):

        # Turn off cal lines
        notify('Turning off calibration lines ...')
        for g in ['CLK','SIN','COS']:
            ezca['SUS-ETMX_L3_CAL_LINE_%sGAIN'%(g)] = 0
            ezca['SUS-ETMX_L2_CAL_LINE_%sGAIN'%(g)] = 0
            ezca['SUS-ETMX_L1_CAL_LINE_%sGAIN'%(g)] = 0
            ezca['CAL-CS_TDEP_SUS_LINE3_COMPARISON_OSC_%sGAIN'%(g)] = 0
            ezca['CAL-CS_TDEP_SUS_LINE2_COMPARISON_OSC_%sGAIN'%(g)] = 0
            ezca['CAL-CS_TDEP_SUS_LINE1_COMPARISON_OSC_%sGAIN'%(g)] = 0
        
        ezca['CAL-PCALX_OSC_SUM_ON'] = 0
        ezca['CAL-PCALY_OSC_SUM_ON'] = 0
        
        ezca['LSC-DARMOSC_SUM_ON'] = 0
        time.sleep(1.0)

        # Turn off ADS loops # 03-09-2019 now turns off lines DCV
        self.ADS_gains = {}
        self.ADS_ClkGains = {}
        for py in ['PIT','YAW']:
            for dof in ['3', '4', '5']:
                chan = 'ASC-ADS_%s%s_DOF'%(py,dof)
                self.ADS_gains[chan] = ezca[chan+'_GAIN']
                ezca.get_LIGOFilter(chan).ramp_gain(0,10,wait=False)

                chanClk = 'ASC-ADS_%s%s_OSC_CLKGAIN'%(py,dof)
                self.ADS_ClkGains[chanClk] = ezca[chanClk] #record dither line amplitudes
                ezca['ASC-ADS_%s%s_OSC_CLKGAIN'%(py,dof)]=0.0 #set them to zero

        # AWG_LINES
        nodes['AWG_LINES'] = 'IDLE'
        nodes['NOISE_CLEAN'] = 'SUBTR_FOR_NLN_CAL_MEAS'
        notify('Lines are OFF ... Start measuring')

        self.counter = 0
        self.timer['pause'] = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    #@ISC_library.unstall_nodes(nodes)
    def run(self):
        if ezca.read('GRD-ISC_LOCK_REQUEST', as_string=True)=='NOMINAL_LOW_NOISE':
            if self.counter==0:

                # Turn on cal Lines
                notify('Turning on calibration lines ...')
                for g in ['CLK','SIN','COS']:
                    ezca['SUS-ETMX_L3_CAL_LINE_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L3']
                    ezca['SUS-ETMX_L2_CAL_LINE_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L2']
                    ezca['SUS-ETMX_L1_CAL_LINE_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L1']
                    ezca['CAL-CS_TDEP_SUS_LINE3_COMPARISON_OSC_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L3']
                    ezca['CAL-CS_TDEP_SUS_LINE2_COMPARISON_OSC_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L2']
                    ezca['CAL-CS_TDEP_SUS_LINE1_COMPARISON_OSC_%sGAIN'%(g)] = lscparams.cal_line_gains['ETMX_L1']
        
                ezca['CAL-PCALX_OSC_SUM_ON'] = 1
                ezca['CAL-PCALY_OSC_SUM_ON'] = 1
        
                ezca['LSC-DARMOSC_SUM_ON'] = 1
                time.sleep(1.0)

                nodes['NOISE_CLEAN'] = 'SUBTRACTING_NOISE'

                if ezca['GRD-CAMERA_SERVO_STATE_N'] < 400:
                    # Turn on ADS lines
                    notify('Turning on the ADS loops')
                    for chanClk, val in self.ADS_ClkGains.items():
                        ezca[chanClk] = 30 # Turn dither amplitudes back to 30

                    time.sleep(5.0) # wait for the lines to come on before turning the loops on

                    for chan, val in self.ADS_gains.items():
                        ezca.get_LIGOFilter(chan).ramp_gain(10,10,wait=False) # Turn gains back to 10, changed 20230308 by EMC

                notify('Lines are ON ...')
                notify('Waiting for line demodulators low-pass to settle (20 sec) ...')
                self.counter += 1
                self.timer['pause'] = 20.0

            if self.counter == 1 and self.timer['pause']:

                # Go back to nominal low noise
                notify('... and to low noise. NLN_CAL_MEAS Game Over. Thank you for playing!')

                return 'NOMINAL_LOW_NOISE'

        else:
            return True




class NLN_ETMY(GuardState):
    index = 710
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        #intended to be run when we are in NLN to transition DARM control to EY
        #new state as of Nov 28 2023, not ready for use
        ezca['SUS-ETMY_L3_LOCK_BIAS_TRAMP'] = 120

        #turn off LSC FF
        ezca['LSC-SRCLFF1_TRAMP'] = 3
        ezca['LSC-MICHFF_TRAMP'] = 3
        time.sleep(0.1)
        ezca['LSC-MICHFF_GAIN'] = 0
        ezca['LSC-SRCLFF1_GAIN'] = 0
        self.timer['ETMswap'] = 3
        self.counter = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.counter == 0 and self.timer['ETMswap']:
            log('set up ETMY to make the transition')

            log('Preparing EY for DARM actuation transition...')
                #send it to EY L1,L2,L3
            ezca.get_LIGOFilter('SUS-ETMY_M0_LOCK_L').turn_off('INPUT')
            ezca['SUS-ETMY_L1_LOCK_OUTSW_L'] = 1
            ezca['SUS-ETMY_L2_LOCK_OUTSW_L'] = 1
            ezca['SUS-ETMY_L3_LOCK_OUTSW_L'] = 1

            ezca.get_LIGOFilter('SUS-ETMY_L1_LOCK_L').only_on('INPUT', 'FM4','FM10', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L1_LOCK_L_GAIN'] = 1.06

            ezca.get_LIGOFilter('SUS-ETMY_L2_LOCK_L').only_on('INPUT', 'FM7', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L2_LOCK_L_GAIN'] = 23
            ezca.get_LIGOFilter('SUS-ETMY_L2_DRIVEALIGN_L2L').only_on('INPUT', 'FM6', 'FM7','OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L2_DRIVEALIGN_L2L_GAIN'] = 1

            ezca.get_LIGOFilter('SUS-ETMY_L3_ISCINF_L').only_on('INPUT','OUTPUT')
            ezca['SUS-ETMY_L3_ISCINF_L_GAIN'] = 1
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').only_on('INPUT', 'FM5', 'FM8', 'FM9', 'FM10', 'OUTPUT', 'DECIMATION')
            ezca.get_LIGOFilter('SUS-ETMY_L3_DRIVEALIGN_L2L').only_on('INPUT', 'FM4', 'FM5', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L3_DRIVEALIGN_L2L_GAIN']=-34.7

            log('Turn on ETMY bias')
            ezca['SUS-ETMY_L3_LOCK_BIAS_OFFSET'] = 9.3
            ezca['SUS-ETMY_L3_LOCK_BIAS_GAIN'] = -1
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_BIAS').only_on('INPUT', 'OFFSET','FM1','OUTPUT', 'DECIMATION')
            #turn off both inputs
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(0, ramp_time=1, wait=True)
            
            ## output matrix
            ezca['LSC-ARM_OUTPUT_MTRX_2_1'] = -1
            time.sleep(0.1)
            self.timer['ETMswap'] = ezca['SUS-ETMY_L3_LOCK_BIAS_TRAMP']
            self.counter += 1
        self.ADS_dofs = [3,4,5]
        if self.timer['ETMswap'] and self.counter ==1:
            if abs(ezca['SUS-ETMY_L3_ESDAMON_DC_OUTMON']) < 400:  # Check if DC bias is off.
                notify('EY ESD bias is off. ')
            else:
                ## Perform the swap
                log('swaping to EY')
                ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(1, ramp_time=5, wait=False) 
                ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').ramp_gain(0,    ramp_time=5, wait=False) 
                self.timer['ETMswap'] = ezca['SUS-ETMY_L3_LOCK_L_TRAMP']
                self.counter += 1
                log(self.counter)
                
        if self.timer['ETMswap'] and self.counter == 2:
            return True
                
class NEW_DARM(GuardState):
    index = 711
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
    
        ezca['SUS-ETMX_L1_LOCK_L_TRAMP'] = 30#self.tramp
        time.sleep(0.1)
        ezca['SUS-ETMX_L1_LOCK_L_GAIN'] = 0
        ezca.get_LIGOFilter('SUS-ETMX_L2_LOCK_L').ramp_gain(0, ramp_time=10, wait=False) 
        self.timer['ramp'] = 30 
        self.counter = 0   
    
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.timer['ramp'] and self.counter == 0:
            #after gains have ramped to zero clear history
            ezca['SUS-ETMX_L1_LOCK_L_RSET']=2
            ezca['SUS-ETMX_L2_LOCK_L_RSET']=2
            ezca['SUS-ETMX_L1_LOCK_L_TRAMP'] = 1

            #set up ETMX in the configuration you want
            ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_TRAMP'] = 10
            ezca.get_LIGOFilter('SUS-ETMX_L3_DRIVEALIGN_L2L').only_on('INPUT', 'OUTPUT', 'FM7', 'DECIMATION')
            ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_GAIN'] = lscparams.ETMX_GND_MIN_DriveAlign_gain # Drivealign gain to match lower bias
            ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').only_on('INPUT','FM5', 'FM8','FM9', 'FM10', 'OUTPUT', 'DECIMATION')
            #SED removed FM1, the boost
            ezca.get_LIGOFilter('SUS-ETMX_L2_LOCK_L').only_on('INPUT', 'FM1','FM6', 'FM10', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMX_L2_LOCK_L_GAIN'] = 30
            ezca.get_LIGOFilter('SUS-ETMX_L2_DRIVEALIGN_L2L').only_on('INPUT', 'FM5', 'FM7','OUTPUT', 'DECIMATION')
            use_old_UIM = False
            if use_old_UIM:
                ezca.get_LIGOFilter('SUS-ETMX_L1_LOCK_L').only_on('INPUT','FM2', 'FM4', 'FM10', 'OUTPUT', 'DECIMATION') 
            else:
                ezca.get_LIGOFilter('SUS-ETMX_L1_LOCK_L').only_on('INPUT','FM2', 'FM6', 'OUTPUT', 'DECIMATION') 
            ezca['SUS-ETMX_L1_LOCK_L_GAIN'] = 1.06
            #turn off a boost in DARM1
            ezca.switch('LSC-DARM1', 'FM1', 'FM2','OFF')
            ezca['SUS-ETMY_L3_LOCK_L_TRAMP'] = 5
            ezca['SUS-ETMX_L3_LOCK_L_TRAMP'] = 5
            self.timer['ramp'] = 11
            self.counter +=1
        if self.timer['ramp'] and self.counter ==1:
            #make the swap    
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(0, ramp_time=10, wait=False) 
            ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').ramp_gain(1, ramp_time=10, wait=False)
            self.timer['ramp'] = 5
            self.counter +=1
        if self.timer['ramp'] and self.counter == 2:
            #turn on UIM integrator
            #ezca.switch('SUS-ETMX_L1_LOCK_L', 'FM1', 'ON')
            #set ETMY back up for feedforward
            ezca['LSC-ARM_OUTPUT_MTRX_2_1'] = 0
            ezca['SUS-ETMY_L3_LOCK_OUTSW_L'] = 0
            ezca['SUS-ETMY_L2_LOCK_L_GAIN'] = 15
            ezca['SUS-ETMY_L2_LOCK_L_TRAMP'] = 10
            ezca['SUS-ETMY_L1_LOCK_L_GAIN'] = 0
            self.timer['ramp'] = 10
            self.counter += 1
        if self.timer['ramp'] and self.counter == 3:
            ezca['SUS-ETMY_L3_LOCK_L_GAIN'] = 1
            ezca['LSC-MICHFF_GAIN'] = 1
            ezca['LSC-SRCLFF1_GAIN'] = 1
            return True # JCD found this line was out by one indent, which was going to skip the last 3 IF statements. 18Dec2023

class RETURN_TO_NLN_ETMY(GuardState):
    index = 712
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
    
        #turn off LSC FF
        ezca['LSC-SRCLFF1_TRAMP'] = 3
        ezca['LSC-MICHFF_TRAMP'] = 3
        time.sleep(0.1)
        ezca['LSC-MICHFF_GAIN'] = 0
        ezca['LSC-SRCLFF1_GAIN'] = 0
        #Ramp down EY filter gains so we can clear history
        ezca['SUS-ETMY_L1_LOCK_L_TRAMP'] = 30#self.tramp
        time.sleep(0.1)
        ezca['SUS-ETMY_L1_LOCK_L_GAIN'] = 0
        ezca.get_LIGOFilter('SUS-ETMY_L2_LOCK_L').ramp_gain(0, ramp_time=10, wait=False)
        ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(0, ramp_time=5, wait=False)  
        self.timer['ramp'] = 30 
        self.counter = 0   
    
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.timer['ramp'] and self.counter == 0:
            #after gains have ramped to zero clear history
            ezca['SUS-ETMY_L1_LOCK_L_RSET']=2
            ezca['SUS-ETMY_L2_LOCK_L_RSET']=2
            ezca['SUS-ETMY_L1_LOCK_L_TRAMP'] = 1
            time.sleep(0.1)
            #set ETMY to the configuration we want for DARM control
            ezca.get_LIGOFilter('SUS-ETMY_M0_LOCK_L').turn_off('INPUT')
            ezca['SUS-ETMY_L1_LOCK_OUTSW_L'] = 1
            ezca['SUS-ETMY_L2_LOCK_OUTSW_L'] = 1
            ezca['SUS-ETMY_L3_LOCK_OUTSW_L'] = 1

            ezca.get_LIGOFilter('SUS-ETMY_L1_LOCK_L').only_on('INPUT', 'FM4','FM10', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L1_LOCK_L_GAIN'] = 1.06

            ezca.get_LIGOFilter('SUS-ETMY_L2_LOCK_L').only_on('INPUT', 'FM7', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L2_LOCK_L_GAIN'] = 23
            ezca.get_LIGOFilter('SUS-ETMY_L2_DRIVEALIGN_L2L').only_on('INPUT', 'FM6', 'FM7','OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L2_DRIVEALIGN_L2L_GAIN'] = 1

            ezca.get_LIGOFilter('SUS-ETMY_L3_ISCINF_L').only_on('INPUT','OUTPUT')
            ezca['SUS-ETMY_L3_ISCINF_L_GAIN'] = 1
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').only_on('INPUT', 'FM5', 'FM8', 'FM9', 'FM10', 'OUTPUT', 'DECIMATION')
            ezca.get_LIGOFilter('SUS-ETMY_L3_DRIVEALIGN_L2L').only_on('INPUT', 'FM4', 'FM5', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMY_L3_DRIVEALIGN_L2L_GAIN']=-34.7

            log('Turn on ETMY bias')
            ezca['SUS-ETMY_L3_LOCK_BIAS_OFFSET'] = 9.3
            ezca['SUS-ETMY_L3_LOCK_BIAS_GAIN'] = -1
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_BIAS').only_on('INPUT', 'OFFSET','FM1','OUTPUT', 'DECIMATION')
            #turn off both inputs
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(0, ramp_time=1, wait=True)

            self.timer['ramp'] = 5
            self.counter +=1
        if self.timer['ramp'] and self.counter ==1:
            #make the swap    
            ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').ramp_gain(0, ramp_time=5, wait=False) 
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(1,    ramp_time=5, wait=False) 
            self.timer['ramp'] = 5
            self.counter +=1
        if self.timer['ramp'] and self.counter == 2:
            return True 

class DARM_RECOVER(GuardState):
    index = 713
    request = True

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):

        ezca['SUS-ETMX_L1_LOCK_L_TRAMP'] = 30#self.tramp
        time.sleep(0.1)
        ezca['SUS-ETMX_L1_LOCK_L_GAIN'] = 0
        ezca.get_LIGOFilter('SUS-ETMX_L2_LOCK_L').ramp_gain(0, ramp_time=10, wait=False)
        self.timer['ramp'] = 30
        self.counter = 0

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        if self.timer['ramp'] and self.counter == 0:
            #after gains have ramped to zero clear history
            ezca['SUS-ETMX_L1_LOCK_L_RSET']=2
            ezca['SUS-ETMX_L2_LOCK_L_RSET']=2
            ezca['SUS-ETMX_L1_LOCK_L_TRAMP'] = 1

            #set up ETMX in the configuration you want
            ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_TRAMP'] = 10
            ezca.get_LIGOFilter('SUS-ETMX_L3_DRIVEALIGN_L2L').only_on('INPUT', 'FM4', 'FM5', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMX_L3_DRIVEALIGN_L2L_GAIN'] = lscparams.ETMX_GND_MIN_DriveAlign_gain # Drivealign gain to match lower bias
            ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').only_on('INPUT','FM5', 'FM8','FM9', 'FM10', 'OUTPUT', 'DECIMATION')

            ezca.get_LIGOFilter('SUS-ETMX_L2_LOCK_L').only_on('INPUT', 'FM7', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMX_L2_LOCK_L_GAIN'] = 23.0
            ezca.get_LIGOFilter('SUS-ETMX_L2_DRIVEALIGN_L2L').only_on('INPUT', 'FM6', 'FM7','OUTPUT', 'DECIMATION')

            ezca.get_LIGOFilter('SUS-ETMX_L1_LOCK_L').only_on('INPUT','FM2', 'FM10', 'OUTPUT', 'DECIMATION')
            ezca.get_LIGOFilter('SUS-ETMX_L1_DRIVEALIGN_L2L').only_on('INPUT', 'OUTPUT', 'DECIMATION')
            ezca['SUS-ETMX_L1_LOCK_L_GAIN'] = 1.06

            self.timer['ramp'] = 5
            self.counter +=1
        if self.timer['ramp'] and self.counter ==1:
            #make the swap
            ezca.get_LIGOFilter('SUS-ETMY_L3_LOCK_L').ramp_gain(0, ramp_time=5, wait=False)
            ezca.get_LIGOFilter('SUS-ETMX_L3_LOCK_L').ramp_gain(1,    ramp_time=5, wait=False)
            ezca['SUS-ETMY_L2_LOCK_L_TRAMP'] = 10
            self.timer['ramp'] = 5
            self.counter +=1
        if self.timer['ramp'] and self.counter == 2:
            #turn on a boost in DARM1
            ezca.switch('LSC-DARM1', 'FM1', 'ON')
            #set ETMY back up for feedforward
            ezca['LSC-ARM_OUTPUT_MTRX_2_1'] = 0
            ezca['SUS-ETMY_L3_LOCK_OUTSW_L'] = 0
            ezca['SUS-ETMY_L2_LOCK_L_GAIN'] = 15
            ezca['SUS-ETMY_L1_LOCK_L_GAIN'] = 0
            self.timer['ramp'] = 10
            self.counter += 1
        if self.timer['ramp'] and self.counter == 3:
            ezca['SUS-ETMY_L3_LOCK_L_GAIN'] = 1
            ezca['LSC-MICHFF_GAIN'] = 1
            ezca['LSC-SRCLFF1_GAIN'] = 1
            self.timer['ramp'] = 5
            self.counter +=1
        if self.timer['ramp'] and self.counter ==4:
            return True 

##################################################
# STATES: Move spot positions on mirrors
##################################################


class PR3_SPOT_MOVE(GuardState):
    index = 920
    request = False
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        notify('Turn PRC1 ASC loop off for moving PRM - turn it on manually when you are done')
        notify('Move PRM alignment sliders to move the spot position on PR3')
        self.ref=ISC_library.alignRef()

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        notify('Turn PRC1 ASC loop off for moving PRM - turn it on manually when you are done')
        notify('Move PRM alignment sliders to move the spot position on PR3')
        ISC_library.pr3spotmove(self.ref)
        return True

class PR2_SPOT_MOVE(GuardState):
    request = False
    #@ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    #@ISC_library.assert_dof_locked_gen(['Full_IFO'])
    #@nodes.checker()
    #@ISC_library.unstall_nodes(nodes)
    def main(self):
        notify('Move PR3 alignment sliders to move the spot position on PR2')
        notify('Take care not to fall off any POP diodes')
        self.ref=ISC_library.alignRef()

    #@ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    #@ISC_library.assert_dof_locked_gen(['Full_IFO'])
    #@nodes.checker()
    #@ISC_library.unstall_nodes(nodes)
    def run(self):
        notify('Move PR3 alignment sliders to move the spot position on PR2')
        notify('Take care not to fall off any POP diodes')
        ISC_library.pr2spotmove(self.ref)
        return True

class PRM_SPOT_MOVE(GuardState):
    index = 890
    request = False
    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def main(self):
        notify('Move IM3 alignment sliders to move the spot position on PRM')
        notify('Take care not to fall off the POP QPDs')
        self.ref=ISC_library.alignRef()

    @ISC_library.get_subordinate_watchdog_check_decorator(nodes)
    @ISC_library.assert_dof_locked_gen(['Full_IFO'])
    @nodes.checker()
    @ISC_library.unstall_nodes(nodes)
    def run(self):
        notify('Move IM3 alignment sliders to move the spot position on PRM')
        notify('Take care not to fall off the POP QPDs')
        ISC_library.prmspotmove(self.ref)
        return True

##################################################
# EDGES
##################################################
def add_edge_weight(bool_arg, weight=5):
    """Based on some bool arg, add an edge weight
    An example use is to have a flag in lscparams
    that is True or False and will be changed between
    commissioning times and observing.
    """
    if bool_arg:
        return weight
    else:
        return 1

edges = [
    ('INIT',                        'DOWN'),
    ('DOWN',                        'PREP_FOR_LOCKING'),
    ('PREP_FOR_LOCKING',            'SDF_REVERT'), 
    ('SDF_REVERT',                  'READY'),
    ('PREP_FOR_LOCKING',            'CHECK_SDF', 5),
    ('CHECK_SDF',                   'READY'),
    ('PREP_FOR_LOCKING',            'IDLE'),
    ('IDLE',                        'PREP_FOR_LOCKING'),
    ('READY',                       'INITIAL_ALIGNMENT'),
    ('READY',                       'MANUAL_INITIAL_ALIGNMENT'),
    # ALS / green
    ('READY',                       'LOCKING_ARMS_GREEN', add_edge_weight(lscparams.manual_control)),  # This should allow easy transitions for manual control during heavy comm times
    ('LOCKING_ARMS_GREEN',          'LOCKING_ALS'),
    ('LOCKING_ARMS_GREEN',          'GREEN_ARMS_MANUAL'),
    ('GREEN_ARMS_MANUAL',           'LOCKING_ARMS_GREEN'),
    ('READY',                       'GREEN_ARMS_MANUAL', add_edge_weight(not lscparams.manual_control)),
    ('GREEN_ARMS_MANUAL',           'LOCKING_ALS'),
    ('LOCKING_ALS',                 'FIND_IR'),
    ('FIND_IR',                     'CHECK_IR'),
    ('CHECK_IR',                    'ARMS_OFF_RESONANCE'),
    #('ARMS_OFF_RESONANCE',          'CHECK_IR'),
    ('ARMS_OFF_RESONANCE',          'ALIGN_RECYCLING_MIRRORS'),
    # DRMI
    ('ALIGN_RECYCLING_MIRRORS',     'ACQUIRE_DRMI_1F'),
    ('ACQUIRE_DRMI_1F',             'DRMI_LOCKED_PREP_ASC'),
    ('DRMI_LOCKED_PREP_ASC',        'ENGAGE_DRMI_ASC'),
    ('ENGAGE_DRMI_ASC',             'TURN_ON_BS_STAGE2'),
    ('TURN_ON_BS_STAGE2',           'TRANSITION_DRMI_TO_3F'),
    #('ENGAGE_DRMI_ASC',             'TRANSITION_DRMI_TO_3F'), #SED added to avoid BS stage 2 glitches, August 9th 2020
    ('TRANSITION_DRMI_TO_3F',       'DRMI_LOCKED_CHECK_ASC'),
    ('DRMI_LOCKED_CHECK_ASC',       'OFFLOAD_DRMI_ASC'),
    ('OFFLOAD_DRMI_ASC',            'CHECK_AS_SHUTTERS'),
    ('LOCKLOSS_DRMI',               'ACQUIRE_DRMI_1F'),
    # PRMI / MICH checks
    ('LOCKLOSS_PRMI',               'ACQUIRE_PRMI'),
    ('ACQUIRE_DRMI_1F',             'ACQUIRE_PRMI'),
    ('ACQUIRE_DRMI_1F',             'CHECK_MICH_FRINGES'),
    ('CHECK_MICH_FRINGES',          'MICH_OFFLOADED'),
    ('MICH_OFFLOADED',              'ACQUIRE_DRMI_1F', 5),
    ('ACQUIRE_PRMI',                'CHECK_MICH_FRINGES', 5),
    ('CHECK_MICH_FRINGES',          'MICH_OFFLOADED'),
    ('MICH_OFFLOADED',              'ACQUIRE_PRMI'),
    ('ARMS_OFF_RESONANCE',          'CHECK_MICH_FRINGES', 5),
    #('ALIGN_RECYCLING_MIRRORS',     'ACQUIRE_PRMI'),
    ('ARMS_OFF_RESONANCE',          'ACQUIRE_PRMI', 5),
    ('ACQUIRE_PRMI',                'PRMI_LOCKED'),
    #('PRMI_LOCKED',                 'PRMI_TO_DRMI_TRANSITION'),
    ('PRMI_LOCKED',                 'PRMI_ASC'),
    ('PRMI_ASC',                    'PRMI_TO_DRMI_TRANSITION'),
    ('PRMI_TO_DRMI_TRANSITION',     'DRMI_LOCKED_PREP_ASC'),
    # CARM Reduction
    ('CHECK_AS_SHUTTERS',           'PREP_TR_CARM'),
    ('PREP_TR_CARM',                'START_TR_CARM'),
    ('START_TR_CARM',               'CARM_TO_TR'),
    ('CARM_TO_TR',                  'CARM_150_PICOMETERS'),
    ('CARM_150_PICOMETERS',         'DARM_TO_RF'),
    ('DARM_TO_RF',                  'DHARD_WFS'),
    ('DHARD_WFS',                   'IDLE_ALS'),
    ('IDLE_ALS',                    'CARM_OFFSET_REDUCTION', 10), #if you want to leave green locked (to reset green refs) change this weight to 1)
    ('DHARD_WFS',                   'PARK_ALS_VCO'),
    ('PARK_ALS_VCO',                'SHUTTER_ALS', 1),
    ('SHUTTER_ALS',                 'CARM_OFFSET_REDUCTION'),
    ('CARM_OFFSET_REDUCTION',       'CARM_5_PICOMETERS'),
    ('CARM_5_PICOMETERS',           'CARM_TO_REFL'),
    ('CARM_TO_REFL',                'RESONANCE'),
    ('RESONANCE',                   'CARM_TO_ANALOG'),
    ('CARM_TO_ANALOG',              'DRMI_TO_POP'),
    ('DRMI_TO_POP',                 'PREP_ASC_FOR_FULL_IFO'),
    #('DARM_OFFSET',                 'ENGAGE_RF_VIOLINS'),
    #ASC
    #('ENGAGE_RF_VIOLINS',           'PREP_ASC_FOR_FULL_IFO'),
    ('PREP_ASC_FOR_FULL_IFO',       'ENGAGE_ASC_FOR_FULL_IFO'),
    ('ENGAGE_ASC_FOR_FULL_IFO',     'DARM_OFFSET'),
    ('DARM_OFFSET',                 'TMS_SERVO'),
    ('TMS_SERVO',                   'ENGAGE_SOFT_LOOPS'),
    ('ENGAGE_SOFT_LOOPS',           'PREP_DC_READOUT_TRANSITION'),
    #('ENGAGE_SOFT_LOOPS',           'DARM_OFFSET'),
    #('DARM_OFFSET',                 'PREP_DC_READOUT_TRANSITION'),
     # DC readout
    ('PREP_DC_READOUT_TRANSITION',  'DARM_TO_DC_READOUT'),
    # Lownoise
    ('DARM_TO_DC_READOUT',          'DAMP_BOUNCE'),
    ('DAMP_BOUNCE',                 'CHECK_VIOLINS_BEFORE_POWERUP'),
    ('CHECK_VIOLINS_BEFORE_POWERUP','POWER_10W'),
    ('POWER_10W',                   'POWER_25W'),
    ('POWER_25W',                   'MOVE_SPOTS'),
    ('MOVE_SPOTS',                  'REDUCE_RF9_MODULATION_DEPTH'),
    ('REDUCE_RF9_MODULATION_DEPTH', 'REDUCE_RF45_MODULATION_DEPTH'),
    ('REDUCE_RF45_MODULATION_DEPTH','MAX_POWER'),
    ('MAX_POWER',                   'LOWNOISE_ASC'),
    ('MAX_POWER',                   'LOWNOISE_ASC'),
    ('MAX_POWER',                   'ADJUST_POWER'),
    ('ADJUST_POWER',                'LOWNOISE_ASC'),
    ('LOWNOISE_ASC',                'LOWNOISE_COIL_DRIVERS'),
    ('LOWNOISE_COIL_DRIVERS',       'LOWNOISE_ESD_ETMY', 5),
    ('LOWNOISE_ESD_ETMY',           'LOWNOISE_LENGTH_CONTROL'),
    ('LOWNOISE_ESD_ETMY',           'BACK_TO_ETMX'),
    ('BACK_TO_ETMX',                'LOWNOISE_LENGTH_CONTROL'),
    ('LOWNOISE_COIL_DRIVERS',       'TRANSITION_FROM_ETMX'),
    ('TRANSITION_FROM_ETMX',        'LOWNOISE_ESD_ETMX'),
    ('LOWNOISE_ESD_ETMX',           'LOWNOISE_LENGTH_CONTROL'),
    ('LOWNOISE_LENGTH_CONTROL',     'TURN_ON_CALIBRATION_LINES'),
    ('TURN_ON_CALIBRATION_LINES',   'INCREASE_DARM_OFFSET'),
    #('LOWNOISE_LENGTH_CONTROL',     'DAMP_VIOLINS_FULL_POWER'),
    ('INCREASE_DARM_OFFSET',        'DAMP_VIOLINS_FULL_POWER'),
    ('DAMP_VIOLINS_FULL_POWER',     'LASER_NOISE_SUPPRESSION'),
    # ('LOWNOISE_LENGTH_CONTROL',     'LASER_NOISE_SUPPRESSION'),
    #('LASER_NOISE_SUPPRESSION',     'NOMINAL_LOW_NOISE'),
    #('LASER_NOISE_SUPPRESSION',     'TURN_ON_SUS_DAC_DITHERS'), # Suggested location: after all actuation configs are settled and OMC_whitening is ON. JSK 2022-Nov-01
    #('TURN_ON_SUS_DAC_DITHERS',     'INJECT_SQUEEZING'),
    ('LASER_NOISE_SUPPRESSION',     'ADS_TO_CAMERAS'),
    ('ADS_TO_CAMERAS',              'INJECT_SQUEEZING'),
    #('LASER_NOISE_SUPPRESSION',     'CLOSE_BEAM_DIVERTERS'),
    ('INJECT_SQUEEZING',            'CLOSE_BEAM_DIVERTERS'),
    ('CLOSE_BEAM_DIVERTERS',        'OMC_WHITENING'),
    ('OMC_WHITENING',               'NOMINAL_LOW_NOISE'),
    ('NOMINAL_LOW_NOISE',           'NLN_CAL_MEAS'),
    ('NOMINAL_LOW_NOISE',           'NLN_ETMY'),
    ('NLN_ETMY',                    'NEW_DARM'),
    ('NEW_DARM',                    'RETURN_TO_NLN_ETMY'),
    ('RETURN_TO_NLN_ETMY',          'DARM_RECOVER'),
    ('NOMINAL_LOW_NOISE',           'SDF_TO_SAFE'),
    ('SDF_TO_SAFE',                 'NOMINAL_LOW_NOISE'),
    ('LOCKLOSS',                    'DOWN'),
]
