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 dbus
      8 import dbus.mainloop.glib
      9 import dbus.service
     10 import gobject
     11 import json
     12 import logging
     13 import logging.handlers
     14 import os
     15 import shutil
     16 
     17 import common
     18 from autotest_lib.client.bin import utils
     19 from autotest_lib.client.common_lib.cros.bluetooth import bluetooth_socket
     20 from autotest_lib.client.cros import constants
     21 from autotest_lib.client.cros import xmlrpc_server
     22 
     23 
     24 class _PinAgent(dbus.service.Object):
     25     """The agent handling bluetooth device with a known pin code.
     26 
     27     _PinAgent overrides RequestPinCode method to return a given pin code.
     28     User can use this agent to pair bluetooth device which has a known pin code.
     29 
     30     """
     31     def __init__(self, pin, *args, **kwargs):
     32         super(_PinAgent, self).__init__(*args, **kwargs)
     33         self._pin = pin
     34 
     35 
     36     @dbus.service.method('org.bluez.Agent1', in_signature="o", out_signature="s")
     37     def RequestPinCode(self, device_path):
     38         """Requests pin code for a device.
     39 
     40         Returns the known pin code for the request.
     41 
     42         @param device_path: The object path of the device.
     43 
     44         @returns: The known pin code.
     45 
     46         """
     47         logging.info('RequestPinCode for %s, return %s', device_path, self._pin)
     48         return self._pin
     49 
     50 
     51 class BluetoothDeviceXmlRpcDelegate(xmlrpc_server.XmlRpcDelegate):
     52     """Exposes DUT methods called remotely during Bluetooth autotests.
     53 
     54     All instance methods of this object without a preceding '_' are exposed via
     55     an XML-RPC server. This is not a stateless handler object, which means that
     56     if you store state inside the delegate, that state will remain around for
     57     future calls.
     58     """
     59 
     60     UPSTART_PATH = 'unix:abstract=/com/ubuntu/upstart'
     61     UPSTART_MANAGER_PATH = '/com/ubuntu/Upstart'
     62     UPSTART_MANAGER_IFACE = 'com.ubuntu.Upstart0_6'
     63     UPSTART_JOB_IFACE = 'com.ubuntu.Upstart0_6.Job'
     64 
     65     UPSTART_ERROR_UNKNOWNINSTANCE = \
     66             'com.ubuntu.Upstart0_6.Error.UnknownInstance'
     67 
     68     BLUETOOTHD_JOB = 'bluetoothd'
     69 
     70     DBUS_ERROR_SERVICEUNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown'
     71 
     72     BLUEZ_SERVICE_NAME = 'org.bluez'
     73     BLUEZ_MANAGER_PATH = '/'
     74     BLUEZ_MANAGER_IFACE = 'org.freedesktop.DBus.ObjectManager'
     75     BLUEZ_ADAPTER_IFACE = 'org.bluez.Adapter1'
     76     BLUEZ_DEVICE_IFACE = 'org.bluez.Device1'
     77     BLUEZ_AGENT_MANAGER_PATH = '/org/bluez'
     78     BLUEZ_AGENT_MANAGER_IFACE = 'org.bluez.AgentManager1'
     79     BLUEZ_PROFILE_MANAGER_PATH = '/org/bluez'
     80     BLUEZ_PROFILE_MANAGER_IFACE = 'org.bluez.ProfileManager1'
     81     BLUEZ_ERROR_ALREADY_EXISTS = 'org.bluez.Error.AlreadyExists'
     82 
     83     BLUETOOTH_LIBDIR = '/var/lib/bluetooth'
     84 
     85     # Timeout for how long we'll wait for BlueZ and the Adapter to show up
     86     # after reset.
     87     ADAPTER_TIMEOUT = 30
     88 
     89     def __init__(self):
     90         super(BluetoothDeviceXmlRpcDelegate, self).__init__()
     91 
     92         # Open the Bluetooth Raw socket to the kernel which provides us direct,
     93         # raw, access to the HCI controller.
     94         self._raw = bluetooth_socket.BluetoothRawSocket()
     95 
     96         # Open the Bluetooth Control socket to the kernel which provides us
     97         # raw management access to the Bluetooth Host Subsystem. Read the list
     98         # of adapter indexes to determine whether or not this device has a
     99         # Bluetooth Adapter or not.
    100         self._control = bluetooth_socket.BluetoothControlSocket()
    101         self._has_adapter = len(self._control.read_index_list()) > 0
    102 
    103         # Set up the connection to Upstart so we can start and stop services
    104         # and fetch the bluetoothd job.
    105         self._upstart_conn = dbus.connection.Connection(self.UPSTART_PATH)
    106         self._upstart = self._upstart_conn.get_object(
    107                 None,
    108                 self.UPSTART_MANAGER_PATH)
    109 
    110         bluetoothd_path = self._upstart.GetJobByName(
    111                 self.BLUETOOTHD_JOB,
    112                 dbus_interface=self.UPSTART_MANAGER_IFACE)
    113         self._bluetoothd = self._upstart_conn.get_object(
    114                 None,
    115                 bluetoothd_path)
    116 
    117         # Arrange for the GLib main loop to be the default.
    118         dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    119 
    120         # Set up the connection to the D-Bus System Bus, get the object for
    121         # the Bluetooth Userspace Daemon (BlueZ) and that daemon's object for
    122         # the Bluetooth Adapter.
    123         self._system_bus = dbus.SystemBus()
    124         self._update_bluez()
    125         self._update_adapter()
    126 
    127         # The agent to handle pin code request, which will be
    128         # created when user calls pair_legacy_device method.
    129         self._pin_agent = None
    130 
    131 
    132     def _update_bluez(self):
    133         """Store a D-Bus proxy for the Bluetooth daemon in self._bluez.
    134 
    135         This may be called in a loop until it returns True to wait for the
    136         daemon to be ready after it has been started.
    137 
    138         @return True on success, False otherwise.
    139 
    140         """
    141         self._bluez = None
    142         try:
    143             self._bluez = self._system_bus.get_object(
    144                     self.BLUEZ_SERVICE_NAME,
    145                     self.BLUEZ_MANAGER_PATH)
    146             logging.debug('bluetoothd is running')
    147             return True
    148         except dbus.exceptions.DBusException, e:
    149             if e.get_dbus_name() == self.DBUS_ERROR_SERVICEUNKNOWN:
    150                 logging.debug('bluetoothd is not running')
    151                 self._bluez = None
    152                 return False
    153             else:
    154                 logging.error('Error updating Bluez!')
    155                 raise
    156 
    157 
    158     def _update_adapter(self):
    159         """Store a D-Bus proxy for the local adapter in self._adapter.
    160 
    161         This may be called in a loop until it returns True to wait for the
    162         daemon to be ready, and have obtained the adapter information itself,
    163         after it has been started.
    164 
    165         Since not all devices will have adapters, this will also return True
    166         in the case where we have obtained an empty adapter index list from the
    167         kernel.
    168 
    169         @return True on success, including if there is no local adapter,
    170             False otherwise.
    171 
    172         """
    173         self._adapter = None
    174         if self._bluez is None:
    175             logging.warning('Bluez not found!')
    176             return False
    177         if not self._has_adapter:
    178             logging.debug('Device has no adapter; returning')
    179             return True
    180 
    181         objects = self._bluez.GetManagedObjects(
    182                 dbus_interface=self.BLUEZ_MANAGER_IFACE)
    183         for path, ifaces in objects.iteritems():
    184             logging.debug('%s -> %r', path, ifaces.keys())
    185             if self.BLUEZ_ADAPTER_IFACE in ifaces:
    186                 logging.debug('using adapter %s', path)
    187                 self._adapter = self._system_bus.get_object(
    188                         self.BLUEZ_SERVICE_NAME,
    189                         path)
    190                 return True
    191         else:
    192             logging.warning('No adapter found in interface!')
    193             return False
    194 
    195 
    196     @xmlrpc_server.dbus_safe(False)
    197     def reset_on(self):
    198         """Reset the adapter and settings and power up the adapter.
    199 
    200         @return True on success, False otherwise.
    201 
    202         """
    203         self._reset()
    204         if not self._adapter:
    205             return False
    206         self._set_powered(True)
    207         return True
    208 
    209 
    210     @xmlrpc_server.dbus_safe(False)
    211     def reset_off(self):
    212         """Reset the adapter and settings, leave the adapter powered off.
    213 
    214         @return True on success, False otherwise.
    215 
    216         """
    217         self._reset()
    218         return True
    219 
    220 
    221     def has_adapter(self):
    222         """Return if an adapter is present.
    223 
    224         This will only return True if we have determined both that there is
    225         a Bluetooth adapter on this device (kernel adapter index list is not
    226         empty) and that the Bluetooth daemon has exported an object for it.
    227 
    228         @return True if an adapter is present, False if not.
    229 
    230         """
    231         return self._has_adapter and self._adapter is not None
    232 
    233 
    234     def _reset(self):
    235         """Reset the Bluetooth adapter and settings."""
    236         logging.debug('_reset')
    237         if self._adapter:
    238             self._set_powered(False)
    239 
    240         try:
    241             self._bluetoothd.Stop(dbus.Array(signature='s'), True,
    242                                   dbus_interface=self.UPSTART_JOB_IFACE)
    243         except dbus.exceptions.DBusException, e:
    244             if e.get_dbus_name() != self.UPSTART_ERROR_UNKNOWNINSTANCE:
    245                 logging.error('Error resetting adapter!')
    246                 raise
    247 
    248         def bluez_stopped():
    249             """Checks the bluetooth daemon status.
    250 
    251             @returns: True if bluez is stopped. False otherwise.
    252 
    253             """
    254             return not self._update_bluez()
    255 
    256         logging.debug('waiting for bluez stop')
    257         utils.poll_for_condition(
    258                 condition=bluez_stopped,
    259                 desc='Bluetooth Daemon has stopped.',
    260                 timeout=self.ADAPTER_TIMEOUT)
    261 
    262         for subdir in os.listdir(self.BLUETOOTH_LIBDIR):
    263             shutil.rmtree(os.path.join(self.BLUETOOTH_LIBDIR, subdir))
    264 
    265         self._bluetoothd.Start(dbus.Array(signature='s'), True,
    266                                dbus_interface=self.UPSTART_JOB_IFACE)
    267 
    268         logging.debug('waiting for bluez start')
    269         utils.poll_for_condition(
    270                 condition=self._update_bluez,
    271                 desc='Bluetooth Daemon has started.',
    272                 timeout=self.ADAPTER_TIMEOUT)
    273 
    274         logging.debug('waiting for bluez to obtain adapter information')
    275         utils.poll_for_condition(
    276                 condition=self._update_adapter,
    277                 desc='Bluetooth Daemon has adapter information.',
    278                 timeout=self.ADAPTER_TIMEOUT)
    279 
    280 
    281     @xmlrpc_server.dbus_safe(False)
    282     def set_powered(self, powered):
    283         """Set the adapter power state.
    284 
    285         @param powered: adapter power state to set (True or False).
    286 
    287         @return True on success, False otherwise.
    288 
    289         """
    290         if not self._adapter:
    291             if not powered:
    292                 # Return success if we are trying to power off an adapter that's
    293                 # missing or gone away, since the expected result has happened.
    294                 return True
    295             else:
    296                 logging.warning('Adapter not found!')
    297                 return False
    298         self._set_powered(powered)
    299         return True
    300 
    301 
    302     def _set_powered(self, powered):
    303         """Set the adapter power state.
    304 
    305         @param powered: adapter power state to set (True or False).
    306 
    307         """
    308         logging.debug('_set_powered %r', powered)
    309         self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 'Powered', powered,
    310                           dbus_interface=dbus.PROPERTIES_IFACE)
    311 
    312 
    313     @xmlrpc_server.dbus_safe(False)
    314     def set_discoverable(self, discoverable):
    315         """Set the adapter discoverable state.
    316 
    317         @param discoverable: adapter discoverable state to set (True or False).
    318 
    319         @return True on success, False otherwise.
    320 
    321         """
    322         if not discoverable and not self._adapter:
    323             # Return success if we are trying to make an adapter that's
    324             # missing or gone away, undiscoverable, since the expected result
    325             # has happened.
    326             return True
    327         self._adapter.Set(self.BLUEZ_ADAPTER_IFACE,
    328                           'Discoverable', discoverable,
    329                           dbus_interface=dbus.PROPERTIES_IFACE)
    330         return True
    331 
    332 
    333     @xmlrpc_server.dbus_safe(False)
    334     def set_pairable(self, pairable):
    335         """Set the adapter pairable state.
    336 
    337         @param pairable: adapter pairable state to set (True or False).
    338 
    339         @return True on success, False otherwise.
    340 
    341         """
    342         self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 'Pairable', pairable,
    343                           dbus_interface=dbus.PROPERTIES_IFACE)
    344         return True
    345 
    346 
    347     @xmlrpc_server.dbus_safe(False)
    348     def get_adapter_properties(self):
    349         """Read the adapter properties from the Bluetooth Daemon.
    350 
    351         @return the properties as a JSON-encoded dictionary on success,
    352             the value False otherwise.
    353 
    354         """
    355         objects = self._bluez.GetManagedObjects(
    356                 dbus_interface=self.BLUEZ_MANAGER_IFACE)
    357         adapter = objects[self._adapter.object_path][self.BLUEZ_ADAPTER_IFACE]
    358         return json.dumps(adapter)
    359 
    360 
    361     def read_version(self):
    362         """Read the version of the management interface from the Kernel.
    363 
    364         @return the information as a JSON-encoded tuple of:
    365           ( version, revision )
    366 
    367         """
    368         return json.dumps(self._control.read_version())
    369 
    370 
    371     def read_supported_commands(self):
    372         """Read the set of supported commands from the Kernel.
    373 
    374         @return the information as a JSON-encoded tuple of:
    375           ( commands, events )
    376 
    377         """
    378         return json.dumps(self._control.read_supported_commands())
    379 
    380 
    381     def read_index_list(self):
    382         """Read the list of currently known controllers from the Kernel.
    383 
    384         @return the information as a JSON-encoded array of controller indexes.
    385 
    386         """
    387         return json.dumps(self._control.read_index_list())
    388 
    389 
    390     def read_info(self):
    391         """Read the adapter information from the Kernel.
    392 
    393         @return the information as a JSON-encoded tuple of:
    394           ( address, bluetooth_version, manufacturer_id,
    395             supported_settings, current_settings, class_of_device,
    396             name, short_name )
    397 
    398         """
    399         return json.dumps(self._control.read_info(0))
    400 
    401 
    402     def add_device(self, address, address_type, action):
    403         """Add a device to the Kernel action list.
    404 
    405         @param address: Address of the device to add.
    406         @param address_type: Type of device in @address.
    407         @param action: Action to take.
    408 
    409         @return on success, a JSON-encoded typle of:
    410           ( address, address_type ), None on failure.
    411 
    412         """
    413         return json.dumps(self._control.add_device(
    414                 0, address, address_type, action))
    415 
    416 
    417     def remove_device(self, address, address_type):
    418         """Remove a device from the Kernel action list.
    419 
    420         @param address: Address of the device to remove.
    421         @param address_type: Type of device in @address.
    422 
    423         @return on success, a JSON-encoded typle of:
    424           ( address, address_type ), None on failure.
    425 
    426         """
    427         return json.dumps(self._control.remove_device(
    428                 0, address, address_type))
    429 
    430 
    431     @xmlrpc_server.dbus_safe(False)
    432     def get_devices(self):
    433         """Read information about remote devices known to the adapter.
    434 
    435         @return the properties of each device as a JSON-encoded array of
    436             dictionaries on success, the value False otherwise.
    437 
    438         """
    439         objects = self._bluez.GetManagedObjects(
    440                 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=True)
    441         devices = []
    442         for path, ifaces in objects.iteritems():
    443             if self.BLUEZ_DEVICE_IFACE in ifaces:
    444                 devices.append(objects[path][self.BLUEZ_DEVICE_IFACE])
    445         return json.dumps(devices)
    446 
    447 
    448     @xmlrpc_server.dbus_safe(False)
    449     def start_discovery(self):
    450         """Start discovery of remote devices.
    451 
    452         Obtain the discovered device information using get_devices(), called
    453         stop_discovery() when done.
    454 
    455         @return True on success, False otherwise.
    456 
    457         """
    458         if not self._adapter:
    459             return False
    460         self._adapter.StartDiscovery(dbus_interface=self.BLUEZ_ADAPTER_IFACE)
    461         return True
    462 
    463 
    464     @xmlrpc_server.dbus_safe(False)
    465     def stop_discovery(self):
    466         """Stop discovery of remote devices.
    467 
    468         @return True on success, False otherwise.
    469 
    470         """
    471         if not self._adapter:
    472             return False
    473         self._adapter.StopDiscovery(dbus_interface=self.BLUEZ_ADAPTER_IFACE)
    474         return True
    475 
    476 
    477     def get_dev_info(self):
    478         """Read raw HCI device information.
    479 
    480         @return JSON-encoded tuple of:
    481                 (index, name, address, flags, device_type, bus_type,
    482                        features, pkt_type, link_policy, link_mode,
    483                        acl_mtu, acl_pkts, sco_mtu, sco_pkts,
    484                        err_rx, err_tx, cmd_tx, evt_rx, acl_tx, acl_rx,
    485                        sco_tx, sco_rx, byte_rx, byte_tx) on success,
    486                 None on failure.
    487 
    488         """
    489         return json.dumps(self._raw.get_dev_info(0))
    490 
    491 
    492     @xmlrpc_server.dbus_safe(False)
    493     def register_profile(self, path, uuid, options):
    494         """Register new profile (service).
    495 
    496         @param path: Path to the profile object.
    497         @param uuid: Service Class ID of the service as string.
    498         @param options: Dictionary of options for the new service, compliant
    499                         with BlueZ D-Bus Profile API standard.
    500 
    501         @return True on success, False otherwise.
    502 
    503         """
    504         profile_manager = dbus.Interface(
    505                               self._system_bus.get_object(
    506                                   self.BLUEZ_SERVICE_NAME,
    507                                   self.BLUEZ_PROFILE_MANAGER_PATH),
    508                               self.BLUEZ_PROFILE_MANAGER_IFACE)
    509         profile_manager.RegisterProfile(path, uuid, options)
    510         return True
    511 
    512 
    513     @xmlrpc_server.dbus_safe(False)
    514     def has_device(self, address):
    515         """Checks if the device with a given address exists.
    516 
    517         @param address: Address of the device.
    518 
    519         @returns: True if there is a device with that address.
    520                   False otherwise.
    521 
    522         """
    523         return self._find_device(address) != None
    524 
    525 
    526     def _find_device(self, address):
    527         """Finds the device with a given address.
    528 
    529         Find the device with a given address and returns the
    530         device interface.
    531 
    532         @param address: Address of the device.
    533 
    534         @returns: An 'org.bluez.Device1' interface to the device.
    535                   None if device can not be found.
    536 
    537         """
    538         objects = self._bluez.GetManagedObjects(
    539                 dbus_interface=self.BLUEZ_MANAGER_IFACE)
    540         for path, ifaces in objects.iteritems():
    541             device = ifaces.get(self.BLUEZ_DEVICE_IFACE)
    542             if device is None:
    543                 continue
    544             if (device['Address'] == address and
    545                 path.startswith(self._adapter.object_path)):
    546                 obj = self._system_bus.get_object(
    547                         self.BLUEZ_SERVICE_NAME, path)
    548                 return dbus.Interface(obj, self.BLUEZ_DEVICE_IFACE)
    549         logging.error('Device not found')
    550         return None
    551 
    552 
    553     def _setup_pin_agent(self, pin):
    554         """Initializes a _PinAgent and registers it to handle pin code request.
    555 
    556         @param pin: The pin code this agent will answer.
    557 
    558         """
    559         agent_path = '/test/agent'
    560         if self._pin_agent:
    561             logging.info('Removing the old agent before initializing a new one')
    562             self._pin_agent.remove_from_connection()
    563             self._pin_agent = None
    564         self._pin_agent = _PinAgent(pin, self._system_bus, agent_path)
    565         agent_manager = dbus.Interface(
    566                 self._system_bus.get_object(self.BLUEZ_SERVICE_NAME,
    567                                             self.BLUEZ_AGENT_MANAGER_PATH),
    568                 self.BLUEZ_AGENT_MANAGER_IFACE)
    569         try:
    570             agent_manager.RegisterAgent(agent_path, 'NoInputNoOutput')
    571         except dbus.exceptions.DBusException, e:
    572             if e.get_dbus_name() == self.BLUEZ_ERROR_ALREADY_EXISTS:
    573                 logging.info('Unregistering old agent and registering the new')
    574                 agent_manager.UnregisterAgent(agent_path)
    575                 agent_manager.RegisterAgent(agent_path, 'NoInputNoOutput')
    576             else:
    577                 logging.error('Error setting up pin agent: %s', e)
    578                 raise
    579         logging.info('Agent registered')
    580 
    581 
    582     def _is_paired(self,  device):
    583         """Checks if a device is paired.
    584 
    585         @param device: An 'org.bluez.Device1' interface to the device.
    586 
    587         @returns: True if device is paired. False otherwise.
    588 
    589         """
    590         props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
    591         paired = props.Get(self.BLUEZ_DEVICE_IFACE, 'Paired')
    592         return bool(paired)
    593 
    594 
    595     def _is_connected(self,  device):
    596         """Checks if a device is connected.
    597 
    598         @param device: An 'org.bluez.Device1' interface to the device.
    599 
    600         @returns: True if device is connected. False otherwise.
    601 
    602         """
    603         props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
    604         connected = props.Get(self.BLUEZ_DEVICE_IFACE, 'Connected')
    605         logging.info('Got connected = %r', connected)
    606         return bool(connected)
    607 
    608 
    609     @xmlrpc_server.dbus_safe(False)
    610     def pair_legacy_device(self, address, pin, timeout):
    611         """Pairs a device with a given pin code.
    612 
    613         Registers a agent who handles pin code request and
    614         pairs a device with known pin code.
    615 
    616         @param address: Address of the device to pair.
    617         @param pin: The pin code of the device to pair.
    618         @param timeout: The timeout in seconds for pairing.
    619 
    620         @returns: True on success. False otherwise.
    621 
    622         """
    623         device = self._find_device(address)
    624         if not device:
    625             logging.error('Device not found')
    626             return False
    627         if self._is_paired(device):
    628             logging.info('Device is already paired')
    629             return True
    630 
    631         self._setup_pin_agent(pin)
    632         mainloop = gobject.MainLoop()
    633 
    634 
    635         def pair_reply():
    636             """Handler when pairing succeeded."""
    637             logging.info('Device paired')
    638             mainloop.quit()
    639 
    640 
    641         def pair_error(error):
    642             """Handler when pairing failed.
    643 
    644             @param error: one of errors defined in org.bluez.Error representing
    645                           the error in pairing.
    646 
    647             """
    648             try:
    649                 error_name = error.get_dbus_name()
    650                 if error_name == 'org.freedesktop.DBus.Error.NoReply':
    651                     logging.error('Timed out. Cancelling pairing')
    652                     device.CancelPairing()
    653                 else:
    654                     logging.error('Pairing device failed: %s', error)
    655             finally:
    656                 mainloop.quit()
    657 
    658 
    659         device.Pair(reply_handler=pair_reply, error_handler=pair_error,
    660                     timeout=timeout * 1000)
    661         mainloop.run()
    662         return self._is_paired(device)
    663 
    664 
    665     @xmlrpc_server.dbus_safe(False)
    666     def remove_device_object(self, address):
    667         """Removes a device object and the pairing information.
    668 
    669         Calls RemoveDevice method to remove remote device
    670         object and the pairing information.
    671 
    672         @param address: Address of the device to unpair.
    673 
    674         @returns: True on success. False otherwise.
    675 
    676         """
    677         device = self._find_device(address)
    678         if not device:
    679             logging.error('Device not found')
    680             return False
    681         self._adapter.RemoveDevice(
    682                 device.object_path, dbus_interface=self.BLUEZ_ADAPTER_IFACE)
    683         return True
    684 
    685 
    686     @xmlrpc_server.dbus_safe(False)
    687     def connect_device(self, address):
    688         """Connects a device.
    689 
    690         Connects a device if it is not connected.
    691 
    692         @param address: Address of the device to connect.
    693 
    694         @returns: True on success. False otherwise.
    695 
    696         """
    697         device = self._find_device(address)
    698         if not device:
    699             logging.error('Device not found')
    700             return False
    701         if self._is_connected(device):
    702           logging.info('Device is already connected')
    703           return True
    704         device.Connect()
    705         return self._is_connected(device)
    706 
    707 
    708     @xmlrpc_server.dbus_safe(False)
    709     def device_is_connected(self, address):
    710         """Checks if a device is connected.
    711 
    712         @param address: Address of the device to connect.
    713 
    714         @returns: True if device is connected. False otherwise.
    715 
    716         """
    717         device = self._find_device(address)
    718         if not device:
    719             logging.error('Device not found')
    720             return False
    721         return self._is_connected(device)
    722 
    723 
    724     @xmlrpc_server.dbus_safe(False)
    725     def disconnect_device(self, address):
    726         """Disconnects a device.
    727 
    728         Disconnects a device if it is connected.
    729 
    730         @param address: Address of the device to disconnect.
    731 
    732         @returns: True on success. False otherwise.
    733 
    734         """
    735         device = self._find_device(address)
    736         if not device:
    737             logging.error('Device not found')
    738             return False
    739         if not self._is_connected(device):
    740           logging.info('Device is not connected')
    741           return True
    742         device.Disconnect()
    743         return not self._is_connected(device)
    744 
    745 
    746 if __name__ == '__main__':
    747     logging.basicConfig(level=logging.DEBUG)
    748     handler = logging.handlers.SysLogHandler(address='/dev/log')
    749     formatter = logging.Formatter(
    750             'bluetooth_device_xmlrpc_server: [%(levelname)s] %(message)s')
    751     handler.setFormatter(formatter)
    752     logging.getLogger().addHandler(handler)
    753     logging.debug('bluetooth_device_xmlrpc_server main...')
    754     server = xmlrpc_server.XmlRpcServer(
    755             'localhost',
    756             constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_PORT)
    757     server.register_delegate(BluetoothDeviceXmlRpcDelegate())
    758     server.run()
    759