1#!/usr/bin/env python3 2# 3# Copyright (C) 2019 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# 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, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16"""Stream music through connected device from phone across different 17attenuations.""" 18 19import json 20import math 21import time 22import logging 23import acts.controllers.iperf_client as ipc 24import acts.controllers.iperf_server as ipf 25import acts_contrib.test_utils.bt.bt_test_utils as btutils 26from acts import asserts 27from acts_contrib.test_utils.bt.A2dpBaseTest import A2dpBaseTest 28from acts_contrib.test_utils.bt.loggers import bluetooth_metric_logger as log 29from acts_contrib.test_utils.wifi import wifi_performance_test_utils as wpeutils 30from acts_contrib.test_utils.wifi import wifi_power_test_utils as wputils 31from acts_contrib.test_utils.wifi import wifi_test_utils as wutils 32from acts_contrib.test_utils.power.PowerBaseTest import ObjNew 33 34MAX_ATTENUATION = 95 35TEMP_FILE = '/sdcard/Download/tmp.log' 36IPERF_CLIENT_ERROR = 'the client has unexpectedly closed the connection' 37 38 39def setup_ap_connection(dut, network, ap, bandwidth=20): 40 """Setup AP and connect DUT to it. 41 42 Args: 43 dut: the android device to connect and run traffic 44 network: the network config for the AP to be setup 45 ap: access point object 46 bandwidth: bandwidth of the WiFi network to be setup 47 Returns: 48 brconfigs: dict for bridge interface configs 49 """ 50 wutils.wifi_toggle_state(dut, True) 51 brconfigs = wputils.ap_setup(ap, network, bandwidth=bandwidth) 52 wutils.wifi_connect(dut, network, num_of_tries=3) 53 return brconfigs 54 55 56def start_iperf_client(traffic_pair_obj, duration): 57 """Setup iperf traffic for TCP downlink. 58 Args: 59 traffic_pair_obj: obj to contain info on traffic pair 60 duration: duration of iperf traffic to run 61 """ 62 # Construct the iperf command based on the test params 63 iperf_cmd = 'iperf3 -c {} -i 1 -t {} -p {} -J -R > {}'.format( 64 traffic_pair_obj.server_address, duration, 65 traffic_pair_obj.iperf_server.port, TEMP_FILE) 66 # Start IPERF client 67 traffic_pair_obj.dut.adb.shell_nb(iperf_cmd) 68 69 70def unpack_custom_file(file): 71 """Unpack the json file to . 72 73 Args: 74 file: custom json file. 75 """ 76 with open(file, 'r') as f: 77 params = json.load(f) 78 return params 79 80 81def get_iperf_results(iperf_server_obj): 82 """Get the iperf results and process. 83 84 Args: 85 iperf_server_obj: the IperfServer object 86 Returns: 87 throughput: the average throughput during tests. 88 """ 89 # Get IPERF results and add this to the plot title 90 iperf_file = iperf_server_obj.log_files[-1] 91 try: 92 iperf_result = ipf.IPerfResult(iperf_file) 93 # Compute the throughput in Mbit/s 94 if iperf_result.error == IPERF_CLIENT_ERROR: 95 rates = [] 96 for item in iperf_result.result['intervals']: 97 rates.append(item['sum']['bits_per_second'] / 8 / 1024 / 1024) 98 throughput = ((math.fsum(rates) / len(rates))) * 8 * (1.024**2) 99 else: 100 throughput = (math.fsum(iperf_result.instantaneous_rates) / len( 101 iperf_result.instantaneous_rates)) * 8 * (1.024**2) 102 except (ValueError, TypeError): 103 throughput = 0 104 return throughput 105 106 107def locate_interference_pair_by_channel(wifi_int_pairs, interference_channels): 108 """Function to find which attenautor to set based on channel info 109 Args: 110 interference_channels: list of interference channels 111 Return: 112 interference_pair_indices: list of indices for interference pair 113 in wifi_int_pairs 114 """ 115 interference_pair_indices = [] 116 for chan in interference_channels: 117 for i in range(len(wifi_int_pairs)): 118 if wifi_int_pairs[i].channel == chan: 119 interference_pair_indices.append(i) 120 return interference_pair_indices 121 122 123def inject_static_wifi_interference(wifi_int_pairs, interference_level, 124 channels): 125 """Function to inject wifi interference to bt link and read rssi. 126 127 Interference of IPERF traffic is always running, by setting attenuation, 128 the gate is opened to release the interference to the setup. 129 Args: 130 interference_level: the signal strength of wifi interference, use 131 attenuation level to represent this 132 channels: wifi channels where interference will 133 be injected, list 134 """ 135 all_pair = range(len(wifi_int_pairs)) 136 interference_pair_indices = locate_interference_pair_by_channel( 137 wifi_int_pairs, channels) 138 inactive_interference_pairs_indices = [ 139 item for item in all_pair if item not in interference_pair_indices 140 ] 141 logging.info('WiFi interference at {} and inactive channels at {}'.format( 142 interference_pair_indices, inactive_interference_pairs_indices)) 143 for i in interference_pair_indices: 144 wifi_int_pairs[i].attenuator.set_atten(interference_level) 145 logging.info('Set attenuation {} dB on attenuator {}'.format( 146 wifi_int_pairs[i].attenuator.get_atten(), i + 1)) 147 for i in inactive_interference_pairs_indices: 148 wifi_int_pairs[i].attenuator.set_atten(MAX_ATTENUATION) 149 logging.info('Set attenuation {} dB on attenuator {}'.format( 150 wifi_int_pairs[i].attenuator.get_atten(), i + 1)) 151 152 153class BtInterferenceBaseTest(A2dpBaseTest): 154 def __init__(self, configs): 155 super().__init__(configs) 156 self.bt_logger = log.BluetoothMetricLogger.for_test_case() 157 self.start_time = time.time() 158 req_params = [ 159 'attenuation_vector', 'wifi_networks', 'codecs', 'custom_files', 160 'audio_params' 161 ] 162 self.unpack_userparams(req_params) 163 for file in self.custom_files: 164 if 'static_interference' in file: 165 self.static_wifi_interference = unpack_custom_file(file) 166 elif 'dynamic_interference' in file: 167 self.dynamic_wifi_interference = unpack_custom_file(file) 168 169 def setup_class(self): 170 super().setup_class() 171 # Build object to store all necessary information for each pair of wifi 172 # interference setup: phone, ap, network, channel, iperf server port/ip 173 # object and bridge interface configs 174 if len(self.android_devices) < 5 or len(self.attenuators) < 4: 175 self.log.error('Need a 4 channel attenuator and 5 android phones' 176 'please update the config file') 177 self.wifi_int_pairs = [] 178 for i in range(len(self.attenuators) - 1): 179 tmp_dict = { 180 'dut': self.android_devices[i + 1], 181 'ap': self.access_points[i], 182 'network': self.wifi_networks[i], 183 'channel': self.wifi_networks[i]['channel'], 184 'iperf_server': self.iperf_servers[i], 185 'attenuator': self.attenuators[i + 1], 186 'ether_int': self.packet_senders[i], 187 'iperf_client': 188 ipc.IPerfClientOverAdb(self.android_devices[i + 1]) 189 } 190 tmp_obj = ObjNew(**tmp_dict) 191 self.wifi_int_pairs.append(tmp_obj) 192 ##Setup connection between WiFi APs and Phones and get DHCP address 193 # for the interface 194 for obj in self.wifi_int_pairs: 195 brconfigs = setup_ap_connection(obj.dut, obj.network, obj.ap) 196 iperf_server_address = wputils.wait_for_dhcp( 197 obj.ether_int.interface) 198 setattr(obj, 'server_address', iperf_server_address) 199 setattr(obj, 'brconfigs', brconfigs) 200 obj.attenuator.set_atten(MAX_ATTENUATION) 201 # Enable BQR on master and slave Android device 202 btutils.enable_bqr(self.dut) 203 btutils.enable_bqr(self.bt_device_controller) 204 205 def teardown_class(self): 206 super().teardown_class() 207 for obj in self.wifi_int_pairs: 208 obj.ap.bridge.teardown(obj.brconfigs) 209 self.log.info('Stop IPERF server at port {}'.format( 210 obj.iperf_server.port)) 211 obj.iperf_server.stop() 212 self.log.info('Stop IPERF process on {}'.format(obj.dut.serial)) 213 #only for glinux machine 214 # wputils.bring_down_interface(obj.ether_int.interface) 215 obj.attenuator.set_atten(MAX_ATTENUATION) 216 obj.ap.close() 217 218 def teardown_test(self): 219 220 super().teardown_test() 221 for obj in self.wifi_int_pairs: 222 obj.attenuator.set_atten(MAX_ATTENUATION) 223 224 def play_and_record_audio(self, duration, queue): 225 """Play and record audio for a set duration. 226 227 Args: 228 duration: duration in seconds for music playing 229 que: multiprocess que to store the return value of this function 230 Returns: 231 audio_captured: captured audio file path 232 """ 233 234 self.log.info('Play and record audio for {} second'.format(duration)) 235 self.media.play() 236 self.audio_device.start() 237 time.sleep(duration) 238 audio_captured = self.audio_device.stop() 239 self.media.stop() 240 self.log.info('Audio play and record stopped') 241 asserts.assert_true(audio_captured, 'Audio not recorded') 242 queue.put(audio_captured) 243 244 def locate_interference_pair_by_channel(self, interference_channels): 245 """Function to find which attenautor to set based on channel info 246 Args: 247 interference_channels: list of interference channels 248 Return: 249 interference_pair_indices: list of indices for interference pair 250 in self.wifi_int_pairs 251 """ 252 interference_pair_indices = [] 253 for chan in interference_channels: 254 for i in range(len(self.wifi_int_pairs)): 255 if self.wifi_int_pairs[i].channel == chan: 256 interference_pair_indices.append(i) 257 return interference_pair_indices 258 259 def get_interference_rssi(self): 260 """Function to read wifi interference RSSI level.""" 261 262 bssids = [] 263 self.interference_rssi = [] 264 wutils.wifi_toggle_state(self.android_devices[0], True) 265 for item in self.wifi_int_pairs: 266 ssid = item.network['SSID'] 267 bssid = item.ap.get_bssid_from_ssid(ssid, '2g') 268 bssids.append(bssid) 269 interference_rssi_dict = { 270 "ssid": ssid, 271 "bssid": bssid, 272 "chan": item.channel, 273 "rssi": 0 274 } 275 self.interference_rssi.append(interference_rssi_dict) 276 scaned_rssi = wpeutils.get_scan_rssi(self.android_devices[0], 277 bssids, 278 num_measurements=2) 279 for item in self.interference_rssi: 280 item['rssi'] = scaned_rssi[item['bssid']]['mean'] 281 self.log.info('Interference RSSI at channel {} is {} dBm'.format( 282 item['chan'], item['rssi'])) 283 wutils.wifi_toggle_state(self.android_devices[0], False) 284