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