• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
18import logging
19
20from acts.keys import Config
21from acts.libs.proc import job
22
23MOBLY_CONTROLLER_CONFIG_NAME = 'Attenuator'
24ACTS_CONTROLLER_REFERENCE_NAME = 'attenuators'
25_ATTENUATOR_OPEN_RETRIES = 3
26
27
28def create(configs):
29    objs = []
30    for c in configs:
31        attn_model = c['Model']
32        # Default to telnet.
33        protocol = c.get('Protocol', 'telnet')
34        module_name = 'acts.controllers.attenuator_lib.%s.%s' % (attn_model,
35                                                                 protocol)
36        module = importlib.import_module(module_name)
37        inst_cnt = c['InstrumentCount']
38        attn_inst = module.AttenuatorInstrument(inst_cnt)
39        attn_inst.model = attn_model
40
41        ip_address = c[Config.key_address.value]
42        port = c[Config.key_port.value]
43
44        for attempt_number in range(1, _ATTENUATOR_OPEN_RETRIES + 1):
45            try:
46                attn_inst.open(ip_address, port)
47            except Exception as e:
48                logging.error('Attempt %s to open connection to attenuator '
49                              'failed: %s' % (attempt_number, e))
50                if attempt_number == _ATTENUATOR_OPEN_RETRIES:
51                    ping_output = job.run('ping %s -c 1 -w 1' % ip_address,
52                                          ignore_status=True)
53                    if ping_output.exit_status == 1:
54                        logging.error('Unable to ping attenuator at %s' %
55                                      ip_address)
56                    else:
57                        logging.error('Able to ping attenuator at %s' %
58                                      ip_address)
59                        job.run('echo "q" | telnet %s %s' % (ip_address, port),
60                                ignore_status=True)
61                    raise
62        for i in range(inst_cnt):
63            attn = Attenuator(attn_inst, idx=i)
64            if 'Paths' in c:
65                try:
66                    setattr(attn, 'path', c['Paths'][i])
67                except IndexError:
68                    logging.error('No path specified for attenuator %d.', i)
69                    raise
70            objs.append(attn)
71    return objs
72
73
74def get_info(attenuators):
75    """Get information on a list of Attenuator objects.
76
77    Args:
78        attenuators: A list of Attenuator objects.
79
80    Returns:
81        A list of dict, each representing info for Attenuator objects.
82    """
83    device_info = []
84    for attenuator in attenuators:
85        info = {
86            "Address": attenuator.instrument.address,
87            "Attenuator_Port": attenuator.idx
88        }
89        device_info.append(info)
90    return device_info
91
92
93def destroy(objs):
94    for attn in objs:
95        attn.instrument.close()
96
97
98def get_attenuators_for_device(device_attenuator_configs, attenuators,
99                               attenuator_key):
100    """Gets the list of attenuators associated to a specified device and builds
101    a list of the attenuator objects associated to the ip address in the
102    device's section of the ACTS config and the Attenuator's IP address.  In the
103    example below the access point object has an attenuator dictionary with
104    IP address associated to an attenuator object.  The address is the only
105    mandatory field and the 'attenuator_ports_wifi_2g' and
106    'attenuator_ports_wifi_5g' are the attenuator_key specified above.  These
107    can be anything and is sent in as a parameter to this function.  The numbers
108    in the list are ports that are in the attenuator object.  Below is an
109    standard Access_Point object and the link to a standard Attenuator object.
110    Notice the link is the IP address, which is why the IP address is mandatory.
111
112    "AccessPoint": [
113        {
114          "ssh_config": {
115            "user": "root",
116            "host": "192.168.42.210"
117          },
118          "Attenuator": [
119            {
120              "Address": "192.168.42.200",
121              "attenuator_ports_wifi_2g": [
122                0,
123                1,
124                3
125              ],
126              "attenuator_ports_wifi_5g": [
127                0,
128                1
129              ]
130            }
131          ]
132        }
133      ],
134      "Attenuator": [
135        {
136          "Model": "minicircuits",
137          "InstrumentCount": 4,
138          "Address": "192.168.42.200",
139          "Port": 23
140        }
141      ]
142    Args:
143        device_attenuator_configs: A list of attenuators config information in
144            the acts config that are associated a particular device.
145        attenuators: A list of all of the available attenuators objects
146            in the testbed.
147        attenuator_key: A string that is the key to search in the device's
148            configuration.
149
150    Returns:
151        A list of attenuator objects for the specified device and the key in
152        that device's config.
153    """
154    attenuator_list = []
155    for device_attenuator_config in device_attenuator_configs:
156        for attenuator_port in device_attenuator_config[attenuator_key]:
157            for attenuator in attenuators:
158                if (attenuator.instrument.address ==
159                        device_attenuator_config['Address']
160                        and attenuator.idx is attenuator_port):
161                    attenuator_list.append(attenuator)
162    return attenuator_list
163
164
165"""Classes for accessing, managing, and manipulating attenuators.
166
167Users will instantiate a specific child class, but almost all operation should
168be performed on the methods and data members defined here in the base classes
169or the wrapper classes.
170"""
171
172
173class AttenuatorError(Exception):
174    """Base class for all errors generated by Attenuator-related modules."""
175
176
177class InvalidDataError(AttenuatorError):
178    """"Raised when an unexpected result is seen on the transport layer.
179
180    When this exception is seen, closing an re-opening the link to the
181    attenuator instrument is probably necessary. Something has gone wrong in
182    the transport.
183    """
184    pass
185
186
187class InvalidOperationError(AttenuatorError):
188    """Raised when the attenuator's state does not allow the given operation.
189
190    Certain methods may only be accessed when the instance upon which they are
191    invoked is in a certain state. This indicates that the object is not in the
192    correct state for a method to be called.
193    """
194    pass
195
196
197class AttenuatorInstrument(object):
198    """Defines the primitive behavior of all attenuator instruments.
199
200    The AttenuatorInstrument class is designed to provide a simple low-level
201    interface for accessing any step attenuator instrument comprised of one or
202    more attenuators and a controller. All AttenuatorInstruments should override
203    all the methods below and call AttenuatorInstrument.__init__ in their
204    constructors. Outside of setup/teardown, devices should be accessed via
205    this generic "interface".
206    """
207    model = None
208    INVALID_MAX_ATTEN = 999.9
209
210    def __init__(self, num_atten=0):
211        """This is the Constructor for Attenuator Instrument.
212
213        Args:
214            num_atten: The number of attenuators contained within the
215                instrument. In some instances setting this number to zero will
216                allow the driver to auto-determine the number of attenuators;
217                however, this behavior is not guaranteed.
218
219        Raises:
220            NotImplementedError if initialization is called from this class.
221        """
222
223        if type(self) is AttenuatorInstrument:
224            raise NotImplementedError(
225                'Base class should not be instantiated directly!')
226
227        self.num_atten = num_atten
228        self.max_atten = AttenuatorInstrument.INVALID_MAX_ATTEN
229        self.properties = None
230
231    def set_atten(self, idx, value, strict=True, retry=False):
232        """Sets the attenuation given its index in the instrument.
233
234        Args:
235            idx: A zero based index used to identify a particular attenuator in
236                an instrument.
237            value: a floating point value for nominal attenuation to be set.
238            strict: if True, function raises an error when given out of
239                bounds attenuation values, if false, the function sets out of
240                bounds values to 0 or max_atten.
241            retry: if True, command will be retried if possible
242        """
243        raise NotImplementedError('Base class should not be called directly!')
244
245    def get_atten(self, idx, retry=False):
246        """Returns the current attenuation of the attenuator at index idx.
247
248        Args:
249            idx: A zero based index used to identify a particular attenuator in
250                an instrument.
251            retry: if True, command will be retried if possible
252
253        Returns:
254            The current attenuation value as a floating point value
255        """
256        raise NotImplementedError('Base class should not be called directly!')
257
258
259class Attenuator(object):
260    """An object representing a single attenuator in a remote instrument.
261
262    A user wishing to abstract the mapping of attenuators to physical
263    instruments should use this class, which provides an object that abstracts
264    the physical implementation and allows the user to think only of attenuators
265    regardless of their location.
266    """
267
268    def __init__(self, instrument, idx=0, offset=0):
269        """This is the constructor for Attenuator
270
271        Args:
272            instrument: Reference to an AttenuatorInstrument on which the
273                Attenuator resides
274            idx: This zero-based index is the identifier for a particular
275                attenuator in an instrument.
276            offset: A power offset value for the attenuator to be used when
277                performing future operations. This could be used for either
278                calibration or to allow group operations with offsets between
279                various attenuators.
280
281        Raises:
282            TypeError if an invalid AttenuatorInstrument is passed in.
283            IndexError if the index is out of range.
284        """
285        if not isinstance(instrument, AttenuatorInstrument):
286            raise TypeError('Must provide an Attenuator Instrument Ref')
287        self.model = instrument.model
288        self.instrument = instrument
289        self.idx = idx
290        self.offset = offset
291
292        if self.idx >= instrument.num_atten:
293            raise IndexError(
294                'Attenuator index out of range for attenuator instrument')
295
296    def set_atten(self, value, strict=True, retry=False):
297        """Sets the attenuation.
298
299        Args:
300            value: A floating point value for nominal attenuation to be set.
301            strict: if True, function raises an error when given out of
302                bounds attenuation values, if false, the function sets out of
303                bounds values to 0 or max_atten.
304            retry: if True, command will be retried if possible
305
306        Raises:
307            ValueError if value + offset is greater than the maximum value.
308        """
309        if value + self.offset > self.instrument.max_atten and strict:
310            raise ValueError(
311                'Attenuator Value+Offset greater than Max Attenuation!')
312
313        self.instrument.set_atten(self.idx,
314                                  value + self.offset,
315                                  strict=strict,
316                                  retry=retry)
317
318    def get_atten(self, retry=False):
319        """Returns the attenuation as a float, normalized by the offset."""
320        return self.instrument.get_atten(self.idx, retry) - self.offset
321
322    def get_max_atten(self):
323        """Returns the max attenuation as a float, normalized by the offset."""
324        if self.instrument.max_atten == AttenuatorInstrument.INVALID_MAX_ATTEN:
325            raise ValueError('Invalid Max Attenuator Value')
326
327        return self.instrument.max_atten - self.offset
328
329
330class AttenuatorGroup(object):
331    """An abstraction for groups of attenuators that will share behavior.
332
333    Attenuator groups are intended to further facilitate abstraction of testing
334    functions from the physical objects underlying them. By adding attenuators
335    to a group, it is possible to operate on functional groups that can be
336    thought of in a common manner in the test. This class is intended to provide
337    convenience to the user and avoid re-implementation of helper functions and
338    small loops scattered throughout user code.
339    """
340
341    def __init__(self, name=''):
342        """This constructor for AttenuatorGroup
343
344        Args:
345            name: An optional parameter intended to further facilitate the
346                passing of easily tracked groups of attenuators throughout code.
347                It is left to the user to use the name in a way that meets their
348                needs.
349        """
350        self.name = name
351        self.attens = []
352        self._value = 0
353
354    def add_from_instrument(self, instrument, indices):
355        """Adds an AttenuatorInstrument to the group.
356
357        This function will create Attenuator objects for all of the indices
358        passed in and add them to the group.
359
360        Args:
361            instrument: the AttenuatorInstrument to pull attenuators from.
362                indices: The index or indices to add to the group. Either a
363                range, a list, or a single integer.
364
365        Raises
366        ------
367        TypeError
368            Requires a valid AttenuatorInstrument to be passed in.
369        """
370        if not instrument or not isinstance(instrument, AttenuatorInstrument):
371            raise TypeError('Must provide an Attenuator Instrument Ref')
372
373        if type(indices) is range or type(indices) is list:
374            for i in indices:
375                self.attens.append(Attenuator(instrument, i))
376        elif type(indices) is int:
377            self.attens.append(Attenuator(instrument, indices))
378
379    def add(self, attenuator):
380        """Adds an already constructed Attenuator object to this group.
381
382        Args:
383            attenuator: An Attenuator object.
384
385        Raises:
386            TypeError if the attenuator parameter is not an Attenuator.
387        """
388        if not isinstance(attenuator, Attenuator):
389            raise TypeError('Must provide an Attenuator')
390
391        self.attens.append(attenuator)
392
393    def synchronize(self):
394        """Sets all grouped attenuators to the group's attenuation value."""
395        self.set_atten(self._value)
396
397    def is_synchronized(self):
398        """Returns true if all attenuators have the synchronized value."""
399        for att in self.attens:
400            if att.get_atten() != self._value:
401                return False
402        return True
403
404    def set_atten(self, value):
405        """Sets the attenuation value of all attenuators in the group.
406
407        Args:
408            value: A floating point value for nominal attenuation to be set.
409        """
410        value = float(value)
411        for att in self.attens:
412            att.set_atten(value)
413        self._value = value
414
415    def get_atten(self):
416        """Returns the current attenuation setting of AttenuatorGroup."""
417        return float(self._value)
418