コード例 #1
0
def check_message(msg, target):
    '''Check msg after a wait() and raise errors if needed.'''
    if msg.reason == drama.REA_RESCHED:
        raise drama.BadStatus(drama.APP_TIMEOUT,
                              f'Timeout waiting for {target}')
    elif msg.reason != drama.REA_COMPLETE:
        raise drama.BadStatus(drama.UNEXPMSG,
                              f'Unexpected reply to {target}: {msg}')
    elif msg.status != 0:
        raise drama.BadStatus(msg.status, f'Bad status from {target}')
コード例 #2
0
def SET_ATT(msg):
    '''Set Photonics attenuator counts (0-63, 0.5 dB per count).'''
    log.debug('SET_ATT(%s)', msg.arg)
    if not initialised:
        raise drama.BadStatus(drama.APP_ERROR, 'task needs INITIALISE')
    if not photonics:
        raise drama.BadStatus(drama.APP_ERROR, 'attenuator not in use')
    args, kwargs = drama.parse_argument(msg.arg)
    out = set_att_args(*args, **kwargs)
    log.info('setting attenuator counts to %d', att)
    photonics.set_attenuation(att)
コード例 #3
0
def SET_SG_HZ(msg):
    '''Set Agilent output frequency in Hz.'''
    log.debug('SET_SG_HZ(%s)', msg.arg)
    if not initialised:
        raise drama.BadStatus(drama.APP_ERROR, 'task needs INITIALISE')
    args, kwargs = drama.parse_argument(msg.arg)
    hz = set_sg_hz_args(*args, **kwargs)
    if hz < 9e3 or hz > 32e9:
        raise drama.BadStatus(drama.INVARG,
                              'HZ %g outside [9 KHz, 32 GHz] range' % (hz))
    log.info('setting agilent hz to %g', hz)
    agilent.set_hz(hz)
    agilent.update(publish_only=True)
コード例 #4
0
def SET_SG_DBM(msg):
    '''Set Agilent output power in dBm.'''
    log.debug('SET_SG_DBM(%s)', msg.arg)
    if not initialised:
        raise drama.BadStatus(drama.APP_ERROR, 'task needs INITIALISE')
    args, kwargs = drama.parse_argument(msg.arg)
    dbm = set_sg_dbm_args(*args, **kwargs)
    if dbm < -130.0 or dbm > 0.0:
        raise drama.BadStatus(drama.INVARG,
                              'DBM %g outside [-130, 0] range' % (dbm))
    log.info('setting agilent dbm to %g', dbm)
    agilent.set_dbm(dbm)
    agilent.update(publish_only=True)
コード例 #5
0
def INITIALISE(msg):
    '''
    Reinitialise (reinstantiate) the cartridge.
    '''
    global cart, inifile, band

    log.debug('INITIALISE(%s)', msg.arg)

    args, kwargs = drama.parse_argument(msg.arg)

    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'])  # bitmask

    if 'BAND' in kwargs:
        band = int(kwargs['BAND'])
    if not band:
        raise drama.BadStatus(drama.INVARG,
                              'missing argument BAND, receiver band number')

    # kick the update loop, if running, just to make sure it can't interfere
    # with cart's initialise().
    try:
        drama.kick(taskname, "UPDATE").wait()
    except drama.DramaException:
        pass

    # we recreate the cart instance to force it to reread its ini file.
    # note that Cart.__init__() calls Cart.initialise().
    log.info('initialising band %d...', band)
    del cart
    cart = None
    gc.collect()
    cart = namakanui.cart.Cart(band, inifile, drama.wait, drama.set_param,
                               simulate)

    # set the SIMULATE bitmask used by the cart
    drama.set_param('SIMULATE', cart.simulate)

    # restart the update loop
    drama.blind_obey(taskname, "UPDATE")
    log.info('initialised.')
