• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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