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