コード例 #6
0
def sequence(msg):
    '''
    Callback called for every entry to the SEQUENCE action.
    This lets us place monitors on the Namakanui engineering tasks
    without starting a background action from CONFIGURE or SETUP_SEQUENCE.
    '''
    log.debug('sequence: msg=%s', msg)

    if msg.reason == drama.REA_OBEY:
        sequence.start = drama.get_param('START')
        sequence.end = drama.get_param('END')
        sequence.dwell = drama.get_param('DWELL')
        sequence.step_counter = sequence.start
        sequence.state_table_index = 0
        sequence.dwell_counter = 0

        # start monitor on CART_TASK to track lock status.
        # TODO: do we need a faster update during obs? 5s is pretty slow.
        sequence.cart_tid = drama.monitor(CART_TASK, 'DYN_STATE')

        # start monitor on NAMAKANUI.LAKESHORE to get TEMP_AMBIENT
        sequence.lakeshore_tid = drama.monitor(NAMAKANUI_TASK, 'LAKESHORE')

    elif msg.reason == drama.REA_TRIGGER and msg.transid == sequence.cart_tid:
        if msg.status == drama.MON_STARTED:
            pass  # lazy, just let the drama dispatcher clean up after us
        elif msg.status == drama.MON_CHANGED:
            # TODO: do we need to check other parameters also?
            if msg.arg['pll_unlock']:
                raise drama.BadStatus(WRAP__RXNOTLOCKED,
                                      'lost lock during sequence')
        else:
            raise drama.BadStatus(
                msg.status,
                f'unexpected message for {CART_TASK}.DYN_STATE monitor: {msg}')

    elif msg.reason == drama.REA_TRIGGER and msg.transid == sequence.lakeshore_tid:
        if msg.status == drama.MON_STARTED:
            pass  # lazy, just let the drama dispatcher clean up after us
        elif msg.status == drama.MON_CHANGED:
            g_state['TEMP_AMBIENT'] = msg.arg['temp5']
        else:
            raise drama.BadStatus(
                msg.status,
                f'unexpected message for {NAMAKANUI_TASK}.LAKESHORE monitor: {msg}'
            )
コード例 #7
0
def SET_SG_OUT(msg):
    '''Set Agilent output on (1) or off (0).'''
    log.debug('SET_SG_OUT(%s)', msg.arg)
    if not initialised:
        raise drama.BadStatus(drama.APP_ERROR, 'task needs INITIALISE')
    args, kwargs = drama.parse_argument(msg.arg)
    out = set_sg_out_args(*args, **kwargs)
    log.info('setting agilent output to %d', out)
    agilent.set_output(out)
    agilent.update(publish_only=True)
コード例 #8
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])
コード例 #9
0
def SET_BAND(msg):
    '''Set IFSwitch band to BAND.  If this would change the selection,
       first sets Agilent to a safe level to avoid high power to mixer.'''
    log.debug('SET_BAND(%s)', msg.arg)
    if not initialised:
        raise drama.BadStatus(drama.APP_ERROR, 'task needs INITIALISE')
    args, kwargs = drama.parse_argument(msg.arg)
    band = set_band_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 ifswitch.get_band() != band:
        log.info('setting IF switch to band %d', band)
        # reduce power to minimum levels
        agilent.set_dbm(agilent.safe_dbm)
        agilent.update(publish_only=True)
        if photonics:
            photonics.set_attenuation(photonics.max_att)
        ifswitch.set_band(band)
    else:
        log.info('IF switch already at band %d', band)
