1# Copyright 2020 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4"""Facade to access the bluetooth-related functionality.""" 5 6from __future__ import absolute_import 7from __future__ import division 8from __future__ import print_function 9 10import base64 11import collections 12from datetime import datetime 13import dbus 14import dbus.mainloop.glib 15import dbus.service 16import glob 17import gobject 18import json 19import logging 20import logging.handlers 21import os 22import re 23import subprocess 24import functools 25import time 26 27import common 28from autotest_lib.client.bin import utils 29from autotest_lib.client.common_lib.cros.bluetooth import bluetooth_socket 30from autotest_lib.client.common_lib import error 31from autotest_lib.client.cros import dbus_util 32from autotest_lib.client.cros.udev_helpers import UdevadmInfo, UdevadmTrigger 33from autotest_lib.client.cros import xmlrpc_server 34from autotest_lib.client.cros.audio import (audio_test_data as 35 audio_test_data_module) 36from autotest_lib.client.cros.audio import check_quality 37from autotest_lib.client.cros.audio import cras_utils 38from autotest_lib.client.cros.audio.sox_utils import ( 39 convert_format, convert_raw_file, get_file_length, 40 trim_silence_from_wav_file) 41from autotest_lib.client.cros.bluetooth import advertisement 42from autotest_lib.client.cros.bluetooth import adv_monitor_helper 43from autotest_lib.client.cros.bluetooth import output_recorder 44from autotest_lib.client.cros.power import sys_power 45import six 46from six.moves import map 47from six.moves import range 48 49CheckQualityArgsClass = collections.namedtuple( 50 'args_type', ['filename', 'rate', 'channel', 'bit_width']) 51 52 53def _dbus_byte_array_to_b64_string(dbus_byte_array): 54 """Base64 encodes a dbus byte array for use with the xml rpc proxy.""" 55 return base64.standard_b64encode(bytearray(dbus_byte_array)) 56 57 58def _b64_string_to_dbus_byte_array(b64_string): 59 """Base64 decodes a dbus byte array for use with the xml rpc proxy.""" 60 dbus_array = dbus.Array([], signature=dbus.Signature('y')) 61 bytes = bytearray(base64.standard_b64decode(b64_string)) 62 for byte in bytes: 63 dbus_array.append(dbus.Byte(byte)) 64 return dbus_array 65 66 67def dbus_print_error(default_return_value=False): 68 """Catch all DBus exceptions and return the error. 69 70 Wrap a function with a try block that catches DBus exceptions and 71 returns the error with the specified return status. The exception is logged 72 to aid in debugging. 73 74 @param wrapped_function function to wrap. 75 76 """ 77 78 def decorator(wrapped_function): 79 """Call a function and catch DBus errors. 80 81 @param wrapped_function function to call in dbus safe context. 82 @return function return value or default_return_value on failure. 83 84 """ 85 86 @functools.wraps(wrapped_function) 87 def wrapper(*args, **kwargs): 88 """Pass args and kwargs to a dbus safe function. 89 90 @param args formal python arguments. 91 @param kwargs keyword python arguments. 92 @return function return value or default_return_value on failure. 93 94 """ 95 logging.debug('%s()', wrapped_function.__name__) 96 try: 97 return wrapped_function(*args, **kwargs) 98 99 except dbus.exceptions.DBusException as e: 100 logging.debug( 101 'Exception while performing operation %s: %s: %s', 102 wrapped_function.__name__, e.get_dbus_name(), 103 e.get_dbus_message()) 104 return (default_return_value, str(e)) 105 106 return wrapper 107 108 return decorator 109 110 111class LogRecorder(object): 112 """The LogRecorder class helps to collect logs without a listening thread""" 113 114 class LoggingException(Exception): 115 """A dummy exception class for LogRecorder class.""" 116 pass 117 118 def __init__(self, log_path): 119 """Initialize log recorder object 120 121 @param log_path: string path to log file to record 122 123 @raises: LogRecorder.LoggingException on non-existent log file 124 """ 125 if not os.path.isfile(log_path): 126 msg = 'Requested log file {} does not exist'.format(log_path) 127 raise LogRecorder.LoggingException(msg) 128 129 self.log_path = log_path 130 131 self.initial_log_size = -1 132 self.log_contents = [] 133 134 def StartRecording(self): 135 """Mark initial log size for later comparison""" 136 137 self.initial_log_size = os.path.getsize(self.log_path) 138 self.log_contents = [] 139 140 def StopRecording(self): 141 """Gather the logs since StartRecording was called 142 143 @raises: LogRecorder.LoggingException if: 144 - Log file disappeared since StartRecording was called 145 - Log file is smaller than when logging began 146 - StartRecording was never called 147 """ 148 now_size = os.path.getsize(self.log_path) 149 150 if not os.path.isfile(self.log_path): 151 msg = 'File {} disappeared unexpectedly'.format(self.log_path) 152 raise LogRecorder.LoggingException(msg) 153 154 if now_size < self.initial_log_size: 155 msg = 'Log became smaller unexpectedly' 156 raise LogRecorder.LoggingException(msg) 157 158 if self.initial_log_size < 0: 159 msg = 'Recording stopped before it started' 160 raise LogRecorder.LoggingException(msg) 161 162 with open(self.log_path, 'r') as mf: 163 # Skip to the point where we started recording 164 mf.seek(self.initial_log_size) 165 166 readsize = now_size - self.initial_log_size 167 self.log_contents = mf.read(readsize).split('\n') 168 169 def LogContains(self, search_str): 170 """Performs simple string checking on each line from the collected log 171 172 @param search_str: string to be located within log contents. This arg 173 is expected to not span between lines in the logs 174 175 @returns: True if search_str was located in the collected log contents, 176 False otherwise 177 """ 178 179 for line in self.log_contents: 180 if search_str in line: 181 return True 182 183 return False 184 185 186class InterleaveLogger(LogRecorder): 187 """LogRecorder class that focus on interleave scan""" 188 189 SYSLOG_PATH = '/var/log/messages' 190 191 # Example bluetooth kernel log: 192 # "2020-11-23T07:52:31.395941Z DEBUG kernel: [ 6469.811135] Bluetooth: " 193 # "cancel_interleave_scan() hci0: hci0 cancelling interleave scan" 194 KERNEL_LOG_PATTERN = ('([^ ]+) DEBUG kernel: \[.*\] Bluetooth: ' 195 '{FUNCTION}\(\) hci0: {LOG_STR}') 196 STATE_PATTERN = KERNEL_LOG_PATTERN.format( 197 FUNCTION='add_le_interleave_adv_monitor_scan', 198 LOG_STR='next state: (.+)') 199 CANCEL_PATTERN = KERNEL_LOG_PATTERN.format( 200 FUNCTION='cancel_interleave_scan', 201 LOG_STR='hci0 cancelling interleave scan') 202 SYSTIME_LENGTH = len('2020-12-18T00:11:22.345678') 203 204 def __init__(self): 205 """ Initialize object 206 """ 207 self.reset() 208 self.state_pattern = re.compile(self.STATE_PATTERN) 209 self.cancel_pattern = re.compile(self.CANCEL_PATTERN) 210 super(InterleaveLogger, self).__init__(self.SYSLOG_PATH) 211 212 def reset(self): 213 """ Clear data between each log collection attempt 214 """ 215 self.records = [] 216 self.cancel_events = [] 217 218 def StartRecording(self): 219 """ Reset the previous data and start recording. 220 """ 221 self.reset() 222 super(InterleaveLogger, self).StartRecording() 223 224 def StopRecording(self): 225 """ Stop recording and parse logs 226 The following data will be set after this call 227 228 - self.records: a dictionary where each item is a record of 229 interleave |state| and the |time| the state starts. 230 |state| could be {'no filter', 'allowlist'} 231 |time| is system time in sec 232 233 - self.cancel_events: a list of |time| when a interleave cancel 234 event log was found 235 |time| is system time in sec 236 237 @returns: True if StopRecording success, False otherwise 238 239 """ 240 try: 241 super(InterleaveLogger, self).StopRecording() 242 except Exception as e: 243 logging.error(e) 244 return False 245 246 success = True 247 248 def sys_time_to_timestamp(time_str): 249 """ Return timestamp of time_str """ 250 251 # This is to remove the suffix of time string, in some cases the 252 # time string ends with an extra 'Z', in other cases, the string 253 # ends with time zone (ex. '+08:00') 254 time_str = time_str[:self.SYSTIME_LENGTH] 255 256 try: 257 dt = datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%S.%f") 258 except Exception as e: 259 logging.error(e) 260 success = False 261 return 0 262 263 return time.mktime(dt.timetuple()) + dt.microsecond * (10**-6) 264 265 for line in self.log_contents: 266 line = line.strip().replace('\\r\\n', '') 267 state_pattern = self.state_pattern.search(line) 268 cancel_pattern = self.cancel_pattern.search(line) 269 270 if cancel_pattern: 271 time_str = cancel_pattern.groups()[0] 272 time_sec = sys_time_to_timestamp(time_str) 273 self.cancel_events.append(time_sec) 274 275 if state_pattern: 276 time_str, state = state_pattern.groups() 277 time_sec = sys_time_to_timestamp(time_str) 278 self.records.append({'time': time_sec, 'state': state}) 279 280 return success 281 282 283class PairingAgent(dbus.service.Object): 284 """The agent handling the authentication process of bluetooth pairing. 285 286 PairingAgent overrides RequestPinCode method to return a given pin code. 287 User can use this agent to pair bluetooth device which has a known 288 pin code. 289 290 TODO (josephsih): more pairing modes other than pin code would be 291 supported later. 292 293 """ 294 295 def __init__(self, pin, *args, **kwargs): 296 super(PairingAgent, self).__init__(*args, **kwargs) 297 self._pin = pin 298 299 @dbus.service.method('org.bluez.Agent1', 300 in_signature='o', 301 out_signature='s') 302 def RequestPinCode(self, device_path): 303 """Requests pin code for a device. 304 305 Returns the known pin code for the request. 306 307 @param device_path: The object path of the device. 308 309 @returns: The known pin code. 310 311 """ 312 logging.info('RequestPinCode for %s; return %s', device_path, 313 self._pin) 314 return self._pin 315 316 317class BluetoothFacadeNative(object): 318 """Exposes DUT methods called remotely during Bluetooth autotests. 319 320 All instance methods of this object without a preceding '_' are exposed via 321 an XML-RPC server. This is not a stateless handler object, which means that 322 if you store state inside the delegate, that state will remain around for 323 future calls. 324 """ 325 326 UPSTART_PATH = 'unix:abstract=/com/ubuntu/upstart' 327 UPSTART_MANAGER_PATH = '/com/ubuntu/Upstart' 328 UPSTART_MANAGER_IFACE = 'com.ubuntu.Upstart0_6' 329 UPSTART_JOB_IFACE = 'com.ubuntu.Upstart0_6.Job' 330 331 UPSTART_ERROR_UNKNOWNINSTANCE = \ 332 'com.ubuntu.Upstart0_6.Error.UnknownInstance' 333 UPSTART_ERROR_ALREADYSTARTED = \ 334 'com.ubuntu.Upstart0_6.Error.AlreadyStarted' 335 336 BLUETOOTHD_JOB = 'bluetoothd' 337 338 DBUS_ERROR_SERVICEUNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown' 339 340 BLUETOOTH_SERVICE_NAME = 'org.chromium.Bluetooth' 341 BLUEZ_SERVICE_NAME = 'org.bluez' 342 BLUEZ_MANAGER_PATH = '/' 343 BLUEZ_DEBUG_LOG_PATH = '/org/chromium/Bluetooth' 344 BLUEZ_DEBUG_LOG_IFACE = 'org.chromium.Bluetooth.Debug' 345 BLUEZ_MANAGER_IFACE = 'org.freedesktop.DBus.ObjectManager' 346 BLUEZ_ADAPTER_IFACE = 'org.bluez.Adapter1' 347 BLUEZ_BATTERY_IFACE = 'org.bluez.Battery1' 348 BLUEZ_DEVICE_IFACE = 'org.bluez.Device1' 349 BLUEZ_GATT_SERV_IFACE = 'org.bluez.GattService1' 350 BLUEZ_GATT_CHAR_IFACE = 'org.bluez.GattCharacteristic1' 351 BLUEZ_GATT_DESC_IFACE = 'org.bluez.GattDescriptor1' 352 BLUEZ_LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1' 353 BLUEZ_ADV_MONITOR_MANAGER_IFACE = 'org.bluez.AdvertisementMonitorManager1' 354 BLUEZ_AGENT_MANAGER_PATH = '/org/bluez' 355 BLUEZ_AGENT_MANAGER_IFACE = 'org.bluez.AgentManager1' 356 BLUEZ_PROFILE_MANAGER_PATH = '/org/bluez' 357 BLUEZ_PROFILE_MANAGER_IFACE = 'org.bluez.ProfileManager1' 358 BLUEZ_ERROR_ALREADY_EXISTS = 'org.bluez.Error.AlreadyExists' 359 BLUEZ_PLUGIN_DEVICE_IFACE = 'org.chromium.BluetoothDevice' 360 DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties' 361 AGENT_PATH = '/test/agent' 362 363 BLUETOOTH_LIBDIR = '/var/lib/bluetooth' 364 BTMON_STOP_DELAY_SECS = 3 365 366 # Due to problems transferring a date object, we convert to stringtime first 367 # This is the standard format that we will use. 368 OUT_DATE_FORMAT = '%Y-%m-%d %H:%M:%S.%f' 369 370 # Timeout for how long we'll wait for BlueZ and the Adapter to show up 371 # after reset. 372 ADAPTER_TIMEOUT = 30 373 374 # How long to wait for hid device 375 HID_TIMEOUT = 15 376 HID_CHECK_SECS = 2 377 378 # How long we should wait for property update signal before we cancel it 379 PROPERTY_UPDATE_TIMEOUT_MILLI_SECS = 5000 380 381 def __init__(self): 382 # Open the Bluetooth Raw socket to the kernel which provides us direct, 383 # raw, access to the HCI controller. 384 self._raw = bluetooth_socket.BluetoothRawSocket() 385 386 # Open the Bluetooth Control socket to the kernel which provides us 387 # raw management access to the Bluetooth Host Subsystem. Read the list 388 # of adapter indexes to determine whether or not this device has a 389 # Bluetooth Adapter or not. 390 self._control = bluetooth_socket.BluetoothControlSocket() 391 self._has_adapter = len(self._control.read_index_list()) > 0 392 393 # Create an Advertisement Monitor App Manager instance. 394 # This needs to be created before making any dbus connections as 395 # AdvMonitorAppMgr internally forks a new helper process and due to 396 # a limitation of python, it is not possible to fork a new process 397 # once any dbus connections are established. 398 self.advmon_appmgr = adv_monitor_helper.AdvMonitorAppMgr() 399 400 # Set up the connection to Upstart so we can start and stop services 401 # and fetch the bluetoothd job. 402 self._upstart_conn = dbus.connection.Connection(self.UPSTART_PATH) 403 self._upstart = self._upstart_conn.get_object( 404 None, self.UPSTART_MANAGER_PATH) 405 406 bluetoothd_path = self._upstart.GetJobByName( 407 self.BLUETOOTHD_JOB, dbus_interface=self.UPSTART_MANAGER_IFACE) 408 self._bluetoothd = self._upstart_conn.get_object(None, bluetoothd_path) 409 410 # Arrange for the GLib main loop to be the default. 411 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 412 413 # Set up the connection to the D-Bus System Bus, get the object for 414 # the Bluetooth Userspace Daemon (BlueZ) and that daemon's object for 415 # the Bluetooth Adapter, and the advertising manager. 416 self._system_bus = dbus.SystemBus() 417 self._update_bluez() 418 self._update_adapter() 419 self._update_advertising() 420 self._update_adv_monitor_manager() 421 422 # The agent to handle pin code request, which will be 423 # created when user calls pair_legacy_device method. 424 self._pairing_agent = None 425 # The default capability of the agent. 426 self._capability = 'KeyboardDisplay' 427 428 # Initailize a btmon object to record bluetoothd's activity. 429 self.btmon = output_recorder.OutputRecorder( 430 'btmon', stop_delay_secs=self.BTMON_STOP_DELAY_SECS) 431 432 # Initialize a messages object to record general logging. 433 self.messages = LogRecorder('/var/log/messages') 434 435 self._cras_test_client = cras_utils.CrasTestClient() 436 437 self.advertisements = [] 438 self.advmon_interleave_logger = InterleaveLogger() 439 self._chrc_property = None 440 self._timeout_id = 0 441 self._signal_watch = None 442 self._dbus_mainloop = gobject.MainLoop() 443 444 @xmlrpc_server.dbus_safe(False) 445 def set_debug_log_levels(self, dispatcher_vb, newblue_vb, bluez_vb, 446 kernel_vb): 447 """Enable or disable the debug logs of bluetooth 448 449 @param dispatcher_vb: verbosity of btdispatcher debug log, either 0 or 1 450 @param newblue_vb: verbosity of newblued debug log, either 0 or 1 451 @param bluez_vb: verbosity of bluez debug log, either 0 or 1 452 @param kernel_vb: verbosity of kernel debug log, either 0 or 1 453 454 """ 455 456 # TODO(b/145163508, b/145749798): update when debug logs is migrated to 457 # bluez. 458 debug_object = self._system_bus.get_object(self.BLUETOOTH_SERVICE_NAME, 459 self.BLUEZ_DEBUG_LOG_PATH) 460 debug_object.SetLevels(dbus.Byte(dispatcher_vb), 461 dbus.Byte(newblue_vb), 462 dbus.Byte(bluez_vb), 463 dbus.Byte(kernel_vb), 464 dbus_interface=self.BLUEZ_DEBUG_LOG_IFACE) 465 return 466 467 def log_message(self, msg): 468 """ log a message to /var/log/messages.""" 469 try: 470 cmd = ['logger', msg] 471 subprocess.call(cmd) 472 except Exception as e: 473 logging.error("log_message %s failed with %s", cmd, str(e)) 474 475 def is_wrt_supported(self): 476 """Check if Bluetooth adapter support WRT logs 477 478 WRT is supported on Intel adapters other than (StP2 and WP2) 479 480 @returns : True if adapter is Intel made. 481 """ 482 # Dict of Intel Adapters that support WRT and vid:pid 483 vid_pid_dict = { 484 'HrP2': '8086:02f0', 485 'ThP2': '8086:2526', 486 'JfP2': '8086:31dc', 487 'JfP2-2': '8086:9df0' 488 } # On Sarien/Arcada 489 490 def _get_lspci_vid_pid(output): 491 """ parse output of lspci -knn and get the vid:pid 492 493 output is of the form '01:00.0 Network controller [0280]: 494 \Intel Corporation Device [8086:2526] (rev 29)\n' 495 496 @returns : 'vid:pid' or None 497 """ 498 try: 499 for i in output.split(b'\n'): 500 if 'Network controller' in i.decode('utf-8'): 501 logging.debug('Got line %s', i) 502 if 'Intel Corporation' in i.decode('utf-8'): 503 return i.split(b'[')[2].split(b']')[0] 504 return None 505 except Exception as e: 506 logging.debug('Exception in _get_lspci_vidpid %s', str(e)) 507 return None 508 509 try: 510 cmd = ['lspci', '-knn'] 511 output = subprocess.check_output(cmd) 512 vid_pid = _get_lspci_vid_pid(output) 513 logging.debug("got vid_pid %s", vid_pid) 514 if vid_pid is not None: 515 if vid_pid in list(vid_pid_dict.values()): 516 return True 517 except Exception as e: 518 logging.error('is_intel_adapter failed with %s', cmd, str(e)) 519 return False 520 521 def enable_wrt_logs(self): 522 """ Enable WRT logs for Intel Bluetooth adapters. 523 524 This is applicable only to Intel adapters. 525 Execute a series of custom hciconfig commands to 526 setup WRT log collection 527 528 Precondition : 529 1) Check if the DUT has Intel controller other than StP2 530 2) Make sure the controller is powered on 531 """ 532 fw_trace_cmd = ( 533 'hcitool cmd 3f 7c 01 10 00 00 00 FE 81 02 80 04 00 00' 534 ' 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' 535 ' 00 00 00 00 00 00 00') 536 ddc_read_cmd = 'hcitool cmd 3f 8c 28 01' 537 ddc_write_cmd_prefix = 'hcitool cmd 3f 8b 03 28 01' 538 hw_trace_cmd = ( 539 'hcitool cmd 3f 6f 01 08 00 00 00 00 00 00 00 00 01 00' 540 ' 00 03 01 03 03 03 10 03 6A 0A 6A 0A 6A 0A 6A 0A 00 00' 541 ' 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00' 542 ' 00 00 00 00 00 00') 543 multi_comm_trace_str = ('000000F600000000005002000000003F3F3F3' 544 'F3F003F000000000000000001000000000000000000' 545 '000000000000000000000000000000000000000000' 546 '00000000000000000000000000000000000000000' 547 '00000000000000000') 548 multi_comm_trace_file = ('/sys/kernel/debug/ieee80211' 549 '/phy0/iwlwifi/iwlmvm/send_hcmd') 550 551 def _execute_cmd(cmd_str, msg=''): 552 """Wrapper around subprocess.check_output. 553 554 @params cmd: Command to be executed as a string 555 @params msg: Optional description of the command 556 557 @returns: (True, output) if execution succeeded 558 (False, None) if execution failed 559 560 """ 561 try: 562 logging.info('Executing %s cmd', msg) 563 cmd = cmd_str.split(' ') 564 logging.debug('command is "%s"', cmd) 565 output = subprocess.check_output(cmd) 566 logging.info('%s cmd successfully executed', msg) 567 logging.debug('output is %s', output) 568 return (True, output) 569 except Exception as e: 570 logging.error('Exception %s while executing %s command', 571 str(e), msg) 572 return (False, None) 573 574 def _get_ddc_write_cmd(ddc_read_result, ddc_write_cmd_prefix): 575 """ Create ddc_write_cmd from read command 576 577 This function performs the following 578 1) Take the output of ddc_read_cmd which is in following form 579 '< HCI Command: ogf 0x3f, ocf 0x008c, plen 1\n 580 01 \n> 581 HCI Event: 0x0e plen 6\n 01 8C FC 12 00 18 \n' 582 2) Take the last value of the output 583 01 8C FC 12 00 ===>> 18 <==== 584 3) Bitwise or with 0x40 585 0x18 | 0x40 = 0x58 586 4) Add it to the end of the ddc_write_cmd 587 'hcitool 01 8C FC 00 28 01 ===> 58 <====' 588 589 """ 590 last_line = [ 591 i for i in ddc_read_result.strip().split(b'\n') if i != '' 592 ][-1] 593 last_byte = [i for i in last_line.split(b' ') if i != ''][-1] 594 processed_byte = hex(int(last_byte, 16) | 0x40).split('0x')[1] 595 cmd = ddc_write_cmd_prefix + ' ' + processed_byte 596 logging.debug('ddc_write_cmd is %s', cmd) 597 return cmd 598 599 try: 600 logging.info('Enabling WRT logs') 601 status, _ = _execute_cmd(fw_trace_cmd, 'FW trace cmd') 602 if not status: 603 logging.info('FW trace command execution failed') 604 return False 605 606 status, ddc_read_result = _execute_cmd(ddc_read_cmd, 'DDC Read') 607 if not status: 608 logging.info('DDC Read command execution failed') 609 return False 610 611 ddc_write_cmd = _get_ddc_write_cmd(ddc_read_result, 612 ddc_write_cmd_prefix) 613 logging.debug('DDC Write command is %s', ddc_write_cmd) 614 status, _ = _execute_cmd(ddc_write_cmd, 'DDC Write') 615 if not status: 616 logging.info('DDC Write commanad execution failed') 617 return False 618 619 status, hw_trace_result = _execute_cmd(hw_trace_cmd, 'HW trace') 620 if not status: 621 logging.info('HW Trace command execution failed') 622 return False 623 624 logging.debug('Executing the multi_comm_trace cmd %s to file %s', 625 multi_comm_trace_str, multi_comm_trace_file) 626 with open(multi_comm_trace_file, 'w') as f: 627 f.write(multi_comm_trace_str + '\n') 628 f.flush() 629 630 logging.info('WRT Logs enabled') 631 return True 632 except Exception as e: 633 logging.error('Exception %s while enabling WRT logs', str(e)) 634 return False 635 636 def collect_wrt_logs(self): 637 """Collect the WRT logs for Intel Bluetooth adapters 638 639 This is applicable only to Intel adapters. 640 Execute following command to collect WRT log. The logs are 641 copied to /var/spool/crash/ 642 643 'echo 1 > sudo tee /sys/kernel/debug/ieee80211/phy0' 644 '/iwlwifi/iwlmvm/fw_dbg_collect' 645 This is to be called only after enable_wrt_logs is called 646 647 648 Precondition: 649 1) enable_wrt_logs has been called 650 """ 651 652 def _collect_logs(): 653 """Execute command to collect wrt logs.""" 654 try: 655 with open( 656 '/sys/kernel/debug/ieee80211/phy0/iwlwifi/' 657 'iwlmvm/fw_dbg_collect', 'w') as f: 658 f.write('1') 659 f.flush() 660 # There is some flakiness in log collection. This sleep 661 # is due to the flakiness 662 time.sleep(10) 663 return True 664 except Exception as e: 665 logging.error('Exception %s in _collect logs ', str(e)) 666 return False 667 668 def _get_num_log_files(): 669 """Return number of WRT log files.""" 670 try: 671 return len(glob.glob('/var/spool/crash/devcoredump_iwlwifi*')) 672 except Exception as e: 673 logging.debug('Exception %s raised in _get_num_log_files', 674 str(e)) 675 return 0 676 677 try: 678 logging.info('Collecting WRT logs') 679 # 680 # The command to trigger the logs does seems to work always. 681 # As a workaround for this flakiness, execute it multiple times 682 # until a new log is created 683 # 684 num_logs_present = _get_num_log_files() 685 logging.debug('%s logs present', num_logs_present) 686 for i in range(10): 687 time.sleep(1) 688 logging.debug('Executing command to collect WRT logs ') 689 if _collect_logs(): 690 logging.debug('Command to collect WRT logs executed') 691 else: 692 logging.debug('Command to collect WRT logs failed') 693 continue 694 695 if _get_num_log_files() > num_logs_present: 696 logging.info('Successfully collected WRT logs ') 697 return True 698 else: 699 logging.debug('Log file not written. Trying again') 700 701 logging.info('Unable to collect WRT logs') 702 return False 703 except Exception as e: 704 logging.error('Exception %s while collecting WRT logs', str(e)) 705 return False 706 707 @xmlrpc_server.dbus_safe(False) 708 def start_bluetoothd(self): 709 """start bluetoothd. 710 711 This includes powering up the adapter. 712 713 @returns: True if bluetoothd is started correctly. 714 False otherwise. 715 716 """ 717 try: 718 self._bluetoothd.Start(dbus.Array(signature='s'), 719 True, 720 dbus_interface=self.UPSTART_JOB_IFACE) 721 except dbus.exceptions.DBusException as e: 722 # if bluetoothd was already started, the exception looks like 723 # dbus.exceptions.DBusException: 724 # com.ubuntu.Upstart0_6.Error.AlreadyStarted: Job is already 725 # running: bluetoothd 726 if e.get_dbus_name() != self.UPSTART_ERROR_ALREADYSTARTED: 727 logging.error('Error starting bluetoothd: %s', e) 728 return False 729 730 logging.debug('waiting for bluez start') 731 try: 732 utils.poll_for_condition(condition=self._update_bluez, 733 desc='Bluetooth Daemon has started.', 734 timeout=self.ADAPTER_TIMEOUT) 735 except Exception as e: 736 logging.error('timeout: error starting bluetoothd: %s', e) 737 return False 738 739 # Waiting for the self._adapter object. 740 # This does not mean that the adapter is powered on. 741 logging.debug('waiting for bluez to obtain adapter information') 742 try: 743 utils.poll_for_condition( 744 condition=self._update_adapter, 745 desc='Bluetooth Daemon has adapter information.', 746 timeout=self.ADAPTER_TIMEOUT) 747 except Exception as e: 748 logging.error('timeout: error starting adapter: %s', e) 749 return False 750 751 # Waiting for the self._advertising interface object. 752 logging.debug('waiting for bluez to obtain interface manager.') 753 try: 754 utils.poll_for_condition( 755 condition=self._update_advertising, 756 desc='Bluetooth Daemon has advertising interface.', 757 timeout=self.ADAPTER_TIMEOUT) 758 except utils.TimeoutError: 759 logging.error('timeout: error getting advertising interface') 760 return False 761 762 return True 763 764 @xmlrpc_server.dbus_safe(False) 765 def stop_bluetoothd(self): 766 """stop bluetoothd. 767 768 @returns: True if bluetoothd is stopped correctly. 769 False otherwise. 770 771 """ 772 773 def bluez_stopped(): 774 """Checks the bluetooth daemon status. 775 776 @returns: True if bluez is stopped. False otherwise. 777 778 """ 779 return not self._update_bluez() 780 781 try: 782 self._bluetoothd.Stop(dbus.Array(signature='s'), 783 True, 784 dbus_interface=self.UPSTART_JOB_IFACE) 785 except dbus.exceptions.DBusException as e: 786 # If bluetoothd was stopped already, the exception looks like 787 # dbus.exceptions.DBusException: 788 # com.ubuntu.Upstart0_6.Error.UnknownInstance: Unknown instance: 789 if e.get_dbus_name() != self.UPSTART_ERROR_UNKNOWNINSTANCE: 790 logging.error('Error stopping bluetoothd!') 791 return False 792 793 logging.debug('waiting for bluez stop') 794 try: 795 utils.poll_for_condition(condition=bluez_stopped, 796 desc='Bluetooth Daemon has stopped.', 797 timeout=self.ADAPTER_TIMEOUT) 798 bluetoothd_stopped = True 799 except Exception as e: 800 logging.error('timeout: error stopping bluetoothd: %s', e) 801 bluetoothd_stopped = False 802 803 return bluetoothd_stopped 804 805 def is_bluetoothd_running(self): 806 """Is bluetoothd running? 807 808 @returns: True if bluetoothd is running 809 810 """ 811 return bool(self._get_dbus_proxy_for_bluetoothd()) 812 813 def is_bluetoothd_proxy_valid(self): 814 """Checks whether the proxy object for bluetoothd is ok. 815 816 The dbus proxy object (self._bluez) can become unusable if bluetoothd 817 crashes or restarts for any reason. This method checks whether this has 818 happened by attempting to use the object proxy. If bluetoothd has 819 restarted (or is not available), then the session will no longer be 820 valid and this will result in a dbus exception. 821 822 Returns: 823 True if the bluez proxy is still usable. False otherwise. 824 """ 825 826 try: 827 _ = self._bluez.GetManagedObjects( 828 dbus_interface=self.BLUEZ_MANAGER_IFACE) 829 except dbus.exceptions.DBusException: 830 return False 831 832 return True 833 834 def _update_bluez(self): 835 """Store a D-Bus proxy for the Bluetooth daemon in self._bluez. 836 837 This may be called in a loop until it returns True to wait for the 838 daemon to be ready after it has been started. 839 840 @return True on success, False otherwise. 841 842 """ 843 self._bluez = self._get_dbus_proxy_for_bluetoothd() 844 return bool(self._bluez) 845 846 @xmlrpc_server.dbus_safe(False) 847 def _get_dbus_proxy_for_bluetoothd(self): 848 """Get the D-Bus proxy for the Bluetooth daemon. 849 850 @return True on success, False otherwise. 851 852 """ 853 bluez = None 854 try: 855 bluez = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, 856 self.BLUEZ_MANAGER_PATH) 857 logging.debug('bluetoothd is running') 858 except dbus.exceptions.DBusException as e: 859 # When bluetoothd is not running, the exception looks like 860 # dbus.exceptions.DBusException: 861 # org.freedesktop.DBus.Error.ServiceUnknown: The name org.bluez 862 # was not provided by any .service files 863 if e.get_dbus_name() == self.DBUS_ERROR_SERVICEUNKNOWN: 864 logging.debug('bluetoothd is not running') 865 else: 866 logging.error('Error getting dbus proxy for Bluez: %s', e) 867 return bluez 868 869 def _update_adapter(self): 870 """Store a D-Bus proxy for the local adapter in self._adapter. 871 872 This may be called in a loop until it returns True to wait for the 873 daemon to be ready, and have obtained the adapter information itself, 874 after it has been started. 875 876 Since not all devices will have adapters, this will also return True 877 in the case where we have obtained an empty adapter index list from the 878 kernel. 879 880 Note that this method does not power on the adapter. 881 882 @return True on success, including if there is no local adapter, 883 False otherwise. 884 885 """ 886 self._adapter = None 887 if self._bluez is None: 888 logging.warning('Bluez not found!') 889 return False 890 if not self._has_adapter: 891 logging.debug('Device has no adapter; returning') 892 return True 893 self._adapter = self._get_adapter() 894 return bool(self._adapter) 895 896 def _update_advertising(self): 897 """Store a D-Bus proxy for the local advertising interface manager. 898 899 This may be called repeatedly in a loop until True is returned; 900 otherwise we wait for bluetoothd to start. After bluetoothd starts, we 901 check the existence of a local adapter and proceed to get the 902 advertisement interface manager. 903 904 Since not all devices will have adapters, this will also return True 905 in the case where there is no adapter. 906 907 @return True on success, including if there is no local adapter, 908 False otherwise. 909 910 """ 911 self._advertising = None 912 if self._bluez is None: 913 logging.warning('Bluez not found!') 914 return False 915 if not self._has_adapter: 916 logging.debug('Device has no adapter; returning') 917 return True 918 self._advertising = self._get_advertising() 919 return bool(self._advertising) 920 921 def _update_adv_monitor_manager(self): 922 """Store a D-Bus proxy for the local advertisement monitor manager. 923 924 This may be called repeatedly in a loop until True is returned; 925 otherwise we wait for bluetoothd to start. After bluetoothd starts, we 926 check the existence of a local adapter and proceed to get the 927 advertisement monitor manager interface. 928 929 Since not all devices will have adapters, this will also return True 930 in the case where there is no adapter. 931 932 @return True on success, including if there is no local adapter, 933 False otherwise. 934 935 """ 936 self._adv_monitor_manager = None 937 if self._bluez is None: 938 logging.warning('Bluez not found!') 939 return False 940 if not self._has_adapter: 941 logging.debug('Device has no adapter; returning without ' 942 'advertisement monitor manager') 943 return True 944 self._adv_monitor_manager = self._get_adv_monitor_manager() 945 return bool(self._adv_monitor_manager) 946 947 @xmlrpc_server.dbus_safe(False) 948 def _get_adapter(self): 949 """Get the D-Bus proxy for the local adapter. 950 951 @return the adapter on success. None otherwise. 952 953 """ 954 objects = self._bluez.GetManagedObjects( 955 dbus_interface=self.BLUEZ_MANAGER_IFACE) 956 for path, ifaces in six.iteritems(objects): 957 logging.debug('%s -> %r', path, list(ifaces.keys())) 958 if self.BLUEZ_ADAPTER_IFACE in ifaces: 959 logging.debug('using adapter %s', path) 960 adapter = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, 961 path) 962 return adapter 963 else: 964 logging.warning('No adapter found in interface!') 965 return None 966 967 @xmlrpc_server.dbus_safe(False) 968 def _get_advertising(self): 969 """Get the D-Bus proxy for the local advertising interface. 970 971 @return the advertising interface object. 972 973 """ 974 return dbus.Interface(self._adapter, 975 self.BLUEZ_LE_ADVERTISING_MANAGER_IFACE) 976 977 @xmlrpc_server.dbus_safe(False) 978 def _get_adv_monitor_manager(self): 979 """Get the D-Bus proxy for the local advertisement monitor manager. 980 981 @return the advertisement monitor manager interface object. 982 983 """ 984 return dbus.Interface(self._adapter, 985 self.BLUEZ_ADV_MONITOR_MANAGER_IFACE) 986 987 @xmlrpc_server.dbus_safe(False) 988 def reset_on(self): 989 """Reset the adapter and settings and power up the adapter. 990 991 @return True on success, False otherwise. 992 993 """ 994 return self._reset(set_power=True) 995 996 @xmlrpc_server.dbus_safe(False) 997 def reset_off(self): 998 """Reset the adapter and settings, leave the adapter powered off. 999 1000 @return True on success, False otherwise. 1001 1002 """ 1003 return self._reset(set_power=False) 1004 1005 def has_adapter(self): 1006 """Return if an adapter is present. 1007 1008 This will only return True if we have determined both that there is 1009 a Bluetooth adapter on this device (kernel adapter index list is not 1010 empty) and that the Bluetooth daemon has exported an object for it. 1011 1012 @return True if an adapter is present, False if not. 1013 1014 """ 1015 return self._has_adapter and self._adapter is not None 1016 1017 def is_wake_enabled(self): 1018 """Checks whether the bluetooth adapter has wake enabled. 1019 1020 This will walk through all parents of the hci0 sysfs path and try to 1021 find one with a 'power/wakeup' entry and returns whether its value is 1022 'enabled'. 1023 1024 @return True if 'power/wakeup' of an hci0 parent is 'enabled' 1025 """ 1026 enabled = self._is_wake_enabled() 1027 return enabled 1028 1029 def set_wake_enabled(self, value): 1030 """Sets wake enabled to the value if path exists. 1031 1032 This will walk through all parents of the hci0 sysfs path and write the 1033 value to the first one it finds. 1034 1035 Args: 1036 value: Sets power/wakeup to "enabled" if value is true, else 1037 "disabled" 1038 1039 @return True if it wrote value to a power/wakeup, False otherwise 1040 """ 1041 return self._set_wake_enabled(value) 1042 1043 def wait_for_hid_device(self, device_address): 1044 """Waits for hid device with given device address. 1045 1046 Args: 1047 device_address: Peripheral address 1048 """ 1049 1050 def match_hid_to_device(hidpath, device_address): 1051 """Check if given hid syspath is for the given device address """ 1052 # If the syspath has a uniq property that matches the peripheral 1053 # device's address, then it has matched 1054 props = UdevadmInfo.GetProperties(hidpath) 1055 if props.get('uniq', '').lower() == device_address.lower(): 1056 logging.info('Found hid device for address {} at {}'.format( 1057 device_address, hidpath)) 1058 return True 1059 else: 1060 logging.info('Path {} is not right device.'.format(hidpath)) 1061 1062 return False 1063 1064 start = datetime.now() 1065 1066 # Keep scanning udev for correct hid device 1067 while (datetime.now() - start).seconds <= self.HID_TIMEOUT: 1068 existing_inputs = UdevadmTrigger( 1069 subsystem_match=['input']).DryRun() 1070 for entry in existing_inputs: 1071 bt_hid = any([t in entry for t in ['uhid', 'hci']]) 1072 logging.info('udevadm trigger entry is {}: {}'.format( 1073 bt_hid, entry)) 1074 1075 if bt_hid and match_hid_to_device(entry, device_address): 1076 return True 1077 1078 time.sleep(self.HID_CHECK_SECS) 1079 1080 return False 1081 1082 def _reset(self, set_power=False): 1083 """Remove remote devices and set adapter to set_power state. 1084 1085 Do not restart bluetoothd as this may incur a side effect. 1086 The unhappy chrome may disable the adapter randomly. 1087 1088 @param set_power: adapter power state to set (True or False). 1089 1090 @return True on success, False otherwise. 1091 1092 """ 1093 logging.debug('_reset') 1094 1095 if not self._adapter: 1096 logging.warning('Adapter not found!') 1097 return False 1098 1099 objects = self._bluez.GetManagedObjects( 1100 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=True) 1101 1102 devices = [] 1103 for path, ifaces in six.iteritems(objects): 1104 if self.BLUEZ_DEVICE_IFACE in ifaces: 1105 devices.append(objects[path][self.BLUEZ_DEVICE_IFACE]) 1106 1107 # Turn on the adapter in order to remove all remote devices. 1108 if not self._is_powered_on(): 1109 if not self._set_powered(True): 1110 logging.warning('Unable to power on the adapter') 1111 return False 1112 1113 for device in devices: 1114 logging.debug('removing %s', device.get('Address')) 1115 self.remove_device_object(device.get('Address')) 1116 1117 # Toggle power to the adapter. 1118 if not self._set_powered(False): 1119 logging.warning('Unable to power off adapter') 1120 return False 1121 if set_power and not self._set_powered(True): 1122 logging.warning('Unable to power on adapter') 1123 return False 1124 1125 return True 1126 1127 @xmlrpc_server.dbus_safe(False) 1128 def set_powered(self, powered): 1129 """Set the adapter power state. 1130 1131 @param powered: adapter power state to set (True or False). 1132 1133 @return True on success, False otherwise. 1134 1135 """ 1136 if not self._adapter: 1137 if not powered: 1138 # Return success if we are trying to power off an adapter that's 1139 # missing or gone away, since the expected result has happened. 1140 return True 1141 else: 1142 logging.warning('Adapter not found!') 1143 return False 1144 return self._set_powered(powered) 1145 1146 @xmlrpc_server.dbus_safe(False) 1147 def _set_powered(self, powered): 1148 """Set the adapter power state. 1149 1150 @param powered: adapter power state to set (True or False). 1151 1152 """ 1153 logging.debug('_set_powered %r', powered) 1154 self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 1155 'Powered', 1156 dbus.Boolean(powered, variant_level=1), 1157 dbus_interface=dbus.PROPERTIES_IFACE) 1158 return True 1159 1160 @xmlrpc_server.dbus_safe(False) 1161 def set_discoverable(self, discoverable): 1162 """Set the adapter discoverable state. 1163 1164 @param discoverable: adapter discoverable state to set (True or False). 1165 1166 @return True on success, False otherwise. 1167 1168 """ 1169 if not discoverable and not self._adapter: 1170 # Return success if we are trying to make an adapter that's 1171 # missing or gone away, undiscoverable, since the expected result 1172 # has happened. 1173 return True 1174 self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 1175 'Discoverable', 1176 dbus.Boolean(discoverable, variant_level=1), 1177 dbus_interface=dbus.PROPERTIES_IFACE) 1178 return True 1179 1180 @xmlrpc_server.dbus_safe(False) 1181 def get_discoverable_timeout(self): 1182 """Get the adapter discoverable_timeout. 1183 1184 @return True on success, False otherwise. 1185 1186 """ 1187 return int( 1188 self._adapter.Get(self.BLUEZ_ADAPTER_IFACE, 1189 'DiscoverableTimeout', 1190 dbus_interface=dbus.PROPERTIES_IFACE)) 1191 1192 @xmlrpc_server.dbus_safe(False) 1193 def set_discoverable_timeout(self, discoverable_timeout): 1194 """Set the adapter discoverable_timeout property. 1195 1196 @param discoverable_timeout: adapter discoverable_timeout value 1197 in seconds to set (Integer). 1198 1199 @return True on success, False otherwise. 1200 1201 """ 1202 self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 1203 'DiscoverableTimeout', 1204 dbus.UInt32(discoverable_timeout, variant_level=1), 1205 dbus_interface=dbus.PROPERTIES_IFACE) 1206 return True 1207 1208 @xmlrpc_server.dbus_safe(False) 1209 def get_pairable_timeout(self): 1210 """Get the adapter pairable_timeout. 1211 1212 @return True on success, False otherwise. 1213 1214 """ 1215 return int( 1216 self._adapter.Get(self.BLUEZ_ADAPTER_IFACE, 1217 'PairableTimeout', 1218 dbus_interface=dbus.PROPERTIES_IFACE)) 1219 1220 @xmlrpc_server.dbus_safe(False) 1221 def set_pairable_timeout(self, pairable_timeout): 1222 """Set the adapter pairable_timeout property. 1223 1224 @param pairable_timeout: adapter pairable_timeout value 1225 in seconds to set (Integer). 1226 1227 @return True on success, False otherwise. 1228 1229 """ 1230 self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 1231 'PairableTimeout', 1232 dbus.UInt32(pairable_timeout, variant_level=1), 1233 dbus_interface=dbus.PROPERTIES_IFACE) 1234 return True 1235 1236 @xmlrpc_server.dbus_safe(False) 1237 def set_pairable(self, pairable): 1238 """Set the adapter pairable state. 1239 1240 @param pairable: adapter pairable state to set (True or False). 1241 1242 @return True on success, False otherwise. 1243 1244 """ 1245 self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 1246 'Pairable', 1247 dbus.Boolean(pairable, variant_level=1), 1248 dbus_interface=dbus.PROPERTIES_IFACE) 1249 return True 1250 1251 @xmlrpc_server.dbus_safe(False) 1252 def set_adapter_alias(self, alias): 1253 """Set the adapter alias. 1254 1255 @param alias: adapter alias to set with type String 1256 1257 @return True on success, False otherwise. 1258 """ 1259 self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 1260 'Alias', 1261 dbus.String(alias), 1262 dbus_interface=dbus.PROPERTIES_IFACE) 1263 return True 1264 1265 @xmlrpc_server.dbus_safe(False) 1266 def _get_adapter_properties(self): 1267 """Read the adapter properties from the Bluetooth Daemon. 1268 1269 @return the properties as a JSON-encoded dictionary on success, 1270 the value False otherwise. 1271 1272 """ 1273 if self._bluez and self._adapter: 1274 objects = self._bluez.GetManagedObjects( 1275 dbus_interface=self.BLUEZ_MANAGER_IFACE) 1276 props = objects[self._adapter.object_path][ 1277 self.BLUEZ_ADAPTER_IFACE] 1278 else: 1279 props = {} 1280 logging.debug('get_adapter_properties') 1281 for i in props.items(): 1282 logging.debug(i) 1283 return props 1284 1285 def get_adapter_properties(self): 1286 return json.dumps(self._get_adapter_properties()) 1287 1288 def _is_powered_on(self): 1289 return bool(self._get_adapter_properties().get(u'Powered')) 1290 1291 def _get_wake_enabled_path(self): 1292 # Walk up the parents from hci0 sysfs path and find the first one with 1293 # a power/wakeup property. Return that path (including power/wakeup). 1294 1295 # Resolve hci path to get full device path (i.e. w/ usb or uart) 1296 search_at = os.path.realpath('/sys/class/bluetooth/hci0') 1297 1298 # Exit early if path doesn't exist 1299 if not os.path.exists(search_at): 1300 return None 1301 1302 # Walk up parents and try to find one with 'power/wakeup' 1303 for _ in range(search_at.count('/') - 1): 1304 search_at = os.path.normpath(os.path.join(search_at, '..')) 1305 try: 1306 path = os.path.join(search_at, 'power', 'wakeup') 1307 with open(path, 'r') as f: 1308 return path 1309 except IOError: 1310 # No power wakeup at the given location so keep going 1311 continue 1312 1313 return None 1314 1315 def _is_wake_enabled(self): 1316 search_at = self._get_wake_enabled_path() 1317 1318 if search_at is not None: 1319 try: 1320 with open(search_at, 'r') as f: 1321 value = f.read() 1322 logging.info('Power/wakeup found at {}: {}'.format( 1323 search_at, value)) 1324 return 'enabled' in value 1325 except IOError: 1326 # Path was not readable 1327 return False 1328 1329 logging.debug('No power/wakeup path found') 1330 return False 1331 1332 def _set_wake_enabled(self, value): 1333 path = self._get_wake_enabled_path() 1334 if path is not None: 1335 try: 1336 with open(path, 'w') as f: 1337 f.write('enabled' if value else 'disabled') 1338 return True 1339 except IOError: 1340 # Path was not writeable 1341 return False 1342 1343 return False 1344 1345 def read_version(self): 1346 """Read the version of the management interface from the Kernel. 1347 1348 @return the information as a JSON-encoded tuple of: 1349 ( version, revision ) 1350 1351 """ 1352 #TODO(howardchung): resolve 'cannot allocate memory' error when 1353 # BluetoothControlSocket idle too long(about 3 secs) 1354 # (b:137603211) 1355 _control = bluetooth_socket.BluetoothControlSocket() 1356 return json.dumps(_control.read_version()) 1357 1358 def read_supported_commands(self): 1359 """Read the set of supported commands from the Kernel. 1360 1361 @return the information as a JSON-encoded tuple of: 1362 ( commands, events ) 1363 1364 """ 1365 #TODO(howardchung): resolve 'cannot allocate memory' error when 1366 # BluetoothControlSocket idle too long(about 3 secs) 1367 # (b:137603211) 1368 _control = bluetooth_socket.BluetoothControlSocket() 1369 return json.dumps(_control.read_supported_commands()) 1370 1371 def read_index_list(self): 1372 """Read the list of currently known controllers from the Kernel. 1373 1374 @return the information as a JSON-encoded array of controller indexes. 1375 1376 """ 1377 #TODO(howardchung): resolve 'cannot allocate memory' error when 1378 # BluetoothControlSocket idle too long(about 3 secs) 1379 # (b:137603211) 1380 _control = bluetooth_socket.BluetoothControlSocket() 1381 return json.dumps(_control.read_index_list()) 1382 1383 def read_info(self): 1384 """Read the adapter information from the Kernel. 1385 1386 @return the information as a JSON-encoded tuple of: 1387 ( address, bluetooth_version, manufacturer_id, 1388 supported_settings, current_settings, class_of_device, 1389 name, short_name ) 1390 1391 """ 1392 #TODO(howardchung): resolve 'cannot allocate memory' error when 1393 # BluetoothControlSocket idle too long(about 3 secs) 1394 # (b:137603211) 1395 _control = bluetooth_socket.BluetoothControlSocket() 1396 return json.dumps(_control.read_info(0)) 1397 1398 def add_device(self, address, address_type, action): 1399 """Add a device to the Kernel action list. 1400 1401 @param address: Address of the device to add. 1402 @param address_type: Type of device in @address. 1403 @param action: Action to take. 1404 1405 @return on success, a JSON-encoded typle of: 1406 ( address, address_type ), None on failure. 1407 1408 """ 1409 #TODO(howardchung): resolve 'cannot allocate memory' error when 1410 # BluetoothControlSocket idle too long(about 3 secs) 1411 # (b:137603211) 1412 _control = bluetooth_socket.BluetoothControlSocket() 1413 return json.dumps(_control.add_device(0, address, address_type, 1414 action)) 1415 1416 def remove_device(self, address, address_type): 1417 """Remove a device from the Kernel action list. 1418 1419 @param address: Address of the device to remove. 1420 @param address_type: Type of device in @address. 1421 1422 @return on success, a JSON-encoded typle of: 1423 ( address, address_type ), None on failure. 1424 1425 """ 1426 #TODO(howardchung): resolve 'cannot allocate memory' error when 1427 # BluetoothControlSocket idle too long(about 3 secs) 1428 # (b:137603211) 1429 _control = bluetooth_socket.BluetoothControlSocket() 1430 return json.dumps(_control.remove_device(0, address, address_type)) 1431 1432 @xmlrpc_server.dbus_safe(False) 1433 def _get_devices(self): 1434 """Read information about remote devices known to the adapter. 1435 1436 @return the properties of each device in a list 1437 1438 """ 1439 objects = self._bluez.GetManagedObjects( 1440 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=True) 1441 devices = [] 1442 for path, ifaces in six.iteritems(objects): 1443 if self.BLUEZ_DEVICE_IFACE in ifaces: 1444 devices.append(objects[path][self.BLUEZ_DEVICE_IFACE]) 1445 return devices 1446 1447 def _encode_base64_json(self, data): 1448 """Base64 encode and json encode the data. 1449 Required to handle non-ascii data 1450 1451 @param data: data to be base64 and JSON encoded 1452 1453 @return: base64 and JSON encoded data 1454 1455 """ 1456 logging.debug('_encode_base64_json raw data is %s', data) 1457 b64_encoded = utils.base64_recursive_encode(data) 1458 logging.debug('base64 encoded data is %s', b64_encoded) 1459 json_encoded = json.dumps(b64_encoded) 1460 logging.debug('JSON encoded data is %s', json_encoded) 1461 return json_encoded 1462 1463 def get_devices(self): 1464 """Read information about remote devices known to the adapter. 1465 1466 @return the properties of each device as a JSON-encoded array of 1467 dictionaries on success, the value False otherwise. 1468 1469 """ 1470 devices = self._get_devices() 1471 return self._encode_base64_json(devices) 1472 1473 @xmlrpc_server.dbus_safe(None) 1474 def get_device_property(self, address, prop_name): 1475 """Read a property of BT device by directly querying device dbus object 1476 1477 @param address: Address of the device to query 1478 @param prop_name: Property to be queried 1479 1480 @return Base 64 JSON repr of property if device is found and has 1481 property, otherwise None on failure. JSON is a recursive 1482 converter, automatically converting dbus types to python natives 1483 and base64 allows us to pass special characters over xmlrpc. 1484 Decode is done in bluetooth_device.py 1485 """ 1486 1487 prop_val = None 1488 1489 # Grab dbus object, _find_device will catch any thrown dbus error 1490 device_obj = self._find_device(address) 1491 1492 if device_obj: 1493 # Query dbus object for property 1494 prop_val = device_obj.Get(self.BLUEZ_DEVICE_IFACE, 1495 prop_name, 1496 dbus_interface=dbus.PROPERTIES_IFACE) 1497 1498 return self._encode_base64_json(prop_val) 1499 1500 @xmlrpc_server.dbus_safe(None) 1501 def get_battery_property(self, address, prop_name): 1502 """Read a property from Battery1 interface. 1503 1504 @param address: Address of the device to query 1505 @param prop_name: Property to be queried 1506 1507 @return The battery percentage value, or None if does not exist. 1508 """ 1509 1510 prop_val = None 1511 1512 # Grab dbus object, _find_battery will catch any thrown dbus error 1513 battery_obj = self._find_battery(address) 1514 1515 if battery_obj: 1516 # Query dbus object for property 1517 prop_val = battery_obj.Get(self.BLUEZ_BATTERY_IFACE, 1518 prop_name, 1519 dbus_interface=dbus.PROPERTIES_IFACE) 1520 1521 return dbus_util.dbus2primitive(prop_val) 1522 1523 @xmlrpc_server.dbus_safe(False) 1524 def set_discovery_filter(self, filter): 1525 """Set the discovery filter. 1526 1527 @param filter: The discovery filter to set. 1528 1529 @return True on success, False otherwise. 1530 1531 """ 1532 if not self._adapter: 1533 return False 1534 self._adapter.SetDiscoveryFilter( 1535 filter, dbus_interface=self.BLUEZ_ADAPTER_IFACE) 1536 return True 1537 1538 @xmlrpc_server.dbus_safe(False) 1539 @dbus_print_error() 1540 def start_discovery(self): 1541 """Start discovery of remote devices. 1542 1543 Obtain the discovered device information using get_devices(), called 1544 stop_discovery() when done. 1545 1546 @return True on success, False otherwise. 1547 1548 """ 1549 if not self._adapter: 1550 return (False, "Adapter Not Found") 1551 self._adapter.StartDiscovery(dbus_interface=self.BLUEZ_ADAPTER_IFACE) 1552 return (True, None) 1553 1554 @dbus_print_error() 1555 def stop_discovery(self): 1556 """Stop discovery of remote devices. 1557 1558 @return True on success, False otherwise. 1559 1560 """ 1561 if not self._adapter: 1562 return (False, "Adapter Not Found") 1563 self._adapter.StopDiscovery(dbus_interface=self.BLUEZ_ADAPTER_IFACE) 1564 return (True, None) 1565 1566 def get_dev_info(self): 1567 """Read raw HCI device information. 1568 1569 @return JSON-encoded tuple of: 1570 (index, name, address, flags, device_type, bus_type, 1571 features, pkt_type, link_policy, link_mode, 1572 acl_mtu, acl_pkts, sco_mtu, sco_pkts, 1573 err_rx, err_tx, cmd_tx, evt_rx, acl_tx, acl_rx, 1574 sco_tx, sco_rx, byte_rx, byte_tx) on success, 1575 None on failure. 1576 1577 """ 1578 return json.dumps(self._raw.get_dev_info(0)) 1579 1580 @dbus_print_error(None) 1581 def get_supported_capabilities(self): 1582 """ Get supported capabilities of the adapter 1583 1584 @returns (capabilities, None) on Success. (None, <error>) on failure 1585 """ 1586 value = self._adapter.GetSupportedCapabilities( 1587 dbus_interface=self.BLUEZ_ADAPTER_IFACE) 1588 return (json.dumps(value), None) 1589 1590 @xmlrpc_server.dbus_safe(False) 1591 def register_profile(self, path, uuid, options): 1592 """Register new profile (service). 1593 1594 @param path: Path to the profile object. 1595 @param uuid: Service Class ID of the service as string. 1596 @param options: Dictionary of options for the new service, compliant 1597 with BlueZ D-Bus Profile API standard. 1598 1599 @return True on success, False otherwise. 1600 1601 """ 1602 profile_manager = dbus.Interface( 1603 self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, 1604 self.BLUEZ_PROFILE_MANAGER_PATH), 1605 self.BLUEZ_PROFILE_MANAGER_IFACE) 1606 dbus_object = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, 1607 path) 1608 profile_manager.RegisterProfile( 1609 dbus_object, uuid, dbus.Dictionary(options, signature='sv')) 1610 return True 1611 1612 def has_device(self, address): 1613 """Checks if the device with a given address exists. 1614 1615 @param address: Address of the device. 1616 1617 @returns: True if there is an interface object with that address. 1618 False if the device is not found. 1619 1620 @raises: Exception if a D-Bus error is encountered. 1621 1622 """ 1623 result = self._find_device(address) 1624 logging.debug('has_device result: %s', str(result)) 1625 1626 # The result being False indicates that there is a D-Bus error. 1627 if result is False: 1628 raise Exception('dbus.Interface error') 1629 1630 # Return True if the result is not None, e.g. a D-Bus interface object; 1631 # False otherwise. 1632 return bool(result) 1633 1634 @xmlrpc_server.dbus_safe(False) 1635 def _find_device(self, address): 1636 """Finds the device with a given address. 1637 1638 Find the device with a given address and returns the 1639 device interface. 1640 1641 @param address: Address of the device. 1642 1643 @returns: An 'org.bluez.Device1' interface to the device. 1644 None if device can not be found. 1645 """ 1646 path = self._get_device_path(address) 1647 if path: 1648 obj = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, path) 1649 return dbus.Interface(obj, self.BLUEZ_DEVICE_IFACE) 1650 logging.info('Device not found') 1651 return None 1652 1653 @xmlrpc_server.dbus_safe(None) 1654 def _find_battery(self, address): 1655 """Finds the battery with a given address. 1656 1657 Find the battery with a given address and returns the 1658 battery interface. 1659 1660 @param address: Address of the device. 1661 1662 @returns: An 'org.bluez.Battery1' interface to the device. 1663 None if device can not be found. 1664 """ 1665 path = self._get_device_path(address) 1666 if path: 1667 obj = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, path) 1668 return dbus.Interface(obj, self.BLUEZ_BATTERY_IFACE) 1669 logging.info('Battery not found') 1670 return None 1671 1672 @xmlrpc_server.dbus_safe(False) 1673 def _get_device_path(self, address): 1674 """Gets the path for a device with a given address. 1675 1676 Find the device with a given address and returns the 1677 the path for the device. 1678 1679 @param address: Address of the device. 1680 1681 @returns: The path to the address of the device, or None if device is 1682 not found in the object tree. 1683 1684 """ 1685 1686 # Create device path, i.e. '/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF' based 1687 # on path assignment scheme used in bluez 1688 address_up = address.replace(':', '_') 1689 device_path = '{}/dev_{}'.format(self._adapter.object_path, address_up) 1690 1691 # Verify the Address property agrees to confirm we have the device 1692 try: 1693 device = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, 1694 device_path) 1695 found_addr = device.Get(self.BLUEZ_DEVICE_IFACE, 1696 'Address', 1697 dbus_interface=dbus.PROPERTIES_IFACE) 1698 1699 if found_addr == address: 1700 logging.info('Device found at {}'.format(device_path)) 1701 return device_path 1702 1703 except dbus.exceptions.DBusException as e: 1704 log_msg = 'Couldn\'t reach device: {}'.format(str(e)) 1705 logging.debug(log_msg) 1706 1707 logging.debug('No device found at {}'.format(device_path)) 1708 return None 1709 1710 @xmlrpc_server.dbus_safe(False) 1711 def _setup_pairing_agent(self, pin): 1712 """Initializes and resiters a PairingAgent to handle authentication. 1713 1714 @param pin: The pin code this agent will answer. 1715 1716 """ 1717 if self._pairing_agent: 1718 logging.info( 1719 'Removing the old agent before initializing a new one') 1720 self._pairing_agent.remove_from_connection() 1721 self._pairing_agent = None 1722 self._pairing_agent = PairingAgent(pin, self._system_bus, 1723 self.AGENT_PATH) 1724 agent_manager = dbus.Interface( 1725 self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, 1726 self.BLUEZ_AGENT_MANAGER_PATH), 1727 self.BLUEZ_AGENT_MANAGER_IFACE) 1728 try: 1729 agent_obj = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, 1730 self.AGENT_PATH) 1731 agent_manager.RegisterAgent(agent_obj, 1732 dbus.String(self._capability)) 1733 except dbus.exceptions.DBusException as e: 1734 if e.get_dbus_name() == self.BLUEZ_ERROR_ALREADY_EXISTS: 1735 logging.info('Unregistering old agent and registering the new') 1736 agent_manager.UnregisterAgent(agent_obj) 1737 agent_manager.RegisterAgent(agent_obj, 1738 dbus.String(self._capability)) 1739 else: 1740 logging.error('Error setting up pin agent: %s', e) 1741 raise 1742 logging.info('Agent registered: %s', self.AGENT_PATH) 1743 1744 @xmlrpc_server.dbus_safe(False) 1745 def _is_paired(self, device): 1746 """Checks if a device is paired. 1747 1748 @param device: An 'org.bluez.Device1' interface to the device. 1749 1750 @returns: True if device is paired. False otherwise. 1751 1752 """ 1753 props = dbus.Interface(device, dbus.PROPERTIES_IFACE) 1754 paired = props.Get(self.BLUEZ_DEVICE_IFACE, 'Paired') 1755 return bool(paired) 1756 1757 @xmlrpc_server.dbus_safe(False) 1758 def device_is_paired(self, address): 1759 """Checks if a device is paired. 1760 1761 @param address: address of the device. 1762 1763 @returns: True if device is paired. False otherwise. 1764 1765 """ 1766 device = self._find_device(address) 1767 if not device: 1768 logging.error('Device not found') 1769 return False 1770 return self._is_paired(device) 1771 1772 @xmlrpc_server.dbus_safe(False) 1773 def _is_connected(self, device): 1774 """Checks if a device is connected. 1775 1776 @param device: An 'org.bluez.Device1' interface to the device. 1777 1778 @returns: True if device is connected. False otherwise. 1779 1780 """ 1781 props = dbus.Interface(device, dbus.PROPERTIES_IFACE) 1782 connected = props.Get(self.BLUEZ_DEVICE_IFACE, 'Connected') 1783 logging.info('Got connected = %r', connected) 1784 return bool(connected) 1785 1786 @xmlrpc_server.dbus_safe(False) 1787 def _set_trusted_by_device(self, device, trusted=True): 1788 """Set the device trusted by device object. 1789 1790 @param device: the device object to set trusted. 1791 @param trusted: True or False indicating whether to set trusted or not. 1792 1793 @returns: True if successful. False otherwise. 1794 1795 """ 1796 try: 1797 properties = dbus.Interface(device, self.DBUS_PROP_IFACE) 1798 properties.Set(self.BLUEZ_DEVICE_IFACE, 'Trusted', 1799 dbus.Boolean(trusted, variant_level=1)) 1800 return True 1801 except Exception as e: 1802 logging.error('_set_trusted_by_device: %s', e) 1803 except: 1804 logging.error('_set_trusted_by_device: unexpected error') 1805 return False 1806 1807 @xmlrpc_server.dbus_safe(False) 1808 def _set_trusted_by_path(self, device_path, trusted=True): 1809 """Set the device trusted by the device path. 1810 1811 @param device_path: the object path of the device. 1812 @param trusted: True or False indicating whether to set trusted or not. 1813 1814 @returns: True if successful. False otherwise. 1815 1816 """ 1817 try: 1818 device = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, 1819 device_path) 1820 return self._set_trusted_by_device(device, trusted) 1821 except Exception as e: 1822 logging.error('_set_trusted_by_path: %s', e) 1823 except: 1824 logging.error('_set_trusted_by_path: unexpected error') 1825 return False 1826 1827 @xmlrpc_server.dbus_safe(False) 1828 def set_trusted(self, address, trusted=True): 1829 """Set the device trusted by address. 1830 1831 @param address: The bluetooth address of the device. 1832 @param trusted: True or False indicating whether to set trusted or not. 1833 1834 @returns: True if successful. False otherwise. 1835 1836 """ 1837 try: 1838 device = self._find_device(address) 1839 return self._set_trusted_by_device(device, trusted) 1840 except Exception as e: 1841 logging.error('set_trusted: %s', e) 1842 except: 1843 logging.error('set_trusted: unexpected error') 1844 return False 1845 1846 @xmlrpc_server.dbus_safe(False) 1847 def pair_legacy_device(self, address, pin, trusted, timeout=60): 1848 """Pairs a device with a given pin code. 1849 1850 Registers a agent who handles pin code request and 1851 pairs a device with known pin code. After pairing, this function will 1852 automatically connect to the device as well (prevents timing issues 1853 between pairing and connect and reduces overall test execution time). 1854 1855 @param address: Address of the device to pair. 1856 @param pin: The pin code of the device to pair. 1857 @param trusted: indicating whether to set the device trusted. 1858 @param timeout: The timeout in seconds for pairing. 1859 1860 @returns: True on success. False otherwise. 1861 1862 """ 1863 1864 def connect_reply(): 1865 """Handler when connect succeeded.""" 1866 logging.info('Device connected: %s', device_path) 1867 mainloop.quit() 1868 1869 def connect_error(error): 1870 """Handler when connect failed. 1871 1872 @param error: one of the errors defined in org.bluez.Error 1873 representing the error in connect. 1874 """ 1875 logging.error('Connect device failed: %s', error) 1876 mainloop.quit() 1877 1878 def pair_reply(): 1879 """Handler when pairing succeeded.""" 1880 logging.info('Device paired: %s', device_path) 1881 if trusted: 1882 self._set_trusted_by_path(device_path, trusted=True) 1883 logging.info('Device trusted: %s', device_path) 1884 1885 # On finishing pairing, also connect; let connect result exit 1886 # mainloop instead 1887 device.Connect(reply_handler=connect_reply, 1888 error_handler=connect_error, 1889 timeout=timeout * 1000) 1890 1891 def pair_error(error): 1892 """Handler when pairing failed. 1893 1894 @param error: one of errors defined in org.bluez.Error representing 1895 the error in pairing. 1896 1897 """ 1898 try: 1899 error_name = error.get_dbus_name() 1900 if error_name == 'org.freedesktop.DBus.Error.NoReply': 1901 logging.error('Timed out after %d ms. Cancelling pairing.', 1902 timeout) 1903 device.CancelPairing() 1904 else: 1905 logging.error('Pairing device failed: %s', error) 1906 finally: 1907 mainloop.quit() 1908 1909 device = self._find_device(address) 1910 if not device: 1911 logging.error('Device not found') 1912 return False 1913 1914 device_path = device.object_path 1915 logging.info('Device %s is found.', device.object_path) 1916 1917 self._setup_pairing_agent(pin) 1918 mainloop = gobject.MainLoop() 1919 1920 try: 1921 if not self._is_paired(device): 1922 logging.info('Device is not paired. Pair and Connect.') 1923 device.Pair(reply_handler=pair_reply, 1924 error_handler=pair_error, 1925 timeout=timeout * 1000) 1926 mainloop.run() 1927 elif not self._is_connected(device): 1928 logging.info('Device is already paired. Connect.') 1929 device.Connect(reply_handler=connect_reply, 1930 error_handler=connect_error, 1931 timeout=timeout * 1000) 1932 mainloop.run() 1933 except Exception as e: 1934 logging.error('Exception %s in pair_legacy_device', e) 1935 return False 1936 1937 return self._is_paired(device) and self._is_connected(device) 1938 1939 @xmlrpc_server.dbus_safe(False) 1940 def remove_device_object(self, address): 1941 """Removes a device object and the pairing information. 1942 1943 Calls RemoveDevice method to remove remote device 1944 object and the pairing information. 1945 1946 @param address: Address of the device to unpair. 1947 1948 @returns: True on success. False otherwise. 1949 1950 """ 1951 device = self._find_device(address) 1952 if not device: 1953 logging.error('Device not found') 1954 return False 1955 self._adapter.RemoveDevice(device.object_path, 1956 dbus_interface=self.BLUEZ_ADAPTER_IFACE) 1957 return True 1958 1959 @xmlrpc_server.dbus_safe(False) 1960 def connect_device(self, address): 1961 """Connects a device. 1962 1963 Connects a device if it is not connected. 1964 1965 @param address: Address of the device to connect. 1966 1967 @returns: True on success. False otherwise. 1968 1969 """ 1970 device = self._find_device(address) 1971 if not device: 1972 logging.error('Device not found') 1973 return False 1974 if self._is_connected(device): 1975 logging.info('Device is already connected') 1976 return True 1977 device.Connect() 1978 return self._is_connected(device) 1979 1980 @xmlrpc_server.dbus_safe(False) 1981 def device_is_connected(self, address): 1982 """Checks if a device is connected. 1983 1984 @param address: Address of the device to connect. 1985 1986 @returns: True if device is connected. False otherwise. 1987 1988 """ 1989 device = self._find_device(address) 1990 if not device: 1991 logging.error('Device not found') 1992 return False 1993 return self._is_connected(device) 1994 1995 @xmlrpc_server.dbus_safe(False) 1996 def disconnect_device(self, address): 1997 """Disconnects a device. 1998 1999 Disconnects a device if it is connected. 2000 2001 @param address: Address of the device to disconnect. 2002 2003 @returns: True on success. False otherwise. 2004 2005 """ 2006 device = self._find_device(address) 2007 if not device: 2008 logging.error('Device not found') 2009 return False 2010 if not self._is_connected(device): 2011 logging.info('Device is not connected') 2012 return True 2013 device.Disconnect() 2014 return not self._is_connected(device) 2015 2016 @xmlrpc_server.dbus_safe(False) 2017 def _device_services_resolved(self, device): 2018 """Checks if services are resolved. 2019 2020 @param device: An 'org.bluez.Device1' interface to the device. 2021 2022 @returns: True if device is connected. False otherwise. 2023 2024 """ 2025 logging.info('device for services resolved: %s', device) 2026 props = dbus.Interface(device, dbus.PROPERTIES_IFACE) 2027 resolved = props.Get(self.BLUEZ_DEVICE_IFACE, 'ServicesResolved') 2028 logging.info('Services resolved = %r', resolved) 2029 return bool(resolved) 2030 2031 @xmlrpc_server.dbus_safe(False) 2032 def device_services_resolved(self, address): 2033 """Checks if service discovery is complete on a device. 2034 2035 Checks whether service discovery has been completed.. 2036 2037 @param address: Address of the remote device. 2038 2039 @returns: True on success. False otherwise. 2040 2041 """ 2042 device = self._find_device(address) 2043 if not device: 2044 logging.error('Device not found') 2045 return False 2046 2047 if not self._is_connected(device): 2048 logging.info('Device is not connected') 2049 return False 2050 2051 return self._device_services_resolved(device) 2052 2053 def btmon_start(self): 2054 """Start btmon monitoring.""" 2055 self.btmon.start() 2056 2057 def btmon_stop(self): 2058 """Stop btmon monitoring.""" 2059 self.btmon.stop() 2060 2061 def btmon_get(self, search_str, start_str): 2062 """Get btmon output contents. 2063 2064 @param search_str: only lines with search_str would be kept. 2065 @param start_str: all lines before the occurrence of start_str would be 2066 filtered. 2067 2068 @returns: the recorded btmon output. 2069 2070 """ 2071 return self.btmon.get_contents(search_str=search_str, 2072 start_str=start_str) 2073 2074 def btmon_find(self, pattern_str): 2075 """Find if a pattern string exists in btmon output. 2076 2077 @param pattern_str: the pattern string to find. 2078 2079 @returns: True on success. False otherwise. 2080 2081 """ 2082 return self.btmon.find(pattern_str) 2083 2084 def messages_start(self): 2085 """Start messages monitoring. 2086 2087 @returns: True if logging started successfully, else False 2088 """ 2089 2090 try: 2091 self.messages.StartRecording() 2092 return True 2093 2094 except Exception as e: 2095 logging.error('Failed to start log recording with error: %s', e) 2096 2097 return False 2098 2099 def messages_stop(self): 2100 """Stop messages monitoring. 2101 2102 @returns: True if logs were successfully gathered since logging started, 2103 else False 2104 """ 2105 try: 2106 self.messages.StopRecording() 2107 return True 2108 2109 except Exception as e: 2110 logging.error('Failed to stop log recording with error: %s', e) 2111 2112 return False 2113 2114 def messages_find(self, pattern_str): 2115 """Find if a pattern string exists in messages output. 2116 2117 @param pattern_str: the pattern string to find. 2118 2119 @returns: True on success. False otherwise. 2120 2121 """ 2122 return self.messages.LogContains(pattern_str) 2123 2124 @xmlrpc_server.dbus_safe(False) 2125 def dbus_async_method(self, dbus_method, reply_handler, error_handler, 2126 *args): 2127 """Run an async dbus method. 2128 2129 @param dbus_method: the dbus async method to invoke. 2130 @param reply_handler: the reply handler for the dbus method. 2131 @param error_handler: the error handler for the dbus method. 2132 @param *args: additional arguments for the dbus method. 2133 2134 @returns: an empty string '' on success; 2135 None if there is no _advertising interface manager; and 2136 an error string if the dbus method fails or exception occurs 2137 2138 """ 2139 2140 def successful_cb(): 2141 """Called when the dbus_method completed successfully.""" 2142 reply_handler() 2143 self.dbus_cb_msg = '' 2144 self._dbus_mainloop.quit() 2145 2146 def error_cb(error): 2147 """Called when the dbus_method failed.""" 2148 error_handler(error) 2149 self.dbus_cb_msg = str(error) 2150 self._dbus_mainloop.quit() 2151 2152 # Call dbus_method with handlers. 2153 try: 2154 dbus_method(*args, 2155 reply_handler=successful_cb, 2156 error_handler=error_cb) 2157 except Exception as e: 2158 logging.error('Exception %s in dbus_async_method ', e) 2159 return str(e) 2160 2161 self._dbus_mainloop.run() 2162 2163 return self.dbus_cb_msg 2164 2165 2166 def advmon_check_manager_interface_exist(self): 2167 """Check if AdvertisementMonitorManager1 interface is available. 2168 2169 @returns: True if Manager interface is available, False otherwise. 2170 2171 """ 2172 objects = self._bluez.GetManagedObjects( 2173 dbus_interface=self.BLUEZ_MANAGER_IFACE) 2174 for _, ifaces in six.iteritems(objects): 2175 if self.BLUEZ_ADV_MONITOR_MANAGER_IFACE in ifaces: 2176 return True 2177 2178 return False 2179 2180 2181 def advmon_read_supported_types(self): 2182 """Read the Advertisement Monitor supported monitor types. 2183 2184 Reads the value of 'SupportedMonitorTypes' property of the 2185 AdvertisementMonitorManager1 interface on the adapter. 2186 2187 @returns: the list of the supported monitor types. 2188 2189 """ 2190 types = self._adapter.Get(self.BLUEZ_ADV_MONITOR_MANAGER_IFACE, 2191 'SupportedMonitorTypes', 2192 dbus_interface=self.DBUS_PROP_IFACE) 2193 return dbus_util.dbus2primitive(types) 2194 2195 def advmon_read_supported_features(self): 2196 """Read the Advertisement Monitor supported features. 2197 2198 Reads the value of 'SupportedFeatures' property of the 2199 AdvertisementMonitorManager1 interface on the adapter. 2200 2201 @returns: the list of the supported features. 2202 2203 """ 2204 features = self._adapter.Get(self.BLUEZ_ADV_MONITOR_MANAGER_IFACE, 2205 'SupportedFeatures', 2206 dbus_interface=self.DBUS_PROP_IFACE) 2207 return dbus_util.dbus2primitive(features) 2208 2209 def advmon_create_app(self): 2210 """Create an advertisement monitor app. 2211 2212 @returns: app id, once the app is created. 2213 2214 """ 2215 return self.advmon_appmgr.create_app() 2216 2217 def advmon_exit_app(self, app_id): 2218 """Exit an advertisement monitor app. 2219 2220 @param app_id: the app id. 2221 2222 @returns: True on success, False otherwise. 2223 2224 """ 2225 return self.advmon_appmgr.exit_app(app_id) 2226 2227 def advmon_kill_app(self, app_id): 2228 """Kill an advertisement monitor app by sending SIGKILL. 2229 2230 @param app_id: the app id. 2231 2232 @returns: True on success, False otherwise. 2233 2234 """ 2235 return self.advmon_appmgr.kill_app(app_id) 2236 2237 def advmon_register_app(self, app_id): 2238 """Register an advertisement monitor app. 2239 2240 @param app_id: the app id. 2241 2242 @returns: True on success, False otherwise. 2243 2244 """ 2245 return self.advmon_appmgr.register_app(app_id) 2246 2247 def advmon_unregister_app(self, app_id): 2248 """Unregister an advertisement monitor app. 2249 2250 @param app_id: the app id. 2251 2252 @returns: True on success, False otherwise. 2253 2254 """ 2255 return self.advmon_appmgr.unregister_app(app_id) 2256 2257 def advmon_add_monitor(self, app_id, monitor_data): 2258 """Create an Advertisement Monitor object. 2259 2260 @param app_id: the app id. 2261 @param monitor_data: the list containing monitor type, RSSI filter 2262 values and patterns. 2263 2264 @returns: monitor id, once the monitor is created, None otherwise. 2265 2266 """ 2267 return self.advmon_appmgr.add_monitor(app_id, monitor_data) 2268 2269 def advmon_remove_monitor(self, app_id, monitor_id): 2270 """Remove the Advertisement Monitor object. 2271 2272 @param app_id: the app id. 2273 @param monitor_id: the monitor id. 2274 2275 @returns: True on success, False otherwise. 2276 2277 """ 2278 return self.advmon_appmgr.remove_monitor(app_id, monitor_id) 2279 2280 def advmon_get_event_count(self, app_id, monitor_id, event): 2281 """Read the count of a particular event on the given monitor. 2282 2283 @param app_id: the app id. 2284 @param monitor_id: the monitor id. 2285 @param event: name of the specific event or 'All' for all events. 2286 2287 @returns: count of the specific event or dict of counts of all events. 2288 2289 """ 2290 return self.advmon_appmgr.get_event_count(app_id, monitor_id, event) 2291 2292 def advmon_reset_event_count(self, app_id, monitor_id, event): 2293 """Reset the count of a particular event on the given monitor. 2294 2295 @param app_id: the app id. 2296 @param monitor_id: the monitor id. 2297 @param event: name of the specific event or 'All' for all events. 2298 2299 @returns: True on success, False otherwise. 2300 2301 """ 2302 return self.advmon_appmgr.reset_event_count(app_id, monitor_id, event) 2303 2304 def advmon_interleave_scan_logger_start(self): 2305 """ Start interleave logger recording 2306 """ 2307 self.advmon_interleave_logger.StartRecording() 2308 2309 def advmon_interleave_scan_logger_stop(self): 2310 """ Stop interleave logger recording 2311 2312 @returns: True if logs were successfully collected, 2313 False otherwise. 2314 2315 """ 2316 return self.advmon_interleave_logger.StopRecording() 2317 2318 def advmon_interleave_scan_logger_get_records(self): 2319 """ Get records in previous log collections 2320 2321 @returns: a list of records, where each item is a record of 2322 interleave |state| and the |time| the state starts. 2323 |state| could be {'no filter', 'allowlist'} 2324 |time| is system time in sec 2325 2326 """ 2327 return self.advmon_interleave_logger.records 2328 2329 def advmon_interleave_scan_logger_get_cancel_events(self): 2330 """ Get cancel events in previous log collections 2331 2332 @returns: a list of cancel |time| when a interleave cancel event log 2333 was found. 2334 |time| is system time in sec 2335 2336 """ 2337 return self.advmon_interleave_logger.cancel_events 2338 2339 def register_advertisement(self, advertisement_data): 2340 """Register an advertisement. 2341 2342 Note that rpc supports only conformable types. Hence, a 2343 dict about the advertisement is passed as a parameter such 2344 that the advertisement object could be constructed on the host. 2345 2346 @param advertisement_data: a dict of the advertisement to register. 2347 2348 @returns: True on success. False otherwise. 2349 2350 """ 2351 adv = advertisement.Advertisement(self._system_bus, advertisement_data) 2352 self.advertisements.append(adv) 2353 return self.dbus_async_method( 2354 self._advertising.RegisterAdvertisement, 2355 # reply handler 2356 lambda: logging.info('register_advertisement: succeeded.'), 2357 # error handler 2358 lambda error: logging.error( 2359 'register_advertisement: failed: %s', str(error)), 2360 # other arguments 2361 adv.get_path(), 2362 dbus.Dictionary({}, signature='sv')) 2363 2364 def unregister_advertisement(self, advertisement_data): 2365 """Unregister an advertisement. 2366 2367 Note that to unregister an advertisement, it is required to use 2368 the same self._advertising interface manager. This is because 2369 bluez only allows the same sender to invoke UnregisterAdvertisement 2370 method. Hence, watch out that the bluetoothd is not restarted or 2371 self.start_bluetoothd() is not executed between the time span that 2372 an advertisement is registered and unregistered. 2373 2374 @param advertisement_data: a dict of the advertisements to unregister. 2375 2376 @returns: True on success. False otherwise. 2377 2378 """ 2379 path = advertisement_data.get('Path') 2380 for index, adv in enumerate(self.advertisements): 2381 if adv.get_path() == path: 2382 break 2383 else: 2384 logging.error('Fail to find the advertisement under the path: %s', 2385 path) 2386 return False 2387 2388 result = self.dbus_async_method( 2389 self._advertising.UnregisterAdvertisement, 2390 # reply handler 2391 lambda: logging.info('unregister_advertisement: succeeded.'), 2392 # error handler 2393 lambda error: logging.error( 2394 'unregister_advertisement: failed: %s', str(error)), 2395 # other arguments 2396 adv.get_path()) 2397 2398 # Call remove_from_connection() so that the same path could be reused. 2399 adv.remove_from_connection() 2400 del self.advertisements[index] 2401 2402 return result 2403 2404 def set_advertising_intervals(self, min_adv_interval_ms, 2405 max_adv_interval_ms): 2406 """Set advertising intervals. 2407 2408 @param min_adv_interval_ms: the min advertising interval in ms. 2409 @param max_adv_interval_ms: the max advertising interval in ms. 2410 2411 @returns: True on success. False otherwise. 2412 2413 """ 2414 return self.dbus_async_method( 2415 self._advertising.SetAdvertisingIntervals, 2416 # reply handler 2417 lambda: logging.info('set_advertising_intervals: succeeded.'), 2418 # error handler 2419 lambda error: logging.error( 2420 'set_advertising_intervals: failed: %s', str(error)), 2421 # other arguments 2422 dbus.UInt16(min_adv_interval_ms), 2423 dbus.UInt16(max_adv_interval_ms)) 2424 2425 def reset_advertising(self): 2426 """Reset advertising. 2427 2428 This includes un-registering all advertisements, reset advertising 2429 intervals, and disable advertising. 2430 2431 @returns: True on success. False otherwise. 2432 2433 """ 2434 # It is required to execute remove_from_connection() to unregister the 2435 # object-path handler of each advertisement. In this way, we could 2436 # register an advertisement with the same path repeatedly. 2437 for adv in self.advertisements: 2438 adv.remove_from_connection() 2439 del self.advertisements[:] 2440 2441 return self.dbus_async_method( 2442 self._advertising.ResetAdvertising, 2443 # reply handler 2444 lambda: logging.info('reset_advertising: succeeded.'), 2445 # error handler 2446 lambda error: logging.error('reset_advertising: failed: %s', 2447 str(error))) 2448 2449 def create_audio_record_directory(self, audio_record_dir): 2450 """Create the audio recording directory. 2451 2452 @param audio_record_dir: the audio recording directory 2453 2454 @returns: True on success. False otherwise. 2455 """ 2456 try: 2457 if not os.path.exists(audio_record_dir): 2458 os.makedirs(audio_record_dir) 2459 return True 2460 except Exception as e: 2461 logging.error('Failed to create %s on the DUT: %s', 2462 audio_record_dir, e) 2463 return False 2464 2465 def start_capturing_audio_subprocess(self, audio_data, recording_device): 2466 """Start capturing audio in a subprocess. 2467 2468 @param audio_data: the audio test data 2469 @param recording_device: which device recorded the audio, 2470 possible values are 'recorded_by_dut' or 'recorded_by_peer' 2471 2472 @returns: True on success. False otherwise. 2473 """ 2474 audio_data = json.loads(audio_data) 2475 return self._cras_test_client.start_capturing_subprocess( 2476 audio_data[recording_device], 2477 sample_format=audio_data['format'], 2478 channels=audio_data['channels'], 2479 rate=audio_data['rate'], 2480 duration=audio_data['duration']) 2481 2482 def stop_capturing_audio_subprocess(self): 2483 """Stop capturing audio. 2484 2485 @returns: True on success. False otherwise. 2486 """ 2487 return self._cras_test_client.stop_capturing_subprocess() 2488 2489 def _generate_playback_file(self, audio_data): 2490 """Generate the playback file if it does not exist yet. 2491 2492 Some audio test files may be large. Generate them on the fly 2493 to save the storage of the source tree. 2494 2495 @param audio_data: the audio test data 2496 """ 2497 if not os.path.exists(audio_data['file']): 2498 data_format = dict(file_type='raw', 2499 sample_format='S16_LE', 2500 channel=audio_data['channels'], 2501 rate=audio_data['rate']) 2502 2503 # Make the audio file a bit longer to handle any delay 2504 # issue in capturing. 2505 duration = audio_data['duration'] + 3 2506 audio_test_data_module.GenerateAudioTestData( 2507 data_format=data_format, 2508 path=audio_data['file'], 2509 duration_secs=duration, 2510 frequencies=audio_data['frequencies']) 2511 logging.debug("Raw file generated: %s", audio_data['file']) 2512 2513 def start_playing_audio_subprocess(self, audio_data): 2514 """Start playing audio in a subprocess. 2515 2516 @param audio_data: the audio test data 2517 2518 @returns: True on success. False otherwise. 2519 """ 2520 audio_data = json.loads(audio_data) 2521 self._generate_playback_file(audio_data) 2522 try: 2523 return self._cras_test_client.start_playing_subprocess( 2524 audio_data['file'], 2525 channels=audio_data['channels'], 2526 rate=audio_data['rate'], 2527 duration=audio_data['duration']) 2528 except Exception as e: 2529 logging.error("start_playing_subprocess() failed: %s", str(e)) 2530 return False 2531 2532 def stop_playing_audio_subprocess(self): 2533 """Stop playing audio in the subprocess. 2534 2535 @returns: True on success. False otherwise. 2536 """ 2537 return self._cras_test_client.stop_playing_subprocess() 2538 2539 def play_audio(self, audio_data): 2540 """Play audio. 2541 2542 It blocks until it has completed playing back the audio. 2543 2544 @param audio_data: the audio test data 2545 2546 @returns: True on success. False otherwise. 2547 """ 2548 audio_data = json.loads(audio_data) 2549 self._generate_playback_file(audio_data) 2550 return self._cras_test_client.play(audio_data['file'], 2551 channels=audio_data['channels'], 2552 rate=audio_data['rate'], 2553 duration=audio_data['duration']) 2554 2555 def check_audio_frames_legitimacy(self, audio_test_data, recording_device, 2556 recorded_file): 2557 """Get the number of frames in the recorded audio file. 2558 2559 @param audio_test_data: the audio test data 2560 @param recording_device: which device recorded the audio, 2561 possible values are 'recorded_by_dut' or 'recorded_by_peer' 2562 @param recorded_file: the recorded file name 2563 2564 @returns: True if audio frames are legitimate. 2565 """ 2566 if bool(recorded_file): 2567 recorded_filename = recorded_file 2568 else: 2569 audio_test_data = json.loads(audio_test_data) 2570 recorded_filename = audio_test_data[recording_device] 2571 2572 if recorded_filename.endswith('.raw'): 2573 # Make sure that the recorded file does not contain all zeros. 2574 filesize = os.path.getsize(recorded_filename) 2575 cmd_str = 'cmp -s -n %d %s /dev/zero' % (filesize, 2576 recorded_filename) 2577 try: 2578 result = subprocess.call(cmd_str.split()) 2579 return result != 0 2580 except Exception as e: 2581 logging.error("Failed: %s (%s)", cmd_str, str(e)) 2582 return False 2583 else: 2584 # The recorded wav file should not be empty. 2585 wav_file = check_quality.WaveFile(recorded_filename) 2586 return wav_file.get_number_frames() > 0 2587 2588 def convert_audio_sample_rate(self, input_file, out_file, test_data, 2589 new_rate): 2590 """Convert audio file to new sample rate. 2591 2592 @param input_file: Path to file to upsample. 2593 @param out_file: Path to create upsampled file. 2594 @param test_data: Dictionary with information about file. 2595 @param new_rate: New rate to upsample file to. 2596 2597 @returns: True if upsampling succeeded, False otherwise. 2598 """ 2599 test_data = json.loads(test_data) 2600 logging.debug('Resampling file {} to new rate {}'.format( 2601 input_file, new_rate)) 2602 2603 convert_format(input_file, 2604 test_data['channels'], 2605 test_data['bit_width'], 2606 test_data['rate'], 2607 out_file, 2608 test_data['channels'], 2609 test_data['bit_width'], 2610 new_rate, 2611 1.0, 2612 use_src_header=True, 2613 use_dst_header=True) 2614 2615 return os.path.isfile(out_file) 2616 2617 def trim_wav_file(self, 2618 in_file, 2619 out_file, 2620 new_duration, 2621 test_data, 2622 tolerance=0.1): 2623 """Trim long file to desired length. 2624 2625 Trims audio file to length by cutting out silence from beginning and 2626 end. 2627 2628 @param in_file: Path to audio file to be trimmed. 2629 @param out_file: Path to trimmed audio file to create. 2630 @param new_duration: A float representing the desired duration of 2631 the resulting trimmed file. 2632 @param test_data: Dictionary containing information about the test file. 2633 @param tolerance: (optional) A float representing the allowable 2634 difference between trimmed file length and desired duration 2635 2636 @returns: True if file was trimmed successfully, False otherwise. 2637 """ 2638 test_data = json.loads(test_data) 2639 trim_silence_from_wav_file(in_file, out_file, new_duration) 2640 measured_length = get_file_length(out_file, test_data['channels'], 2641 test_data['bit_width'], 2642 test_data['rate']) 2643 return abs(measured_length - new_duration) <= tolerance 2644 2645 def unzip_audio_test_data(self, tar_path, data_dir): 2646 """Unzip audio test data files. 2647 2648 @param tar_path: Path to audio test data tarball on DUT. 2649 @oaram data_dir: Path to directory where to extract test data directory. 2650 2651 @returns: True if audio test data folder exists, False otherwise. 2652 """ 2653 logging.debug('Downloading audio test data on DUT') 2654 # creates path to dir to extract test data to by taking name of the 2655 # tarball without the extension eg. <dir>/file.ext to data_dir/file/ 2656 audio_test_dir = os.path.join( 2657 data_dir, 2658 os.path.split(tar_path)[1].split('.', 1)[0]) 2659 2660 unzip_cmd = 'tar -xf {0} -C {1}'.format(tar_path, data_dir) 2661 2662 unzip_proc = subprocess.Popen(unzip_cmd.split(), 2663 stdout=subprocess.PIPE, 2664 stderr=subprocess.PIPE) 2665 _, stderr = unzip_proc.communicate() 2666 2667 if stderr: 2668 logging.error('Error occurred in unzipping audio data: {}'.format( 2669 str(stderr))) 2670 return False 2671 2672 return unzip_proc.returncode == 0 and os.path.isdir(audio_test_dir) 2673 2674 def convert_raw_to_wav(self, input_file, output_file, test_data): 2675 """Convert raw audio file to wav file. 2676 2677 @oaram input_file: the location of the raw file 2678 @param output_file: the location to place the resulting wav file 2679 @param test_data: the data for the file being converted 2680 2681 @returns: True if conversion was successful otherwise false 2682 """ 2683 test_data = json.loads(test_data) 2684 convert_raw_file(input_file, test_data['channels'], 2685 test_data['bit_width'], test_data['rate'], 2686 output_file) 2687 2688 return os.path.isfile(output_file) 2689 2690 def get_primary_frequencies(self, audio_test_data, recording_device, 2691 recorded_file): 2692 """Get primary frequencies of the audio test file. 2693 2694 @param audio_test_data: the audio test data 2695 @param recording_device: which device recorded the audio, 2696 possible values are 'recorded_by_dut' or 'recorded_by_peer' 2697 @param recorded_file: the recorded file name 2698 2699 @returns: a list of primary frequencies of channels in the audio file 2700 """ 2701 audio_test_data = json.loads(audio_test_data) 2702 2703 if bool(recorded_file): 2704 recorded_filename = recorded_file 2705 else: 2706 recorded_filename = audio_test_data[recording_device] 2707 2708 args = CheckQualityArgsClass(filename=recorded_filename, 2709 rate=audio_test_data['rate'], 2710 channel=audio_test_data['channels'], 2711 bit_width=16) 2712 raw_data, rate = check_quality.read_audio_file(args) 2713 checker = check_quality.QualityChecker(raw_data, rate) 2714 # The highest frequency recorded would be near 24 Khz 2715 # as the max sample rate is 48000 in our tests. 2716 # So let's set ignore_high_freq to be 48000. 2717 checker.do_spectral_analysis(ignore_high_freq=48000, 2718 check_quality=False, 2719 quality_params=None) 2720 spectra = checker._spectrals 2721 primary_freq = [ 2722 float(spectra[i][0][0]) if spectra[i] else 0 2723 for i in range(len(spectra)) 2724 ] 2725 primary_freq.sort() 2726 return primary_freq 2727 2728 def enable_wbs(self, value): 2729 """Enable or disable wideband speech (wbs) per the value. 2730 2731 @param value: True to enable wbs. 2732 2733 @returns: True if the operation succeeds. 2734 """ 2735 return self._cras_test_client.enable_wbs(value) 2736 2737 def set_player_playback_status(self, status): 2738 """Set playback status for the registered media player. 2739 2740 @param status: playback status in string. 2741 2742 """ 2743 return self._cras_test_client.set_player_playback_status(status) 2744 2745 def set_player_position(self, position): 2746 """Set media position for the registered media player. 2747 2748 @param position: position in micro seconds. 2749 2750 """ 2751 return self._cras_test_client.set_player_position(position) 2752 2753 def set_player_metadata(self, metadata): 2754 """Set metadata for the registered media player. 2755 2756 @param metadata: dictionary of media metadata. 2757 2758 """ 2759 return self._cras_test_client.set_player_metadata(metadata) 2760 2761 def set_player_length(self, length): 2762 """Set media length for the registered media player. 2763 2764 Media length is a part of metadata information. However, without 2765 specify its type to int64. dbus-python will guess the variant type to 2766 be int32 by default. Separate it from the metadata function to help 2767 prepare the data differently. 2768 2769 @param length: length in micro seconds. 2770 2771 """ 2772 length_variant = dbus.types.Int64(length, variant_level=1) 2773 length_dict = dbus.Dictionary({'length': length_variant}, 2774 signature='sv') 2775 return self._cras_test_client.set_player_length(length_dict) 2776 2777 def select_input_device(self, device_name): 2778 """Select the audio input device. 2779 2780 @param device_name: the name of the Bluetooth peer device 2781 2782 @returns: True if the operation succeeds. 2783 """ 2784 return self._cras_test_client.select_input_device(device_name) 2785 2786 @xmlrpc_server.dbus_safe(None) 2787 def select_output_node(self, node_type): 2788 """Select the audio output node. 2789 2790 @param node_type: the node type of the Bluetooth peer device 2791 2792 @returns: True if the operation succeeds. 2793 """ 2794 return cras_utils.set_single_selected_output_node(node_type) 2795 2796 @xmlrpc_server.dbus_safe(None) 2797 def get_selected_output_device_type(self): 2798 """Get the selected audio output node type. 2799 2800 @returns: the node type of the selected output device. 2801 """ 2802 # Note: should convert the dbus.String to the regular string. 2803 return str(cras_utils.get_selected_output_device_type()) 2804 2805 def get_gatt_attributes_map(self, address): 2806 """Return a JSON formatted string of the GATT attributes of a device, 2807 keyed by UUID 2808 @param address: a string of the MAC address of the device 2809 2810 @return: JSON formated string, stored the nested structure of the 2811 attributes. Each attribute has 'path' and 2812 ['characteristics' | 'descriptors'], which store their object path and 2813 children respectively. 2814 2815 """ 2816 attribute_map = dict() 2817 2818 device_object_path = self._get_device_path(address) 2819 objects = self._bluez.GetManagedObjects( 2820 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=False) 2821 service_map = self._get_service_map(device_object_path, objects) 2822 2823 servs = dict() 2824 attribute_map['services'] = servs 2825 2826 for uuid, path in service_map.items(): 2827 2828 servs[uuid] = dict() 2829 serv = servs[uuid] 2830 2831 serv['path'] = path 2832 serv['characteristics'] = dict() 2833 chrcs = serv['characteristics'] 2834 2835 chrcs_map = self._get_characteristic_map(path, objects) 2836 for uuid, path in chrcs_map.items(): 2837 chrcs[uuid] = dict() 2838 chrc = chrcs[uuid] 2839 2840 chrc['path'] = path 2841 chrc['descriptors'] = dict() 2842 descs = chrc['descriptors'] 2843 2844 descs_map = self._get_descriptor_map(path, objects) 2845 2846 for uuid, path in descs_map.items(): 2847 descs[uuid] = dict() 2848 desc = descs[uuid] 2849 2850 desc['path'] = path 2851 2852 return json.dumps(attribute_map) 2853 2854 def _get_gatt_interface(self, uuid, object_path, interface): 2855 """Get dbus interface by uuid 2856 @param uuid: a string of uuid 2857 @param object_path: a string of the object path of the service 2858 2859 @return: a dbus interface 2860 """ 2861 2862 return dbus.Interface( 2863 self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, 2864 object_path), interface) 2865 2866 def get_gatt_service_property(self, object_path, property_name): 2867 """Get property from a service attribute 2868 @param object_path: a string of the object path of the service 2869 @param property_name: a string of a property, ex: 'Value', 'UUID' 2870 2871 @return: the property if success, 2872 none otherwise 2873 2874 """ 2875 return self.get_gatt_attribute_property(object_path, 2876 self.BLUEZ_GATT_SERV_IFACE, 2877 property_name) 2878 2879 def get_gatt_characteristic_property(self, object_path, property_name): 2880 """Get property from a characteristic attribute 2881 @param object_path: a string of the object path of the characteristic 2882 @param property_name: a string of a property, ex: 'Value', 'UUID' 2883 2884 @return: the property if success, 2885 none otherwise 2886 2887 """ 2888 return self.get_gatt_attribute_property(object_path, 2889 self.BLUEZ_GATT_CHAR_IFACE, 2890 property_name) 2891 2892 def get_gatt_descriptor_property(self, object_path, property_name): 2893 """Get property from descriptor attribute 2894 @param object_path: a string of the object path of the descriptor 2895 @param property_name: a string of a property, ex: 'Value', 'UUID' 2896 2897 @return: the property if success, 2898 none otherwise 2899 2900 """ 2901 return self.get_gatt_attribute_property(object_path, 2902 self.BLUEZ_GATT_DESC_IFACE, 2903 property_name) 2904 2905 @xmlrpc_server.dbus_safe(None) 2906 def get_gatt_attribute_property(self, object_path, interface, 2907 property_name): 2908 """Get property from attribute 2909 @param object_path: a string of the bject path 2910 @param property_name: a string of a property, ex: 'Value', 'UUID' 2911 2912 @return: the property if success, 2913 none otherwise 2914 2915 """ 2916 gatt_object = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, 2917 object_path) 2918 prop = self._get_dbus_object_property(gatt_object, interface, 2919 property_name) 2920 logging.info(prop) 2921 if isinstance(prop, dbus.ByteArray): 2922 return _dbus_byte_array_to_b64_string(prop) 2923 if isinstance(prop, dbus.Boolean): 2924 return bool(prop) 2925 if isinstance(prop, dbus.String): 2926 return str(prop) 2927 if isinstance(prop, dbus.ObjectPath): 2928 return str(prop) 2929 if isinstance(prop, dbus.Array): 2930 return list(map(str, prop)) 2931 return prop 2932 2933 @xmlrpc_server.dbus_safe(None) 2934 def gatt_characteristic_read_value(self, uuid, object_path): 2935 """Perform method ReadValue on a characteristic attribute 2936 @param uuid: a string of uuid 2937 @param object_path: a string of the object path of the characteristic 2938 2939 @return: base64 string of dbus bytearray 2940 """ 2941 2942 dbus_interface = self._get_gatt_interface(uuid, object_path, 2943 self.BLUEZ_GATT_CHAR_IFACE) 2944 value = dbus_interface.ReadValue(dbus.Dictionary({}, signature='sv')) 2945 return _dbus_byte_array_to_b64_string(value) 2946 2947 @xmlrpc_server.dbus_safe(None) 2948 def gatt_descriptor_read_value(self, uuid, object_path): 2949 """Perform method ReadValue on a descriptor attribute 2950 @param uuid: a string of uuid 2951 @param object_path: a string of the object path of the descriptor 2952 2953 @return: base64 string of dbus bytearray 2954 """ 2955 2956 dbus_interface = self._get_gatt_interface(uuid, object_path, 2957 self.BLUEZ_GATT_DESC_IFACE) 2958 value = dbus_interface.ReadValue(dbus.Dictionary({}, signature='sv')) 2959 return _dbus_byte_array_to_b64_string(value) 2960 2961 @xmlrpc_server.dbus_safe(False) 2962 def _get_attribute_map(self, object_path, dbus_interface, objects): 2963 """Gets a map of object paths under an object path. 2964 2965 Walks the object tree, and returns a map of UUIDs to object paths for 2966 all resolved gatt object. 2967 2968 @param object_path: The object path of the attribute to retrieve 2969 gatt UUIDs and paths from. 2970 @param objects: The managed objects. 2971 2972 @returns: A dictionary of object paths, keyed by UUID. 2973 2974 """ 2975 attr_map = {} 2976 2977 if object_path: 2978 for path, ifaces in six.iteritems(objects): 2979 if (dbus_interface in ifaces and path.startswith(object_path)): 2980 uuid = ifaces[dbus_interface]['UUID'].lower() 2981 attr_map[uuid] = path 2982 2983 else: 2984 logging.warning('object_path %s is not valid', object_path) 2985 2986 return attr_map 2987 2988 def _get_service_map(self, device_path, objects): 2989 """Gets a map of service paths for a device. 2990 2991 @param device_path: the object path of the device. 2992 @param objects: The managed objects. 2993 """ 2994 return self._get_attribute_map(device_path, self.BLUEZ_GATT_SERV_IFACE, 2995 objects) 2996 2997 def _get_characteristic_map(self, serv_path, objects): 2998 """Gets a map of characteristic paths for a service. 2999 3000 @param serv_path: the object path of the service. 3001 @param objects: The managed objects. 3002 """ 3003 return self._get_attribute_map(serv_path, self.BLUEZ_GATT_CHAR_IFACE, 3004 objects) 3005 3006 def _get_descriptor_map(self, chrc_path, objects): 3007 """Gets a map of descriptor paths for a characteristic. 3008 3009 @param chrc_path: the object path of the characteristic. 3010 @param objects: The managed objects. 3011 """ 3012 return self._get_attribute_map(chrc_path, self.BLUEZ_GATT_DESC_IFACE, 3013 objects) 3014 3015 @xmlrpc_server.dbus_safe(None) 3016 def _get_dbus_object_property(self, dbus_object, dbus_interface, 3017 dbus_property): 3018 """Get the property in an object. 3019 3020 @param dbus_object: a dbus object 3021 @param dbus_property: a dbus property of the dbus object, as a string 3022 3023 @return: dbus type object if it success, e.g. dbus.Boolean, dbus.String, 3024 none otherwise 3025 3026 """ 3027 return dbus_object.Get(dbus_interface, 3028 dbus_property, 3029 dbus_interface=dbus.PROPERTIES_IFACE) 3030 3031 @xmlrpc_server.dbus_safe(False) 3032 def get_characteristic_map(self, address): 3033 """Gets a map of characteristic paths for a device. 3034 3035 Walks the object tree, and returns a map of uuids to object paths for 3036 all resolved gatt characteristics. 3037 3038 @param address: The MAC address of the device to retrieve 3039 gatt characteristic uuids and paths from. 3040 3041 @returns: A dictionary of characteristic paths, keyed by uuid. 3042 3043 """ 3044 device_path = self._get_device_path(address) 3045 char_map = {} 3046 3047 if device_path: 3048 objects = self._bluez.GetManagedObjects( 3049 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=False) 3050 3051 for path, ifaces in six.iteritems(objects): 3052 if (self.BLUEZ_GATT_CHAR_IFACE in ifaces 3053 and path.startswith(device_path)): 3054 uuid = ifaces[self.BLUEZ_GATT_CHAR_IFACE]['UUID'].lower() 3055 char_map[uuid] = path 3056 else: 3057 logging.warning('Device %s not in object tree.', address) 3058 3059 return char_map 3060 3061 @xmlrpc_server.dbus_safe(None) 3062 def _get_char_object(self, uuid, address): 3063 """Gets a characteristic object. 3064 3065 Gets a characteristic object for a given UUID and address. 3066 3067 @param uuid: The UUID of the characteristic, as a string. 3068 @param address: The MAC address of the remote device. 3069 3070 @returns: A dbus interface for the characteristic if the uuid/address 3071 is in the object tree. 3072 None if the address/uuid is not found in the object tree. 3073 3074 """ 3075 path = self.get_characteristic_map(address).get(uuid) 3076 if not path: 3077 logging.error("path not found: %s %s", uuid, address) 3078 return None 3079 return dbus.Interface( 3080 self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, path), 3081 self.BLUEZ_GATT_CHAR_IFACE) 3082 3083 @xmlrpc_server.dbus_safe(None) 3084 def read_characteristic(self, uuid, address): 3085 """Reads the value of a gatt characteristic. 3086 3087 Reads the current value of a gatt characteristic. Base64 endcoding is 3088 used for compatibility with the XML RPC interface. 3089 3090 @param uuid: The uuid of the characteristic to read, as a string. 3091 @param address: The MAC address of the remote device. 3092 3093 @returns: A b64 encoded version of a byte array containing the value 3094 if the uuid/address is in the object tree. 3095 None if the uuid/address was not found in the object tree, or 3096 if a DBus exception was raised by the read operation. 3097 3098 """ 3099 char_obj = self._get_char_object(uuid, address) 3100 if char_obj is None: 3101 return None 3102 value = char_obj.ReadValue(dbus.Dictionary({}, signature='sv')) 3103 return _dbus_byte_array_to_b64_string(value) 3104 3105 @xmlrpc_server.dbus_safe(None) 3106 def write_characteristic(self, uuid, address, value): 3107 """Performs a write operation on a gatt characteristic. 3108 3109 Writes to a GATT characteristic on a remote device. Base64 endcoding is 3110 used for compatibility with the XML RPC interface. 3111 3112 @param uuid: The uuid of the characteristic to write to, as a string. 3113 @param address: The MAC address of the remote device, as a string. 3114 @param value: A byte array containing the data to write. 3115 3116 @returns: True if the write operation does not raise an exception. 3117 None if the uuid/address was not found in the object tree, or 3118 if a DBus exception was raised by the write operation. 3119 3120 """ 3121 char_obj = self._get_char_object(uuid, address) 3122 if char_obj is None: 3123 return None 3124 dbus_value = _b64_string_to_dbus_byte_array(value) 3125 char_obj.WriteValue(dbus_value, dbus.Dictionary({}, signature='sv')) 3126 return True 3127 3128 @xmlrpc_server.dbus_safe(None) 3129 def exchange_messages(self, tx_object_path, rx_object_path, value): 3130 """Performs a write operation on a gatt characteristic and wait for 3131 the response on another characteristic. 3132 3133 @param tx_object_path: the object path of the characteristic to write. 3134 @param rx_object_path: the object path of the characteristic ti read. 3135 @param value: A byte array containing the data to write. 3136 3137 @returns: The value of the characteristic to read from. 3138 None if the uuid/address was not found in the object tree, or 3139 if a DBus exception was raised by the write operation. 3140 3141 """ 3142 tx_obj = self._get_gatt_characteristic_object(tx_object_path) 3143 3144 if tx_obj is None: 3145 return None 3146 3147 self._chrc_property = None 3148 3149 self._signal_watch = self._system_bus.add_signal_receiver( 3150 self._property_changed, 3151 signal_name='PropertiesChanged', 3152 path=rx_object_path) 3153 3154 self._timeout_id = gobject.timeout_add( 3155 self.PROPERTY_UPDATE_TIMEOUT_MILLI_SECS, 3156 self._property_wait_timeout) 3157 3158 write_value = _b64_string_to_dbus_byte_array(value) 3159 tx_obj.WriteValue(write_value, dbus.Dictionary({}, signature='sv')) 3160 3161 self._dbus_mainloop.run() 3162 3163 return _dbus_byte_array_to_b64_string(self._chrc_property) 3164 3165 def _property_changed(self, *args, **kwargs): 3166 """Handler for properties changed signal.""" 3167 gobject.source_remove(self._timeout_id) 3168 self._signal_watch.remove() 3169 changed_prop = args 3170 3171 logging.info(changed_prop) 3172 prop_dict = changed_prop[1] 3173 self._chrc_property = prop_dict['Value'] 3174 if self._dbus_mainloop.is_running(): 3175 self._dbus_mainloop.quit() 3176 3177 def _property_wait_timeout(self): 3178 """Timeout handler when waiting for properties update signal.""" 3179 self._signal_watch.remove() 3180 if self._dbus_mainloop.is_running(): 3181 logging.warn("quit main loop due to timeout") 3182 self._dbus_mainloop.quit() 3183 # Return false so that this method will not be called again. 3184 return False 3185 3186 @xmlrpc_server.dbus_safe(False) 3187 def _get_gatt_characteristic_object(self, object_path): 3188 return dbus.Interface( 3189 self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, 3190 object_path), 3191 self.BLUEZ_GATT_CHAR_IFACE) 3192 3193 @xmlrpc_server.dbus_safe(False) 3194 def start_notify(self, object_path, cccd_value): 3195 """Starts the notification session on the gatt characteristic. 3196 3197 @param object_path: the object path of the characteristic. 3198 @param cccd_value: Possible CCCD values include 3199 0x00 - inferred from the remote characteristic's properties 3200 0x01 - notification 3201 0x02 - indication 3202 3203 @returns: True if the operation succeeds. 3204 False if the characteristic is not found, or 3205 if a DBus exception was raised by the operation. 3206 3207 """ 3208 char_obj = self._get_gatt_characteristic_object(object_path) 3209 if char_obj is None: 3210 logging.error("characteristic not found: %s %s", object_path) 3211 return False 3212 3213 try: 3214 char_obj.StartNotify(dbus.Byte(cccd_value)) 3215 return True 3216 except Exception as e: 3217 logging.error('start_notify: %s', e) 3218 except: 3219 logging.error('start_notify: unexpected error') 3220 return False 3221 3222 @xmlrpc_server.dbus_safe(False) 3223 def stop_notify(self, object_path): 3224 """Stops the notification session on the gatt characteristic. 3225 3226 @param object_path: the object path of the characteristic. 3227 3228 @returns: True if the operation succeeds. 3229 False if the characteristic is not found, or 3230 if a DBus exception was raised by the operation. 3231 3232 """ 3233 char_obj = self._get_gatt_characteristic_object(object_path) 3234 if char_obj is None: 3235 logging.error("characteristic not found: %s %s", object_path) 3236 return False 3237 3238 try: 3239 char_obj.StopNotify() 3240 return True 3241 except Exception as e: 3242 logging.error('stop_notify: %s', e) 3243 except: 3244 logging.error('stop_notify: unexpected error') 3245 return False 3246 3247 @xmlrpc_server.dbus_safe(False) 3248 def is_notifying(self, object_path): 3249 """Is the GATT characteristic in a notifying session? 3250 3251 @param object_path: the object path of the characteristic. 3252 3253 @return True if it is in a notification session. False otherwise. 3254 3255 """ 3256 3257 return self.get_gatt_characteristic_property(object_path, 'Notifying') 3258 3259 @xmlrpc_server.dbus_safe(False) 3260 def is_characteristic_path_resolved(self, uuid, address): 3261 """Checks whether a characteristic is in the object tree. 3262 3263 Checks whether a characteristic is curently found in the object tree. 3264 3265 @param uuid: The uuid of the characteristic to search for. 3266 @param address: The MAC address of the device on which to search for 3267 the characteristic. 3268 3269 @returns: True if the characteristic is found. 3270 False if the characteristic path is not found. 3271 3272 """ 3273 return bool(self.get_characteristic_map(address).get(uuid)) 3274 3275 @xmlrpc_server.dbus_safe(False) 3276 def get_connection_info(self, address): 3277 """Get device connection info. 3278 3279 @param address: The MAC address of the device. 3280 3281 @returns: On success, a JSON-encoded tuple of: 3282 ( RSSI, transmit_power, max_transmit_power ) 3283 None otherwise. 3284 3285 """ 3286 plugin_device = self._get_plugin_device_interface(address) 3287 if plugin_device is None: 3288 return None 3289 3290 try: 3291 connection_info = plugin_device.GetConnInfo() 3292 return json.dumps(connection_info) 3293 except Exception as e: 3294 logging.error('get_connection_info: %s', e) 3295 except: 3296 logging.error('get_connection_info: unexpected error') 3297 return None 3298 3299 @xmlrpc_server.dbus_safe(False) 3300 def set_le_connection_parameters(self, address, parameters): 3301 """Set the LE connection parameters. 3302 3303 @param address: The MAC address of the device. 3304 @param parameters: The LE connection parameters to set. 3305 3306 @return: True on success. False otherwise. 3307 3308 """ 3309 plugin_device = self._get_plugin_device_interface(address) 3310 if plugin_device is None: 3311 return False 3312 3313 return not self.dbus_async_method( 3314 plugin_device.SetLEConnectionParameters, 3315 # reply handler 3316 lambda: logging.info('set_le_connection_parameters: succeeded.' 3317 ), 3318 # error handler 3319 lambda error: logging. 3320 error('set_le_connection_parameters: failed: %s', str(error)), 3321 # other arguments 3322 parameters) 3323 3324 @xmlrpc_server.dbus_safe(False) 3325 def _get_plugin_device_interface(self, address): 3326 """Get the BlueZ Chromium device plugin interface. 3327 3328 This interface can be used to issue dbus requests such as 3329 GetConnInfo and SetLEConnectionParameters. 3330 3331 @param address: The MAC address of the device. 3332 3333 @return: On success, the BlueZ Chromium device plugin interface 3334 None otherwise. 3335 3336 """ 3337 path = self._get_device_path(address) 3338 if path is None: 3339 return None 3340 3341 return dbus.Interface( 3342 self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, path), 3343 self.BLUEZ_PLUGIN_DEVICE_IFACE) 3344 3345 def _powerd_last_resume_details(self, before=5, after=0): 3346 """ Look at powerd logs for last suspend/resume attempt. 3347 3348 Note that logs are in reverse order (chronologically). Keep that in mind 3349 for the 'before' and 'after' parameters. 3350 3351 @param before: Number of context lines before search item to show. 3352 @param after: Number of context lines after search item to show. 3353 3354 @return Most recent lines containing suspend resume details or ''. 3355 """ 3356 event_file = '/var/log/power_manager/powerd.LATEST' 3357 3358 # Each powerd_suspend wakeup has a log "powerd_suspend returned 0", 3359 # with the return code of the suspend. We search for the last 3360 # occurrence in the log, and then find the collocated event_count log, 3361 # indicating the wakeup cause. -B option for grep will actually grab the 3362 # *next* 5 logs in time, since we are piping the powerd file backwards 3363 # with tac command 3364 resume_indicator = 'powerd_suspend returned' 3365 cmd = 'tac {} | grep -A {} -B {} -m1 "{}"'.format( 3366 event_file, after, before, resume_indicator) 3367 3368 try: 3369 return utils.run(cmd).stdout 3370 except error.CmdError: 3371 logging.error('Could not locate recent suspend') 3372 3373 return '' 3374 3375 def bt_caused_last_resume(self): 3376 """Checks if last resume from suspend was caused by bluetooth 3377 3378 @return: True if BT wake path was cause of resume, False otherwise 3379 """ 3380 3381 # When the resume cause is printed to powerd log, it omits the 3382 # /power/wakeup portion of wake path 3383 bt_wake_path = self._get_wake_enabled_path() 3384 3385 # If bluetooth does not have a valid wake path, it could not have caused 3386 # the resume 3387 if not bt_wake_path: 3388 return False 3389 3390 bt_wake_path = bt_wake_path.replace('/power/wakeup', '') 3391 3392 last_resume_details = self._powerd_last_resume_details() 3393 3394 # If BT caused wake, there will be a line describing the bt wake 3395 # path's event_count before and after the resume 3396 for line in last_resume_details.split('\n'): 3397 if 'event_count' in line: 3398 logging.info('Checking wake event: {}'.format(line)) 3399 if bt_wake_path in line: 3400 return True 3401 3402 return False 3403 3404 def find_last_suspend_via_powerd_logs(self): 3405 """ Finds the last suspend attempt via powerd logs. 3406 3407 Finds the last suspend attempt using powerd logs by searching backwards 3408 through the logs to find the latest entries with 'powerd_suspend'. If we 3409 can't find a suspend attempt, we return None. 3410 3411 @return: Tuple (suspend start time, suspend end time, suspend result) or 3412 None if we can't find a suspend attempt 3413 """ 3414 # Logs look like this: 3415 # [1102/202036.973853:INFO:daemon.cc(704)] powerd_suspend returned 0 3416 # ... stuff in between ... 3417 # [1102/202025.785372:INFO:suspender.cc(574)] Starting suspend 3418 3419 # Date format for strptime and strftime 3420 date_format = '%m%d/%H%M%S.%f' 3421 date_group_re = '(?P<date>[0-9]+/[0-9]+[.][0-9]+)' 3422 3423 finish_suspend_re = re.compile( 3424 '^\\[{date_regex}' 3425 '.*daemon.*powerd_suspend returned ' 3426 '(?P<exitcode>[0-9]+)'.format(date_regex=date_group_re)) 3427 start_suspend_re = re.compile( 3428 '^\\[{date_regex}.*suspender.*' 3429 'Starting suspend'.format(date_regex=date_group_re)) 3430 3431 now = datetime.now() 3432 last_resume_details = self._powerd_last_resume_details(before=0, 3433 after=8) 3434 if last_resume_details: 3435 start_time, end_time, ret = None, None, None 3436 try: 3437 for line in last_resume_details.split('\n'): 3438 logging.debug('Last suspend search: %s', line) 3439 m = finish_suspend_re.match(line) 3440 if m: 3441 logging.debug('Found suspend end: date(%s) ret(%s)', 3442 m.group('date'), m.group('exitcode')) 3443 end_time = datetime.strptime( 3444 m.group('date'), 3445 date_format).replace(year=now.year) 3446 ret = int(m.group('exitcode')) 3447 3448 m = start_suspend_re.match(line) 3449 if m: 3450 logging.debug('Found suspend start: date(%s)', 3451 m.group('date')) 3452 start_time = datetime.strptime( 3453 m.group('date'), 3454 date_format).replace(year=now.year) 3455 break 3456 3457 if all([x is not None for x in [start_time, end_time, ret]]): 3458 # Return dates in string format due to inconsistency between 3459 # python2/3 usage on host and dut 3460 return (start_time.strftime(self.OUT_DATE_FORMAT), 3461 end_time.strftime(self.OUT_DATE_FORMAT), ret) 3462 else: 3463 logging.error( 3464 'Failed to parse details from last suspend. %s %s %s', 3465 str(start_time), str(end_time), str(ret)) 3466 except Exception as e: 3467 logging.error('Failed to parse last suspend: %s', str(e)) 3468 else: 3469 logging.error('No powerd_suspend attempt found') 3470 3471 return None 3472 3473 def do_suspend(self, seconds, expect_bt_wake): 3474 """Suspend DUT using the power manager. 3475 3476 @param seconds: The number of seconds to suspend the device. 3477 @param expect_bt_wake: Whether we expect bluetooth to wake us from 3478 suspend. If true, we expect this resume will occur early 3479 3480 @throws: SuspendFailure on resume with unexpected timing or wake source. 3481 The raised exception will be handled as a non-zero retcode over the 3482 RPC, signalling for the test to fail. 3483 """ 3484 early_wake = False 3485 try: 3486 sys_power.do_suspend(seconds) 3487 3488 except sys_power.SpuriousWakeupError: 3489 logging.info('Early resume detected...') 3490 early_wake = True 3491 3492 # Handle error conditions based on test expectations, whether resume 3493 # was early, and cause of the resume 3494 bt_caused_wake = self.bt_caused_last_resume() 3495 logging.info('Cause for resume: {}'.format( 3496 'BT' if bt_caused_wake else 'Not BT')) 3497 3498 if not expect_bt_wake and bt_caused_wake: 3499 raise sys_power.SuspendFailure('BT woke us unexpectedly') 3500 3501 # TODO(b/160803597) - Uncomment when BT wake reason is correctly 3502 # captured in powerd log. 3503 # 3504 # if expect_bt_wake and not bt_caused_wake: 3505 # raise sys_power.SuspendFailure('BT should have woken us') 3506 # 3507 # if bt_caused_wake and not early_wake: 3508 # raise sys_power.SuspendFailure('BT wake did not come early') 3509 3510 return True 3511 3512 def get_wlan_vid_pid(self): 3513 """ Return vendor id and product id of the wlan chip on BT/WiFi module 3514 3515 @returns: (vid,pid) on success; (None,None) on failure 3516 """ 3517 vid = None 3518 pid = None 3519 path_template = '/sys/class/net/%s/device/' 3520 for dev_name in ['wlan0', 'mlan0']: 3521 if os.path.exists(path_template % dev_name): 3522 path_v = path_template % dev_name + 'vendor' 3523 path_d = path_template % dev_name + 'device' 3524 logging.debug('Paths are %s %s', path_v, path_d) 3525 try: 3526 vid = open(path_v).read().strip('\n') 3527 pid = open(path_d).read().strip('\n') 3528 break 3529 except Exception as e: 3530 logging.error('Exception %s while reading vid/pid', str(e)) 3531 logging.debug('returning vid:%s pid:%s', vid, pid) 3532 return (vid, pid) 3533 3534 def get_bt_module_name(self): 3535 """ Return bluetooth module name for non-USB devices 3536 3537 @returns '' on failure. On success return chipset name, if found in 3538 dict.Otherwise it returns the raw string read. 3539 """ 3540 # map the string read from device to chipset name 3541 chipset_string_dict = {'qcom,wcn3991-bt\x00': 'WCN3991'} 3542 3543 hci_device = '/sys/class/bluetooth/hci0' 3544 real_path = os.path.realpath(hci_device) 3545 3546 logging.debug('real path is %s', real_path) 3547 if 'usb' in real_path: 3548 return '' 3549 3550 device_path = os.path.join(real_path, 'device', 'of_node', 3551 'compatible') 3552 try: 3553 chipset_string = open(device_path).read() 3554 logging.debug('read string %s from %s', chipset_string, 3555 device_path) 3556 except Exception as e: 3557 logging.error('Exception %s while reading from file', str(e), 3558 device_path) 3559 return '' 3560 3561 if chipset_string in chipset_string_dict: 3562 return chipset_string_dict[chipset_string] 3563 else: 3564 logging.debug("Chipset not known. Returning %s", chipset_string) 3565 return chipset_string 3566 3567 def get_device_time(self): 3568 """ Get the current device time. """ 3569 return datetime.now().strftime(self.OUT_DATE_FORMAT) 3570 3571 def cleanup(self): 3572 """Cleanup before exiting the client xmlrpc process.""" 3573 3574 self.advmon_appmgr.destroy() 3575