def main_loop(): for bw_mhz in bws: if set_bw(bw_mhz): return for if_ghz in ifs: lo2_mhz = if_ghz * 1e3 + 2500 if bw_mhz == 250: lo2_mhz += 125 if set_lo2(lo2_mhz): return agilent.set_hz_dbm(if_ghz * 1e9, dbms[0]) if if_setup(2, bw_mhz, if_ghz): # level only (won't actually set bw/lo2) return for dbm in dbms: agilent.set_dbm(dbm) time.sleep(0.05) # start IFTASK action transid = drama.obey("IFTASK@if-micro", "WRITE_TP2", FILE="NONE", ITIME=0.1) # get IFTASK reply msg = transid.wait(5) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.WRITE_TP2: %s', msg) return 1 sys.stdout.write('%.2f %g %g' % (dbm, if_ghz, bw_mhz)) for j, dcm in enumerate(dcms): sys.stdout.write(' %g' % (msg.arg['POWER%d' % (dcm)])) sys.stdout.write('\n') sys.stdout.flush()
def if_setup(adjust, bw_mhz, if_ghz): # LEVEL_ADJUST 0=setup_only, 1=setup_and_level, 2=level_only # BIT_MASK is DCMs to use: bit0=DCM0, bit1=DCM1, ... bit31=DCM31. setup_type = ['setup_only', 'setup_and_level', 'level_only'] logging.info('setup IFTASK, LEVEL_ADJUST %d: %s', adjust, setup_type[adjust]) bitmask = 0 for dcm in dcms: bitmask |= 1 << dcm # TODO configurable IF_FREQ? will 6 be default for both bands? msg = drama.obey('IFTASK@if-micro', 'TEST_SETUP', NASM_SET='R_CABIN', BAND_WIDTH=bw_mhz, QUAD_MODE=4, IF_FREQ=if_ghz, LEVEL_ADJUST=adjust, BIT_MASK=bitmask).wait(90) if msg.reason != drama.REA_COMPLETE or msg.status != 0: if msg.status == 261456746: # ACSISIF__ATTEN_ZERO logging.warning('low attenuator setting from IFTASK.TEST_SETUP') else: logging.error('bad reply from IFTASK.TEST_SETUP: %s', msg) return 1 return 0
def iftask_set_bw(bw_mhz): # this goes fast; probably doesn't do much. logging.info('set bandwidth %g MHz', bw_mhz) msg = drama.obey('IFTASK@if-micro', 'SET_DCM_BW', DCM=-1, MHZ=bw_mhz).wait(10) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.SET_DCM_BW: %s', msg) return 1 return 0
def iftask_set_lo2(lo2_mhz): # this can take ~20s, or ~40s if it needs to change the coax switches. logging.info('set lo2 freq %g MHz', lo2_mhz) msg = drama.obey('IFTASK@if-micro', 'SET_LO2_FREQ', LO2=-1, MHZ=lo2_mhz).wait(90) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.SET_LO2_FREQ: %s', msg) return 1 return 0
def iftask_get_tp2(dcms, itime=0.1): msg = drama.obey("IFTASK@if-micro", "WRITE_TP2", FILE="NONE", ITIME=itime).wait(5) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.WRITE_TP2: %s', msg) return None tps = [] for dcm in dcms: tps.append(msg.arg['POWER%d'%(dcm)]) return tps
def iftask_get_att(dcms): msg = drama.obey('IFTASK@if-micro', 'GET_DCM_ATTEN', DCM=-1).wait(5) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.GET_DCM_ATTEN: %s', msg) return None att = [] for dcm in dcms: att.append(msg.arg['ATTEN%d'%(dcm)]) return att
def iv(target, rows): if target == 'hot': p_index = hot_p_index else: p_index = sky_p_index load.move('b%d_%s' % (band, target)) if target == 'hot': cart.tune(lo_ghz, 0.0, skip_servo_pa=True) cart._set_pa([pas[0], pas[1]]) cart.update_all() if namakanui.util.iftask_setup(2, 1000, 6, dcms): # level only return 1 sys.stderr.write('%s: ' % (target)) sys.stderr.flush() mult = 1.0 if band == 6: mult = -1.0 cart._ramp_sis_bias_voltages( [mult * mvs[0], mvs[0], mult * mvs[0], mvs[0]]) for i, mv in enumerate(mvs): if (i + 1) % 20 == 0: sys.stderr.write('%.2f%% ' % (0.0 + 50 * i / len(mvs))) sys.stderr.flush() cart.update_all() # for anyone monitoring for po in range(2): cart.femc.set_sis_voltage(cart.ca, po, 0, mult * mv) cart.femc.set_sis_voltage(cart.ca, po, 1, mv) rows[i][mv_index] = mv # start IFTASK action while we average the mixer current readings transid = drama.obey("IFTASK@if-micro", "WRITE_TP2", FILE="NONE", ITIME=0.1) # TODO: separate hot/cold mixer currents, or only calc hot for j in range(ua_n): for po in range(2): for sb in range(2): ua = cart.femc.get_sis_current(cart.ca, po, sb) * 1e3 rows[i][ua_avg_index + po * 2 + sb] += abs( ua) # for band 6 rows[i][ua_dev_index + po * 2 + sb] += ua * ua # get IFTASK reply msg = transid.wait(5) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.WRITE_TP2: %s', msg) return 1 for j, dcm in enumerate(dcms): rows[i][p_index + j] = msg.arg['POWER%d' % (dcm)] sys.stderr.write('\n') sys.stderr.flush() return 0
def loop(): global i, prev_powers while i < 23: time.sleep(1) i += 1 if i < 0: sys.stderr.write('.') else: sys.stderr.write('%d ' % (i)) sys.stderr.flush() if i % 5 == 4: # retune the cart; make sure it loses the lock dbm = agilent.state['dbm'] while dbm > agilent.safe_dbm and not cart.state['pll_unlock']: agilent.set_dbm(dbm) cart.update_all() dbm -= 0.1 dbm = agilent.safe_dbm agilent.set_dbm(dbm) agilent.set_output(0) time.sleep(0.05) agilent.set_output(1) agilent.set_dbm(orig_dbm) time.sleep(0.05) cart.tune(lo_ghz, 0.0) time.sleep(0.05) transid = drama.obey("IFTASK@if-micro", "WRITE_TP2", FILE="NONE", ITIME=0.1) cart.update_all() msg = transid.wait(5) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.WRITE_TP2: %s', msg) return if cart.state['pll_unlock']: logging.error('failed to tune') return powers = [] for dcm in dcms: powers.append(msg.arg['POWER%d' % (dcm)]) for j, (prev, curr) in enumerate(zip(prev_powers, powers)): pdiff = abs((prev - curr) / min(prev, curr)) * 100.0 if pdiff > 1.5: logging.info('%.2f%% jump in DCM %d', pdiff, dcms[j]) # let's write to the output file too, might come in handy sys.stdout.write('# jump DCM %d, %.2f%%\n' % (dcms[j], pdiff)) if i < 0: i = 0 # collect a bit more data, then quit prev_powers = powers output(powers)
def print_dbm(i, dbm): #logging.info('%d dbm %.2f', i, dbm) transid = drama.obey("IFTASK@if-micro", "WRITE_TP2", FILE="NONE", ITIME=0.1) cart.update_all() logging.info('%d dbm %.2f, pll_if %.3f', i, dbm, cart.state['pll_if_power']) msg = transid.wait(5) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.WRITE_TP2: %s', msg) return False if cart.state['pll_unlock']: return False sys.stdout.write('%.2f %.3f'%(dbm, cart.state['pll_if_power'])) for dcm in dcms: sys.stdout.write(' %.3f'%(msg.arg['POWER%d'%(dcm)])) sys.stdout.write('\n') sys.stdout.flush() return True
def ip(target, rows, pas): if target == 'hot': p_index = hot_p_index else: p_index = sky_p_index load.move('b%d_%s' % (band, target)) sys.stderr.write('%s: ' % (target)) sys.stderr.flush() for i, pa in enumerate(pas): if (i + 1) % 20 == 0: sys.stderr.write('%.2f%% ' % (100.0 * i / len(pas))) sys.stderr.flush() cart.update_all() cart._set_pa([pa, pa]) rows[i][pa_index] = pa # start IFTASK action while we average the mixer current readings transid = drama.obey("IFTASK@if-micro", "WRITE_TP2", FILE="NONE", ITIME=0.1) for j in range(ua_n): for po in range(2): for sb in range(2): ua = cart.femc.get_sis_current(cart.ca, po, sb) * 1e3 rows[i][ua_avg_index + po * 2 + sb] += abs( ua) # for band 6 rows[i][ua_dev_index + po * 2 + sb] += ua * ua # get IFTASK reply msg = transid.wait(5) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.WRITE_TP2: %s', msg) return 1 for j, dcm in enumerate(dcm_0 + dcm_1): rows[i][p_index + j] = msg.arg['POWER%d' % (dcm)] sys.stderr.write('\n') sys.stderr.flush() return 0
def main_loop(): random.seed() lock_only = 1 sys.stderr.write('starting %d iters\n' % (args.iters)) sys.stderr.flush() for i in range(args.iters): sys.stderr.write('%d ' % (i)) sys.stderr.flush() if i == args.iters // 2: lock_only = 0 sys.stderr.write('\nhalfway\n') sys.stderr.flush() lo_ghz = random.uniform(args.lo_ghz - args.off_ghz, args.lo_ghz + args.off_ghz) if not util.tune(cart, agilent, photonics, lo_ghz, lock_only=lock_only): logging.error('failed to tune to %.6f ghz', lo_ghz) return transid = drama.obey("IFTASK@if-micro", "WRITE_TP2", FILE="NONE", ITIME=0.1) cart.update_all() msg = transid.wait(5) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.WRITE_TP2: %s', msg) return if cart.state['pll_unlock']: logging.error('lost lock at %.6f GHz', lo_ghz) return sys.stdout.write('%.6f %d' % (lo_ghz, lock_only)) for key in state_keys: if key not in cart.state: key, sep, index = key.rpartition('_') index = int(index) sys.stdout.write(' %s' % (cart.state[key][index])) else: sys.stdout.write(' %s' % (cart.state[key])) for dcm in dcms: sys.stdout.write(' %.5f' % (msg.arg['POWER%d' % (dcm)])) sys.stdout.write('\n') sys.stdout.flush()
def CART_POWER(msg): '''Power a cartridge on or off. Arguments: BAND: One of 3,6,7 ENABLE: Can be 1/0, on/off, true/false ''' log.debug('CART_POWER(%s)', msg.arg) if not initialised: raise drama.BadStatus(drama.APP_ERROR, 'task needs INITIALISE') args, kwargs = drama.parse_argument(msg.arg) band, enable = cart_power_args(*args, **kwargs) if band not in [3, 6, 7]: raise drama.BadStatus(drama.INVARG, 'BAND %d not one of [3,6,7]' % (band)) cartname = cartridge_tasknames[band] onoff = ['off', 'on'][enable] log.info('band %d powering %s...', band, onoff) msg = drama.obey(cartname, 'POWER', enable).wait() if msg.status != 0: raise drama.BadStatus(msg.status, '%s POWER %s failed' % (cartname, onoff)) log.info('band %d powered %s.', band, ['off', 'on'][enable])
def INITIALISE(msg): ''' Start the cartridge tasks and initialise them, then initialise the local control classes. Arguments: INITIALISE: The ini file path SIMULATE: Bitmask. If given, overrides config file settings. ''' global initialised, inifile, agilent, cryo, load, ifswitch, photonics global cartridge_tasknames, cold_mult, warm_mult log.debug('INITIALISE(%s)', msg.arg) args, kwargs = drama.parse_argument(msg.arg) initialised = False if 'INITIALISE' in kwargs: inifile = kwargs['INITIALISE'] if not inifile: raise drama.BadStatus(drama.INVARG, 'missing argument INITIALISE, .ini file path') simulate = None if 'SIMULATE' in kwargs: simulate = int(kwargs['SIMULATE']) config = IncludeParser(inifile) nconfig = config['namakanui'] cartridge_tasknames[3] = nconfig['b3_taskname'] cartridge_tasknames[6] = nconfig['b6_taskname'] cartridge_tasknames[7] = nconfig['b7_taskname'] # export these so the frontend task doesn't have to guess drama.set_param('TASKNAMES', {'B%d' % (k): v for k, v in cartridge_tasknames.items()}) # start the cartridge tasks in the background. # will exit immediately if already running, which is fine. log.info('starting cartridge tasks') subprocess.Popen([binpath + 'cartridge_task.py', cartridge_tasknames[3]]) subprocess.Popen([binpath + 'cartridge_task.py', cartridge_tasknames[6]]) subprocess.Popen([binpath + 'cartridge_task.py', cartridge_tasknames[7]]) # kill the UPDATE action while we fire things up try: drama.kick(taskname, "UPDATE").wait() except drama.DramaException: pass # kludge: sleep a short time to let cartridge tasks run up log.info('sleeping 3s for cartridge task startup') drama.wait(3) # TODO: do the ini file names really need to be configurable? # probably a bit overkill. cart_kwargs = {} if simulate is not None: cart_kwargs["SIMULATE"] = simulate for band in [3, 6, 7]: task = cartridge_tasknames[band] ini = datapath + nconfig['b%d_ini' % (band)] log.info('initialising %s', task) msg = drama.obey(task, "INITIALISE", BAND=band, INITIALISE=ini, **cart_kwargs).wait() if msg.status != 0: raise drama.BadStatus(msg.status, task + ' INITIALISE failed') # setting agilent frequency requires warm/cold multipliers for each band. # TODO: this assumes pubname=DYN_STATE -- could instead [include] config. # also this is rather a large get() for just a couple values. for band in [3, 6, 7]: dyn_state = drama.get(cartridge_tasknames[band], "DYN_STATE").wait().arg["DYN_STATE"] cold_mult[band] = dyn_state['cold_mult'] warm_mult[band] = dyn_state['warm_mult'] # now reinstantiate the local stuff if load is not None: load.close() del agilent del cryo del load del ifswitch del photonics agilent = None cryo = None load = None ifswitch = None photonics = None gc.collect() agilent = namakanui.agilent.Agilent(datapath + nconfig['agilent_ini'], drama.wait, drama.set_param, simulate) cryo = namakanui.cryo.Cryo(datapath + nconfig['cryo_ini'], drama.wait, drama.set_param, simulate) # wait a moment for load, jcms4 is fussy about reconnects drama.wait(1) load = namakanui.load.Load(datapath + nconfig['load_ini'], drama.wait, drama.set_param, simulate) ifswitch = namakanui.ifswitch.IFSwitch(datapath + nconfig['ifswitch_ini'], drama.wait, drama.set_param, simulate) if 'photonics_ini' in nconfig: photonics = namakanui.photonics.Photonics( datapath + nconfig['photonics_ini'], drama.wait, drama.set_param, simulate) # publish the load.positions table for the GUI drama.set_param('LOAD_TABLE', load.positions) # rebuild the simulate bitmask from what was actually set simulate = agilent.simulate | cryo.simulate | load.simulate | ifswitch.simulate | ( photonics.simulate if photonics else 0) for band in [3, 6, 7]: task = cartridge_tasknames[band] simulate |= drama.get(task, 'SIMULATE').wait(5).arg['SIMULATE'] drama.set_param('SIMULATE', simulate) # restart the update loop drama.blind_obey(taskname, "UPDATE") # TODO: power up the cartridges? tune? leave it for the FE wrapper? initialised = True log.info('initialised.')
def configure(msg, wait_set, done_set): ''' Callback for the CONFIGURE action. ''' log.debug('configure: msg=%s, wait_set=%s, done_set=%s', msg, wait_set, done_set) global g_sideband, g_rest_freq, g_center_freq, g_doppler global g_freq_mult, g_freq_off_scale global g_mech_tuning, g_elec_tuning, g_group, g_esma_mode if msg.reason == drama.REA_OBEY: config = drama.get_param('CONFIGURATION') if log.isEnabledFor(logging.DEBUG): # expensive formatting log.debug("CONFIGURATION:\n%s", pprint.pformat(config)) config = config['OCS_CONFIG'] fe = config['FRONTEND_CONFIG'] init = drama.get_param('INITIALISE') init = init['frontend_init'] inst = init['INSTRUMENT'] # init/config must have same number/order of receptors for i, (ir, cr) in enumerate(zip(inst['receptor'], fe['RECEPTOR_MASK'])): iid = ir['id'] cid = cr['RECEPTOR_ID'] if iid != cid: raise drama.BadStatus( WRAP__WRONG_RECEPTOR_IN_CONFIGURE, f'configure RECEPTOR_MASK[{i}].RECEPTOR_ID={cid} but initialise receptor[{i}].id={iid}' ) ival = ir['health'] cval = cr['VALUE'] if cval == 'NEED' and ival != 'ON': raise drama.BadStatus( WRAP__NEED_BAD_RECEPTOR, f'{cid}: configure RECEPTOR_MASK.VALUE={cval} but initialise receptor.health={ival}' ) if cval == 'ON' and ival == 'OFF': raise drama.BadStatus( WRAP__ON_RECEPTOR_IS_OFF, f'{cid}: configure RECEPTOR_MASK.VALUE={cval} but initialise receptor.health={ival}' ) if cval == 'OFF': g_state['RECEPTOR_VAL%d' % (i + 1)] = 'OFF' else: g_state['RECEPTOR_VAL%d' % (i + 1)] = ival g_sideband = fe['SIDEBAND'] if g_sideband not in ['USB', 'LSB']: raise drama.BadStatus(WRAP__UNKNOWN_SIDEBAND, f'SIDEBAND={g_sideband}') g_rest_freq = float(fe['REST_FREQUENCY']) g_freq_mult = {'USB': 1.0, 'LSB': -1.0}[g_sideband] # use IF_CENTER_FREQ from config file instead of initialise #g_center_freq = float(inst['IF_CENTER_FREQ']) g_center_freq = float(config['INSTRUMENT']['IF_CENTER_FREQ']) g_freq_off_scale = float(fe['FREQ_OFF_SCALE']) # MHz dtrack = fe['DOPPLER_TRACK'] g_mech_tuning = dtrack['MECH_TUNING'] g_elec_tuning = dtrack['ELEC_TUNING'] drama.set_param( 'MECH_TUNE', numpy.int32({ 'NEVER': 0, 'NONE': 0 }.get(g_mech_tuning, 1))) drama.set_param( 'ELEC_TUNE', numpy.int32({ 'NEVER': 0, 'NONE': 0 }.get(g_elec_tuning, 1))) drama.set_param('REST_FREQUENCY', g_rest_freq) drama.set_param('SIDEBAND', g_sideband) drama.set_param('SB_MODE', fe['SB_MODE']) #drama.set_param('SB_MODE', 'SSB') # debug # RMB 20200701: tune here even for CONTINUOUS/DISCRETE, # since the DCMs probably level themselves in CONFIGURE. og = ['ONCE', 'GROUP', 'CONTINUOUS', 'DISCRETE'] if g_esma_mode: configure.tune = False wait_set.add(ANTENNA_TASK) elif g_mech_tuning in og or g_elec_tuning in og: configure.tune = True wait_set.add(ANTENNA_TASK) else: configure.tune = False drama.cache_path(ANTENNA_TASK) # we can save a bit of time by moving the load to AMBIENT # while waiting for the ANTENNA_TASK to supply the doppler value. # TODO: is this really necessary? if configure.tune: pos = f'b{g_band}_hot' g_state['LOAD'] = '' # TODO unknown/invalid value; drama.set_param log.info('moving load to %s...', pos) configure.load_tid = drama.obey(NAMAKANUI_TASK, 'LOAD_MOVE', pos) configure.load_target = f'obey({NAMAKANUI_TASK},LOAD_MOVE,{pos})' configure.load_timeout = time.time() + 30 drama.reschedule(configure.load_timeout) return else: configure.load_tid = None # obey elif msg.reason == drama.REA_RESCHED: # load must have timed out raise drama.BadStatus(drama.APP_TIMEOUT, f'Timeout waiting for {configure.load_target}') elif configure.load_tid is not None: if msg.transid != configure.load_tid: drama.reschedule(configure.load_timeout) return elif msg.reason != drama.REA_COMPLETE: raise drama.BadStatus( drama.UNEXPMSG, f'Unexpected reply to {configure.load_target}: {msg}') elif msg.status != 0: raise drama.BadStatus(msg.status, f'Bad status from {configure.load_target}') g_state['LOAD'] = 'AMBIENT' drama.set_param('LOAD', g_state['LOAD']) configure.load_tid = None # TODO: figure out how to generalize this pattern to more obeys. if ANTENNA_TASK not in wait_set and ANTENNA_TASK not in done_set: done_set.add(ANTENNA_TASK) # once only g_doppler = 1.0 elif ANTENNA_TASK in wait_set and ANTENNA_TASK in done_set: wait_set.remove(ANTENNA_TASK) # once only msg = drama.get(ANTENNA_TASK, 'RV_BASE').wait(5) check_message(msg, f'get({ANTENNA_TASK},RV_BASE)') rv_base = msg.arg['RV_BASE'] g_doppler = float(rv_base['DOPPLER']) else: # this is okay to wait on a single task... return g_state['DOPPLER'] = g_doppler if configure.tune: # tune the receiver. # TODO: target control voltage for fast frequency switching. lo_freq = g_doppler * g_rest_freq - g_center_freq * g_freq_mult voltage = 0.0 g_state['LOCKED'] = 'NO' drama.set_param('LOCK_STATUS', numpy.int32(0)) log.info('tuning receiver LO to %.9f GHz, %.3f V...', lo_freq, voltage) msg = drama.obey(NAMAKANUI_TASK, 'CART_TUNE', g_band, lo_freq, voltage).wait(30) check_message( msg, f'obey({NAMAKANUI_TASK},CART_TUNE,{g_band},{lo_freq},{voltage})') g_state['LOCKED'] = 'YES' drama.set_param('LOCK_STATUS', numpy.int32(1)) else: # ask the receiver for its current status. # NOTE: no lo_ghz, or being unlocked, is not necessarily an error here. msg = drama.get(CART_TASK, 'DYN_STATE').wait(3) check_message(msg, f'get({CART_TASK},DYN_STATE)') dyn_state = msg.arg['DYN_STATE'] lo_freq = dyn_state['lo_ghz'] locked = int(not dyn_state['pll_unlock']) g_state['LOCKED'] = ['NO', 'YES'][locked] drama.set_param('LOCK_STATUS', numpy.int32(locked)) g_state['LO_FREQUENCY'] = lo_freq drama.set_param('LO_FREQ', lo_freq) # TODO: remove, we don't have a cold load t_cold = interpolate_t_cold(lo_freq) or g_state['TEMP_LOAD2'] g_state['TEMP_LOAD2'] = t_cold # TODO: do something with g_group? # it seems silly to potentially retune immediately in SETUP_SEQUENCE. log.info('configure done.')
def setup_sequence(msg, wait_set, done_set): ''' Callback for SETUP_SEQUENCE action. ''' log.debug('setup_sequence: msg=%s, wait_set=%s, done_set=%s', msg, wait_set, done_set) global g_sideband, g_rest_freq, g_center_freq, g_doppler global g_freq_mult, g_freq_off_scale global g_mech_tuning, g_elec_tuning, g_group, g_esma_mode if msg.reason == drama.REA_OBEY: # TODO these can probably be skipped init = drama.get_param('INITIALISE') init = init['frontend_init'] cal = init['CALIBRATION'] t_hot = float(cal['T_HOT']) t_cold = float(cal['T_COLD']) # TODO fast frequency switching state_table_name = msg.arg.get('FE_STATE', g_state['FE_STATE']) set_state_table(state_table_name) g_state['FE_STATE'] = state_table_name st = drama.get_param('MY_STATE_TABLE') state_index_size = int(st['size']) fe_state = st['FE_state'] if not isinstance(fe_state, dict): fe_state = fe_state[0] offset = float(fe_state['offset']) # for now, slow offset only if st['name'].startswith('OFFSET'): g_state['FREQ_OFFSET'] = offset else: g_state['FREQ_OFFSET'] = 0.0 setup_sequence.tune = False new_group = msg.arg.get('GROUP', g_group) if new_group != g_group and 'GROUP' in [g_mech_tuning, g_elec_tuning]: setup_sequence.tune = True g_group = new_group cd = ['CONTINUOUS', 'DISCRETE'] if g_mech_tuning in cd or g_elec_tuning in cd: setup_sequence.tune = True if g_esma_mode: setup_sequence.tune = False # if PTCS is in TASKS, get updated DOPPLER value -- # regardless of whether or not we're tuning the receiver. if ANTENNA_TASK in drama.get_param("TASKS").split(): wait_set.add(ANTENNA_TASK) else: drama.cache_path(ANTENNA_TASK) # shouldn't actually need this here # save time by moving load while waiting on doppler from ANTENNA_TASK load = msg.arg.get('LOAD', g_state['LOAD']) if load == 'LOAD2': # TODO: should this raise drama.BadStatus instead? # NOTE: LOAD in SDP/STATE still remains "LOAD2", # since otherwise the reducers will hang forever # waiting on LOAD2 data. log.warning('no LOAD2, setting to SKY instead') pos = 'b%d_sky' % (g_band) else: pos = 'b%d_%s' % (g_band, {'AMBIENT': 'hot', 'SKY': 'sky'}[load]) g_state['LOAD'] = '' # TODO unknown/invalid value log.info('moving load to %s...', pos) setup_sequence.load = load setup_sequence.load_tid = drama.obey(NAMAKANUI_TASK, 'LOAD_MOVE', pos) setup_sequence.load_target = f'obey({NAMAKANUI_TASK},LOAD_MOVE,{pos})' setup_sequence.load_timeout = time.time() + 30 drama.reschedule(setup_sequence.load_timeout) return {'MULT': numpy.int32(state_index_size)} # for JOS_MULT # obey elif msg.reason == drama.REA_RESCHED: # load must have timed out raise drama.BadStatus( drama.APP_TIMEOUT, f'Timeout waiting for {setup_sequence.load_target}') elif setup_sequence.load_tid is not None: if msg.transid != setup_sequence.load_tid: drama.reschedule(setup_sequence.load_timeout) return elif msg.reason != drama.REA_COMPLETE: raise drama.BadStatus( drama.UNEXPMSG, f'Unexpected reply to {setup_sequence.load_target}: {msg}') elif msg.status != 0: raise drama.BadStatus( msg.status, f'Bad status from {setup_sequence.load_target}') g_state['LOAD'] = setup_sequence.load # sdp should already hold the desired value, so no set_param here setup_sequence.load_tid = None if ANTENNA_TASK not in wait_set and ANTENNA_TASK not in done_set: done_set.add(ANTENNA_TASK) # once only #g_doppler = 1.0 # RMB 20200720: hold doppler at previous value elif ANTENNA_TASK in wait_set and ANTENNA_TASK in done_set: wait_set.remove(ANTENNA_TASK) # once only msg = drama.get(ANTENNA_TASK, 'RV_BASE').wait(5) check_message(msg, f'get({ANTENNA_TASK},RV_BASE)') rv_base = msg.arg['RV_BASE'] g_doppler = float(rv_base['DOPPLER']) else: # this is okay to wait on a single task... return g_state['DOPPLER'] = g_doppler if setup_sequence.tune: # tune the receiver. # TODO: target control voltage for fast frequency switching. lo_freq = (g_doppler * g_rest_freq) - (g_center_freq * g_freq_mult) + ( g_freq_off_scale * g_state['FREQ_OFFSET'] * 1e-3) voltage = 0.0 g_state['LOCKED'] = 'NO' drama.set_param('LOCK_STATUS', numpy.int32(0)) log.info('tuning receiver LO to %.9f GHz, %.3f V...', lo_freq, voltage) # RMB 20200714: try to hold output power steady while doppler tracking # by keeping the same tuning params (bias voltage, PA, etc). msg = drama.obey(NAMAKANUI_TASK, 'CART_TUNE', g_band, lo_freq, voltage, LOCK_ONLY=True).wait(30) check_message( msg, f'obey({NAMAKANUI_TASK},CART_TUNE,{g_band},{lo_freq},{voltage})') g_state['LO_FREQUENCY'] = lo_freq drama.set_param('LO_FREQ', lo_freq) g_state['LOCKED'] = 'YES' drama.set_param('LOCK_STATUS', numpy.int32(1)) else: # make sure rx is tuned and locked. better to fail here than in SEQUENCE. msg = drama.get(CART_TASK, 'DYN_STATE').wait(3) check_message(msg, f'get({CART_TASK},DYN_STATE)') dyn_state = msg.arg['DYN_STATE'] lo_freq = dyn_state['lo_ghz'] locked = int(not dyn_state['pll_unlock']) g_state['LO_FREQUENCY'] = lo_freq drama.set_param('LO_FREQ', lo_freq) g_state['LOCKED'] = ['NO', 'YES'][locked] drama.set_param('LOCK_STATUS', numpy.int32(locked)) if not lo_freq or not locked: raise drama.BadStatus(WRAP__RXNOTLOCKED, 'receiver unlocked in setup_sequence') # TODO: remove, we don't have a cold load t_cold = interpolate_t_cold(lo_freq) or g_state['TEMP_LOAD2'] g_state['TEMP_LOAD2'] = t_cold # TODO: get TEMP_TSPILL from NAMAKANUI and/or ENVIRO msg = drama.get(NAMAKANUI_TASK, 'LAKESHORE').wait(5) check_message(msg, f'get({NAMAKANUI_TASK},LAKESHORE)') g_state['TEMP_AMBIENT'] = msg.arg['LAKESHORE']['temp5'] log.info('setup_sequence done.')
def sequence_frame(frame): ''' Callback for every frame structure in RTS.STATE (endInt). Modify the passed frame in-place or return a dict to publish. Note that we assume a single-element STATE structure, so we just copy g_state into every frame. Even in batch mode this would still be correct, but excessive. TODO: How do we support fast frequency switching? The passed-in frame actually provides the following: NUMBER TAI_START TAI_END LAST_INTEG So we could keep g_state as a timeseries, and figure out the appropriate value for each frame. Some frames would have LOCKED=NO while the receiver tunes. Could probably support batch processing for free in that case, though I'd need to double-check the RTS/ACSIS code for how the feed-forward works -- does it only interpolate from the first frame, or from the last complete frame it saw? LAST_FREQ wouldn't necessarily be accurate either; there could be a few more frames on the current frequency depending on lag. Do we still need the LAST_FREQ field? Who uses it and how? ''' log.debug('sequence_frame: frame=%s', frame) # could compare this to start/end for first/last integration handling sequence.step_counter = frame['NUMBER'] # update frame with values that were used for this integration frame.update(g_state) # if state will change next frame, set up for it here. mst = drama.get_param('MY_STATE_TABLE') fe_state = mst['FE_state'] if isinstance(fe_state, dict): fe_state = [fe_state] old_sti = sequence.state_table_index sequence.dwell_counter += 1 if sequence.dwell_counter == sequence.dwell: sequence.dwell_counter = 0 sequence.state_table_index = (sequence.state_table_index + 1) % len(fe_state) # set LAST_FREQ if this was the last frame at this state table index if old_sti != sequence.state_table_index: frame['LAST_FREQ'] = numpy.int32(1) else: frame['LAST_FREQ'] = numpy.int32(0) # no tuning (fast frequency switching) in ESMA_MODE global g_esma_mode if g_esma_mode: return frame # skip the rest of this since fast frequency switching isn't supported yet. return frame if old_sti != sequence.state_table_index: # TODO: if we're tuning anyway, should we update doppler first? offset = float(fe_state[sequence.state_table_index]['offset']) g_state['FREQ_OFFSET'] = offset lo_freq = (g_doppler * g_rest_freq) - (g_center_freq * g_freq_mult) + ( g_freq_off_scale * offset * 1e-3) # TODO: target control voltage for fast frequency switching. voltage = 0.0 g_state['LOCKED'] = 'NO' drama.set_param('LOCK_STATUS', numpy.int32(0)) drama.set_param('LO_FREQ', lo_freq) log.info('tuning receiver LO to %.9f GHz, %.3f V...', lo_freq, voltage) msg = drama.obey(NAMAKANUI_TASK, 'CART_TUNE', g_band, lo_freq, voltage).wait(30) check_message( msg, f'obey({NAMAKANUI_TASK},CART_TUNE,{g_band},{lo_freq},{voltage})') g_state['LO_FREQUENCY'] = lo_freq g_state['LOCKED'] = 'YES' drama.set_param('LOCK_STATUS', numpy.int32(1)) # TODO: remove, we don't have a cold load t_cold = interpolate_t_cold(lo_freq) or g_state['TEMP_LOAD2'] g_state['TEMP_LOAD2'] = t_cold
def iv(target, rows, pa): if target == 'hot': p_index = hot_p_index else: p_index = sky_p_index load.move('b%d_%s' % (band, target)) # TODO Maybe it's wrong to relevel for each PA; it makes it harder # to compare power between PAs if the leveling is slightly different. # Ambient temperature shouldn't be changing much compared to the # difference between hot load and sky, either. # at the start of a HOT row, re-tune and re-level the power meters # at the nominal values. do not relevel on SKY or y-factor won't work. # actually re-leveling makes it difficult to compare power levels # across sweeps, so skip it. retuning is fine though. # 20200221 but ACTUALLY we're having problems with saturating power levels, # so DO relevel the detectors here. we won't be able to see # relative power levels, but we mostly only do 2 PAs these days and care # more about Y-factor values anyway. if target == 'hot': # dbm/att should already be set from namakanui.util.tune cart.tune(lo_ghz, 0.0, skip_servo_pa=True) cart._set_pa([pa, pa]) cart.update_all() if namakanui.util.iftask_setup(2, 1000, 6, dcms): # level only return 1 sys.stderr.write('%s: ' % (target)) sys.stderr.flush() # NOTE: The two SIS mixers in each polarization module are not really USB and LSB. # Rather the input to one is phase-shifted relative to the other, and their # signals are then combined to produce USB and LSB outputs. So to check the # power output and Y-factor from each mixer individually, the other one needs # to have its output disabled by setting its bias voltage to zero. # Since we still need to smoothly ramp SIS bias voltage for large changes, # we therefore do two separate loops for sis1 and sis2. # TODO: Once we have the mixers optimized individually, we might still need # to optimize their combined outputs. This will require a 2D scan of # mixer bias voltage for each PA setting. # sis1 sb = 0 mult = 1.0 if band == 6: mult = -1.0 if args.zero: cart._ramp_sis_bias_voltages([mult * mvs[0], 0.0, mult * mvs[0], 0.0]) else: cart._ramp_sis_bias_voltages( [mult * mvs[0], nom_v[1], mult * mvs[0], nom_v[3]]) for i, mv in enumerate(mvs): if (i + 1) % 20 == 0: sys.stderr.write('%.2f%% ' % (0.0 + 50 * i / len(mvs))) sys.stderr.flush() cart.update_all() # for anyone monitoring for po in range(2): cart.femc.set_sis_voltage(cart.ca, po, sb, mult * mv) rows[i][mv_index] = mv # start IFTASK action while we average the mixer current readings transid = drama.obey("IFTASK@if-micro", "WRITE_TP2", FILE="NONE", ITIME=0.1) for j in range(ua_n): for po in range(2): ua = cart.femc.get_sis_current(cart.ca, po, sb) * 1e3 rows[i][ua_avg_index + po * 2 + sb] += abs(ua) # for band 6 rows[i][ua_dev_index + po * 2 + sb] += ua * ua # get IFTASK reply msg = transid.wait(5) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.WRITE_TP2: %s', msg) return 1 for j, dcm in enumerate(dcm_0): rows[i][p_index + j + 0] = msg.arg['POWER%d' % (dcm)] for j, dcm in enumerate(dcm_1): rows[i][p_index + j + 16] = msg.arg['POWER%d' % (dcm)] # sis2 sb = 1 if args.zero: cart._ramp_sis_bias_voltages([0.0, mvs[0], 0.0, mvs[0]]) else: cart._ramp_sis_bias_voltages([nom_v[0], mvs[0], nom_v[2], mvs[0]]) for i, mv in enumerate(mvs): if (i + 1) % 20 == 0: sys.stderr.write('%.2f%% ' % (50.0 + 50 * i / len(mvs))) sys.stderr.flush() cart.update_all() # for anyone monitoring for po in range(2): cart.femc.set_sis_voltage(cart.ca, po, sb, mv) rows[i][mv_index] = mv # start IFTASK action while we average the mixer current readings transid = drama.obey("IFTASK@if-micro", "WRITE_TP2", FILE="NONE", ITIME=0.1) for j in range(ua_n): for po in range(2): ua = cart.femc.get_sis_current(cart.ca, po, sb) * 1e3 rows[i][ua_avg_index + po * 2 + sb] += ua rows[i][ua_dev_index + po * 2 + sb] += ua * ua # get IFTASK reply msg = transid.wait(5) if msg.reason != drama.REA_COMPLETE or msg.status != 0: logging.error('bad reply from IFTASK.WRITE_TP2: %s', msg) return 1 for j, dcm in enumerate(dcm_0): rows[i][p_index + j + 8] = msg.arg['POWER%d' % (dcm)] for j, dcm in enumerate(dcm_1): rows[i][p_index + j + 24] = msg.arg['POWER%d' % (dcm)] sys.stderr.write('\n') sys.stderr.flush() return 0
def CART_TUNE(msg): '''Tune a cartridge, after setting reference frequency. Arguments: BAND: One of 3,6,7 LO_GHZ: Local oscillator frequency in gigahertz VOLTAGE: Desired PLL control voltage, [-10,10]. If not given, voltage will not be adjusted following the initial lock. LOCK_ONLY: if True, bias voltage, PA, LNA, and magnets will not be adjusted after locking the receiver. TODO: lock polarity (below or above reference) could be a parameter. for now we just read back from the cartridge task. TODO: save dbm offset and use it for close frequencies. ''' log.debug('CART_TUNE(%s)', msg.arg) if not initialised: raise drama.BadStatus(drama.APP_ERROR, 'task needs INITIALISE') args, kwargs = drama.parse_argument(msg.arg) band, lo_ghz, voltage, lock_only = cart_tune_args(*args, **kwargs) if band not in [3, 6, 7]: raise drama.BadStatus(drama.INVARG, 'BAND %d not one of [3,6,7]' % (band)) if not 70 <= lo_ghz <= 400: # TODO be more specific raise drama.BadStatus(drama.INVARG, 'LO_GHZ %g not in [70,400]' % (lo_ghz)) if voltage and not -10 <= voltage <= 10: raise drama.BadStatus(drama.INVARG, 'VOLTAGE %g not in [-10,10]' % (voltage)) if ifswitch.get_band() != band: log.info('setting IF switch to band %d', band) # reduce power first agilent.set_dbm(agilent.safe_dbm) if photonics: photonics.set_attenuation(photonics.max_att) ifswitch.set_band(band) cartname = cartridge_tasknames[band] # TODO don't assume pubname is DYN_STATE dyn_state = drama.get(cartname, "DYN_STATE").wait().arg["DYN_STATE"] lock_polarity = dyn_state['pll_sb_lock'] # 0=below_ref, 1=above_ref lock_polarity = -2.0 * lock_polarity + 1.0 fyig = lo_ghz / (cold_mult[band] * warm_mult[band]) fsig = (fyig * warm_mult[band] + agilent.floog * lock_polarity) / agilent.harmonic if photonics: dbm = agilent.interp_dbm(0, fsig) att = photonics.interp_attenuation(band, lo_ghz) log.info('setting photonics attenuator to %d counts', att) else: dbm = agilent.interp_dbm(band, lo_ghz) dbm = min(dbm, agilent.max_dbm) log.info('setting agilent to %g GHz, %g dBm', fsig, dbm) # set power safely while maintaining lock, if possible. hz = fsig * 1e9 if photonics: if att < photonics.state['attenuation']: agilent.set_hz_dbm(hz, dbm) photonics.set_attenuation(att) else: photonics.set_attenuation(att) agilent.set_hz_dbm(hz, dbm) else: agilent.set_hz_dbm(hz, dbm) agilent.set_output(1) agilent.update(publish_only=True) time.sleep(0.05) # wait 50ms; for small changes PLL might hold lock vstr = '' band_kwargs = {"LO_GHZ": lo_ghz} if voltage is not None: vstr += ', %g V' % (voltage) band_kwargs["VOLTAGE"] = voltage if lock_only: vstr += ', LOCK_ONLY' band_kwargs["LOCK_ONLY"] = lock_only log.info('band %d tuning to LO %g GHz%s...', band, lo_ghz, vstr) pll_if_power = 0.0 # tune in a loop, adjusting signal to get pll_if_power in proper range. # adjust attenuator if present, then adjust output dBm. if photonics: orig_att = att att_min = max(0, att - 24) # limit 2x nominal power tries = 0 max_tries = 5 while True: tries += 1 msg = drama.obey(cartname, 'TUNE', **band_kwargs).wait() if msg.reason != drama.REA_COMPLETE: raise drama.BadStatus(drama.UNEXPMSG, '%s bad TUNE msg: %s' % (cartname, msg)) elif msg.status == drama.INVARG: # frequency out of range agilent.set_dbm(agilent.safe_dbm) photonics.set_attenuation(photonics.max_att) raise drama.BadStatus(msg.status, '%s TUNE failed' % (cartname)) elif msg.status != 0: # tune failure, raise the power and try again old_att = att att -= 8 if att < att_min: att = att_min if att == old_att or tries > max_tries: # stuck at the limit, time to give up photonics.set_attenuation(orig_att) raise drama.BadStatus(msg.status, '%s TUNE failed' % (cartname)) log.warning( 'band %d tune failed, retuning at %d attenuator counts...', band, att) photonics.set_attenuation(att) time.sleep(0.05) continue # we got a lock. check the pll_if_power level. # TODO don't assume pubname is DYN_STATE dyn_state = drama.get(cartname, "DYN_STATE").wait().arg["DYN_STATE"] pll_if_power = dyn_state['pll_if_power'] if tries > max_tries: # avoid bouncing around forever break old_att = att if pll_if_power < -2.5: # power too high att += 4 elif pll_if_power > -0.7: # power too low att -= 4 else: # power is fine break if att < att_min: att = att_min elif att > photonics.max_att: att = photonics.max_att if att == old_att: # stuck at the limit, so it'll have to do break log.warning( 'band %d bad pll_if_power %.2f; retuning at %d attenuator counts...', band, pll_if_power, att) photonics.set_attenuation(att) time.sleep(0.05) log.info( 'band %d tuned to LO %g GHz, pll_if_power %.2f at %.2f dBm, %d attenuator counts', band, lo_ghz, pll_if_power, dbm, att) #else: if not (-2.5 <= pll_if_power <= -0.7): # adjust dbm after photonics if needed orig_dbm = dbm orig_att = att if photonics else 0 dbm_max = min(agilent.max_dbm, dbm + 3.0) # limit to 2x nominal power att_min = max(0, att - 6) if photonics else 0 # ditto tries = 0 max_tries = 5 while True: tries += 1 msg = drama.obey(cartname, 'TUNE', **band_kwargs).wait() if msg.reason != drama.REA_COMPLETE: raise drama.BadStatus(drama.UNEXPMSG, '%s bad TUNE msg: %s' % (cartname, msg)) elif msg.status == drama.INVARG: # frequency out of range agilent.set_dbm(agilent.safe_dbm) raise drama.BadStatus(msg.status, '%s TUNE failed' % (cartname)) elif msg.status != 0: # tune failure, raise the power and try again old_dbm = dbm dbm += 1.0 if dbm > dbm_max: dbm = dbm_max if dbm == old_dbm or tries > max_tries: # stuck at the limit, time to give up agilent.set_dbm(orig_dbm) raise drama.BadStatus(msg.status, '%s TUNE failed' % (cartname)) log.warning('band %d tune failed, retuning at %.2f dBm...', band, dbm) agilent.set_dbm(dbm) time.sleep(0.05) continue # we got a lock. check the pll_if_power level. # TODO don't assume pubname is DYN_STATE dyn_state = drama.get(cartname, "DYN_STATE").wait().arg["DYN_STATE"] pll_if_power = dyn_state['pll_if_power'] if tries > max_tries: # avoid bouncing around forever break old_dbm = dbm if pll_if_power < -2.5: # power too high dbm -= 0.5 elif pll_if_power > -0.7: # power too low dbm += 0.5 else: # power is fine break if dbm > dbm_max: dbm = dbm_max elif dbm < -20.0: dbm = -20.0 if dbm == old_dbm: # stuck at the limit, so it'll have to do break log.warning( 'band %d bad pll_if_power %.2f; retuning at %.2f dBm...', band, pll_if_power, dbm) agilent.set_dbm(dbm) time.sleep(0.05) log.info('band %d tuned to LO %g GHz, pll_if_power %.2f at %.2f dBm.', band, lo_ghz, pll_if_power, dbm)
def initialise(msg): ''' Callback for the INITIALISE action. ''' log.info('initialise: msg=%s', msg) if msg.reason == drama.REA_OBEY: xmlname = msg.arg['INITIALISE'] initxml = drama.obj_from_xml(xmlname) drama.set_param('INITIALISE', initxml) if log.isEnabledFor(logging.DEBUG): # expensive formatting log.debug("INITIALISE:\n%s", pprint.pformat(initxml)) global g_esma_mode g_esma_mode = int(bool(msg.arg.get('ESMA_MODE', 0))) drama.set_param('ESMA_MODE', numpy.int32(g_esma_mode)) initxml = initxml['frontend_init'] inst = initxml['INSTRUMENT'] name = inst['NAME'] if name != taskname: raise drama.BadStatus( WRAP__WRONG_INSTRUMENT_NAME, 'got INSTRUMENT.NAME=%s instead of %s' % (name, taskname)) for i, r in enumerate(inst['receptor']): ID = r['id'] VAL = r['health'] # ON or OFF valid_ids = { 3: ['NA0', 'NA1'], 6: ['NU0L', 'NU1L', 'NU0U', 'NU1U'], 7: ['NW0L', 'NW1L', 'NW0U', 'NW1U'] } if ID not in valid_ids[g_band]: raise drama.BadStatus(WRAP__WRONG_RECEPTOR_IN_INITIALISE, 'bad INSTRUMENT.receptor.id %s' % (ID)) g_state['RECEPTOR_ID%d' % (i + 1)] = ID g_state['RECEPTOR_VAL%d' % (i + 1)] = VAL # fill in the static bits for first cell of STATE table cal = initxml['CALIBRATION'] t_cold = float(cal['T_COLD']) # K? t_spill = float(cal['T_SPILL']) t_hot = float(cal['T_HOT']) g_state['TEMP_LOAD2'] = t_cold g_state['TEMP_TSPILL'] = t_spill g_state['TEMP_AMBIENT'] = t_hot global t_cold_freq, t_cold_temp t_cold_table = cal['T_COLD_TABLE'] t_cold_freq = [float(x['FREQ']) for x in t_cold_table] t_cold_temp = [float(x['TEMP']) for x in t_cold_table] assert t_cold_freq == sorted(t_cold_freq) # TODO remove, not used manual = inst.get('TUNING', '').startswith('MANUAL') # skipping waveBand stuff # get name and path to our cartridge task global CART_TASK msg = drama.get(NAMAKANUI_TASK, 'TASKNAMES').wait(5) check_message(msg, f'get({NAMAKANUI_TASK},TASKNAMES)') CART_TASK = msg.arg['TASKNAMES'][f'B{g_band}'] drama.cache_path(CART_TASK) # get SIMULATE value and mask out the bits we don't care about msg = drama.get(NAMAKANUI_TASK, 'SIMULATE').wait(5) check_message(msg, f'get({NAMAKANUI_TASK},SIMULATE)') simulate = msg.arg['SIMULATE'] otherbands = [3, 6, 7] otherbands.remove(g_band) otherbits = 0 for band in otherbands: for bit in namakanui.sim.bits_for_band(band): otherbits |= bit simulate &= ~otherbits drama.set_param('SIMULATE', numpy.int32(simulate)) # might need to be 4-byte int # send load to AMBIENT, will fail if not already homed pos = f'b{g_band}_hot' log.info('moving load to %s...', pos) msg = drama.obey(NAMAKANUI_TASK, 'LOAD_MOVE', pos).wait(30) check_message(msg, f'obey({NAMAKANUI_TASK},LOAD_MOVE,{pos})') g_state['LOAD'] = 'AMBIENT' # power up the cartridge if necessary. this might take a little while. log.info('powering up band %d cartridge...', g_band) msg = drama.obey(NAMAKANUI_TASK, 'CART_POWER', g_band, 1).wait(30) check_message(msg, f'obey({NAMAKANUI_TASK},CART_POWER,{g_band},1)') # publish initial parameters; not sure if really needed. drama.set_param('LOAD', g_state['LOAD']) drama.set_param('STATE', [{'NUMBER': numpy.int32(0), **g_state}]) drama.set_param('TUNE_PAUSE', numpy.int32(0)) # TODO use this? drama.set_param('MECH_TUNE', numpy.int32(0)) drama.set_param('ELEC_TUNE', numpy.int32(0)) drama.set_param('LOCK_STATUS', numpy.int32(0)) drama.set_param('COMB_ON', numpy.int32(0)) # fesim only? drama.set_param('LO_FREQ', g_state['LO_FREQUENCY']) drama.set_param('REST_FREQUENCY', 0.0) drama.set_param('SIDEBAND', 'USB') drama.set_param('TEMP_TSPILL', t_spill) #drama.set_param('SB_MODE', {3:'SSB',6:'2SB',7:'2SB'}[g_band]) drama.set_param('SB_MODE', 'SSB') # debug # obey log.info('initialise done.')