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)
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()
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])
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)
def INITIALISE(msg): ''' Start the cartridge tasks and initialise them, then initialise the local control classes. Arguments: INITIALISE: The ini file path SIMULATE: Bitmask. If given, overrides config file settings. ''' global initialised, inifile, agilent, cryo, load, ifswitch, photonics global cartridge_tasknames, cold_mult, warm_mult log.debug('INITIALISE(%s)', msg.arg) args, kwargs = drama.parse_argument(msg.arg) initialised = False if 'INITIALISE' in kwargs: inifile = kwargs['INITIALISE'] if not inifile: raise drama.BadStatus(drama.INVARG, 'missing argument INITIALISE, .ini file path') simulate = None if 'SIMULATE' in kwargs: simulate = int(kwargs['SIMULATE']) config = IncludeParser(inifile) nconfig = config['namakanui'] cartridge_tasknames[3] = nconfig['b3_taskname'] cartridge_tasknames[6] = nconfig['b6_taskname'] cartridge_tasknames[7] = nconfig['b7_taskname'] # export these so the frontend task doesn't have to guess drama.set_param('TASKNAMES', {'B%d' % (k): v for k, v in cartridge_tasknames.items()}) # start the cartridge tasks in the background. # will exit immediately if already running, which is fine. log.info('starting cartridge tasks') subprocess.Popen([binpath + 'cartridge_task.py', cartridge_tasknames[3]]) subprocess.Popen([binpath + 'cartridge_task.py', cartridge_tasknames[6]]) subprocess.Popen([binpath + 'cartridge_task.py', cartridge_tasknames[7]]) # kill the UPDATE action while we fire things up try: drama.kick(taskname, "UPDATE").wait() except drama.DramaException: pass # kludge: sleep a short time to let cartridge tasks run up log.info('sleeping 3s for cartridge task startup') drama.wait(3) # TODO: do the ini file names really need to be configurable? # probably a bit overkill. cart_kwargs = {} if simulate is not None: cart_kwargs["SIMULATE"] = simulate for band in [3, 6, 7]: task = cartridge_tasknames[band] ini = datapath + nconfig['b%d_ini' % (band)] log.info('initialising %s', task) msg = drama.obey(task, "INITIALISE", BAND=band, INITIALISE=ini, **cart_kwargs).wait() if msg.status != 0: raise drama.BadStatus(msg.status, task + ' INITIALISE failed') # setting agilent frequency requires warm/cold multipliers for each band. # TODO: this assumes pubname=DYN_STATE -- could instead [include] config. # also this is rather a large get() for just a couple values. for band in [3, 6, 7]: dyn_state = drama.get(cartridge_tasknames[band], "DYN_STATE").wait().arg["DYN_STATE"] cold_mult[band] = dyn_state['cold_mult'] warm_mult[band] = dyn_state['warm_mult'] # now reinstantiate the local stuff if load is not None: load.close() del agilent del cryo del load del ifswitch del photonics agilent = None cryo = None load = None ifswitch = None photonics = None gc.collect() agilent = namakanui.agilent.Agilent(datapath + nconfig['agilent_ini'], drama.wait, drama.set_param, simulate) cryo = namakanui.cryo.Cryo(datapath + nconfig['cryo_ini'], drama.wait, drama.set_param, simulate) # wait a moment for load, jcms4 is fussy about reconnects drama.wait(1) load = namakanui.load.Load(datapath + nconfig['load_ini'], drama.wait, drama.set_param, simulate) ifswitch = namakanui.ifswitch.IFSwitch(datapath + nconfig['ifswitch_ini'], drama.wait, drama.set_param, simulate) if 'photonics_ini' in nconfig: photonics = namakanui.photonics.Photonics( datapath + nconfig['photonics_ini'], drama.wait, drama.set_param, simulate) # publish the load.positions table for the GUI drama.set_param('LOAD_TABLE', load.positions) # rebuild the simulate bitmask from what was actually set simulate = agilent.simulate | cryo.simulate | load.simulate | ifswitch.simulate | ( photonics.simulate if photonics else 0) for band in [3, 6, 7]: task = cartridge_tasknames[band] simulate |= drama.get(task, 'SIMULATE').wait(5).arg['SIMULATE'] drama.set_param('SIMULATE', simulate) # restart the update loop drama.blind_obey(taskname, "UPDATE") # TODO: power up the cartridges? tune? leave it for the FE wrapper? initialised = True log.info('initialised.')
def 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)