1#!/usr/bin/env python3
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"""
17Class for HTTP control of Mini-Circuits RCDAT series attenuators
18
19This class provides a wrapper to the MC-RCDAT attenuator modules for purposes
20of simplifying and abstracting control down to the basic necessities. It is
21not the intention of the module to expose all functionality, but to allow
22interchangeable HW to be used.
23
24See http://www.minicircuits.com/softwaredownload/Prog_Manual-6-Programmable_Attenuator.pdf
25"""
26
27import urllib
28from acts.controllers import attenuator
29
30
31class AttenuatorInstrument(attenuator.AttenuatorInstrument):
32    """A specific HTTP-controlled implementation of AttenuatorInstrument for
33    Mini-Circuits RC-DAT attenuators.
34
35    With the exception of HTTP-specific commands, all functionality is defined
36    by the AttenuatorInstrument class.
37    """
38
39    def __init__(self, num_atten=1):
40        super(AttenuatorInstrument, self).__init__(num_atten)
41        self._ip_address = None
42        self._port = None
43        self._timeout = None
44        self.address = None
45
46    def open(self, host, port=80, timeout=2):
47        """Initializes the AttenuatorInstrument and queries basic information.
48
49        Args:
50            host: A valid hostname (IP address or DNS-resolvable name) to an
51            MC-DAT attenuator instrument.
52            port: An optional port number (defaults to http default 80)
53            timeout: An optional timeout for http requests
54        """
55        self._ip_address = host
56        self._port = port
57        self._timeout = timeout
58        self.address = host
59
60        att_req = urllib.request.urlopen('http://{}:{}/MN?'.format(
61            self._ip_address, self._port))
62        config_str = att_req.read().decode('utf-8')
63        if not config_str.startswith('MN='):
64            raise attenuator.InvalidDataError(
65                'Attenuator returned invalid data. Attenuator returned: {}'.
66                format(config_str))
67
68        config_str = config_str[len('MN='):]
69        self.properties = dict(
70            zip(['model', 'max_freq', 'max_atten'], config_str.split('-', 2)))
71        self.max_atten = float(self.properties['max_atten'])
72
73    def is_open(self):
74        """Returns True if the AttenuatorInstrument has an open connection.
75
76        Since this controller is based on HTTP requests, there is no connection
77        required and the attenuator is always ready to accept requests.
78        """
79        return True
80
81    def close(self):
82        """Closes the connection to the attenuator.
83
84        Since this controller is based on HTTP requests, there is no connection
85        teardowns required.
86        """
87        pass
88
89    def set_atten(self, idx, value, strict_flag=True):
90        """This function sets the attenuation of an attenuator given its index
91        in the instrument.
92
93        Args:
94            idx: A zero-based index that identifies a particular attenuator in
95                an instrument. For instruments that only have one channel, this
96                is ignored by the device.
97            value: A floating point value for nominal attenuation to be set.
98            strict_flag: if True, function raises an error when given out of
99                bounds attenuation values, if false, the function sets out of
100                bounds values to 0 or max_atten.
101
102        Raises:
103            InvalidDataError if the attenuator does not respond with the
104            expected output.
105        """
106        if not (0 <= idx < self.num_atten):
107            raise IndexError('Attenuator index out of range!', self.num_atten,
108                             idx)
109
110        if value > self.max_atten and strict_flag:
111            raise ValueError('Attenuator value out of range!', self.max_atten,
112                             value)
113        # The actual device uses one-based index for channel numbers.
114        att_req = urllib.request.urlopen(
115            'http://{}:{}/CHAN:{}:SETATT:{}'.format(
116                self._ip_address, self._port, idx + 1, value),
117            timeout=self._timeout)
118        att_resp = att_req.read().decode('utf-8')
119        if att_resp != '1':
120            raise attenuator.InvalidDataError(
121                'Attenuator returned invalid data. Attenuator returned: {}'.
122                format(att_resp))
123
124    def get_atten(self, idx):
125        """Returns the current attenuation of the attenuator at the given index.
126
127        Args:
128            idx: The index of the attenuator.
129
130        Raises:
131            InvalidDataError if the attenuator does not respond with the
132            expected outpu
133
134        Returns:
135            the current attenuation value as a float
136        """
137        if not (0 <= idx < self.num_atten):
138            raise IndexError('Attenuator index out of range!', self.num_atten,
139                             idx)
140        att_req = urllib.request.urlopen(
141            'http://{}:{}/CHAN:{}:ATT?'.format(self._ip_address, self.port,
142                                               idx + 1),
143            timeout=self._timeout)
144        att_resp = att_req.read().decode('utf-8')
145        try:
146            atten_val = float(att_resp)
147        except:
148            raise attenuator.InvalidDataError(
149                'Attenuator returned invalid data. Attenuator returned: {}'.
150                format(att_resp))
151        return atten_val
152