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