Home | History | Annotate | Download | only in bluetooth
      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 base64
      6 import json
      7 
      8 from autotest_lib.client.cros import constants
      9 from autotest_lib.server import autotest
     10 
     11 
     12 class 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