1#!/usr/bin/env python3 2# 3# Copyright 2018 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 17import logging 18import time 19import datetime 20import statistics 21import os 22from acts_contrib.test_utils.bt.bt_constants import advertising_set_started 23import acts_contrib.test_utils.wifi.wifi_performance_test_utils.bokeh_figure as bokeh_figure 24from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_phys 25from acts_contrib.test_utils.bt.bt_constants import ble_scan_settings_modes 26from acts_contrib.test_utils.bt.bt_gatt_utils import close_gatt_client 27from acts_contrib.test_utils.bt.bt_coc_test_utils import do_multi_connection_throughput 28from acts_contrib.test_utils.bt.bt_gatt_utils import disconnect_gatt_connection 29from queue import Empty 30from acts_contrib.test_utils.bt.bt_constants import gatt_cb_err 31from acts_contrib.test_utils.bt.bt_constants import gatt_cb_strings 32from acts_contrib.test_utils.bt.bt_constants import l2cap_coc_header_size 33from acts_contrib.test_utils.bt.bt_gatt_utils import GattTestUtilsError 34from acts_contrib.test_utils.bt.bt_test_utils import generate_ble_scan_objects 35from acts_contrib.test_utils.bt.bt_coc_test_utils import orchestrate_coc_connection 36from acts_contrib.test_utils.bt.bt_gatt_utils import orchestrate_gatt_connection 37from concurrent.futures import ThreadPoolExecutor 38 39default_event_timeout = 10 40rssi_read_duration = 25 41 42 43def establish_ble_connection(client_ad, server_ad): 44 """Function to establish BLE connection between two BLE devices. 45 46 Args: 47 client_ad: the Android device performing the connection. 48 server_ad: the Android device accepting the connection. 49 Returns: 50 bluetooth_gatt: GATT object 51 gatt_callback: Gatt callback object 52 adv_callback: advertisement callback object 53 gatt_server: the gatt server 54 """ 55 gatt_server_cb = server_ad.droid.gattServerCreateGattServerCallback() 56 gatt_server = server_ad.droid.gattServerOpenGattServer(gatt_server_cb) 57 try: 58 bluetooth_gatt, gatt_callback, adv_callback = ( 59 orchestrate_gatt_connection(client_ad, server_ad)) 60 except GattTestUtilsError as err: 61 logging.error(err) 62 return False 63 return bluetooth_gatt, gatt_callback, adv_callback, gatt_server 64 65 66def read_ble_rssi(client_ad, gatt_server, gatt_callback): 67 """Function to Read BLE RSSI of the remote BLE device. 68 Args: 69 client_ad: the Android device performing the connection. 70 gatt_server: the gatt server 71 gatt_callback:the gatt connection call back object 72 Returns: 73 ble_rssi: RSSI value of the remote BLE device 74 """ 75 AVG_RSSI = [] 76 end_time = time.time() + rssi_read_duration 77 logging.info("Reading BLE RSSI for {} sec".format(rssi_read_duration)) 78 while time.time() < end_time: 79 expected_event = gatt_cb_strings['rd_remote_rssi'].format( 80 gatt_callback) 81 read_rssi = client_ad.droid.gattClientReadRSSI(gatt_server) 82 if read_rssi: 83 try: 84 event = client_ad.ed.pop_event(expected_event, 85 default_event_timeout) 86 except Empty: 87 logging.error( 88 gatt_cb_err['rd_remote_rssi_err'].format(expected_event)) 89 return False 90 rssi_value = event['data']['Rssi'] 91 AVG_RSSI.append(rssi_value) 92 logging.debug("First & Last reading of RSSI :{:03d} & {:03d}".format( 93 AVG_RSSI[0], AVG_RSSI[-1])) 94 ble_rssi = statistics.mean(AVG_RSSI) 95 ble_rssi = round(ble_rssi, 2) 96 97 return ble_rssi 98 99 100def read_ble_scan_rssi(client_ad, scan_callback, rssi_read_duration=30): 101 """Function to Read BLE RSSI of the remote BLE device. 102 Args: 103 client_ad: the Android device performing the connection. 104 scan_callback: the scan callback of the server 105 Returns: 106 ble_rssi: RSSI value of the remote BLE device 107 raw_rssi: RSSI list of remote BLE device 108 """ 109 raw_rssi = [] 110 timestamp = [] 111 end_time = time.time() + rssi_read_duration 112 logging.info("Reading BLE Scan RSSI for {} sec".format(rssi_read_duration)) 113 while time.time() < end_time: 114 expected_event = gatt_cb_strings['rd_remote_ble_rssi'].format( 115 scan_callback) 116 try: 117 event = client_ad.ed.pop_event(expected_event, 118 default_event_timeout) 119 except Empty: 120 logging.error( 121 gatt_cb_err['rd_remote_rssi_err'].format(expected_event)) 122 return False 123 rssi_value = event['data']['Result']['rssi'] 124 epoch_time = event['time'] 125 d = datetime.datetime.fromtimestamp(epoch_time / 1000) 126 tstamp = d.strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] 127 timestamp.append(tstamp) 128 raw_rssi.append(rssi_value) 129 logging.debug("First & Last reading of RSSI :{:03d} & {:03d}".format( 130 raw_rssi[0], raw_rssi[-1])) 131 ble_rssi = statistics.mean(raw_rssi) 132 ble_rssi = round(ble_rssi, 2) 133 134 return ble_rssi, raw_rssi, timestamp 135 136 137def ble_coc_connection(client_ad, server_ad): 138 """Sets up the CoC connection between two Android devices. 139 140 Args: 141 client_ad: the Android device performing the connection. 142 server_ad: the Android device accepting the connection. 143 144 Returns: 145 True if connection was successful or false if unsuccessful, 146 gatt_callback: GATT callback object 147 client connection ID: Client connection ID 148 and server connection ID : server connection ID 149 """ 150 # secured_conn: True if using secured connection 151 # le_connection_interval: LE Connection interval. 0 means use default. 152 # buffer_size : is the number of bytes per L2CAP data buffer 153 # le_tx_data_length: LE Data Length used by BT Controller to transmit. 154 is_secured = False 155 le_connection_interval = 30 156 buffer_size = 240 157 le_tx_data_length = buffer_size + l2cap_coc_header_size 158 gatt_server_cb = server_ad.droid.gattServerCreateGattServerCallback() 159 gatt_server = server_ad.droid.gattServerOpenGattServer(gatt_server_cb) 160 161 logging.info( 162 "orchestrate_ble_coc_connection. is_secured={}, Connection Interval={}msec, " 163 "buffer_size={}bytes".format(is_secured, le_connection_interval, 164 buffer_size)) 165 try: 166 status, client_conn_id, server_conn_id, bluetooth_gatt, gatt_callback = orchestrate_coc_connection( 167 client_ad, 168 server_ad, 169 True, 170 is_secured, 171 le_connection_interval, 172 le_tx_data_length, 173 gatt_disconnection=False) 174 except Exception as err: 175 logging.info("Failed to esatablish COC connection".format(err)) 176 return 0 177 return True, gatt_callback, gatt_server, bluetooth_gatt, client_conn_id 178 179 180def run_ble_throughput(server_ad, 181 client_conn_id, 182 client_ad, 183 num_iterations=30): 184 """Function to measure Throughput from one client to one-or-many servers 185 186 Args: 187 server_ad: the Android device accepting the connection. 188 client_conn_id: the client connection ID. 189 client_ad: the Android device performing the connection. 190 num_iterations: The num_iterations is that number of repetitions of each 191 set of buffers r/w. 192 Returns: 193 data_rate: Throughput in terms of bytes per second, 0 if test failed. 194 """ 195 # number_buffers is the total number of data buffers to transmit per 196 # set of buffers r/w. 197 # buffer_size is the number of bytes per L2CAP data buffer. 198 number_buffers = 100 199 buffer_size = 240 200 list_server_ad = [server_ad] 201 list_client_conn_id = [client_conn_id] 202 data_rate = do_multi_connection_throughput(client_ad, list_server_ad, 203 list_client_conn_id, 204 num_iterations, number_buffers, 205 buffer_size) 206 if data_rate <= 0: 207 return False 208 data_rate = data_rate * 8 209 logging.info( 210 "run_ble_coc_connection_throughput: throughput=%d bites per sec", 211 data_rate) 212 return data_rate 213 214 215def run_ble_throughput_and_read_rssi(client_ad, server_ad, client_conn_id, 216 gatt_server, gatt_callback): 217 """Function to measure ble rssi while sendinng data from client to server 218 219 Args: 220 client_ad: the Android device performing the connection. 221 server_ad: the Android device accepting the connection. 222 client_conn_id: the client connection ID. 223 gatt_server: the gatt server 224 gatt_callback: Gatt callback object 225 Returns: 226 ble_rssi: RSSI value of the remote BLE device. 227 """ 228 executor = ThreadPoolExecutor(2) 229 ble_throughput = executor.submit(run_ble_throughput, client_ad, 230 client_conn_id, server_ad) 231 ble_rssi = executor.submit(read_ble_rssi, server_ad, gatt_server, 232 gatt_callback) 233 logging.info("BLE RSSI is:{} dBm with data rate={} bites per sec ".format( 234 ble_rssi.result(), ble_throughput.result())) 235 return ble_rssi.result() 236 237 238def ble_gatt_disconnection(client_ad, bluetooth_gatt, gatt_callback): 239 """Function to disconnect GATT connection between client and server. 240 241 Args: 242 client_ad: the Android device performing the connection. 243 bluetooth_gatt: GATT object 244 gatt_callback:the gatt connection call back object 245 Returns: 246 ble_rssi: RSSI value of the remote BLE device 247 """ 248 logging.info("Disconnecting from peripheral device.") 249 try: 250 disconnect_gatt_connection(client_ad, bluetooth_gatt, gatt_callback) 251 close_gatt_client(client_ad, bluetooth_gatt) 252 except GattTestUtilsError as err: 253 logging.error(err) 254 return False 255 return True 256 257 258def plot_graph(df, plot_data, bokeh_data, secondary_y_label=None): 259 """ Plotting for generating bokeh figure 260 261 Args: 262 df: Summary of results contains attenuation, DUT RSSI, remote RSSI and Tx Power 263 plot_data: plot_data for adding line to existing BokehFigure 264 bokeh_data: bokeh data for generating BokehFigure 265 secondary_y_label : label for secondary y axis , None if not available 266 """ 267 plot = bokeh_figure.BokehFigure( 268 title='{}'.format(bokeh_data['current_test_name']), 269 x_label=bokeh_data['x_label'], 270 primary_y_label=bokeh_data['primary_y_label'], 271 secondary_y_label=secondary_y_label, 272 axis_label_size='16pt', 273 legend_label_size='16pt', 274 axis_tick_label_size='16pt', 275 sizing_mode='stretch_both') 276 277 for data in plot_data: 278 plot.add_line(df[plot_data[data].get('x_column')], 279 df[plot_data[data].get('y_column')], 280 legend=plot_data[data].get('legend'), 281 marker=plot_data[data].get('marker'), 282 y_axis=plot_data[data].get('y_axis')) 283 284 results_file_path = os.path.join( 285 bokeh_data['log_path'], 286 '{}.html'.format(bokeh_data['current_test_name'])) 287 plot.generate_figure() 288 bokeh_figure.BokehFigure.save_figures([plot], results_file_path) 289 290 291def start_advertising_and_scanning(client_ad, server_ad, Legacymode=True): 292 """Function to start bt5 advertisement. 293 294 Args: 295 client_ad: the Android device performing the scanning. 296 server_ad: the Android device performing the bt advertising 297 Legacymode: True for Legacy advertising mode, false for bt5 advertising mode 298 Returns: 299 adv_callback: the advertising callback 300 scan_callback: the scan_callback 301 """ 302 adv_callback = server_ad.droid.bleAdvSetGenCallback() 303 adv_data = { 304 "includeDeviceName": True, 305 } 306 server_ad.droid.bleAdvSetStartAdvertisingSet( 307 { 308 "connectable": False, 309 "legacyMode": Legacymode, 310 "primaryPhy": "PHY_LE_1M", 311 "secondaryPhy": "PHY_LE_1M", 312 "interval": 320 313 }, adv_data, None, None, None, 0, 0, adv_callback) 314 server_ad.ed.pop_event(advertising_set_started.format(adv_callback), 315 default_event_timeout) 316 logging.info("Bt5 Advertiser Started Successfully") 317 client_ad.droid.bleSetScanSettingsLegacy(False) 318 client_ad.droid.bleSetScanSettingsScanMode( 319 ble_scan_settings_modes['low_latency']) 320 client_ad.droid.bleSetScanSettingsPhy(ble_scan_settings_phys['1m']) 321 322 filter_list, scan_settings, scan_callback = generate_ble_scan_objects( 323 client_ad.droid) 324 adv_device_name = server_ad.droid.bluetoothGetLocalName() 325 client_ad.droid.bleSetScanFilterDeviceName(adv_device_name) 326 client_ad.droid.bleBuildScanFilter(filter_list) 327 client_ad.droid.bleStartBleScan(filter_list, scan_settings, scan_callback) 328 return adv_callback, scan_callback 329