Example #1
0
def POWER(msg):
    '''
    Enable or disable power to the cartridge.
    Single argument is ENABLE, which can be 1/0, on/off, true/false.
    Enabling power will trigger the demagnetization and defluxing sequence
    plus bias voltage error measurement, which could take considerable time.
    
    TODO: Invoking SIMULATE during this procedure (or interrupting it in
    some other way) could leave the cart powered but not setup properly.
    That is, not demagnetized or defluxed.
    Can we do anything about that?  It would also be a problem if this task
    suddenly died, though, and there wouldn't be any indication.
    There will probably just need to be a procedure that an error during
    power up will require power-cycling the cartridge.
    '''
    log.debug('POWER(%s)', msg.arg)
    args, kwargs = drama.parse_argument(msg.arg)
    enable = kwargs.get('ENABLE', '') or args[0]
    if hasattr(enable, 'lower'):
        enable = enable.lower().strip()
        enable = {
            '0': 0,
            'off': 0,
            'false': 0,
            '1': 1,
            'on': 1,
            'true': 1
        }[enable]
    else:
        enable = int(bool(enable))
    onoff = ['off', 'on'][enable]
    log.info('powering %s...', onoff)
    cart.power(enable)
    log.info('powered %s.', onoff)
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)
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)
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)
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)
Example #6
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.')
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()
Example #8
0
def TUNE(msg):
    '''
    Takes three arguments, LO_GHZ, VOLTAGE, and LOCK_ONLY.
    If VOLTAGE is not given, PLL control voltage will not be adjusted
        following the initial lock.
    If LOCK_ONLY is True, bias voltage, PA, LNA, and magnets will not
        be adjusted after locking the receiver.
    The reference signal and IF switch must already be set externally.
    '''
    log.debug('TUNE(%s)', msg.arg)
    args, kwargs = drama.parse_argument(msg.arg)
    lo_ghz, voltage, lock_only = tune_args(*args, **kwargs)
    vstr = ''
    if voltage is not None:
        vstr += ', %g V' % (voltage)
    if lock_only:
        vstr += ', LOCK_ONLY'
    log.info('tuning to LO %g GHz%s...', lo_ghz, vstr)
    cart.tune(lo_ghz, voltage, lock_only=lock_only)
    log.info('tuned.')
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 #10
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)
Example #11
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 #12
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)