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').strip() 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 88 def set_atten(self, idx, value, strict=True, retry=False, **_): 89 """This function sets the attenuation of an attenuator given its index 90 in the instrument. 91 92 Args: 93 idx: A zero-based index that identifies a particular attenuator in 94 an instrument. For instruments that only have one channel, this 95 is ignored by the device. 96 value: A floating point value for nominal attenuation to be set. 97 strict: if True, function raises an error when given out of 98 bounds attenuation values, if false, the function sets out of 99 bounds values to 0 or max_atten. 100 retry: if True, command will be retried if possible 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: 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 adjusted_value = min(max(0, value), self.max_atten) 115 att_req = urllib.request.urlopen( 116 'http://{}:{}/CHAN:{}:SETATT:{}'.format(self._ip_address, 117 self._port, idx + 1, 118 adjusted_value), 119 timeout=self._timeout) 120 att_resp = att_req.read().decode('utf-8').strip() 121 if att_resp != '1': 122 if retry: 123 self.set_atten(idx, value, strict, retry=False) 124 else: 125 raise attenuator.InvalidDataError( 126 'Attenuator returned invalid data. Attenuator returned: {}' 127 .format(att_resp)) 128 129 def get_atten(self, idx, retry=False, **_): 130 """Returns the current attenuation of the attenuator at the given index. 131 132 Args: 133 idx: The index of the attenuator. 134 retry: if True, command will be retried if possible 135 136 Raises: 137 InvalidDataError if the attenuator does not respond with the 138 expected outpu 139 140 Returns: 141 the current attenuation value as a float 142 """ 143 if not (0 <= idx < self.num_atten): 144 raise IndexError('Attenuator index out of range!', self.num_atten, 145 idx) 146 att_req = urllib.request.urlopen( 147 'http://{}:{}/CHAN:{}:ATT?'.format(self._ip_address, self.port, idx + 1), 148 timeout=self._timeout) 149 att_resp = att_req.read().decode('utf-8').strip() 150 try: 151 atten_val = float(att_resp) 152 except: 153 if retry: 154 self.get_atten(idx, retry=False) 155 else: 156 raise attenuator.InvalidDataError( 157 'Attenuator returned invalid data. Attenuator returned: {}' 158 .format(att_resp)) 159 return atten_val 160