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 import logging
      8 
      9 from autotest_lib.client.bin import utils
     10 from autotest_lib.client.cros import constants
     11 from autotest_lib.server import autotest
     12 
     13 
     14 class 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