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