コード例 #10
0
def MAIN(msg):
    try:
        log.info('getting YFACTOR...')
        # be careful here
        #yfactor = drama.get('IFTASK@if-micro', 'YFACTOR').wait(1).arg['YFACTOR']
        msg = drama.get('IFTASK@if-micro', 'YFACTOR').wait(1)
        if msg.reason != drama.REA_COMPLETE:
            raise drama.BadStatus(msg.status or drama.APP_ERROR,
                                  'bad reply: %s' % (msg))
        yfactor = msg.arg['YFACTOR']

        log.info('getting ambient temperature...')
        jamb = epics.caget('nmnCryo:ls:temp5')
        jcold = 80.0
        # AVG_PWR, LOW_PWR, HIGH_PWR, Y_FAC should already be numpy arrays
        log.info('J: %.3f, %.3f', jcold, jamb)
        log.info('AVG_SIZE:\n%s', yfactor['AVG_SIZE'])
        log.info('LOW_SIZE:\n%s', yfactor['LOW_SIZE'])
        log.info('HIGH_SIZE:\n%s', yfactor['HIGH_SIZE'])
        log.info('AVG_PWR:\n%s', yfactor['AVG_PWR'])
        log.info('HIGH_PWR:\n%s', yfactor['HIGH_PWR'])
        log.info('LOW_PWR:\n%s', yfactor['LOW_PWR'])
        log.info('Y_FAC:\n%s', yfactor['Y_FAC'])
        # why does this differ from Y_FAC? how is Y_FAC calculated?
        # PWR might be in dbm, in which case Y = 10**((HIGH-LOW)/10)
        #my_y = yfactor['HIGH_PWR'] / yfactor['LOW_PWR']
        #log.info('MY_Y:\n%s', my_y)
        # this one is correct, so no more reason to print it out
        #my_y2 = 10**(0.1*(yfactor['HIGH_PWR']-yfactor['LOW_PWR']))
        #log.info('MY_Y2:\n%s', my_y2)
        y = yfactor['Y_FAC']
        trx = (jamb - y * jcold) / (y - 1)
        # zero out any spots with no samples
        trx *= yfactor['AVG_SIZE'].astype(bool).astype(float)
        log.info('trx:\n%s', trx)

        # print out stats/summary in blocks of 4
        i = 0
        n = 4
        while i < len(trx):
            b = trx[i:i + n]

            log.info('dcm %2d-%2d: %s avg: %.2f +- %.2f', i, i + n - 1,
                     b.round(2), b.mean(), b.std())
            i += n

    finally:
        drama.Exit('done')  # no need to raise instance
コード例 #11
0
def LOAD_HOME(msg):
    '''Home the load stage.  No arguments.
       
       NOTE: This can twist up any wires running to the load stage,
             e.g. for a tone source. Supervise as needed.
    '''
    log.debug('LOAD_HOME')
    if not initialised:
        raise drama.BadStatus(drama.APP_ERROR, 'task needs INITIALISE')
    if msg.reason == drama.REA_OBEY:
        log.info('homing load...')
        load.home()
        log.info('load homed.')
    else:
        log.error('LOAD_HOME stopping load due to unexpected msg %s', msg)
        load.stop()
コード例 #12
0
def LOAD_MOVE(msg):
    '''Move the load.  Arguments:
            POSITION:  Named position or absolute encoder counts.
    '''
    log.debug('LOAD_MOVE(%s)', msg.arg)
    if not initialised:
        raise drama.BadStatus(drama.APP_ERROR, 'task needs INITIALISE')
    if msg.reason == drama.REA_OBEY:
        args, kwargs = drama.parse_argument(msg.arg)
        pos = load_move_args(*args, **kwargs)
        log.info('moving load to %s...', pos)
        load.move(pos)
        log.info('load at %d, %s.', load.state['pos_counts'],
                 load.state['pos_name'])
    else:
        log.error('LOAD_MOVE stopping load due to unexpected msg %s', msg)
        load.stop()
コード例 #13
0
def UPDATE(msg):
    '''
    Update local class instances every 10s.
    This is half the frequency of the nominal cryo update rate,
    but we don't expect state to change quickly.
    
    Try updating everything in one call since it is simpler than staggering.
    
    TODO: wrap this in a try/catch block?
    
    TODO: small delay between updates to let DRAMA message loop run?
          or stagger individual updates?
    '''
    delay = 10
    if msg.reason == drama.REA_KICK:
        log.debug('UPDATE kicked.')
        return
    if msg.reason == drama.REA_OBEY:
        log.debug('UPDATE started.')
        if not initialised:
            raise drama.BadStatus(drama.APP_ERROR, 'task needs INITIALISE')
        # INITIALISE has just updated everything, so skip the first update.
        drama.reschedule(delay)
        return
    log.debug('UPDATE reschedule.')

    # RMB 20200610: try each update function independently; keep UPDATE running.
    #cryo.update()
    #load.update()
    #agilent.update()
    #ifswitch.update()
    update_funcs = [cryo.update, load.update, agilent.update, ifswitch.update]
    if photonics:
        update_funcs.append(photonics.update)
    for f in update_funcs:
        try:
            f()
        except:  # TODO limit bycatch
            log.exception('UPDATE exception')
    drama.reschedule(delay)
コード例 #14
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.')
コード例 #15
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)
コード例 #16
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.')
コード例 #17
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.')
コード例 #18
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.')