1 # 2 # Copyright 2016 - The Android Open Source Project 3 # 4 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # you may not use this file except in compliance with the License. 6 # You may obtain a copy of the License at 7 # 8 # http://www.apache.org/licenses/LICENSE-2.0 9 # 10 # Unless required by applicable law or agreed to in writing, software 11 # distributed under the License is distributed on an "AS IS" BASIS, 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 # See the License for the specific language governing permissions and 14 # limitations under the License. 15 16 import importlib 17 18 from vts.runners.host.keys import Config 19 20 VTS_CONTROLLER_CONFIG_NAME = "Attenuator" 21 VTS_CONTROLLER_REFERENCE_NAME = "attenuators" 22 23 24 def create(configs, logger): 25 objs = [] 26 for c in configs: 27 attn_model = c["Model"] 28 # Default to telnet. 29 protocol = "telnet" 30 if "Protocol" in c: 31 protocol = c["Protocol"] 32 module_name = "vts.utils.python.controllers.attenuator_lib.%s.%s" % ( 33 attn_model, protocol) 34 module = importlib.import_module(module_name) 35 inst_cnt = c["InstrumentCount"] 36 attn_inst = module.AttenuatorInstrument(inst_cnt) 37 attn_inst.model = attn_model 38 insts = attn_inst.open(c[Config.key_address.value], 39 c[Config.key_port.value]) 40 for i in range(inst_cnt): 41 attn = Attenuator(attn_inst, idx=i) 42 if "Paths" in c: 43 try: 44 setattr(attn, "path", c["Paths"][i]) 45 except IndexError: 46 logger.error("No path specified for attenuator %d." % i) 47 raise 48 objs.append(attn) 49 return objs 50 51 52 def destroy(objs): 53 return 54 55 56 r""" 57 Base classes which define how attenuators should be accessed, managed, and manipulated. 58 59 Users will instantiate a specific child class, but almost all operation should be performed 60 on the methods and data members defined here in the base classes or the wrapper classes. 61 """ 62 63 64 class AttenuatorError(Exception): 65 r"""This is the Exception class defined for all errors generated by Attenuator-related modules. 66 """ 67 pass 68 69 70 class InvalidDataError(AttenuatorError): 71 r"""This exception is thrown when an unexpected result is seen on the transport layer below 72 the module. 73 74 When this exception is seen, closing an re-opening the link to the attenuator instrument is 75 probably necessary. Something has gone wrong in the transport. 76 """ 77 pass 78 79 80 class InvalidOperationError(AttenuatorError): 81 r"""Certain methods may only be accessed when the instance upon which they are invoked is in 82 a certain state. This indicates that the object is not in the correct state for a method to be 83 called. 84 """ 85 pass 86 87 88 class AttenuatorInstrument(): 89 r"""This is a base class that defines the primitive behavior of all attenuator 90 instruments. 91 92 The AttenuatorInstrument class is designed to provide a simple low-level interface for 93 accessing any step attenuator instrument comprised of one or more attenuators and a 94 controller. All AttenuatorInstruments should override all the methods below and call 95 AttenuatorInstrument.__init__ in their constructors. Outside of setup/teardown, 96 devices should be accessed via this generic "interface". 97 """ 98 model = None 99 INVALID_MAX_ATTEN = 999.9 100 101 def __init__(self, num_atten=0): 102 r"""This is the Constructor for Attenuator Instrument. 103 104 Parameters 105 ---------- 106 num_atten : This optional parameter is the number of attenuators contained within the 107 instrument. In some instances setting this number to zero will allow the driver to 108 auto-determine, the number of attenuators; however, this behavior is not guaranteed. 109 110 Raises 111 ------ 112 NotImplementedError 113 This constructor should never be called directly. It may only be called by a child. 114 115 Returns 116 ------- 117 self 118 Returns a newly constructed AttenuatorInstrument 119 """ 120 121 if type(self) is AttenuatorInstrument: 122 raise NotImplementedError( 123 "Base class should not be instantiated directly!") 124 125 self.num_atten = num_atten 126 self.max_atten = AttenuatorInstrument.INVALID_MAX_ATTEN 127 self.properties = None 128 129 def set_atten(self, idx, value): 130 r"""This function sets the attenuation of an attenuator given its index in the instrument. 131 132 Parameters 133 ---------- 134 idx : This zero-based index is the identifier for a particular attenuator in an 135 instrument. 136 value : This is a floating point value for nominal attenuation to be set. 137 138 Raises 139 ------ 140 NotImplementedError 141 This constructor should never be called directly. It may only be called by a child. 142 """ 143 raise NotImplementedError("Base class should not be called directly!") 144 145 def get_atten(self, idx): 146 r"""This function returns the current attenuation from an attenuator at a given index in 147 the instrument. 148 149 Parameters 150 ---------- 151 idx : This zero-based index is the identifier for a particular attenuator in an instrument. 152 153 Raises 154 ------ 155 NotImplementedError 156 This constructor should never be called directly. It may only be called by a child. 157 158 Returns 159 ------- 160 float 161 Returns a the current attenuation value 162 """ 163 raise NotImplementedError("Base class should not be called directly!") 164 165 166 class Attenuator(): 167 r"""This class defines an object representing a single attenuator in a remote instrument. 168 169 A user wishing to abstract the mapping of attenuators to physical instruments should use this 170 class, which provides an object that obscures the physical implementation an allows the user 171 to think only of attenuators regardless of their location. 172 """ 173 174 def __init__(self, instrument, idx=0, offset=0): 175 r"""This is the constructor for Attenuator 176 177 Parameters 178 ---------- 179 instrument : Reference to an AttenuatorInstrument on which the Attenuator resides 180 idx : This zero-based index is the identifier for a particular attenuator in an instrument. 181 offset : A power offset value for the attenuator to be used when performing future 182 operations. This could be used for either calibration or to allow group operations with 183 offsets between various attenuators. 184 185 Raises 186 ------ 187 TypeError 188 Requires a valid AttenuatorInstrument to be passed in. 189 IndexError 190 The index of the attenuator in the AttenuatorInstrument must be within the valid range. 191 192 Returns 193 ------- 194 self 195 Returns a newly constructed Attenuator 196 """ 197 if not isinstance(instrument, AttenuatorInstrument): 198 raise TypeError("Must provide an Attenuator Instrument Ref") 199 self.model = instrument.model 200 self.instrument = instrument 201 self.idx = idx 202 self.offset = offset 203 204 if (self.idx >= instrument.num_atten): 205 raise IndexError( 206 "Attenuator index out of range for attenuator instrument") 207 208 def set_atten(self, value): 209 r"""This function sets the attenuation of Attenuator. 210 211 Parameters 212 ---------- 213 value : This is a floating point value for nominal attenuation to be set. 214 215 Raises 216 ------ 217 ValueError 218 The requested set value+offset must be less than the maximum value. 219 """ 220 221 if value + self.offset > self.instrument.max_atten: 222 raise ValueError( 223 "Attenuator Value+Offset greater than Max Attenuation!") 224 225 self.instrument.set_atten(self.idx, value + self.offset) 226 227 def get_atten(self): 228 r"""This function returns the current attenuation setting of Attenuator, normalized by 229 the set offset. 230 231 Returns 232 ------- 233 float 234 Returns a the current attenuation value 235 """ 236 237 return self.instrument.get_atten(self.idx) - self.offset 238 239 def get_max_atten(self): 240 r"""This function returns the max attenuation setting of Attenuator, normalized by 241 the set offset. 242 243 Returns 244 ------- 245 float 246 Returns a the max attenuation value 247 """ 248 if (self.instrument.max_atten == 249 AttenuatorInstrument.INVALID_MAX_ATTEN): 250 raise ValueError("Invalid Max Attenuator Value") 251 252 return self.instrument.max_atten - self.offset 253 254 255 class AttenuatorGroup(object): 256 r"""This is a handy abstraction for groups of attenuators that will share behavior. 257 258 Attenuator groups are intended to further facilitate abstraction of testing functions from 259 the physical objects underlying them. By adding attenuators to a group, it is possible to 260 operate on functional groups that can be thought of in a common manner in the test. This 261 class is intended to provide convenience to the user and avoid re-implementation of helper 262 functions and small loops scattered throughout user code. 263 264 """ 265 266 def __init__(self, name=""): 267 r"""This is the constructor for AttenuatorGroup 268 269 Parameters 270 ---------- 271 name : The name is an optional parameter intended to further facilitate the passing of 272 easily tracked groups of attenuators throughout code. It is left to the user to use the 273 name in a way that meets their needs. 274 275 Returns 276 ------- 277 self 278 Returns a newly constructed AttenuatorGroup 279 """ 280 self.name = name 281 self.attens = [] 282 self._value = 0 283 284 def add_from_instrument(self, instrument, indices): 285 r"""This function provides a way to create groups directly from the Attenuator Instrument. 286 287 This function will create Attenuator objects for all of the indices passed in and add 288 them to the group. 289 290 Parameters 291 ---------- 292 instrument : A ref to the instrument from which attenuators will be added 293 indices : You pay pass in the indices either as a range, a list, or a single integer. 294 295 Raises 296 ------ 297 TypeError 298 Requires a valid AttenuatorInstrument to be passed in. 299 """ 300 301 if not instrument or not isinstance(instrument, AttenuatorInstrument): 302 raise TypeError("Must provide an Attenuator Instrument Ref") 303 304 if type(indices) is range or type(indices) is list: 305 for i in indices: 306 self.attens.append(Attenuator(instrument, i)) 307 elif type(indices) is int: 308 self.attens.append(Attenuator(instrument, indices)) 309 310 def add(self, attenuator): 311 r"""This function adds an already constructed Attenuator object to the AttenuatorGroup. 312 313 Parameters 314 ---------- 315 attenuator : An Attenuator object. 316 317 Raises 318 ------ 319 TypeError 320 Requires a valid Attenuator to be passed in. 321 """ 322 323 if not isinstance(attenuator, Attenuator): 324 raise TypeError("Must provide an Attenuator") 325 326 self.attens.append(attenuator) 327 328 def synchronize(self): 329 r"""This function can be called to ensure all Attenuators within a group are set 330 appropriately. 331 """ 332 333 self.set_atten(self._value) 334 335 def is_synchronized(self): 336 r"""This function queries all the Attenuators in the group to determine whether or not 337 they are synchronized. 338 339 Returns 340 ------- 341 bool 342 True if the attenuators are synchronized. 343 """ 344 345 for att in self.attens: 346 if att.get_atten() != self._value: 347 return False 348 return True 349 350 def set_atten(self, value): 351 r"""This function sets the attenuation value of all attenuators in the group. 352 353 Parameters 354 ---------- 355 value : This is a floating point value for nominal attenuation to be set. 356 357 Returns 358 ------- 359 bool 360 True if the attenuators are synchronized. 361 """ 362 363 value = float(value) 364 for att in self.attens: 365 att.set_atten(value) 366 self._value = value 367 368 def get_atten(self): 369 r"""This function returns the current attenuation setting of AttenuatorGroup. 370 371 This returns a cached value that assumes the attenuators are synchronized. It avoids a 372 relatively expensive call for a common operation, and trusts the user to ensure 373 synchronization. 374 375 Returns 376 ------- 377 float 378 Returns a the current attenuation value for the group, which is independent of any 379 individual attenuator offsets. 380 """ 381 382 return float(self._value) 383