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
16import importlib
17
18from vts.runners.host.keys import Config
19
20VTS_CONTROLLER_CONFIG_NAME = "Attenuator"
21VTS_CONTROLLER_REFERENCE_NAME = "attenuators"
22
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 = "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
52def destroy(objs):
53    return
54
55
56r"""
57Base classes which define how attenuators should be accessed, managed, and manipulated.
58
59Users will instantiate a specific child class, but almost all operation should be performed
60on the methods and data members defined here in the base classes or the wrapper classes.
61"""
62
63
64class AttenuatorError(Exception):
65    r"""This is the Exception class defined for all errors generated by Attenuator-related modules.
66    """
67    pass
68
69
70class 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
80class 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
88class 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
166class 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
255class 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