Example #1
0
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()
Example #2
0
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
Example #3
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
Example #4
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
Example #5
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
Example #6
0
 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
Example #7
0
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
Example #8
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)
Example #9
0
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
Example #10
0
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
Example #11
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()
Example #12
0
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])
Example #13
0
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.')
Example #14
0
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.')
Example #15
0
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.')
Example #16
0
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
Example #17
0
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
Example #18
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)
Example #19
0
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.')