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