1# Copyright 2020 Google Inc. All Rights Reserved. 2# Author: oelayach@google.com 3 4import pyvisa 5import time 6from acts import logger 7 8class KeysightChamber(object): 9 """Base class implementation for signal generators. 10 11 Base class provides functions whose implementation is shared by all 12 chambers. 13 """ 14 CHAMBER_SLEEP = 10 15 16 VISA_LOCATION = '/opt/keysight/iolibs/libktvisa32.so' 17 18 def __init__(self, config): 19 self.config = config 20 self.log = logger.create_tagged_trace_logger("{}{}".format( 21 self.config['brand'], self.config['model'])) 22 self.chamber_resource = pyvisa.ResourceManager(self.VISA_LOCATION) 23 self.chamber_inst = self.chamber_resource.open_resource( 24 'TCPIP0::{}::{}::INSTR'.format(self.config['ip_address'], 25 self.config['hislip_interface'])) 26 self.chamber_inst.timeout = 200000 27 self.chamber_inst.write_termination = '\n' 28 self.chamber_inst.read_termination = '\n' 29 30 self.id_check(self.config) 31 self.current_azim = 0 32 self.current_roll = 0 33 if self.config.get('reset_home', True): 34 self.find_chamber_home() 35 self.move_theta_phi_abs(self.config['chamber_home']['theta'], 36 self.config['chamber_home']['phi']) 37 self.set_new_home_position() 38 else: 39 self.config['chamber_home'] = {'phi': 0, 'theta': 0} 40 self.log.warning( 41 'Reset home set to false. Assumed [0,0]. Chamber angles may not be as expected.' 42 ) 43 self.preset_orientations = self.config['preset_orientations'] 44 45 def id_check(self, config): 46 """ Checks Chamber ID.""" 47 self.log.info("ID Check Successful.") 48 self.log.info(self.chamber_inst.query("*IDN?")) 49 50 def reset(self): 51 self.reset_phi_theta() 52 53 def disconnect(self): 54 if self.config.get('reset_home', True): 55 self.reset_phi_theta() 56 self.chamber_inst.close() 57 self.chamber_resource.close() 58 59 def find_chamber_home(self): 60 self.chamber_inst.write(f"POS:BOR:INIT") 61 self.set_new_home_position() 62 self.wait_for_move_end() 63 64 def set_new_home_position(self): 65 self.chamber_inst.write(f"POS:ZERO:RES") 66 67 def get_phi(self): 68 return self.current_azim 69 70 def get_theta(self): 71 return self.current_roll 72 73 def get_pattern_sweep_limits(self): 74 return { 75 "pattern_phi_start": -self.config['chamber_home']['phi'], 76 "pattern_phi_stop": 165 - self.config['chamber_home']['phi'], 77 "pattern_theta_start": -self.config['chamber_home']['theta'], 78 "pattern_theta_stop": 360 - self.config['chamber_home']['theta'], 79 } 80 81 def move_phi_abs(self, phi): 82 self.log.info("Moving to Phi={}".format(phi)) 83 self.move_to_azim_roll(phi, self.current_roll) 84 85 def move_theta_abs(self, theta): 86 self.log.info("Moving to Theta={}".format(theta)) 87 self.move_to_azim_roll(self.current_azim, theta) 88 89 def move_theta_phi_abs(self, theta, phi): 90 self.log.info("Moving to Theta={}, Phi={}".format(theta, phi)) 91 self.move_to_azim_roll(phi, theta) 92 93 def move_theta_phi_abs(self, theta, phi): 94 self.log.info("Moving chamber to [{}, {}]".format(theta, phi)) 95 self.move_to_azim_roll(phi, theta) 96 97 def move_phi_rel(self, phi): 98 self.log.info("Moving Phi by {} degrees".format(phi)) 99 self.move_to_azim_roll(self.current_azim + phi, self.current_roll) 100 101 def move_theta_rel(self, theta): 102 self.log.info("Moving Theta by {} degrees".format(theta)) 103 self.move_to_azim_roll(self.current_azim, self.current_roll + theta) 104 105 def move_feed_roll(self, roll): 106 self.log.info("Moving feed roll to {} degrees".format(roll)) 107 self.chamber_inst.write(f"POS:MOVE:ROLL:FEED {roll}") 108 self.chamber_inst.write("POS:MOVE:INIT") 109 self.wait_for_move_end() 110 self.current_feed_roll = self.chamber_inst.query("POS:MOVE:ROLL:FEED?") 111 112 def reset_phi(self): 113 self.log.info("Resetting Phi.") 114 self.move_to_azim_roll(0, self.current_roll) 115 self.phi = 0 116 117 def reset_theta(self): 118 self.log.info("Resetting Theta.") 119 self.move_to_azim_roll(self.current_azim, 0) 120 self.theta = 0 121 122 def reset_phi_theta(self): 123 """ Resets signal generator.""" 124 self.log.info("Resetting to home.") 125 self.chamber_inst.write(f"POS:ZERO:GOTO") 126 self.wait_for_move_end() 127 128 # Keysight-provided functions 129 def wait_for_move_end(self): 130 moving_bitmask = 4 131 while True: 132 stat = int(self.chamber_inst.query("STAT:OPER:COND?")) 133 if (stat & moving_bitmask) == 0: 134 return 135 time.sleep(0.25) 136 137 def wait_for_sweep_end(self): 138 sweeping_bitmask = 16 139 while True: 140 stat = int(self.chamber_inst.query("STAT:OPER:COND?")) 141 if (stat & sweeping_bitmask) == 0: 142 return 143 time.sleep(0.25) 144 145 def move_to_azim_roll(self, azim, roll): 146 self.chamber_inst.write(f"POS:MOVE:AZIM {azim};ROLL {roll}") 147 self.chamber_inst.write("POS:MOVE:INIT") 148 self.wait_for_move_end() 149 curr_motor = self.chamber_inst.query("POS:CURR:MOT?") 150 curr_azim, curr_roll = map(float, (curr_motor.split(','))) 151 self.current_azim = curr_azim 152 self.current_roll = curr_roll 153 return curr_azim, curr_roll 154 155 def sweep_setup(self, azim_sss: tuple, roll_sss: tuple, sweep_type: str): 156 self.chamber_inst.write( 157 f"POS:SWE:AZIM:STAR {azim_sss[0]};STOP {azim_sss[1]};STEP {azim_sss[2]}" 158 ) 159 self.chamber_inst.write( 160 f"POS:SWE:ROLL:STAR {roll_sss[0]};STOP {roll_sss[1]};STEP {roll_sss[2]}" 161 ) 162 self.chamber_inst.write(f"POS:SWE:TYPE {sweep_type}") 163 self.chamber_inst.write("POS:SWE:CONT 1") 164 165 def sweep_init(self): 166 def query_float_list(inst, scpi): 167 resp = inst.query(scpi) 168 return list(map(float, resp.split(','))) 169 170 self.chamber_inst.write("POS:SWE:INIT") 171 self.wait_for_sweep_end() 172 azims = query_float_list(self.chamber_inst, "FETC:AZIM?") 173 rolls = query_float_list(self.chamber_inst, "FETC:ROLL?") 174 phis = query_float_list(self.chamber_inst, "FETC:DUT:PHI?") 175 thetas = query_float_list(self.chamber_inst, "FETC:DUT:THET?") 176 return zip(azims, rolls, phis, thetas)