1# Copyright 2020 Google Inc. All Rights Reserved. 2# Author: oelayach@google.com 3 4import pyvisa 5import time 6from acts import logger 7from ota_chamber import Chamber 8 9 10class Chamber(Chamber): 11 """Base class implementation for signal generators. 12 13 Base class provides functions whose implementation is shared by all 14 chambers. 15 """ 16 CHAMBER_SLEEP = 10 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() 23 self.chamber_inst = self.chamber_resource.open_resource( 24 '{}::{}::{}::INSTR'.format(self.config['network_id'], 25 self.config['ip_address'], 26 self.config['hislip_interface'])) 27 self.chamber_inst.timeout = 200000 28 self.chamber_inst.write_termination = '\n' 29 self.chamber_inst.read_termination = '\n' 30 31 self.id_check(self.config) 32 self.current_azim = 0 33 self.current_roll = 0 34 if self.config.get('reset_home', True): 35 self.find_chamber_home() 36 self.move_theta_phi_abs(self.config['chamber_home']['theta'], 37 self.config['chamber_home']['phi']) 38 self.set_new_home_position() 39 else: 40 self.config['chamber_home'] = {'phi': 0, 'theta': 0} 41 self.log.warning( 42 'Reset home set to false. Assumed [0,0]. Chamber angles may not be as expected.' 43 ) 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 chamber to [{}, {}]".format(theta, phi)) 91 self.move_to_azim_roll(phi, theta) 92 93 def move_phi_rel(self, phi): 94 self.log.info("Moving Phi by {} degrees".format(phi)) 95 self.move_to_azim_roll(self.current_azim + phi, self.current_roll) 96 97 def move_theta_rel(self, theta): 98 self.log.info("Moving Theta by {} degrees".format(theta)) 99 self.move_to_azim_roll(self.current_azim, self.current_roll + theta) 100 101 def move_feed_roll(self, roll): 102 self.log.info("Moving feed roll to {} degrees".format(roll)) 103 self.chamber_inst.write(f"POS:MOVE:ROLL:FEED {roll}") 104 self.chamber_inst.write("POS:MOVE:INIT") 105 self.wait_for_move_end() 106 self.current_feed_roll = self.chamber_inst.query("POS:MOVE:ROLL:FEED?") 107 108 def reset_phi(self): 109 self.log.info("Resetting Phi.") 110 self.move_to_azim_roll(0, self.current_roll) 111 self.phi = 0 112 113 def reset_theta(self): 114 self.log.info("Resetting Theta.") 115 self.move_to_azim_roll(self.current_azim, 0) 116 self.theta = 0 117 118 def reset_phi_theta(self): 119 """ Resets signal generator.""" 120 self.log.info("Resetting to home.") 121 self.chamber_inst.write(f"POS:ZERO:GOTO") 122 self.wait_for_move_end() 123 124 # Keysight-provided functions 125 def wait_for_move_end(self): 126 moving_bitmask = 4 127 while True: 128 stat = int(self.chamber_inst.query("STAT:OPER:COND?")) 129 if (stat & moving_bitmask) == 0: 130 return 131 time.sleep(0.25) 132 133 def wait_for_sweep_end(self): 134 sweeping_bitmask = 16 135 while True: 136 stat = int(self.chamber_inst.query("STAT:OPER:COND?")) 137 if (stat & sweeping_bitmask) == 0: 138 return 139 time.sleep(0.25) 140 141 def move_to_azim_roll(self, azim, roll): 142 self.chamber_inst.write(f"POS:MOVE:AZIM {azim};ROLL {roll}") 143 self.chamber_inst.write("POS:MOVE:INIT") 144 self.wait_for_move_end() 145 curr_motor = self.chamber_inst.query("POS:CURR:MOT?") 146 curr_azim, curr_roll = map(float, (curr_motor.split(','))) 147 self.current_azim = curr_azim 148 self.current_roll = curr_roll 149 return curr_azim, curr_roll 150 151 def sweep_setup(self, azim_sss: tuple, roll_sss: tuple, sweep_type: str): 152 self.chamber_inst.write( 153 f"POS:SWE:AZIM:STAR {azim_sss[0]};STOP {azim_sss[1]};STEP {azim_sss[2]}" 154 ) 155 self.chamber_inst.write( 156 f"POS:SWE:ROLL:STAR {roll_sss[0]};STOP {roll_sss[1]};STEP {roll_sss[2]}" 157 ) 158 self.chamber_inst.write(f"POS:SWE:TYPE {sweep_type}") 159 self.chamber_inst.write("POS:SWE:CONT 1") 160 161 def sweep_init(self): 162 163 def query_float_list(inst, scpi): 164 resp = inst.query(scpi) 165 return list(map(float, resp.split(','))) 166 167 self.chamber_inst.write("POS:SWE:INIT") 168 self.wait_for_sweep_end() 169 azims = query_float_list(self.chamber_inst, "FETC:AZIM?") 170 rolls = query_float_list(self.chamber_inst, "FETC:ROLL?") 171 phis = query_float_list(self.chamber_inst, "FETC:DUT:PHI?") 172 thetas = query_float_list(self.chamber_inst, "FETC:DUT:THET?") 173 return zip(azims, rolls, phis, thetas) 174 175 def configure_positioner(self, pos_name, pos_visa_addr): 176 select = "True" 177 simulate = "False" 178 options = "" 179 data = f"'{pos_name}~{select}~{simulate}~{pos_visa_addr}~{options}'" 180 self.chamber_inst.write(f"EQU:CONF {data}") 181 self.chamber_inst.write("EQU:UPD") 182