1#!/usr/bin/env python3 2# 3# Copyright (C) 2016 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""" 17Ble libraries 18""" 19 20import time 21import queue 22import logging 23 24from cert.bt_constants import ble_advertise_settings_modes 25from cert.bt_constants import small_timeout 26from cert.bt_constants import adv_fail 27from cert.bt_constants import adv_succ 28from cert.bt_constants import advertising_set_on_own_address_read 29from cert.bt_constants import advertising_set_started 30from cert.bt_constants import bluetooth_on 31from cert.bt_constants import bluetooth_off 32from cert.bt_constants import bt_default_timeout 33 34 35def enable_bluetooth(droid, ed): 36 if droid.bluetoothCheckState(): 37 return True 38 39 droid.bluetoothToggleState(True) 40 expected_bluetooth_on_event_name = bluetooth_on 41 try: 42 ed.pop_event(expected_bluetooth_on_event_name, bt_default_timeout) 43 except Exception: 44 logging.info("Failed to toggle Bluetooth on (no broadcast received)") 45 if droid.bluetoothCheckState(): 46 logging.info(".. actual state is ON") 47 return True 48 logging.info(".. actual state is OFF") 49 return False 50 51 return True 52 53 54def disable_bluetooth(droid, ed): 55 if not droid.bluetoothCheckState(): 56 return True 57 droid.bluetoothToggleState(False) 58 expected_bluetooth_off_event_name = bluetooth_off 59 try: 60 ed.pop_event(expected_bluetooth_off_event_name, bt_default_timeout) 61 except Exception: 62 logging.info("Failed to toggle Bluetooth off (no broadcast received)") 63 if droid.bluetoothCheckState(): 64 logging.info(".. actual state is ON") 65 return False 66 logging.info(".. actual state is OFF") 67 return True 68 return True 69 70 71def generate_ble_scan_objects(droid): 72 """Generate generic LE scan objects. 73 74 Args: 75 droid: The droid object to generate LE scan objects from. 76 77 Returns: 78 filter_list: The generated scan filter list id. 79 scan_settings: The generated scan settings id. 80 scan_callback: The generated scan callback id. 81 """ 82 filter_list = droid.bleGenFilterList() 83 scan_settings = droid.bleBuildScanSetting() 84 scan_callback = droid.bleGenScanCallback() 85 return filter_list, scan_settings, scan_callback 86 87 88def generate_ble_advertise_objects(droid): 89 """Generate generic LE advertise objects. 90 91 Args: 92 droid: The droid object to generate advertise LE objects from. 93 94 Returns: 95 advertise_callback: The generated advertise callback id. 96 advertise_data: The generated advertise data id. 97 advertise_settings: The generated advertise settings id. 98 """ 99 advertise_callback = droid.bleGenBleAdvertiseCallback() 100 advertise_data = droid.bleBuildAdvertiseData() 101 advertise_settings = droid.bleBuildAdvertiseSettings() 102 return advertise_callback, advertise_data, advertise_settings 103 104 105class BleLib(): 106 107 def __init__(self, log, dut): 108 self.advertisement_list = [] 109 self.dut = dut 110 self.log = log 111 self.default_timeout = 5 112 self.set_advertisement_list = [] 113 self.generic_uuid = "0000{}-0000-1000-8000-00805f9b34fb" 114 115 def _verify_ble_adv_started(self, advertise_callback): 116 """Helper for verifying if an advertisment started or not""" 117 regex = "({}|{})".format(adv_succ.format(advertise_callback), adv_fail.format(advertise_callback)) 118 try: 119 event = self.dut.ed.pop_events(regex, 5, small_timeout) 120 except queue.Empty: 121 self.dut.log.error("Failed to get success or failed event.") 122 return 123 if event[0]["name"] == adv_succ.format(advertise_callback): 124 self.dut.log.info("Advertisement started successfully.") 125 return True 126 else: 127 self.dut.log.info("Advertisement failed to start.") 128 return False 129 130 def start_generic_connectable_advertisement(self, line): 131 """Start a connectable LE advertisement""" 132 scan_response = None 133 if line: 134 scan_response = bool(line) 135 self.dut.droid.bleSetAdvertiseSettingsAdvertiseMode(ble_advertise_settings_modes['low_latency']) 136 self.dut.droid.bleSetAdvertiseSettingsIsConnectable(True) 137 advertise_callback, advertise_data, advertise_settings = (generate_ble_advertise_objects(self.dut.droid)) 138 if scan_response: 139 self.dut.droid.bleStartBleAdvertisingWithScanResponse(advertise_callback, advertise_data, 140 advertise_settings, advertise_data) 141 else: 142 self.dut.droid.bleStartBleAdvertising(advertise_callback, advertise_data, advertise_settings) 143 if self._verify_ble_adv_started(advertise_callback): 144 self.log.info("Tracking Callback ID: {}".format(advertise_callback)) 145 self.advertisement_list.append(advertise_callback) 146 self.log.info(self.advertisement_list) 147 148 def start_connectable_advertisement_set(self, line): 149 """Start Connectable Advertisement Set""" 150 adv_callback = self.dut.droid.bleAdvSetGenCallback() 151 adv_data = { 152 "includeDeviceName": True, 153 } 154 self.dut.droid.bleAdvSetStartAdvertisingSet({ 155 "connectable": True, 156 "legacyMode": False, 157 "primaryPhy": "PHY_LE_1M", 158 "secondaryPhy": "PHY_LE_1M", 159 "interval": 320 160 }, adv_data, None, None, None, 0, 0, adv_callback) 161 evt = self.dut.ed.pop_event(advertising_set_started.format(adv_callback), self.default_timeout) 162 set_id = evt['data']['setId'] 163 self.log.error("did not receive the set started event!") 164 evt = self.dut.ed.pop_event(advertising_set_on_own_address_read.format(set_id), self.default_timeout) 165 address = evt['data']['address'] 166 self.log.info("Advertiser address is: {}".format(str(address))) 167 self.set_advertisement_list.append(adv_callback) 168 169 def stop_all_advertisement_set(self, line): 170 """Stop all Advertisement Sets""" 171 for adv in self.set_advertisement_list: 172 try: 173 self.dut.droid.bleAdvSetStopAdvertisingSet(adv) 174 except Exception as err: 175 self.log.error("Failed to stop advertisement: {}".format(err)) 176 177 def adv_add_service_uuid_list(self, line): 178 """Add service UUID to the LE advertisement inputs: 179 [uuid1 uuid2 ... uuidN]""" 180 uuids = line.split() 181 uuid_list = [] 182 for uuid in uuids: 183 if len(uuid) == 4: 184 uuid = self.generic_uuid.format(line) 185 uuid_list.append(uuid) 186 self.dut.droid.bleSetAdvertiseDataSetServiceUuids(uuid_list) 187 188 def adv_data_include_local_name(self, is_included): 189 """Include local name in the advertisement. inputs: [true|false]""" 190 self.dut.droid.bleSetAdvertiseDataIncludeDeviceName(bool(is_included)) 191 192 def adv_data_include_tx_power_level(self, is_included): 193 """Include tx power level in the advertisement. inputs: [true|false]""" 194 self.dut.droid.bleSetAdvertiseDataIncludeTxPowerLevel(bool(is_included)) 195 196 def adv_data_add_manufacturer_data(self, line): 197 """Include manufacturer id and data to the advertisment: 198 [id data1 data2 ... dataN]""" 199 info = line.split() 200 manu_id = int(info[0]) 201 manu_data = [] 202 for data in info[1:]: 203 manu_data.append(int(data)) 204 self.dut.droid.bleAddAdvertiseDataManufacturerId(manu_id, manu_data) 205 206 def start_generic_nonconnectable_advertisement(self, line): 207 """Start a nonconnectable LE advertisement""" 208 self.dut.droid.bleSetAdvertiseSettingsAdvertiseMode(ble_advertise_settings_modes['low_latency']) 209 self.dut.droid.bleSetAdvertiseSettingsIsConnectable(False) 210 advertise_callback, advertise_data, advertise_settings = (generate_ble_advertise_objects(self.dut.droid)) 211 self.dut.droid.bleStartBleAdvertising(advertise_callback, advertise_data, advertise_settings) 212 if self._verify_ble_adv_started(advertise_callback): 213 self.log.info("Tracking Callback ID: {}".format(advertise_callback)) 214 self.advertisement_list.append(advertise_callback) 215 self.log.info(self.advertisement_list) 216 217 def stop_all_advertisements(self, line): 218 """Stop all LE advertisements""" 219 for callback_id in self.advertisement_list: 220 self.log.info("Stopping Advertisement {}".format(callback_id)) 221 self.dut.droid.bleStopBleAdvertising(callback_id) 222 time.sleep(1) 223 self.advertisement_list = [] 224 225 def ble_stop_advertisement(self, callback_id): 226 """Stop an LE advertisement""" 227 if not callback_id: 228 self.log.info("Need a callback ID") 229 return 230 callback_id = int(callback_id) 231 if callback_id not in self.advertisement_list: 232 self.log.info("Callback not in list of advertisements.") 233 return 234 self.dut.droid.bleStopBleAdvertising(callback_id) 235 self.advertisement_list.remove(callback_id) 236 237 def start_max_advertisements(self, line): 238 scan_response = None 239 if line: 240 scan_response = bool(line) 241 while (True): 242 try: 243 self.dut.droid.bleSetAdvertiseSettingsAdvertiseMode(ble_advertise_settings_modes['low_latency']) 244 self.dut.droid.bleSetAdvertiseSettingsIsConnectable(True) 245 advertise_callback, advertise_data, advertise_settings = (generate_ble_advertise_objects( 246 self.dut.droid)) 247 if scan_response: 248 self.dut.droid.bleStartBleAdvertisingWithScanResponse(advertise_callback, advertise_data, 249 advertise_settings, advertise_data) 250 else: 251 self.dut.droid.bleStartBleAdvertising(advertise_callback, advertise_data, advertise_settings) 252 if self._verify_ble_adv_started(advertise_callback): 253 self.log.info("Tracking Callback ID: {}".format(advertise_callback)) 254 self.advertisement_list.append(advertise_callback) 255 self.log.info(self.advertisement_list) 256 else: 257 self.log.info("Advertisements active: {}".format(len(self.advertisement_list))) 258 return False 259 except Exception as err: 260 self.log.info("Advertisements active: {}".format(len(self.advertisement_list))) 261 return True 262