1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright 2020 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li"""Facade to access the bluetooth-related functionality.""" 6*9c5db199SXin Li 7*9c5db199SXin Lifrom __future__ import absolute_import 8*9c5db199SXin Lifrom __future__ import division 9*9c5db199SXin Lifrom __future__ import print_function 10*9c5db199SXin Li 11*9c5db199SXin Liimport base64 12*9c5db199SXin Liimport binascii 13*9c5db199SXin Liimport collections 14*9c5db199SXin Lifrom datetime import datetime, timedelta 15*9c5db199SXin Liimport glob 16*9c5db199SXin Li# AU tests use ToT client code, but ToT -3 client version. 17*9c5db199SXin Litry: 18*9c5db199SXin Li from gi.repository import GLib, GObject 19*9c5db199SXin Liexcept ImportError: 20*9c5db199SXin Li import gobject as GObject 21*9c5db199SXin Liimport json 22*9c5db199SXin Liimport logging 23*9c5db199SXin Liimport logging.handlers 24*9c5db199SXin Liimport os 25*9c5db199SXin Li 26*9c5db199SXin Li# TODO(b/215715213) - Wait until ebuild runs as python3 to remove this try 27*9c5db199SXin Litry: 28*9c5db199SXin Li import pydbus 29*9c5db199SXin Liexcept Exception as e: 30*9c5db199SXin Li import platform 31*9c5db199SXin Li logging.error('Unable to import pydbus at version=%s: %s', 32*9c5db199SXin Li platform.python_version(), e) 33*9c5db199SXin Li pydbus = {} 34*9c5db199SXin Li 35*9c5db199SXin Liimport re 36*9c5db199SXin Liimport subprocess 37*9c5db199SXin Liimport functools 38*9c5db199SXin Liimport time 39*9c5db199SXin Liimport threading 40*9c5db199SXin Liimport traceback 41*9c5db199SXin Li 42*9c5db199SXin Liimport common 43*9c5db199SXin Lifrom autotest_lib.client.bin import utils 44*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros.bluetooth import bluetooth_socket 45*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 46*9c5db199SXin Lifrom autotest_lib.client.cros.udev_helpers import UdevadmInfo, UdevadmTrigger 47*9c5db199SXin Lifrom autotest_lib.client.cros.audio import (audio_test_data as 48*9c5db199SXin Li audio_test_data_module) 49*9c5db199SXin Lifrom autotest_lib.client.cros.audio import check_quality 50*9c5db199SXin Lifrom autotest_lib.client.cros.audio import cras_utils 51*9c5db199SXin Lifrom autotest_lib.client.cros.audio.sox_utils import ( 52*9c5db199SXin Li convert_format, convert_raw_file, get_file_length, 53*9c5db199SXin Li trim_silence_from_wav_file) 54*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth import advertisement 55*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth import adv_monitor_helper 56*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth import output_recorder 57*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth import logger_helper 58*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth.floss.adapter_client import ( 59*9c5db199SXin Li FlossAdapterClient, BluetoothCallbacks, BluetoothConnectionCallbacks, 60*9c5db199SXin Li BondState, SspVariant, Transport) 61*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth.floss.manager_client import FlossManagerClient 62*9c5db199SXin Lifrom autotest_lib.client.cros.bluetooth.floss.utils import GLIB_THREAD_NAME 63*9c5db199SXin Lifrom autotest_lib.client.cros.power import sys_power 64*9c5db199SXin Liimport six 65*9c5db199SXin Lifrom six.moves import map 66*9c5db199SXin Lifrom six.moves import range 67*9c5db199SXin Li 68*9c5db199SXin LiCheckQualityArgsClass = collections.namedtuple( 69*9c5db199SXin Li 'args_type', ['filename', 'rate', 'channel', 'bit_width']) 70*9c5db199SXin Li 71*9c5db199SXin Li 72*9c5db199SXin Lidef _dbus_byte_array_to_b64_string(dbus_byte_array): 73*9c5db199SXin Li """Base64 encodes a dbus byte array for use with the xml rpc proxy. 74*9c5db199SXin Li 75*9c5db199SXin Li Input is encoded to bytes using base64 encoding. Then the base64 bytes is 76*9c5db199SXin Li decoded as string. 77*9c5db199SXin Li """ 78*9c5db199SXin Li return base64.standard_b64encode(bytearray(dbus_byte_array)).decode() 79*9c5db199SXin Li 80*9c5db199SXin Li 81*9c5db199SXin Lidef _b64_string_to_dbus_byte_array(b64_string): 82*9c5db199SXin Li """Base64 decodes a dbus byte array for use with the xml rpc proxy.""" 83*9c5db199SXin Li dbus_array = [] 84*9c5db199SXin Li bytes = bytearray(base64.standard_b64decode(b64_string)) 85*9c5db199SXin Li for byte in bytes: 86*9c5db199SXin Li dbus_array.append(byte) 87*9c5db199SXin Li return dbus_array 88*9c5db199SXin Li 89*9c5db199SXin Li 90*9c5db199SXin Lidef dbus_safe(default_return_value, return_error=False): 91*9c5db199SXin Li """Catch all DBus exceptions and return a default value instead. 92*9c5db199SXin Li 93*9c5db199SXin Li Wrap a function with a try block that catches DBus exceptions and 94*9c5db199SXin Li returns the error with the specified return status. The exception is logged 95*9c5db199SXin Li to aid in debugging. 96*9c5db199SXin Li 97*9c5db199SXin Li If |return_error| is set, the call will return a tuple with 98*9c5db199SXin Li (default_return_value, str(error)). 99*9c5db199SXin Li 100*9c5db199SXin Li @param default_return_value: What value to return in case of errors. 101*9c5db199SXin Li @param return_error: Whether to return the error string as well. 102*9c5db199SXin Li 103*9c5db199SXin Li @return Either the return value from the method call if successful or 104*9c5db199SXin Li the |default_return_value| or a tuple(default_return_value, 105*9c5db199SXin Li str(error)) 106*9c5db199SXin Li """ 107*9c5db199SXin Li 108*9c5db199SXin Li def decorator(wrapped_function): 109*9c5db199SXin Li """Call a function and catch DBus errors. 110*9c5db199SXin Li 111*9c5db199SXin Li @param wrapped_function function to call in dbus safe context. 112*9c5db199SXin Li @return function return value or default_return_value on failure. 113*9c5db199SXin Li 114*9c5db199SXin Li """ 115*9c5db199SXin Li 116*9c5db199SXin Li @functools.wraps(wrapped_function) 117*9c5db199SXin Li def wrapper(*args, **kwargs): 118*9c5db199SXin Li """Pass args and kwargs to a dbus safe function. 119*9c5db199SXin Li 120*9c5db199SXin Li @param args formal python arguments. 121*9c5db199SXin Li @param kwargs keyword python arguments. 122*9c5db199SXin Li @return function return value or default_return_value on failure. 123*9c5db199SXin Li 124*9c5db199SXin Li """ 125*9c5db199SXin Li logging.debug('%s()', wrapped_function.__name__) 126*9c5db199SXin Li try: 127*9c5db199SXin Li return wrapped_function(*args, **kwargs) 128*9c5db199SXin Li except GLib.Error as e: 129*9c5db199SXin Li logging.debug('Exception while performing operation %s: %s', 130*9c5db199SXin Li wrapped_function.__name__, e) 131*9c5db199SXin Li 132*9c5db199SXin Li if return_error: 133*9c5db199SXin Li return (default_return_value, str(e)) 134*9c5db199SXin Li else: 135*9c5db199SXin Li return default_return_value 136*9c5db199SXin Li except Exception as e: 137*9c5db199SXin Li logging.debug('Exception in %s: %s', wrapped_function.__name__, 138*9c5db199SXin Li e) 139*9c5db199SXin Li logging.debug(traceback.format_exc()) 140*9c5db199SXin Li raise 141*9c5db199SXin Li 142*9c5db199SXin Li return wrapper 143*9c5db199SXin Li 144*9c5db199SXin Li return decorator 145*9c5db199SXin Li 146*9c5db199SXin Li 147*9c5db199SXin Lidef raw_dbus_call_sync(bus, 148*9c5db199SXin Li proxy, 149*9c5db199SXin Li iface, 150*9c5db199SXin Li method, 151*9c5db199SXin Li variant_in_args, 152*9c5db199SXin Li variant_out_type, 153*9c5db199SXin Li timeout_ms=None): 154*9c5db199SXin Li """Makes a raw D-Bus call and returns the unpacked result. 155*9c5db199SXin Li 156*9c5db199SXin Li @param bus: System bus object. 157*9c5db199SXin Li @param proxy: Proxy object. 158*9c5db199SXin Li @param iface: D-Bus interface that exposes this method. 159*9c5db199SXin Li @param method: Name of method to call. 160*9c5db199SXin Li @param variant_in_args: A Glib.Variant that corresponds to the method's 161*9c5db199SXin Li inputs. 162*9c5db199SXin Li @param variant_out_type: A Glib.VariantType that describes the output. This 163*9c5db199SXin Li is the type that will be unpacked from the result. 164*9c5db199SXin Li @param timeout_ms: Timeout in milliseconds for this method call. 165*9c5db199SXin Li 166*9c5db199SXin Li @returns: Unpacked result from the method call. 167*9c5db199SXin Li """ 168*9c5db199SXin Li if timeout_ms is None: 169*9c5db199SXin Li timeout_ms = GLib.MAXINT 170*9c5db199SXin Li 171*9c5db199SXin Li return bus.con.call_sync(proxy._bus_name, proxy._path, iface, method, 172*9c5db199SXin Li variant_in_args, variant_out_type, 0, timeout_ms, 173*9c5db199SXin Li None).unpack() 174*9c5db199SXin Li 175*9c5db199SXin Li 176*9c5db199SXin Lidef unpack_if_variant(value): 177*9c5db199SXin Li """If given value is GLib.Variant, unpack it to the actual type.""" 178*9c5db199SXin Li if isinstance(value, GLib.Variant): 179*9c5db199SXin Li return value.unpack() 180*9c5db199SXin Li 181*9c5db199SXin Li return value 182*9c5db199SXin Li 183*9c5db199SXin Li 184*9c5db199SXin Liclass UpstartClient: 185*9c5db199SXin Li """Upstart D-Bus client that allows actions on upstart targets.""" 186*9c5db199SXin Li 187*9c5db199SXin Li UPSTART_MANAGER_SERVICE = 'com.ubuntu.Upstart' 188*9c5db199SXin Li UPSTART_MANAGER_PATH = '/com/ubuntu/Upstart' 189*9c5db199SXin Li UPSTART_MANAGER_IFACE = 'com.ubuntu.Upstart0_6' 190*9c5db199SXin Li UPSTART_JOB_IFACE = 'com.ubuntu.Upstart0_6.Job' 191*9c5db199SXin Li 192*9c5db199SXin Li UPSTART_ERROR_UNKNOWNINSTANCE = ( 193*9c5db199SXin Li 'com.ubuntu.Upstart0_6.Error.UnknownInstance') 194*9c5db199SXin Li UPSTART_ERROR_ALREADYSTARTED = ( 195*9c5db199SXin Li 'com.ubuntu.Upstart0_6.Error.AlreadyStarted') 196*9c5db199SXin Li 197*9c5db199SXin Li @classmethod 198*9c5db199SXin Li def _get_job(cls, job_name): 199*9c5db199SXin Li """Get job by name.""" 200*9c5db199SXin Li bus = pydbus.SystemBus() 201*9c5db199SXin Li obj = bus.get(cls.UPSTART_MANAGER_SERVICE, cls.UPSTART_MANAGER_PATH) 202*9c5db199SXin Li job_path = obj[cls.UPSTART_MANAGER_IFACE].GetJobByName(job_name) 203*9c5db199SXin Li 204*9c5db199SXin Li return bus.get(cls.UPSTART_MANAGER_SERVICE, 205*9c5db199SXin Li job_path)[cls.UPSTART_JOB_IFACE] 206*9c5db199SXin Li 207*9c5db199SXin Li @staticmethod 208*9c5db199SXin Li def _convert_instance_args(source): 209*9c5db199SXin Li """Convert instance args dict to array.""" 210*9c5db199SXin Li return ['{}={}'.format(k, v) for k, v in source.items()] 211*9c5db199SXin Li 212*9c5db199SXin Li @classmethod 213*9c5db199SXin Li def start(cls, job_name, instance_args = {}): 214*9c5db199SXin Li """Starts a job. 215*9c5db199SXin Li 216*9c5db199SXin Li @param job_name: Name of upstart job to start. 217*9c5db199SXin Li @param instance_args: Instance arguments. Will be converted to array of 218*9c5db199SXin Li "key=value". 219*9c5db199SXin Li 220*9c5db199SXin Li @return True if job start was sent successfully. 221*9c5db199SXin Li """ 222*9c5db199SXin Li try: 223*9c5db199SXin Li job = cls._get_job(job_name) 224*9c5db199SXin Li converted_args = cls._convert_instance_args(instance_args) 225*9c5db199SXin Li job.Start(converted_args, True) 226*9c5db199SXin Li except TypeError as t: 227*9c5db199SXin Li # Can occur if cls._get_job fails 228*9c5db199SXin Li logging.error('Error starting {}: {}'.format(job_name, t)) 229*9c5db199SXin Li return False 230*9c5db199SXin Li except GLib.Error as e: 231*9c5db199SXin Li # An already started error is ok. All other dbus errors should 232*9c5db199SXin Li # return False. 233*9c5db199SXin Li if cls.UPSTART_ERROR_ALREADYSTARTED not in str(e): 234*9c5db199SXin Li logging.error('Error starting {}: {}'.format(job_name, e)) 235*9c5db199SXin Li return False 236*9c5db199SXin Li 237*9c5db199SXin Li return True 238*9c5db199SXin Li 239*9c5db199SXin Li @classmethod 240*9c5db199SXin Li def stop(cls, job_name, instance_args = {}): 241*9c5db199SXin Li """Stops a job. 242*9c5db199SXin Li 243*9c5db199SXin Li @param job_name: Name of upstart job to stop. 244*9c5db199SXin Li @param instance_args: Instance arguments. Will be converted to 245*9c5db199SXin Li array of "key=value". 246*9c5db199SXin Li 247*9c5db199SXin Li @return True if job stop was sent successfully. 248*9c5db199SXin Li """ 249*9c5db199SXin Li try: 250*9c5db199SXin Li job = cls._get_job(job_name) 251*9c5db199SXin Li converted_args = cls._convert_instance_args(instance_args) 252*9c5db199SXin Li job.Stop(converted_args, True) 253*9c5db199SXin Li except TypeError as t: 254*9c5db199SXin Li # Can occur if cls._get_job fails 255*9c5db199SXin Li logging.error('Error stopping {}: {}'.format(job_name, t)) 256*9c5db199SXin Li return False 257*9c5db199SXin Li except GLib.Error as e: 258*9c5db199SXin Li # If the job was already stopped, we will see an UnknownInstance 259*9c5db199SXin Li # exception. All other failure reasons should be treated as 260*9c5db199SXin Li # a failure to stop. 261*9c5db199SXin Li if cls.UPSTART_ERROR_UNKNOWNINSTANCE not in str(e): 262*9c5db199SXin Li logging.error('Error starting {}: {}'.format(job_name, e)) 263*9c5db199SXin Li return False 264*9c5db199SXin Li 265*9c5db199SXin Li return True 266*9c5db199SXin Li 267*9c5db199SXin Li 268*9c5db199SXin Liclass BluetoothBaseFacadeLocal(object): 269*9c5db199SXin Li """Base facade shared by Bluez and Floss daemons. This takes care of any 270*9c5db199SXin Li functionality that is common across the two daemons. 271*9c5db199SXin Li """ 272*9c5db199SXin Li 273*9c5db199SXin Li # Both bluez and floss share the same lib dir for configuration and cache 274*9c5db199SXin Li BLUETOOTH_LIBDIR = '/var/lib/bluetooth' 275*9c5db199SXin Li 276*9c5db199SXin Li SYSLOG_LEVELS = [ 277*9c5db199SXin Li 'EMERG', 'ALERT', 'CRIT', 'ERR', 'WARNING', 'NOTICE', 'INFO', 278*9c5db199SXin Li 'DEBUG' 279*9c5db199SXin Li ] 280*9c5db199SXin Li 281*9c5db199SXin Li # How long to wait for hid device 282*9c5db199SXin Li HID_TIMEOUT = 15 283*9c5db199SXin Li HID_CHECK_SECS = 2 284*9c5db199SXin Li 285*9c5db199SXin Li # Due to problems transferring a date object, we convert to stringtime first 286*9c5db199SXin Li # This is the standard format that we will use. 287*9c5db199SXin Li OUT_DATE_FORMAT = '%Y-%m-%d %H:%M:%S.%f' 288*9c5db199SXin Li 289*9c5db199SXin Li # Upstart job name for the Floss Manager daemon 290*9c5db199SXin Li MANAGER_JOB = "btmanagerd" 291*9c5db199SXin Li # File path for btmanagerd 292*9c5db199SXin Li BTMANGERD_FILE_PATH = '/usr/bin/btmanagerd' 293*9c5db199SXin Li # How long we wait for the manager daemon to come up after we start it 294*9c5db199SXin Li DAEMON_TIMEOUT_SEC = 5 295*9c5db199SXin Li 296*9c5db199SXin Li # Upstart job name for ChromeOS Audio daemon 297*9c5db199SXin Li CRAS_JOB = "cras" 298*9c5db199SXin Li 299*9c5db199SXin Li CHIPSET_TO_VIDPID = { 300*9c5db199SXin Li 'MVL-8897': [(('0x02df', '0x912d'), 'SDIO')], 301*9c5db199SXin Li 'MVL-8997': [(('0x1b4b', '0x2b42'), 'USB')], 302*9c5db199SXin Li 'QCA-6174A-5-USB': [(('0x168c', '0x003e'), 'USB')], 303*9c5db199SXin Li 'QCA-6174A-3-UART': [(('0x0271', '0x050a'), 'UART')], 304*9c5db199SXin Li 'QCA-WCN6856': [(('0x17cb', '0x1103'), 'USB')], 305*9c5db199SXin Li 'Intel-AX200': [(('0x8086', '0x2723'), 'USB')], # CcP2 306*9c5db199SXin Li 'Intel-AX201': [ 307*9c5db199SXin Li (('0x8086', '0x02f0'), 'USB'), 308*9c5db199SXin Li (('0x8086', '0x4df0'), 'USB'), 309*9c5db199SXin Li (('0x8086', '0xa0f0'), 'USB'), 310*9c5db199SXin Li ], # HrP2 311*9c5db199SXin Li 'Intel-AC9260': [(('0x8086', '0x2526'), 'USB')], # ThP2 312*9c5db199SXin Li 'Intel-AC9560': [ 313*9c5db199SXin Li (('0x8086', '0x31dc'), 'USB'), # JfP2 314*9c5db199SXin Li (('0x8086', '0x9df0'), 'USB') 315*9c5db199SXin Li ], 316*9c5db199SXin Li 'Intel-AC7260': [ 317*9c5db199SXin Li (('0x8086', '0x08b1'), 'USB'), # WP2 318*9c5db199SXin Li (('0x8086', '0x08b2'), 'USB') 319*9c5db199SXin Li ], 320*9c5db199SXin Li 'Intel-AC7265': [ 321*9c5db199SXin Li (('0x8086', '0x095a'), 'USB'), # StP2 322*9c5db199SXin Li (('0x8086', '0x095b'), 'USB') 323*9c5db199SXin Li ], 324*9c5db199SXin Li 'Realtek-RTL8822C-USB': [(('0x10ec', '0xc822'), 'USB')], 325*9c5db199SXin Li 'Realtek-RTL8822C-UART': [(('0x10ec', '0xc822'), 'UART')], 326*9c5db199SXin Li 'Realtek-RTL8852A-USB': [(('0x10ec', '0x8852'), 'USB')], 327*9c5db199SXin Li 'Mediatek-MTK7921-USB': [(('0x14c3', '0x7961'), 'USB')], 328*9c5db199SXin Li 'Mediatek-MTK7921-SDIO': [(('0x037a', '0x7901'), 'SDIO')] 329*9c5db199SXin Li 330*9c5db199SXin Li # The following doesn't expose vid:pid 331*9c5db199SXin Li # 'WCN3991-UART' 332*9c5db199SXin Li } 333*9c5db199SXin Li 334*9c5db199SXin Li def __init__(self): 335*9c5db199SXin Li # Initialize a messages object to record general logging. 336*9c5db199SXin Li self.messages = logger_helper.LogManager() 337*9c5db199SXin Li 338*9c5db199SXin Li # Set up cras test client for audio tests 339*9c5db199SXin Li self._cras_test_client = cras_utils.CrasTestClient() 340*9c5db199SXin Li 341*9c5db199SXin Li def configure_floss(self, enabled): 342*9c5db199SXin Li """Start and configure the Floss manager daemon. 343*9c5db199SXin Li 344*9c5db199SXin Li In order to manage whether we use bluez or floss, we need to start the 345*9c5db199SXin Li Floss manager daemon and then set floss enabled. This exists in the base 346*9c5db199SXin Li implementation because bluez tests will need to start the manager to 347*9c5db199SXin Li disable Floss. 348*9c5db199SXin Li 349*9c5db199SXin Li @param enabled: Whether to enable Floss 350*9c5db199SXin Li 351*9c5db199SXin Li @return Whether Floss was configured successfully. 352*9c5db199SXin Li """ 353*9c5db199SXin Li # Start manager daemon or exit early 354*9c5db199SXin Li if not UpstartClient.start(self.MANAGER_JOB): 355*9c5db199SXin Li return False 356*9c5db199SXin Li 357*9c5db199SXin Li # Since we've just started the manager daemon, we also need to recreate 358*9c5db199SXin Li # the client. 359*9c5db199SXin Li self.manager_client = FlossManagerClient(self.bus) 360*9c5db199SXin Li 361*9c5db199SXin Li # Wait for the manager daemon to come up 362*9c5db199SXin Li try: 363*9c5db199SXin Li utils.poll_for_condition( 364*9c5db199SXin Li condition=(lambda: self.manager_client.has_proxy()), 365*9c5db199SXin Li desc='Wait for manager daemon to come up', 366*9c5db199SXin Li sleep_interval=0.5, 367*9c5db199SXin Li timeout=self.DAEMON_TIMEOUT_SEC) 368*9c5db199SXin Li except Exception as e: 369*9c5db199SXin Li logging.error('timeout: error starting manager daemon: %s', e) 370*9c5db199SXin Li 371*9c5db199SXin Li # We need to observe callbacks for proper operation. 372*9c5db199SXin Li if not self.manager_client.register_callbacks(): 373*9c5db199SXin Li logging.error('manager_client: Failed to register callbacks') 374*9c5db199SXin Li return False 375*9c5db199SXin Li 376*9c5db199SXin Li # Floss may not yet be enabled so make sure to enable it here. 377*9c5db199SXin Li if self.manager_client.get_floss_enabled() != enabled: 378*9c5db199SXin Li self.manager_client.set_floss_enabled(enabled) 379*9c5db199SXin Li default_adapter = self.manager_client.get_default_adapter() 380*9c5db199SXin Li try: 381*9c5db199SXin Li utils.poll_for_condition( 382*9c5db199SXin Li condition=(lambda: self.manager_client. 383*9c5db199SXin Li get_adapter_enabled(default_adapter 384*9c5db199SXin Li ) == enabled), 385*9c5db199SXin Li desc='Wait for set floss enabled to complete', 386*9c5db199SXin Li sleep_interval=0.5, 387*9c5db199SXin Li timeout=self.DAEMON_TIMEOUT_SEC) 388*9c5db199SXin Li except Exception as e: 389*9c5db199SXin Li logging.error('timeout: error waiting for set_floss_enabled') 390*9c5db199SXin Li 391*9c5db199SXin Li # Also configure cras to enable/disable floss 392*9c5db199SXin Li self.configure_cras_floss(enabled) 393*9c5db199SXin Li 394*9c5db199SXin Li return True 395*9c5db199SXin Li 396*9c5db199SXin Li def configure_cras_floss(self, enabled): 397*9c5db199SXin Li """Configure whether CRAS has floss enabled.""" 398*9c5db199SXin Li cras_utils.set_floss_enabled(enabled) 399*9c5db199SXin Li 400*9c5db199SXin Li def _restart_cras(self, enable_floss=False): 401*9c5db199SXin Li """Restarts CRAS and sets whether Floss is enabled.""" 402*9c5db199SXin Li UpstartClient.stop(self.CRAS_JOB) 403*9c5db199SXin Li started = UpstartClient.start(self.CRAS_JOB) 404*9c5db199SXin Li 405*9c5db199SXin Li def _set_floss(): 406*9c5db199SXin Li try: 407*9c5db199SXin Li self.configure_cras_floss(enable_floss) 408*9c5db199SXin Li return True 409*9c5db199SXin Li except: 410*9c5db199SXin Li return False 411*9c5db199SXin Li 412*9c5db199SXin Li try: 413*9c5db199SXin Li if started: 414*9c5db199SXin Li utils.poll_for_condition( 415*9c5db199SXin Li condition=_set_floss, 416*9c5db199SXin Li desc='Wait for CRAS to come up and configure floss', 417*9c5db199SXin Li sleep_interval=1, 418*9c5db199SXin Li timeout=self.DAEMON_TIMEOUT_SEC) 419*9c5db199SXin Li except Exception as e: 420*9c5db199SXin Li logging.error('timeout: error waiting to set floss on cras') 421*9c5db199SXin Li return False 422*9c5db199SXin Li 423*9c5db199SXin Li # Did we successfully start the cras daemon? 424*9c5db199SXin Li return started 425*9c5db199SXin Li 426*9c5db199SXin Li def log_message(self, msg): 427*9c5db199SXin Li """ log a message to /var/log/messages.""" 428*9c5db199SXin Li try: 429*9c5db199SXin Li cmd = ['logger', msg] 430*9c5db199SXin Li subprocess.call(cmd) 431*9c5db199SXin Li except Exception as e: 432*9c5db199SXin Li logging.error("log_message %s failed with %s", cmd, str(e)) 433*9c5db199SXin Li 434*9c5db199SXin Li def messages_start(self): 435*9c5db199SXin Li """Start messages monitoring. 436*9c5db199SXin Li 437*9c5db199SXin Li @returns: True if logging started successfully, else False 438*9c5db199SXin Li """ 439*9c5db199SXin Li 440*9c5db199SXin Li try: 441*9c5db199SXin Li self.messages.StartRecording() 442*9c5db199SXin Li return True 443*9c5db199SXin Li 444*9c5db199SXin Li except Exception as e: 445*9c5db199SXin Li logging.error('Failed to start log recording with error: %s', e) 446*9c5db199SXin Li 447*9c5db199SXin Li return False 448*9c5db199SXin Li 449*9c5db199SXin Li def messages_stop(self): 450*9c5db199SXin Li """Stop messages monitoring. 451*9c5db199SXin Li 452*9c5db199SXin Li @returns: True if logs were successfully gathered since logging started, 453*9c5db199SXin Li else False 454*9c5db199SXin Li """ 455*9c5db199SXin Li try: 456*9c5db199SXin Li self.messages.StopRecording() 457*9c5db199SXin Li return True 458*9c5db199SXin Li 459*9c5db199SXin Li except Exception as e: 460*9c5db199SXin Li logging.error('Failed to stop log recording with error: %s', e) 461*9c5db199SXin Li 462*9c5db199SXin Li return False 463*9c5db199SXin Li 464*9c5db199SXin Li def messages_find(self, pattern_str): 465*9c5db199SXin Li """Find if a pattern string exists in messages output. 466*9c5db199SXin Li 467*9c5db199SXin Li @param pattern_str: the pattern string to find. 468*9c5db199SXin Li 469*9c5db199SXin Li @returns: True on success. False otherwise. 470*9c5db199SXin Li 471*9c5db199SXin Li """ 472*9c5db199SXin Li return self.messages.LogContains(pattern_str) 473*9c5db199SXin Li 474*9c5db199SXin Li def clean_bluetooth_kernel_log(self, log_level): 475*9c5db199SXin Li """Remove Bluetooth kernel logs in /var/log/messages with loglevel 476*9c5db199SXin Li equal to or greater than |log_level| 477*9c5db199SXin Li 478*9c5db199SXin Li @param log_level: int in range [0..7] 479*9c5db199SXin Li """ 480*9c5db199SXin Li reg_exp = '[^ ]+ ({LEVEL}) kernel: \[.*\] Bluetooth: .*'.format( 481*9c5db199SXin Li LEVEL='|'.join(self.SYSLOG_LEVELS[log_level:])) 482*9c5db199SXin Li 483*9c5db199SXin Li logging.debug('Set kernel filter to level %d', log_level) 484*9c5db199SXin Li 485*9c5db199SXin Li self.messages.FilterOut(reg_exp) 486*9c5db199SXin Li 487*9c5db199SXin Li def _encode_base64_json(self, data): 488*9c5db199SXin Li """Base64 encode and json encode the data. 489*9c5db199SXin Li Required to handle non-ascii data 490*9c5db199SXin Li 491*9c5db199SXin Li @param data: data to be base64 and JSON encoded 492*9c5db199SXin Li 493*9c5db199SXin Li @return: base64 and JSON encoded data 494*9c5db199SXin Li 495*9c5db199SXin Li """ 496*9c5db199SXin Li logging.debug('_encode_base64_json raw data is %s', data) 497*9c5db199SXin Li b64_encoded = utils.base64_recursive_encode(data) 498*9c5db199SXin Li logging.debug('base64 encoded data is %s', b64_encoded) 499*9c5db199SXin Li json_encoded = json.dumps(b64_encoded) 500*9c5db199SXin Li logging.debug('JSON encoded data is %s', json_encoded) 501*9c5db199SXin Li return json_encoded 502*9c5db199SXin Li 503*9c5db199SXin Li def is_wrt_supported(self): 504*9c5db199SXin Li """Check if Bluetooth adapter support WRT logs 505*9c5db199SXin Li 506*9c5db199SXin Li WRT is supported on Intel adapters other than (StP2 and WP2) 507*9c5db199SXin Li 508*9c5db199SXin Li @returns : True if adapter is Intel made. 509*9c5db199SXin Li """ 510*9c5db199SXin Li # Dict of Intel Adapters that support WRT and vid:pid 511*9c5db199SXin Li vid_pid_dict = { 512*9c5db199SXin Li 'HrP2': '8086:02f0', 513*9c5db199SXin Li 'ThP2': '8086:2526', 514*9c5db199SXin Li 'JfP2': '8086:31dc', 515*9c5db199SXin Li 'JfP2-2': '8086:9df0' 516*9c5db199SXin Li } # On Sarien/Arcada 517*9c5db199SXin Li 518*9c5db199SXin Li def _get_lspci_vid_pid(output): 519*9c5db199SXin Li """ parse output of lspci -knn and get the vid:pid 520*9c5db199SXin Li 521*9c5db199SXin Li output is of the form '01:00.0 Network controller [0280]: 522*9c5db199SXin Li \Intel Corporation Device [8086:2526] (rev 29)\n' 523*9c5db199SXin Li 524*9c5db199SXin Li @returns : 'vid:pid' or None 525*9c5db199SXin Li """ 526*9c5db199SXin Li try: 527*9c5db199SXin Li for i in output.split(b'\n'): 528*9c5db199SXin Li if 'Network controller' in i.decode('utf-8'): 529*9c5db199SXin Li logging.debug('Got line %s', i) 530*9c5db199SXin Li if 'Intel Corporation' in i.decode('utf-8'): 531*9c5db199SXin Li return i.split(b'[')[2].split(b']')[0] 532*9c5db199SXin Li return None 533*9c5db199SXin Li except Exception as e: 534*9c5db199SXin Li logging.debug('Exception in _get_lspci_vidpid %s', str(e)) 535*9c5db199SXin Li return None 536*9c5db199SXin Li 537*9c5db199SXin Li try: 538*9c5db199SXin Li cmd = ['lspci', '-knn'] 539*9c5db199SXin Li output = subprocess.check_output(cmd, encoding='UTF-8') 540*9c5db199SXin Li vid_pid = _get_lspci_vid_pid(output) 541*9c5db199SXin Li logging.debug("got vid_pid %s", vid_pid) 542*9c5db199SXin Li if vid_pid is not None: 543*9c5db199SXin Li if vid_pid in list(vid_pid_dict.values()): 544*9c5db199SXin Li return True 545*9c5db199SXin Li except Exception as e: 546*9c5db199SXin Li logging.error('is_intel_adapter failed with %s', cmd, str(e)) 547*9c5db199SXin Li return False 548*9c5db199SXin Li 549*9c5db199SXin Li def enable_wrt_logs(self): 550*9c5db199SXin Li """ Enable WRT logs for Intel Bluetooth adapters. 551*9c5db199SXin Li 552*9c5db199SXin Li This is applicable only to Intel adapters. 553*9c5db199SXin Li Execute a series of custom hciconfig commands to 554*9c5db199SXin Li setup WRT log collection 555*9c5db199SXin Li 556*9c5db199SXin Li Precondition : 557*9c5db199SXin Li 1) Check if the DUT has Intel controller other than StP2 558*9c5db199SXin Li 2) Make sure the controller is powered on 559*9c5db199SXin Li """ 560*9c5db199SXin Li fw_trace_cmd = ( 561*9c5db199SXin Li 'hcitool cmd 3f 7c 01 10 00 00 00 FE 81 02 80 04 00 00' 562*9c5db199SXin Li ' 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' 563*9c5db199SXin Li ' 00 00 00 00 00 00 00') 564*9c5db199SXin Li ddc_read_cmd = 'hcitool cmd 3f 8c 28 01' 565*9c5db199SXin Li ddc_write_cmd_prefix = 'hcitool cmd 3f 8b 03 28 01' 566*9c5db199SXin Li hw_trace_cmd = ( 567*9c5db199SXin Li 'hcitool cmd 3f 6f 01 08 00 00 00 00 00 00 00 00 01 00' 568*9c5db199SXin Li ' 00 03 01 03 03 03 10 03 6A 0A 6A 0A 6A 0A 6A 0A 00 00' 569*9c5db199SXin Li ' 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' 570*9c5db199SXin Li ' 00 00 00 00 00 00') 571*9c5db199SXin Li multi_comm_trace_str = ('000000F600000000005002000000003F3F3F3' 572*9c5db199SXin Li 'F3F003F000000000000000001000000000000000000' 573*9c5db199SXin Li '000000000000000000000000000000000000000000' 574*9c5db199SXin Li '00000000000000000000000000000000000000000' 575*9c5db199SXin Li '00000000000000000') 576*9c5db199SXin Li multi_comm_trace_file = ('/sys/kernel/debug/ieee80211' 577*9c5db199SXin Li '/phy0/iwlwifi/iwlmvm/send_hcmd') 578*9c5db199SXin Li 579*9c5db199SXin Li def _execute_cmd(cmd_str, msg=''): 580*9c5db199SXin Li """Wrapper around subprocess.check_output. 581*9c5db199SXin Li 582*9c5db199SXin Li @params cmd: Command to be executed as a string 583*9c5db199SXin Li @params msg: Optional description of the command 584*9c5db199SXin Li 585*9c5db199SXin Li @returns: (True, output) if execution succeeded 586*9c5db199SXin Li (False, None) if execution failed 587*9c5db199SXin Li 588*9c5db199SXin Li """ 589*9c5db199SXin Li try: 590*9c5db199SXin Li logging.info('Executing %s cmd', msg) 591*9c5db199SXin Li cmd = cmd_str.split(' ') 592*9c5db199SXin Li logging.debug('command is "%s"', cmd) 593*9c5db199SXin Li output = subprocess.check_output(cmd, enconding='UTF-8') 594*9c5db199SXin Li logging.info('%s cmd successfully executed', msg) 595*9c5db199SXin Li logging.debug('output is %s', output) 596*9c5db199SXin Li return (True, output) 597*9c5db199SXin Li except Exception as e: 598*9c5db199SXin Li logging.error('Exception %s while executing %s command', 599*9c5db199SXin Li str(e), msg) 600*9c5db199SXin Li return (False, None) 601*9c5db199SXin Li 602*9c5db199SXin Li def _get_ddc_write_cmd(ddc_read_result, ddc_write_cmd_prefix): 603*9c5db199SXin Li """ Create ddc_write_cmd from read command 604*9c5db199SXin Li 605*9c5db199SXin Li This function performs the following 606*9c5db199SXin Li 1) Take the output of ddc_read_cmd which is in following form 607*9c5db199SXin Li '< HCI Command: ogf 0x3f, ocf 0x008c, plen 1\n 608*9c5db199SXin Li 01 \n> 609*9c5db199SXin Li HCI Event: 0x0e plen 6\n 01 8C FC 12 00 18 \n' 610*9c5db199SXin Li 2) Take the last value of the output 611*9c5db199SXin Li 01 8C FC 12 00 ===>> 18 <==== 612*9c5db199SXin Li 3) Bitwise or with 0x40 613*9c5db199SXin Li 0x18 | 0x40 = 0x58 614*9c5db199SXin Li 4) Add it to the end of the ddc_write_cmd 615*9c5db199SXin Li 'hcitool 01 8C FC 00 28 01 ===> 58 <====' 616*9c5db199SXin Li 617*9c5db199SXin Li """ 618*9c5db199SXin Li last_line = [ 619*9c5db199SXin Li i for i in ddc_read_result.strip().split(b'\n') if i != '' 620*9c5db199SXin Li ][-1] 621*9c5db199SXin Li last_byte = [i for i in last_line.split(b' ') if i != ''][-1] 622*9c5db199SXin Li processed_byte = hex(int(last_byte, 16) | 0x40).split('0x')[1] 623*9c5db199SXin Li cmd = ddc_write_cmd_prefix + ' ' + processed_byte 624*9c5db199SXin Li logging.debug('ddc_write_cmd is %s', cmd) 625*9c5db199SXin Li return cmd 626*9c5db199SXin Li 627*9c5db199SXin Li try: 628*9c5db199SXin Li logging.info('Enabling WRT logs') 629*9c5db199SXin Li status, _ = _execute_cmd(fw_trace_cmd, 'FW trace cmd') 630*9c5db199SXin Li if not status: 631*9c5db199SXin Li logging.info('FW trace command execution failed') 632*9c5db199SXin Li return False 633*9c5db199SXin Li 634*9c5db199SXin Li status, ddc_read_result = _execute_cmd(ddc_read_cmd, 'DDC Read') 635*9c5db199SXin Li if not status: 636*9c5db199SXin Li logging.info('DDC Read command execution failed') 637*9c5db199SXin Li return False 638*9c5db199SXin Li 639*9c5db199SXin Li ddc_write_cmd = _get_ddc_write_cmd(ddc_read_result, 640*9c5db199SXin Li ddc_write_cmd_prefix) 641*9c5db199SXin Li logging.debug('DDC Write command is %s', ddc_write_cmd) 642*9c5db199SXin Li status, _ = _execute_cmd(ddc_write_cmd, 'DDC Write') 643*9c5db199SXin Li if not status: 644*9c5db199SXin Li logging.info('DDC Write commanad execution failed') 645*9c5db199SXin Li return False 646*9c5db199SXin Li 647*9c5db199SXin Li status, hw_trace_result = _execute_cmd(hw_trace_cmd, 'HW trace') 648*9c5db199SXin Li if not status: 649*9c5db199SXin Li logging.info('HW Trace command execution failed') 650*9c5db199SXin Li return False 651*9c5db199SXin Li 652*9c5db199SXin Li logging.debug('Executing the multi_comm_trace cmd %s to file %s', 653*9c5db199SXin Li multi_comm_trace_str, multi_comm_trace_file) 654*9c5db199SXin Li with open(multi_comm_trace_file, 'w') as f: 655*9c5db199SXin Li f.write(multi_comm_trace_str + '\n') 656*9c5db199SXin Li f.flush() 657*9c5db199SXin Li 658*9c5db199SXin Li logging.info('WRT Logs enabled') 659*9c5db199SXin Li return True 660*9c5db199SXin Li except Exception as e: 661*9c5db199SXin Li logging.error('Exception %s while enabling WRT logs', str(e)) 662*9c5db199SXin Li return False 663*9c5db199SXin Li 664*9c5db199SXin Li def collect_wrt_logs(self): 665*9c5db199SXin Li """Collect the WRT logs for Intel Bluetooth adapters 666*9c5db199SXin Li 667*9c5db199SXin Li This is applicable only to Intel adapters. 668*9c5db199SXin Li Execute following command to collect WRT log. The logs are 669*9c5db199SXin Li copied to /var/spool/crash/ 670*9c5db199SXin Li 671*9c5db199SXin Li 'echo 1 > sudo tee /sys/kernel/debug/ieee80211/phy0' 672*9c5db199SXin Li '/iwlwifi/iwlmvm/fw_dbg_collect' 673*9c5db199SXin Li This is to be called only after enable_wrt_logs is called 674*9c5db199SXin Li 675*9c5db199SXin Li 676*9c5db199SXin Li Precondition: 677*9c5db199SXin Li 1) enable_wrt_logs has been called 678*9c5db199SXin Li """ 679*9c5db199SXin Li 680*9c5db199SXin Li def _collect_logs(): 681*9c5db199SXin Li """Execute command to collect wrt logs.""" 682*9c5db199SXin Li try: 683*9c5db199SXin Li with open( 684*9c5db199SXin Li '/sys/kernel/debug/ieee80211/phy0/iwlwifi/' 685*9c5db199SXin Li 'iwlmvm/fw_dbg_collect', 'w') as f: 686*9c5db199SXin Li f.write('1') 687*9c5db199SXin Li f.flush() 688*9c5db199SXin Li # There is some flakiness in log collection. This sleep 689*9c5db199SXin Li # is due to the flakiness 690*9c5db199SXin Li time.sleep(10) 691*9c5db199SXin Li return True 692*9c5db199SXin Li except Exception as e: 693*9c5db199SXin Li logging.error('Exception %s in _collect logs ', str(e)) 694*9c5db199SXin Li return False 695*9c5db199SXin Li 696*9c5db199SXin Li def _get_num_log_files(): 697*9c5db199SXin Li """Return number of WRT log files.""" 698*9c5db199SXin Li try: 699*9c5db199SXin Li return len(glob.glob('/var/spool/crash/devcoredump_iwlwifi*')) 700*9c5db199SXin Li except Exception as e: 701*9c5db199SXin Li logging.debug('Exception %s raised in _get_num_log_files', 702*9c5db199SXin Li str(e)) 703*9c5db199SXin Li return 0 704*9c5db199SXin Li 705*9c5db199SXin Li try: 706*9c5db199SXin Li logging.info('Collecting WRT logs') 707*9c5db199SXin Li # 708*9c5db199SXin Li # The command to trigger the logs does seems to work always. 709*9c5db199SXin Li # As a workaround for this flakiness, execute it multiple times 710*9c5db199SXin Li # until a new log is created 711*9c5db199SXin Li # 712*9c5db199SXin Li num_logs_present = _get_num_log_files() 713*9c5db199SXin Li logging.debug('%s logs present', num_logs_present) 714*9c5db199SXin Li for i in range(10): 715*9c5db199SXin Li time.sleep(1) 716*9c5db199SXin Li logging.debug('Executing command to collect WRT logs ') 717*9c5db199SXin Li if _collect_logs(): 718*9c5db199SXin Li logging.debug('Command to collect WRT logs executed') 719*9c5db199SXin Li else: 720*9c5db199SXin Li logging.debug('Command to collect WRT logs failed') 721*9c5db199SXin Li continue 722*9c5db199SXin Li 723*9c5db199SXin Li if _get_num_log_files() > num_logs_present: 724*9c5db199SXin Li logging.info('Successfully collected WRT logs ') 725*9c5db199SXin Li return True 726*9c5db199SXin Li else: 727*9c5db199SXin Li logging.debug('Log file not written. Trying again') 728*9c5db199SXin Li 729*9c5db199SXin Li logging.info('Unable to collect WRT logs') 730*9c5db199SXin Li return False 731*9c5db199SXin Li except Exception as e: 732*9c5db199SXin Li logging.error('Exception %s while collecting WRT logs', str(e)) 733*9c5db199SXin Li return False 734*9c5db199SXin Li 735*9c5db199SXin Li def _get_wake_enabled_path(self): 736*9c5db199SXin Li # Walk up the parents from hci0 sysfs path and find the first one with 737*9c5db199SXin Li # a power/wakeup property. Return that path (including power/wakeup). 738*9c5db199SXin Li 739*9c5db199SXin Li # Resolve hci path to get full device path (i.e. w/ usb or uart) 740*9c5db199SXin Li search_at = os.path.realpath('/sys/class/bluetooth/hci0') 741*9c5db199SXin Li 742*9c5db199SXin Li # Exit early if path doesn't exist 743*9c5db199SXin Li if not os.path.exists(search_at): 744*9c5db199SXin Li return None 745*9c5db199SXin Li 746*9c5db199SXin Li # Walk up parents and try to find one with 'power/wakeup' 747*9c5db199SXin Li for _ in range(search_at.count('/') - 1): 748*9c5db199SXin Li search_at = os.path.normpath(os.path.join(search_at, '..')) 749*9c5db199SXin Li try: 750*9c5db199SXin Li path = os.path.join(search_at, 'power', 'wakeup') 751*9c5db199SXin Li with open(path, 'r') as f: 752*9c5db199SXin Li return path 753*9c5db199SXin Li except IOError: 754*9c5db199SXin Li # No power wakeup at the given location so keep going 755*9c5db199SXin Li continue 756*9c5db199SXin Li 757*9c5db199SXin Li return None 758*9c5db199SXin Li 759*9c5db199SXin Li def _is_wake_enabled(self): 760*9c5db199SXin Li search_at = self._get_wake_enabled_path() 761*9c5db199SXin Li 762*9c5db199SXin Li if search_at is not None: 763*9c5db199SXin Li try: 764*9c5db199SXin Li with open(search_at, 'r') as f: 765*9c5db199SXin Li value = f.read() 766*9c5db199SXin Li logging.info('Power/wakeup found at {}: {}'.format( 767*9c5db199SXin Li search_at, value)) 768*9c5db199SXin Li return 'enabled' in value 769*9c5db199SXin Li except IOError: 770*9c5db199SXin Li # Path was not readable 771*9c5db199SXin Li return False 772*9c5db199SXin Li 773*9c5db199SXin Li logging.debug('No power/wakeup path found') 774*9c5db199SXin Li return False 775*9c5db199SXin Li 776*9c5db199SXin Li def _set_wake_enabled(self, value): 777*9c5db199SXin Li path = self._get_wake_enabled_path() 778*9c5db199SXin Li if path is not None: 779*9c5db199SXin Li try: 780*9c5db199SXin Li with open(path, 'w') as f: 781*9c5db199SXin Li f.write('enabled' if value else 'disabled') 782*9c5db199SXin Li return True 783*9c5db199SXin Li except IOError: 784*9c5db199SXin Li # Path was not writeable 785*9c5db199SXin Li return False 786*9c5db199SXin Li 787*9c5db199SXin Li return False 788*9c5db199SXin Li 789*9c5db199SXin Li def is_wake_enabled(self): 790*9c5db199SXin Li """Checks whether the bluetooth adapter has wake enabled. 791*9c5db199SXin Li 792*9c5db199SXin Li This will walk through all parents of the hci0 sysfs path and try to 793*9c5db199SXin Li find one with a 'power/wakeup' entry and returns whether its value is 794*9c5db199SXin Li 'enabled'. 795*9c5db199SXin Li 796*9c5db199SXin Li @return True if 'power/wakeup' of an hci0 parent is 'enabled' 797*9c5db199SXin Li """ 798*9c5db199SXin Li enabled = self._is_wake_enabled() 799*9c5db199SXin Li return enabled 800*9c5db199SXin Li 801*9c5db199SXin Li def set_wake_enabled(self, value): 802*9c5db199SXin Li """Sets wake enabled to the value if path exists. 803*9c5db199SXin Li 804*9c5db199SXin Li This will walk through all parents of the hci0 sysfs path and write the 805*9c5db199SXin Li value to the first one it finds. 806*9c5db199SXin Li 807*9c5db199SXin Li @param value: Sets power/wakeup to "enabled" if value is true, else 808*9c5db199SXin Li "disabled" 809*9c5db199SXin Li 810*9c5db199SXin Li @return True if it wrote value to a power/wakeup, False otherwise 811*9c5db199SXin Li """ 812*9c5db199SXin Li return self._set_wake_enabled(value) 813*9c5db199SXin Li 814*9c5db199SXin Li def wait_for_hid_device(self, device_address, timeout, sleep_interval): 815*9c5db199SXin Li """Waits for hid device with given device address. 816*9c5db199SXin Li 817*9c5db199SXin Li @param device_address: Peripheral address 818*9c5db199SXin Li @param timeout: maximum number of seconds to wait 819*9c5db199SXin Li @param sleep_interval: time to sleep between polls 820*9c5db199SXin Li 821*9c5db199SXin Li @return True if hid device found, False otherwise 822*9c5db199SXin Li """ 823*9c5db199SXin Li 824*9c5db199SXin Li def _match_hid_to_device(hidpath, device_address): 825*9c5db199SXin Li """Check if given hid syspath is for the given device address """ 826*9c5db199SXin Li # If the syspath has a uniq property that matches the peripheral 827*9c5db199SXin Li # device's address, then it has matched 828*9c5db199SXin Li props = UdevadmInfo.GetProperties(hidpath) 829*9c5db199SXin Li if (props.get(b'uniq', b'').lower().decode() == device_address): 830*9c5db199SXin Li logging.info('Found hid device for address {} at {}'.format( 831*9c5db199SXin Li device_address, hidpath)) 832*9c5db199SXin Li return True 833*9c5db199SXin Li else: 834*9c5db199SXin Li logging.info('Path {} is not right device.'.format(hidpath)) 835*9c5db199SXin Li 836*9c5db199SXin Li return False 837*9c5db199SXin Li 838*9c5db199SXin Li def _hid_is_created(device_address): 839*9c5db199SXin Li existing_inputs = UdevadmTrigger( 840*9c5db199SXin Li subsystem_match=['input']).DryRun() 841*9c5db199SXin Li for entry in existing_inputs: 842*9c5db199SXin Li entry = entry.decode() 843*9c5db199SXin Li bt_hid = any([t in entry for t in ['uhid', 'hci']]) 844*9c5db199SXin Li logging.info('udevadm trigger entry is {}: {}'.format( 845*9c5db199SXin Li bt_hid, entry)) 846*9c5db199SXin Li 847*9c5db199SXin Li if (bt_hid and _match_hid_to_device(entry, 848*9c5db199SXin Li device_address.lower())): 849*9c5db199SXin Li return True 850*9c5db199SXin Li 851*9c5db199SXin Li return False 852*9c5db199SXin Li 853*9c5db199SXin Li if timeout is None: 854*9c5db199SXin Li timeout = self.HID_TIMEOUT 855*9c5db199SXin Li if sleep_interval is None: 856*9c5db199SXin Li sleep_interval = self.HID_CHECK_SECS 857*9c5db199SXin Li 858*9c5db199SXin Li method_name = 'wait_for_hid_device' 859*9c5db199SXin Li try: 860*9c5db199SXin Li utils.poll_for_condition( 861*9c5db199SXin Li condition=(lambda: _hid_is_created(device_address)), 862*9c5db199SXin Li timeout=timeout, 863*9c5db199SXin Li sleep_interval=sleep_interval, 864*9c5db199SXin Li desc=('Waiting for HID device to be created from %s' % 865*9c5db199SXin Li device_address)) 866*9c5db199SXin Li return True 867*9c5db199SXin Li except utils.TimeoutError as e: 868*9c5db199SXin Li logging.error('%s: %s', method_name, e) 869*9c5db199SXin Li except Exception as e: 870*9c5db199SXin Li logging.error('%s: unexpected error: %s', method_name, e) 871*9c5db199SXin Li 872*9c5db199SXin Li return False 873*9c5db199SXin Li 874*9c5db199SXin Li def _powerd_last_resume_details(self, before=5, after=0): 875*9c5db199SXin Li """ Look at powerd logs for last suspend/resume attempt. 876*9c5db199SXin Li 877*9c5db199SXin Li Note that logs are in reverse order (chronologically). Keep that in mind 878*9c5db199SXin Li for the 'before' and 'after' parameters. 879*9c5db199SXin Li 880*9c5db199SXin Li @param before: Number of context lines before search item to show. 881*9c5db199SXin Li @param after: Number of context lines after search item to show. 882*9c5db199SXin Li 883*9c5db199SXin Li @return Most recent lines containing suspend resume details or ''. 884*9c5db199SXin Li """ 885*9c5db199SXin Li event_file = '/var/log/power_manager/powerd.LATEST' 886*9c5db199SXin Li 887*9c5db199SXin Li # Each powerd_suspend wakeup has a log "powerd_suspend returned 0", 888*9c5db199SXin Li # with the return code of the suspend. We search for the last 889*9c5db199SXin Li # occurrence in the log, and then find the collocated event_count log, 890*9c5db199SXin Li # indicating the wakeup cause. -B option for grep will actually grab the 891*9c5db199SXin Li # *next* 5 logs in time, since we are piping the powerd file backwards 892*9c5db199SXin Li # with tac command 893*9c5db199SXin Li resume_indicator = 'powerd_suspend returned' 894*9c5db199SXin Li cmd = 'tac {} | grep -A {} -B {} -m1 "{}"'.format( 895*9c5db199SXin Li event_file, after, before, resume_indicator) 896*9c5db199SXin Li 897*9c5db199SXin Li try: 898*9c5db199SXin Li return utils.run(cmd).stdout 899*9c5db199SXin Li except error.CmdError: 900*9c5db199SXin Li logging.error('Could not locate recent suspend') 901*9c5db199SXin Li 902*9c5db199SXin Li return '' 903*9c5db199SXin Li 904*9c5db199SXin Li def bt_caused_last_resume(self): 905*9c5db199SXin Li """Checks if last resume from suspend was caused by bluetooth 906*9c5db199SXin Li 907*9c5db199SXin Li @return: True if BT wake path was cause of resume, False otherwise 908*9c5db199SXin Li """ 909*9c5db199SXin Li 910*9c5db199SXin Li # When the resume cause is printed to powerd log, it omits the 911*9c5db199SXin Li # /power/wakeup portion of wake path 912*9c5db199SXin Li bt_wake_path = self._get_wake_enabled_path() 913*9c5db199SXin Li 914*9c5db199SXin Li # If bluetooth does not have a valid wake path, it could not have caused 915*9c5db199SXin Li # the resume 916*9c5db199SXin Li if not bt_wake_path: 917*9c5db199SXin Li return False 918*9c5db199SXin Li 919*9c5db199SXin Li bt_wake_path = bt_wake_path.replace('/power/wakeup', '') 920*9c5db199SXin Li 921*9c5db199SXin Li last_resume_details = self._powerd_last_resume_details().rstrip( 922*9c5db199SXin Li '\n ').split('\n') 923*9c5db199SXin Li logging.debug('/var/log/power_manager/powerd.LATEST: 5 lines after ' 924*9c5db199SXin Li 'powerd_suspend returns:') 925*9c5db199SXin Li for l in last_resume_details[::-1]: 926*9c5db199SXin Li logging.debug(l) 927*9c5db199SXin Li # If BT caused wake, there will be a line describing the bt wake 928*9c5db199SXin Li # path's event_count before and after the resume 929*9c5db199SXin Li for line in last_resume_details: 930*9c5db199SXin Li if 'event_count' in line: 931*9c5db199SXin Li logging.info('Checking wake event: {}'.format(line)) 932*9c5db199SXin Li if bt_wake_path in line: 933*9c5db199SXin Li logging.debug('BT event woke the DUT') 934*9c5db199SXin Li return True 935*9c5db199SXin Li 936*9c5db199SXin Li return False 937*9c5db199SXin Li 938*9c5db199SXin Li def find_last_suspend_via_powerd_logs(self): 939*9c5db199SXin Li """ Finds the last suspend attempt via powerd logs. 940*9c5db199SXin Li 941*9c5db199SXin Li Finds the last suspend attempt using powerd logs by searching backwards 942*9c5db199SXin Li through the logs to find the latest entries with 'powerd_suspend'. If we 943*9c5db199SXin Li can't find a suspend attempt, we return None. 944*9c5db199SXin Li 945*9c5db199SXin Li @return: Tuple (suspend start time, suspend end time, suspend result) or 946*9c5db199SXin Li None if we can't find a suspend attempt 947*9c5db199SXin Li """ 948*9c5db199SXin Li # Logs look like this (ignore newline): 949*9c5db199SXin Li # 2021-02-11T18:53:43.561880Z INFO powerd: 950*9c5db199SXin Li # [daemon.cc(724)] powerd_suspend returned 0 951*9c5db199SXin Li # ... stuff in between ... 952*9c5db199SXin Li # 2021-02-11T18:53:13.277695Z INFO powerd: 953*9c5db199SXin Li # [suspender.cc(574)] Starting suspend 954*9c5db199SXin Li 955*9c5db199SXin Li # Date format for strptime and strftime 956*9c5db199SXin Li date_format = '%Y-%m-%dT%H:%M:%S.%fZ' 957*9c5db199SXin Li date_group_re = ('(?P<date>[0-9]+-[0-9]+-[0-9]+T' 958*9c5db199SXin Li '[0-9]+:[0-9]+:[0-9]+[.][0-9]+Z)\s') 959*9c5db199SXin Li 960*9c5db199SXin Li finish_suspend_re = re.compile( 961*9c5db199SXin Li '^{date_regex}' 962*9c5db199SXin Li '.*daemon.*powerd_suspend returned ' 963*9c5db199SXin Li '(?P<exitcode>[0-9]+)'.format(date_regex=date_group_re)) 964*9c5db199SXin Li start_suspend_re = re.compile( 965*9c5db199SXin Li '^{date_regex}.*suspender.*' 966*9c5db199SXin Li 'Starting suspend'.format(date_regex=date_group_re)) 967*9c5db199SXin Li 968*9c5db199SXin Li now = datetime.now() 969*9c5db199SXin Li last_resume_details = self._powerd_last_resume_details(before=0, 970*9c5db199SXin Li after=8) 971*9c5db199SXin Li if last_resume_details: 972*9c5db199SXin Li start_time, end_time, ret = None, None, None 973*9c5db199SXin Li try: 974*9c5db199SXin Li for line in last_resume_details.split('\n'): 975*9c5db199SXin Li logging.debug('Last suspend search: %s', line) 976*9c5db199SXin Li m = finish_suspend_re.match(line) 977*9c5db199SXin Li if m: 978*9c5db199SXin Li logging.debug('Found suspend end: date(%s) ret(%s)', 979*9c5db199SXin Li m.group('date'), m.group('exitcode')) 980*9c5db199SXin Li end_time = datetime.strptime( 981*9c5db199SXin Li m.group('date'), 982*9c5db199SXin Li date_format).replace(year=now.year) 983*9c5db199SXin Li ret = int(m.group('exitcode')) 984*9c5db199SXin Li 985*9c5db199SXin Li m = start_suspend_re.match(line) 986*9c5db199SXin Li if m: 987*9c5db199SXin Li logging.debug('Found suspend start: date(%s)', 988*9c5db199SXin Li m.group('date')) 989*9c5db199SXin Li start_time = datetime.strptime( 990*9c5db199SXin Li m.group('date'), 991*9c5db199SXin Li date_format).replace(year=now.year) 992*9c5db199SXin Li break 993*9c5db199SXin Li 994*9c5db199SXin Li if all([x is not None for x in [start_time, end_time, ret]]): 995*9c5db199SXin Li # Return dates in string format due to inconsistency between 996*9c5db199SXin Li # python2/3 usage on host and dut 997*9c5db199SXin Li return (start_time.strftime(self.OUT_DATE_FORMAT), 998*9c5db199SXin Li end_time.strftime(self.OUT_DATE_FORMAT), ret) 999*9c5db199SXin Li else: 1000*9c5db199SXin Li logging.error( 1001*9c5db199SXin Li 'Failed to parse details from last suspend. %s %s %s', 1002*9c5db199SXin Li str(start_time), str(end_time), str(ret)) 1003*9c5db199SXin Li except Exception as e: 1004*9c5db199SXin Li logging.error('Failed to parse last suspend: %s', str(e)) 1005*9c5db199SXin Li else: 1006*9c5db199SXin Li logging.error('No powerd_suspend attempt found') 1007*9c5db199SXin Li 1008*9c5db199SXin Li return None 1009*9c5db199SXin Li 1010*9c5db199SXin Li def do_suspend(self, seconds, expect_bt_wake): 1011*9c5db199SXin Li """Suspend DUT using the power manager. 1012*9c5db199SXin Li 1013*9c5db199SXin Li @param seconds: The number of seconds to suspend the device. 1014*9c5db199SXin Li @param expect_bt_wake: Whether we expect bluetooth to wake us from 1015*9c5db199SXin Li suspend. If true, we expect this resume will occur early 1016*9c5db199SXin Li 1017*9c5db199SXin Li @throws: SuspendFailure on resume with unexpected timing or wake source. 1018*9c5db199SXin Li The raised exception will be handled as a non-zero retcode over the 1019*9c5db199SXin Li RPC, signalling for the test to fail. 1020*9c5db199SXin Li """ 1021*9c5db199SXin Li early_wake = False 1022*9c5db199SXin Li try: 1023*9c5db199SXin Li sys_power.do_suspend(seconds) 1024*9c5db199SXin Li 1025*9c5db199SXin Li except sys_power.SpuriousWakeupError: 1026*9c5db199SXin Li logging.info('Early resume detected...') 1027*9c5db199SXin Li early_wake = True 1028*9c5db199SXin Li 1029*9c5db199SXin Li # Handle error conditions based on test expectations, whether resume 1030*9c5db199SXin Li # was early, and cause of the resume 1031*9c5db199SXin Li bt_caused_wake = self.bt_caused_last_resume() 1032*9c5db199SXin Li logging.info('Cause for resume: {}'.format( 1033*9c5db199SXin Li 'BT' if bt_caused_wake else 'Not BT')) 1034*9c5db199SXin Li 1035*9c5db199SXin Li if not expect_bt_wake and bt_caused_wake: 1036*9c5db199SXin Li raise sys_power.SuspendFailure('BT woke us unexpectedly') 1037*9c5db199SXin Li 1038*9c5db199SXin Li # TODO(b/160803597) - Uncomment when BT wake reason is correctly 1039*9c5db199SXin Li # captured in powerd log. 1040*9c5db199SXin Li # 1041*9c5db199SXin Li # if expect_bt_wake and not bt_caused_wake: 1042*9c5db199SXin Li # raise sys_power.SuspendFailure('BT should have woken us') 1043*9c5db199SXin Li # 1044*9c5db199SXin Li # if bt_caused_wake and not early_wake: 1045*9c5db199SXin Li # raise sys_power.SuspendFailure('BT wake did not come early') 1046*9c5db199SXin Li 1047*9c5db199SXin Li return True 1048*9c5db199SXin Li 1049*9c5db199SXin Li def get_wlan_vid_pid(self): 1050*9c5db199SXin Li """ Return vendor id and product id of the wlan chip on BT/WiFi module 1051*9c5db199SXin Li 1052*9c5db199SXin Li @returns: (vid,pid) on success; (None,None) on failure 1053*9c5db199SXin Li """ 1054*9c5db199SXin Li vid = None 1055*9c5db199SXin Li pid = None 1056*9c5db199SXin Li path_template = '/sys/class/net/%s/device/' 1057*9c5db199SXin Li for dev_name in ['wlan0', 'mlan0']: 1058*9c5db199SXin Li if os.path.exists(path_template % dev_name): 1059*9c5db199SXin Li path_v = path_template % dev_name + 'vendor' 1060*9c5db199SXin Li path_d = path_template % dev_name + 'device' 1061*9c5db199SXin Li logging.debug('Paths are %s %s', path_v, path_d) 1062*9c5db199SXin Li try: 1063*9c5db199SXin Li vid = open(path_v).read().strip('\n') 1064*9c5db199SXin Li pid = open(path_d).read().strip('\n') 1065*9c5db199SXin Li break 1066*9c5db199SXin Li except Exception as e: 1067*9c5db199SXin Li logging.error('Exception %s while reading vid/pid', str(e)) 1068*9c5db199SXin Li logging.debug('returning vid:%s pid:%s', vid, pid) 1069*9c5db199SXin Li return (vid, pid) 1070*9c5db199SXin Li 1071*9c5db199SXin Li def get_bt_transport(self): 1072*9c5db199SXin Li """ Return transport (UART/USB/SDIO) used by BT module 1073*9c5db199SXin Li 1074*9c5db199SXin Li @returns: USB/UART/SDIO on success; None on failure 1075*9c5db199SXin Li """ 1076*9c5db199SXin Li try: 1077*9c5db199SXin Li transport_str = os.path.realpath( 1078*9c5db199SXin Li '/sys/class/bluetooth/hci0/device/driver/module') 1079*9c5db199SXin Li logging.debug('transport is %s', transport_str) 1080*9c5db199SXin Li transport = transport_str.split('/')[-1] 1081*9c5db199SXin Li if transport == 'btusb': 1082*9c5db199SXin Li return 'USB' 1083*9c5db199SXin Li elif transport == 'hci_uart': 1084*9c5db199SXin Li return 'UART' 1085*9c5db199SXin Li elif transport in ['btmrvl_sdio', 'btmtksdio']: 1086*9c5db199SXin Li return 'SDIO' 1087*9c5db199SXin Li else: 1088*9c5db199SXin Li return None 1089*9c5db199SXin Li except Exception as e: 1090*9c5db199SXin Li logging.error('Exception %s in get_bt_transport', str(e)) 1091*9c5db199SXin Li return None 1092*9c5db199SXin Li 1093*9c5db199SXin Li def get_bt_module_name(self): 1094*9c5db199SXin Li """ Return bluetooth module name for non-USB devices 1095*9c5db199SXin Li 1096*9c5db199SXin Li @returns '' on failure. On success return chipset name, if found in 1097*9c5db199SXin Li dict.Otherwise it returns the raw string read. 1098*9c5db199SXin Li """ 1099*9c5db199SXin Li # map the string read from device to chipset name 1100*9c5db199SXin Li chipset_string_dict = { 1101*9c5db199SXin Li 'qcom,wcn3991-bt\x00': 'WCN3991', 1102*9c5db199SXin Li 'qcom,wcn6750-bt\x00': 'WCN6750', 1103*9c5db199SXin Li } 1104*9c5db199SXin Li 1105*9c5db199SXin Li hci_device = '/sys/class/bluetooth/hci0' 1106*9c5db199SXin Li real_path = os.path.realpath(hci_device) 1107*9c5db199SXin Li 1108*9c5db199SXin Li logging.debug('real path is %s', real_path) 1109*9c5db199SXin Li if 'usb' in real_path: 1110*9c5db199SXin Li return '' 1111*9c5db199SXin Li 1112*9c5db199SXin Li device_path = os.path.join(real_path, 'device', 'of_node', 1113*9c5db199SXin Li 'compatible') 1114*9c5db199SXin Li try: 1115*9c5db199SXin Li chipset_string = open(device_path).read() 1116*9c5db199SXin Li logging.debug('read string %s from %s', chipset_string, 1117*9c5db199SXin Li device_path) 1118*9c5db199SXin Li except Exception as e: 1119*9c5db199SXin Li logging.error('Exception %s while reading from file', str(e), 1120*9c5db199SXin Li device_path) 1121*9c5db199SXin Li return '' 1122*9c5db199SXin Li 1123*9c5db199SXin Li if chipset_string in chipset_string_dict: 1124*9c5db199SXin Li return chipset_string_dict[chipset_string] 1125*9c5db199SXin Li else: 1126*9c5db199SXin Li logging.debug("Chipset not known. Returning %s", chipset_string) 1127*9c5db199SXin Li return chipset_string 1128*9c5db199SXin Li 1129*9c5db199SXin Li def get_chipset_name(self): 1130*9c5db199SXin Li """ Get the name of BT/WiFi chipset on this host 1131*9c5db199SXin Li 1132*9c5db199SXin Li @returns chipset name if successful else '' 1133*9c5db199SXin Li """ 1134*9c5db199SXin Li (vid, pid) = self.get_wlan_vid_pid() 1135*9c5db199SXin Li logging.debug('Bluetooth module vid pid is %s %s', vid, pid) 1136*9c5db199SXin Li transport = self.get_bt_transport() 1137*9c5db199SXin Li logging.debug('Bluetooth transport is %s', transport) 1138*9c5db199SXin Li if vid is None or pid is None: 1139*9c5db199SXin Li # Controllers that aren't WLAN+BT combo chips does not expose 1140*9c5db199SXin Li # Vendor ID/Product ID. Use alternate method. 1141*9c5db199SXin Li # This will return one of ['WCN3991', ''] or a string containing 1142*9c5db199SXin Li # the name of chipset read from DUT 1143*9c5db199SXin Li return self.get_bt_module_name() 1144*9c5db199SXin Li for name, l in self.CHIPSET_TO_VIDPID.items(): 1145*9c5db199SXin Li if ((vid, pid), transport) in l: 1146*9c5db199SXin Li return name 1147*9c5db199SXin Li return '' 1148*9c5db199SXin Li 1149*9c5db199SXin Li def get_bt_usb_device_strs(self): 1150*9c5db199SXin Li """ Return the usb endpoints for the bluetooth device, if they exist 1151*9c5db199SXin Li 1152*9c5db199SXin Li We wish to be able to identify usb disconnect events that affect our 1153*9c5db199SXin Li bluetooth operation. To do so, we must first identify the usb endpoint 1154*9c5db199SXin Li that is associated with our bluetooth device. 1155*9c5db199SXin Li 1156*9c5db199SXin Li @returns: Relevant usb endpoints for the bluetooth device, 1157*9c5db199SXin Li i.e. ['1-1','1-1.2'] if they exist, 1158*9c5db199SXin Li [] otherwise 1159*9c5db199SXin Li """ 1160*9c5db199SXin Li 1161*9c5db199SXin Li hci_device = '/sys/class/bluetooth/hci0' 1162*9c5db199SXin Li real_path = os.path.realpath(hci_device) 1163*9c5db199SXin Li 1164*9c5db199SXin Li # real_path for a usb bluetooth controller will look something like: 1165*9c5db199SXin Li # ../../devices/pci0000:00/0000:00:14.0/usb1/1-4/1-4:1.0/bluetooth/hci0 1166*9c5db199SXin Li if 'usb' not in real_path: 1167*9c5db199SXin Li return [] 1168*9c5db199SXin Li 1169*9c5db199SXin Li logging.debug('Searching for usb path: {}'.format(real_path)) 1170*9c5db199SXin Li 1171*9c5db199SXin Li # Grab all numbered entries between 'usb' and 'bluetooth' descriptors 1172*9c5db199SXin Li m = re.search(r'usb(.*)bluetooth', real_path) 1173*9c5db199SXin Li 1174*9c5db199SXin Li if not m: 1175*9c5db199SXin Li logging.error( 1176*9c5db199SXin Li 'Unable to extract usb dev from {}'.format(real_path)) 1177*9c5db199SXin Li return [] 1178*9c5db199SXin Li 1179*9c5db199SXin Li # Return the path as a list of individual usb descriptors 1180*9c5db199SXin Li return m.group(1).split('/') 1181*9c5db199SXin Li 1182*9c5db199SXin Li def get_bt_usb_disconnect_str(self): 1183*9c5db199SXin Li """ Return the expected log error on USB disconnect 1184*9c5db199SXin Li 1185*9c5db199SXin Li Locate the descriptor that will be used from the list of all usb 1186*9c5db199SXin Li descriptors associated with our bluetooth chip, and format into the 1187*9c5db199SXin Li expected string error for USB disconnect 1188*9c5db199SXin Li 1189*9c5db199SXin Li @returns: string representing expected usb disconnect log entry if usb 1190*9c5db199SXin Li device could be identified, None otherwise 1191*9c5db199SXin Li """ 1192*9c5db199SXin Li disconnect_log_template = 'usb {}: USB disconnect' 1193*9c5db199SXin Li descriptors = self.get_bt_usb_device_strs() 1194*9c5db199SXin Li 1195*9c5db199SXin Li # The usb disconnect log message seems to use the most detailed 1196*9c5db199SXin Li # descriptor that does not use the ':1.0' entry 1197*9c5db199SXin Li for d in sorted(descriptors, key=len, reverse=True): 1198*9c5db199SXin Li if ':' not in d: 1199*9c5db199SXin Li return disconnect_log_template.format(d) 1200*9c5db199SXin Li 1201*9c5db199SXin Li return None 1202*9c5db199SXin Li 1203*9c5db199SXin Li def get_device_utc_time(self): 1204*9c5db199SXin Li """ Get the current device time in UTC. """ 1205*9c5db199SXin Li return datetime.utcnow().strftime(self.OUT_DATE_FORMAT) 1206*9c5db199SXin Li 1207*9c5db199SXin Li def create_audio_record_directory(self, audio_record_dir): 1208*9c5db199SXin Li """Create the audio recording directory. 1209*9c5db199SXin Li 1210*9c5db199SXin Li @param audio_record_dir: the audio recording directory 1211*9c5db199SXin Li 1212*9c5db199SXin Li @returns: True on success. False otherwise. 1213*9c5db199SXin Li """ 1214*9c5db199SXin Li try: 1215*9c5db199SXin Li if not os.path.exists(audio_record_dir): 1216*9c5db199SXin Li os.makedirs(audio_record_dir) 1217*9c5db199SXin Li return True 1218*9c5db199SXin Li except Exception as e: 1219*9c5db199SXin Li logging.error('Failed to create %s on the DUT: %s', 1220*9c5db199SXin Li audio_record_dir, e) 1221*9c5db199SXin Li return False 1222*9c5db199SXin Li 1223*9c5db199SXin Li def start_capturing_audio_subprocess(self, audio_data, recording_device): 1224*9c5db199SXin Li """Start capturing audio in a subprocess. 1225*9c5db199SXin Li 1226*9c5db199SXin Li @param audio_data: the audio test data 1227*9c5db199SXin Li @param recording_device: which device recorded the audio, 1228*9c5db199SXin Li possible values are 'recorded_by_dut' or 'recorded_by_peer' 1229*9c5db199SXin Li 1230*9c5db199SXin Li @returns: True on success. False otherwise. 1231*9c5db199SXin Li """ 1232*9c5db199SXin Li audio_data = json.loads(audio_data) 1233*9c5db199SXin Li return self._cras_test_client.start_capturing_subprocess( 1234*9c5db199SXin Li audio_data[recording_device], 1235*9c5db199SXin Li sample_format=audio_data['format'], 1236*9c5db199SXin Li channels=audio_data['channels'], 1237*9c5db199SXin Li rate=audio_data['rate'], 1238*9c5db199SXin Li duration=audio_data['duration']) 1239*9c5db199SXin Li 1240*9c5db199SXin Li def stop_capturing_audio_subprocess(self): 1241*9c5db199SXin Li """Stop capturing audio. 1242*9c5db199SXin Li 1243*9c5db199SXin Li @returns: True on success. False otherwise. 1244*9c5db199SXin Li """ 1245*9c5db199SXin Li return self._cras_test_client.stop_capturing_subprocess() 1246*9c5db199SXin Li 1247*9c5db199SXin Li def _generate_playback_file(self, audio_data): 1248*9c5db199SXin Li """Generate the playback file if it does not exist yet. 1249*9c5db199SXin Li 1250*9c5db199SXin Li Some audio test files may be large. Generate them on the fly 1251*9c5db199SXin Li to save the storage of the source tree. 1252*9c5db199SXin Li 1253*9c5db199SXin Li @param audio_data: the audio test data 1254*9c5db199SXin Li """ 1255*9c5db199SXin Li if not os.path.exists(audio_data['file']): 1256*9c5db199SXin Li data_format = dict(file_type='raw', 1257*9c5db199SXin Li sample_format='S16_LE', 1258*9c5db199SXin Li channel=audio_data['channels'], 1259*9c5db199SXin Li rate=audio_data['rate']) 1260*9c5db199SXin Li 1261*9c5db199SXin Li # Make the audio file a bit longer to handle any delay 1262*9c5db199SXin Li # issue in capturing. 1263*9c5db199SXin Li duration = audio_data['duration'] + 3 1264*9c5db199SXin Li audio_test_data_module.GenerateAudioTestData( 1265*9c5db199SXin Li data_format=data_format, 1266*9c5db199SXin Li path=audio_data['file'], 1267*9c5db199SXin Li duration_secs=duration, 1268*9c5db199SXin Li frequencies=audio_data['frequencies']) 1269*9c5db199SXin Li logging.debug("Raw file generated: %s", audio_data['file']) 1270*9c5db199SXin Li 1271*9c5db199SXin Li def start_playing_audio_subprocess(self, audio_data, pin_device=None): 1272*9c5db199SXin Li """Start playing audio in a subprocess. 1273*9c5db199SXin Li 1274*9c5db199SXin Li @param audio_data: the audio test data. 1275*9c5db199SXin Li @param pin_device: the device id to play audio. 1276*9c5db199SXin Li 1277*9c5db199SXin Li @returns: True on success. False otherwise. 1278*9c5db199SXin Li """ 1279*9c5db199SXin Li audio_data = json.loads(audio_data) 1280*9c5db199SXin Li self._generate_playback_file(audio_data) 1281*9c5db199SXin Li try: 1282*9c5db199SXin Li return self._cras_test_client.start_playing_subprocess( 1283*9c5db199SXin Li audio_data['file'], 1284*9c5db199SXin Li pin_device=pin_device, 1285*9c5db199SXin Li channels=audio_data['channels'], 1286*9c5db199SXin Li rate=audio_data['rate'], 1287*9c5db199SXin Li duration=audio_data['duration']) 1288*9c5db199SXin Li except Exception as e: 1289*9c5db199SXin Li logging.error("start_playing_subprocess() failed: %s", str(e)) 1290*9c5db199SXin Li return False 1291*9c5db199SXin Li 1292*9c5db199SXin Li def stop_playing_audio_subprocess(self): 1293*9c5db199SXin Li """Stop playing audio in the subprocess. 1294*9c5db199SXin Li 1295*9c5db199SXin Li @returns: True on success. False otherwise. 1296*9c5db199SXin Li """ 1297*9c5db199SXin Li return self._cras_test_client.stop_playing_subprocess() 1298*9c5db199SXin Li 1299*9c5db199SXin Li def play_audio(self, audio_data): 1300*9c5db199SXin Li """Play audio. 1301*9c5db199SXin Li 1302*9c5db199SXin Li It blocks until it has completed playing back the audio. 1303*9c5db199SXin Li 1304*9c5db199SXin Li @param audio_data: the audio test data 1305*9c5db199SXin Li 1306*9c5db199SXin Li @returns: True on success. False otherwise. 1307*9c5db199SXin Li """ 1308*9c5db199SXin Li audio_data = json.loads(audio_data) 1309*9c5db199SXin Li self._generate_playback_file(audio_data) 1310*9c5db199SXin Li return self._cras_test_client.play(audio_data['file'], 1311*9c5db199SXin Li channels=audio_data['channels'], 1312*9c5db199SXin Li rate=audio_data['rate'], 1313*9c5db199SXin Li duration=audio_data['duration']) 1314*9c5db199SXin Li 1315*9c5db199SXin Li def check_audio_frames_legitimacy(self, audio_test_data, recording_device, 1316*9c5db199SXin Li recorded_file): 1317*9c5db199SXin Li """Get the number of frames in the recorded audio file. 1318*9c5db199SXin Li 1319*9c5db199SXin Li @param audio_test_data: the audio test data 1320*9c5db199SXin Li @param recording_device: which device recorded the audio, 1321*9c5db199SXin Li possible values are 'recorded_by_dut' or 'recorded_by_peer' 1322*9c5db199SXin Li @param recorded_file: the recorded file name 1323*9c5db199SXin Li 1324*9c5db199SXin Li @returns: True if audio frames are legitimate. 1325*9c5db199SXin Li """ 1326*9c5db199SXin Li if bool(recorded_file): 1327*9c5db199SXin Li recorded_filename = recorded_file 1328*9c5db199SXin Li else: 1329*9c5db199SXin Li audio_test_data = json.loads(audio_test_data) 1330*9c5db199SXin Li recorded_filename = audio_test_data[recording_device] 1331*9c5db199SXin Li 1332*9c5db199SXin Li if recorded_filename.endswith('.raw'): 1333*9c5db199SXin Li # Make sure that the recorded file does not contain all zeros. 1334*9c5db199SXin Li filesize = os.path.getsize(recorded_filename) 1335*9c5db199SXin Li cmd_str = 'cmp -s -n %d %s /dev/zero' % (filesize, 1336*9c5db199SXin Li recorded_filename) 1337*9c5db199SXin Li try: 1338*9c5db199SXin Li result = subprocess.call(cmd_str.split()) 1339*9c5db199SXin Li return result != 0 1340*9c5db199SXin Li except Exception as e: 1341*9c5db199SXin Li logging.error("Failed: %s (%s)", cmd_str, str(e)) 1342*9c5db199SXin Li return False 1343*9c5db199SXin Li else: 1344*9c5db199SXin Li # The recorded wav file should not be empty. 1345*9c5db199SXin Li wav_file = check_quality.WaveFile(recorded_filename) 1346*9c5db199SXin Li return wav_file.get_number_frames() > 0 1347*9c5db199SXin Li 1348*9c5db199SXin Li def convert_audio_sample_rate(self, input_file, out_file, test_data, 1349*9c5db199SXin Li new_rate): 1350*9c5db199SXin Li """Convert audio file to new sample rate. 1351*9c5db199SXin Li 1352*9c5db199SXin Li @param input_file: Path to file to upsample. 1353*9c5db199SXin Li @param out_file: Path to create upsampled file. 1354*9c5db199SXin Li @param test_data: Dictionary with information about file. 1355*9c5db199SXin Li @param new_rate: New rate to upsample file to. 1356*9c5db199SXin Li 1357*9c5db199SXin Li @returns: True if upsampling succeeded, False otherwise. 1358*9c5db199SXin Li """ 1359*9c5db199SXin Li test_data = json.loads(test_data) 1360*9c5db199SXin Li logging.debug('Resampling file {} to new rate {}'.format( 1361*9c5db199SXin Li input_file, new_rate)) 1362*9c5db199SXin Li 1363*9c5db199SXin Li convert_format(input_file, 1364*9c5db199SXin Li test_data['channels'], 1365*9c5db199SXin Li test_data['bit_width'], 1366*9c5db199SXin Li test_data['rate'], 1367*9c5db199SXin Li out_file, 1368*9c5db199SXin Li test_data['channels'], 1369*9c5db199SXin Li test_data['bit_width'], 1370*9c5db199SXin Li new_rate, 1371*9c5db199SXin Li 1.0, 1372*9c5db199SXin Li use_src_header=True, 1373*9c5db199SXin Li use_dst_header=True) 1374*9c5db199SXin Li 1375*9c5db199SXin Li return os.path.isfile(out_file) 1376*9c5db199SXin Li 1377*9c5db199SXin Li def trim_wav_file(self, 1378*9c5db199SXin Li in_file, 1379*9c5db199SXin Li out_file, 1380*9c5db199SXin Li new_duration, 1381*9c5db199SXin Li test_data, 1382*9c5db199SXin Li tolerance=0.1): 1383*9c5db199SXin Li """Trim long file to desired length. 1384*9c5db199SXin Li 1385*9c5db199SXin Li Trims audio file to length by cutting out silence from beginning and 1386*9c5db199SXin Li end. 1387*9c5db199SXin Li 1388*9c5db199SXin Li @param in_file: Path to audio file to be trimmed. 1389*9c5db199SXin Li @param out_file: Path to trimmed audio file to create. 1390*9c5db199SXin Li @param new_duration: A float representing the desired duration of 1391*9c5db199SXin Li the resulting trimmed file. 1392*9c5db199SXin Li @param test_data: Dictionary containing information about the test file. 1393*9c5db199SXin Li @param tolerance: (optional) A float representing the allowable 1394*9c5db199SXin Li difference between trimmed file length and desired duration 1395*9c5db199SXin Li 1396*9c5db199SXin Li @returns: True if file was trimmed successfully, False otherwise. 1397*9c5db199SXin Li """ 1398*9c5db199SXin Li test_data = json.loads(test_data) 1399*9c5db199SXin Li trim_silence_from_wav_file(in_file, out_file, new_duration) 1400*9c5db199SXin Li measured_length = get_file_length(out_file, test_data['channels'], 1401*9c5db199SXin Li test_data['bit_width'], 1402*9c5db199SXin Li test_data['rate']) 1403*9c5db199SXin Li return abs(measured_length - new_duration) <= tolerance 1404*9c5db199SXin Li 1405*9c5db199SXin Li def unzip_audio_test_data(self, tar_path, data_dir): 1406*9c5db199SXin Li """Unzip audio test data files. 1407*9c5db199SXin Li 1408*9c5db199SXin Li @param tar_path: Path to audio test data tarball on DUT. 1409*9c5db199SXin Li @oaram data_dir: Path to directory where to extract test data directory. 1410*9c5db199SXin Li 1411*9c5db199SXin Li @returns: True if audio test data folder exists, False otherwise. 1412*9c5db199SXin Li """ 1413*9c5db199SXin Li logging.debug('Downloading audio test data on DUT') 1414*9c5db199SXin Li # creates path to dir to extract test data to by taking name of the 1415*9c5db199SXin Li # tarball without the extension eg. <dir>/file.ext to data_dir/file/ 1416*9c5db199SXin Li audio_test_dir = os.path.join( 1417*9c5db199SXin Li data_dir, 1418*9c5db199SXin Li os.path.split(tar_path)[1].split('.', 1)[0]) 1419*9c5db199SXin Li 1420*9c5db199SXin Li unzip_cmd = 'tar -xf {0} -C {1}'.format(tar_path, data_dir) 1421*9c5db199SXin Li 1422*9c5db199SXin Li unzip_proc = subprocess.Popen(unzip_cmd.split(), 1423*9c5db199SXin Li stdout=subprocess.PIPE, 1424*9c5db199SXin Li stderr=subprocess.PIPE) 1425*9c5db199SXin Li _, stderr = unzip_proc.communicate() 1426*9c5db199SXin Li 1427*9c5db199SXin Li if stderr: 1428*9c5db199SXin Li logging.error('Error occurred in unzipping audio data: {}'.format( 1429*9c5db199SXin Li str(stderr))) 1430*9c5db199SXin Li return False 1431*9c5db199SXin Li 1432*9c5db199SXin Li return unzip_proc.returncode == 0 and os.path.isdir(audio_test_dir) 1433*9c5db199SXin Li 1434*9c5db199SXin Li def convert_raw_to_wav(self, input_file, output_file, test_data): 1435*9c5db199SXin Li """Convert raw audio file to wav file. 1436*9c5db199SXin Li 1437*9c5db199SXin Li @oaram input_file: the location of the raw file 1438*9c5db199SXin Li @param output_file: the location to place the resulting wav file 1439*9c5db199SXin Li @param test_data: the data for the file being converted 1440*9c5db199SXin Li 1441*9c5db199SXin Li @returns: True if conversion was successful otherwise false 1442*9c5db199SXin Li """ 1443*9c5db199SXin Li test_data = json.loads(test_data) 1444*9c5db199SXin Li convert_raw_file(input_file, test_data['channels'], 1445*9c5db199SXin Li test_data['bit_width'], test_data['rate'], 1446*9c5db199SXin Li output_file) 1447*9c5db199SXin Li 1448*9c5db199SXin Li return os.path.isfile(output_file) 1449*9c5db199SXin Li 1450*9c5db199SXin Li def get_primary_frequencies(self, audio_test_data, recording_device, 1451*9c5db199SXin Li recorded_file): 1452*9c5db199SXin Li """Get primary frequencies of the audio test file. 1453*9c5db199SXin Li 1454*9c5db199SXin Li @param audio_test_data: the audio test data 1455*9c5db199SXin Li @param recording_device: which device recorded the audio, 1456*9c5db199SXin Li possible values are 'recorded_by_dut' or 'recorded_by_peer' 1457*9c5db199SXin Li @param recorded_file: the recorded file name 1458*9c5db199SXin Li 1459*9c5db199SXin Li @returns: a list of primary frequencies of channels in the audio file 1460*9c5db199SXin Li """ 1461*9c5db199SXin Li audio_test_data = json.loads(audio_test_data) 1462*9c5db199SXin Li 1463*9c5db199SXin Li if bool(recorded_file): 1464*9c5db199SXin Li recorded_filename = recorded_file 1465*9c5db199SXin Li else: 1466*9c5db199SXin Li recorded_filename = audio_test_data[recording_device] 1467*9c5db199SXin Li 1468*9c5db199SXin Li args = CheckQualityArgsClass(filename=recorded_filename, 1469*9c5db199SXin Li rate=audio_test_data['rate'], 1470*9c5db199SXin Li channel=audio_test_data['channels'], 1471*9c5db199SXin Li bit_width=16) 1472*9c5db199SXin Li raw_data, rate = check_quality.read_audio_file(args) 1473*9c5db199SXin Li checker = check_quality.QualityChecker(raw_data, rate) 1474*9c5db199SXin Li # The highest frequency recorded would be near 24 Khz 1475*9c5db199SXin Li # as the max sample rate is 48000 in our tests. 1476*9c5db199SXin Li # So let's set ignore_high_freq to be 48000. 1477*9c5db199SXin Li checker.do_spectral_analysis(ignore_high_freq=48000, 1478*9c5db199SXin Li check_quality=False, 1479*9c5db199SXin Li quality_params=None) 1480*9c5db199SXin Li spectra = checker._spectrals 1481*9c5db199SXin Li primary_freq = [ 1482*9c5db199SXin Li float(spectra[i][0][0]) if spectra[i] else 0 1483*9c5db199SXin Li for i in range(len(spectra)) 1484*9c5db199SXin Li ] 1485*9c5db199SXin Li primary_freq.sort() 1486*9c5db199SXin Li return primary_freq 1487*9c5db199SXin Li 1488*9c5db199SXin Li def enable_wbs(self, value): 1489*9c5db199SXin Li """Enable or disable wideband speech (wbs) per the value. 1490*9c5db199SXin Li 1491*9c5db199SXin Li @param value: True to enable wbs. 1492*9c5db199SXin Li 1493*9c5db199SXin Li @returns: True if the operation succeeds. 1494*9c5db199SXin Li """ 1495*9c5db199SXin Li return self._cras_test_client.enable_wbs(value) 1496*9c5db199SXin Li 1497*9c5db199SXin Li def set_player_playback_status(self, status): 1498*9c5db199SXin Li """Set playback status for the registered media player. 1499*9c5db199SXin Li 1500*9c5db199SXin Li @param status: playback status in string. 1501*9c5db199SXin Li 1502*9c5db199SXin Li """ 1503*9c5db199SXin Li return self._cras_test_client.set_player_playback_status(status) 1504*9c5db199SXin Li 1505*9c5db199SXin Li def set_player_position(self, position): 1506*9c5db199SXin Li """Set media position for the registered media player. 1507*9c5db199SXin Li 1508*9c5db199SXin Li @param position: position in micro seconds. 1509*9c5db199SXin Li 1510*9c5db199SXin Li """ 1511*9c5db199SXin Li return self._cras_test_client.set_player_position(position) 1512*9c5db199SXin Li 1513*9c5db199SXin Li def set_player_metadata(self, metadata): 1514*9c5db199SXin Li """Set metadata for the registered media player. 1515*9c5db199SXin Li 1516*9c5db199SXin Li @param metadata: dictionary of media metadata. 1517*9c5db199SXin Li 1518*9c5db199SXin Li """ 1519*9c5db199SXin Li return self._cras_test_client.set_player_metadata(metadata) 1520*9c5db199SXin Li 1521*9c5db199SXin Li def set_player_length(self, length): 1522*9c5db199SXin Li """Set media length for the registered media player. 1523*9c5db199SXin Li 1524*9c5db199SXin Li Media length is a part of metadata information. However, without 1525*9c5db199SXin Li specify its type to int64. dbus-python will guess the variant type to 1526*9c5db199SXin Li be int32 by default. Separate it from the metadata function to help 1527*9c5db199SXin Li prepare the data differently. 1528*9c5db199SXin Li 1529*9c5db199SXin Li @param length: length in micro seconds. 1530*9c5db199SXin Li 1531*9c5db199SXin Li """ 1532*9c5db199SXin Li return self._cras_test_client.set_player_length(length) 1533*9c5db199SXin Li 1534*9c5db199SXin Li def select_input_device(self, device_name): 1535*9c5db199SXin Li """Select the audio input device. 1536*9c5db199SXin Li 1537*9c5db199SXin Li @param device_name: the name of the Bluetooth peer device 1538*9c5db199SXin Li 1539*9c5db199SXin Li @returns: True if the operation succeeds. 1540*9c5db199SXin Li """ 1541*9c5db199SXin Li return self._cras_test_client.select_input_device(device_name) 1542*9c5db199SXin Li 1543*9c5db199SXin Li @dbus_safe(None) 1544*9c5db199SXin Li def select_output_node(self, node_type): 1545*9c5db199SXin Li """Select the audio output node. 1546*9c5db199SXin Li 1547*9c5db199SXin Li @param node_type: the node type of the Bluetooth peer device 1548*9c5db199SXin Li 1549*9c5db199SXin Li @returns: True if the operation succeeds. 1550*9c5db199SXin Li """ 1551*9c5db199SXin Li return cras_utils.set_single_selected_output_node(node_type) 1552*9c5db199SXin Li 1553*9c5db199SXin Li @dbus_safe(None) 1554*9c5db199SXin Li def get_selected_output_device_type(self): 1555*9c5db199SXin Li """Get the selected audio output node type. 1556*9c5db199SXin Li 1557*9c5db199SXin Li @returns: the node type of the selected output device. 1558*9c5db199SXin Li """ 1559*9c5db199SXin Li # Note: should convert the dbus.String to the regular string. 1560*9c5db199SXin Li return str(cras_utils.get_selected_output_device_type()) 1561*9c5db199SXin Li 1562*9c5db199SXin Li @dbus_safe(None) 1563*9c5db199SXin Li def get_device_id_from_node_type(self, node_type, is_input): 1564*9c5db199SXin Li """Gets device id from node type. 1565*9c5db199SXin Li 1566*9c5db199SXin Li @param node_type: a node type defined in CRAS_NODE_TYPES. 1567*9c5db199SXin Li @param is_input: True if the node is input. False otherwise. 1568*9c5db199SXin Li 1569*9c5db199SXin Li @returns: a string for device id. 1570*9c5db199SXin Li """ 1571*9c5db199SXin Li return cras_utils.get_device_id_from_node_type(node_type, is_input) 1572*9c5db199SXin Li 1573*9c5db199SXin Li def get_audio_thread_summary(self): 1574*9c5db199SXin Li """Dumps audio thread info. 1575*9c5db199SXin Li 1576*9c5db199SXin Li @returns: a list of cras audio information. 1577*9c5db199SXin Li """ 1578*9c5db199SXin Li return cras_utils.get_audio_thread_summary() 1579*9c5db199SXin Li 1580*9c5db199SXin Li def is_btmanagerd_present(self): 1581*9c5db199SXin Li """ Check if /usr/bin/btmanagerd file is present 1582*9c5db199SXin Li 1583*9c5db199SXin Li @returns: True if /usr/bin/btmanagerd is present and False if not 1584*9c5db199SXin Li """ 1585*9c5db199SXin Li return os.path.exists(self.BTMANGERD_FILE_PATH) 1586*9c5db199SXin Li 1587*9c5db199SXin Li 1588*9c5db199SXin Liclass BluezPairingAgent: 1589*9c5db199SXin Li """The agent handling the authentication process of bluetooth pairing. 1590*9c5db199SXin Li 1591*9c5db199SXin Li BluezPairingAgent overrides RequestPinCode method to return a given pin code. 1592*9c5db199SXin Li User can use this agent to pair bluetooth device which has a known 1593*9c5db199SXin Li pin code. 1594*9c5db199SXin Li 1595*9c5db199SXin Li TODO (josephsih): more pairing modes other than pin code would be 1596*9c5db199SXin Li supported later. 1597*9c5db199SXin Li 1598*9c5db199SXin Li """ 1599*9c5db199SXin Li 1600*9c5db199SXin Li def __init__(self, bus, path, pin): 1601*9c5db199SXin Li """Constructor. 1602*9c5db199SXin Li 1603*9c5db199SXin Li @param bus: system bus object. 1604*9c5db199SXin Li @param path: Object path to register. 1605*9c5db199SXin Li @param pin: Pin to respond with for |RequestPinCode|. 1606*9c5db199SXin Li """ 1607*9c5db199SXin Li self._pin = pin 1608*9c5db199SXin Li self.path = path 1609*9c5db199SXin Li self.obj = bus.register_object(path, self, None) 1610*9c5db199SXin Li 1611*9c5db199SXin Li # D-Bus service definition (required by pydbus). 1612*9c5db199SXin Li dbus = """ 1613*9c5db199SXin Li <node> 1614*9c5db199SXin Li <interface name="org.bluez.Agent1"> 1615*9c5db199SXin Li <method name="RequestPinCode"> 1616*9c5db199SXin Li <arg type="o" name="device_path" direction="in" /> 1617*9c5db199SXin Li <arg type="s" name="response" direction="out" /> 1618*9c5db199SXin Li </method> 1619*9c5db199SXin Li <method name="AuthorizeService"> 1620*9c5db199SXin Li <arg type="o" name="device_path" direction="in" /> 1621*9c5db199SXin Li <arg type="s" name="uuid" direction="in" /> 1622*9c5db199SXin Li <arg type="b" name="response" direction="out" /> 1623*9c5db199SXin Li </method> 1624*9c5db199SXin Li </interface> 1625*9c5db199SXin Li </node> 1626*9c5db199SXin Li """ 1627*9c5db199SXin Li 1628*9c5db199SXin Li def unregister(self): 1629*9c5db199SXin Li """Unregisters self from bus.""" 1630*9c5db199SXin Li self.obj.unregister() 1631*9c5db199SXin Li 1632*9c5db199SXin Li def RequestPinCode(self, device_path): 1633*9c5db199SXin Li """Requests pin code for a device. 1634*9c5db199SXin Li 1635*9c5db199SXin Li Returns the known pin code for the request. 1636*9c5db199SXin Li 1637*9c5db199SXin Li @param device_path: The object path of the device. 1638*9c5db199SXin Li 1639*9c5db199SXin Li @returns: The known pin code. 1640*9c5db199SXin Li 1641*9c5db199SXin Li """ 1642*9c5db199SXin Li logging.info('RequestPinCode for %s; return %s', device_path, 1643*9c5db199SXin Li self._pin) 1644*9c5db199SXin Li return self._pin 1645*9c5db199SXin Li 1646*9c5db199SXin Li def AuthorizeService(self, device_path, uuid): 1647*9c5db199SXin Li """Authorize given service for device. 1648*9c5db199SXin Li 1649*9c5db199SXin Li @param device_path: The object path of the device. 1650*9c5db199SXin Li @param uuid: The service that needs to be authorized. 1651*9c5db199SXin Li 1652*9c5db199SXin Li @returns: True (we authorize everything since this is a test) 1653*9c5db199SXin Li """ 1654*9c5db199SXin Li return True 1655*9c5db199SXin Li 1656*9c5db199SXin Li 1657*9c5db199SXin Liclass BluezFacadeLocal(BluetoothBaseFacadeLocal): 1658*9c5db199SXin Li """Exposes DUT methods called remotely during Bluetooth autotests for the 1659*9c5db199SXin Li Bluez daemon. 1660*9c5db199SXin Li 1661*9c5db199SXin Li All instance methods of this object without a preceding '_' are exposed via 1662*9c5db199SXin Li an XML-RPC server. This is not a stateless handler object, which means that 1663*9c5db199SXin Li if you store state inside the delegate, that state will remain around for 1664*9c5db199SXin Li future calls. 1665*9c5db199SXin Li """ 1666*9c5db199SXin Li 1667*9c5db199SXin Li BLUETOOTHD_JOB = 'bluetoothd' 1668*9c5db199SXin Li 1669*9c5db199SXin Li DBUS_ERROR_SERVICEUNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown' 1670*9c5db199SXin Li 1671*9c5db199SXin Li BLUEZ_SERVICE_NAME = 'org.bluez' 1672*9c5db199SXin Li BLUEZ_MANAGER_PATH = '/' 1673*9c5db199SXin Li BLUEZ_DEBUG_LOG_PATH = '/org/chromium/Bluetooth' 1674*9c5db199SXin Li BLUEZ_DEBUG_LOG_IFACE = 'org.chromium.Bluetooth.Debug' 1675*9c5db199SXin Li BLUEZ_MANAGER_IFACE = 'org.freedesktop.DBus.ObjectManager' 1676*9c5db199SXin Li BLUEZ_ADAPTER_IFACE = 'org.bluez.Adapter1' 1677*9c5db199SXin Li BLUEZ_ADMIN_POLICY_SET_IFACE = 'org.bluez.AdminPolicySet1' 1678*9c5db199SXin Li BLUEZ_ADMIN_POLICY_STATUS_IFACE = 'org.bluez.AdminPolicyStatus1' 1679*9c5db199SXin Li BLUEZ_BATTERY_IFACE = 'org.bluez.Battery1' 1680*9c5db199SXin Li BLUEZ_DEVICE_IFACE = 'org.bluez.Device1' 1681*9c5db199SXin Li BLUEZ_GATT_SERV_IFACE = 'org.bluez.GattService1' 1682*9c5db199SXin Li BLUEZ_GATT_CHAR_IFACE = 'org.bluez.GattCharacteristic1' 1683*9c5db199SXin Li BLUEZ_GATT_DESC_IFACE = 'org.bluez.GattDescriptor1' 1684*9c5db199SXin Li BLUEZ_LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1' 1685*9c5db199SXin Li BLUEZ_ADV_MONITOR_MANAGER_IFACE = 'org.bluez.AdvertisementMonitorManager1' 1686*9c5db199SXin Li BLUEZ_AGENT_MANAGER_PATH = '/org/bluez' 1687*9c5db199SXin Li BLUEZ_AGENT_MANAGER_IFACE = 'org.bluez.AgentManager1' 1688*9c5db199SXin Li BLUEZ_PROFILE_MANAGER_PATH = '/org/bluez' 1689*9c5db199SXin Li BLUEZ_PROFILE_MANAGER_IFACE = 'org.bluez.ProfileManager1' 1690*9c5db199SXin Li BLUEZ_ERROR_ALREADY_EXISTS = 'org.bluez.Error.AlreadyExists' 1691*9c5db199SXin Li BLUEZ_PLUGIN_DEVICE_IFACE = 'org.chromium.BluetoothDevice' 1692*9c5db199SXin Li DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties' 1693*9c5db199SXin Li AGENT_PATH = '/test/agent' 1694*9c5db199SXin Li 1695*9c5db199SXin Li BTMON_STOP_DELAY_SECS = 3 1696*9c5db199SXin Li 1697*9c5db199SXin Li # Timeout for how long we'll wait for BlueZ and the Adapter to show up 1698*9c5db199SXin Li # after reset. 1699*9c5db199SXin Li ADAPTER_TIMEOUT = 30 1700*9c5db199SXin Li 1701*9c5db199SXin Li # How long we should wait for property update signal before we cancel it. 1702*9c5db199SXin Li PROPERTY_UPDATE_TIMEOUT_MILLI_SECS = 5000 1703*9c5db199SXin Li 1704*9c5db199SXin Li # How often we should check for property update exit. 1705*9c5db199SXin Li PROPERTY_UPDATE_CHECK_MILLI_SECS = 500 1706*9c5db199SXin Li 1707*9c5db199SXin Li def __init__(self): 1708*9c5db199SXin Li # Init the BaseFacade first 1709*9c5db199SXin Li super(BluezFacadeLocal, self).__init__() 1710*9c5db199SXin Li 1711*9c5db199SXin Li # Open the Bluetooth Raw socket to the kernel which provides us direct, 1712*9c5db199SXin Li # raw, access to the HCI controller. 1713*9c5db199SXin Li self._raw = bluetooth_socket.BluetoothRawSocket() 1714*9c5db199SXin Li 1715*9c5db199SXin Li # Open the Bluetooth Control socket to the kernel which provides us 1716*9c5db199SXin Li # raw management access to the Bluetooth Host Subsystem. Read the list 1717*9c5db199SXin Li # of adapter indexes to determine whether or not this device has a 1718*9c5db199SXin Li # Bluetooth Adapter or not. 1719*9c5db199SXin Li self._control = bluetooth_socket.BluetoothControlSocket() 1720*9c5db199SXin Li self._has_adapter = len(self._control.read_index_list()) > 0 1721*9c5db199SXin Li 1722*9c5db199SXin Li # Create an Advertisement Monitor App Manager instance. 1723*9c5db199SXin Li # This needs to be created before making any dbus connections as 1724*9c5db199SXin Li # AdvMonitorAppMgr internally forks a new helper process and due to 1725*9c5db199SXin Li # a limitation of python, it is not possible to fork a new process 1726*9c5db199SXin Li # once any dbus connections are established. 1727*9c5db199SXin Li self.advmon_appmgr = adv_monitor_helper.AdvMonitorAppMgr() 1728*9c5db199SXin Li 1729*9c5db199SXin Li # Set up the connection to the D-Bus System Bus, get the object for 1730*9c5db199SXin Li # the Bluetooth Userspace Daemon (BlueZ) and that daemon's object for 1731*9c5db199SXin Li # the Bluetooth Adapter, and the advertising manager. 1732*9c5db199SXin Li self.bus = pydbus.SystemBus() 1733*9c5db199SXin Li self._update_bluez() 1734*9c5db199SXin Li self._update_adapter() 1735*9c5db199SXin Li self._update_advertising() 1736*9c5db199SXin Li self._update_adv_monitor_manager() 1737*9c5db199SXin Li 1738*9c5db199SXin Li # The agent to handle pin code request, which will be 1739*9c5db199SXin Li # created when user calls pair_legacy_device method. 1740*9c5db199SXin Li self._pairing_agent = None 1741*9c5db199SXin Li # The default capability of the agent. 1742*9c5db199SXin Li self._capability = 'KeyboardDisplay' 1743*9c5db199SXin Li 1744*9c5db199SXin Li # Initialize a btmon object to record bluetoothd's activity. 1745*9c5db199SXin Li self.btmon = output_recorder.OutputRecorder( 1746*9c5db199SXin Li ['btmon', '-c', 'never'], 1747*9c5db199SXin Li stop_delay_secs=self.BTMON_STOP_DELAY_SECS) 1748*9c5db199SXin Li 1749*9c5db199SXin Li self.advertisements = [] 1750*9c5db199SXin Li self.advmon_interleave_logger = logger_helper.InterleaveLogger() 1751*9c5db199SXin Li self._chrc_property = None 1752*9c5db199SXin Li self._timeout_id = 0 1753*9c5db199SXin Li self._signal_watch = None 1754*9c5db199SXin Li self._dbus_mainloop = GObject.MainLoop() 1755*9c5db199SXin Li 1756*9c5db199SXin Li @dbus_safe(False) 1757*9c5db199SXin Li def set_debug_log_levels(self, bluez_vb, kernel_vb): 1758*9c5db199SXin Li """Enable or disable the debug logs of bluetooth 1759*9c5db199SXin Li 1760*9c5db199SXin Li @param bluez_vb: verbosity of bluez debug log, either 0 or 1 1761*9c5db199SXin Li @param kernel_vb: verbosity of kernel debug log, either 0 or 1 1762*9c5db199SXin Li 1763*9c5db199SXin Li """ 1764*9c5db199SXin Li debug_object = self.bus.get(self.BLUEZ_SERVICE_NAME, 1765*9c5db199SXin Li self.BLUEZ_DEBUG_LOG_PATH) 1766*9c5db199SXin Li 1767*9c5db199SXin Li # Make a raw synchronous call using GLib (pydbus doesn't correctly 1768*9c5db199SXin Li # serialize '(yy)'. 1769*9c5db199SXin Li raw_dbus_call_sync(self.bus, debug_object, self.BLUEZ_DEBUG_LOG_IFACE, 1770*9c5db199SXin Li 'SetLevels', 1771*9c5db199SXin Li GLib.Variant('(yy)', (bluez_vb, kernel_vb)), 1772*9c5db199SXin Li GLib.VariantType.new('()')) 1773*9c5db199SXin Li return 1774*9c5db199SXin Li 1775*9c5db199SXin Li @dbus_safe(False) 1776*9c5db199SXin Li def set_quality_debug_log(self, enable): 1777*9c5db199SXin Li """Enable or disable bluez quality debug log in the DUT 1778*9c5db199SXin Li @param enable: True to enable all of the debug log, 1779*9c5db199SXin Li False to disable all of the debug log. 1780*9c5db199SXin Li """ 1781*9c5db199SXin Li bluez_debug = self.bus.get( 1782*9c5db199SXin Li self.BLUEZ_SERVICE_NAME, self.BLUEZ_DEBUG_LOG_PATH)[ 1783*9c5db199SXin Li self.BLUEZ_DEBUG_LOG_IFACE] 1784*9c5db199SXin Li bluez_debug.SetQualityDebug(enable) 1785*9c5db199SXin Li 1786*9c5db199SXin Li @dbus_safe(False) 1787*9c5db199SXin Li def start_bluetoothd(self): 1788*9c5db199SXin Li """start bluetoothd. 1789*9c5db199SXin Li 1790*9c5db199SXin Li This includes powering up the adapter. 1791*9c5db199SXin Li 1792*9c5db199SXin Li @returns: True if bluetoothd is started correctly. 1793*9c5db199SXin Li False otherwise. 1794*9c5db199SXin Li 1795*9c5db199SXin Li """ 1796*9c5db199SXin Li # Always start bluez tests with Floss disabled 1797*9c5db199SXin Li self.configure_floss(enabled=False) 1798*9c5db199SXin Li 1799*9c5db199SXin Li # Start the daemon and exit if that fails. 1800*9c5db199SXin Li if not UpstartClient.start(self.BLUETOOTHD_JOB): 1801*9c5db199SXin Li return False 1802*9c5db199SXin Li 1803*9c5db199SXin Li logging.debug('waiting for bluez start') 1804*9c5db199SXin Li try: 1805*9c5db199SXin Li utils.poll_for_condition(condition=self._update_bluez, 1806*9c5db199SXin Li desc='Bluetooth Daemon has started.', 1807*9c5db199SXin Li timeout=self.ADAPTER_TIMEOUT) 1808*9c5db199SXin Li except Exception as e: 1809*9c5db199SXin Li logging.error('timeout: error starting bluetoothd: %s', e) 1810*9c5db199SXin Li return False 1811*9c5db199SXin Li 1812*9c5db199SXin Li # Waiting for the self._adapter object. 1813*9c5db199SXin Li # This does not mean that the adapter is powered on. 1814*9c5db199SXin Li logging.debug('waiting for bluez to obtain adapter information') 1815*9c5db199SXin Li try: 1816*9c5db199SXin Li utils.poll_for_condition( 1817*9c5db199SXin Li condition=self._update_adapter, 1818*9c5db199SXin Li desc='Bluetooth Daemon has adapter information.', 1819*9c5db199SXin Li timeout=self.ADAPTER_TIMEOUT) 1820*9c5db199SXin Li except Exception as e: 1821*9c5db199SXin Li logging.error('timeout: error starting adapter: %s', e) 1822*9c5db199SXin Li return False 1823*9c5db199SXin Li 1824*9c5db199SXin Li # Waiting for the self._advertising interface object. 1825*9c5db199SXin Li logging.debug('waiting for bluez to obtain interface manager.') 1826*9c5db199SXin Li try: 1827*9c5db199SXin Li utils.poll_for_condition( 1828*9c5db199SXin Li condition=self._update_advertising, 1829*9c5db199SXin Li desc='Bluetooth Daemon has advertising interface.', 1830*9c5db199SXin Li timeout=self.ADAPTER_TIMEOUT) 1831*9c5db199SXin Li except utils.TimeoutError: 1832*9c5db199SXin Li logging.error('timeout: error getting advertising interface') 1833*9c5db199SXin Li return False 1834*9c5db199SXin Li 1835*9c5db199SXin Li # Register the pairing agent so we can authorize connections 1836*9c5db199SXin Li logging.debug('registering default pairing agent') 1837*9c5db199SXin Li self._setup_pairing_agent(0) 1838*9c5db199SXin Li 1839*9c5db199SXin Li return True 1840*9c5db199SXin Li 1841*9c5db199SXin Li @dbus_safe(False) 1842*9c5db199SXin Li def stop_bluetoothd(self): 1843*9c5db199SXin Li """stop bluetoothd. 1844*9c5db199SXin Li 1845*9c5db199SXin Li @returns: True if bluetoothd is stopped correctly. 1846*9c5db199SXin Li False otherwise. 1847*9c5db199SXin Li 1848*9c5db199SXin Li """ 1849*9c5db199SXin Li 1850*9c5db199SXin Li def bluez_stopped(): 1851*9c5db199SXin Li """Checks the bluetooth daemon status. 1852*9c5db199SXin Li 1853*9c5db199SXin Li @returns: True if bluez is stopped. False otherwise. 1854*9c5db199SXin Li 1855*9c5db199SXin Li """ 1856*9c5db199SXin Li return not self._update_bluez() 1857*9c5db199SXin Li 1858*9c5db199SXin Li # Stop the daemon and exit if that fails. 1859*9c5db199SXin Li if not UpstartClient.stop(self.BLUETOOTHD_JOB): 1860*9c5db199SXin Li return False 1861*9c5db199SXin Li 1862*9c5db199SXin Li logging.debug('waiting for bluez stop') 1863*9c5db199SXin Li try: 1864*9c5db199SXin Li utils.poll_for_condition(condition=bluez_stopped, 1865*9c5db199SXin Li desc='Bluetooth Daemon has stopped.', 1866*9c5db199SXin Li timeout=self.ADAPTER_TIMEOUT) 1867*9c5db199SXin Li bluetoothd_stopped = True 1868*9c5db199SXin Li except Exception as e: 1869*9c5db199SXin Li logging.error('timeout: error stopping bluetoothd: %s', e) 1870*9c5db199SXin Li bluetoothd_stopped = False 1871*9c5db199SXin Li 1872*9c5db199SXin Li return bluetoothd_stopped 1873*9c5db199SXin Li 1874*9c5db199SXin Li def restart_cras(self): 1875*9c5db199SXin Li """Restarts the cras daemon.""" 1876*9c5db199SXin Li return self._restart_cras() 1877*9c5db199SXin Li 1878*9c5db199SXin Li def is_bluetoothd_running(self): 1879*9c5db199SXin Li """Is bluetoothd running? 1880*9c5db199SXin Li 1881*9c5db199SXin Li @returns: True if bluetoothd is running 1882*9c5db199SXin Li 1883*9c5db199SXin Li """ 1884*9c5db199SXin Li return bool(self._get_dbus_proxy_for_bluetoothd()) 1885*9c5db199SXin Li 1886*9c5db199SXin Li def is_bluetoothd_proxy_valid(self): 1887*9c5db199SXin Li """Checks whether the proxy object for bluetoothd is ok. 1888*9c5db199SXin Li 1889*9c5db199SXin Li The dbus proxy object (self._bluez) can become unusable if bluetoothd 1890*9c5db199SXin Li crashes or restarts for any reason. This method checks whether this has 1891*9c5db199SXin Li happened by attempting to use the object proxy. If bluetoothd has 1892*9c5db199SXin Li restarted (or is not available), then the session will no longer be 1893*9c5db199SXin Li valid and this will result in a dbus exception (GLib.Error). 1894*9c5db199SXin Li 1895*9c5db199SXin Li Returns: 1896*9c5db199SXin Li True if the bluez proxy is still usable. False otherwise. 1897*9c5db199SXin Li """ 1898*9c5db199SXin Li 1899*9c5db199SXin Li try: 1900*9c5db199SXin Li return self.is_bluetoothd_running() and bool( 1901*9c5db199SXin Li self._objmgr_proxy) and bool( 1902*9c5db199SXin Li self._objmgr_proxy.GetManagedObjects()) 1903*9c5db199SXin Li except GLib.Error: 1904*9c5db199SXin Li return False 1905*9c5db199SXin Li 1906*9c5db199SXin Li def _update_bluez(self): 1907*9c5db199SXin Li """Store a D-Bus proxy for the Bluetooth daemon in self._bluez. 1908*9c5db199SXin Li 1909*9c5db199SXin Li This may be called in a loop until it returns True to wait for the 1910*9c5db199SXin Li daemon to be ready after it has been started. 1911*9c5db199SXin Li 1912*9c5db199SXin Li @return True on success, False otherwise. 1913*9c5db199SXin Li 1914*9c5db199SXin Li """ 1915*9c5db199SXin Li self._bluez = self._get_dbus_proxy_for_bluetoothd() 1916*9c5db199SXin Li return bool(self._bluez) 1917*9c5db199SXin Li 1918*9c5db199SXin Li @property 1919*9c5db199SXin Li def _objmgr_proxy(self): 1920*9c5db199SXin Li """Returns proxy object to object manager if bluez is valid.""" 1921*9c5db199SXin Li if self._bluez: 1922*9c5db199SXin Li return self._bluez[self.BLUEZ_MANAGER_IFACE] 1923*9c5db199SXin Li 1924*9c5db199SXin Li return None 1925*9c5db199SXin Li 1926*9c5db199SXin Li @dbus_safe(False) 1927*9c5db199SXin Li def _get_dbus_proxy_for_bluetoothd(self): 1928*9c5db199SXin Li """Get the D-Bus proxy for the Bluetooth daemon. 1929*9c5db199SXin Li 1930*9c5db199SXin Li @return True on success, False otherwise. 1931*9c5db199SXin Li 1932*9c5db199SXin Li """ 1933*9c5db199SXin Li bluez = None 1934*9c5db199SXin Li try: 1935*9c5db199SXin Li bluez = self.bus.get(self.BLUEZ_SERVICE_NAME, 1936*9c5db199SXin Li self.BLUEZ_MANAGER_PATH) 1937*9c5db199SXin Li logging.debug('bluetoothd is running') 1938*9c5db199SXin Li except GLib.Error as e: 1939*9c5db199SXin Li # When bluetoothd is not running, the exception looks like 1940*9c5db199SXin Li # org.freedesktop.DBus.Error.ServiceUnknown: The name org.bluez 1941*9c5db199SXin Li # was not provided by any .service files 1942*9c5db199SXin Li if self.DBUS_ERROR_SERVICEUNKNOWN in str(e): 1943*9c5db199SXin Li logging.debug('bluetoothd is not running') 1944*9c5db199SXin Li else: 1945*9c5db199SXin Li logging.error('Error getting dbus proxy for Bluez: %s', e) 1946*9c5db199SXin Li return bluez 1947*9c5db199SXin Li 1948*9c5db199SXin Li def _update_adapter(self): 1949*9c5db199SXin Li """Store a D-Bus proxy for the local adapter in self._adapter. 1950*9c5db199SXin Li 1951*9c5db199SXin Li This may be called in a loop until it returns True to wait for the 1952*9c5db199SXin Li daemon to be ready, and have obtained the adapter information itself, 1953*9c5db199SXin Li after it has been started. 1954*9c5db199SXin Li 1955*9c5db199SXin Li Since not all devices will have adapters, this will also return True 1956*9c5db199SXin Li in the case where we have obtained an empty adapter index list from the 1957*9c5db199SXin Li kernel. 1958*9c5db199SXin Li 1959*9c5db199SXin Li Note that this method does not power on the adapter. 1960*9c5db199SXin Li 1961*9c5db199SXin Li @return True on success, including if there is no local adapter, 1962*9c5db199SXin Li False otherwise. 1963*9c5db199SXin Li 1964*9c5db199SXin Li """ 1965*9c5db199SXin Li self._adapter = None 1966*9c5db199SXin Li self._adapter_path = None 1967*9c5db199SXin Li 1968*9c5db199SXin Li # Re-check kernel to make sure adapter is available 1969*9c5db199SXin Li self._has_adapter = len(self._control.read_index_list()) > 0 1970*9c5db199SXin Li 1971*9c5db199SXin Li if self._bluez is None: 1972*9c5db199SXin Li logging.warning('Bluez not found!') 1973*9c5db199SXin Li return False 1974*9c5db199SXin Li if not self._has_adapter: 1975*9c5db199SXin Li logging.debug('Device has no adapter; returning') 1976*9c5db199SXin Li return True 1977*9c5db199SXin Li (self._adapter, self._adapter_path) = self._get_adapter() 1978*9c5db199SXin Li return bool(self._adapter) 1979*9c5db199SXin Li 1980*9c5db199SXin Li def _update_advertising(self): 1981*9c5db199SXin Li """Store a D-Bus proxy for the local advertising interface manager. 1982*9c5db199SXin Li 1983*9c5db199SXin Li This may be called repeatedly in a loop until True is returned; 1984*9c5db199SXin Li otherwise we wait for bluetoothd to start. After bluetoothd starts, we 1985*9c5db199SXin Li check the existence of a local adapter and proceed to get the 1986*9c5db199SXin Li advertisement interface manager. 1987*9c5db199SXin Li 1988*9c5db199SXin Li Since not all devices will have adapters, this will also return True 1989*9c5db199SXin Li in the case where there is no adapter. 1990*9c5db199SXin Li 1991*9c5db199SXin Li @return True on success, including if there is no local adapter, 1992*9c5db199SXin Li False otherwise. 1993*9c5db199SXin Li 1994*9c5db199SXin Li """ 1995*9c5db199SXin Li self._advertising = None 1996*9c5db199SXin Li if self._bluez is None: 1997*9c5db199SXin Li logging.warning('Bluez not found!') 1998*9c5db199SXin Li return False 1999*9c5db199SXin Li if not self._has_adapter: 2000*9c5db199SXin Li logging.debug('Device has no adapter; returning') 2001*9c5db199SXin Li return True 2002*9c5db199SXin Li self._advertising = self._advertising_proxy 2003*9c5db199SXin Li return bool(self._advertising) 2004*9c5db199SXin Li 2005*9c5db199SXin Li def _update_adv_monitor_manager(self): 2006*9c5db199SXin Li """Store a D-Bus proxy for the local advertisement monitor manager. 2007*9c5db199SXin Li 2008*9c5db199SXin Li This may be called repeatedly in a loop until True is returned; 2009*9c5db199SXin Li otherwise we wait for bluetoothd to start. After bluetoothd starts, we 2010*9c5db199SXin Li check the existence of a local adapter and proceed to get the 2011*9c5db199SXin Li advertisement monitor manager interface. 2012*9c5db199SXin Li 2013*9c5db199SXin Li Since not all devices will have adapters, this will also return True 2014*9c5db199SXin Li in the case where there is no adapter. 2015*9c5db199SXin Li 2016*9c5db199SXin Li @return True on success, including if there is no local adapter, 2017*9c5db199SXin Li False otherwise. 2018*9c5db199SXin Li 2019*9c5db199SXin Li """ 2020*9c5db199SXin Li self._adv_monitor_manager = None 2021*9c5db199SXin Li if self._bluez is None: 2022*9c5db199SXin Li logging.warning('Bluez not found!') 2023*9c5db199SXin Li return False 2024*9c5db199SXin Li if not self._has_adapter: 2025*9c5db199SXin Li logging.debug('Device has no adapter; returning without ' 2026*9c5db199SXin Li 'advertisement monitor manager') 2027*9c5db199SXin Li return True 2028*9c5db199SXin Li self._adv_monitor_manager = self._get_adv_monitor_manager() 2029*9c5db199SXin Li return bool(self._adv_monitor_manager) 2030*9c5db199SXin Li 2031*9c5db199SXin Li @dbus_safe(False) 2032*9c5db199SXin Li def _get_adapter(self): 2033*9c5db199SXin Li """Get the D-Bus proxy for the local adapter. 2034*9c5db199SXin Li 2035*9c5db199SXin Li @return Tuple of (adapter, object_path) on success else (None, None). 2036*9c5db199SXin Li 2037*9c5db199SXin Li """ 2038*9c5db199SXin Li objects = self._objmgr_proxy.GetManagedObjects() 2039*9c5db199SXin Li for path, ifaces in six.iteritems(objects): 2040*9c5db199SXin Li logging.debug('%s -> %r', path, list(ifaces.keys())) 2041*9c5db199SXin Li if self.BLUEZ_ADAPTER_IFACE in ifaces: 2042*9c5db199SXin Li logging.debug('using adapter %s', path) 2043*9c5db199SXin Li adapter = self.bus.get(self.BLUEZ_SERVICE_NAME, path) 2044*9c5db199SXin Li return (adapter, path) 2045*9c5db199SXin Li else: 2046*9c5db199SXin Li logging.warning('No adapter found in interface!') 2047*9c5db199SXin Li return (None, None) 2048*9c5db199SXin Li 2049*9c5db199SXin Li @property 2050*9c5db199SXin Li def _adapter_proxy(self): 2051*9c5db199SXin Li """Returns proxy object to adapter interface if adapter is valid.""" 2052*9c5db199SXin Li if self._adapter: 2053*9c5db199SXin Li return self._adapter[self.BLUEZ_ADAPTER_IFACE] 2054*9c5db199SXin Li 2055*9c5db199SXin Li return None 2056*9c5db199SXin Li 2057*9c5db199SXin Li @property 2058*9c5db199SXin Li def _property_proxy(self): 2059*9c5db199SXin Li """Returns proxy object to adapter properties if adapter is valid.""" 2060*9c5db199SXin Li if self._adapter: 2061*9c5db199SXin Li return self._adapter[self.DBUS_PROP_IFACE] 2062*9c5db199SXin Li 2063*9c5db199SXin Li return None 2064*9c5db199SXin Li 2065*9c5db199SXin Li @property 2066*9c5db199SXin Li def _advertising_proxy(self): 2067*9c5db199SXin Li """Returns proxy object to advertising interface if adapter is valid.""" 2068*9c5db199SXin Li if self._adapter: 2069*9c5db199SXin Li return self._adapter[self.BLUEZ_LE_ADVERTISING_MANAGER_IFACE] 2070*9c5db199SXin Li 2071*9c5db199SXin Li return None 2072*9c5db199SXin Li 2073*9c5db199SXin Li @dbus_safe(False) 2074*9c5db199SXin Li def _get_adv_monitor_manager(self): 2075*9c5db199SXin Li """Get the D-Bus proxy for the local advertisement monitor manager. 2076*9c5db199SXin Li 2077*9c5db199SXin Li @return the advertisement monitor manager interface object. 2078*9c5db199SXin Li 2079*9c5db199SXin Li """ 2080*9c5db199SXin Li return self._adapter[self.BLUEZ_ADV_MONITOR_MANAGER_IFACE] 2081*9c5db199SXin Li 2082*9c5db199SXin Li @dbus_safe(False) 2083*9c5db199SXin Li def reset_on(self): 2084*9c5db199SXin Li """Reset the adapter and settings and power up the adapter. 2085*9c5db199SXin Li 2086*9c5db199SXin Li @return True on success, False otherwise. 2087*9c5db199SXin Li 2088*9c5db199SXin Li """ 2089*9c5db199SXin Li return self._reset(set_power=True) 2090*9c5db199SXin Li 2091*9c5db199SXin Li @dbus_safe(False) 2092*9c5db199SXin Li def reset_off(self): 2093*9c5db199SXin Li """Reset the adapter and settings, leave the adapter powered off. 2094*9c5db199SXin Li 2095*9c5db199SXin Li @return True on success, False otherwise. 2096*9c5db199SXin Li 2097*9c5db199SXin Li """ 2098*9c5db199SXin Li return self._reset(set_power=False) 2099*9c5db199SXin Li 2100*9c5db199SXin Li def has_adapter(self): 2101*9c5db199SXin Li """Return if an adapter is present. 2102*9c5db199SXin Li 2103*9c5db199SXin Li This will only return True if we have determined both that there is 2104*9c5db199SXin Li a Bluetooth adapter on this device (kernel adapter index list is not 2105*9c5db199SXin Li empty) and that the Bluetooth daemon has exported an object for it. 2106*9c5db199SXin Li 2107*9c5db199SXin Li @return True if an adapter is present, False if not. 2108*9c5db199SXin Li 2109*9c5db199SXin Li """ 2110*9c5db199SXin Li return self._has_adapter and self._adapter is not None 2111*9c5db199SXin Li 2112*9c5db199SXin Li def _reset(self, set_power=False): 2113*9c5db199SXin Li """Remove remote devices and set adapter to set_power state. 2114*9c5db199SXin Li 2115*9c5db199SXin Li Do not restart bluetoothd as this may incur a side effect. 2116*9c5db199SXin Li The unhappy chrome may disable the adapter randomly. 2117*9c5db199SXin Li 2118*9c5db199SXin Li @param set_power: adapter power state to set (True or False). 2119*9c5db199SXin Li 2120*9c5db199SXin Li @return True on success, False otherwise. 2121*9c5db199SXin Li 2122*9c5db199SXin Li """ 2123*9c5db199SXin Li logging.debug('_reset') 2124*9c5db199SXin Li 2125*9c5db199SXin Li if not self._adapter: 2126*9c5db199SXin Li logging.warning('Adapter not found!') 2127*9c5db199SXin Li return False 2128*9c5db199SXin Li 2129*9c5db199SXin Li objects = self._objmgr_proxy.GetManagedObjects() 2130*9c5db199SXin Li 2131*9c5db199SXin Li devices = [] 2132*9c5db199SXin Li for path, ifaces in six.iteritems(objects): 2133*9c5db199SXin Li if self.BLUEZ_DEVICE_IFACE in ifaces: 2134*9c5db199SXin Li devices.append(objects[path][self.BLUEZ_DEVICE_IFACE]) 2135*9c5db199SXin Li 2136*9c5db199SXin Li # Turn on the adapter in order to remove all remote devices. 2137*9c5db199SXin Li if not self.is_powered_on(): 2138*9c5db199SXin Li if not self.set_powered(True): 2139*9c5db199SXin Li logging.warning('Unable to power on the adapter') 2140*9c5db199SXin Li return False 2141*9c5db199SXin Li 2142*9c5db199SXin Li for device in devices: 2143*9c5db199SXin Li logging.debug('removing %s', device.get('Address')) 2144*9c5db199SXin Li self.remove_device_object(device.get('Address')) 2145*9c5db199SXin Li 2146*9c5db199SXin Li # Toggle power to the adapter. 2147*9c5db199SXin Li if not self.set_powered(False): 2148*9c5db199SXin Li logging.warning('Unable to power off adapter') 2149*9c5db199SXin Li return False 2150*9c5db199SXin Li if set_power and not self.set_powered(True): 2151*9c5db199SXin Li logging.warning('Unable to power on adapter') 2152*9c5db199SXin Li return False 2153*9c5db199SXin Li 2154*9c5db199SXin Li return True 2155*9c5db199SXin Li 2156*9c5db199SXin Li @dbus_safe(False) 2157*9c5db199SXin Li def is_discoverable(self): 2158*9c5db199SXin Li """Returns whether the adapter is discoverable.""" 2159*9c5db199SXin Li return bool(self._get_adapter_properties().get('Discoverable') == 1) 2160*9c5db199SXin Li 2161*9c5db199SXin Li @dbus_safe(False) 2162*9c5db199SXin Li def set_powered(self, powered): 2163*9c5db199SXin Li """Set the adapter power state. 2164*9c5db199SXin Li 2165*9c5db199SXin Li @param powered: adapter power state to set (True or False). 2166*9c5db199SXin Li 2167*9c5db199SXin Li @return True on success, False otherwise. 2168*9c5db199SXin Li 2169*9c5db199SXin Li """ 2170*9c5db199SXin Li if not self._adapter: 2171*9c5db199SXin Li if not powered: 2172*9c5db199SXin Li # Return success if we are trying to power off an adapter that's 2173*9c5db199SXin Li # missing or gone away, since the expected result has happened. 2174*9c5db199SXin Li return True 2175*9c5db199SXin Li else: 2176*9c5db199SXin Li logging.warning('Adapter not found!') 2177*9c5db199SXin Li return False 2178*9c5db199SXin Li 2179*9c5db199SXin Li logging.debug('_set_powered %r', powered) 2180*9c5db199SXin Li self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE, 'Powered', 2181*9c5db199SXin Li GLib.Variant('b', powered)) 2182*9c5db199SXin Li 2183*9c5db199SXin Li return True 2184*9c5db199SXin Li 2185*9c5db199SXin Li @dbus_safe(False) 2186*9c5db199SXin Li def set_discoverable(self, discoverable): 2187*9c5db199SXin Li """Set the adapter discoverable state. 2188*9c5db199SXin Li 2189*9c5db199SXin Li @param discoverable: adapter discoverable state to set (True or False). 2190*9c5db199SXin Li 2191*9c5db199SXin Li @return True on success, False otherwise. 2192*9c5db199SXin Li 2193*9c5db199SXin Li """ 2194*9c5db199SXin Li if not discoverable and not self._adapter: 2195*9c5db199SXin Li # Return success if we are trying to make an adapter that's 2196*9c5db199SXin Li # missing or gone away, undiscoverable, since the expected result 2197*9c5db199SXin Li # has happened. 2198*9c5db199SXin Li return True 2199*9c5db199SXin Li self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE, 'Discoverable', 2200*9c5db199SXin Li GLib.Variant('b', discoverable)) 2201*9c5db199SXin Li return True 2202*9c5db199SXin Li 2203*9c5db199SXin Li @dbus_safe(False) 2204*9c5db199SXin Li def get_discoverable_timeout(self): 2205*9c5db199SXin Li """Get the adapter discoverable_timeout. 2206*9c5db199SXin Li 2207*9c5db199SXin Li @return True on success, False otherwise. 2208*9c5db199SXin Li 2209*9c5db199SXin Li """ 2210*9c5db199SXin Li return int( 2211*9c5db199SXin Li self._property_proxy.Get(self.BLUEZ_ADAPTER_IFACE, 2212*9c5db199SXin Li 'DiscoverableTimeout')) 2213*9c5db199SXin Li 2214*9c5db199SXin Li @dbus_safe(False) 2215*9c5db199SXin Li def set_discoverable_timeout(self, discoverable_timeout): 2216*9c5db199SXin Li """Set the adapter discoverable_timeout property. 2217*9c5db199SXin Li 2218*9c5db199SXin Li @param discoverable_timeout: adapter discoverable_timeout value 2219*9c5db199SXin Li in seconds to set (Integer). 2220*9c5db199SXin Li 2221*9c5db199SXin Li @return True on success, False otherwise. 2222*9c5db199SXin Li 2223*9c5db199SXin Li """ 2224*9c5db199SXin Li self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE, 2225*9c5db199SXin Li 'DiscoverableTimeout', 2226*9c5db199SXin Li GLib.Variant('u', discoverable_timeout)) 2227*9c5db199SXin Li return True 2228*9c5db199SXin Li 2229*9c5db199SXin Li @dbus_safe(False) 2230*9c5db199SXin Li def get_pairable_timeout(self): 2231*9c5db199SXin Li """Get the adapter pairable_timeout. 2232*9c5db199SXin Li 2233*9c5db199SXin Li @return True on success, False otherwise. 2234*9c5db199SXin Li 2235*9c5db199SXin Li """ 2236*9c5db199SXin Li return int( 2237*9c5db199SXin Li self._property_proxy.Get(self.BLUEZ_ADAPTER_IFACE, 2238*9c5db199SXin Li 'PairableTimeout')) 2239*9c5db199SXin Li 2240*9c5db199SXin Li @dbus_safe(False) 2241*9c5db199SXin Li def set_pairable_timeout(self, pairable_timeout): 2242*9c5db199SXin Li """Set the adapter pairable_timeout property. 2243*9c5db199SXin Li 2244*9c5db199SXin Li @param pairable_timeout: adapter pairable_timeout value 2245*9c5db199SXin Li in seconds to set (Integer). 2246*9c5db199SXin Li 2247*9c5db199SXin Li @return True on success, False otherwise. 2248*9c5db199SXin Li 2249*9c5db199SXin Li """ 2250*9c5db199SXin Li self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE, 'PairableTimeout', 2251*9c5db199SXin Li GLib.Variant('u', pairable_timeout)) 2252*9c5db199SXin Li return True 2253*9c5db199SXin Li 2254*9c5db199SXin Li @dbus_safe(False) 2255*9c5db199SXin Li def get_pairable(self): 2256*9c5db199SXin Li """Gets the adapter pairable state. 2257*9c5db199SXin Li 2258*9c5db199SXin Li @return Pairable property value. 2259*9c5db199SXin Li """ 2260*9c5db199SXin Li return bool( 2261*9c5db199SXin Li self._property_proxy.Get(self.BLUEZ_ADAPTER_IFACE, 'Pairable')) 2262*9c5db199SXin Li 2263*9c5db199SXin Li @dbus_safe(False) 2264*9c5db199SXin Li def set_pairable(self, pairable): 2265*9c5db199SXin Li """Set the adapter pairable state. 2266*9c5db199SXin Li 2267*9c5db199SXin Li @param pairable: adapter pairable state to set (True or False). 2268*9c5db199SXin Li 2269*9c5db199SXin Li @return True on success, False otherwise. 2270*9c5db199SXin Li 2271*9c5db199SXin Li """ 2272*9c5db199SXin Li self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE, 'Pairable', 2273*9c5db199SXin Li GLib.Variant('b', pairable)) 2274*9c5db199SXin Li return True 2275*9c5db199SXin Li 2276*9c5db199SXin Li @dbus_safe(False) 2277*9c5db199SXin Li def set_adapter_alias(self, alias): 2278*9c5db199SXin Li """Set the adapter alias. 2279*9c5db199SXin Li 2280*9c5db199SXin Li @param alias: adapter alias to set with type String 2281*9c5db199SXin Li 2282*9c5db199SXin Li @return True on success, False otherwise. 2283*9c5db199SXin Li """ 2284*9c5db199SXin Li self._property_proxy.Set(self.BLUEZ_ADAPTER_IFACE, 'Alias', 2285*9c5db199SXin Li GLib.Variant('s', alias)) 2286*9c5db199SXin Li return True 2287*9c5db199SXin Li 2288*9c5db199SXin Li def _get_adapter_properties(self): 2289*9c5db199SXin Li """Read the adapter properties from the Bluetooth Daemon. 2290*9c5db199SXin Li 2291*9c5db199SXin Li @return the properties as a JSON-encoded dictionary on success, 2292*9c5db199SXin Li the value False otherwise. 2293*9c5db199SXin Li 2294*9c5db199SXin Li """ 2295*9c5db199SXin Li 2296*9c5db199SXin Li @dbus_safe({}) 2297*9c5db199SXin Li def get_props(): 2298*9c5db199SXin Li """Get props from dbus.""" 2299*9c5db199SXin Li objects = self._objmgr_proxy.GetManagedObjects() 2300*9c5db199SXin Li return objects[self._adapter_path][self.BLUEZ_ADAPTER_IFACE] 2301*9c5db199SXin Li 2302*9c5db199SXin Li if self._bluez and self._adapter: 2303*9c5db199SXin Li props = get_props().copy() 2304*9c5db199SXin Li else: 2305*9c5db199SXin Li props = {} 2306*9c5db199SXin Li logging.debug('get_adapter_properties') 2307*9c5db199SXin Li for i in props.items(): 2308*9c5db199SXin Li logging.debug(i) 2309*9c5db199SXin Li return props 2310*9c5db199SXin Li 2311*9c5db199SXin Li def get_adapter_properties(self): 2312*9c5db199SXin Li return json.dumps(self._get_adapter_properties()) 2313*9c5db199SXin Li 2314*9c5db199SXin Li def is_powered_on(self): 2315*9c5db199SXin Li """Checks whether the adapter is currently powered.""" 2316*9c5db199SXin Li return bool(self._get_adapter_properties().get('Powered')) 2317*9c5db199SXin Li 2318*9c5db199SXin Li def get_address(self): 2319*9c5db199SXin Li """Gets the current bluez adapter address.""" 2320*9c5db199SXin Li return str(self._get_adapter_properties()['Address']) 2321*9c5db199SXin Li 2322*9c5db199SXin Li def get_bluez_version(self): 2323*9c5db199SXin Li """Get the BlueZ version. 2324*9c5db199SXin Li 2325*9c5db199SXin Li Returns: 2326*9c5db199SXin Li Bluez version like 'BlueZ 5.39'. 2327*9c5db199SXin Li """ 2328*9c5db199SXin Li return str(self._get_adapter_properties()['Name']) 2329*9c5db199SXin Li 2330*9c5db199SXin Li def get_bluetooth_class(self): 2331*9c5db199SXin Li """Get the bluetooth class of the adapter. 2332*9c5db199SXin Li 2333*9c5db199SXin Li Example for Chromebook: 4718852 2334*9c5db199SXin Li 2335*9c5db199SXin Li Returns: 2336*9c5db199SXin Li Class of device for the adapter. 2337*9c5db199SXin Li """ 2338*9c5db199SXin Li return str(self._get_adapter_properties()['Class']) 2339*9c5db199SXin Li 2340*9c5db199SXin Li def read_version(self): 2341*9c5db199SXin Li """Read the version of the management interface from the Kernel. 2342*9c5db199SXin Li 2343*9c5db199SXin Li @return the information as a JSON-encoded tuple of: 2344*9c5db199SXin Li ( version, revision ) 2345*9c5db199SXin Li 2346*9c5db199SXin Li """ 2347*9c5db199SXin Li #TODO(howardchung): resolve 'cannot allocate memory' error when 2348*9c5db199SXin Li # BluetoothControlSocket idle too long(about 3 secs) 2349*9c5db199SXin Li # (b:137603211) 2350*9c5db199SXin Li _control = bluetooth_socket.BluetoothControlSocket() 2351*9c5db199SXin Li return json.dumps(_control.read_version()) 2352*9c5db199SXin Li 2353*9c5db199SXin Li def read_supported_commands(self): 2354*9c5db199SXin Li """Read the set of supported commands from the Kernel. 2355*9c5db199SXin Li 2356*9c5db199SXin Li @return the information as a JSON-encoded tuple of: 2357*9c5db199SXin Li ( commands, events ) 2358*9c5db199SXin Li 2359*9c5db199SXin Li """ 2360*9c5db199SXin Li #TODO(howardchung): resolve 'cannot allocate memory' error when 2361*9c5db199SXin Li # BluetoothControlSocket idle too long(about 3 secs) 2362*9c5db199SXin Li # (b:137603211) 2363*9c5db199SXin Li _control = bluetooth_socket.BluetoothControlSocket() 2364*9c5db199SXin Li return json.dumps(_control.read_supported_commands()) 2365*9c5db199SXin Li 2366*9c5db199SXin Li def read_index_list(self): 2367*9c5db199SXin Li """Read the list of currently known controllers from the Kernel. 2368*9c5db199SXin Li 2369*9c5db199SXin Li @return the information as a JSON-encoded array of controller indexes. 2370*9c5db199SXin Li 2371*9c5db199SXin Li """ 2372*9c5db199SXin Li #TODO(howardchung): resolve 'cannot allocate memory' error when 2373*9c5db199SXin Li # BluetoothControlSocket idle too long(about 3 secs) 2374*9c5db199SXin Li # (b:137603211) 2375*9c5db199SXin Li _control = bluetooth_socket.BluetoothControlSocket() 2376*9c5db199SXin Li return json.dumps(_control.read_index_list()) 2377*9c5db199SXin Li 2378*9c5db199SXin Li def read_info(self): 2379*9c5db199SXin Li """Read the adapter information from the Kernel. 2380*9c5db199SXin Li 2381*9c5db199SXin Li @return the information as a JSON-encoded tuple of: 2382*9c5db199SXin Li ( address, bluetooth_version, manufacturer_id, 2383*9c5db199SXin Li supported_settings, current_settings, class_of_device, 2384*9c5db199SXin Li name, short_name ) 2385*9c5db199SXin Li 2386*9c5db199SXin Li """ 2387*9c5db199SXin Li #TODO(howardchung): resolve 'cannot allocate memory' error when 2388*9c5db199SXin Li # BluetoothControlSocket idle too long(about 3 secs) 2389*9c5db199SXin Li # (b:137603211) 2390*9c5db199SXin Li _control = bluetooth_socket.BluetoothControlSocket() 2391*9c5db199SXin Li return json.dumps(_control.read_info(0)) 2392*9c5db199SXin Li 2393*9c5db199SXin Li def add_device(self, address, address_type, action): 2394*9c5db199SXin Li """Add a device to the Kernel action list. 2395*9c5db199SXin Li 2396*9c5db199SXin Li @param address: Address of the device to add. 2397*9c5db199SXin Li @param address_type: Type of device in @address. 2398*9c5db199SXin Li @param action: Action to take. 2399*9c5db199SXin Li 2400*9c5db199SXin Li @return on success, a JSON-encoded typle of: 2401*9c5db199SXin Li ( address, address_type ), None on failure. 2402*9c5db199SXin Li 2403*9c5db199SXin Li """ 2404*9c5db199SXin Li #TODO(howardchung): resolve 'cannot allocate memory' error when 2405*9c5db199SXin Li # BluetoothControlSocket idle too long(about 3 secs) 2406*9c5db199SXin Li # (b:137603211) 2407*9c5db199SXin Li _control = bluetooth_socket.BluetoothControlSocket() 2408*9c5db199SXin Li return json.dumps(_control.add_device(0, address, address_type, 2409*9c5db199SXin Li action)) 2410*9c5db199SXin Li 2411*9c5db199SXin Li def remove_device(self, address, address_type): 2412*9c5db199SXin Li """Remove a device from the Kernel action list. 2413*9c5db199SXin Li 2414*9c5db199SXin Li @param address: Address of the device to remove. 2415*9c5db199SXin Li @param address_type: Type of device in @address. 2416*9c5db199SXin Li 2417*9c5db199SXin Li @return on success, a JSON-encoded typle of: 2418*9c5db199SXin Li ( address, address_type ), None on failure. 2419*9c5db199SXin Li 2420*9c5db199SXin Li """ 2421*9c5db199SXin Li #TODO(howardchung): resolve 'cannot allocate memory' error when 2422*9c5db199SXin Li # BluetoothControlSocket idle too long(about 3 secs) 2423*9c5db199SXin Li # (b:137603211) 2424*9c5db199SXin Li _control = bluetooth_socket.BluetoothControlSocket() 2425*9c5db199SXin Li return json.dumps(_control.remove_device(0, address, address_type)) 2426*9c5db199SXin Li 2427*9c5db199SXin Li @dbus_safe(False) 2428*9c5db199SXin Li def _get_devices(self): 2429*9c5db199SXin Li """Read information about remote devices known to the adapter. 2430*9c5db199SXin Li 2431*9c5db199SXin Li @return the properties of each device in a list 2432*9c5db199SXin Li 2433*9c5db199SXin Li """ 2434*9c5db199SXin Li objects = self._objmgr_proxy.GetManagedObjects() 2435*9c5db199SXin Li devices = [] 2436*9c5db199SXin Li for path, ifaces in six.iteritems(objects): 2437*9c5db199SXin Li if self.BLUEZ_DEVICE_IFACE in ifaces: 2438*9c5db199SXin Li devices.append(objects[path][self.BLUEZ_DEVICE_IFACE]) 2439*9c5db199SXin Li return devices 2440*9c5db199SXin Li 2441*9c5db199SXin Li def _encode_json(self, data): 2442*9c5db199SXin Li """Encodes input data as JSON object. 2443*9c5db199SXin Li 2444*9c5db199SXin Li Note that for bytes elements in the input data, they are decoded as 2445*9c5db199SXin Li unicode string. 2446*9c5db199SXin Li 2447*9c5db199SXin Li @param data: data to be JSON encoded 2448*9c5db199SXin Li 2449*9c5db199SXin Li @return: JSON encoded data 2450*9c5db199SXin Li """ 2451*9c5db199SXin Li logging.debug('_encode_json raw data is %s', data) 2452*9c5db199SXin Li str_data = utils.bytes_to_str_recursive(data) 2453*9c5db199SXin Li json_encoded = json.dumps(str_data) 2454*9c5db199SXin Li logging.debug('JSON encoded data is %s', json_encoded) 2455*9c5db199SXin Li return json_encoded 2456*9c5db199SXin Li 2457*9c5db199SXin Li def get_devices(self): 2458*9c5db199SXin Li """Read information about remote devices known to the adapter. 2459*9c5db199SXin Li 2460*9c5db199SXin Li @return the properties of each device as a JSON-encoded array of 2461*9c5db199SXin Li dictionaries on success, the value False otherwise. 2462*9c5db199SXin Li 2463*9c5db199SXin Li """ 2464*9c5db199SXin Li devices = self._get_devices() 2465*9c5db199SXin Li # Note that bluetooth facade now runs in Python 3. 2466*9c5db199SXin Li # Refer to crrev.com/c/3268347. 2467*9c5db199SXin Li return self._encode_json(devices) 2468*9c5db199SXin Li 2469*9c5db199SXin Li def get_num_connected_devices(self): 2470*9c5db199SXin Li """ Return number of remote devices currently connected to the DUT. 2471*9c5db199SXin Li 2472*9c5db199SXin Li @returns: The number of devices known to bluez with the Connected 2473*9c5db199SXin Li property active 2474*9c5db199SXin Li """ 2475*9c5db199SXin Li num_connected_devices = 0 2476*9c5db199SXin Li for dev in self._get_devices(): 2477*9c5db199SXin Li if dev and dev.get('Connected', False): 2478*9c5db199SXin Li num_connected_devices += 1 2479*9c5db199SXin Li 2480*9c5db199SXin Li return num_connected_devices 2481*9c5db199SXin Li 2482*9c5db199SXin Li @dbus_safe(None) 2483*9c5db199SXin Li def get_device_property(self, address, prop_name): 2484*9c5db199SXin Li """Read a property of BT device by directly querying device dbus object 2485*9c5db199SXin Li 2486*9c5db199SXin Li @param address: Address of the device to query 2487*9c5db199SXin Li @param prop_name: Property to be queried 2488*9c5db199SXin Li 2489*9c5db199SXin Li @return Base 64 JSON repr of property if device is found and has 2490*9c5db199SXin Li property, otherwise None on failure. JSON is a recursive 2491*9c5db199SXin Li converter, automatically converting dbus types to python natives 2492*9c5db199SXin Li and base64 allows us to pass special characters over xmlrpc. 2493*9c5db199SXin Li Decode is done in bluetooth_device.py 2494*9c5db199SXin Li """ 2495*9c5db199SXin Li 2496*9c5db199SXin Li prop_val = None 2497*9c5db199SXin Li 2498*9c5db199SXin Li # Grab dbus object, _find_device will catch any thrown dbus error 2499*9c5db199SXin Li device_obj = self._find_device(address) 2500*9c5db199SXin Li 2501*9c5db199SXin Li if device_obj: 2502*9c5db199SXin Li # Query dbus object for property 2503*9c5db199SXin Li prop_val = unpack_if_variant(device_obj[self.DBUS_PROP_IFACE].Get( 2504*9c5db199SXin Li self.BLUEZ_DEVICE_IFACE, prop_name)) 2505*9c5db199SXin Li 2506*9c5db199SXin Li return self._encode_json(prop_val) 2507*9c5db199SXin Li 2508*9c5db199SXin Li @dbus_safe(None) 2509*9c5db199SXin Li def get_battery_property(self, address, prop_name): 2510*9c5db199SXin Li """Read a property from Battery1 interface. 2511*9c5db199SXin Li 2512*9c5db199SXin Li @param address: Address of the device to query 2513*9c5db199SXin Li @param prop_name: Property to be queried 2514*9c5db199SXin Li 2515*9c5db199SXin Li @return The battery percentage value, or None if does not exist. 2516*9c5db199SXin Li """ 2517*9c5db199SXin Li 2518*9c5db199SXin Li prop_val = None 2519*9c5db199SXin Li 2520*9c5db199SXin Li # Grab dbus object, _find_battery will catch any thrown dbus error 2521*9c5db199SXin Li battery_obj = self._find_battery(address) 2522*9c5db199SXin Li 2523*9c5db199SXin Li if battery_obj: 2524*9c5db199SXin Li # Query dbus object for property 2525*9c5db199SXin Li prop_val = unpack_if_variant(battery_obj[self.DBUS_PROP_IFACE].Get( 2526*9c5db199SXin Li self.BLUEZ_BATTERY_IFACE, prop_name)) 2527*9c5db199SXin Li 2528*9c5db199SXin Li return prop_val 2529*9c5db199SXin Li 2530*9c5db199SXin Li @dbus_safe(False) 2531*9c5db199SXin Li def set_discovery_filter(self, filter): 2532*9c5db199SXin Li """Set the discovery filter. 2533*9c5db199SXin Li 2534*9c5db199SXin Li @param filter: The discovery filter to set. 2535*9c5db199SXin Li 2536*9c5db199SXin Li @return True on success, False otherwise. 2537*9c5db199SXin Li 2538*9c5db199SXin Li """ 2539*9c5db199SXin Li if not self._adapter: 2540*9c5db199SXin Li return False 2541*9c5db199SXin Li 2542*9c5db199SXin Li converted_filter = {} 2543*9c5db199SXin Li for key in filter: 2544*9c5db199SXin Li converted_filter[key] = GLib.Variant('s', filter[key]) 2545*9c5db199SXin Li 2546*9c5db199SXin Li self._adapter_proxy.SetDiscoveryFilter(converted_filter) 2547*9c5db199SXin Li return True 2548*9c5db199SXin Li 2549*9c5db199SXin Li @dbus_safe(False, return_error=True) 2550*9c5db199SXin Li def start_discovery(self): 2551*9c5db199SXin Li """Start discovery of remote devices. 2552*9c5db199SXin Li 2553*9c5db199SXin Li Obtain the discovered device information using get_devices(), called 2554*9c5db199SXin Li stop_discovery() when done. 2555*9c5db199SXin Li 2556*9c5db199SXin Li @return True on success, False otherwise. 2557*9c5db199SXin Li 2558*9c5db199SXin Li """ 2559*9c5db199SXin Li if not self._adapter: 2560*9c5db199SXin Li return (False, "Adapter Not Found") 2561*9c5db199SXin Li self._adapter_proxy.StartDiscovery() 2562*9c5db199SXin Li return (True, None) 2563*9c5db199SXin Li 2564*9c5db199SXin Li @dbus_safe(False, return_error=True) 2565*9c5db199SXin Li def stop_discovery(self): 2566*9c5db199SXin Li """Stop discovery of remote devices. 2567*9c5db199SXin Li 2568*9c5db199SXin Li @return True on success, False otherwise. 2569*9c5db199SXin Li 2570*9c5db199SXin Li """ 2571*9c5db199SXin Li if not self._adapter: 2572*9c5db199SXin Li return (False, "Adapter Not Found") 2573*9c5db199SXin Li self._adapter_proxy.StopDiscovery() 2574*9c5db199SXin Li return (True, None) 2575*9c5db199SXin Li 2576*9c5db199SXin Li def is_discovering(self): 2577*9c5db199SXin Li """Check if adapter is discovering.""" 2578*9c5db199SXin Li return self._get_adapter_properties().get('Discovering', 0) == 1 2579*9c5db199SXin Li 2580*9c5db199SXin Li def get_dev_info(self): 2581*9c5db199SXin Li """Read raw HCI device information. 2582*9c5db199SXin Li 2583*9c5db199SXin Li @return JSON-encoded tuple of: 2584*9c5db199SXin Li (index, name, address, flags, device_type, bus_type, 2585*9c5db199SXin Li features, pkt_type, link_policy, link_mode, 2586*9c5db199SXin Li acl_mtu, acl_pkts, sco_mtu, sco_pkts, 2587*9c5db199SXin Li err_rx, err_tx, cmd_tx, evt_rx, acl_tx, acl_rx, 2588*9c5db199SXin Li sco_tx, sco_rx, byte_rx, byte_tx) on success, 2589*9c5db199SXin Li None on failure. 2590*9c5db199SXin Li 2591*9c5db199SXin Li """ 2592*9c5db199SXin Li return json.dumps(self._raw.get_dev_info(0)) 2593*9c5db199SXin Li 2594*9c5db199SXin Li @dbus_safe(None, return_error=True) 2595*9c5db199SXin Li def get_supported_capabilities(self): 2596*9c5db199SXin Li """ Get supported capabilities of the adapter 2597*9c5db199SXin Li 2598*9c5db199SXin Li @returns (capabilities, None) on Success. (None, <error>) on failure 2599*9c5db199SXin Li """ 2600*9c5db199SXin Li value = self._adapter_proxy.GetSupportedCapabilities() 2601*9c5db199SXin Li return (json.dumps(value), None) 2602*9c5db199SXin Li 2603*9c5db199SXin Li @dbus_safe(False) 2604*9c5db199SXin Li def register_profile(self, path, uuid, options): 2605*9c5db199SXin Li """Register new profile (service). 2606*9c5db199SXin Li 2607*9c5db199SXin Li @param path: Path to the profile object. 2608*9c5db199SXin Li @param uuid: Service Class ID of the service as string. 2609*9c5db199SXin Li @param options: Dictionary of options for the new service, compliant 2610*9c5db199SXin Li with BlueZ D-Bus Profile API standard. 2611*9c5db199SXin Li 2612*9c5db199SXin Li @return True on success, False otherwise. 2613*9c5db199SXin Li 2614*9c5db199SXin Li """ 2615*9c5db199SXin Li converted_options = {} 2616*9c5db199SXin Li if 'ServiceRecord' in options: 2617*9c5db199SXin Li converted_options['ServiceRecord'] = GLib.Variant( 2618*9c5db199SXin Li 's', options['ServiceRecord']) 2619*9c5db199SXin Li 2620*9c5db199SXin Li profile_manager = self.bus.get( 2621*9c5db199SXin Li self.BLUEZ_SERVICE_NAME, self.BLUEZ_PROFILE_MANAGER_PATH)[ 2622*9c5db199SXin Li self.BLUEZ_PROFILE_MANAGER_IFACE] 2623*9c5db199SXin Li profile_manager.RegisterProfile(path, uuid, converted_options) 2624*9c5db199SXin Li return True 2625*9c5db199SXin Li 2626*9c5db199SXin Li def has_device(self, address): 2627*9c5db199SXin Li """Checks if the device with a given address exists. 2628*9c5db199SXin Li 2629*9c5db199SXin Li @param address: Address of the device. 2630*9c5db199SXin Li 2631*9c5db199SXin Li @returns: True if there is an interface object with that address. 2632*9c5db199SXin Li False if the device is not found. 2633*9c5db199SXin Li 2634*9c5db199SXin Li @raises: Exception if a D-Bus error is encountered. 2635*9c5db199SXin Li 2636*9c5db199SXin Li """ 2637*9c5db199SXin Li result = self._find_device(address) 2638*9c5db199SXin Li logging.debug('has_device result: %s', str(result)) 2639*9c5db199SXin Li 2640*9c5db199SXin Li # The result being False indicates that there is a D-Bus error. 2641*9c5db199SXin Li if result is False: 2642*9c5db199SXin Li raise Exception('dbus.Interface error') 2643*9c5db199SXin Li 2644*9c5db199SXin Li # Return True if the result is not None, e.g. a D-Bus interface object; 2645*9c5db199SXin Li # False otherwise. 2646*9c5db199SXin Li return bool(result) 2647*9c5db199SXin Li 2648*9c5db199SXin Li @dbus_safe(False) 2649*9c5db199SXin Li def _find_device(self, address): 2650*9c5db199SXin Li """Finds the device with a given address. 2651*9c5db199SXin Li 2652*9c5db199SXin Li Find the device with a given address and returns the 2653*9c5db199SXin Li device interface. 2654*9c5db199SXin Li 2655*9c5db199SXin Li @param address: Address of the device. 2656*9c5db199SXin Li 2657*9c5db199SXin Li @returns: An 'org.bluez.Device1' interface to the device. 2658*9c5db199SXin Li None if device can not be found. 2659*9c5db199SXin Li """ 2660*9c5db199SXin Li path = self._get_device_path(address) 2661*9c5db199SXin Li if path: 2662*9c5db199SXin Li return self.bus.get(self.BLUEZ_SERVICE_NAME, path) 2663*9c5db199SXin Li logging.info('Device not found') 2664*9c5db199SXin Li return None 2665*9c5db199SXin Li 2666*9c5db199SXin Li @dbus_safe(None) 2667*9c5db199SXin Li def _find_battery(self, address): 2668*9c5db199SXin Li """Finds the battery with a given address. 2669*9c5db199SXin Li 2670*9c5db199SXin Li Find the battery with a given address and returns the 2671*9c5db199SXin Li battery interface. 2672*9c5db199SXin Li 2673*9c5db199SXin Li @param address: Address of the device. 2674*9c5db199SXin Li 2675*9c5db199SXin Li @returns: An 'org.bluez.Battery1' interface to the device. 2676*9c5db199SXin Li None if device can not be found. 2677*9c5db199SXin Li """ 2678*9c5db199SXin Li path = self._get_device_path(address) 2679*9c5db199SXin Li if path: 2680*9c5db199SXin Li try: 2681*9c5db199SXin Li obj = self.bus.get(self.BLUEZ_SERVICE_NAME, path) 2682*9c5db199SXin Li if obj[self.BLUEZ_BATTERY_IFACE] is not None: 2683*9c5db199SXin Li return obj 2684*9c5db199SXin Li except: 2685*9c5db199SXin Li pass 2686*9c5db199SXin Li logging.info('Battery not found') 2687*9c5db199SXin Li return None 2688*9c5db199SXin Li 2689*9c5db199SXin Li @dbus_safe(False) 2690*9c5db199SXin Li def _get_device_path(self, address): 2691*9c5db199SXin Li """Gets the path for a device with a given address. 2692*9c5db199SXin Li 2693*9c5db199SXin Li Find the device with a given address and returns the 2694*9c5db199SXin Li the path for the device. 2695*9c5db199SXin Li 2696*9c5db199SXin Li @param address: Address of the device. 2697*9c5db199SXin Li 2698*9c5db199SXin Li @returns: The path to the address of the device, or None if device is 2699*9c5db199SXin Li not found in the object tree. 2700*9c5db199SXin Li 2701*9c5db199SXin Li """ 2702*9c5db199SXin Li 2703*9c5db199SXin Li # Create device path, i.e. '/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF' based 2704*9c5db199SXin Li # on path assignment scheme used in bluez 2705*9c5db199SXin Li address_up = address.replace(':', '_') 2706*9c5db199SXin Li device_path = '{}/dev_{}'.format(self._adapter_path, address_up) 2707*9c5db199SXin Li 2708*9c5db199SXin Li # Verify the Address property agrees to confirm we have the device 2709*9c5db199SXin Li try: 2710*9c5db199SXin Li device = self.bus.get(self.BLUEZ_SERVICE_NAME, device_path) 2711*9c5db199SXin Li found_addr = device[self.DBUS_PROP_IFACE].Get( 2712*9c5db199SXin Li self.BLUEZ_DEVICE_IFACE, 'Address') 2713*9c5db199SXin Li 2714*9c5db199SXin Li if found_addr == address: 2715*9c5db199SXin Li logging.info('Device found at {}'.format(device_path)) 2716*9c5db199SXin Li return device_path 2717*9c5db199SXin Li 2718*9c5db199SXin Li except KeyError as ke: 2719*9c5db199SXin Li logging.debug('Couldn\'t reach device: %s: %s', address, ke) 2720*9c5db199SXin Li except GLib.Error as e: 2721*9c5db199SXin Li log_msg = 'Couldn\'t reach device: {}'.format(str(e)) 2722*9c5db199SXin Li logging.debug(log_msg) 2723*9c5db199SXin Li 2724*9c5db199SXin Li logging.debug('No device found at {}'.format(device_path)) 2725*9c5db199SXin Li return None 2726*9c5db199SXin Li 2727*9c5db199SXin Li @dbus_safe(False) 2728*9c5db199SXin Li def _setup_pairing_agent(self, pin): 2729*9c5db199SXin Li """Initializes and resiters a BluezPairingAgent to handle authentication. 2730*9c5db199SXin Li 2731*9c5db199SXin Li @param pin: The pin code this agent will answer. 2732*9c5db199SXin Li 2733*9c5db199SXin Li """ 2734*9c5db199SXin Li if self._pairing_agent: 2735*9c5db199SXin Li logging.info( 2736*9c5db199SXin Li 'Removing the old agent before initializing a new one') 2737*9c5db199SXin Li self._pairing_agent.unregister() 2738*9c5db199SXin Li self._pairing_agent = None 2739*9c5db199SXin Li 2740*9c5db199SXin Li # Create and register pairing agent 2741*9c5db199SXin Li self._pairing_agent = BluezPairingAgent(self.bus, self.AGENT_PATH, pin) 2742*9c5db199SXin Li 2743*9c5db199SXin Li agent_manager = self.bus.get( 2744*9c5db199SXin Li self.BLUEZ_SERVICE_NAME, 2745*9c5db199SXin Li self.BLUEZ_AGENT_MANAGER_PATH)[self.BLUEZ_AGENT_MANAGER_IFACE] 2746*9c5db199SXin Li try: 2747*9c5db199SXin Li # Make sure agent is accessible on bus 2748*9c5db199SXin Li #agent_obj = self.bus.get(self.BLUEZ_SERVICE_NAME, self.AGENT_PATH) 2749*9c5db199SXin Li agent_manager.RegisterAgent(self.AGENT_PATH, str(self._capability)) 2750*9c5db199SXin Li except GLib.Error as e: 2751*9c5db199SXin Li if self.BLUEZ_ERROR_ALREADY_EXISTS in str(e): 2752*9c5db199SXin Li logging.info('Unregistering old agent and registering the new') 2753*9c5db199SXin Li agent_manager.UnregisterAgent(self.AGENT_PATH) 2754*9c5db199SXin Li agent_manager.RegisterAgent(self.AGENT_PATH, 2755*9c5db199SXin Li str(self._capability)) 2756*9c5db199SXin Li else: 2757*9c5db199SXin Li logging.error('Error setting up pin agent: %s', e) 2758*9c5db199SXin Li raise 2759*9c5db199SXin Li except Exception as e: 2760*9c5db199SXin Li logging.debug('Setup pairing agent: %s', str(e)) 2761*9c5db199SXin Li raise 2762*9c5db199SXin Li logging.info('Agent registered: %s', self.AGENT_PATH) 2763*9c5db199SXin Li 2764*9c5db199SXin Li @dbus_safe(False) 2765*9c5db199SXin Li def _is_paired(self, device): 2766*9c5db199SXin Li """Checks if a device is paired. 2767*9c5db199SXin Li 2768*9c5db199SXin Li @param device: An 'org.bluez.Device1' interface to the device. 2769*9c5db199SXin Li 2770*9c5db199SXin Li @returns: True if device is paired. False otherwise. 2771*9c5db199SXin Li 2772*9c5db199SXin Li """ 2773*9c5db199SXin Li props = device[self.DBUS_PROP_IFACE] 2774*9c5db199SXin Li paired = props.Get(self.BLUEZ_DEVICE_IFACE, 'Paired') 2775*9c5db199SXin Li return bool(paired) 2776*9c5db199SXin Li 2777*9c5db199SXin Li @dbus_safe(False) 2778*9c5db199SXin Li def device_is_paired(self, address): 2779*9c5db199SXin Li """Checks if a device is paired. 2780*9c5db199SXin Li 2781*9c5db199SXin Li @param address: address of the device. 2782*9c5db199SXin Li 2783*9c5db199SXin Li @returns: True if device is paired. False otherwise. 2784*9c5db199SXin Li 2785*9c5db199SXin Li """ 2786*9c5db199SXin Li device = self._find_device(address) 2787*9c5db199SXin Li if not device: 2788*9c5db199SXin Li logging.error('Device not found') 2789*9c5db199SXin Li return False 2790*9c5db199SXin Li return self._is_paired(device) 2791*9c5db199SXin Li 2792*9c5db199SXin Li @dbus_safe(False) 2793*9c5db199SXin Li def _is_connected(self, device): 2794*9c5db199SXin Li """Checks if a device is connected. 2795*9c5db199SXin Li 2796*9c5db199SXin Li @param device: An 'org.bluez.Device1' interface to the device. 2797*9c5db199SXin Li 2798*9c5db199SXin Li @returns: True if device is connected. False otherwise. 2799*9c5db199SXin Li 2800*9c5db199SXin Li """ 2801*9c5db199SXin Li props = device[self.DBUS_PROP_IFACE] 2802*9c5db199SXin Li connected = props.Get(self.BLUEZ_DEVICE_IFACE, 'Connected') 2803*9c5db199SXin Li logging.info('Got connected = %r', connected) 2804*9c5db199SXin Li return bool(connected) 2805*9c5db199SXin Li 2806*9c5db199SXin Li @dbus_safe(False) 2807*9c5db199SXin Li def _set_trusted_by_device(self, device, trusted=True): 2808*9c5db199SXin Li """Set the device trusted by device object. 2809*9c5db199SXin Li 2810*9c5db199SXin Li @param device: the device object to set trusted. 2811*9c5db199SXin Li @param trusted: True or False indicating whether to set trusted or not. 2812*9c5db199SXin Li 2813*9c5db199SXin Li @returns: True if successful. False otherwise. 2814*9c5db199SXin Li 2815*9c5db199SXin Li """ 2816*9c5db199SXin Li try: 2817*9c5db199SXin Li properties = device[self.DBUS_PROP_IFACE] 2818*9c5db199SXin Li properties.Set(self.BLUEZ_DEVICE_IFACE, 'Trusted', 2819*9c5db199SXin Li GLib.Variant('b', trusted)) 2820*9c5db199SXin Li return True 2821*9c5db199SXin Li except Exception as e: 2822*9c5db199SXin Li logging.error('_set_trusted_by_device: %s', e) 2823*9c5db199SXin Li except: 2824*9c5db199SXin Li logging.error('_set_trusted_by_device: unexpected error') 2825*9c5db199SXin Li return False 2826*9c5db199SXin Li 2827*9c5db199SXin Li @dbus_safe(False) 2828*9c5db199SXin Li def _set_trusted_by_path(self, device_path, trusted=True): 2829*9c5db199SXin Li """Set the device trusted by the device path. 2830*9c5db199SXin Li 2831*9c5db199SXin Li @param device_path: the object path of the device. 2832*9c5db199SXin Li @param trusted: True or False indicating whether to set trusted or not. 2833*9c5db199SXin Li 2834*9c5db199SXin Li @returns: True if successful. False otherwise. 2835*9c5db199SXin Li 2836*9c5db199SXin Li """ 2837*9c5db199SXin Li try: 2838*9c5db199SXin Li device = self.bus.get(self.BLUEZ_SERVICE_NAME, device_path) 2839*9c5db199SXin Li return self._set_trusted_by_device(device, trusted) 2840*9c5db199SXin Li except Exception as e: 2841*9c5db199SXin Li logging.error('_set_trusted_by_path: %s', e) 2842*9c5db199SXin Li except: 2843*9c5db199SXin Li logging.error('_set_trusted_by_path: unexpected error') 2844*9c5db199SXin Li return False 2845*9c5db199SXin Li 2846*9c5db199SXin Li @dbus_safe(False) 2847*9c5db199SXin Li def set_trusted(self, address, trusted=True): 2848*9c5db199SXin Li """Set the device trusted by address. 2849*9c5db199SXin Li 2850*9c5db199SXin Li @param address: The bluetooth address of the device. 2851*9c5db199SXin Li @param trusted: True or False indicating whether to set trusted or not. 2852*9c5db199SXin Li 2853*9c5db199SXin Li @returns: True if successful. False otherwise. 2854*9c5db199SXin Li 2855*9c5db199SXin Li """ 2856*9c5db199SXin Li try: 2857*9c5db199SXin Li device = self._find_device(address) 2858*9c5db199SXin Li return self._set_trusted_by_device(device, trusted) 2859*9c5db199SXin Li except Exception as e: 2860*9c5db199SXin Li logging.error('set_trusted: %s', e) 2861*9c5db199SXin Li except: 2862*9c5db199SXin Li logging.error('set_trusted: unexpected error') 2863*9c5db199SXin Li return False 2864*9c5db199SXin Li 2865*9c5db199SXin Li @dbus_safe(False) 2866*9c5db199SXin Li def pair_legacy_device(self, address, pin, trusted, timeout=60): 2867*9c5db199SXin Li """Pairs a device with a given pin code. 2868*9c5db199SXin Li 2869*9c5db199SXin Li Registers a agent who handles pin code request and 2870*9c5db199SXin Li pairs a device with known pin code. After pairing, this function will 2871*9c5db199SXin Li automatically connect to the device as well (prevents timing issues 2872*9c5db199SXin Li between pairing and connect and reduces overall test execution time). 2873*9c5db199SXin Li 2874*9c5db199SXin Li @param address: Address of the device to pair. 2875*9c5db199SXin Li @param pin: The pin code of the device to pair. 2876*9c5db199SXin Li @param trusted: indicating whether to set the device trusted. 2877*9c5db199SXin Li @param timeout: The timeout in seconds for pairing. 2878*9c5db199SXin Li 2879*9c5db199SXin Li @returns: True on success. False otherwise. 2880*9c5db199SXin Li 2881*9c5db199SXin Li """ 2882*9c5db199SXin Li 2883*9c5db199SXin Li def connect_reply(): 2884*9c5db199SXin Li """Handler when connect succeeded.""" 2885*9c5db199SXin Li logging.info('Device connected: %s', device_path) 2886*9c5db199SXin Li 2887*9c5db199SXin Li def connect_error(error): 2888*9c5db199SXin Li """Handler when connect failed. 2889*9c5db199SXin Li 2890*9c5db199SXin Li @param error: one of the errors defined in org.bluez.Error 2891*9c5db199SXin Li representing the error in connect. 2892*9c5db199SXin Li """ 2893*9c5db199SXin Li logging.error('Connect device failed: %s', error) 2894*9c5db199SXin Li 2895*9c5db199SXin Li def pair_reply(): 2896*9c5db199SXin Li """Handler when pairing succeeded.""" 2897*9c5db199SXin Li logging.info('Device paired: %s', device_path) 2898*9c5db199SXin Li if trusted: 2899*9c5db199SXin Li self._set_trusted_by_path(device_path, trusted=True) 2900*9c5db199SXin Li logging.info('Device trusted: %s', device_path) 2901*9c5db199SXin Li 2902*9c5db199SXin Li # On finishing pairing, also connect 2903*9c5db199SXin Li self.dbus_method_with_handlers(device.Connect, 2904*9c5db199SXin Li connect_reply, 2905*9c5db199SXin Li connect_error, 2906*9c5db199SXin Li timeout=timeout * 1000) 2907*9c5db199SXin Li 2908*9c5db199SXin Li def pair_error(error): 2909*9c5db199SXin Li """Handler when pairing failed. 2910*9c5db199SXin Li 2911*9c5db199SXin Li @param error: one of errors defined in org.bluez.Error representing 2912*9c5db199SXin Li the error in pairing. 2913*9c5db199SXin Li 2914*9c5db199SXin Li """ 2915*9c5db199SXin Li if 'org.freedesktop.DBus.Error.NoReply' in str(error): 2916*9c5db199SXin Li logging.error('Timed out after %d ms. Cancelling pairing.', 2917*9c5db199SXin Li timeout) 2918*9c5db199SXin Li device.CancelPairing() 2919*9c5db199SXin Li else: 2920*9c5db199SXin Li logging.error('Pairing device failed: %s', error) 2921*9c5db199SXin Li 2922*9c5db199SXin Li device = self._find_device(address) 2923*9c5db199SXin Li if not device: 2924*9c5db199SXin Li logging.error('Device not found') 2925*9c5db199SXin Li return False 2926*9c5db199SXin Li 2927*9c5db199SXin Li device_path = self._get_device_path(address) 2928*9c5db199SXin Li logging.info('Device %s is found.', device_path) 2929*9c5db199SXin Li 2930*9c5db199SXin Li self._setup_pairing_agent(pin) 2931*9c5db199SXin Li 2932*9c5db199SXin Li try: 2933*9c5db199SXin Li if not self._is_paired(device): 2934*9c5db199SXin Li logging.info('Device is not paired. Pair and Connect.') 2935*9c5db199SXin Li self.dbus_method_with_handlers(device.Pair, 2936*9c5db199SXin Li pair_reply, 2937*9c5db199SXin Li pair_error, 2938*9c5db199SXin Li timeout=timeout * 1000) 2939*9c5db199SXin Li elif not self._is_connected(device): 2940*9c5db199SXin Li logging.info('Device is already paired. Connect.') 2941*9c5db199SXin Li self.dbus_method_with_handlers(device.Connect, 2942*9c5db199SXin Li connect_reply, 2943*9c5db199SXin Li connect_error, 2944*9c5db199SXin Li tiemout=timeout * 1000) 2945*9c5db199SXin Li except Exception as e: 2946*9c5db199SXin Li logging.error('Exception %s in pair_legacy_device', e) 2947*9c5db199SXin Li return False 2948*9c5db199SXin Li 2949*9c5db199SXin Li return self._is_paired(device) and self._is_connected(device) 2950*9c5db199SXin Li 2951*9c5db199SXin Li @dbus_safe(False) 2952*9c5db199SXin Li def remove_device_object(self, address): 2953*9c5db199SXin Li """Removes a device object and the pairing information. 2954*9c5db199SXin Li 2955*9c5db199SXin Li Calls RemoveDevice method to remove remote device 2956*9c5db199SXin Li object and the pairing information. 2957*9c5db199SXin Li 2958*9c5db199SXin Li @param address: Address of the device to unpair. 2959*9c5db199SXin Li 2960*9c5db199SXin Li @returns: True on success. False otherwise. 2961*9c5db199SXin Li 2962*9c5db199SXin Li """ 2963*9c5db199SXin Li device = self._find_device(address) 2964*9c5db199SXin Li if not device: 2965*9c5db199SXin Li logging.error('Device not found') 2966*9c5db199SXin Li return False 2967*9c5db199SXin Li self._adapter_proxy.RemoveDevice(self._get_device_path(address)) 2968*9c5db199SXin Li return True 2969*9c5db199SXin Li 2970*9c5db199SXin Li @dbus_safe(False) 2971*9c5db199SXin Li def connect_device(self, address): 2972*9c5db199SXin Li """Connects a device. 2973*9c5db199SXin Li 2974*9c5db199SXin Li Connects a device if it is not connected. 2975*9c5db199SXin Li 2976*9c5db199SXin Li @param address: Address of the device to connect. 2977*9c5db199SXin Li 2978*9c5db199SXin Li @returns: True on success. False otherwise. 2979*9c5db199SXin Li 2980*9c5db199SXin Li """ 2981*9c5db199SXin Li device = self._find_device(address) 2982*9c5db199SXin Li if not device: 2983*9c5db199SXin Li logging.error('Device not found') 2984*9c5db199SXin Li return False 2985*9c5db199SXin Li if self._is_connected(device): 2986*9c5db199SXin Li logging.info('Device is already connected') 2987*9c5db199SXin Li return True 2988*9c5db199SXin Li device.Connect() 2989*9c5db199SXin Li return self._is_connected(device) 2990*9c5db199SXin Li 2991*9c5db199SXin Li @dbus_safe(False) 2992*9c5db199SXin Li def device_is_connected(self, address): 2993*9c5db199SXin Li """Checks if a device is connected. 2994*9c5db199SXin Li 2995*9c5db199SXin Li @param address: Address of the device to connect. 2996*9c5db199SXin Li 2997*9c5db199SXin Li @returns: True if device is connected. False otherwise. 2998*9c5db199SXin Li 2999*9c5db199SXin Li """ 3000*9c5db199SXin Li device = self._find_device(address) 3001*9c5db199SXin Li if not device: 3002*9c5db199SXin Li logging.error('Device not found') 3003*9c5db199SXin Li return False 3004*9c5db199SXin Li return self._is_connected(device) 3005*9c5db199SXin Li 3006*9c5db199SXin Li @dbus_safe(False) 3007*9c5db199SXin Li def disconnect_device(self, address): 3008*9c5db199SXin Li """Disconnects a device. 3009*9c5db199SXin Li 3010*9c5db199SXin Li Disconnects a device if it is connected. 3011*9c5db199SXin Li 3012*9c5db199SXin Li @param address: Address of the device to disconnect. 3013*9c5db199SXin Li 3014*9c5db199SXin Li @returns: True on success. False otherwise. 3015*9c5db199SXin Li 3016*9c5db199SXin Li """ 3017*9c5db199SXin Li device = self._find_device(address) 3018*9c5db199SXin Li if not device: 3019*9c5db199SXin Li logging.error('Device not found') 3020*9c5db199SXin Li return False 3021*9c5db199SXin Li if not self._is_connected(device): 3022*9c5db199SXin Li logging.info('Device is not connected') 3023*9c5db199SXin Li return True 3024*9c5db199SXin Li device.Disconnect() 3025*9c5db199SXin Li return not self._is_connected(device) 3026*9c5db199SXin Li 3027*9c5db199SXin Li @dbus_safe(False) 3028*9c5db199SXin Li def _device_services_resolved(self, device): 3029*9c5db199SXin Li """Checks if services are resolved. 3030*9c5db199SXin Li 3031*9c5db199SXin Li @param device: An 'org.bluez.Device1' interface to the device. 3032*9c5db199SXin Li 3033*9c5db199SXin Li @returns: True if device is connected. False otherwise. 3034*9c5db199SXin Li 3035*9c5db199SXin Li """ 3036*9c5db199SXin Li logging.info('device for services resolved: %s', device) 3037*9c5db199SXin Li props = device[self.DBUS_PROP_IFACE] 3038*9c5db199SXin Li resolved = props.Get(self.BLUEZ_DEVICE_IFACE, 'ServicesResolved') 3039*9c5db199SXin Li logging.info('Services resolved = %r', resolved) 3040*9c5db199SXin Li return bool(resolved) 3041*9c5db199SXin Li 3042*9c5db199SXin Li @dbus_safe(False) 3043*9c5db199SXin Li def device_services_resolved(self, address): 3044*9c5db199SXin Li """Checks if service discovery is complete on a device. 3045*9c5db199SXin Li 3046*9c5db199SXin Li Checks whether service discovery has been completed.. 3047*9c5db199SXin Li 3048*9c5db199SXin Li @param address: Address of the remote device. 3049*9c5db199SXin Li 3050*9c5db199SXin Li @returns: True on success. False otherwise. 3051*9c5db199SXin Li 3052*9c5db199SXin Li """ 3053*9c5db199SXin Li device = self._find_device(address) 3054*9c5db199SXin Li if not device: 3055*9c5db199SXin Li logging.error('Device not found') 3056*9c5db199SXin Li return False 3057*9c5db199SXin Li 3058*9c5db199SXin Li if not self._is_connected(device): 3059*9c5db199SXin Li logging.info('Device is not connected') 3060*9c5db199SXin Li return False 3061*9c5db199SXin Li 3062*9c5db199SXin Li return self._device_services_resolved(device) 3063*9c5db199SXin Li 3064*9c5db199SXin Li def btmon_start(self): 3065*9c5db199SXin Li """Start btmon monitoring.""" 3066*9c5db199SXin Li self.btmon.start() 3067*9c5db199SXin Li 3068*9c5db199SXin Li def btmon_stop(self): 3069*9c5db199SXin Li """Stop btmon monitoring.""" 3070*9c5db199SXin Li self.btmon.stop() 3071*9c5db199SXin Li 3072*9c5db199SXin Li def btmon_get(self, search_str, start_str): 3073*9c5db199SXin Li """Get btmon output contents. 3074*9c5db199SXin Li 3075*9c5db199SXin Li @param search_str: only lines with search_str would be kept. 3076*9c5db199SXin Li @param start_str: all lines before the occurrence of start_str would be 3077*9c5db199SXin Li filtered. 3078*9c5db199SXin Li 3079*9c5db199SXin Li @returns: the recorded btmon output. 3080*9c5db199SXin Li 3081*9c5db199SXin Li """ 3082*9c5db199SXin Li return self.btmon.get_contents(search_str=search_str, 3083*9c5db199SXin Li start_str=start_str) 3084*9c5db199SXin Li 3085*9c5db199SXin Li def btmon_find(self, pattern_str): 3086*9c5db199SXin Li """Find if a pattern string exists in btmon output. 3087*9c5db199SXin Li 3088*9c5db199SXin Li @param pattern_str: the pattern string to find. 3089*9c5db199SXin Li 3090*9c5db199SXin Li @returns: True on success. False otherwise. 3091*9c5db199SXin Li 3092*9c5db199SXin Li """ 3093*9c5db199SXin Li return self.btmon.find(pattern_str) 3094*9c5db199SXin Li 3095*9c5db199SXin Li def dbus_method_with_handlers(self, dbus_method, reply_handler, 3096*9c5db199SXin Li error_handler, *args, **kwargs): 3097*9c5db199SXin Li """Run an async dbus method. 3098*9c5db199SXin Li 3099*9c5db199SXin Li @param dbus_method: the dbus async method to invoke. 3100*9c5db199SXin Li @param reply_handler: the reply handler for the dbus method. 3101*9c5db199SXin Li @param error_handler: the error handler for the dbus method. 3102*9c5db199SXin Li @param *args: additional arguments for the dbus method. 3103*9c5db199SXin Li @param **kwargs: additional keyword arguments for the dbus method. 3104*9c5db199SXin Li 3105*9c5db199SXin Li @returns: an empty string '' on success; 3106*9c5db199SXin Li None if there is no _advertising interface manager; and 3107*9c5db199SXin Li an error string if the dbus method fails or exception occurs 3108*9c5db199SXin Li 3109*9c5db199SXin Li """ 3110*9c5db199SXin Li 3111*9c5db199SXin Li def successful_cb(): 3112*9c5db199SXin Li """Called when the dbus_method completed successfully.""" 3113*9c5db199SXin Li reply_handler() 3114*9c5db199SXin Li self.dbus_cb_msg = '' 3115*9c5db199SXin Li 3116*9c5db199SXin Li def error_cb(error): 3117*9c5db199SXin Li """Called when the dbus_method failed.""" 3118*9c5db199SXin Li error_handler(error) 3119*9c5db199SXin Li self.dbus_cb_msg = str(error) 3120*9c5db199SXin Li 3121*9c5db199SXin Li # Successful dbus calls will have a non-throwing result and error 3122*9c5db199SXin Li # results will throw GLib.Error. 3123*9c5db199SXin Li try: 3124*9c5db199SXin Li _ = dbus_method(*args, **kwargs) 3125*9c5db199SXin Li successful_cb() 3126*9c5db199SXin Li except GLib.Error as e: 3127*9c5db199SXin Li error_cb(e) 3128*9c5db199SXin Li except Exception as e: 3129*9c5db199SXin Li logging.error('Exception %s in dbus_method_with_handlers ', e) 3130*9c5db199SXin Li return str(e) 3131*9c5db199SXin Li 3132*9c5db199SXin Li return self.dbus_cb_msg 3133*9c5db199SXin Li 3134*9c5db199SXin Li def advmon_check_manager_interface_exist(self): 3135*9c5db199SXin Li """Check if AdvertisementMonitorManager1 interface is available. 3136*9c5db199SXin Li 3137*9c5db199SXin Li @returns: True if Manager interface is available, False otherwise. 3138*9c5db199SXin Li 3139*9c5db199SXin Li """ 3140*9c5db199SXin Li objects = self._objmgr_proxy.GetManagedObjects() 3141*9c5db199SXin Li for _, ifaces in six.iteritems(objects): 3142*9c5db199SXin Li if self.BLUEZ_ADV_MONITOR_MANAGER_IFACE in ifaces: 3143*9c5db199SXin Li return True 3144*9c5db199SXin Li 3145*9c5db199SXin Li return False 3146*9c5db199SXin Li 3147*9c5db199SXin Li def advmon_read_supported_types(self): 3148*9c5db199SXin Li """Read the Advertisement Monitor supported monitor types. 3149*9c5db199SXin Li 3150*9c5db199SXin Li Reads the value of 'SupportedMonitorTypes' property of the 3151*9c5db199SXin Li AdvertisementMonitorManager1 interface on the adapter. 3152*9c5db199SXin Li 3153*9c5db199SXin Li @returns: the list of the supported monitor types. 3154*9c5db199SXin Li 3155*9c5db199SXin Li """ 3156*9c5db199SXin Li return unpack_if_variant( 3157*9c5db199SXin Li self._property_proxy.Get(self.BLUEZ_ADV_MONITOR_MANAGER_IFACE, 3158*9c5db199SXin Li 'SupportedMonitorTypes')) 3159*9c5db199SXin Li 3160*9c5db199SXin Li def advmon_read_supported_features(self): 3161*9c5db199SXin Li """Read the Advertisement Monitor supported features. 3162*9c5db199SXin Li 3163*9c5db199SXin Li Reads the value of 'SupportedFeatures' property of the 3164*9c5db199SXin Li AdvertisementMonitorManager1 interface on the adapter. 3165*9c5db199SXin Li 3166*9c5db199SXin Li @returns: the list of the supported features. 3167*9c5db199SXin Li 3168*9c5db199SXin Li """ 3169*9c5db199SXin Li return unpack_if_variant( 3170*9c5db199SXin Li self._property_proxy.Get(self.BLUEZ_ADV_MONITOR_MANAGER_IFACE, 3171*9c5db199SXin Li 'SupportedFeatures')) 3172*9c5db199SXin Li 3173*9c5db199SXin Li def advmon_create_app(self): 3174*9c5db199SXin Li """Create an advertisement monitor app. 3175*9c5db199SXin Li 3176*9c5db199SXin Li @returns: app id, once the app is created. 3177*9c5db199SXin Li 3178*9c5db199SXin Li """ 3179*9c5db199SXin Li return self.advmon_appmgr.create_app() 3180*9c5db199SXin Li 3181*9c5db199SXin Li def advmon_exit_app(self, app_id): 3182*9c5db199SXin Li """Exit an advertisement monitor app. 3183*9c5db199SXin Li 3184*9c5db199SXin Li @param app_id: the app id. 3185*9c5db199SXin Li 3186*9c5db199SXin Li @returns: True on success, False otherwise. 3187*9c5db199SXin Li 3188*9c5db199SXin Li """ 3189*9c5db199SXin Li return self.advmon_appmgr.exit_app(app_id) 3190*9c5db199SXin Li 3191*9c5db199SXin Li def advmon_kill_app(self, app_id): 3192*9c5db199SXin Li """Kill an advertisement monitor app by sending SIGKILL. 3193*9c5db199SXin Li 3194*9c5db199SXin Li @param app_id: the app id. 3195*9c5db199SXin Li 3196*9c5db199SXin Li @returns: True on success, False otherwise. 3197*9c5db199SXin Li 3198*9c5db199SXin Li """ 3199*9c5db199SXin Li return self.advmon_appmgr.kill_app(app_id) 3200*9c5db199SXin Li 3201*9c5db199SXin Li def advmon_register_app(self, app_id): 3202*9c5db199SXin Li """Register an advertisement monitor app. 3203*9c5db199SXin Li 3204*9c5db199SXin Li @param app_id: the app id. 3205*9c5db199SXin Li 3206*9c5db199SXin Li @returns: True on success, False otherwise. 3207*9c5db199SXin Li 3208*9c5db199SXin Li """ 3209*9c5db199SXin Li return self.advmon_appmgr.register_app(app_id) 3210*9c5db199SXin Li 3211*9c5db199SXin Li def advmon_unregister_app(self, app_id): 3212*9c5db199SXin Li """Unregister an advertisement monitor app. 3213*9c5db199SXin Li 3214*9c5db199SXin Li @param app_id: the app id. 3215*9c5db199SXin Li 3216*9c5db199SXin Li @returns: True on success, False otherwise. 3217*9c5db199SXin Li 3218*9c5db199SXin Li """ 3219*9c5db199SXin Li return self.advmon_appmgr.unregister_app(app_id) 3220*9c5db199SXin Li 3221*9c5db199SXin Li def advmon_add_monitor(self, app_id, monitor_data): 3222*9c5db199SXin Li """Create an Advertisement Monitor object. 3223*9c5db199SXin Li 3224*9c5db199SXin Li @param app_id: the app id. 3225*9c5db199SXin Li @param monitor_data: the list containing monitor type, RSSI filter 3226*9c5db199SXin Li values and patterns. 3227*9c5db199SXin Li 3228*9c5db199SXin Li @returns: monitor id, once the monitor is created, None otherwise. 3229*9c5db199SXin Li 3230*9c5db199SXin Li """ 3231*9c5db199SXin Li return self.advmon_appmgr.add_monitor(app_id, monitor_data) 3232*9c5db199SXin Li 3233*9c5db199SXin Li def advmon_remove_monitor(self, app_id, monitor_id): 3234*9c5db199SXin Li """Remove the Advertisement Monitor object. 3235*9c5db199SXin Li 3236*9c5db199SXin Li @param app_id: the app id. 3237*9c5db199SXin Li @param monitor_id: the monitor id. 3238*9c5db199SXin Li 3239*9c5db199SXin Li @returns: True on success, False otherwise. 3240*9c5db199SXin Li 3241*9c5db199SXin Li """ 3242*9c5db199SXin Li return self.advmon_appmgr.remove_monitor(app_id, monitor_id) 3243*9c5db199SXin Li 3244*9c5db199SXin Li def advmon_get_event_count(self, app_id, monitor_id, event): 3245*9c5db199SXin Li """Read the count of a particular event on the given monitor. 3246*9c5db199SXin Li 3247*9c5db199SXin Li @param app_id: the app id. 3248*9c5db199SXin Li @param monitor_id: the monitor id. 3249*9c5db199SXin Li @param event: name of the specific event or 'All' for all events. 3250*9c5db199SXin Li 3251*9c5db199SXin Li @returns: count of the specific event or dict of counts of all events. 3252*9c5db199SXin Li 3253*9c5db199SXin Li """ 3254*9c5db199SXin Li return self.advmon_appmgr.get_event_count(app_id, monitor_id, event) 3255*9c5db199SXin Li 3256*9c5db199SXin Li def advmon_reset_event_count(self, app_id, monitor_id, event): 3257*9c5db199SXin Li """Reset the count of a particular event on the given monitor. 3258*9c5db199SXin Li 3259*9c5db199SXin Li @param app_id: the app id. 3260*9c5db199SXin Li @param monitor_id: the monitor id. 3261*9c5db199SXin Li @param event: name of the specific event or 'All' for all events. 3262*9c5db199SXin Li 3263*9c5db199SXin Li @returns: True on success, False otherwise. 3264*9c5db199SXin Li 3265*9c5db199SXin Li """ 3266*9c5db199SXin Li return self.advmon_appmgr.reset_event_count(app_id, monitor_id, event) 3267*9c5db199SXin Li 3268*9c5db199SXin Li def advmon_set_target_devices(self, app_id, monitor_id, devices): 3269*9c5db199SXin Li """Set the target devices to the given monitor. 3270*9c5db199SXin Li 3271*9c5db199SXin Li DeviceFound and DeviceLost will only be counted if it is triggered by a 3272*9c5db199SXin Li target device. 3273*9c5db199SXin Li 3274*9c5db199SXin Li @param app_id: the app id. 3275*9c5db199SXin Li @param monitor_id: the monitor id. 3276*9c5db199SXin Li @param devices: a list of devices in MAC address 3277*9c5db199SXin Li 3278*9c5db199SXin Li @returns: True on success, False otherwise. 3279*9c5db199SXin Li 3280*9c5db199SXin Li """ 3281*9c5db199SXin Li paths = [] 3282*9c5db199SXin Li for addr in devices: 3283*9c5db199SXin Li paths.append('{}/dev_{}'.format(self._adapter_path, 3284*9c5db199SXin Li addr.replace(':', '_'))) 3285*9c5db199SXin Li 3286*9c5db199SXin Li return self.advmon_appmgr.set_target_devices(app_id, monitor_id, paths) 3287*9c5db199SXin Li 3288*9c5db199SXin Li def advmon_interleave_scan_logger_start(self): 3289*9c5db199SXin Li """ Start interleave logger recording 3290*9c5db199SXin Li """ 3291*9c5db199SXin Li self.advmon_interleave_logger.StartRecording() 3292*9c5db199SXin Li 3293*9c5db199SXin Li def advmon_interleave_scan_logger_stop(self): 3294*9c5db199SXin Li """ Stop interleave logger recording 3295*9c5db199SXin Li 3296*9c5db199SXin Li @returns: True if logs were successfully collected, 3297*9c5db199SXin Li False otherwise. 3298*9c5db199SXin Li 3299*9c5db199SXin Li """ 3300*9c5db199SXin Li return self.advmon_interleave_logger.StopRecording() 3301*9c5db199SXin Li 3302*9c5db199SXin Li def advmon_interleave_scan_logger_get_records(self): 3303*9c5db199SXin Li """ Get records in previous log collections 3304*9c5db199SXin Li 3305*9c5db199SXin Li @returns: a list of records, where each item is a record of 3306*9c5db199SXin Li interleave |state| and the |time| the state starts. 3307*9c5db199SXin Li |state| could be {'no filter', 'allowlist'} 3308*9c5db199SXin Li |time| is system time in sec 3309*9c5db199SXin Li 3310*9c5db199SXin Li """ 3311*9c5db199SXin Li return self.advmon_interleave_logger.records 3312*9c5db199SXin Li 3313*9c5db199SXin Li def advmon_interleave_scan_logger_get_cancel_events(self): 3314*9c5db199SXin Li """ Get cancel events in previous log collections 3315*9c5db199SXin Li 3316*9c5db199SXin Li @returns: a list of cancel |time| when a interleave cancel event log 3317*9c5db199SXin Li was found. 3318*9c5db199SXin Li |time| is system time in sec 3319*9c5db199SXin Li 3320*9c5db199SXin Li """ 3321*9c5db199SXin Li return self.advmon_interleave_logger.cancel_events 3322*9c5db199SXin Li 3323*9c5db199SXin Li def register_advertisement(self, advertisement_data): 3324*9c5db199SXin Li """Register an advertisement. 3325*9c5db199SXin Li 3326*9c5db199SXin Li Note that rpc supports only conformable types. Hence, a 3327*9c5db199SXin Li dict about the advertisement is passed as a parameter such 3328*9c5db199SXin Li that the advertisement object could be constructed on the host. 3329*9c5db199SXin Li 3330*9c5db199SXin Li @param advertisement_data: a dict of the advertisement to register. 3331*9c5db199SXin Li 3332*9c5db199SXin Li @returns: True on success. False otherwise. 3333*9c5db199SXin Li 3334*9c5db199SXin Li """ 3335*9c5db199SXin Li adv = advertisement.Advertisement(self.bus, advertisement_data) 3336*9c5db199SXin Li self.advertisements.append(adv) 3337*9c5db199SXin Li return self.dbus_method_with_handlers( 3338*9c5db199SXin Li self._advertising.RegisterAdvertisement, 3339*9c5db199SXin Li # reply handler 3340*9c5db199SXin Li lambda: logging.info('register_advertisement: succeeded.'), 3341*9c5db199SXin Li # error handler 3342*9c5db199SXin Li lambda error: logging.error( 3343*9c5db199SXin Li 'register_advertisement: failed: %s', str(error)), 3344*9c5db199SXin Li # other arguments 3345*9c5db199SXin Li adv.get_path(), 3346*9c5db199SXin Li {}) 3347*9c5db199SXin Li 3348*9c5db199SXin Li def unregister_advertisement(self, advertisement_data): 3349*9c5db199SXin Li """Unregister an advertisement. 3350*9c5db199SXin Li 3351*9c5db199SXin Li Note that to unregister an advertisement, it is required to use 3352*9c5db199SXin Li the same self._advertising interface manager. This is because 3353*9c5db199SXin Li bluez only allows the same sender to invoke UnregisterAdvertisement 3354*9c5db199SXin Li method. Hence, watch out that the bluetoothd is not restarted or 3355*9c5db199SXin Li self.start_bluetoothd() is not executed between the time span that 3356*9c5db199SXin Li an advertisement is registered and unregistered. 3357*9c5db199SXin Li 3358*9c5db199SXin Li @param advertisement_data: a dict of the advertisements to unregister. 3359*9c5db199SXin Li 3360*9c5db199SXin Li @returns: True on success. False otherwise. 3361*9c5db199SXin Li 3362*9c5db199SXin Li """ 3363*9c5db199SXin Li path = advertisement_data.get('Path') 3364*9c5db199SXin Li for index, adv in enumerate(self.advertisements): 3365*9c5db199SXin Li if adv.get_path() == path: 3366*9c5db199SXin Li break 3367*9c5db199SXin Li else: 3368*9c5db199SXin Li logging.error('Fail to find the advertisement under the path: %s', 3369*9c5db199SXin Li path) 3370*9c5db199SXin Li return False 3371*9c5db199SXin Li 3372*9c5db199SXin Li result = self.dbus_method_with_handlers( 3373*9c5db199SXin Li self._advertising.UnregisterAdvertisement, 3374*9c5db199SXin Li # reply handler 3375*9c5db199SXin Li lambda: logging.info('unregister_advertisement: succeeded.'), 3376*9c5db199SXin Li # error handler 3377*9c5db199SXin Li lambda error: logging.error( 3378*9c5db199SXin Li 'unregister_advertisement: failed: %s', str(error)), 3379*9c5db199SXin Li # other arguments 3380*9c5db199SXin Li adv.get_path()) 3381*9c5db199SXin Li 3382*9c5db199SXin Li # Call unregister() so that the same path could be reused. 3383*9c5db199SXin Li adv.unregister() 3384*9c5db199SXin Li del self.advertisements[index] 3385*9c5db199SXin Li 3386*9c5db199SXin Li return result 3387*9c5db199SXin Li 3388*9c5db199SXin Li def set_advertising_intervals(self, min_adv_interval_ms, 3389*9c5db199SXin Li max_adv_interval_ms): 3390*9c5db199SXin Li """Set advertising intervals. 3391*9c5db199SXin Li 3392*9c5db199SXin Li @param min_adv_interval_ms: the min advertising interval in ms. 3393*9c5db199SXin Li @param max_adv_interval_ms: the max advertising interval in ms. 3394*9c5db199SXin Li 3395*9c5db199SXin Li @returns: True on success. False otherwise. 3396*9c5db199SXin Li 3397*9c5db199SXin Li """ 3398*9c5db199SXin Li return self.dbus_method_with_handlers( 3399*9c5db199SXin Li self._advertising.SetAdvertisingIntervals, 3400*9c5db199SXin Li # reply handler 3401*9c5db199SXin Li lambda: logging.info('set_advertising_intervals: succeeded.'), 3402*9c5db199SXin Li # error handler 3403*9c5db199SXin Li lambda error: logging.error( 3404*9c5db199SXin Li 'set_advertising_intervals: failed: %s', str(error)), 3405*9c5db199SXin Li # other arguments 3406*9c5db199SXin Li min_adv_interval_ms, 3407*9c5db199SXin Li max_adv_interval_ms) 3408*9c5db199SXin Li 3409*9c5db199SXin Li def get_advertisement_property(self, adv_path, prop_name): 3410*9c5db199SXin Li """Grab property of an advertisement registered on the DUT 3411*9c5db199SXin Li 3412*9c5db199SXin Li The service on the DUT registers a dbus object and holds it. During the 3413*9c5db199SXin Li test, some properties on the object may change, so this allows the test 3414*9c5db199SXin Li access to the properties at run-time. 3415*9c5db199SXin Li 3416*9c5db199SXin Li @param adv_path: string path of the dbus object 3417*9c5db199SXin Li @param prop_name: string name of the property required 3418*9c5db199SXin Li 3419*9c5db199SXin Li @returns: the value of the property in standard (non-dbus) type if the 3420*9c5db199SXin Li property exists, else None 3421*9c5db199SXin Li """ 3422*9c5db199SXin Li for adv in self.advertisements: 3423*9c5db199SXin Li if str(adv.get_path()) == adv_path: 3424*9c5db199SXin Li adv_props = adv.GetAll('org.bluez.LEAdvertisement1') 3425*9c5db199SXin Li return unpack_if_variant(adv_props.get(prop_name, None)) 3426*9c5db199SXin Li 3427*9c5db199SXin Li return None 3428*9c5db199SXin Li 3429*9c5db199SXin Li def get_advertising_manager_property(self, prop_name): 3430*9c5db199SXin Li """Grab property of the bluez advertising manager 3431*9c5db199SXin Li 3432*9c5db199SXin Li This allows us to understand the DUT's advertising capabilities, for 3433*9c5db199SXin Li instance the maximum number of advertising instances supported, so that 3434*9c5db199SXin Li we can test these capabilities. 3435*9c5db199SXin Li 3436*9c5db199SXin Li @param adv_path: string path of the dbus object 3437*9c5db199SXin Li @param prop_name: string name of the property required 3438*9c5db199SXin Li 3439*9c5db199SXin Li @returns: the value of the property in standard (non-dbus) type if the 3440*9c5db199SXin Li property exists, else None 3441*9c5db199SXin Li """ 3442*9c5db199SXin Li 3443*9c5db199SXin Li return unpack_if_variant( 3444*9c5db199SXin Li self._property_proxy.Get( 3445*9c5db199SXin Li self.BLUEZ_LE_ADVERTISING_MANAGER_IFACE, prop_name)) 3446*9c5db199SXin Li 3447*9c5db199SXin Li def reset_advertising(self): 3448*9c5db199SXin Li """Reset advertising. 3449*9c5db199SXin Li 3450*9c5db199SXin Li This includes un-registering all advertisements, reset advertising 3451*9c5db199SXin Li intervals, and disable advertising. 3452*9c5db199SXin Li 3453*9c5db199SXin Li @returns: True on success. False otherwise. 3454*9c5db199SXin Li 3455*9c5db199SXin Li """ 3456*9c5db199SXin Li # It is required to execute unregister() to unregister the 3457*9c5db199SXin Li # object-path handler of each advertisement. In this way, we could 3458*9c5db199SXin Li # register an advertisement with the same path repeatedly. 3459*9c5db199SXin Li for adv in self.advertisements: 3460*9c5db199SXin Li adv.unregister() 3461*9c5db199SXin Li del self.advertisements[:] 3462*9c5db199SXin Li 3463*9c5db199SXin Li return self.dbus_method_with_handlers( 3464*9c5db199SXin Li self._advertising.ResetAdvertising, 3465*9c5db199SXin Li # reply handler 3466*9c5db199SXin Li lambda: logging.info('reset_advertising: succeeded.'), 3467*9c5db199SXin Li # error handler 3468*9c5db199SXin Li lambda error: logging.error('reset_advertising: failed: %s', 3469*9c5db199SXin Li str(error))) 3470*9c5db199SXin Li 3471*9c5db199SXin Li def get_gatt_attributes_map(self, address): 3472*9c5db199SXin Li """Return a JSON formatted string of the GATT attributes of a device, 3473*9c5db199SXin Li keyed by UUID 3474*9c5db199SXin Li @param address: a string of the MAC address of the device 3475*9c5db199SXin Li 3476*9c5db199SXin Li @return: JSON formated string, stored the nested structure of the 3477*9c5db199SXin Li attributes. Each attribute has 'path' and 3478*9c5db199SXin Li ['characteristics' | 'descriptors'], which store their object path and 3479*9c5db199SXin Li children respectively. 3480*9c5db199SXin Li 3481*9c5db199SXin Li """ 3482*9c5db199SXin Li attribute_map = dict() 3483*9c5db199SXin Li 3484*9c5db199SXin Li device_object_path = self._get_device_path(address) 3485*9c5db199SXin Li objects = self._objmgr_proxy.GetManagedObjects() 3486*9c5db199SXin Li service_map = self._get_service_map(device_object_path, objects) 3487*9c5db199SXin Li 3488*9c5db199SXin Li servs = dict() 3489*9c5db199SXin Li attribute_map['services'] = servs 3490*9c5db199SXin Li 3491*9c5db199SXin Li for uuid, path in service_map.items(): 3492*9c5db199SXin Li 3493*9c5db199SXin Li servs[uuid] = dict() 3494*9c5db199SXin Li serv = servs[uuid] 3495*9c5db199SXin Li 3496*9c5db199SXin Li serv['path'] = path 3497*9c5db199SXin Li serv['characteristics'] = dict() 3498*9c5db199SXin Li chrcs = serv['characteristics'] 3499*9c5db199SXin Li 3500*9c5db199SXin Li chrcs_map = self._get_characteristic_map(path, objects) 3501*9c5db199SXin Li for uuid, path in chrcs_map.items(): 3502*9c5db199SXin Li chrcs[uuid] = dict() 3503*9c5db199SXin Li chrc = chrcs[uuid] 3504*9c5db199SXin Li 3505*9c5db199SXin Li chrc['path'] = path 3506*9c5db199SXin Li chrc['descriptors'] = dict() 3507*9c5db199SXin Li descs = chrc['descriptors'] 3508*9c5db199SXin Li 3509*9c5db199SXin Li descs_map = self._get_descriptor_map(path, objects) 3510*9c5db199SXin Li 3511*9c5db199SXin Li for uuid, path in descs_map.items(): 3512*9c5db199SXin Li descs[uuid] = dict() 3513*9c5db199SXin Li desc = descs[uuid] 3514*9c5db199SXin Li 3515*9c5db199SXin Li desc['path'] = path 3516*9c5db199SXin Li 3517*9c5db199SXin Li return json.dumps(attribute_map) 3518*9c5db199SXin Li 3519*9c5db199SXin Li def _get_gatt_interface(self, uuid, object_path, interface): 3520*9c5db199SXin Li """Get dbus interface by uuid 3521*9c5db199SXin Li @param uuid: a string of uuid 3522*9c5db199SXin Li @param object_path: a string of the object path of the service 3523*9c5db199SXin Li 3524*9c5db199SXin Li @return: a dbus interface 3525*9c5db199SXin Li """ 3526*9c5db199SXin Li 3527*9c5db199SXin Li return self.bus.get(self.BLUEZ_SERVICE_NAME, object_path)[interface] 3528*9c5db199SXin Li 3529*9c5db199SXin Li def get_gatt_service_property(self, object_path, property_name): 3530*9c5db199SXin Li """Get property from a service attribute 3531*9c5db199SXin Li @param object_path: a string of the object path of the service 3532*9c5db199SXin Li @param property_name: a string of a property, ex: 'Value', 'UUID' 3533*9c5db199SXin Li 3534*9c5db199SXin Li @return: the property if success, 3535*9c5db199SXin Li none otherwise 3536*9c5db199SXin Li 3537*9c5db199SXin Li """ 3538*9c5db199SXin Li return self.get_gatt_attribute_property(object_path, 3539*9c5db199SXin Li self.BLUEZ_GATT_SERV_IFACE, 3540*9c5db199SXin Li property_name) 3541*9c5db199SXin Li 3542*9c5db199SXin Li def get_gatt_characteristic_property(self, object_path, property_name): 3543*9c5db199SXin Li """Get property from a characteristic attribute 3544*9c5db199SXin Li @param object_path: a string of the object path of the characteristic 3545*9c5db199SXin Li @param property_name: a string of a property, ex: 'Value', 'UUID' 3546*9c5db199SXin Li 3547*9c5db199SXin Li @return: the property if success, 3548*9c5db199SXin Li none otherwise 3549*9c5db199SXin Li 3550*9c5db199SXin Li """ 3551*9c5db199SXin Li return self.get_gatt_attribute_property(object_path, 3552*9c5db199SXin Li self.BLUEZ_GATT_CHAR_IFACE, 3553*9c5db199SXin Li property_name) 3554*9c5db199SXin Li 3555*9c5db199SXin Li def get_gatt_descriptor_property(self, object_path, property_name): 3556*9c5db199SXin Li """Get property from descriptor attribute 3557*9c5db199SXin Li @param object_path: a string of the object path of the descriptor 3558*9c5db199SXin Li @param property_name: a string of a property, ex: 'Value', 'UUID' 3559*9c5db199SXin Li 3560*9c5db199SXin Li @return: the property if success, 3561*9c5db199SXin Li none otherwise 3562*9c5db199SXin Li 3563*9c5db199SXin Li """ 3564*9c5db199SXin Li return self.get_gatt_attribute_property(object_path, 3565*9c5db199SXin Li self.BLUEZ_GATT_DESC_IFACE, 3566*9c5db199SXin Li property_name) 3567*9c5db199SXin Li 3568*9c5db199SXin Li @dbus_safe(None) 3569*9c5db199SXin Li def get_gatt_attribute_property(self, object_path, interface, 3570*9c5db199SXin Li property_name): 3571*9c5db199SXin Li """Get property from attribute 3572*9c5db199SXin Li @param object_path: a string of the bject path 3573*9c5db199SXin Li @param property_name: a string of a property, ex: 'Value', 'UUID' 3574*9c5db199SXin Li 3575*9c5db199SXin Li @return: the property if success, 3576*9c5db199SXin Li none otherwise 3577*9c5db199SXin Li 3578*9c5db199SXin Li """ 3579*9c5db199SXin Li gatt_object = self.bus.get(self.BLUEZ_SERVICE_NAME, object_path) 3580*9c5db199SXin Li prop = self._get_dbus_object_property(gatt_object, interface, 3581*9c5db199SXin Li property_name) 3582*9c5db199SXin Li logging.info(prop) 3583*9c5db199SXin Li if isinstance(prop, bytearray): 3584*9c5db199SXin Li return _dbus_byte_array_to_b64_string(prop) 3585*9c5db199SXin Li if isinstance(prop, bool): 3586*9c5db199SXin Li return bool(prop) 3587*9c5db199SXin Li if isinstance(prop, list): 3588*9c5db199SXin Li return list(map(str, prop)) 3589*9c5db199SXin Li return prop 3590*9c5db199SXin Li 3591*9c5db199SXin Li @dbus_safe(None) 3592*9c5db199SXin Li def gatt_characteristic_read_value(self, uuid, object_path): 3593*9c5db199SXin Li """Perform method ReadValue on a characteristic attribute 3594*9c5db199SXin Li @param uuid: a string of uuid 3595*9c5db199SXin Li @param object_path: a string of the object path of the characteristic 3596*9c5db199SXin Li 3597*9c5db199SXin Li @return: base64 string of dbus bytearray 3598*9c5db199SXin Li """ 3599*9c5db199SXin Li 3600*9c5db199SXin Li dbus_interface = self._get_gatt_interface(uuid, object_path, 3601*9c5db199SXin Li self.BLUEZ_GATT_CHAR_IFACE) 3602*9c5db199SXin Li value = dbus_interface.ReadValue({}) 3603*9c5db199SXin Li return _dbus_byte_array_to_b64_string(value) 3604*9c5db199SXin Li 3605*9c5db199SXin Li @dbus_safe(None) 3606*9c5db199SXin Li def gatt_descriptor_read_value(self, uuid, object_path): 3607*9c5db199SXin Li """Perform method ReadValue on a descriptor attribute 3608*9c5db199SXin Li @param uuid: a string of uuid 3609*9c5db199SXin Li @param object_path: a string of the object path of the descriptor 3610*9c5db199SXin Li 3611*9c5db199SXin Li @return: base64 string of dbus bytearray 3612*9c5db199SXin Li """ 3613*9c5db199SXin Li 3614*9c5db199SXin Li dbus_interface = self._get_gatt_interface(uuid, object_path, 3615*9c5db199SXin Li self.BLUEZ_GATT_DESC_IFACE) 3616*9c5db199SXin Li value = dbus_interface.ReadValue({}) 3617*9c5db199SXin Li return _dbus_byte_array_to_b64_string(value) 3618*9c5db199SXin Li 3619*9c5db199SXin Li @dbus_safe(False) 3620*9c5db199SXin Li def _get_attribute_map(self, object_path, dbus_interface, objects): 3621*9c5db199SXin Li """Gets a map of object paths under an object path. 3622*9c5db199SXin Li 3623*9c5db199SXin Li Walks the object tree, and returns a map of UUIDs to object paths for 3624*9c5db199SXin Li all resolved gatt object. 3625*9c5db199SXin Li 3626*9c5db199SXin Li @param object_path: The object path of the attribute to retrieve 3627*9c5db199SXin Li gatt UUIDs and paths from. 3628*9c5db199SXin Li @param objects: The managed objects. 3629*9c5db199SXin Li 3630*9c5db199SXin Li @returns: A dictionary of object paths, keyed by UUID. 3631*9c5db199SXin Li 3632*9c5db199SXin Li """ 3633*9c5db199SXin Li attr_map = {} 3634*9c5db199SXin Li 3635*9c5db199SXin Li if object_path: 3636*9c5db199SXin Li for path, ifaces in six.iteritems(objects): 3637*9c5db199SXin Li if (dbus_interface in ifaces and path.startswith(object_path)): 3638*9c5db199SXin Li uuid = ifaces[dbus_interface]['UUID'].lower() 3639*9c5db199SXin Li attr_map[uuid] = path 3640*9c5db199SXin Li 3641*9c5db199SXin Li else: 3642*9c5db199SXin Li logging.warning('object_path %s is not valid', object_path) 3643*9c5db199SXin Li 3644*9c5db199SXin Li return attr_map 3645*9c5db199SXin Li 3646*9c5db199SXin Li def _get_service_map(self, device_path, objects): 3647*9c5db199SXin Li """Gets a map of service paths for a device. 3648*9c5db199SXin Li 3649*9c5db199SXin Li @param device_path: the object path of the device. 3650*9c5db199SXin Li @param objects: The managed objects. 3651*9c5db199SXin Li """ 3652*9c5db199SXin Li return self._get_attribute_map(device_path, self.BLUEZ_GATT_SERV_IFACE, 3653*9c5db199SXin Li objects) 3654*9c5db199SXin Li 3655*9c5db199SXin Li def _get_characteristic_map(self, serv_path, objects): 3656*9c5db199SXin Li """Gets a map of characteristic paths for a service. 3657*9c5db199SXin Li 3658*9c5db199SXin Li @param serv_path: the object path of the service. 3659*9c5db199SXin Li @param objects: The managed objects. 3660*9c5db199SXin Li """ 3661*9c5db199SXin Li return self._get_attribute_map(serv_path, self.BLUEZ_GATT_CHAR_IFACE, 3662*9c5db199SXin Li objects) 3663*9c5db199SXin Li 3664*9c5db199SXin Li def _get_descriptor_map(self, chrc_path, objects): 3665*9c5db199SXin Li """Gets a map of descriptor paths for a characteristic. 3666*9c5db199SXin Li 3667*9c5db199SXin Li @param chrc_path: the object path of the characteristic. 3668*9c5db199SXin Li @param objects: The managed objects. 3669*9c5db199SXin Li """ 3670*9c5db199SXin Li return self._get_attribute_map(chrc_path, self.BLUEZ_GATT_DESC_IFACE, 3671*9c5db199SXin Li objects) 3672*9c5db199SXin Li 3673*9c5db199SXin Li @dbus_safe(None) 3674*9c5db199SXin Li def _get_dbus_object_property(self, dbus_object, dbus_interface, 3675*9c5db199SXin Li dbus_property): 3676*9c5db199SXin Li """Get the property in an object. 3677*9c5db199SXin Li 3678*9c5db199SXin Li @param dbus_object: a dbus object 3679*9c5db199SXin Li @param dbus_interface: a dbus interface where the property exists 3680*9c5db199SXin Li @param dbus_property: a dbus property of the dbus object, as a string 3681*9c5db199SXin Li 3682*9c5db199SXin Li @return: dbus type object if it success, e.g. dbus.Boolean, dbus.String, 3683*9c5db199SXin Li none otherwise 3684*9c5db199SXin Li 3685*9c5db199SXin Li """ 3686*9c5db199SXin Li return dbus_object[self.DBUS_PROP_IFACE].Get(dbus_interface, 3687*9c5db199SXin Li dbus_property) 3688*9c5db199SXin Li 3689*9c5db199SXin Li @dbus_safe(False) 3690*9c5db199SXin Li def get_characteristic_map(self, address): 3691*9c5db199SXin Li """Gets a map of characteristic paths for a device. 3692*9c5db199SXin Li 3693*9c5db199SXin Li Walks the object tree, and returns a map of uuids to object paths for 3694*9c5db199SXin Li all resolved gatt characteristics. 3695*9c5db199SXin Li 3696*9c5db199SXin Li @param address: The MAC address of the device to retrieve 3697*9c5db199SXin Li gatt characteristic uuids and paths from. 3698*9c5db199SXin Li 3699*9c5db199SXin Li @returns: A dictionary of characteristic paths, keyed by uuid. 3700*9c5db199SXin Li 3701*9c5db199SXin Li """ 3702*9c5db199SXin Li device_path = self._get_device_path(address) 3703*9c5db199SXin Li char_map = {} 3704*9c5db199SXin Li 3705*9c5db199SXin Li if device_path: 3706*9c5db199SXin Li objects = self._objmgr_proxy.GetManagedObjects() 3707*9c5db199SXin Li 3708*9c5db199SXin Li for path, ifaces in six.iteritems(objects): 3709*9c5db199SXin Li if (self.BLUEZ_GATT_CHAR_IFACE in ifaces 3710*9c5db199SXin Li and path.startswith(device_path)): 3711*9c5db199SXin Li uuid = ifaces[self.BLUEZ_GATT_CHAR_IFACE]['UUID'].lower() 3712*9c5db199SXin Li char_map[uuid] = path 3713*9c5db199SXin Li else: 3714*9c5db199SXin Li logging.warning('Device %s not in object tree.', address) 3715*9c5db199SXin Li 3716*9c5db199SXin Li return char_map 3717*9c5db199SXin Li 3718*9c5db199SXin Li @dbus_safe(None) 3719*9c5db199SXin Li def _get_char_object(self, uuid, address): 3720*9c5db199SXin Li """Gets a characteristic object. 3721*9c5db199SXin Li 3722*9c5db199SXin Li Gets a characteristic object for a given UUID and address. 3723*9c5db199SXin Li 3724*9c5db199SXin Li @param uuid: The UUID of the characteristic, as a string. 3725*9c5db199SXin Li @param address: The MAC address of the remote device. 3726*9c5db199SXin Li 3727*9c5db199SXin Li @returns: A dbus interface for the characteristic if the uuid/address 3728*9c5db199SXin Li is in the object tree. 3729*9c5db199SXin Li None if the address/uuid is not found in the object tree. 3730*9c5db199SXin Li 3731*9c5db199SXin Li """ 3732*9c5db199SXin Li path = self.get_characteristic_map(address).get(uuid) 3733*9c5db199SXin Li if not path: 3734*9c5db199SXin Li logging.error("path not found: %s %s", uuid, address) 3735*9c5db199SXin Li return None 3736*9c5db199SXin Li return self.bus.get(self.BLUEZ_SERVICE_NAME, 3737*9c5db199SXin Li path)[self.BLUEZ_GATT_CHAR_IFACE] 3738*9c5db199SXin Li 3739*9c5db199SXin Li @dbus_safe(None) 3740*9c5db199SXin Li def read_characteristic(self, uuid, address): 3741*9c5db199SXin Li """Reads the value of a gatt characteristic. 3742*9c5db199SXin Li 3743*9c5db199SXin Li Reads the current value of a gatt characteristic. Base64 endcoding is 3744*9c5db199SXin Li used for compatibility with the XML RPC interface. 3745*9c5db199SXin Li 3746*9c5db199SXin Li @param uuid: The uuid of the characteristic to read, as a string. 3747*9c5db199SXin Li @param address: The MAC address of the remote device. 3748*9c5db199SXin Li 3749*9c5db199SXin Li @returns: A b64 encoded version of a byte array containing the value 3750*9c5db199SXin Li if the uuid/address is in the object tree. 3751*9c5db199SXin Li None if the uuid/address was not found in the object tree, or 3752*9c5db199SXin Li if a DBus exception was raised by the read operation. 3753*9c5db199SXin Li 3754*9c5db199SXin Li """ 3755*9c5db199SXin Li char_obj = self._get_char_object(uuid, address) 3756*9c5db199SXin Li if char_obj is None: 3757*9c5db199SXin Li return None 3758*9c5db199SXin Li value = char_obj.ReadValue({}) 3759*9c5db199SXin Li return _dbus_byte_array_to_b64_string(value) 3760*9c5db199SXin Li 3761*9c5db199SXin Li @dbus_safe(None) 3762*9c5db199SXin Li def write_characteristic(self, uuid, address, value): 3763*9c5db199SXin Li """Performs a write operation on a gatt characteristic. 3764*9c5db199SXin Li 3765*9c5db199SXin Li Writes to a GATT characteristic on a remote device. Base64 endcoding is 3766*9c5db199SXin Li used for compatibility with the XML RPC interface. 3767*9c5db199SXin Li 3768*9c5db199SXin Li @param uuid: The uuid of the characteristic to write to, as a string. 3769*9c5db199SXin Li @param address: The MAC address of the remote device, as a string. 3770*9c5db199SXin Li @param value: A byte array containing the data to write. 3771*9c5db199SXin Li 3772*9c5db199SXin Li @returns: True if the write operation does not raise an exception. 3773*9c5db199SXin Li None if the uuid/address was not found in the object tree, or 3774*9c5db199SXin Li if a DBus exception was raised by the write operation. 3775*9c5db199SXin Li 3776*9c5db199SXin Li """ 3777*9c5db199SXin Li char_obj = self._get_char_object(uuid, address) 3778*9c5db199SXin Li if char_obj is None: 3779*9c5db199SXin Li return None 3780*9c5db199SXin Li dbus_value = _b64_string_to_dbus_byte_array(value) 3781*9c5db199SXin Li char_obj.WriteValue(dbus_value, {}) 3782*9c5db199SXin Li return True 3783*9c5db199SXin Li 3784*9c5db199SXin Li @dbus_safe(None) 3785*9c5db199SXin Li def exchange_messages(self, tx_object_path, rx_object_path, value): 3786*9c5db199SXin Li """Performs a write operation on a gatt characteristic and wait for 3787*9c5db199SXin Li the response on another characteristic. 3788*9c5db199SXin Li 3789*9c5db199SXin Li @param tx_object_path: the object path of the characteristic to write. 3790*9c5db199SXin Li @param rx_object_path: the object path of the characteristic to read. 3791*9c5db199SXin Li @param value: A byte array containing the data to write. 3792*9c5db199SXin Li 3793*9c5db199SXin Li @returns: The value of the characteristic to read from. 3794*9c5db199SXin Li None if the uuid/address was not found in the object tree, or 3795*9c5db199SXin Li if a DBus exception was raised by the write operation. 3796*9c5db199SXin Li 3797*9c5db199SXin Li """ 3798*9c5db199SXin Li tx_obj = self._get_gatt_characteristic_object(tx_object_path) 3799*9c5db199SXin Li 3800*9c5db199SXin Li if tx_obj is None: 3801*9c5db199SXin Li return None 3802*9c5db199SXin Li 3803*9c5db199SXin Li self._chrc_property = ''.encode('utf-8') 3804*9c5db199SXin Li 3805*9c5db199SXin Li value = str(value) 3806*9c5db199SXin Li proxy = self.bus.get(self.BLUEZ_SERVICE_NAME, rx_object_path)[self.DBUS_PROP_IFACE] 3807*9c5db199SXin Li self._signal_watch = proxy.PropertiesChanged.connect(self._property_changed) 3808*9c5db199SXin Li 3809*9c5db199SXin Li # Start timeout source 3810*9c5db199SXin Li self._timeout_start = time.time() 3811*9c5db199SXin Li self._timeout_early = False 3812*9c5db199SXin Li self._timeout_id = GObject.timeout_add( 3813*9c5db199SXin Li self.PROPERTY_UPDATE_CHECK_MILLI_SECS, 3814*9c5db199SXin Li self._property_wait_timeout) 3815*9c5db199SXin Li 3816*9c5db199SXin Li write_value = _b64_string_to_dbus_byte_array(value) 3817*9c5db199SXin Li tx_obj.WriteValue(write_value, {}) 3818*9c5db199SXin Li 3819*9c5db199SXin Li self._dbus_mainloop.run() 3820*9c5db199SXin Li 3821*9c5db199SXin Li return _dbus_byte_array_to_b64_string(self._chrc_property) 3822*9c5db199SXin Li 3823*9c5db199SXin Li def _property_changed(self, *args, **kwargs): 3824*9c5db199SXin Li """Handler for properties changed signal.""" 3825*9c5db199SXin Li # We don't cancel the timeout here due to a problem with the GLib 3826*9c5db199SXin Li # mainloop. See |_property_wait_timeout| for a full explanation. 3827*9c5db199SXin Li self._timeout_early = True 3828*9c5db199SXin Li self._signal_watch.disconnect() 3829*9c5db199SXin Li changed_prop = args 3830*9c5db199SXin Li 3831*9c5db199SXin Li logging.info(changed_prop) 3832*9c5db199SXin Li prop_dict = changed_prop[1] 3833*9c5db199SXin Li self._chrc_property = prop_dict['Value'] 3834*9c5db199SXin Li if self._dbus_mainloop.is_running(): 3835*9c5db199SXin Li self._dbus_mainloop.quit() 3836*9c5db199SXin Li 3837*9c5db199SXin Li def _property_wait_timeout(self): 3838*9c5db199SXin Li """Timeout handler when waiting for properties update signal.""" 3839*9c5db199SXin Li # Sometimes, GLib.Mainloop doesn't exit after |mainloop.quit()| is 3840*9c5db199SXin Li # called. This seems to occur only if a timeout source was active and 3841*9c5db199SXin Li # was removed before it had a chance to run. To mitigate this, we don't 3842*9c5db199SXin Li # cancel the timeout but mark an early completion instead. 3843*9c5db199SXin Li # See b/222364364#comment3 for more information. 3844*9c5db199SXin Li if not self._timeout_early and int( 3845*9c5db199SXin Li (time.time() - self._timeout_start) * 3846*9c5db199SXin Li 1000) <= self.PROPERTY_UPDATE_TIMEOUT_MILLI_SECS: 3847*9c5db199SXin Li # Returning True means this will be called again. 3848*9c5db199SXin Li return True 3849*9c5db199SXin Li 3850*9c5db199SXin Li self._signal_watch.disconnect() 3851*9c5db199SXin Li if self._dbus_mainloop.is_running(): 3852*9c5db199SXin Li logging.warning("quit main loop due to timeout") 3853*9c5db199SXin Li self._dbus_mainloop.quit() 3854*9c5db199SXin Li # Return false so that this method will not be called again. 3855*9c5db199SXin Li return False 3856*9c5db199SXin Li 3857*9c5db199SXin Li @dbus_safe(False) 3858*9c5db199SXin Li def _get_gatt_characteristic_object(self, object_path): 3859*9c5db199SXin Li return self.bus.get(self.BLUEZ_SERVICE_NAME, 3860*9c5db199SXin Li object_path)[self.BLUEZ_GATT_CHAR_IFACE] 3861*9c5db199SXin Li 3862*9c5db199SXin Li @dbus_safe(False) 3863*9c5db199SXin Li def start_notify(self, object_path, cccd_value): 3864*9c5db199SXin Li """Starts the notification session on the gatt characteristic. 3865*9c5db199SXin Li 3866*9c5db199SXin Li @param object_path: the object path of the characteristic. 3867*9c5db199SXin Li @param cccd_value: Possible CCCD values include 3868*9c5db199SXin Li 0x00 - inferred from the remote characteristic's properties 3869*9c5db199SXin Li 0x01 - notification 3870*9c5db199SXin Li 0x02 - indication 3871*9c5db199SXin Li 3872*9c5db199SXin Li @returns: True if the operation succeeds. 3873*9c5db199SXin Li False if the characteristic is not found, or 3874*9c5db199SXin Li if a DBus exception was raised by the operation. 3875*9c5db199SXin Li 3876*9c5db199SXin Li """ 3877*9c5db199SXin Li char_obj = self._get_gatt_characteristic_object(object_path) 3878*9c5db199SXin Li if char_obj is None: 3879*9c5db199SXin Li logging.error("characteristic not found: %s %s", object_path) 3880*9c5db199SXin Li return False 3881*9c5db199SXin Li 3882*9c5db199SXin Li try: 3883*9c5db199SXin Li char_obj.StartNotify(cccd_value) 3884*9c5db199SXin Li return True 3885*9c5db199SXin Li except Exception as e: 3886*9c5db199SXin Li logging.error('start_notify: %s', e) 3887*9c5db199SXin Li except: 3888*9c5db199SXin Li logging.error('start_notify: unexpected error') 3889*9c5db199SXin Li return False 3890*9c5db199SXin Li 3891*9c5db199SXin Li @dbus_safe(False) 3892*9c5db199SXin Li def stop_notify(self, object_path): 3893*9c5db199SXin Li """Stops the notification session on the gatt characteristic. 3894*9c5db199SXin Li 3895*9c5db199SXin Li @param object_path: the object path of the characteristic. 3896*9c5db199SXin Li 3897*9c5db199SXin Li @returns: True if the operation succeeds. 3898*9c5db199SXin Li False if the characteristic is not found, or 3899*9c5db199SXin Li if a DBus exception was raised by the operation. 3900*9c5db199SXin Li 3901*9c5db199SXin Li """ 3902*9c5db199SXin Li char_obj = self._get_gatt_characteristic_object(object_path) 3903*9c5db199SXin Li if char_obj is None: 3904*9c5db199SXin Li logging.error("characteristic not found: %s %s", object_path) 3905*9c5db199SXin Li return False 3906*9c5db199SXin Li 3907*9c5db199SXin Li try: 3908*9c5db199SXin Li char_obj.StopNotify() 3909*9c5db199SXin Li return True 3910*9c5db199SXin Li except Exception as e: 3911*9c5db199SXin Li logging.error('stop_notify: %s', e) 3912*9c5db199SXin Li except: 3913*9c5db199SXin Li logging.error('stop_notify: unexpected error') 3914*9c5db199SXin Li return False 3915*9c5db199SXin Li 3916*9c5db199SXin Li @dbus_safe(False) 3917*9c5db199SXin Li def is_notifying(self, object_path): 3918*9c5db199SXin Li """Is the GATT characteristic in a notifying session? 3919*9c5db199SXin Li 3920*9c5db199SXin Li @param object_path: the object path of the characteristic. 3921*9c5db199SXin Li 3922*9c5db199SXin Li @return True if it is in a notification session. False otherwise. 3923*9c5db199SXin Li 3924*9c5db199SXin Li """ 3925*9c5db199SXin Li 3926*9c5db199SXin Li return self.get_gatt_characteristic_property(object_path, 'Notifying') 3927*9c5db199SXin Li 3928*9c5db199SXin Li @dbus_safe(False) 3929*9c5db199SXin Li def is_characteristic_path_resolved(self, uuid, address): 3930*9c5db199SXin Li """Checks whether a characteristic is in the object tree. 3931*9c5db199SXin Li 3932*9c5db199SXin Li Checks whether a characteristic is curently found in the object tree. 3933*9c5db199SXin Li 3934*9c5db199SXin Li @param uuid: The uuid of the characteristic to search for. 3935*9c5db199SXin Li @param address: The MAC address of the device on which to search for 3936*9c5db199SXin Li the characteristic. 3937*9c5db199SXin Li 3938*9c5db199SXin Li @returns: True if the characteristic is found. 3939*9c5db199SXin Li False if the characteristic path is not found. 3940*9c5db199SXin Li 3941*9c5db199SXin Li """ 3942*9c5db199SXin Li return bool(self.get_characteristic_map(address).get(uuid)) 3943*9c5db199SXin Li 3944*9c5db199SXin Li @dbus_safe(False) 3945*9c5db199SXin Li def get_connection_info(self, address): 3946*9c5db199SXin Li """Get device connection info. 3947*9c5db199SXin Li 3948*9c5db199SXin Li @param address: The MAC address of the device. 3949*9c5db199SXin Li 3950*9c5db199SXin Li @returns: On success, a JSON-encoded tuple of: 3951*9c5db199SXin Li ( RSSI, transmit_power, max_transmit_power ) 3952*9c5db199SXin Li None otherwise. 3953*9c5db199SXin Li 3954*9c5db199SXin Li """ 3955*9c5db199SXin Li plugin_device = self._get_plugin_device_interface(address) 3956*9c5db199SXin Li if plugin_device is None: 3957*9c5db199SXin Li return None 3958*9c5db199SXin Li 3959*9c5db199SXin Li try: 3960*9c5db199SXin Li connection_info = plugin_device.GetConnInfo() 3961*9c5db199SXin Li return json.dumps(connection_info) 3962*9c5db199SXin Li except Exception as e: 3963*9c5db199SXin Li logging.error('get_connection_info: %s', e) 3964*9c5db199SXin Li except: 3965*9c5db199SXin Li logging.error('get_connection_info: unexpected error') 3966*9c5db199SXin Li return None 3967*9c5db199SXin Li 3968*9c5db199SXin Li def has_connection_info(self, address): 3969*9c5db199SXin Li """Checks whether the address has connection info. 3970*9c5db199SXin Li 3971*9c5db199SXin Li @param address: The MAC address of the device. 3972*9c5db199SXin Li @returns True if connection info can be found. 3973*9c5db199SXin Li """ 3974*9c5db199SXin Li return self.get_connection_info(address) is not None 3975*9c5db199SXin Li 3976*9c5db199SXin Li @dbus_safe(False) 3977*9c5db199SXin Li def set_le_connection_parameters(self, address, parameters): 3978*9c5db199SXin Li """Set the LE connection parameters. 3979*9c5db199SXin Li 3980*9c5db199SXin Li @param address: The MAC address of the device. 3981*9c5db199SXin Li @param parameters: The LE connection parameters to set. 3982*9c5db199SXin Li 3983*9c5db199SXin Li @return: True on success. False otherwise. 3984*9c5db199SXin Li 3985*9c5db199SXin Li """ 3986*9c5db199SXin Li plugin_device = self._get_plugin_device_interface(address) 3987*9c5db199SXin Li if plugin_device is None: 3988*9c5db199SXin Li return False 3989*9c5db199SXin Li 3990*9c5db199SXin Li return not self.dbus_method_with_handlers( 3991*9c5db199SXin Li plugin_device.SetLEConnectionParameters, 3992*9c5db199SXin Li # reply handler 3993*9c5db199SXin Li lambda: logging.info('set_le_connection_parameters: succeeded.' 3994*9c5db199SXin Li ), 3995*9c5db199SXin Li # error handler 3996*9c5db199SXin Li lambda error: logging. 3997*9c5db199SXin Li error('set_le_connection_parameters: failed: %s', str(error)), 3998*9c5db199SXin Li # other arguments 3999*9c5db199SXin Li parameters) 4000*9c5db199SXin Li 4001*9c5db199SXin Li @dbus_safe(False) 4002*9c5db199SXin Li def _get_plugin_device_interface(self, address): 4003*9c5db199SXin Li """Get the BlueZ Chromium device plugin interface. 4004*9c5db199SXin Li 4005*9c5db199SXin Li This interface can be used to issue dbus requests such as 4006*9c5db199SXin Li GetConnInfo and SetLEConnectionParameters. 4007*9c5db199SXin Li 4008*9c5db199SXin Li @param address: The MAC address of the device. 4009*9c5db199SXin Li 4010*9c5db199SXin Li @return: On success, the BlueZ Chromium device plugin interface 4011*9c5db199SXin Li None otherwise. 4012*9c5db199SXin Li 4013*9c5db199SXin Li """ 4014*9c5db199SXin Li path = self._get_device_path(address) 4015*9c5db199SXin Li if path is None: 4016*9c5db199SXin Li return None 4017*9c5db199SXin Li 4018*9c5db199SXin Li return self.bus.get(self.BLUEZ_SERVICE_NAME, 4019*9c5db199SXin Li path)[self.BLUEZ_PLUGIN_DEVICE_IFACE] 4020*9c5db199SXin Li 4021*9c5db199SXin Li @dbus_safe(False) 4022*9c5db199SXin Li def policy_get_service_allow_list(self): 4023*9c5db199SXin Li """Get the service allow list for enterprise policy. 4024*9c5db199SXin Li 4025*9c5db199SXin Li @returns: array of strings representing the allowed service UUIDs. 4026*9c5db199SXin Li """ 4027*9c5db199SXin Li uuids = unpack_if_variant( 4028*9c5db199SXin Li self._property_proxy.Get(self.BLUEZ_ADMIN_POLICY_STATUS_IFACE, 4029*9c5db199SXin Li 'ServiceAllowList')) 4030*9c5db199SXin Li logging.debug('ServiceAllowList: %s', uuids) 4031*9c5db199SXin Li return uuids 4032*9c5db199SXin Li 4033*9c5db199SXin Li @dbus_safe(False, return_error=True) 4034*9c5db199SXin Li def policy_set_service_allow_list(self, uuids): 4035*9c5db199SXin Li """Set the service allow list for enterprise policy. 4036*9c5db199SXin Li 4037*9c5db199SXin Li @param uuids: a string representing the uuids; e.g., "1234,0xabcd" or "" 4038*9c5db199SXin Li 4039*9c5db199SXin Li @returns: (True, '') on success, (False, '<error>') on failure. 4040*9c5db199SXin Li """ 4041*9c5db199SXin Li dbus_array = [] 4042*9c5db199SXin Li if bool(uuids.strip()): 4043*9c5db199SXin Li for uuid in uuids.split(','): 4044*9c5db199SXin Li dbus_array.append(uuid.strip()) 4045*9c5db199SXin Li 4046*9c5db199SXin Li logging.debug('policy_set_service_allow_list: %s', dbus_array) 4047*9c5db199SXin Li self._adapter[self.BLUEZ_ADMIN_POLICY_SET_IFACE].SetServiceAllowList( 4048*9c5db199SXin Li dbus_array) 4049*9c5db199SXin Li return (True, '') 4050*9c5db199SXin Li 4051*9c5db199SXin Li @dbus_safe(False, return_error=True) 4052*9c5db199SXin Li def policy_get_device_affected(self, device_address): 4053*9c5db199SXin Li """Check if the device is affected by enterprise policy. 4054*9c5db199SXin Li 4055*9c5db199SXin Li @param device_address: address of the device 4056*9c5db199SXin Li e.g. '6C:29:95:1A:D4:6F' 4057*9c5db199SXin Li 4058*9c5db199SXin Li @returns: True if the device is affected by the enterprise policy. 4059*9c5db199SXin Li False if not. None if the device is not found. 4060*9c5db199SXin Li """ 4061*9c5db199SXin Li device = self._find_device(device_address) 4062*9c5db199SXin Li if not device: 4063*9c5db199SXin Li logging.debug('Failed to find device %s', device_address) 4064*9c5db199SXin Li return None 4065*9c5db199SXin Li 4066*9c5db199SXin Li affected = unpack_if_variant(device[self.DBUS_PROP_IFACE].Get( 4067*9c5db199SXin Li self.BLUEZ_ADMIN_POLICY_STATUS_IFACE, 'AffectedByPolicy')) 4068*9c5db199SXin Li logging.debug('policy_get_device_affected(%s): %s', device_address, 4069*9c5db199SXin Li affected) 4070*9c5db199SXin Li return affected 4071*9c5db199SXin Li 4072*9c5db199SXin Li def cleanup(self): 4073*9c5db199SXin Li """Cleanup before exiting the client xmlrpc process.""" 4074*9c5db199SXin Li 4075*9c5db199SXin Li self.advmon_appmgr.destroy() 4076*9c5db199SXin Li 4077*9c5db199SXin Li def get_sysconfig(self): 4078*9c5db199SXin Li """Helper function to get default controller parameters 4079*9c5db199SXin Li 4080*9c5db199SXin Li @returns: dict of type to values, both are in string form, 4081*9c5db199SXin Li None if the operation read-sysconfig failed. 4082*9c5db199SXin Li """ 4083*9c5db199SXin Li tlv_re = re.compile('Type: (0x[0-9A-Fa-f]{4})\s+' 4084*9c5db199SXin Li 'Length: ([0-9A-Fa-f]{2})\s+' 4085*9c5db199SXin Li 'Value: ([0-9A-Fa-f]+)') 4086*9c5db199SXin Li 4087*9c5db199SXin Li cmd = 'btmgmt read-sysconfig' 4088*9c5db199SXin Li # btmgmt needs stdin, otherwise it won't output anything. 4089*9c5db199SXin Li # Please refer to 4090*9c5db199SXin Li # third_party/bluez/current/src/shared/shell.c:bt_shell_printf 4091*9c5db199SXin Li # for more information 4092*9c5db199SXin Li output = subprocess.check_output(cmd.split(), 4093*9c5db199SXin Li stdin=subprocess.PIPE, 4094*9c5db199SXin Li encoding='UTF-8') 4095*9c5db199SXin Li 4096*9c5db199SXin Li if output is None: 4097*9c5db199SXin Li logging.warning('Unable to retrieve output of %s', cmd) 4098*9c5db199SXin Li return None 4099*9c5db199SXin Li 4100*9c5db199SXin Li sysconfig = dict() 4101*9c5db199SXin Li 4102*9c5db199SXin Li for line in output.splitlines(): 4103*9c5db199SXin Li try: 4104*9c5db199SXin Li m = tlv_re.match(line) 4105*9c5db199SXin Li t, l, v = m.groups() 4106*9c5db199SXin Li sysconfig[int(t, 16)] = v 4107*9c5db199SXin Li except Exception as e: 4108*9c5db199SXin Li logging.warning('Unexpected error %s at "%s"', str(e), line) 4109*9c5db199SXin Li 4110*9c5db199SXin Li logging.debug("default controller parameters: %s", sysconfig) 4111*9c5db199SXin Li return sysconfig 4112*9c5db199SXin Li 4113*9c5db199SXin Li def _le_hex_to_int(self, le_hex): 4114*9c5db199SXin Li """Convert a little-endian hex-string to an unsigned integer. 4115*9c5db199SXin Li For example, _le_hex_to_int('0x0102') returns the same value as 4116*9c5db199SXin Li int('0201', 16) 4117*9c5db199SXin Li """ 4118*9c5db199SXin Li if le_hex is None: 4119*9c5db199SXin Li return None 4120*9c5db199SXin Li 4121*9c5db199SXin Li ba = bytearray.fromhex(le_hex) 4122*9c5db199SXin Li ba.reverse() 4123*9c5db199SXin Li return int(binascii.hexlify(ba), 16) 4124*9c5db199SXin Li 4125*9c5db199SXin Li def get_advmon_interleave_durations(self): 4126*9c5db199SXin Li """Get durations of allowlist scan and no filter scan 4127*9c5db199SXin Li 4128*9c5db199SXin Li @returns: a dict of {'allowlist': allowlist_duration, 4129*9c5db199SXin Li 'no filter': no_filter_duration}, 4130*9c5db199SXin Li or None if something went wrong 4131*9c5db199SXin Li """ 4132*9c5db199SXin Li 4133*9c5db199SXin Li sysconfig = self.get_sysconfig() 4134*9c5db199SXin Li 4135*9c5db199SXin Li if sysconfig is None: 4136*9c5db199SXin Li return None 4137*9c5db199SXin Li 4138*9c5db199SXin Li AllowlistScanDuration = self._le_hex_to_int(sysconfig.get( 4139*9c5db199SXin Li 0x001d, None)) 4140*9c5db199SXin Li NoFilterScanDuration = self._le_hex_to_int(sysconfig.get(0x001e, None)) 4141*9c5db199SXin Li 4142*9c5db199SXin Li return { 4143*9c5db199SXin Li 'allowlist': AllowlistScanDuration, 4144*9c5db199SXin Li 'no filter': NoFilterScanDuration 4145*9c5db199SXin Li } 4146*9c5db199SXin Li 4147*9c5db199SXin Li 4148*9c5db199SXin Liclass FlossFacadeLocal(BluetoothBaseFacadeLocal): 4149*9c5db199SXin Li """Exposes DUT methods called remotely during Bluetooth autotests for the 4150*9c5db199SXin Li Floss daemon. 4151*9c5db199SXin Li 4152*9c5db199SXin Li All instance methods of this object without a preceding '_' are exposed via 4153*9c5db199SXin Li an XML-RPC server. This is not a stateless handler object, which means that 4154*9c5db199SXin Li if you store state inside the delegate, that state will remain around for 4155*9c5db199SXin Li future calls. 4156*9c5db199SXin Li """ 4157*9c5db199SXin Li 4158*9c5db199SXin Li # Default to this adapter during init. We will initialize to the correct 4159*9c5db199SXin Li # default adapter after the manager client is initialized. 4160*9c5db199SXin Li DEFAULT_ADAPTER = 0 4161*9c5db199SXin Li 4162*9c5db199SXin Li # How long we wait for the adapter to come up after we start it 4163*9c5db199SXin Li ADAPTER_DAEMON_TIMEOUT_SEC = 20 4164*9c5db199SXin Li 4165*9c5db199SXin Li # Floss stops discovery after ~12s after starting. To improve discovery 4166*9c5db199SXin Li # chances in tests, we need to keep restarting discovery. This timeout 4167*9c5db199SXin Li # tracks how long an overall discovery session should be. 4168*9c5db199SXin Li DISCOVERY_TIMEOUT_SEC = 60 4169*9c5db199SXin Li 4170*9c5db199SXin Li class DiscoveryObserver(BluetoothCallbacks): 4171*9c5db199SXin Li """ Discovery observer that restarts discovery until a timeout. 4172*9c5db199SXin Li 4173*9c5db199SXin Li By default, the Floss stack stops discovery after ~12s. This can be an 4174*9c5db199SXin Li insufficient amount of time to discover a device, especially classic 4175*9c5db199SXin Li devices. To mimic Bluez, we have this observer restart discovery each 4176*9c5db199SXin Li time it is stopped up until a given timeout. 4177*9c5db199SXin Li """ 4178*9c5db199SXin Li 4179*9c5db199SXin Li def __init__(self, adapter_client, timeout_secs): 4180*9c5db199SXin Li """Constructor. 4181*9c5db199SXin Li 4182*9c5db199SXin Li @param adapter_client: Already initialized client instance. 4183*9c5db199SXin Li @param timeout_secs: How long to continue refreshing discovery. 4184*9c5db199SXin Li """ 4185*9c5db199SXin Li self.adapter_client = adapter_client 4186*9c5db199SXin Li self.deadline = datetime.now() + timedelta(seconds=timeout_secs) 4187*9c5db199SXin Li self.adapter_client.register_callback_observer( 4188*9c5db199SXin Li 'DiscoveryObserver', self) 4189*9c5db199SXin Li self.discovering = None 4190*9c5db199SXin Li 4191*9c5db199SXin Li def __del__(self): 4192*9c5db199SXin Li if self.adapter_client: 4193*9c5db199SXin Li self.cleanup() 4194*9c5db199SXin Li 4195*9c5db199SXin Li def cleanup(self): 4196*9c5db199SXin Li """Clean up after this observer.""" 4197*9c5db199SXin Li self.adapter_client.unregister_callback_observer( 4198*9c5db199SXin Li 'DiscoveryObserver', self) 4199*9c5db199SXin Li self.adapter_client = None 4200*9c5db199SXin Li 4201*9c5db199SXin Li def on_discovering_changed(self, discovering): 4202*9c5db199SXin Li """Discovering has changed.""" 4203*9c5db199SXin Li 4204*9c5db199SXin Li logging.info('Discovering changed to %s', discovering) 4205*9c5db199SXin Li 4206*9c5db199SXin Li prev = self.discovering 4207*9c5db199SXin Li self.discovering = discovering 4208*9c5db199SXin Li 4209*9c5db199SXin Li # No-op if this is the same notification sent multiple times 4210*9c5db199SXin Li if prev == discovering: 4211*9c5db199SXin Li pass 4212*9c5db199SXin Li # If discovering ended, check if the observer has timed out yet. If 4213*9c5db199SXin Li # not, re-start the discovery. 4214*9c5db199SXin Li if not discovering and datetime.now() < self.deadline: 4215*9c5db199SXin Li self.adapter_client.start_discovery( 4216*9c5db199SXin Li method_callback=self.start_discovery_rsp) 4217*9c5db199SXin Li 4218*9c5db199SXin Li def start_discovery_rsp(self, err, result): 4219*9c5db199SXin Li """Result to |adapter_client.start_discovery|.""" 4220*9c5db199SXin Li # Log any errors that may have occurred 4221*9c5db199SXin Li if err: 4222*9c5db199SXin Li logging.error('Error on start_discovery: %s', err) 4223*9c5db199SXin Li elif result: 4224*9c5db199SXin Li logging.error('Error on start_discovery: Status=%s', result) 4225*9c5db199SXin Li 4226*9c5db199SXin Li def __init__(self): 4227*9c5db199SXin Li # Init the BaseFacade first 4228*9c5db199SXin Li super(FlossFacadeLocal, self).__init__() 4229*9c5db199SXin Li 4230*9c5db199SXin Li # Start mainloop thread in background. This will also initialize a few 4231*9c5db199SXin Li # other variables (self.bus, self.mainloop, self.event_context) that may 4232*9c5db199SXin Li # be necessary for proper operation. 4233*9c5db199SXin Li self.mainloop_quit = threading.Event() 4234*9c5db199SXin Li self.mainloop_ready = threading.Event() 4235*9c5db199SXin Li self.thread = threading.Thread( 4236*9c5db199SXin Li name=GLIB_THREAD_NAME, 4237*9c5db199SXin Li target=FlossFacadeLocal.mainloop_thread, 4238*9c5db199SXin Li args=(self, )) 4239*9c5db199SXin Li self.thread.start() 4240*9c5db199SXin Li 4241*9c5db199SXin Li # Wait for mainloop to be ready 4242*9c5db199SXin Li if not self.mainloop_ready.wait(timeout=5): 4243*9c5db199SXin Li raise Exception('Unable to initialize GLib mainloop') 4244*9c5db199SXin Li 4245*9c5db199SXin Li # Always initialize the manager client since there is a single instance. 4246*9c5db199SXin Li self.manager_client = FlossManagerClient(self.bus) 4247*9c5db199SXin Li self.adapter_client = FlossAdapterClient(self.bus, 4248*9c5db199SXin Li self.DEFAULT_ADAPTER) 4249*9c5db199SXin Li 4250*9c5db199SXin Li self.is_clean = False 4251*9c5db199SXin Li 4252*9c5db199SXin Li # Discovery needs to last longer than the default 12s. Keep an observer 4253*9c5db199SXin Li # that re-enables discovery up to some timeout. 4254*9c5db199SXin Li self.discovery_observer = None 4255*9c5db199SXin Li 4256*9c5db199SXin Li # Cache some mock properties for testing. These may be properties that 4257*9c5db199SXin Li # are required in bluez but don't carry over well into Floss. 4258*9c5db199SXin Li self.mock_properties = {} 4259*9c5db199SXin Li 4260*9c5db199SXin Li def __del__(self): 4261*9c5db199SXin Li if not self.is_clean: 4262*9c5db199SXin Li self.cleanup() 4263*9c5db199SXin Li 4264*9c5db199SXin Li def cleanup(self): 4265*9c5db199SXin Li """Clean up the mainloop thread.""" 4266*9c5db199SXin Li self.mainloop_quit.set() 4267*9c5db199SXin Li self.mainloop.quit() 4268*9c5db199SXin Li self.is_clean = True 4269*9c5db199SXin Li 4270*9c5db199SXin Li @staticmethod 4271*9c5db199SXin Li def mainloop_thread(self): 4272*9c5db199SXin Li """Runs GLib mainloop until we signal that we should quit.""" 4273*9c5db199SXin Li 4274*9c5db199SXin Li # Set up mainloop. All subsequent buses and connections will use this 4275*9c5db199SXin Li # mainloop. We also use a separate main context to avoid multithreading 4276*9c5db199SXin Li # issues. 4277*9c5db199SXin Li #self.event_context = GLib.MainContext() 4278*9c5db199SXin Li #self.mainloop = GLib.MainLoop(context=self.event_context) 4279*9c5db199SXin Li GLib.threads_init() 4280*9c5db199SXin Li self.mainloop = GLib.MainLoop() 4281*9c5db199SXin Li 4282*9c5db199SXin Li # Set up bus connection 4283*9c5db199SXin Li self.bus = pydbus.SystemBus() 4284*9c5db199SXin Li 4285*9c5db199SXin Li # Set thread ready 4286*9c5db199SXin Li self.mainloop_ready.set() 4287*9c5db199SXin Li 4288*9c5db199SXin Li while not self.mainloop_quit.is_set(): 4289*9c5db199SXin Li self.mainloop.run() 4290*9c5db199SXin Li 4291*9c5db199SXin Li def get_floss_enabled(self): 4292*9c5db199SXin Li """Is Floss enabled right now? 4293*9c5db199SXin Li 4294*9c5db199SXin Li Returns: 4295*9c5db199SXin Li True if Floss is enabled, False if Bluez is enabled. 4296*9c5db199SXin Li """ 4297*9c5db199SXin Li return self.manager_client.get_floss_enabled() 4298*9c5db199SXin Li 4299*9c5db199SXin Li def set_floss_enabled(self, enabled): 4300*9c5db199SXin Li """Enable or disable Floss.""" 4301*9c5db199SXin Li self.manager_client.set_floss_enabled(enabled) 4302*9c5db199SXin Li 4303*9c5db199SXin Li def start_bluetoothd(self): 4304*9c5db199SXin Li """Starts Floss. This includes enabling the adapter. 4305*9c5db199SXin Li 4306*9c5db199SXin Li Returns: 4307*9c5db199SXin Li True if default adapter is enabled successfully. False otherwise. 4308*9c5db199SXin Li """ 4309*9c5db199SXin Li # Start manager and enable Floss 4310*9c5db199SXin Li if not self.configure_floss(enabled=True): 4311*9c5db199SXin Li return False 4312*9c5db199SXin Li 4313*9c5db199SXin Li # Restarts the default adapter 4314*9c5db199SXin Li if not self.reset_on(): 4315*9c5db199SXin Li return False 4316*9c5db199SXin Li 4317*9c5db199SXin Li # If we need to wait for any other interfaces, add below here: 4318*9c5db199SXin Li # ------------------------------------------------------------ 4319*9c5db199SXin Li 4320*9c5db199SXin Li return True 4321*9c5db199SXin Li 4322*9c5db199SXin Li def stop_bluetoothd(self): 4323*9c5db199SXin Li """Stops Floss. This includes disabling btmanagerd. 4324*9c5db199SXin Li 4325*9c5db199SXin Li Returns: 4326*9c5db199SXin Li True if adapter daemon and manager daemon are both off. 4327*9c5db199SXin Li """ 4328*9c5db199SXin Li # First power off the adapter 4329*9c5db199SXin Li if not self.reset_off(): 4330*9c5db199SXin Li logging.warn('Failed to stop btadapterd') 4331*9c5db199SXin Li return False 4332*9c5db199SXin Li 4333*9c5db199SXin Li if not UpstartClient.stop(self.MANAGER_JOB): 4334*9c5db199SXin Li logging.warn('Failed to stop btmanagerd') 4335*9c5db199SXin Li return False 4336*9c5db199SXin Li 4337*9c5db199SXin Li def _daemon_stopped(): 4338*9c5db199SXin Li return all([ 4339*9c5db199SXin Li not self.manager_client.has_proxy(), 4340*9c5db199SXin Li not self.adapter_client.has_proxy(), 4341*9c5db199SXin Li ]) 4342*9c5db199SXin Li 4343*9c5db199SXin Li try: 4344*9c5db199SXin Li utils.poll_for_condition(condition=_daemon_stopped, 4345*9c5db199SXin Li desc='Bluetooth daemons have stopped', 4346*9c5db199SXin Li timeout=self.DAEMON_TIMEOUT_SEC) 4347*9c5db199SXin Li daemon_stopped = True 4348*9c5db199SXin Li except Exception as e: 4349*9c5db199SXin Li logging.error('timeout: error stopping floss daemons: %s', e) 4350*9c5db199SXin Li daemon_stopped = False 4351*9c5db199SXin Li 4352*9c5db199SXin Li return daemon_stopped 4353*9c5db199SXin Li 4354*9c5db199SXin Li def restart_cras(self): 4355*9c5db199SXin Li """Restarts the cras daemon.""" 4356*9c5db199SXin Li self._restart_cras(enable_floss=True) 4357*9c5db199SXin Li 4358*9c5db199SXin Li def is_bluetoothd_proxy_valid(self): 4359*9c5db199SXin Li """Checks whether the proxy objects for Floss are ok.""" 4360*9c5db199SXin Li return all([ 4361*9c5db199SXin Li self.manager_client.has_proxy(), 4362*9c5db199SXin Li self.adapter_client.has_proxy() 4363*9c5db199SXin Li ]) 4364*9c5db199SXin Li 4365*9c5db199SXin Li def is_bluetoothd_running(self): 4366*9c5db199SXin Li """Checks whether Floss daemon is running.""" 4367*9c5db199SXin Li # This api doesn't enforce that the adapter is powered so we only check 4368*9c5db199SXin Li # that the manager proxy is up. 4369*9c5db199SXin Li return self.manager_client.has_proxy() 4370*9c5db199SXin Li 4371*9c5db199SXin Li def has_adapter(self): 4372*9c5db199SXin Li """Checks whether an adapter exists.""" 4373*9c5db199SXin Li return len(self.manager_client.get_available_adapters()) > 0 4374*9c5db199SXin Li 4375*9c5db199SXin Li def set_debug_log_levels(self, bluez_vb, kernel_vb): 4376*9c5db199SXin Li """Enables verbose logging.""" 4377*9c5db199SXin Li # TODO(abps) - This will be necessary for Floss but may not need to 4378*9c5db199SXin Li # touch the kernel. This needs to be implemented at the 4379*9c5db199SXin Li # daemon level still. 4380*9c5db199SXin Li return False 4381*9c5db199SXin Li 4382*9c5db199SXin Li def start_discovery(self): 4383*9c5db199SXin Li """Start discovery of remote devices.""" 4384*9c5db199SXin Li if not self.adapter_client.has_proxy(): 4385*9c5db199SXin Li return (False, 'Adapter not found') 4386*9c5db199SXin Li 4387*9c5db199SXin Li if self.discovery_observer: 4388*9c5db199SXin Li self.discovery_observer.cleanup() 4389*9c5db199SXin Li 4390*9c5db199SXin Li self.discovery_observer = self.DiscoveryObserver( 4391*9c5db199SXin Li self.adapter_client, self.DISCOVERY_TIMEOUT_SEC) 4392*9c5db199SXin Li return (self.adapter_client.start_discovery(), '') 4393*9c5db199SXin Li 4394*9c5db199SXin Li def stop_discovery(self): 4395*9c5db199SXin Li """Stop discovery of remote deviecs.""" 4396*9c5db199SXin Li if not self.adapter_client.has_proxy(): 4397*9c5db199SXin Li return (False, 'Adapter not found') 4398*9c5db199SXin Li 4399*9c5db199SXin Li if self.discovery_observer: 4400*9c5db199SXin Li self.discovery_observer.cleanup() 4401*9c5db199SXin Li self.discovery_observer = None 4402*9c5db199SXin Li 4403*9c5db199SXin Li return (self.adapter_client.stop_discovery(), '') 4404*9c5db199SXin Li 4405*9c5db199SXin Li def is_discovering(self): 4406*9c5db199SXin Li """Check if adapter is discovering.""" 4407*9c5db199SXin Li return self.adapter_client.is_discovering() 4408*9c5db199SXin Li 4409*9c5db199SXin Li def is_powered_on(self): 4410*9c5db199SXin Li """Gets whether the default adapter is enabled.""" 4411*9c5db199SXin Li default_adapter = self.manager_client.get_default_adapter() 4412*9c5db199SXin Li return self.manager_client.get_adapter_enabled(default_adapter) 4413*9c5db199SXin Li 4414*9c5db199SXin Li def set_powered(self, powered): 4415*9c5db199SXin Li """Sets the default adapter's enabled state.""" 4416*9c5db199SXin Li default_adapter = self.manager_client.get_default_adapter() 4417*9c5db199SXin Li 4418*9c5db199SXin Li if powered and not self.manager_client.has_default_adapter(): 4419*9c5db199SXin Li logging.warning('set_powered: Default adapter not available.') 4420*9c5db199SXin Li return False 4421*9c5db199SXin Li 4422*9c5db199SXin Li if powered: 4423*9c5db199SXin Li self.manager_client.start(default_adapter) 4424*9c5db199SXin Li else: 4425*9c5db199SXin Li self.manager_client.stop(default_adapter) 4426*9c5db199SXin Li 4427*9c5db199SXin Li return True 4428*9c5db199SXin Li 4429*9c5db199SXin Li def reset_on(self): 4430*9c5db199SXin Li """Reset the default adapter into an ON state.""" 4431*9c5db199SXin Li return self.do_reset(True) 4432*9c5db199SXin Li 4433*9c5db199SXin Li def reset_off(self): 4434*9c5db199SXin Li """Reset the default adapter into an OFF state.""" 4435*9c5db199SXin Li return self.do_reset(False) 4436*9c5db199SXin Li 4437*9c5db199SXin Li def do_reset(self, power_on): 4438*9c5db199SXin Li """Resets the default adapter.""" 4439*9c5db199SXin Li # Start manager and enable Floss if not already up 4440*9c5db199SXin Li if not self.configure_floss(enabled=True): 4441*9c5db199SXin Li return False 4442*9c5db199SXin Li 4443*9c5db199SXin Li default_adapter = self.manager_client.get_default_adapter() 4444*9c5db199SXin Li 4445*9c5db199SXin Li def _is_adapter_down(client): 4446*9c5db199SXin Li return lambda: not client.has_proxy() 4447*9c5db199SXin Li 4448*9c5db199SXin Li def _is_adapter_ready(client): 4449*9c5db199SXin Li return lambda: client.has_proxy() and client.get_address() 4450*9c5db199SXin Li 4451*9c5db199SXin Li self.manager_client.stop(default_adapter) 4452*9c5db199SXin Li try: 4453*9c5db199SXin Li condition = _is_adapter_down(self.adapter_client) 4454*9c5db199SXin Li utils.poll_for_condition(condition=condition, 4455*9c5db199SXin Li desc='Wait for adapter stop', 4456*9c5db199SXin Li sleep_interval=0.5, 4457*9c5db199SXin Li timeout=self.ADAPTER_DAEMON_TIMEOUT_SEC) 4458*9c5db199SXin Li except Exception as e: 4459*9c5db199SXin Li logging.error('timeout: error stopping adapter daemon: %s', e) 4460*9c5db199SXin Li logging.error(traceback.format_exc()) 4461*9c5db199SXin Li return False 4462*9c5db199SXin Li 4463*9c5db199SXin Li if not power_on: 4464*9c5db199SXin Li logging.debug('do_reset: Completed with power_on=False') 4465*9c5db199SXin Li return True 4466*9c5db199SXin Li 4467*9c5db199SXin Li # Start the client again 4468*9c5db199SXin Li self.manager_client.start(default_adapter) 4469*9c5db199SXin Li self.adapter_client = FlossAdapterClient(self.bus, default_adapter) 4470*9c5db199SXin Li 4471*9c5db199SXin Li try: 4472*9c5db199SXin Li condition = _is_adapter_ready(self.adapter_client) 4473*9c5db199SXin Li utils.poll_for_condition(condition=condition, 4474*9c5db199SXin Li desc='Wait for adapter start', 4475*9c5db199SXin Li sleep_interval=0.5, 4476*9c5db199SXin Li timeout=self.ADAPTER_DAEMON_TIMEOUT_SEC) 4477*9c5db199SXin Li except Exception as e: 4478*9c5db199SXin Li logging.error('timeout: error starting adapter daemon: %s', e) 4479*9c5db199SXin Li logging.error(traceback.format_exc()) 4480*9c5db199SXin Li return False 4481*9c5db199SXin Li 4482*9c5db199SXin Li # We need to observe callbacks for proper operation. 4483*9c5db199SXin Li if not self.adapter_client.register_callbacks(): 4484*9c5db199SXin Li logging.error('adapter_client: Failed to register callbacks') 4485*9c5db199SXin Li return False 4486*9c5db199SXin Li 4487*9c5db199SXin Li logging.debug('do_reset: Completed with power_on=True') 4488*9c5db199SXin Li return True 4489*9c5db199SXin Li 4490*9c5db199SXin Li def policy_get_service_allow_list(self): 4491*9c5db199SXin Li """Gets the service allow list for enterprise policy.""" 4492*9c5db199SXin Li # TODO(abps) - Actually implement this 4493*9c5db199SXin Li return [] 4494*9c5db199SXin Li 4495*9c5db199SXin Li def policy_set_service_allow_list(self, uuids): 4496*9c5db199SXin Li """Sets the service allow list for enterprise policy.""" 4497*9c5db199SXin Li # TODO(abps) - Actually implement this 4498*9c5db199SXin Li return (True, '') 4499*9c5db199SXin Li 4500*9c5db199SXin Li def get_address(self): 4501*9c5db199SXin Li """Gets the default adapter address.""" 4502*9c5db199SXin Li return self.adapter_client.get_address() 4503*9c5db199SXin Li 4504*9c5db199SXin Li def has_device(self, address): 4505*9c5db199SXin Li """Checks if adapter knows the device.""" 4506*9c5db199SXin Li return self.adapter_client.has_device(address) 4507*9c5db199SXin Li 4508*9c5db199SXin Li def remove_device_object(self, address): 4509*9c5db199SXin Li """Removes a known device object.""" 4510*9c5db199SXin Li return self.adapter_client.forget_device(address) 4511*9c5db199SXin Li 4512*9c5db199SXin Li def connect_device(self, address): 4513*9c5db199SXin Li """Connect a specific address.""" 4514*9c5db199SXin Li return self.adapter_client.connect_all_enabled_profiles(address) 4515*9c5db199SXin Li 4516*9c5db199SXin Li def disconnect_device(self, address): 4517*9c5db199SXin Li """Disconnect a specific address.""" 4518*9c5db199SXin Li return self.adapter_client.disconnect_all_enabled_profiles(address) 4519*9c5db199SXin Li 4520*9c5db199SXin Li def get_device_property(self, address, prop_name): 4521*9c5db199SXin Li """Read a property from a remote device. 4522*9c5db199SXin Li 4523*9c5db199SXin Li @param address: Address of the device to query 4524*9c5db199SXin Li @param prop_name: Property to be queried 4525*9c5db199SXin Li 4526*9c5db199SXin Li @return Base64 encoded json if property exists or None. 4527*9c5db199SXin Li """ 4528*9c5db199SXin Li prop_val = None 4529*9c5db199SXin Li 4530*9c5db199SXin Li if self.adapter_client.has_device(address): 4531*9c5db199SXin Li prop_val = self.adapter_client.get_remote_property( 4532*9c5db199SXin Li address, prop_name) 4533*9c5db199SXin Li 4534*9c5db199SXin Li return self._encode_base64_json(prop_val) 4535*9c5db199SXin Li 4536*9c5db199SXin Li def get_pairable(self): 4537*9c5db199SXin Li """Gets whether the default adapter is pairable. 4538*9c5db199SXin Li 4539*9c5db199SXin Li @return True if default adapter is pairable. 4540*9c5db199SXin Li """ 4541*9c5db199SXin Li # TODO(abps) - Control pairable setting on adapter 4542*9c5db199SXin Li return self.mock_properties.get('Pairable', False) 4543*9c5db199SXin Li 4544*9c5db199SXin Li def set_pairable(self, pairable): 4545*9c5db199SXin Li """Sets default adapter as pairable. 4546*9c5db199SXin Li 4547*9c5db199SXin Li @param pairable: Control pairable property of the adapter. 4548*9c5db199SXin Li 4549*9c5db199SXin Li @return True on success. 4550*9c5db199SXin Li """ 4551*9c5db199SXin Li # TODO(abps) - Control pairable setting on adapter 4552*9c5db199SXin Li self.mock_properties['Pairable'] = pairable 4553*9c5db199SXin Li return True 4554*9c5db199SXin Li 4555*9c5db199SXin Li def pair_legacy_device(self, address, pin, trusted, timeout=60): 4556*9c5db199SXin Li """Pairs a peer device. 4557*9c5db199SXin Li 4558*9c5db199SXin Li @param address: BT address of the peer device. 4559*9c5db199SXin Li @param pin: What pin to use for pairing. 4560*9c5db199SXin Li @param trusted: Unused by Floss. 4561*9c5db199SXin Li @param timeout: How long to wait for pairing to complete. 4562*9c5db199SXin Li """ 4563*9c5db199SXin Li 4564*9c5db199SXin Li class PairingObserver(BluetoothCallbacks, 4565*9c5db199SXin Li BluetoothConnectionCallbacks): 4566*9c5db199SXin Li """Observer of certain callbacks for pairing.""" 4567*9c5db199SXin Li 4568*9c5db199SXin Li def __init__(self, adapter_client, done_event, address, pin): 4569*9c5db199SXin Li self.adapter_client = adapter_client 4570*9c5db199SXin Li self.adapter_client.register_callback_observer( 4571*9c5db199SXin Li 'PairingObserver' + address, self) 4572*9c5db199SXin Li 4573*9c5db199SXin Li # Event to trigger once we are paired and connected. 4574*9c5db199SXin Li self.done_event = done_event 4575*9c5db199SXin Li self.address = address 4576*9c5db199SXin Li self.pin = pin 4577*9c5db199SXin Li self.bond_state = BondState.NOT_BONDED 4578*9c5db199SXin Li self.connected = self.adapter_client.is_connected(address) 4579*9c5db199SXin Li 4580*9c5db199SXin Li def __del__(self): 4581*9c5db199SXin Li """Destructor""" 4582*9c5db199SXin Li if self.adapter_client: 4583*9c5db199SXin Li self.cleanup() 4584*9c5db199SXin Li 4585*9c5db199SXin Li def cleanup(self): 4586*9c5db199SXin Li """Clean up after this observer.""" 4587*9c5db199SXin Li self.adapter_client.unregister_callback_observer( 4588*9c5db199SXin Li 'PairingObserver' + address, self) 4589*9c5db199SXin Li self.adapter_client = None 4590*9c5db199SXin Li 4591*9c5db199SXin Li def on_bond_state_changed(self, status, device_address, state): 4592*9c5db199SXin Li """Handle bond state change.""" 4593*9c5db199SXin Li logging.info('[%s] bond state=%d', device_address, state) 4594*9c5db199SXin Li 4595*9c5db199SXin Li if device_address != self.address: 4596*9c5db199SXin Li return 4597*9c5db199SXin Li 4598*9c5db199SXin Li # If we have a non-zero status, bonding failed in some way. 4599*9c5db199SXin Li # Report it and unblock the main thread. 4600*9c5db199SXin Li if status != 0: 4601*9c5db199SXin Li logging.error('[%s] failed to bond. Status=%d, State=%d', 4602*9c5db199SXin Li device_address, status, state) 4603*9c5db199SXin Li self.done_event.set() 4604*9c5db199SXin Li return 4605*9c5db199SXin Li 4606*9c5db199SXin Li self.bond_state = state 4607*9c5db199SXin Li logging.info('[%s] bond state=%d', device_address, state) 4608*9c5db199SXin Li 4609*9c5db199SXin Li # We've completed bonding. Make sure to connect 4610*9c5db199SXin Li if state == BondState.BONDED: 4611*9c5db199SXin Li # If not connected, connect profiles and wait for connected 4612*9c5db199SXin Li # callback. Else, unblock the main thread. 4613*9c5db199SXin Li if not self.connected: 4614*9c5db199SXin Li if not self.adapter_client.connect_all_enabled_profiles( 4615*9c5db199SXin Li self.address): 4616*9c5db199SXin Li logging.error( 4617*9c5db199SXin Li '[%s] failed on connect_all_enabled_profiles', 4618*9c5db199SXin Li self.address) 4619*9c5db199SXin Li self.done_event.set() 4620*9c5db199SXin Li else: 4621*9c5db199SXin Li self.done_event.set() 4622*9c5db199SXin Li 4623*9c5db199SXin Li def on_ssp_request(self, remote_device, class_of_device, variant, 4624*9c5db199SXin Li passkey): 4625*9c5db199SXin Li """Handle SSP request.""" 4626*9c5db199SXin Li (remote_address, remote_name) = remote_device 4627*9c5db199SXin Li 4628*9c5db199SXin Li if remote_address != self.address: 4629*9c5db199SXin Li return 4630*9c5db199SXin Li 4631*9c5db199SXin Li logging.info('Ssp: [%s: %s]: Class=%d, Variant=%d, Passkey=%d', 4632*9c5db199SXin Li remote_address, remote_name, class_of_device, 4633*9c5db199SXin Li variant, passkey) 4634*9c5db199SXin Li 4635*9c5db199SXin Li if variant == int(SspVariant.CONSENT): 4636*9c5db199SXin Li self.adapter_client.set_pairing_confirmation( 4637*9c5db199SXin Li remote_address, 4638*9c5db199SXin Li True, 4639*9c5db199SXin Li method_callback=self.on_set_pairing_confirmation) 4640*9c5db199SXin Li 4641*9c5db199SXin Li logging.info('Exited ssp request.') 4642*9c5db199SXin Li 4643*9c5db199SXin Li def on_set_pairing_confirmation(self, err, result): 4644*9c5db199SXin Li """Handle async method result from set pairing confirmation.""" 4645*9c5db199SXin Li if err or not result: 4646*9c5db199SXin Li logging.error( 4647*9c5db199SXin Li 'Pairing confirmation failed: err[%s], result[%s]', 4648*9c5db199SXin Li err, result) 4649*9c5db199SXin Li self.done_event.set() 4650*9c5db199SXin Li 4651*9c5db199SXin Li def on_device_connected(self, remote_device): 4652*9c5db199SXin Li """Handle device connection.""" 4653*9c5db199SXin Li (remote_address, _) = remote_device 4654*9c5db199SXin Li 4655*9c5db199SXin Li logging.info('[%s] connected', remote_address) 4656*9c5db199SXin Li 4657*9c5db199SXin Li if remote_address != self.address: 4658*9c5db199SXin Li return 4659*9c5db199SXin Li 4660*9c5db199SXin Li self.connected = True 4661*9c5db199SXin Li 4662*9c5db199SXin Li # If we're already bonded, unblock the main thread. 4663*9c5db199SXin Li if self.bond_state == BondState.BONDED: 4664*9c5db199SXin Li self.done_event.set() 4665*9c5db199SXin Li 4666*9c5db199SXin Li # Start pairing process in main thread 4667*9c5db199SXin Li 4668*9c5db199SXin Li done_evt = threading.Event() 4669*9c5db199SXin Li 4670*9c5db199SXin Li # First we need an observer that watches for callbacks 4671*9c5db199SXin Li pairing_observer = PairingObserver(self.adapter_client, done_evt, 4672*9c5db199SXin Li address, pin) 4673*9c5db199SXin Li 4674*9c5db199SXin Li # Pair and connect. If either action fails, mark the done event so that 4675*9c5db199SXin Li # we fall through without blocking. 4676*9c5db199SXin Li if not self.device_is_paired(address): 4677*9c5db199SXin Li if not self.adapter_client.create_bond(address, Transport.AUTO): 4678*9c5db199SXin Li done_evt.set() 4679*9c5db199SXin Li elif not self.device_is_connected(address): 4680*9c5db199SXin Li if not self.adapter_client.connect_all_enabled_profiles(address): 4681*9c5db199SXin Li done_evt.set() 4682*9c5db199SXin Li 4683*9c5db199SXin Li done_evt.wait(timeout=timeout) 4684*9c5db199SXin Li if not done_evt.is_set(): 4685*9c5db199SXin Li logging.error('Timed out waiting for pairing to complete.') 4686*9c5db199SXin Li 4687*9c5db199SXin Li is_paired = self.device_is_paired(address) 4688*9c5db199SXin Li is_connected = self.device_is_connected(address) 4689*9c5db199SXin Li 4690*9c5db199SXin Li # If pairing and hci connection is complete, also trigger all profile 4691*9c5db199SXin Li # connections here. This is necessary because device connection doesn't 4692*9c5db199SXin Li # always imply profile connection. 4693*9c5db199SXin Li if is_paired and is_connected: 4694*9c5db199SXin Li self.adapter_client.connect_all_enabled_profiles(address) 4695*9c5db199SXin Li 4696*9c5db199SXin Li logging.info('Pairing result: paired(%s) connected(%s)', is_paired, 4697*9c5db199SXin Li is_connected) 4698*9c5db199SXin Li 4699*9c5db199SXin Li return is_paired and is_connected 4700*9c5db199SXin Li 4701*9c5db199SXin Li def device_is_connected(self, address): 4702*9c5db199SXin Li """Checks whether a device is connected. 4703*9c5db199SXin Li 4704*9c5db199SXin Li @param address: BT address of peer device. 4705*9c5db199SXin Li @return True if connected. 4706*9c5db199SXin Li """ 4707*9c5db199SXin Li return self.adapter_client.is_connected(address) 4708*9c5db199SXin Li 4709*9c5db199SXin Li def has_connection_info(self, address): 4710*9c5db199SXin Li """Same as |device_is_connected| on Floss. 4711*9c5db199SXin Li 4712*9c5db199SXin Li Bluez has a separate ConnectionInfo tuple that is read from the kernel 4713*9c5db199SXin Li but Floss doesn't have this. We have this function simply for 4714*9c5db199SXin Li compatibility. 4715*9c5db199SXin Li 4716*9c5db199SXin Li @param address: BT address of peer device. 4717*9c5db199SXin Li @return True if connected. 4718*9c5db199SXin Li """ 4719*9c5db199SXin Li return self.device_is_connected(address) 4720*9c5db199SXin Li 4721*9c5db199SXin Li def get_num_connected_devices(self): 4722*9c5db199SXin Li """ Return number of remote devices currently connected to the DUT. 4723*9c5db199SXin Li 4724*9c5db199SXin Li @returns: The number of devices known to bluez with the Connected 4725*9c5db199SXin Li property active 4726*9c5db199SXin Li """ 4727*9c5db199SXin Li return self.adapter_client.get_connected_devices_count() 4728*9c5db199SXin Li 4729*9c5db199SXin Li def device_is_paired(self, address): 4730*9c5db199SXin Li """Checks if a device is paired. 4731*9c5db199SXin Li 4732*9c5db199SXin Li @param address: address of the device. 4733*9c5db199SXin Li @returns: True if device is paired. False otherwise. 4734*9c5db199SXin Li """ 4735*9c5db199SXin Li return self.adapter_client.is_bonded(address) 4736*9c5db199SXin Li 4737*9c5db199SXin Li def is_discoverable(self): 4738*9c5db199SXin Li """Return whether the adapter is currently discoverable.""" 4739*9c5db199SXin Li return self.adapter_client.get_property('Discoverable') 4740*9c5db199SXin Li 4741*9c5db199SXin Li def set_discoverable(self, discoverable, duration=60): 4742*9c5db199SXin Li """Sets the adapter as discoverable for given duration in seconds.""" 4743*9c5db199SXin Li return self.adapter_client.set_property('Discoverable', discoverable, 4744*9c5db199SXin Li duration) 4745*9c5db199SXin Li 4746*9c5db199SXin Li def get_supported_capabilities(self): 4747*9c5db199SXin Li """" Get supported capabilities of the adapter.""" 4748*9c5db199SXin Li return (json.dumps({}), 'Not yet implemented') 4749