1# Copyright (c) 2013 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 5import base64 6import json 7import logging 8 9from autotest_lib.client.bin import utils 10from autotest_lib.client.cros import constants 11from autotest_lib.server import autotest 12 13 14class BluetoothDevice(object): 15 """BluetoothDevice is a thin layer of logic over a remote DUT. 16 17 The Autotest host object representing the remote DUT, passed to this 18 class on initialization, can be accessed from its host property. 19 20 """ 21 22 XMLRPC_BRINGUP_TIMEOUT_SECONDS = 60 23 XMLRPC_LOG_PATH = '/var/log/bluetooth_xmlrpc_device.log' 24 25 def __init__(self, device_host): 26 """Construct a BluetoothDevice. 27 28 @param device_host: host object representing a remote host. 29 30 """ 31 self.host = device_host 32 # Make sure the client library is on the device so that the proxy code 33 # is there when we try to call it. 34 client_at = autotest.Autotest(self.host) 35 client_at.install() 36 # Start up the XML-RPC proxy on the client. 37 self._proxy = self.host.rpc_server_tracker.xmlrpc_connect( 38 constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_COMMAND, 39 constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_PORT, 40 command_name= 41 constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_CLEANUP_PATTERN, 42 ready_test_name= 43 constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_READY_METHOD, 44 timeout_seconds=self.XMLRPC_BRINGUP_TIMEOUT_SECONDS, 45 logfile=self.XMLRPC_LOG_PATH) 46 47 # Get some static information about the bluetooth adapter. 48 properties = self.get_adapter_properties() 49 self.bluez_version = properties.get('Name') 50 self.address = properties.get('Address') 51 self.bluetooth_class = properties.get('Class') 52 self.UUIDs = properties.get('UUIDs') 53 54 55 def start_bluetoothd(self): 56 """start bluetoothd. 57 58 @returns: True if bluetoothd is started correctly. 59 False otherwise. 60 61 """ 62 return self._proxy.start_bluetoothd() 63 64 65 def stop_bluetoothd(self): 66 """stop bluetoothd. 67 68 @returns: True if bluetoothd is stopped correctly. 69 False otherwise. 70 71 """ 72 return self._proxy.stop_bluetoothd() 73 74 75 def is_bluetoothd_running(self): 76 """Is bluetoothd running? 77 78 @returns: True if bluetoothd is running 79 80 """ 81 return self._proxy.is_bluetoothd_running() 82 83 84 def reset_on(self): 85 """Reset the adapter and settings and power up the adapter. 86 87 @return True on success, False otherwise. 88 89 """ 90 return self._proxy.reset_on() 91 92 93 def reset_off(self): 94 """Reset the adapter and settings, leave the adapter powered off. 95 96 @return True on success, False otherwise. 97 98 """ 99 return self._proxy.reset_off() 100 101 102 def has_adapter(self): 103 """@return True if an adapter is present, False if not.""" 104 return self._proxy.has_adapter() 105 106 107 def set_powered(self, powered): 108 """Set the adapter power state. 109 110 @param powered: adapter power state to set (True or False). 111 112 @return True on success, False otherwise. 113 114 """ 115 return self._proxy.set_powered(powered) 116 117 118 def is_powered_on(self): 119 """Is the adapter powered on? 120 121 @returns: True if the adapter is powered on 122 123 """ 124 properties = self.get_adapter_properties() 125 return bool(properties.get(u'Powered')) 126 127 128 def get_hci(self): 129 """Get hci of the adapter; normally, it is 'hci0'. 130 131 @returns: the hci name of the adapter. 132 133 """ 134 dev_info = self.get_dev_info() 135 hci = (dev_info[1] if isinstance(dev_info, list) and 136 len(dev_info) > 1 else None) 137 return hci 138 139 140 def get_address(self): 141 """Get the bluetooth address of the adapter. 142 143 An example of the bluetooth address of the adapter: '6C:29:95:1A:D4:6F' 144 145 @returns: the bluetooth address of the adapter. 146 147 """ 148 return self.address 149 150 151 def get_bluez_version(self): 152 """Get bluez version. 153 154 An exmaple of bluez version: 'BlueZ 5.39' 155 156 @returns: the bluez version 157 158 """ 159 return self.bluez_version 160 161 162 def get_bluetooth_class(self): 163 """Get the bluetooth class of the adapter. 164 165 An example of the bluetooth class of a chromebook: 4718852 166 167 @returns: the bluetooth class. 168 169 """ 170 return self.bluetooth_class 171 172 173 def get_UUIDs(self): 174 """Get the UUIDs. 175 176 An example of UUIDs: 177 [u'00001112-0000-1000-8000-00805f9b34fb', 178 u'00001801-0000-1000-8000-00805f9b34fb', 179 u'0000110a-0000-1000-8000-00805f9b34fb', 180 u'0000111f-0000-1000-8000-00805f9b34fb', 181 u'00001200-0000-1000-8000-00805f9b34fb', 182 u'00001800-0000-1000-8000-00805f9b34fb'] 183 184 @returns: the list of the UUIDs. 185 186 """ 187 return self.UUIDs 188 189 190 def set_discoverable(self, discoverable): 191 """Set the adapter discoverable state. 192 193 @param discoverable: adapter discoverable state to set (True or False). 194 195 @return True on success, False otherwise. 196 197 """ 198 return self._proxy.set_discoverable(discoverable) 199 200 201 def is_discoverable(self): 202 """Is the adapter in the discoverable state? 203 204 @return True if discoverable. False otherwise. 205 206 """ 207 properties = self.get_adapter_properties() 208 return properties.get('Discoverable') == 1 209 210 211 def set_pairable(self, pairable): 212 """Set the adapter pairable state. 213 214 @param pairable: adapter pairable state to set (True or False). 215 216 @return True on success, False otherwise. 217 218 """ 219 return self._proxy.set_pairable(pairable) 220 221 222 def is_pairable(self): 223 """Is the adapter in the pairable state? 224 225 @return True if pairable. False otherwise. 226 227 """ 228 properties = self.get_adapter_properties() 229 return properties.get('Pairable') == 1 230 231 232 def get_adapter_properties(self): 233 """Read the adapter properties from the Bluetooth Daemon. 234 235 An example of the adapter properties looks like 236 {u'Name': u'BlueZ 5.35', 237 u'Alias': u'Chromebook', 238 u'Modalias': u'bluetooth:v00E0p2436d0400', 239 u'Powered': 1, 240 u'DiscoverableTimeout': 180, 241 u'PairableTimeout': 0, 242 u'Discoverable': 0, 243 u'Address': u'6C:29:95:1A:D4:6F', 244 u'Discovering': 0, 245 u'Pairable': 1, 246 u'Class': 4718852, 247 u'UUIDs': [u'00001112-0000-1000-8000-00805f9b34fb', 248 u'00001801-0000-1000-8000-00805f9b34fb', 249 u'0000110a-0000-1000-8000-00805f9b34fb', 250 u'0000111f-0000-1000-8000-00805f9b34fb', 251 u'00001200-0000-1000-8000-00805f9b34fb', 252 u'00001800-0000-1000-8000-00805f9b34fb']} 253 254 @return the properties as a dictionary on success, 255 the value False otherwise. 256 257 """ 258 return json.loads(self._proxy.get_adapter_properties()) 259 260 261 def read_version(self): 262 """Read the version of the management interface from the Kernel. 263 264 @return the version as a tuple of: 265 ( version, revision ) 266 267 """ 268 return json.loads(self._proxy.read_version()) 269 270 271 def read_supported_commands(self): 272 """Read the set of supported commands from the Kernel. 273 274 @return set of supported commands as arrays in a tuple of: 275 ( commands, events ) 276 277 """ 278 return json.loads(self._proxy.read_supported_commands()) 279 280 281 def read_index_list(self): 282 """Read the list of currently known controllers from the Kernel. 283 284 @return array of controller indexes. 285 286 """ 287 return json.loads(self._proxy.read_index_list()) 288 289 290 def read_info(self): 291 """Read the adapter information from the Kernel. 292 293 An example of the adapter information looks like 294 [u'6C:29:95:1A:D4:6F', 6, 2, 65535, 2769, 4718852, u'Chromebook', u''] 295 296 @return the information as a tuple of: 297 ( address, bluetooth_version, manufacturer_id, 298 supported_settings, current_settings, class_of_device, 299 name, short_name ) 300 301 """ 302 return json.loads(self._proxy.read_info()) 303 304 305 def add_device(self, address, address_type, action): 306 """Add a device to the Kernel action list. 307 308 @param address: Address of the device to add. 309 @param address_type: Type of device in @address. 310 @param action: Action to take. 311 312 @return tuple of ( address, address_type ) on success, 313 None on failure. 314 315 """ 316 return json.loads(self._proxy.add_device(address, address_type, action)) 317 318 319 def remove_device(self, address, address_type): 320 """Remove a device from the Kernel action list. 321 322 @param address: Address of the device to remove. 323 @param address_type: Type of device in @address. 324 325 @return tuple of ( address, address_type ) on success, 326 None on failure. 327 328 """ 329 return json.loads(self._proxy.remove_device(address, address_type)) 330 331 def _decode_json_base64(self, data): 332 """Load serialized JSON and then base64 decode it 333 334 Required to handle non-ascii data 335 @param data: data to be JSON and base64 decode 336 337 @return : JSON and base64 decoded date 338 339 340 """ 341 logging.debug("_decode_json_base64 raw data is %s", data) 342 json_encoded = json.loads(data) 343 logging.debug("JSON encoded data is %s", json_encoded) 344 base64_decoded = utils.base64_recursive_decode(json_encoded) 345 logging.debug("base64 decoded data is %s", base64_decoded) 346 return base64_decoded 347 348 def get_devices(self): 349 """Read information about remote devices known to the adapter. 350 351 An example of the device information of RN-42 looks like 352 [{u'Name': u'RNBT-A96F', 353 u'Alias': u'RNBT-A96F', 354 u'Adapter': u'/org/bluez/hci0', 355 u'LegacyPairing': 0, 356 u'Paired': 1, 357 u'Connected': 0, 358 u'UUIDs': [u'00001124-0000-1000-8000-00805f9b34fb'], 359 u'Address': u'00:06:66:75:A9:6F', 360 u'Icon': u'input-mouse', 361 u'Class': 1408, 362 u'Trusted': 1, 363 u'Blocked': 0}] 364 365 @return the properties of each device as an array of 366 dictionaries on success, the value False otherwise. 367 368 """ 369 encoded_devices = self._proxy.get_devices() 370 return self._decode_json_base64(encoded_devices) 371 372 373 def get_device_properties(self, address): 374 """Read information about remote devices known to the adapter. 375 376 An example of the device information of RN-42 looks like 377 378 @param address: Address of the device to pair. 379 380 @returns: a dictionary of device properties of the device on success; 381 an empty dictionary otherwise. 382 383 """ 384 encoded_devices = self._proxy.get_device_by_address(address) 385 return self._decode_json_base64(encoded_devices) 386 387 388 def start_discovery(self): 389 """Start discovery of remote devices. 390 391 Obtain the discovered device information using get_devices(), called 392 stop_discovery() when done. 393 394 @return True on success, False otherwise. 395 396 """ 397 return self._proxy.start_discovery() 398 399 400 def stop_discovery(self): 401 """Stop discovery of remote devices. 402 403 @return True on success, False otherwise. 404 405 """ 406 return self._proxy.stop_discovery() 407 408 409 def is_discovering(self): 410 """Is it discovering? 411 412 @return True if it is discovering. False otherwise. 413 414 """ 415 return self.get_adapter_properties().get('Discovering') == 1 416 417 418 def get_dev_info(self): 419 """Read raw HCI device information. 420 421 An example of the device information looks like: 422 [0, u'hci0', u'6C:29:95:1A:D4:6F', 13, 0, 1, 581900950526, 52472, 7, 423 32768, 1021, 5, 96, 6, 0, 0, 151, 151, 0, 0, 0, 0, 1968, 12507] 424 425 @return tuple of (index, name, address, flags, device_type, bus_type, 426 features, pkt_type, link_policy, link_mode, 427 acl_mtu, acl_pkts, sco_mtu, sco_pkts, 428 err_rx, err_tx, cmd_tx, evt_rx, acl_tx, acl_rx, 429 sco_tx, sco_rx, byte_rx, byte_tx) on success, 430 None on failure. 431 432 """ 433 return json.loads(self._proxy.get_dev_info()) 434 435 436 def register_profile(self, path, uuid, options): 437 """Register new profile (service). 438 439 @param path: Path to the profile object. 440 @param uuid: Service Class ID of the service as string. 441 @param options: Dictionary of options for the new service, compliant 442 with BlueZ D-Bus Profile API standard. 443 444 @return True on success, False otherwise. 445 446 """ 447 return self._proxy.register_profile(path, uuid, options) 448 449 450 def has_device(self, address): 451 """Checks if the device with a given address exists. 452 453 @param address: Address of the device. 454 455 @returns: True if there is a device with that address. 456 False otherwise. 457 458 """ 459 return self._proxy.has_device(address) 460 461 462 def device_is_paired(self, address): 463 """Checks if a device is paired. 464 465 @param address: address of the device. 466 467 @returns: True if device is paired. False otherwise. 468 469 """ 470 return self._proxy.device_is_paired(address) 471 472 473 def device_services_resolved(self, address): 474 """Checks if services are resolved for a device. 475 476 @param address: address of the device. 477 478 @returns: True if services are resolved. False otherwise. 479 480 """ 481 return self._proxy.device_services_resolved(address) 482 483 484 def set_trusted(self, address, trusted=True): 485 """Set the device trusted. 486 487 @param address: The bluetooth address of the device. 488 @param trusted: True or False indicating whether to set trusted or not. 489 490 @returns: True if successful. False otherwise. 491 492 """ 493 return self._proxy.set_trusted(address, trusted) 494 495 496 def pair_legacy_device(self, address, pin, trusted, timeout): 497 """Pairs a device with a given pin code. 498 499 Registers an agent who handles pin code request and 500 pairs a device with known pin code. 501 502 @param address: Address of the device to pair. 503 @param pin: The pin code of the device to pair. 504 @param trusted: indicating whether to set the device trusted. 505 @param timeout: The timeout in seconds for pairing. 506 507 @returns: True on success. False otherwise. 508 509 """ 510 return self._proxy.pair_legacy_device(address, pin, trusted, timeout) 511 512 513 def remove_device_object(self, address): 514 """Removes a device object and the pairing information. 515 516 Calls RemoveDevice method to remove remote device 517 object and the pairing information. 518 519 @param address: address of the device to unpair. 520 521 @returns: True on success. False otherwise. 522 523 """ 524 return self._proxy.remove_device_object(address) 525 526 527 def connect_device(self, address): 528 """Connects a device. 529 530 Connects a device if it is not connected. 531 532 @param address: Address of the device to connect. 533 534 @returns: True on success. False otherwise. 535 536 """ 537 return self._proxy.connect_device(address) 538 539 540 def device_is_connected(self, address): 541 """Checks if a device is connected. 542 543 @param address: Address of the device to check if it is connected. 544 545 @returns: True if device is connected. False otherwise. 546 547 """ 548 return self._proxy.device_is_connected(address) 549 550 551 def disconnect_device(self, address): 552 """Disconnects a device. 553 554 Disconnects a device if it is connected. 555 556 @param address: Address of the device to disconnect. 557 558 @returns: True on success. False otherwise. 559 560 """ 561 return self._proxy.disconnect_device(address) 562 563 564 def btmon_start(self): 565 """Start btmon monitoring.""" 566 self._proxy.btmon_start() 567 568 569 def btmon_stop(self): 570 """Stop btmon monitoring.""" 571 self._proxy.btmon_stop() 572 573 574 def btmon_get(self, search_str='', start_str=''): 575 """Get btmon output contents. 576 577 @param search_str: only lines with search_str would be kept. 578 @param start_str: all lines before the occurrence of start_str would be 579 filtered. 580 581 @returns: the recorded btmon output. 582 583 """ 584 return self._proxy.btmon_get(search_str, start_str) 585 586 587 def btmon_find(self, pattern_str): 588 """Find if a pattern string exists in btmon output. 589 590 @param pattern_str: the pattern string to find. 591 592 @returns: True on success. False otherwise. 593 594 """ 595 return self._proxy.btmon_find(pattern_str) 596 597 598 def register_advertisement(self, advertisement_data): 599 """Register an advertisement. 600 601 Note that rpc supports only conformable types. Hence, a 602 dict about the advertisement is passed as a parameter such 603 that the advertisement object could be contructed on the host. 604 605 @param advertisement_data: a dict of the advertisement for 606 the adapter to register. 607 608 @returns: True on success. False otherwise. 609 610 """ 611 return self._proxy.register_advertisement(advertisement_data) 612 613 614 def unregister_advertisement(self, advertisement_data): 615 """Unregister an advertisement. 616 617 @param advertisement_data: a dict of the advertisement to unregister. 618 619 @returns: True on success. False otherwise. 620 621 """ 622 return self._proxy.unregister_advertisement(advertisement_data) 623 624 625 def set_advertising_intervals(self, min_adv_interval_ms, 626 max_adv_interval_ms): 627 """Set advertising intervals. 628 629 @param min_adv_interval_ms: the min advertising interval in ms. 630 @param max_adv_interval_ms: the max advertising interval in ms. 631 632 @returns: True on success. False otherwise. 633 634 """ 635 return self._proxy.set_advertising_intervals(min_adv_interval_ms, 636 max_adv_interval_ms) 637 638 639 def reset_advertising(self): 640 """Reset advertising. 641 642 This includes unregister all advertisements, reset advertising 643 intervals, and disable advertising. 644 645 @returns: True on success. False otherwise. 646 647 """ 648 return self._proxy.reset_advertising() 649 650 651 def read_characteristic(self, uuid, address): 652 """Reads the value of a gatt characteristic. 653 654 Reads the current value of a gatt characteristic. 655 656 @param uuid: The uuid of the characteristic to read, as a string. 657 @param address: The MAC address of the remote device. 658 659 @returns: A byte array containing the value of the if the uuid/address 660 was found in the object tree. 661 None if the uuid/address was not found in the object tree, or 662 if a DBus exception was raised by the read operation. 663 664 """ 665 value = self._proxy.read_characteristic(uuid, address) 666 if value is None: 667 return None 668 return bytearray(base64.standard_b64decode(value)) 669 670 671 def write_characteristic(self, uuid, address, bytes_to_write): 672 """Performs a write operation on a gatt characteristic. 673 674 Writes to a GATT characteristic on a remote device. 675 676 @param uuid: The uuid of the characteristic to write to, as a string. 677 @param address: The MAC address of the remote device, as a string. 678 @param bytes_to_write: A byte array containing the data to write. 679 680 @returns: True if the write operation does not raise an exception. 681 None if the uuid/address was not found in the object tree, or 682 if a DBus exception was raised by the write operation. 683 684 """ 685 return self._proxy.write_characteristic( 686 uuid, address, base64.standard_b64encode(bytes_to_write)) 687 688 689 def is_characteristic_path_resolved(self, uuid, address): 690 """Checks whether a characteristic is in the object tree. 691 692 Checks whether a characteristic is curently found in the object tree. 693 694 @param uuid: The uuid of the characteristic to search for. 695 @param address: The MAC address of the device on which to search for 696 the characteristic. 697 698 @returns: True if the characteristic is found, False otherwise. 699 700 """ 701 return self._proxy.is_characteristic_path_resolved(uuid, address) 702 703 704 def copy_logs(self, destination): 705 """Copy the logs generated by this device to a given location. 706 707 @param destination: destination directory for the logs. 708 709 """ 710 self.host.collect_logs(self.XMLRPC_LOG_PATH, destination) 711 712 713 def close(self, close_host=True): 714 """Tear down state associated with the client. 715 716 @param close_host: If True, shut down the xml rpc server by closing the 717 underlying host object (which also shuts down all other xml rpc 718 servers running on the DUT). Otherwise, only shut down the 719 bluetooth device xml rpc server, which can be desirable if the host 720 object and/or other xml rpc servers need to be used afterwards. 721 """ 722 # Turn off the discoverable flag since it may affect future tests. 723 self._proxy.set_discoverable(False) 724 # Leave the adapter powered off, but don't do a full reset. 725 self._proxy.set_powered(False) 726 # This kills the RPC server. 727 if close_host: 728 self.host.close() 729 else: 730 self.host.rpc_server_tracker.disconnect( 731 constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_PORT) 732