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