Home | History | Annotate | Download | only in bluetooth
      1 #!/usr/bin/env python
      2 
      3 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      4 # Use of this source code is governed by a BSD-style license that can be
      5 # found in the LICENSE file.
      6 
      7 import base64
      8 import dbus
      9 import dbus.mainloop.glib
     10 import dbus.service
     11 import gobject
     12 import json
     13 import logging
     14 import logging.handlers
     15 import os
     16 import shutil
     17 
     18 import common
     19 from autotest_lib.client.bin import utils
     20 from autotest_lib.client.common_lib.cros.bluetooth import bluetooth_socket
     21 from autotest_lib.client.cros import constants
     22 from autotest_lib.client.cros import xmlrpc_server
     23 from autotest_lib.client.cros.bluetooth import advertisement
     24 from autotest_lib.client.cros.bluetooth import output_recorder
     25 
     26 
     27 def _dbus_byte_array_to_b64_string(dbus_byte_array):
     28     """Base64 encodes a dbus byte array for use with the xml rpc proxy."""
     29     return base64.standard_b64encode(bytearray(dbus_byte_array))
     30 
     31 
     32 def _b64_string_to_dbus_byte_array(b64_string):
     33   """Base64 decodes a dbus byte array for use with the xml rpc proxy."""
     34   dbus_array = dbus.Array([], signature=dbus.Signature('y'))
     35   bytes = bytearray(base64.standard_b64decode(b64_string))
     36   for byte in bytes:
     37     dbus_array.append(dbus.Byte(byte))
     38   return dbus_array
     39 
     40 
     41 class PairingAgent(dbus.service.Object):
     42     """The agent handling the authentication process of bluetooth pairing.
     43 
     44     PairingAgent overrides RequestPinCode method to return a given pin code.
     45     User can use this agent to pair bluetooth device which has a known
     46     pin code.
     47 
     48     TODO (josephsih): more pairing modes other than pin code would be
     49     supported later.
     50 
     51     """
     52 
     53     def __init__(self, pin, *args, **kwargs):
     54         super(PairingAgent, self).__init__(*args, **kwargs)
     55         self._pin = pin
     56 
     57 
     58     @dbus.service.method('org.bluez.Agent1',
     59                          in_signature='o', out_signature='s')
     60     def RequestPinCode(self, device_path):
     61         """Requests pin code for a device.
     62 
     63         Returns the known pin code for the request.
     64 
     65         @param device_path: The object path of the device.
     66 
     67         @returns: The known pin code.
     68 
     69         """
     70         logging.info('RequestPinCode for %s; return %s', device_path, self._pin)
     71         return self._pin
     72 
     73 
     74 class BluetoothDeviceXmlRpcDelegate(xmlrpc_server.XmlRpcDelegate):
     75     """Exposes DUT methods called remotely during Bluetooth autotests.
     76 
     77     All instance methods of this object without a preceding '_' are exposed via
     78     an XML-RPC server. This is not a stateless handler object, which means that
     79     if you store state inside the delegate, that state will remain around for
     80     future calls.
     81     """
     82 
     83     UPSTART_PATH = 'unix:abstract=/com/ubuntu/upstart'
     84     UPSTART_MANAGER_PATH = '/com/ubuntu/Upstart'
     85     UPSTART_MANAGER_IFACE = 'com.ubuntu.Upstart0_6'
     86     UPSTART_JOB_IFACE = 'com.ubuntu.Upstart0_6.Job'
     87 
     88     UPSTART_ERROR_UNKNOWNINSTANCE = \
     89             'com.ubuntu.Upstart0_6.Error.UnknownInstance'
     90     UPSTART_ERROR_ALREADYSTARTED = \
     91             'com.ubuntu.Upstart0_6.Error.AlreadyStarted'
     92 
     93     BLUETOOTHD_JOB = 'bluetoothd'
     94 
     95     DBUS_ERROR_SERVICEUNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown'
     96 
     97     BLUEZ_SERVICE_NAME = 'org.bluez'
     98     BLUEZ_MANAGER_PATH = '/'
     99     BLUEZ_MANAGER_IFACE = 'org.freedesktop.DBus.ObjectManager'
    100     BLUEZ_ADAPTER_IFACE = 'org.bluez.Adapter1'
    101     BLUEZ_DEVICE_IFACE = 'org.bluez.Device1'
    102     BLUEZ_GATT_IFACE = 'org.bluez.GattCharacteristic1'
    103     BLUEZ_LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
    104     BLUEZ_AGENT_MANAGER_PATH = '/org/bluez'
    105     BLUEZ_AGENT_MANAGER_IFACE = 'org.bluez.AgentManager1'
    106     BLUEZ_PROFILE_MANAGER_PATH = '/org/bluez'
    107     BLUEZ_PROFILE_MANAGER_IFACE = 'org.bluez.ProfileManager1'
    108     BLUEZ_ERROR_ALREADY_EXISTS = 'org.bluez.Error.AlreadyExists'
    109     DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
    110     AGENT_PATH = '/test/agent'
    111 
    112     BLUETOOTH_LIBDIR = '/var/lib/bluetooth'
    113     BTMON_STOP_DELAY_SECS = 3
    114 
    115     # Timeout for how long we'll wait for BlueZ and the Adapter to show up
    116     # after reset.
    117     ADAPTER_TIMEOUT = 30
    118 
    119     def __init__(self):
    120         super(BluetoothDeviceXmlRpcDelegate, self).__init__()
    121 
    122         # Open the Bluetooth Raw socket to the kernel which provides us direct,
    123         # raw, access to the HCI controller.
    124         self._raw = bluetooth_socket.BluetoothRawSocket()
    125 
    126         # Open the Bluetooth Control socket to the kernel which provides us
    127         # raw management access to the Bluetooth Host Subsystem. Read the list
    128         # of adapter indexes to determine whether or not this device has a
    129         # Bluetooth Adapter or not.
    130         self._control = bluetooth_socket.BluetoothControlSocket()
    131         self._has_adapter = len(self._control.read_index_list()) > 0
    132 
    133         # Set up the connection to Upstart so we can start and stop services
    134         # and fetch the bluetoothd job.
    135         self._upstart_conn = dbus.connection.Connection(self.UPSTART_PATH)
    136         self._upstart = self._upstart_conn.get_object(
    137                 None,
    138                 self.UPSTART_MANAGER_PATH)
    139 
    140         bluetoothd_path = self._upstart.GetJobByName(
    141                 self.BLUETOOTHD_JOB,
    142                 dbus_interface=self.UPSTART_MANAGER_IFACE)
    143         self._bluetoothd = self._upstart_conn.get_object(
    144                 None,
    145                 bluetoothd_path)
    146 
    147         # Arrange for the GLib main loop to be the default.
    148         dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    149 
    150         # Set up the connection to the D-Bus System Bus, get the object for
    151         # the Bluetooth Userspace Daemon (BlueZ) and that daemon's object for
    152         # the Bluetooth Adapter, and the advertising manager.
    153         self._system_bus = dbus.SystemBus()
    154         self._update_bluez()
    155         self._update_adapter()
    156         self._update_advertising()
    157 
    158         # The agent to handle pin code request, which will be
    159         # created when user calls pair_legacy_device method.
    160         self._pairing_agent = None
    161         # The default capability of the agent.
    162         self._capability = 'KeyboardDisplay'
    163 
    164         # Initailize a btmon object to record bluetoothd's activity.
    165         self.btmon = output_recorder.OutputRecorder(
    166                 'btmon', stop_delay_secs=self.BTMON_STOP_DELAY_SECS)
    167 
    168         self.advertisements = []
    169         self._adv_mainloop = gobject.MainLoop()
    170 
    171 
    172     @xmlrpc_server.dbus_safe(False)
    173     def start_bluetoothd(self):
    174         """start bluetoothd.
    175 
    176         This includes powering up the adapter.
    177 
    178         @returns: True if bluetoothd is started correctly.
    179                   False otherwise.
    180 
    181         """
    182         try:
    183             self._bluetoothd.Start(dbus.Array(signature='s'), True,
    184                                    dbus_interface=self.UPSTART_JOB_IFACE)
    185         except dbus.exceptions.DBusException as e:
    186             # if bluetoothd was already started, the exception looks like
    187             #     dbus.exceptions.DBusException:
    188             #     com.ubuntu.Upstart0_6.Error.AlreadyStarted: Job is already
    189             #     running: bluetoothd
    190             if e.get_dbus_name() != self.UPSTART_ERROR_ALREADYSTARTED:
    191                 logging.error('Error starting bluetoothd: %s', e)
    192                 return False
    193 
    194         logging.debug('waiting for bluez start')
    195         try:
    196             utils.poll_for_condition(
    197                     condition=self._update_bluez,
    198                     desc='Bluetooth Daemon has started.',
    199                     timeout=self.ADAPTER_TIMEOUT)
    200         except Exception as e:
    201             logging.error('timeout: error starting bluetoothd: %s', e)
    202             return False
    203 
    204         # Waiting for the self._adapter object.
    205         # This does not mean that the adapter is powered on.
    206         logging.debug('waiting for bluez to obtain adapter information')
    207         try:
    208             utils.poll_for_condition(
    209                     condition=self._update_adapter,
    210                     desc='Bluetooth Daemon has adapter information.',
    211                     timeout=self.ADAPTER_TIMEOUT)
    212         except Exception as e:
    213             logging.error('timeout: error starting adapter: %s', e)
    214             return False
    215 
    216         # Waiting for the self._advertising interface object.
    217         logging.debug('waiting for bluez to obtain interface manager.')
    218         try:
    219             utils.poll_for_condition(
    220                     condition=self._update_advertising,
    221                     desc='Bluetooth Daemon has advertising interface.',
    222                     timeout=self.ADAPTER_TIMEOUT)
    223         except utils.TimeoutError:
    224             logging.error('timeout: error getting advertising interface')
    225             return False
    226 
    227         return True
    228 
    229 
    230     @xmlrpc_server.dbus_safe(False)
    231     def stop_bluetoothd(self):
    232         """stop bluetoothd.
    233 
    234         @returns: True if bluetoothd is stopped correctly.
    235                   False otherwise.
    236 
    237         """
    238         def bluez_stopped():
    239             """Checks the bluetooth daemon status.
    240 
    241             @returns: True if bluez is stopped. False otherwise.
    242 
    243             """
    244             return not self._update_bluez()
    245 
    246         try:
    247             self._bluetoothd.Stop(dbus.Array(signature='s'), True,
    248                                   dbus_interface=self.UPSTART_JOB_IFACE)
    249         except dbus.exceptions.DBusException as e:
    250             # If bluetoothd was stopped already, the exception looks like
    251             #    dbus.exceptions.DBusException:
    252             #    com.ubuntu.Upstart0_6.Error.UnknownInstance: Unknown instance:
    253             if e.get_dbus_name() != self.UPSTART_ERROR_UNKNOWNINSTANCE:
    254                 logging.error('Error stopping bluetoothd!')
    255                 return False
    256 
    257         logging.debug('waiting for bluez stop')
    258         try:
    259             utils.poll_for_condition(
    260                     condition=bluez_stopped,
    261                     desc='Bluetooth Daemon has stopped.',
    262                     timeout=self.ADAPTER_TIMEOUT)
    263             bluetoothd_stopped = True
    264         except Exception as e:
    265             logging.error('timeout: error stopping bluetoothd: %s', e)
    266             bluetoothd_stopped = False
    267 
    268         return bluetoothd_stopped
    269 
    270 
    271     def is_bluetoothd_running(self):
    272         """Is bluetoothd running?
    273 
    274         @returns: True if bluetoothd is running
    275 
    276         """
    277         return bool(self._get_dbus_proxy_for_bluetoothd())
    278 
    279 
    280     def _update_bluez(self):
    281         """Store a D-Bus proxy for the Bluetooth daemon in self._bluez.
    282 
    283         This may be called in a loop until it returns True to wait for the
    284         daemon to be ready after it has been started.
    285 
    286         @return True on success, False otherwise.
    287 
    288         """
    289         self._bluez = self._get_dbus_proxy_for_bluetoothd()
    290         return bool(self._bluez)
    291 
    292 
    293     @xmlrpc_server.dbus_safe(False)
    294     def _get_dbus_proxy_for_bluetoothd(self):
    295         """Get the D-Bus proxy for the Bluetooth daemon.
    296 
    297         @return True on success, False otherwise.
    298 
    299         """
    300         bluez = None
    301         try:
    302             bluez = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME,
    303                                                 self.BLUEZ_MANAGER_PATH)
    304             logging.debug('bluetoothd is running')
    305         except dbus.exceptions.DBusException as e:
    306             # When bluetoothd is not running, the exception looks like
    307             #     dbus.exceptions.DBusException:
    308             #     org.freedesktop.DBus.Error.ServiceUnknown: The name org.bluez
    309             #     was not provided by any .service files
    310             if e.get_dbus_name() == self.DBUS_ERROR_SERVICEUNKNOWN:
    311                 logging.debug('bluetoothd is not running')
    312             else:
    313                 logging.error('Error getting dbus proxy for Bluez: %s', e)
    314         return bluez
    315 
    316 
    317     def _update_adapter(self):
    318         """Store a D-Bus proxy for the local adapter in self._adapter.
    319 
    320         This may be called in a loop until it returns True to wait for the
    321         daemon to be ready, and have obtained the adapter information itself,
    322         after it has been started.
    323 
    324         Since not all devices will have adapters, this will also return True
    325         in the case where we have obtained an empty adapter index list from the
    326         kernel.
    327 
    328         Note that this method does not power on the adapter.
    329 
    330         @return True on success, including if there is no local adapter,
    331             False otherwise.
    332 
    333         """
    334         self._adapter = None
    335         if self._bluez is None:
    336             logging.warning('Bluez not found!')
    337             return False
    338         if not self._has_adapter:
    339             logging.debug('Device has no adapter; returning')
    340             return True
    341         self._adapter = self._get_adapter()
    342         return bool(self._adapter)
    343 
    344     def _update_advertising(self):
    345         """Store a D-Bus proxy for the local advertising interface manager.
    346 
    347         This may be called repeatedly in a loop until True is returned;
    348         otherwise we wait for bluetoothd to start. After bluetoothd starts, we
    349         check the existence of a local adapter and proceed to get the
    350         advertisement interface manager.
    351 
    352         Since not all devices will have adapters, this will also return True
    353         in the case where there is no adapter.
    354 
    355         @return True on success, including if there is no local adapter,
    356                 False otherwise.
    357 
    358         """
    359         self._advertising = None
    360         if self._bluez is None:
    361             logging.warning('Bluez not found!')
    362             return False
    363         if not self._has_adapter:
    364             logging.debug('Device has no adapter; returning')
    365             return True
    366         self._advertising = self._get_advertising()
    367         return bool(self._advertising)
    368 
    369 
    370     @xmlrpc_server.dbus_safe(False)
    371     def _get_adapter(self):
    372         """Get the D-Bus proxy for the local adapter.
    373 
    374         @return the adapter on success. None otherwise.
    375 
    376         """
    377         objects = self._bluez.GetManagedObjects(
    378                 dbus_interface=self.BLUEZ_MANAGER_IFACE)
    379         for path, ifaces in objects.iteritems():
    380             logging.debug('%s -> %r', path, ifaces.keys())
    381             if self.BLUEZ_ADAPTER_IFACE in ifaces:
    382                 logging.debug('using adapter %s', path)
    383                 adapter = self._system_bus.get_object(
    384                         self.BLUEZ_SERVICE_NAME,
    385                         path)
    386                 return adapter
    387         else:
    388             logging.warning('No adapter found in interface!')
    389             return None
    390 
    391 
    392     @xmlrpc_server.dbus_safe(False)
    393     def _get_advertising(self):
    394         """Get the D-Bus proxy for the local advertising interface.
    395 
    396         @return the advertising interface object.
    397 
    398         """
    399         return dbus.Interface(self._adapter,
    400                               self.BLUEZ_LE_ADVERTISING_MANAGER_IFACE)
    401 
    402 
    403     @xmlrpc_server.dbus_safe(False)
    404     def reset_on(self):
    405         """Reset the adapter and settings and power up the adapter.
    406 
    407         @return True on success, False otherwise.
    408 
    409         """
    410         return self._reset(set_power=True)
    411 
    412 
    413     @xmlrpc_server.dbus_safe(False)
    414     def reset_off(self):
    415         """Reset the adapter and settings, leave the adapter powered off.
    416 
    417         @return True on success, False otherwise.
    418 
    419         """
    420         return self._reset(set_power=False)
    421 
    422 
    423     def has_adapter(self):
    424         """Return if an adapter is present.
    425 
    426         This will only return True if we have determined both that there is
    427         a Bluetooth adapter on this device (kernel adapter index list is not
    428         empty) and that the Bluetooth daemon has exported an object for it.
    429 
    430         @return True if an adapter is present, False if not.
    431 
    432         """
    433         return self._has_adapter and self._adapter is not None
    434 
    435 
    436     def _reset(self, set_power=False):
    437         """Remove remote devices and set adapter to set_power state.
    438 
    439         Do not restart bluetoothd as this may incur a side effect.
    440         The unhappy chrome may disable the adapter randomly.
    441 
    442         @param set_power: adapter power state to set (True or False).
    443 
    444         @return True on success, False otherwise.
    445 
    446         """
    447         logging.debug('_reset')
    448 
    449         if not self._adapter:
    450             logging.warning('Adapter not found!')
    451             return False
    452 
    453         objects = self._bluez.GetManagedObjects(
    454                 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=True)
    455 
    456         devices = []
    457         for path, ifaces in objects.iteritems():
    458             if self.BLUEZ_DEVICE_IFACE in ifaces:
    459                 devices.append(objects[path][self.BLUEZ_DEVICE_IFACE])
    460 
    461         # Turn on the adapter in order to remove all remote devices.
    462         if not self._is_powered_on():
    463             self._set_powered(True)
    464 
    465         for device in devices:
    466             logging.debug('removing %s', device.get('Address'))
    467             self.remove_device_object(device.get('Address'))
    468 
    469         if not set_power:
    470             self._set_powered(False)
    471 
    472         return True
    473 
    474 
    475     @xmlrpc_server.dbus_safe(False)
    476     def set_powered(self, powered):
    477         """Set the adapter power state.
    478 
    479         @param powered: adapter power state to set (True or False).
    480 
    481         @return True on success, False otherwise.
    482 
    483         """
    484         if not self._adapter:
    485             if not powered:
    486                 # Return success if we are trying to power off an adapter that's
    487                 # missing or gone away, since the expected result has happened.
    488                 return True
    489             else:
    490                 logging.warning('Adapter not found!')
    491                 return False
    492         self._set_powered(powered)
    493         return True
    494 
    495 
    496     @xmlrpc_server.dbus_safe(False)
    497     def _set_powered(self, powered):
    498         """Set the adapter power state.
    499 
    500         @param powered: adapter power state to set (True or False).
    501 
    502         """
    503         logging.debug('_set_powered %r', powered)
    504         self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 'Powered', powered,
    505                           dbus_interface=dbus.PROPERTIES_IFACE)
    506 
    507 
    508     @xmlrpc_server.dbus_safe(False)
    509     def set_discoverable(self, discoverable):
    510         """Set the adapter discoverable state.
    511 
    512         @param discoverable: adapter discoverable state to set (True or False).
    513 
    514         @return True on success, False otherwise.
    515 
    516         """
    517         if not discoverable and not self._adapter:
    518             # Return success if we are trying to make an adapter that's
    519             # missing or gone away, undiscoverable, since the expected result
    520             # has happened.
    521             return True
    522         self._adapter.Set(self.BLUEZ_ADAPTER_IFACE,
    523                           'Discoverable', discoverable,
    524                           dbus_interface=dbus.PROPERTIES_IFACE)
    525         return True
    526 
    527 
    528     @xmlrpc_server.dbus_safe(False)
    529     def set_pairable(self, pairable):
    530         """Set the adapter pairable state.
    531 
    532         @param pairable: adapter pairable state to set (True or False).
    533 
    534         @return True on success, False otherwise.
    535 
    536         """
    537         self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 'Pairable', pairable,
    538                           dbus_interface=dbus.PROPERTIES_IFACE)
    539         return True
    540 
    541 
    542     @xmlrpc_server.dbus_safe(False)
    543     def _get_adapter_properties(self):
    544         """Read the adapter properties from the Bluetooth Daemon.
    545 
    546         @return the properties as a JSON-encoded dictionary on success,
    547             the value False otherwise.
    548 
    549         """
    550         if self._bluez:
    551             objects = self._bluez.GetManagedObjects(
    552                     dbus_interface=self.BLUEZ_MANAGER_IFACE)
    553             props = objects[self._adapter.object_path][self.BLUEZ_ADAPTER_IFACE]
    554         else:
    555             props = {}
    556         logging.debug('get_adapter_properties: %s', props)
    557         return props
    558 
    559 
    560     def get_adapter_properties(self):
    561         return json.dumps(self._get_adapter_properties())
    562 
    563 
    564     def _is_powered_on(self):
    565         return bool(self._get_adapter_properties().get(u'Powered'))
    566 
    567 
    568     def read_version(self):
    569         """Read the version of the management interface from the Kernel.
    570 
    571         @return the information as a JSON-encoded tuple of:
    572           ( version, revision )
    573 
    574         """
    575         return json.dumps(self._control.read_version())
    576 
    577 
    578     def read_supported_commands(self):
    579         """Read the set of supported commands from the Kernel.
    580 
    581         @return the information as a JSON-encoded tuple of:
    582           ( commands, events )
    583 
    584         """
    585         return json.dumps(self._control.read_supported_commands())
    586 
    587 
    588     def read_index_list(self):
    589         """Read the list of currently known controllers from the Kernel.
    590 
    591         @return the information as a JSON-encoded array of controller indexes.
    592 
    593         """
    594         return json.dumps(self._control.read_index_list())
    595 
    596 
    597     def read_info(self):
    598         """Read the adapter information from the Kernel.
    599 
    600         @return the information as a JSON-encoded tuple of:
    601           ( address, bluetooth_version, manufacturer_id,
    602             supported_settings, current_settings, class_of_device,
    603             name, short_name )
    604 
    605         """
    606         return json.dumps(self._control.read_info(0))
    607 
    608 
    609     def add_device(self, address, address_type, action):
    610         """Add a device to the Kernel action list.
    611 
    612         @param address: Address of the device to add.
    613         @param address_type: Type of device in @address.
    614         @param action: Action to take.
    615 
    616         @return on success, a JSON-encoded typle of:
    617           ( address, address_type ), None on failure.
    618 
    619         """
    620         return json.dumps(self._control.add_device(
    621                 0, address, address_type, action))
    622 
    623 
    624     def remove_device(self, address, address_type):
    625         """Remove a device from the Kernel action list.
    626 
    627         @param address: Address of the device to remove.
    628         @param address_type: Type of device in @address.
    629 
    630         @return on success, a JSON-encoded typle of:
    631           ( address, address_type ), None on failure.
    632 
    633         """
    634         return json.dumps(self._control.remove_device(
    635                 0, address, address_type))
    636 
    637 
    638     @xmlrpc_server.dbus_safe(False)
    639     def get_devices(self):
    640         """Read information about remote devices known to the adapter.
    641 
    642         @return the properties of each device as a JSON-encoded array of
    643             dictionaries on success, the value False otherwise.
    644 
    645         """
    646         objects = self._bluez.GetManagedObjects(
    647                 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=True)
    648         devices = []
    649         for path, ifaces in objects.iteritems():
    650             if self.BLUEZ_DEVICE_IFACE in ifaces:
    651                 devices.append(objects[path][self.BLUEZ_DEVICE_IFACE])
    652         return json.dumps(devices)
    653 
    654 
    655     @xmlrpc_server.dbus_safe(False)
    656     def get_device_by_address(self, address):
    657         """Read information about the remote device with the specified address.
    658 
    659         @param address: Address of the device to get.
    660 
    661         @return the properties of the device as a JSON-encoded dictionary
    662             on success, the value False otherwise.
    663 
    664         """
    665         objects = self._bluez.GetManagedObjects(
    666                 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=True)
    667         devices = []
    668         for path, ifaces in objects.iteritems():
    669             if self.BLUEZ_DEVICE_IFACE in ifaces:
    670                 device = objects[path][self.BLUEZ_DEVICE_IFACE]
    671                 if device.get('Address') == address:
    672                     return json.dumps(device)
    673 
    674         devices = json.loads(self.get_devices())
    675         for device in devices:
    676             if device.get['Address'] == address:
    677                 return json.dumps(device)
    678         return json.dumps(dict())
    679 
    680 
    681     @xmlrpc_server.dbus_safe(False)
    682     def start_discovery(self):
    683         """Start discovery of remote devices.
    684 
    685         Obtain the discovered device information using get_devices(), called
    686         stop_discovery() when done.
    687 
    688         @return True on success, False otherwise.
    689 
    690         """
    691         if not self._adapter:
    692             return False
    693         self._adapter.StartDiscovery(dbus_interface=self.BLUEZ_ADAPTER_IFACE)
    694         return True
    695 
    696 
    697     @xmlrpc_server.dbus_safe(False)
    698     def stop_discovery(self):
    699         """Stop discovery of remote devices.
    700 
    701         @return True on success, False otherwise.
    702 
    703         """
    704         if not self._adapter:
    705             return False
    706         self._adapter.StopDiscovery(dbus_interface=self.BLUEZ_ADAPTER_IFACE)
    707         return True
    708 
    709 
    710     def get_dev_info(self):
    711         """Read raw HCI device information.
    712 
    713         @return JSON-encoded tuple of:
    714                 (index, name, address, flags, device_type, bus_type,
    715                        features, pkt_type, link_policy, link_mode,
    716                        acl_mtu, acl_pkts, sco_mtu, sco_pkts,
    717                        err_rx, err_tx, cmd_tx, evt_rx, acl_tx, acl_rx,
    718                        sco_tx, sco_rx, byte_rx, byte_tx) on success,
    719                 None on failure.
    720 
    721         """
    722         return json.dumps(self._raw.get_dev_info(0))
    723 
    724 
    725     @xmlrpc_server.dbus_safe(False)
    726     def register_profile(self, path, uuid, options):
    727         """Register new profile (service).
    728 
    729         @param path: Path to the profile object.
    730         @param uuid: Service Class ID of the service as string.
    731         @param options: Dictionary of options for the new service, compliant
    732                         with BlueZ D-Bus Profile API standard.
    733 
    734         @return True on success, False otherwise.
    735 
    736         """
    737         profile_manager = dbus.Interface(
    738                               self._system_bus.get_object(
    739                                   self.BLUEZ_SERVICE_NAME,
    740                                   self.BLUEZ_PROFILE_MANAGER_PATH),
    741                               self.BLUEZ_PROFILE_MANAGER_IFACE)
    742         profile_manager.RegisterProfile(path, uuid, options)
    743         return True
    744 
    745 
    746     def has_device(self, address):
    747         """Checks if the device with a given address exists.
    748 
    749         @param address: Address of the device.
    750 
    751         @returns: True if there is an interface object with that address.
    752                   False if the device is not found.
    753 
    754         @raises: Exception if a D-Bus error is encountered.
    755 
    756         """
    757         result = self._find_device(address)
    758         logging.debug('has_device result: %s', str(result))
    759 
    760         # The result being False indicates that there is a D-Bus error.
    761         if result is False:
    762             raise Exception('dbus.Interface error')
    763 
    764         # Return True if the result is not None, e.g. a D-Bus interface object;
    765         # False otherwise.
    766         return bool(result)
    767 
    768 
    769     @xmlrpc_server.dbus_safe(False)
    770     def _find_device(self, address):
    771         """Finds the device with a given address.
    772 
    773         Find the device with a given address and returns the
    774         device interface.
    775 
    776         @param address: Address of the device.
    777 
    778         @returns: An 'org.bluez.Device1' interface to the device.
    779                   None if device can not be found.
    780         """
    781         path = self._get_device_path(address)
    782         if path:
    783             obj = self._system_bus.get_object(
    784                         self.BLUEZ_SERVICE_NAME, path)
    785             return dbus.Interface(obj, self.BLUEZ_DEVICE_IFACE)
    786         logging.info('Device not found')
    787         return None
    788 
    789 
    790     @xmlrpc_server.dbus_safe(False)
    791     def _get_device_path(self, address):
    792         """Gets the path for a device with a given address.
    793 
    794         Find the device with a given address and returns the
    795         the path for the device.
    796 
    797         @param address: Address of the device.
    798 
    799         @returns: The path to the address of the device, or None if device is
    800             not found in the object tree.
    801 
    802         """
    803         objects = self._bluez.GetManagedObjects(
    804                 dbus_interface=self.BLUEZ_MANAGER_IFACE)
    805         for path, ifaces in objects.iteritems():
    806             device = ifaces.get(self.BLUEZ_DEVICE_IFACE)
    807             if device is None:
    808                 continue
    809             if (device['Address'] == address and
    810                 path.startswith(self._adapter.object_path)):
    811                 return path
    812         logging.info('Device path not found')
    813 
    814 
    815     @xmlrpc_server.dbus_safe(False)
    816     def _setup_pairing_agent(self, pin):
    817         """Initializes and resiters a PairingAgent to handle authentication.
    818 
    819         @param pin: The pin code this agent will answer.
    820 
    821         """
    822         if self._pairing_agent:
    823             logging.info('Removing the old agent before initializing a new one')
    824             self._pairing_agent.remove_from_connection()
    825             self._pairing_agent = None
    826         self._pairing_agent= PairingAgent(pin, self._system_bus,
    827                                           self.AGENT_PATH)
    828         agent_manager = dbus.Interface(
    829                 self._system_bus.get_object(self.BLUEZ_SERVICE_NAME,
    830                                             self.BLUEZ_AGENT_MANAGER_PATH),
    831                 self.BLUEZ_AGENT_MANAGER_IFACE)
    832         try:
    833             agent_manager.RegisterAgent(self.AGENT_PATH, self._capability)
    834         except dbus.exceptions.DBusException, e:
    835             if e.get_dbus_name() == self.BLUEZ_ERROR_ALREADY_EXISTS:
    836                 logging.info('Unregistering old agent and registering the new')
    837                 agent_manager.UnregisterAgent(self.AGENT_PATH)
    838                 agent_manager.RegisterAgent(self.AGENT_PATH, self._capability)
    839             else:
    840                 logging.error('Error setting up pin agent: %s', e)
    841                 raise
    842         logging.info('Agent registered: %s', self.AGENT_PATH)
    843 
    844 
    845     @xmlrpc_server.dbus_safe(False)
    846     def _is_paired(self,  device):
    847         """Checks if a device is paired.
    848 
    849         @param device: An 'org.bluez.Device1' interface to the device.
    850 
    851         @returns: True if device is paired. False otherwise.
    852 
    853         """
    854         props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
    855         paired = props.Get(self.BLUEZ_DEVICE_IFACE, 'Paired')
    856         return bool(paired)
    857 
    858 
    859     @xmlrpc_server.dbus_safe(False)
    860     def device_is_paired(self, address):
    861         """Checks if a device is paired.
    862 
    863         @param address: address of the device.
    864 
    865         @returns: True if device is paired. False otherwise.
    866 
    867         """
    868         device = self._find_device(address)
    869         if not device:
    870             logging.error('Device not found')
    871             return False
    872         return self._is_paired(device)
    873 
    874 
    875     @xmlrpc_server.dbus_safe(False)
    876     def _is_connected(self, device):
    877         """Checks if a device is connected.
    878 
    879         @param device: An 'org.bluez.Device1' interface to the device.
    880 
    881         @returns: True if device is connected. False otherwise.
    882 
    883         """
    884         props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
    885         connected = props.Get(self.BLUEZ_DEVICE_IFACE, 'Connected')
    886         logging.info('Got connected = %r', connected)
    887         return bool(connected)
    888 
    889 
    890 
    891     @xmlrpc_server.dbus_safe(False)
    892     def _set_trusted_by_device(self, device, trusted=True):
    893         """Set the device trusted by device object.
    894 
    895         @param device: the device object to set trusted.
    896         @param trusted: True or False indicating whether to set trusted or not.
    897 
    898         @returns: True if successful. False otherwise.
    899 
    900         """
    901         try:
    902             properties = dbus.Interface(device, self.DBUS_PROP_IFACE)
    903             properties.Set(self.BLUEZ_DEVICE_IFACE, 'Trusted', trusted)
    904             return True
    905         except Exception as e:
    906             logging.error('_set_trusted_by_device: %s', e)
    907         except:
    908             logging.error('_set_trusted_by_device: unexpected error')
    909         return False
    910 
    911 
    912     @xmlrpc_server.dbus_safe(False)
    913     def _set_trusted_by_path(self, device_path, trusted=True):
    914         """Set the device trusted by the device path.
    915 
    916         @param device_path: the object path of the device.
    917         @param trusted: True or False indicating whether to set trusted or not.
    918 
    919         @returns: True if successful. False otherwise.
    920 
    921         """
    922         try:
    923             device = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME,
    924                                                  device_path)
    925             return self._set_trusted_by_device(device, trusted)
    926         except Exception as e:
    927             logging.error('_set_trusted_by_path: %s', e)
    928         except:
    929             logging.error('_set_trusted_by_path: unexpected error')
    930         return False
    931 
    932 
    933     @xmlrpc_server.dbus_safe(False)
    934     def set_trusted(self, address, trusted=True):
    935         """Set the device trusted by address.
    936 
    937         @param address: The bluetooth address of the device.
    938         @param trusted: True or False indicating whether to set trusted or not.
    939 
    940         @returns: True if successful. False otherwise.
    941 
    942         """
    943         try:
    944             device = self._find_device(address)
    945             return self._set_trusted_by_device(device, trusted)
    946         except Exception as e:
    947             logging.error('set_trusted: %s', e)
    948         except:
    949             logging.error('set_trusted: unexpected error')
    950         return False
    951 
    952 
    953     @xmlrpc_server.dbus_safe(False)
    954     def pair_legacy_device(self, address, pin, trusted, timeout=60):
    955         """Pairs a device with a given pin code.
    956 
    957         Registers a agent who handles pin code request and
    958         pairs a device with known pin code.
    959 
    960         Note that the adapter does not automatically connnect to the device
    961         when pairing is done. The connect_device() method has to be invoked
    962         explicitly to connect to the device. This provides finer control
    963         for testing purpose.
    964 
    965         @param address: Address of the device to pair.
    966         @param pin: The pin code of the device to pair.
    967         @param trusted: indicating whether to set the device trusted.
    968         @param timeout: The timeout in seconds for pairing.
    969 
    970         @returns: True on success. False otherwise.
    971 
    972         """
    973         device = self._find_device(address)
    974         if not device:
    975             logging.error('Device not found')
    976             return False
    977         if self._is_paired(device):
    978             logging.info('Device is already paired')
    979             return True
    980 
    981         device_path = device.object_path
    982         logging.info('Device %s is found.', device.object_path)
    983 
    984         self._setup_pairing_agent(pin)
    985         mainloop = gobject.MainLoop()
    986 
    987 
    988         def pair_reply():
    989             """Handler when pairing succeeded."""
    990             logging.info('Device paired: %s', device_path)
    991             if trusted:
    992                 self._set_trusted_by_path(device_path, trusted=True)
    993                 logging.info('Device trusted: %s', device_path)
    994             mainloop.quit()
    995 
    996 
    997         def pair_error(error):
    998             """Handler when pairing failed.
    999 
   1000             @param error: one of errors defined in org.bluez.Error representing
   1001                           the error in pairing.
   1002 
   1003             """
   1004             try:
   1005                 error_name = error.get_dbus_name()
   1006                 if error_name == 'org.freedesktop.DBus.Error.NoReply':
   1007                     logging.error('Timed out after %d ms. Cancelling pairing.',
   1008                                   timeout)
   1009                     device.CancelPairing()
   1010                 else:
   1011                     logging.error('Pairing device failed: %s', error)
   1012             finally:
   1013                 mainloop.quit()
   1014 
   1015 
   1016         device.Pair(reply_handler=pair_reply, error_handler=pair_error,
   1017                     timeout=timeout * 1000)
   1018         mainloop.run()
   1019         return self._is_paired(device)
   1020 
   1021 
   1022     @xmlrpc_server.dbus_safe(False)
   1023     def remove_device_object(self, address):
   1024         """Removes a device object and the pairing information.
   1025 
   1026         Calls RemoveDevice method to remove remote device
   1027         object and the pairing information.
   1028 
   1029         @param address: Address of the device to unpair.
   1030 
   1031         @returns: True on success. False otherwise.
   1032 
   1033         """
   1034         device = self._find_device(address)
   1035         if not device:
   1036             logging.error('Device not found')
   1037             return False
   1038         self._adapter.RemoveDevice(
   1039                 device.object_path, dbus_interface=self.BLUEZ_ADAPTER_IFACE)
   1040         return True
   1041 
   1042 
   1043     @xmlrpc_server.dbus_safe(False)
   1044     def connect_device(self, address):
   1045         """Connects a device.
   1046 
   1047         Connects a device if it is not connected.
   1048 
   1049         @param address: Address of the device to connect.
   1050 
   1051         @returns: True on success. False otherwise.
   1052 
   1053         """
   1054         device = self._find_device(address)
   1055         if not device:
   1056             logging.error('Device not found')
   1057             return False
   1058         if self._is_connected(device):
   1059           logging.info('Device is already connected')
   1060           return True
   1061         device.Connect()
   1062         return self._is_connected(device)
   1063 
   1064 
   1065     @xmlrpc_server.dbus_safe(False)
   1066     def device_is_connected(self, address):
   1067         """Checks if a device is connected.
   1068 
   1069         @param address: Address of the device to connect.
   1070 
   1071         @returns: True if device is connected. False otherwise.
   1072 
   1073         """
   1074         device = self._find_device(address)
   1075         if not device:
   1076             logging.error('Device not found')
   1077             return False
   1078         return self._is_connected(device)
   1079 
   1080 
   1081     @xmlrpc_server.dbus_safe(False)
   1082     def disconnect_device(self, address):
   1083         """Disconnects a device.
   1084 
   1085         Disconnects a device if it is connected.
   1086 
   1087         @param address: Address of the device to disconnect.
   1088 
   1089         @returns: True on success. False otherwise.
   1090 
   1091         """
   1092         device = self._find_device(address)
   1093         if not device:
   1094             logging.error('Device not found')
   1095             return False
   1096         if not self._is_connected(device):
   1097           logging.info('Device is not connected')
   1098           return True
   1099         device.Disconnect()
   1100         return not self._is_connected(device)
   1101 
   1102 
   1103     @xmlrpc_server.dbus_safe(False)
   1104     def _device_services_resolved(self, device):
   1105         """Checks if services are resolved.
   1106 
   1107         @param device: An 'org.bluez.Device1' interface to the device.
   1108 
   1109         @returns: True if device is connected. False otherwise.
   1110 
   1111         """
   1112         logging.info('device for services resolved: %s', device)
   1113         props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
   1114         resolved = props.Get(self.BLUEZ_DEVICE_IFACE, 'ServicesResolved')
   1115         logging.info('Services resolved = %r', resolved)
   1116         return bool(resolved)
   1117 
   1118 
   1119     @xmlrpc_server.dbus_safe(False)
   1120     def device_services_resolved(self, address):
   1121         """Checks if service discovery is complete on a device.
   1122 
   1123         Checks whether service discovery has been completed..
   1124 
   1125         @param address: Address of the remote device.
   1126 
   1127         @returns: True on success. False otherwise.
   1128 
   1129         """
   1130         device = self._find_device(address)
   1131         if not device:
   1132             logging.error('Device not found')
   1133             return False
   1134 
   1135         if not self._is_connected(device):
   1136           logging.info('Device is not connected')
   1137           return False
   1138 
   1139         return self._device_services_resolved(device)
   1140 
   1141 
   1142     def btmon_start(self):
   1143         """Start btmon monitoring."""
   1144         self.btmon.start()
   1145 
   1146 
   1147     def btmon_stop(self):
   1148         """Stop btmon monitoring."""
   1149         self.btmon.stop()
   1150 
   1151 
   1152     def btmon_get(self, search_str, start_str):
   1153         """Get btmon output contents.
   1154 
   1155         @param search_str: only lines with search_str would be kept.
   1156         @param start_str: all lines before the occurrence of start_str would be
   1157                 filtered.
   1158 
   1159         @returns: the recorded btmon output.
   1160 
   1161         """
   1162         return self.btmon.get_contents(search_str=search_str,
   1163                                        start_str=start_str)
   1164 
   1165 
   1166     def btmon_find(self, pattern_str):
   1167         """Find if a pattern string exists in btmon output.
   1168 
   1169         @param pattern_str: the pattern string to find.
   1170 
   1171         @returns: True on success. False otherwise.
   1172 
   1173         """
   1174         return self.btmon.find(pattern_str)
   1175 
   1176 
   1177     @xmlrpc_server.dbus_safe(False)
   1178     def advertising_async_method(self, dbus_method,
   1179                                  reply_handler, error_handler, *args):
   1180         """Run an async dbus method.
   1181 
   1182         @param dbus_method: the dbus async method to invoke.
   1183         @param reply_handler: the reply handler for the dbus method.
   1184         @param error_handler: the error handler for the dbus method.
   1185         @param *args: additional arguments for the dbus method.
   1186 
   1187         @returns: an empty string '' on success;
   1188                   None if there is no _advertising interface manager; and
   1189                   an error string if the dbus method fails.
   1190 
   1191         """
   1192 
   1193         def successful_cb():
   1194             """Called when the dbus_method completed successfully."""
   1195             reply_handler()
   1196             self.advertising_cb_msg = ''
   1197             self._adv_mainloop.quit()
   1198 
   1199 
   1200         def error_cb(error):
   1201             """Called when the dbus_method failed."""
   1202             error_handler(error)
   1203             self.advertising_cb_msg = str(error)
   1204             self._adv_mainloop.quit()
   1205 
   1206 
   1207         if not self._advertising:
   1208             return None
   1209 
   1210         # Call dbus_method with handlers.
   1211         dbus_method(*args, reply_handler=successful_cb, error_handler=error_cb)
   1212 
   1213         self._adv_mainloop.run()
   1214 
   1215         return self.advertising_cb_msg
   1216 
   1217 
   1218     def register_advertisement(self, advertisement_data):
   1219         """Register an advertisement.
   1220 
   1221         Note that rpc supports only conformable types. Hence, a
   1222         dict about the advertisement is passed as a parameter such
   1223         that the advertisement object could be constructed on the host.
   1224 
   1225         @param advertisement_data: a dict of the advertisement to register.
   1226 
   1227         @returns: True on success. False otherwise.
   1228 
   1229         """
   1230         adv = advertisement.Advertisement(self._system_bus, advertisement_data)
   1231         self.advertisements.append(adv)
   1232         return self.advertising_async_method(
   1233                 self._advertising.RegisterAdvertisement,
   1234                 # reply handler
   1235                 lambda: logging.info('register_advertisement: succeeded.'),
   1236                 # error handler
   1237                 lambda error: logging.error(
   1238                     'register_advertisement: failed: %s', str(error)),
   1239                 # other arguments
   1240                 adv.get_path(), {})
   1241 
   1242 
   1243     def unregister_advertisement(self, advertisement_data):
   1244         """Unregister an advertisement.
   1245 
   1246         Note that to unregister an advertisement, it is required to use
   1247         the same self._advertising interface manager. This is because
   1248         bluez only allows the same sender to invoke UnregisterAdvertisement
   1249         method. Hence, watch out that the bluetoothd is not restarted or
   1250         self.start_bluetoothd() is not executed between the time span that
   1251         an advertisement is registered and unregistered.
   1252 
   1253         @param advertisement_data: a dict of the advertisements to unregister.
   1254 
   1255         @returns: True on success. False otherwise.
   1256 
   1257         """
   1258         path = advertisement_data.get('Path')
   1259         for index, adv in enumerate(self.advertisements):
   1260             if adv.get_path() == path:
   1261                 break
   1262         else:
   1263             logging.error('Fail to find the advertisement under the path: %s',
   1264                           path)
   1265             return False
   1266 
   1267         result = self.advertising_async_method(
   1268                 self._advertising.UnregisterAdvertisement,
   1269                 # reply handler
   1270                 lambda: logging.info('unregister_advertisement: succeeded.'),
   1271                 # error handler
   1272                 lambda error: logging.error(
   1273                     'unregister_advertisement: failed: %s', str(error)),
   1274                 # other arguments
   1275                 adv.get_path())
   1276 
   1277         # Call remove_from_connection() so that the same path could be reused.
   1278         adv.remove_from_connection()
   1279         del self.advertisements[index]
   1280 
   1281         return result
   1282 
   1283 
   1284     def set_advertising_intervals(self, min_adv_interval_ms,
   1285                                   max_adv_interval_ms):
   1286         """Set advertising intervals.
   1287 
   1288         @param min_adv_interval_ms: the min advertising interval in ms.
   1289         @param max_adv_interval_ms: the max advertising interval in ms.
   1290 
   1291         @returns: True on success. False otherwise.
   1292 
   1293         """
   1294         return self.advertising_async_method(
   1295                 self._advertising.SetAdvertisingIntervals,
   1296                 # reply handler
   1297                 lambda: logging.info('set_advertising_intervals: succeeded.'),
   1298                 # error handler
   1299                 lambda error: logging.error(
   1300                     'set_advertising_intervals: failed: %s', str(error)),
   1301                 # other arguments
   1302                 min_adv_interval_ms, max_adv_interval_ms)
   1303 
   1304 
   1305     def reset_advertising(self):
   1306         """Reset advertising.
   1307 
   1308         This includes un-registering all advertisements, reset advertising
   1309         intervals, and disable advertising.
   1310 
   1311         @returns: True on success. False otherwise.
   1312 
   1313         """
   1314         # It is required to execute remove_from_connection() to unregister the
   1315         # object-path handler of each advertisement. In this way, we could
   1316         # register an advertisement with the same path repeatedly.
   1317         for adv in self.advertisements:
   1318             adv.remove_from_connection()
   1319         del self.advertisements[:]
   1320 
   1321         return self.advertising_async_method(
   1322                 self._advertising.ResetAdvertising,
   1323                 # reply handler
   1324                 lambda: logging.info('reset_advertising: succeeded.'),
   1325                 # error handler
   1326                 lambda error: logging.error(
   1327                     'reset_advertising: failed: %s', str(error)))
   1328 
   1329 
   1330     @xmlrpc_server.dbus_safe(False)
   1331     def get_characteristic_map(self, address):
   1332         """Gets a map of characteristic paths for a device.
   1333 
   1334         Walks the object tree, and returns a map of uuids to object paths for
   1335         all resolved gatt characteristics.
   1336 
   1337         @param address: The MAC address of the device to retrieve
   1338             gatt characteristic uuids and paths from.
   1339 
   1340         @returns: A dictionary of characteristic paths, keyed by uuid.
   1341 
   1342         """
   1343         device_path = self._get_device_path(address)
   1344         char_map = {}
   1345 
   1346         if device_path:
   1347           objects = self._bluez.GetManagedObjects(
   1348               dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=False)
   1349 
   1350           for path, ifaces in objects.iteritems():
   1351               if (self.BLUEZ_GATT_IFACE in ifaces and
   1352                   path.startswith(device_path)):
   1353                   uuid = ifaces[self.BLUEZ_GATT_IFACE]['UUID'].lower()
   1354                   char_map[uuid] = path
   1355         else:
   1356             logging.warning('Device %s not in object tree.', address)
   1357 
   1358         return char_map
   1359 
   1360 
   1361     @xmlrpc_server.dbus_safe(False)
   1362     def _get_char_object(self, uuid, address):
   1363         """Gets a characteristic object.
   1364 
   1365         Gets a characteristic object for a given uuid and address.
   1366 
   1367         @param uuid: The uuid of the characteristic, as a string.
   1368         @param address: The MAC address of the remote device.
   1369 
   1370         @returns: A dbus interface for the characteristic if the uuid/address
   1371                       is in the object tree.
   1372                   None if the address/uuid is not found in the object tree.
   1373 
   1374         """
   1375         path = self.get_characteristic_map(address).get(uuid)
   1376         if not path:
   1377             return None
   1378         return dbus.Interface(
   1379             self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, path),
   1380             self.BLUEZ_GATT_IFACE)
   1381 
   1382 
   1383     @xmlrpc_server.dbus_safe(None)
   1384     def read_characteristic(self, uuid, address):
   1385         """Reads the value of a gatt characteristic.
   1386 
   1387         Reads the current value of a gatt characteristic. Base64 endcoding is
   1388         used for compatibility with the XML RPC interface.
   1389 
   1390         @param uuid: The uuid of the characteristic to read, as a string.
   1391         @param address: The MAC address of the remote device.
   1392 
   1393         @returns: A b64 encoded version of a byte array containing the value
   1394                       if the uuid/address is in the object tree.
   1395                   None if the uuid/address was not found in the object tree, or
   1396                       if a DBus exception was raised by the read operation.
   1397 
   1398         """
   1399         char_obj = self._get_char_object(uuid, address)
   1400         if char_obj is None:
   1401             return None
   1402         value = char_obj.ReadValue(dbus.Dictionary())
   1403         return _dbus_byte_array_to_b64_string(value)
   1404 
   1405 
   1406     @xmlrpc_server.dbus_safe(None)
   1407     def write_characteristic(self, uuid, address, value):
   1408         """Performs a write operation on a gatt characteristic.
   1409 
   1410         Writes to a GATT characteristic on a remote device. Base64 endcoding is
   1411         used for compatibility with the XML RPC interface.
   1412 
   1413         @param uuid: The uuid of the characteristic to write to, as a string.
   1414         @param address: The MAC address of the remote device, as a string.
   1415         @param value: A byte array containing the data to write.
   1416 
   1417         @returns: True if the write operation does not raise an exception.
   1418                   None if the uuid/address was not found in the object tree, or
   1419                       if a DBus exception was raised by the write operation.
   1420 
   1421         """
   1422         char_obj = self._get_char_object(uuid, address)
   1423         if char_obj is None:
   1424             return None
   1425         dbus_value = _b64_string_to_dbus_byte_array(value)
   1426         char_obj.WriteValue(dbus_value, dbus.Dictionary())
   1427         return True
   1428 
   1429 
   1430     @xmlrpc_server.dbus_safe(False)
   1431     def is_characteristic_path_resolved(self, uuid, address):
   1432         """Checks whether a characteristic is in the object tree.
   1433 
   1434         Checks whether a characteristic is curently found in the object tree.
   1435 
   1436         @param uuid: The uuid of the characteristic to search for.
   1437         @param address: The MAC address of the device on which to search for
   1438             the characteristic.
   1439 
   1440         @returns: True if the characteristic is found.
   1441                   False if the characteristic path is not found.
   1442 
   1443         """
   1444         return bool(self.get_characteristic_map(address).get(uuid))
   1445 
   1446 
   1447 if __name__ == '__main__':
   1448     logging.basicConfig(level=logging.DEBUG)
   1449     handler = logging.handlers.SysLogHandler(address='/dev/log')
   1450     formatter = logging.Formatter(
   1451             'bluetooth_device_xmlrpc_server: [%(levelname)s] %(message)s')
   1452     handler.setFormatter(formatter)
   1453     logging.getLogger().addHandler(handler)
   1454     logging.debug('bluetooth_device_xmlrpc_server main...')
   1455     server = xmlrpc_server.XmlRpcServer(
   1456             'localhost',
   1457             constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_PORT)
   1458     server.register_delegate(BluetoothDeviceXmlRpcDelegate())
   1459     server.run()
   1460