blob: 18a37e0eeae1057d638d6153a978476700a38116 [file] [log] [blame]
Mike Frysingerd03e6b52019-08-03 12:49:01 -04001#!/usr/bin/env python2
Scott James Remnant4dcd73f2013-07-22 15:00:24 -07002
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
Dane Pollockaee75642017-06-09 12:56:28 -07007import base64
Scott James Remnanta6442f52013-07-24 15:04:55 -07008import dbus
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08009import dbus.mainloop.glib
10import dbus.service
11import gobject
Shyh-In Hwang9f759f82018-11-19 09:40:19 +000012import json
Scott James Remnant4dcd73f2013-07-22 15:00:24 -070013import logging
14import logging.handlers
Shijin Abraham63665982019-12-27 11:45:30 -080015import subprocess
Scott James Remnant4dcd73f2013-07-22 15:00:24 -070016
17import common
Scott James Remnant1c72d7a2013-07-29 15:00:04 -070018from autotest_lib.client.bin import utils
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -070019from autotest_lib.client.common_lib.cros.bluetooth import bluetooth_socket
Hsinyu Chaoe0b08e62015-08-11 10:50:37 +000020from autotest_lib.client.cros import constants
Christopher Wileyec0b71d2015-08-05 09:44:42 -070021from autotest_lib.client.cros import xmlrpc_server
Joseph Hwangefaf0352016-09-29 14:53:33 +080022from autotest_lib.client.cros.bluetooth import advertisement
23from autotest_lib.client.cros.bluetooth import output_recorder
Scott James Remnant4dcd73f2013-07-22 15:00:24 -070024
25
Dane Pollockaee75642017-06-09 12:56:28 -070026def _dbus_byte_array_to_b64_string(dbus_byte_array):
27 """Base64 encodes a dbus byte array for use with the xml rpc proxy."""
28 return base64.standard_b64encode(bytearray(dbus_byte_array))
29
30
31def _b64_string_to_dbus_byte_array(b64_string):
32 """Base64 decodes a dbus byte array for use with the xml rpc proxy."""
33 dbus_array = dbus.Array([], signature=dbus.Signature('y'))
34 bytes = bytearray(base64.standard_b64decode(b64_string))
35 for byte in bytes:
36 dbus_array.append(dbus.Byte(byte))
37 return dbus_array
38
39
Joseph Hwangf064e0f2016-05-23 16:46:45 +080040class PairingAgent(dbus.service.Object):
41 """The agent handling the authentication process of bluetooth pairing.
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +080042
Joseph Hwangf064e0f2016-05-23 16:46:45 +080043 PairingAgent overrides RequestPinCode method to return a given pin code.
44 User can use this agent to pair bluetooth device which has a known
45 pin code.
46
47 TODO (josephsih): more pairing modes other than pin code would be
48 supported later.
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +080049
50 """
Joseph Hwangf064e0f2016-05-23 16:46:45 +080051
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +080052 def __init__(self, pin, *args, **kwargs):
Joseph Hwangf064e0f2016-05-23 16:46:45 +080053 super(PairingAgent, self).__init__(*args, **kwargs)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +080054 self._pin = pin
55
56
Joseph Hwangf064e0f2016-05-23 16:46:45 +080057 @dbus.service.method('org.bluez.Agent1',
58 in_signature='o', out_signature='s')
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +080059 def RequestPinCode(self, device_path):
60 """Requests pin code for a device.
61
62 Returns the known pin code for the request.
63
64 @param device_path: The object path of the device.
65
66 @returns: The known pin code.
67
68 """
Joseph Hwangf064e0f2016-05-23 16:46:45 +080069 logging.info('RequestPinCode for %s; return %s', device_path, self._pin)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +080070 return self._pin
71
72
Scott James Remnant8d2cbf32013-11-12 11:00:25 -080073class BluetoothDeviceXmlRpcDelegate(xmlrpc_server.XmlRpcDelegate):
Scott James Remnant1c72d7a2013-07-29 15:00:04 -070074 """Exposes DUT methods called remotely during Bluetooth autotests.
Scott James Remnant4dcd73f2013-07-22 15:00:24 -070075
76 All instance methods of this object without a preceding '_' are exposed via
77 an XML-RPC server. This is not a stateless handler object, which means that
Scott James Remnant1c72d7a2013-07-29 15:00:04 -070078 if you store state inside the delegate, that state will remain around for
Scott James Remnant4dcd73f2013-07-22 15:00:24 -070079 future calls.
80 """
81
Scott James Remnanta6442f52013-07-24 15:04:55 -070082 UPSTART_PATH = 'unix:abstract=/com/ubuntu/upstart'
83 UPSTART_MANAGER_PATH = '/com/ubuntu/Upstart'
84 UPSTART_MANAGER_IFACE = 'com.ubuntu.Upstart0_6'
85 UPSTART_JOB_IFACE = 'com.ubuntu.Upstart0_6.Job'
86
87 UPSTART_ERROR_UNKNOWNINSTANCE = \
88 'com.ubuntu.Upstart0_6.Error.UnknownInstance'
Joseph Hwang97ff05d2016-05-10 16:07:22 +080089 UPSTART_ERROR_ALREADYSTARTED = \
90 'com.ubuntu.Upstart0_6.Error.AlreadyStarted'
Scott James Remnanta6442f52013-07-24 15:04:55 -070091
Sonny Sasaka8635d0b2019-12-03 14:16:28 -080092 BLUETOOTHD_JOB = 'bluetoothd'
Scott James Remnanta6442f52013-07-24 15:04:55 -070093
94 DBUS_ERROR_SERVICEUNKNOWN = 'org.freedesktop.DBus.Error.ServiceUnknown'
95
Michael Sun8f81f382019-08-12 14:42:26 -070096 BLUETOOTH_SERVICE_NAME = 'org.chromium.Bluetooth'
Sonny Sasaka8635d0b2019-12-03 14:16:28 -080097 BLUEZ_SERVICE_NAME = 'org.bluez'
Scott James Remnanta6442f52013-07-24 15:04:55 -070098 BLUEZ_MANAGER_PATH = '/'
howardchung25072402019-11-11 19:15:16 +080099 BLUEZ_DEBUG_LOG_PATH = '/org/chromium/Bluetooth'
100 BLUEZ_DEBUG_LOG_IFACE = 'org.chromium.Bluetooth.Debug'
Scott James Remnanta6442f52013-07-24 15:04:55 -0700101 BLUEZ_MANAGER_IFACE = 'org.freedesktop.DBus.ObjectManager'
102 BLUEZ_ADAPTER_IFACE = 'org.bluez.Adapter1'
Scott James Remnantaec4edd2013-08-26 18:47:11 -0700103 BLUEZ_DEVICE_IFACE = 'org.bluez.Device1'
howardchungb3752742019-09-18 09:57:14 +0800104 BLUEZ_GATT_SERV_IFACE = 'org.bluez.GattService1'
105 BLUEZ_GATT_CHAR_IFACE = 'org.bluez.GattCharacteristic1'
106 BLUEZ_GATT_DESC_IFACE = 'org.bluez.GattDescriptor1'
Joseph Hwangefaf0352016-09-29 14:53:33 +0800107 BLUEZ_LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800108 BLUEZ_AGENT_MANAGER_PATH = '/org/bluez'
109 BLUEZ_AGENT_MANAGER_IFACE = 'org.bluez.AgentManager1'
Artem Rakhovb144dce2014-02-20 21:02:09 -0800110 BLUEZ_PROFILE_MANAGER_PATH = '/org/bluez'
111 BLUEZ_PROFILE_MANAGER_IFACE = 'org.bluez.ProfileManager1'
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800112 BLUEZ_ERROR_ALREADY_EXISTS = 'org.bluez.Error.AlreadyExists'
Archie Pusakaa31b15b2019-11-06 18:13:05 +0800113 BLUEZ_PLUGIN_DEVICE_IFACE = 'org.chromium.BluetoothDevice'
Joseph Hwangf064e0f2016-05-23 16:46:45 +0800114 DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
115 AGENT_PATH = '/test/agent'
Scott James Remnanta6442f52013-07-24 15:04:55 -0700116
117 BLUETOOTH_LIBDIR = '/var/lib/bluetooth'
Joseph Hwangefaf0352016-09-29 14:53:33 +0800118 BTMON_STOP_DELAY_SECS = 3
Scott James Remnanta6442f52013-07-24 15:04:55 -0700119
120 # Timeout for how long we'll wait for BlueZ and the Adapter to show up
121 # after reset.
122 ADAPTER_TIMEOUT = 30
123
Scott James Remnant4dcd73f2013-07-22 15:00:24 -0700124 def __init__(self):
Scott James Remnant8d2cbf32013-11-12 11:00:25 -0800125 super(BluetoothDeviceXmlRpcDelegate, self).__init__()
Scott James Remnanta6442f52013-07-24 15:04:55 -0700126
Scott James Remnantc7fd7a42014-12-01 16:43:38 -0800127 # Open the Bluetooth Raw socket to the kernel which provides us direct,
128 # raw, access to the HCI controller.
129 self._raw = bluetooth_socket.BluetoothRawSocket()
130
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700131 # Open the Bluetooth Control socket to the kernel which provides us
132 # raw management access to the Bluetooth Host Subsystem. Read the list
133 # of adapter indexes to determine whether or not this device has a
134 # Bluetooth Adapter or not.
135 self._control = bluetooth_socket.BluetoothControlSocket()
136 self._has_adapter = len(self._control.read_index_list()) > 0
137
Scott James Remnanta6442f52013-07-24 15:04:55 -0700138 # Set up the connection to Upstart so we can start and stop services
139 # and fetch the bluetoothd job.
140 self._upstart_conn = dbus.connection.Connection(self.UPSTART_PATH)
141 self._upstart = self._upstart_conn.get_object(
142 None,
143 self.UPSTART_MANAGER_PATH)
144
145 bluetoothd_path = self._upstart.GetJobByName(
146 self.BLUETOOTHD_JOB,
147 dbus_interface=self.UPSTART_MANAGER_IFACE)
148 self._bluetoothd = self._upstart_conn.get_object(
149 None,
150 bluetoothd_path)
151
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800152 # Arrange for the GLib main loop to be the default.
153 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
154
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700155 # Set up the connection to the D-Bus System Bus, get the object for
156 # the Bluetooth Userspace Daemon (BlueZ) and that daemon's object for
Joseph Hwangefaf0352016-09-29 14:53:33 +0800157 # the Bluetooth Adapter, and the advertising manager.
Scott James Remnanta6442f52013-07-24 15:04:55 -0700158 self._system_bus = dbus.SystemBus()
159 self._update_bluez()
160 self._update_adapter()
Joseph Hwangefaf0352016-09-29 14:53:33 +0800161 self._update_advertising()
Scott James Remnanta6442f52013-07-24 15:04:55 -0700162
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800163 # The agent to handle pin code request, which will be
164 # created when user calls pair_legacy_device method.
Joseph Hwangf064e0f2016-05-23 16:46:45 +0800165 self._pairing_agent = None
166 # The default capability of the agent.
167 self._capability = 'KeyboardDisplay'
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800168
Joseph Hwangefaf0352016-09-29 14:53:33 +0800169 # Initailize a btmon object to record bluetoothd's activity.
170 self.btmon = output_recorder.OutputRecorder(
Yun-Hao Chungfa1db772020-01-14 22:57:40 +0000171 'btmon', stop_delay_secs=self.BTMON_STOP_DELAY_SECS)
Joseph Hwangefaf0352016-09-29 14:53:33 +0800172
Joseph Hwang8de9dae2016-10-19 18:16:31 +0800173 self.advertisements = []
Joseph Hwang7668c9c2016-11-21 16:04:38 +0800174 self._adv_mainloop = gobject.MainLoop()
Joseph Hwang8de9dae2016-10-19 18:16:31 +0800175
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700176
Shijin Abraham8d63f622019-12-05 17:03:08 -0800177 @xmlrpc_server.dbus_safe(False)
howardchung25072402019-11-11 19:15:16 +0800178 def set_debug_log_levels(self, dispatcher_vb, newblue_vb, bluez_vb,
179 kernel_vb):
180 """Enable or disable the debug logs of bluetooth
181
182 @param dispatcher_vb: verbosity of btdispatcher debug log, either 0 or 1
183 @param newblue_vb: verbosity of newblued debug log, either 0 or 1
184 @param bluez_vb: verbosity of bluez debug log, either 0 or 1
185 @param kernel_vb: verbosity of kernel debug log, either 0 or 1
186
187 """
Archie Pusaka054d1722019-12-09 19:04:57 +0800188
189 # TODO(b/145163508, b/145749798): update when debug logs is migrated to
190 # bluez.
howardchung25072402019-11-11 19:15:16 +0800191 debug_object = self._system_bus.get_object(
Archie Pusaka054d1722019-12-09 19:04:57 +0800192 self.BLUETOOTH_SERVICE_NAME,
howardchung25072402019-11-11 19:15:16 +0800193 self.BLUEZ_DEBUG_LOG_PATH)
194 debug_object.SetLevels(dbus.Byte(dispatcher_vb),
195 dbus.Byte(newblue_vb),
196 dbus.Byte(bluez_vb),
197 dbus.Byte(kernel_vb),
198 dbus_interface=self.BLUEZ_DEBUG_LOG_IFACE)
199 return
200
Shijin Abraham63665982019-12-27 11:45:30 -0800201 def log_message(self, msg):
202 """ log a message to /var/log/messages."""
203 try:
204 cmd = ['logger', msg]
205 subprocess.call(cmd)
206 except Exception as e:
207 logging.error("log_message %s failed with %s", cmd, str(e))
208
howardchung25072402019-11-11 19:15:16 +0800209
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800210 @xmlrpc_server.dbus_safe(False)
211 def start_bluetoothd(self):
212 """start bluetoothd.
213
214 This includes powering up the adapter.
215
216 @returns: True if bluetoothd is started correctly.
217 False otherwise.
218
219 """
220 try:
221 self._bluetoothd.Start(dbus.Array(signature='s'), True,
222 dbus_interface=self.UPSTART_JOB_IFACE)
223 except dbus.exceptions.DBusException as e:
224 # if bluetoothd was already started, the exception looks like
225 # dbus.exceptions.DBusException:
226 # com.ubuntu.Upstart0_6.Error.AlreadyStarted: Job is already
227 # running: bluetoothd
228 if e.get_dbus_name() != self.UPSTART_ERROR_ALREADYSTARTED:
229 logging.error('Error starting bluetoothd: %s', e)
230 return False
231
232 logging.debug('waiting for bluez start')
233 try:
234 utils.poll_for_condition(
235 condition=self._update_bluez,
236 desc='Bluetooth Daemon has started.',
237 timeout=self.ADAPTER_TIMEOUT)
238 except Exception as e:
239 logging.error('timeout: error starting bluetoothd: %s', e)
240 return False
241
242 # Waiting for the self._adapter object.
243 # This does not mean that the adapter is powered on.
244 logging.debug('waiting for bluez to obtain adapter information')
245 try:
246 utils.poll_for_condition(
247 condition=self._update_adapter,
248 desc='Bluetooth Daemon has adapter information.',
249 timeout=self.ADAPTER_TIMEOUT)
250 except Exception as e:
251 logging.error('timeout: error starting adapter: %s', e)
252 return False
253
Joseph Hwangefaf0352016-09-29 14:53:33 +0800254 # Waiting for the self._advertising interface object.
255 logging.debug('waiting for bluez to obtain interface manager.')
256 try:
257 utils.poll_for_condition(
258 condition=self._update_advertising,
259 desc='Bluetooth Daemon has advertising interface.',
260 timeout=self.ADAPTER_TIMEOUT)
261 except utils.TimeoutError:
262 logging.error('timeout: error getting advertising interface')
263 return False
264
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800265 return True
266
267
268 @xmlrpc_server.dbus_safe(False)
269 def stop_bluetoothd(self):
270 """stop bluetoothd.
271
272 @returns: True if bluetoothd is stopped correctly.
273 False otherwise.
274
275 """
276 def bluez_stopped():
277 """Checks the bluetooth daemon status.
278
279 @returns: True if bluez is stopped. False otherwise.
280
281 """
282 return not self._update_bluez()
283
284 try:
285 self._bluetoothd.Stop(dbus.Array(signature='s'), True,
286 dbus_interface=self.UPSTART_JOB_IFACE)
287 except dbus.exceptions.DBusException as e:
288 # If bluetoothd was stopped already, the exception looks like
289 # dbus.exceptions.DBusException:
290 # com.ubuntu.Upstart0_6.Error.UnknownInstance: Unknown instance:
291 if e.get_dbus_name() != self.UPSTART_ERROR_UNKNOWNINSTANCE:
292 logging.error('Error stopping bluetoothd!')
293 return False
294
295 logging.debug('waiting for bluez stop')
296 try:
297 utils.poll_for_condition(
298 condition=bluez_stopped,
299 desc='Bluetooth Daemon has stopped.',
300 timeout=self.ADAPTER_TIMEOUT)
301 bluetoothd_stopped = True
302 except Exception as e:
303 logging.error('timeout: error stopping bluetoothd: %s', e)
304 bluetoothd_stopped = False
305
306 return bluetoothd_stopped
307
308
309 def is_bluetoothd_running(self):
310 """Is bluetoothd running?
311
312 @returns: True if bluetoothd is running
313
314 """
315 return bool(self._get_dbus_proxy_for_bluetoothd())
316
317
Scott James Remnanta6442f52013-07-24 15:04:55 -0700318 def _update_bluez(self):
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700319 """Store a D-Bus proxy for the Bluetooth daemon in self._bluez.
320
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700321 This may be called in a loop until it returns True to wait for the
322 daemon to be ready after it has been started.
323
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700324 @return True on success, False otherwise.
325
326 """
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800327 self._bluez = self._get_dbus_proxy_for_bluetoothd()
328 return bool(self._bluez)
329
330
331 @xmlrpc_server.dbus_safe(False)
332 def _get_dbus_proxy_for_bluetoothd(self):
333 """Get the D-Bus proxy for the Bluetooth daemon.
334
335 @return True on success, False otherwise.
336
337 """
338 bluez = None
Scott James Remnanta6442f52013-07-24 15:04:55 -0700339 try:
Shijin Abrahamaa52eda2020-01-04 15:45:03 -0800340 bluez = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME,
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800341 self.BLUEZ_MANAGER_PATH)
Scott James Remnanta6442f52013-07-24 15:04:55 -0700342 logging.debug('bluetoothd is running')
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800343 except dbus.exceptions.DBusException as e:
344 # When bluetoothd is not running, the exception looks like
345 # dbus.exceptions.DBusException:
346 # org.freedesktop.DBus.Error.ServiceUnknown: The name org.bluez
347 # was not provided by any .service files
Scott James Remnanta6442f52013-07-24 15:04:55 -0700348 if e.get_dbus_name() == self.DBUS_ERROR_SERVICEUNKNOWN:
349 logging.debug('bluetoothd is not running')
Scott James Remnanta6442f52013-07-24 15:04:55 -0700350 else:
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800351 logging.error('Error getting dbus proxy for Bluez: %s', e)
352 return bluez
Scott James Remnanta6442f52013-07-24 15:04:55 -0700353
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700354
Scott James Remnanta6442f52013-07-24 15:04:55 -0700355 def _update_adapter(self):
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700356 """Store a D-Bus proxy for the local adapter in self._adapter.
357
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700358 This may be called in a loop until it returns True to wait for the
359 daemon to be ready, and have obtained the adapter information itself,
360 after it has been started.
361
362 Since not all devices will have adapters, this will also return True
363 in the case where we have obtained an empty adapter index list from the
364 kernel.
365
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800366 Note that this method does not power on the adapter.
367
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700368 @return True on success, including if there is no local adapter,
369 False otherwise.
370
371 """
Scott James Remnanta6442f52013-07-24 15:04:55 -0700372 self._adapter = None
373 if self._bluez is None:
Katherine Threlkeldc0269872015-08-19 16:15:29 -0700374 logging.warning('Bluez not found!')
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700375 return False
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700376 if not self._has_adapter:
Katherine Threlkeldc0269872015-08-19 16:15:29 -0700377 logging.debug('Device has no adapter; returning')
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700378 return True
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800379 self._adapter = self._get_adapter()
380 return bool(self._adapter)
Scott James Remnanta6442f52013-07-24 15:04:55 -0700381
Joseph Hwangefaf0352016-09-29 14:53:33 +0800382 def _update_advertising(self):
383 """Store a D-Bus proxy for the local advertising interface manager.
384
385 This may be called repeatedly in a loop until True is returned;
386 otherwise we wait for bluetoothd to start. After bluetoothd starts, we
387 check the existence of a local adapter and proceed to get the
388 advertisement interface manager.
389
390 Since not all devices will have adapters, this will also return True
391 in the case where there is no adapter.
392
393 @return True on success, including if there is no local adapter,
394 False otherwise.
395
396 """
397 self._advertising = None
398 if self._bluez is None:
399 logging.warning('Bluez not found!')
400 return False
401 if not self._has_adapter:
402 logging.debug('Device has no adapter; returning')
403 return True
404 self._advertising = self._get_advertising()
405 return bool(self._advertising)
406
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800407
408 @xmlrpc_server.dbus_safe(False)
409 def _get_adapter(self):
410 """Get the D-Bus proxy for the local adapter.
411
412 @return the adapter on success. None otherwise.
413
414 """
Scott James Remnanta6442f52013-07-24 15:04:55 -0700415 objects = self._bluez.GetManagedObjects(
416 dbus_interface=self.BLUEZ_MANAGER_IFACE)
417 for path, ifaces in objects.iteritems():
418 logging.debug('%s -> %r', path, ifaces.keys())
419 if self.BLUEZ_ADAPTER_IFACE in ifaces:
420 logging.debug('using adapter %s', path)
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800421 adapter = self._system_bus.get_object(
Shijin Abrahamaa52eda2020-01-04 15:45:03 -0800422 self.BLUEZ_SERVICE_NAME,
Scott James Remnanta6442f52013-07-24 15:04:55 -0700423 path)
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800424 return adapter
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700425 else:
Katherine Threlkeldc0269872015-08-19 16:15:29 -0700426 logging.warning('No adapter found in interface!')
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800427 return None
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700428
Scott James Remnanta6442f52013-07-24 15:04:55 -0700429
430 @xmlrpc_server.dbus_safe(False)
Joseph Hwangefaf0352016-09-29 14:53:33 +0800431 def _get_advertising(self):
432 """Get the D-Bus proxy for the local advertising interface.
433
434 @return the advertising interface object.
435
436 """
437 return dbus.Interface(self._adapter,
438 self.BLUEZ_LE_ADVERTISING_MANAGER_IFACE)
439
440
441 @xmlrpc_server.dbus_safe(False)
Scott James Remnanta6442f52013-07-24 15:04:55 -0700442 def reset_on(self):
443 """Reset the adapter and settings and power up the adapter.
444
445 @return True on success, False otherwise.
446
447 """
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800448 return self._reset(set_power=True)
Scott James Remnanta6442f52013-07-24 15:04:55 -0700449
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700450
Scott James Remnanta6442f52013-07-24 15:04:55 -0700451 @xmlrpc_server.dbus_safe(False)
452 def reset_off(self):
453 """Reset the adapter and settings, leave the adapter powered off.
454
455 @return True on success, False otherwise.
456
457 """
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800458 return self._reset(set_power=False)
Scott James Remnanta6442f52013-07-24 15:04:55 -0700459
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700460
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700461 def has_adapter(self):
462 """Return if an adapter is present.
463
464 This will only return True if we have determined both that there is
465 a Bluetooth adapter on this device (kernel adapter index list is not
466 empty) and that the Bluetooth daemon has exported an object for it.
467
468 @return True if an adapter is present, False if not.
469
470 """
471 return self._has_adapter and self._adapter is not None
472
473
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800474 def _reset(self, set_power=False):
Joseph Hwang1e9b4cc2017-06-26 15:02:51 +0800475 """Remove remote devices and set adapter to set_power state.
476
477 Do not restart bluetoothd as this may incur a side effect.
478 The unhappy chrome may disable the adapter randomly.
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800479
480 @param set_power: adapter power state to set (True or False).
481
482 @return True on success, False otherwise.
483
484 """
Scott James Remnanta6442f52013-07-24 15:04:55 -0700485 logging.debug('_reset')
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800486
Joseph Hwang1e9b4cc2017-06-26 15:02:51 +0800487 if not self._adapter:
488 logging.warning('Adapter not found!')
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800489 return False
490
Joseph Hwang1e9b4cc2017-06-26 15:02:51 +0800491 objects = self._bluez.GetManagedObjects(
492 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=True)
Scott James Remnanta6442f52013-07-24 15:04:55 -0700493
Joseph Hwang1e9b4cc2017-06-26 15:02:51 +0800494 devices = []
495 for path, ifaces in objects.iteritems():
496 if self.BLUEZ_DEVICE_IFACE in ifaces:
497 devices.append(objects[path][self.BLUEZ_DEVICE_IFACE])
Cheng-Yi Chiangc5b6d2c2015-05-25 23:52:42 +0800498
Joseph Hwang1e9b4cc2017-06-26 15:02:51 +0800499 # Turn on the adapter in order to remove all remote devices.
500 if not self._is_powered_on():
Shijin Abrahame06a80e2019-10-03 11:13:28 -0700501 if not self._set_powered(True):
502 logging.warning('Unable to power on the adapter')
503 return False
Cheng-Yi Chiangc5b6d2c2015-05-25 23:52:42 +0800504
Joseph Hwang1e9b4cc2017-06-26 15:02:51 +0800505 for device in devices:
506 logging.debug('removing %s', device.get('Address'))
507 self.remove_device_object(device.get('Address'))
508
Shijin Abrahame06a80e2019-10-03 11:13:28 -0700509 # Toggle power to the adapter.
510 if not self._set_powered(False):
511 logging.warning('Unable to power off adapter')
512 return False
513 if set_power and not self._set_powered(True):
514 logging.warning('Unable to power on adapter')
515 return False
Joseph Hwang1e9b4cc2017-06-26 15:02:51 +0800516
517 return True
518
Scott James Remnanta6442f52013-07-24 15:04:55 -0700519
520 @xmlrpc_server.dbus_safe(False)
521 def set_powered(self, powered):
522 """Set the adapter power state.
523
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700524 @param powered: adapter power state to set (True or False).
Scott James Remnanta6442f52013-07-24 15:04:55 -0700525
526 @return True on success, False otherwise.
527
528 """
Katherine Threlkeldc0269872015-08-19 16:15:29 -0700529 if not self._adapter:
530 if not powered:
531 # Return success if we are trying to power off an adapter that's
532 # missing or gone away, since the expected result has happened.
533 return True
534 else:
535 logging.warning('Adapter not found!')
536 return False
Shijin Abrahame06a80e2019-10-03 11:13:28 -0700537 return self._set_powered(powered)
Scott James Remnanta6442f52013-07-24 15:04:55 -0700538
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700539
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800540 @xmlrpc_server.dbus_safe(False)
Scott James Remnanta6442f52013-07-24 15:04:55 -0700541 def _set_powered(self, powered):
542 """Set the adapter power state.
543
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700544 @param powered: adapter power state to set (True or False).
Scott James Remnanta6442f52013-07-24 15:04:55 -0700545
546 """
547 logging.debug('_set_powered %r', powered)
howardchungac0af312019-09-27 15:28:20 +0800548 self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 'Powered',
549 dbus.Boolean(powered, variant_level=1),
Scott James Remnanta6442f52013-07-24 15:04:55 -0700550 dbus_interface=dbus.PROPERTIES_IFACE)
Shijin Abrahame06a80e2019-10-03 11:13:28 -0700551 return True
Scott James Remnanta6442f52013-07-24 15:04:55 -0700552
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700553
Scott James Remnanta6442f52013-07-24 15:04:55 -0700554 @xmlrpc_server.dbus_safe(False)
555 def set_discoverable(self, discoverable):
556 """Set the adapter discoverable state.
557
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700558 @param discoverable: adapter discoverable state to set (True or False).
Scott James Remnanta6442f52013-07-24 15:04:55 -0700559
560 @return True on success, False otherwise.
561
562 """
Scott James Remnantda9f43c2013-08-07 17:53:14 -0700563 if not discoverable and not self._adapter:
564 # Return success if we are trying to make an adapter that's
565 # missing or gone away, undiscoverable, since the expected result
566 # has happened.
567 return True
Scott James Remnanta6442f52013-07-24 15:04:55 -0700568 self._adapter.Set(self.BLUEZ_ADAPTER_IFACE,
howardchungac0af312019-09-27 15:28:20 +0800569 'Discoverable',
570 dbus.Boolean(discoverable, variant_level=1),
Scott James Remnanta6442f52013-07-24 15:04:55 -0700571 dbus_interface=dbus.PROPERTIES_IFACE)
572 return True
573
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700574
Scott James Remnanta6442f52013-07-24 15:04:55 -0700575 @xmlrpc_server.dbus_safe(False)
Shijin Abraham0d7d8fa2019-06-14 16:48:46 -0700576 def get_discoverable_timeout(self):
577 """Get the adapter discoverable_timeout.
578
579 @return True on success, False otherwise.
580
581 """
582 return int(self._adapter.Get(self.BLUEZ_ADAPTER_IFACE,
583 'DiscoverableTimeout',
584 dbus_interface=dbus.PROPERTIES_IFACE))
585
586
587 @xmlrpc_server.dbus_safe(False)
588 def set_discoverable_timeout(self, discoverable_timeout):
589 """Set the adapter discoverable_timeout property.
590
591 @param discoverable_timeout: adapter discoverable_timeout value
592 in seconds to set (Integer).
593
594 @return True on success, False otherwise.
595
596 """
597 self._adapter.Set(self.BLUEZ_ADAPTER_IFACE,
598 'DiscoverableTimeout',
599 dbus.UInt32(discoverable_timeout, variant_level=1),
600 dbus_interface=dbus.PROPERTIES_IFACE)
601 return True
602
603
604 @xmlrpc_server.dbus_safe(False)
Shijin Abrahama9f47922019-06-28 12:43:43 -0700605 def get_pairable_timeout(self):
606 """Get the adapter pairable_timeout.
607
608 @return True on success, False otherwise.
609
610 """
611 return int(self._adapter.Get(self.BLUEZ_ADAPTER_IFACE,
612 'PairableTimeout',
613 dbus_interface=dbus.PROPERTIES_IFACE))
614
615
616 @xmlrpc_server.dbus_safe(False)
617 def set_pairable_timeout(self, pairable_timeout):
618 """Set the adapter pairable_timeout property.
619
620 @param pairable_timeout: adapter pairable_timeout value
621 in seconds to set (Integer).
622
623 @return True on success, False otherwise.
624
625 """
626 self._adapter.Set(self.BLUEZ_ADAPTER_IFACE,
627 'PairableTimeout',
628 dbus.UInt32(pairable_timeout, variant_level=1),
629 dbus_interface=dbus.PROPERTIES_IFACE)
630 return True
631
632
633 @xmlrpc_server.dbus_safe(False)
Scott James Remnanta6442f52013-07-24 15:04:55 -0700634 def set_pairable(self, pairable):
635 """Set the adapter pairable state.
636
Scott James Remnant1c72d7a2013-07-29 15:00:04 -0700637 @param pairable: adapter pairable state to set (True or False).
Scott James Remnanta6442f52013-07-24 15:04:55 -0700638
639 @return True on success, False otherwise.
640
641 """
howardchungac0af312019-09-27 15:28:20 +0800642 self._adapter.Set(self.BLUEZ_ADAPTER_IFACE, 'Pairable',
643 dbus.Boolean(pairable, variant_level=1),
Scott James Remnanta6442f52013-07-24 15:04:55 -0700644 dbus_interface=dbus.PROPERTIES_IFACE)
645 return True
Scott James Remnant4dcd73f2013-07-22 15:00:24 -0700646
647
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700648 @xmlrpc_server.dbus_safe(False)
Joseph Hwang1e9b4cc2017-06-26 15:02:51 +0800649 def _get_adapter_properties(self):
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700650 """Read the adapter properties from the Bluetooth Daemon.
651
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000652 @return the properties as a JSON-encoded dictionary on success,
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700653 the value False otherwise.
654
655 """
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800656 if self._bluez:
657 objects = self._bluez.GetManagedObjects(
658 dbus_interface=self.BLUEZ_MANAGER_IFACE)
659 props = objects[self._adapter.object_path][self.BLUEZ_ADAPTER_IFACE]
660 else:
661 props = {}
662 logging.debug('get_adapter_properties: %s', props)
Joseph Hwang1e9b4cc2017-06-26 15:02:51 +0800663 return props
664
665
666 def get_adapter_properties(self):
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000667 return json.dumps(self._get_adapter_properties())
Joseph Hwang1e9b4cc2017-06-26 15:02:51 +0800668
669
670 def _is_powered_on(self):
671 return bool(self._get_adapter_properties().get(u'Powered'))
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700672
673
Scott James Remnant50915ad2014-12-01 13:51:39 -0800674 def read_version(self):
675 """Read the version of the management interface from the Kernel.
676
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000677 @return the information as a JSON-encoded tuple of:
Scott James Remnant50915ad2014-12-01 13:51:39 -0800678 ( version, revision )
679
680 """
howardchung76423462019-07-12 16:21:31 +0800681 #TODO(howardchung): resolve 'cannot allocate memory' error when
682 # BluetoothControlSocket idle too long(about 3 secs)
683 # (b:137603211)
684 _control = bluetooth_socket.BluetoothControlSocket()
685 return json.dumps(_control.read_version())
Scott James Remnant50915ad2014-12-01 13:51:39 -0800686
687
688 def read_supported_commands(self):
689 """Read the set of supported commands from the Kernel.
690
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000691 @return the information as a JSON-encoded tuple of:
Scott James Remnant50915ad2014-12-01 13:51:39 -0800692 ( commands, events )
693
694 """
howardchung76423462019-07-12 16:21:31 +0800695 #TODO(howardchung): resolve 'cannot allocate memory' error when
696 # BluetoothControlSocket idle too long(about 3 secs)
697 # (b:137603211)
698 _control = bluetooth_socket.BluetoothControlSocket()
699 return json.dumps(_control.read_supported_commands())
Scott James Remnant50915ad2014-12-01 13:51:39 -0800700
701
702 def read_index_list(self):
703 """Read the list of currently known controllers from the Kernel.
704
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000705 @return the information as a JSON-encoded array of controller indexes.
Scott James Remnant50915ad2014-12-01 13:51:39 -0800706
707 """
howardchung76423462019-07-12 16:21:31 +0800708 #TODO(howardchung): resolve 'cannot allocate memory' error when
709 # BluetoothControlSocket idle too long(about 3 secs)
710 # (b:137603211)
711 _control = bluetooth_socket.BluetoothControlSocket()
712 return json.dumps(_control.read_index_list())
Scott James Remnant50915ad2014-12-01 13:51:39 -0800713
714
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700715 def read_info(self):
716 """Read the adapter information from the Kernel.
717
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000718 @return the information as a JSON-encoded tuple of:
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700719 ( address, bluetooth_version, manufacturer_id,
720 supported_settings, current_settings, class_of_device,
721 name, short_name )
722
723 """
howardchung76423462019-07-12 16:21:31 +0800724 #TODO(howardchung): resolve 'cannot allocate memory' error when
725 # BluetoothControlSocket idle too long(about 3 secs)
726 # (b:137603211)
727 _control = bluetooth_socket.BluetoothControlSocket()
728 return json.dumps(_control.read_info(0))
Scott James Remnant1ca2e0e2013-07-31 16:49:07 -0700729
730
Scott James Remnantabea37c2014-12-01 14:22:23 -0800731 def add_device(self, address, address_type, action):
732 """Add a device to the Kernel action list.
733
734 @param address: Address of the device to add.
735 @param address_type: Type of device in @address.
736 @param action: Action to take.
737
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000738 @return on success, a JSON-encoded typle of:
Scott James Remnantabea37c2014-12-01 14:22:23 -0800739 ( address, address_type ), None on failure.
740
741 """
howardchung76423462019-07-12 16:21:31 +0800742 #TODO(howardchung): resolve 'cannot allocate memory' error when
743 # BluetoothControlSocket idle too long(about 3 secs)
744 # (b:137603211)
745 _control = bluetooth_socket.BluetoothControlSocket()
746 return json.dumps(_control.add_device(
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000747 0, address, address_type, action))
Scott James Remnantabea37c2014-12-01 14:22:23 -0800748
749
750 def remove_device(self, address, address_type):
751 """Remove a device from the Kernel action list.
752
753 @param address: Address of the device to remove.
754 @param address_type: Type of device in @address.
755
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000756 @return on success, a JSON-encoded typle of:
Scott James Remnantabea37c2014-12-01 14:22:23 -0800757 ( address, address_type ), None on failure.
758
759 """
howardchung76423462019-07-12 16:21:31 +0800760 #TODO(howardchung): resolve 'cannot allocate memory' error when
761 # BluetoothControlSocket idle too long(about 3 secs)
762 # (b:137603211)
763 _control = bluetooth_socket.BluetoothControlSocket()
764 return json.dumps(_control.remove_device(
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000765 0, address, address_type))
Scott James Remnantabea37c2014-12-01 14:22:23 -0800766
767
Scott James Remnantaec4edd2013-08-26 18:47:11 -0700768 @xmlrpc_server.dbus_safe(False)
Joseph Hwange3cb1b02018-10-26 17:50:05 +0800769 def _get_devices(self):
Scott James Remnantaec4edd2013-08-26 18:47:11 -0700770 """Read information about remote devices known to the adapter.
771
Joseph Hwange3cb1b02018-10-26 17:50:05 +0800772 @return the properties of each device in a list
Scott James Remnantaec4edd2013-08-26 18:47:11 -0700773
774 """
775 objects = self._bluez.GetManagedObjects(
Cheng-Yi Chiangcdea4042015-05-25 18:35:34 +0800776 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=True)
Scott James Remnantaec4edd2013-08-26 18:47:11 -0700777 devices = []
778 for path, ifaces in objects.iteritems():
779 if self.BLUEZ_DEVICE_IFACE in ifaces:
780 devices.append(objects[path][self.BLUEZ_DEVICE_IFACE])
Joseph Hwange3cb1b02018-10-26 17:50:05 +0800781 return devices
782
783
784 def _encode_base64_json(self, data):
785 """Base64 encode and json encode the data.
786 Required to handle non-ascii data
787
788 @param data: data to be base64 and JSON encoded
789
790 @return: base64 and JSON encoded data
791
792 """
793 logging.debug('_encode_base64_json raw data is %s', data)
794 b64_encoded = utils.base64_recursive_encode(data)
795 logging.debug('base64 encoded data is %s', b64_encoded)
796 json_encoded = json.dumps(b64_encoded)
797 logging.debug('JSON encoded data is %s', json_encoded)
798 return json_encoded
799
800
801 def get_devices(self):
802 """Read information about remote devices known to the adapter.
803
804 @return the properties of each device as a JSON-encoded array of
805 dictionaries on success, the value False otherwise.
806
807 """
808 devices = self._get_devices()
809 return self._encode_base64_json(devices)
Scott James Remnantaec4edd2013-08-26 18:47:11 -0700810
811
812 @xmlrpc_server.dbus_safe(False)
Joseph Hwangf064e0f2016-05-23 16:46:45 +0800813 def get_device_by_address(self, address):
814 """Read information about the remote device with the specified address.
815
816 @param address: Address of the device to get.
817
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000818 @return the properties of the device as a JSON-encoded dictionary
Joseph Hwangf064e0f2016-05-23 16:46:45 +0800819 on success, the value False otherwise.
820
821 """
822 objects = self._bluez.GetManagedObjects(
823 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=True)
824 devices = []
Joseph Hwange3cb1b02018-10-26 17:50:05 +0800825 devices = self._get_devices()
Joseph Hwangf064e0f2016-05-23 16:46:45 +0800826 for device in devices:
Joseph Hwange3cb1b02018-10-26 17:50:05 +0800827 if device.get('Address') == address:
828 return self._encode_base64_json(device)
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000829 return json.dumps(dict())
Joseph Hwangf064e0f2016-05-23 16:46:45 +0800830
831
832 @xmlrpc_server.dbus_safe(False)
Scott James Remnantaec4edd2013-08-26 18:47:11 -0700833 def start_discovery(self):
834 """Start discovery of remote devices.
835
836 Obtain the discovered device information using get_devices(), called
837 stop_discovery() when done.
838
839 @return True on success, False otherwise.
840
841 """
Cheng-Yi Chiang2fd063e2016-01-05 17:54:31 +0800842 if not self._adapter:
843 return False
Scott James Remnantaec4edd2013-08-26 18:47:11 -0700844 self._adapter.StartDiscovery(dbus_interface=self.BLUEZ_ADAPTER_IFACE)
845 return True
846
847
848 @xmlrpc_server.dbus_safe(False)
849 def stop_discovery(self):
850 """Stop discovery of remote devices.
851
852 @return True on success, False otherwise.
853
854 """
Cheng-Yi Chiang2fd063e2016-01-05 17:54:31 +0800855 if not self._adapter:
856 return False
Scott James Remnantaec4edd2013-08-26 18:47:11 -0700857 self._adapter.StopDiscovery(dbus_interface=self.BLUEZ_ADAPTER_IFACE)
858 return True
859
860
Scott James Remnantc7fd7a42014-12-01 16:43:38 -0800861 def get_dev_info(self):
862 """Read raw HCI device information.
863
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000864 @return JSON-encoded tuple of:
Scott James Remnantc7fd7a42014-12-01 16:43:38 -0800865 (index, name, address, flags, device_type, bus_type,
866 features, pkt_type, link_policy, link_mode,
867 acl_mtu, acl_pkts, sco_mtu, sco_pkts,
868 err_rx, err_tx, cmd_tx, evt_rx, acl_tx, acl_rx,
869 sco_tx, sco_rx, byte_rx, byte_tx) on success,
870 None on failure.
871
872 """
Shyh-In Hwang9f759f82018-11-19 09:40:19 +0000873 return json.dumps(self._raw.get_dev_info(0))
Scott James Remnantc7fd7a42014-12-01 16:43:38 -0800874
875
Artem Rakhovb144dce2014-02-20 21:02:09 -0800876 @xmlrpc_server.dbus_safe(False)
877 def register_profile(self, path, uuid, options):
878 """Register new profile (service).
879
880 @param path: Path to the profile object.
881 @param uuid: Service Class ID of the service as string.
882 @param options: Dictionary of options for the new service, compliant
883 with BlueZ D-Bus Profile API standard.
884
885 @return True on success, False otherwise.
886
887 """
888 profile_manager = dbus.Interface(
889 self._system_bus.get_object(
Shijin Abrahamaa52eda2020-01-04 15:45:03 -0800890 self.BLUEZ_SERVICE_NAME,
Artem Rakhovb144dce2014-02-20 21:02:09 -0800891 self.BLUEZ_PROFILE_MANAGER_PATH),
892 self.BLUEZ_PROFILE_MANAGER_IFACE)
howardchunge8c06dd2019-10-02 12:28:31 +0800893 dbus_object = self._system_bus.get_object(
Shijin Abrahamaa52eda2020-01-04 15:45:03 -0800894 self.BLUEZ_SERVICE_NAME, path)
howardchunge8c06dd2019-10-02 12:28:31 +0800895 profile_manager.RegisterProfile(dbus_object, uuid,
896 dbus.Dictionary(options, signature='sv'))
Artem Rakhovb144dce2014-02-20 21:02:09 -0800897 return True
898
899
Cheng-Yi Chiangdc6442b2015-07-08 16:07:02 +0800900 def has_device(self, address):
901 """Checks if the device with a given address exists.
902
903 @param address: Address of the device.
904
Joseph Hwang060a7112017-01-13 15:16:29 +0800905 @returns: True if there is an interface object with that address.
906 False if the device is not found.
907
908 @raises: Exception if a D-Bus error is encountered.
Cheng-Yi Chiangdc6442b2015-07-08 16:07:02 +0800909
910 """
Joseph Hwang060a7112017-01-13 15:16:29 +0800911 result = self._find_device(address)
912 logging.debug('has_device result: %s', str(result))
913
914 # The result being False indicates that there is a D-Bus error.
915 if result is False:
916 raise Exception('dbus.Interface error')
917
918 # Return True if the result is not None, e.g. a D-Bus interface object;
919 # False otherwise.
920 return bool(result)
Cheng-Yi Chiangdc6442b2015-07-08 16:07:02 +0800921
922
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800923 @xmlrpc_server.dbus_safe(False)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800924 def _find_device(self, address):
925 """Finds the device with a given address.
926
927 Find the device with a given address and returns the
928 device interface.
929
Cheng-Yi Chiangdc6442b2015-07-08 16:07:02 +0800930 @param address: Address of the device.
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800931
932 @returns: An 'org.bluez.Device1' interface to the device.
933 None if device can not be found.
Dane Pollockaee75642017-06-09 12:56:28 -0700934 """
935 path = self._get_device_path(address)
936 if path:
937 obj = self._system_bus.get_object(
Shijin Abrahamaa52eda2020-01-04 15:45:03 -0800938 self.BLUEZ_SERVICE_NAME, path)
Dane Pollockaee75642017-06-09 12:56:28 -0700939 return dbus.Interface(obj, self.BLUEZ_DEVICE_IFACE)
940 logging.info('Device not found')
941 return None
942
943
944 @xmlrpc_server.dbus_safe(False)
945 def _get_device_path(self, address):
946 """Gets the path for a device with a given address.
947
948 Find the device with a given address and returns the
949 the path for the device.
950
951 @param address: Address of the device.
952
953 @returns: The path to the address of the device, or None if device is
954 not found in the object tree.
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800955
956 """
Daniel Winkler0cc43eb2020-01-17 13:03:46 -0800957
958 # Create device path, i.e. '/org/bluez/hci0/dev_AA_BB_CC_DD_EE_FF' based
959 # on path assignment scheme used in bluez
960 address_up = address.replace(':', '_')
961 device_path = '{}/dev_{}'.format(self._adapter.object_path, address_up)
962
963 # Verify the Address property agrees to confirm we have the device
964 try:
965 device = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME,
966 device_path)
967 found_addr = device.Get(self.BLUEZ_DEVICE_IFACE, 'Address',
968 dbus_interface=dbus.PROPERTIES_IFACE)
969
970 if found_addr == address:
971 logging.info('Device found at {}'.format(device_path))
972 return device_path
973
974 except dbus.exceptions.DBusException, e:
975 log_msg = 'Couldn\'t reach device: {}'.format(str(e))
976 logging.debug(log_msg)
977
978 logging.debug('No device found at {}'.format(device_path))
979 return None
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800980
981
Joseph Hwang97ff05d2016-05-10 16:07:22 +0800982 @xmlrpc_server.dbus_safe(False)
Joseph Hwangf064e0f2016-05-23 16:46:45 +0800983 def _setup_pairing_agent(self, pin):
Dane Pollockaee75642017-06-09 12:56:28 -0700984 """Initializes and resiters a PairingAgent to handle authentication.
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800985
986 @param pin: The pin code this agent will answer.
987
988 """
Joseph Hwangf064e0f2016-05-23 16:46:45 +0800989 if self._pairing_agent:
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800990 logging.info('Removing the old agent before initializing a new one')
Joseph Hwangf064e0f2016-05-23 16:46:45 +0800991 self._pairing_agent.remove_from_connection()
992 self._pairing_agent = None
993 self._pairing_agent= PairingAgent(pin, self._system_bus,
994 self.AGENT_PATH)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800995 agent_manager = dbus.Interface(
Shijin Abrahamaa52eda2020-01-04 15:45:03 -0800996 self._system_bus.get_object(self.BLUEZ_SERVICE_NAME,
Katherine Threlkeldc0269872015-08-19 16:15:29 -0700997 self.BLUEZ_AGENT_MANAGER_PATH),
998 self.BLUEZ_AGENT_MANAGER_IFACE)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +0800999 try:
howardchung39881072019-10-18 13:52:05 +08001000 agent_obj = self._system_bus.get_object(
Shijin Abrahamaa52eda2020-01-04 15:45:03 -08001001 self.BLUEZ_SERVICE_NAME,
howardchung39881072019-10-18 13:52:05 +08001002 self.AGENT_PATH)
1003 agent_manager.RegisterAgent(agent_obj,
1004 dbus.String(self._capability))
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001005 except dbus.exceptions.DBusException, e:
1006 if e.get_dbus_name() == self.BLUEZ_ERROR_ALREADY_EXISTS:
Katherine Threlkeldc0269872015-08-19 16:15:29 -07001007 logging.info('Unregistering old agent and registering the new')
howardchung39881072019-10-18 13:52:05 +08001008 agent_manager.UnregisterAgent(agent_obj)
1009 agent_manager.RegisterAgent(agent_obj,
1010 dbus.String(self._capability))
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001011 else:
Katherine Threlkeldc0269872015-08-19 16:15:29 -07001012 logging.error('Error setting up pin agent: %s', e)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001013 raise
Joseph Hwangf064e0f2016-05-23 16:46:45 +08001014 logging.info('Agent registered: %s', self.AGENT_PATH)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001015
1016
Joseph Hwang97ff05d2016-05-10 16:07:22 +08001017 @xmlrpc_server.dbus_safe(False)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001018 def _is_paired(self, device):
1019 """Checks if a device is paired.
1020
1021 @param device: An 'org.bluez.Device1' interface to the device.
1022
1023 @returns: True if device is paired. False otherwise.
1024
1025 """
1026 props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
1027 paired = props.Get(self.BLUEZ_DEVICE_IFACE, 'Paired')
1028 return bool(paired)
1029
1030
Joseph Hwang97ff05d2016-05-10 16:07:22 +08001031 @xmlrpc_server.dbus_safe(False)
Joseph Hwangf064e0f2016-05-23 16:46:45 +08001032 def device_is_paired(self, address):
1033 """Checks if a device is paired.
1034
1035 @param address: address of the device.
1036
1037 @returns: True if device is paired. False otherwise.
1038
1039 """
1040 device = self._find_device(address)
1041 if not device:
1042 logging.error('Device not found')
1043 return False
1044 return self._is_paired(device)
1045
1046
1047 @xmlrpc_server.dbus_safe(False)
Dane Pollockaee75642017-06-09 12:56:28 -07001048 def _is_connected(self, device):
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001049 """Checks if a device is connected.
1050
1051 @param device: An 'org.bluez.Device1' interface to the device.
1052
1053 @returns: True if device is connected. False otherwise.
1054
1055 """
1056 props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
1057 connected = props.Get(self.BLUEZ_DEVICE_IFACE, 'Connected')
Katherine Threlkeldc0269872015-08-19 16:15:29 -07001058 logging.info('Got connected = %r', connected)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001059 return bool(connected)
1060
1061
Dane Pollockaee75642017-06-09 12:56:28 -07001062
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001063 @xmlrpc_server.dbus_safe(False)
Joseph Hwangf064e0f2016-05-23 16:46:45 +08001064 def _set_trusted_by_device(self, device, trusted=True):
1065 """Set the device trusted by device object.
1066
1067 @param device: the device object to set trusted.
1068 @param trusted: True or False indicating whether to set trusted or not.
1069
1070 @returns: True if successful. False otherwise.
1071
1072 """
1073 try:
1074 properties = dbus.Interface(device, self.DBUS_PROP_IFACE)
howardchung39881072019-10-18 13:52:05 +08001075 properties.Set(self.BLUEZ_DEVICE_IFACE, 'Trusted',
1076 dbus.Boolean(trusted, variant_level=1))
Joseph Hwangf064e0f2016-05-23 16:46:45 +08001077 return True
1078 except Exception as e:
1079 logging.error('_set_trusted_by_device: %s', e)
1080 except:
1081 logging.error('_set_trusted_by_device: unexpected error')
1082 return False
1083
1084
1085 @xmlrpc_server.dbus_safe(False)
1086 def _set_trusted_by_path(self, device_path, trusted=True):
1087 """Set the device trusted by the device path.
1088
1089 @param device_path: the object path of the device.
1090 @param trusted: True or False indicating whether to set trusted or not.
1091
1092 @returns: True if successful. False otherwise.
1093
1094 """
1095 try:
Shijin Abrahamaa52eda2020-01-04 15:45:03 -08001096 device = self._system_bus.get_object(self.BLUEZ_SERVICE_NAME,
Joseph Hwangf064e0f2016-05-23 16:46:45 +08001097 device_path)
1098 return self._set_trusted_by_device(device, trusted)
1099 except Exception as e:
1100 logging.error('_set_trusted_by_path: %s', e)
1101 except:
1102 logging.error('_set_trusted_by_path: unexpected error')
1103 return False
1104
1105
1106 @xmlrpc_server.dbus_safe(False)
1107 def set_trusted(self, address, trusted=True):
1108 """Set the device trusted by address.
1109
1110 @param address: The bluetooth address of the device.
1111 @param trusted: True or False indicating whether to set trusted or not.
1112
1113 @returns: True if successful. False otherwise.
1114
1115 """
1116 try:
1117 device = self._find_device(address)
1118 return self._set_trusted_by_device(device, trusted)
1119 except Exception as e:
1120 logging.error('set_trusted: %s', e)
1121 except:
1122 logging.error('set_trusted: unexpected error')
1123 return False
1124
1125
1126 @xmlrpc_server.dbus_safe(False)
1127 def pair_legacy_device(self, address, pin, trusted, timeout=60):
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001128 """Pairs a device with a given pin code.
1129
1130 Registers a agent who handles pin code request and
1131 pairs a device with known pin code.
1132
Joseph Hwangf064e0f2016-05-23 16:46:45 +08001133 Note that the adapter does not automatically connnect to the device
1134 when pairing is done. The connect_device() method has to be invoked
1135 explicitly to connect to the device. This provides finer control
1136 for testing purpose.
1137
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001138 @param address: Address of the device to pair.
1139 @param pin: The pin code of the device to pair.
Joseph Hwangf064e0f2016-05-23 16:46:45 +08001140 @param trusted: indicating whether to set the device trusted.
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001141 @param timeout: The timeout in seconds for pairing.
1142
1143 @returns: True on success. False otherwise.
1144
1145 """
1146 device = self._find_device(address)
1147 if not device:
1148 logging.error('Device not found')
1149 return False
1150 if self._is_paired(device):
1151 logging.info('Device is already paired')
1152 return True
1153
Joseph Hwangf064e0f2016-05-23 16:46:45 +08001154 device_path = device.object_path
Dane Pollockaee75642017-06-09 12:56:28 -07001155 logging.info('Device %s is found.', device.object_path)
Joseph Hwangf064e0f2016-05-23 16:46:45 +08001156
1157 self._setup_pairing_agent(pin)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001158 mainloop = gobject.MainLoop()
1159
1160
1161 def pair_reply():
1162 """Handler when pairing succeeded."""
Joseph Hwangf064e0f2016-05-23 16:46:45 +08001163 logging.info('Device paired: %s', device_path)
1164 if trusted:
1165 self._set_trusted_by_path(device_path, trusted=True)
1166 logging.info('Device trusted: %s', device_path)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001167 mainloop.quit()
1168
1169
1170 def pair_error(error):
Cheng-Yi Chiangc5b6d2c2015-05-25 23:52:42 +08001171 """Handler when pairing failed.
1172
1173 @param error: one of errors defined in org.bluez.Error representing
1174 the error in pairing.
1175
1176 """
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001177 try:
1178 error_name = error.get_dbus_name()
1179 if error_name == 'org.freedesktop.DBus.Error.NoReply':
Joseph Hwangf064e0f2016-05-23 16:46:45 +08001180 logging.error('Timed out after %d ms. Cancelling pairing.',
1181 timeout)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001182 device.CancelPairing()
1183 else:
1184 logging.error('Pairing device failed: %s', error)
1185 finally:
1186 mainloop.quit()
1187
Shijin Abraham4f907d72019-10-22 12:24:27 -07001188 try:
1189 device.Pair(reply_handler=pair_reply, error_handler=pair_error,
1190 timeout=timeout * 1000)
1191 except Exception as e:
1192 logging.error('Exception %s in pair_legacy_device', e)
1193 return False
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001194 mainloop.run()
1195 return self._is_paired(device)
1196
1197
1198 @xmlrpc_server.dbus_safe(False)
1199 def remove_device_object(self, address):
1200 """Removes a device object and the pairing information.
1201
1202 Calls RemoveDevice method to remove remote device
1203 object and the pairing information.
1204
1205 @param address: Address of the device to unpair.
1206
1207 @returns: True on success. False otherwise.
1208
1209 """
1210 device = self._find_device(address)
1211 if not device:
1212 logging.error('Device not found')
1213 return False
1214 self._adapter.RemoveDevice(
1215 device.object_path, dbus_interface=self.BLUEZ_ADAPTER_IFACE)
1216 return True
1217
1218
1219 @xmlrpc_server.dbus_safe(False)
1220 def connect_device(self, address):
1221 """Connects a device.
1222
1223 Connects a device if it is not connected.
1224
1225 @param address: Address of the device to connect.
1226
1227 @returns: True on success. False otherwise.
1228
1229 """
1230 device = self._find_device(address)
1231 if not device:
1232 logging.error('Device not found')
1233 return False
1234 if self._is_connected(device):
1235 logging.info('Device is already connected')
1236 return True
1237 device.Connect()
Cheng-Yi Chiangbd1b6262015-09-14 18:47:39 +08001238 return self._is_connected(device)
1239
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001240
Cheng-Yi Chianga5328522015-10-21 14:46:41 +08001241 @xmlrpc_server.dbus_safe(False)
1242 def device_is_connected(self, address):
1243 """Checks if a device is connected.
1244
1245 @param address: Address of the device to connect.
1246
1247 @returns: True if device is connected. False otherwise.
1248
1249 """
1250 device = self._find_device(address)
1251 if not device:
1252 logging.error('Device not found')
1253 return False
1254 return self._is_connected(device)
1255
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001256
1257 @xmlrpc_server.dbus_safe(False)
1258 def disconnect_device(self, address):
1259 """Disconnects a device.
1260
1261 Disconnects a device if it is connected.
1262
1263 @param address: Address of the device to disconnect.
1264
1265 @returns: True on success. False otherwise.
1266
1267 """
1268 device = self._find_device(address)
1269 if not device:
1270 logging.error('Device not found')
1271 return False
1272 if not self._is_connected(device):
1273 logging.info('Device is not connected')
1274 return True
1275 device.Disconnect()
Cheng-Yi Chiangbd1b6262015-09-14 18:47:39 +08001276 return not self._is_connected(device)
Cheng-Yi Chiang75e70942015-04-19 04:00:48 +08001277
1278
Dane Pollockaee75642017-06-09 12:56:28 -07001279 @xmlrpc_server.dbus_safe(False)
1280 def _device_services_resolved(self, device):
1281 """Checks if services are resolved.
1282
1283 @param device: An 'org.bluez.Device1' interface to the device.
1284
1285 @returns: True if device is connected. False otherwise.
1286
1287 """
1288 logging.info('device for services resolved: %s', device)
1289 props = dbus.Interface(device, dbus.PROPERTIES_IFACE)
1290 resolved = props.Get(self.BLUEZ_DEVICE_IFACE, 'ServicesResolved')
1291 logging.info('Services resolved = %r', resolved)
1292 return bool(resolved)
1293
1294
1295 @xmlrpc_server.dbus_safe(False)
1296 def device_services_resolved(self, address):
1297 """Checks if service discovery is complete on a device.
1298
1299 Checks whether service discovery has been completed..
1300
1301 @param address: Address of the remote device.
1302
1303 @returns: True on success. False otherwise.
1304
1305 """
1306 device = self._find_device(address)
1307 if not device:
1308 logging.error('Device not found')
1309 return False
1310
1311 if not self._is_connected(device):
1312 logging.info('Device is not connected')
1313 return False
1314
1315 return self._device_services_resolved(device)
1316
1317
Joseph Hwangefaf0352016-09-29 14:53:33 +08001318 def btmon_start(self):
1319 """Start btmon monitoring."""
1320 self.btmon.start()
1321
1322
1323 def btmon_stop(self):
1324 """Stop btmon monitoring."""
1325 self.btmon.stop()
1326
1327
Joseph Hwang5c692b82016-11-21 15:56:57 +08001328 def btmon_get(self, search_str, start_str):
Joseph Hwangefaf0352016-09-29 14:53:33 +08001329 """Get btmon output contents.
1330
Joseph Hwang5c692b82016-11-21 15:56:57 +08001331 @param search_str: only lines with search_str would be kept.
1332 @param start_str: all lines before the occurrence of start_str would be
1333 filtered.
1334
Joseph Hwangefaf0352016-09-29 14:53:33 +08001335 @returns: the recorded btmon output.
1336
1337 """
Joseph Hwang5c692b82016-11-21 15:56:57 +08001338 return self.btmon.get_contents(search_str=search_str,
1339 start_str=start_str)
Joseph Hwangefaf0352016-09-29 14:53:33 +08001340
1341
1342 def btmon_find(self, pattern_str):
1343 """Find if a pattern string exists in btmon output.
1344
1345 @param pattern_str: the pattern string to find.
1346
1347 @returns: True on success. False otherwise.
1348
1349 """
1350 return self.btmon.find(pattern_str)
1351
1352
1353 @xmlrpc_server.dbus_safe(False)
1354 def advertising_async_method(self, dbus_method,
1355 reply_handler, error_handler, *args):
1356 """Run an async dbus method.
1357
1358 @param dbus_method: the dbus async method to invoke.
1359 @param reply_handler: the reply handler for the dbus method.
1360 @param error_handler: the error handler for the dbus method.
1361 @param *args: additional arguments for the dbus method.
1362
Joseph Hwang7668c9c2016-11-21 16:04:38 +08001363 @returns: an empty string '' on success;
1364 None if there is no _advertising interface manager; and
Shijin Abraham4f907d72019-10-22 12:24:27 -07001365 an error string if the dbus method fails or exception occurs
Joseph Hwangefaf0352016-09-29 14:53:33 +08001366
1367 """
1368
1369 def successful_cb():
1370 """Called when the dbus_method completed successfully."""
1371 reply_handler()
Joseph Hwang7668c9c2016-11-21 16:04:38 +08001372 self.advertising_cb_msg = ''
1373 self._adv_mainloop.quit()
Joseph Hwangefaf0352016-09-29 14:53:33 +08001374
1375
1376 def error_cb(error):
1377 """Called when the dbus_method failed."""
1378 error_handler(error)
Joseph Hwang7668c9c2016-11-21 16:04:38 +08001379 self.advertising_cb_msg = str(error)
1380 self._adv_mainloop.quit()
Joseph Hwangefaf0352016-09-29 14:53:33 +08001381
1382
1383 if not self._advertising:
Joseph Hwang7668c9c2016-11-21 16:04:38 +08001384 return None
Joseph Hwangefaf0352016-09-29 14:53:33 +08001385
Joseph Hwang7668c9c2016-11-21 16:04:38 +08001386 # Call dbus_method with handlers.
Shijin Abraham4f907d72019-10-22 12:24:27 -07001387 try:
1388 dbus_method(*args, reply_handler=successful_cb,
1389 error_handler=error_cb)
1390 except Exception as e:
1391 logging.error('Exception %s in advertising_async_method ', e)
1392 return str(e)
Joseph Hwangefaf0352016-09-29 14:53:33 +08001393
Joseph Hwang7668c9c2016-11-21 16:04:38 +08001394 self._adv_mainloop.run()
Joseph Hwangefaf0352016-09-29 14:53:33 +08001395
Joseph Hwang7668c9c2016-11-21 16:04:38 +08001396 return self.advertising_cb_msg
Joseph Hwangefaf0352016-09-29 14:53:33 +08001397
1398
1399 def register_advertisement(self, advertisement_data):
1400 """Register an advertisement.
1401
1402 Note that rpc supports only conformable types. Hence, a
1403 dict about the advertisement is passed as a parameter such
1404 that the advertisement object could be constructed on the host.
1405
1406 @param advertisement_data: a dict of the advertisement to register.
1407
1408 @returns: True on success. False otherwise.
1409
1410 """
1411 adv = advertisement.Advertisement(self._system_bus, advertisement_data)
Joseph Hwang8de9dae2016-10-19 18:16:31 +08001412 self.advertisements.append(adv)
Joseph Hwangefaf0352016-09-29 14:53:33 +08001413 return self.advertising_async_method(
1414 self._advertising.RegisterAdvertisement,
1415 # reply handler
1416 lambda: logging.info('register_advertisement: succeeded.'),
1417 # error handler
1418 lambda error: logging.error(
1419 'register_advertisement: failed: %s', str(error)),
1420 # other arguments
howardchung7388c9b2019-10-17 11:39:57 +08001421 adv.get_path(), dbus.Dictionary({}, signature='sv'))
Joseph Hwangefaf0352016-09-29 14:53:33 +08001422
1423
Joseph Hwang4d9495a2016-11-21 16:05:46 +08001424 def unregister_advertisement(self, advertisement_data):
1425 """Unregister an advertisement.
1426
1427 Note that to unregister an advertisement, it is required to use
1428 the same self._advertising interface manager. This is because
1429 bluez only allows the same sender to invoke UnregisterAdvertisement
1430 method. Hence, watch out that the bluetoothd is not restarted or
1431 self.start_bluetoothd() is not executed between the time span that
1432 an advertisement is registered and unregistered.
1433
1434 @param advertisement_data: a dict of the advertisements to unregister.
1435
1436 @returns: True on success. False otherwise.
1437
1438 """
1439 path = advertisement_data.get('Path')
1440 for index, adv in enumerate(self.advertisements):
1441 if adv.get_path() == path:
1442 break
1443 else:
1444 logging.error('Fail to find the advertisement under the path: %s',
1445 path)
1446 return False
1447
1448 result = self.advertising_async_method(
1449 self._advertising.UnregisterAdvertisement,
1450 # reply handler
1451 lambda: logging.info('unregister_advertisement: succeeded.'),
1452 # error handler
1453 lambda error: logging.error(
1454 'unregister_advertisement: failed: %s', str(error)),
1455 # other arguments
1456 adv.get_path())
1457
1458 # Call remove_from_connection() so that the same path could be reused.
1459 adv.remove_from_connection()
1460 del self.advertisements[index]
1461
1462 return result
1463
1464
Joseph Hwangefaf0352016-09-29 14:53:33 +08001465 def set_advertising_intervals(self, min_adv_interval_ms,
1466 max_adv_interval_ms):
1467 """Set advertising intervals.
1468
1469 @param min_adv_interval_ms: the min advertising interval in ms.
1470 @param max_adv_interval_ms: the max advertising interval in ms.
1471
1472 @returns: True on success. False otherwise.
1473
1474 """
1475 return self.advertising_async_method(
1476 self._advertising.SetAdvertisingIntervals,
1477 # reply handler
1478 lambda: logging.info('set_advertising_intervals: succeeded.'),
1479 # error handler
1480 lambda error: logging.error(
1481 'set_advertising_intervals: failed: %s', str(error)),
1482 # other arguments
howardchung7388c9b2019-10-17 11:39:57 +08001483 dbus.UInt16(min_adv_interval_ms),
1484 dbus.UInt16(max_adv_interval_ms))
Joseph Hwangefaf0352016-09-29 14:53:33 +08001485
1486
1487 def reset_advertising(self):
1488 """Reset advertising.
1489
1490 This includes un-registering all advertisements, reset advertising
1491 intervals, and disable advertising.
1492
1493 @returns: True on success. False otherwise.
1494
1495 """
Joseph Hwang8de9dae2016-10-19 18:16:31 +08001496 # It is required to execute remove_from_connection() to unregister the
1497 # object-path handler of each advertisement. In this way, we could
1498 # register an advertisement with the same path repeatedly.
1499 for adv in self.advertisements:
1500 adv.remove_from_connection()
1501 del self.advertisements[:]
1502
Joseph Hwangefaf0352016-09-29 14:53:33 +08001503 return self.advertising_async_method(
1504 self._advertising.ResetAdvertising,
1505 # reply handler
1506 lambda: logging.info('reset_advertising: succeeded.'),
1507 # error handler
1508 lambda error: logging.error(
1509 'reset_advertising: failed: %s', str(error)))
1510
1511
howardchungb3752742019-09-18 09:57:14 +08001512 @xmlrpc_server.dbus_safe(None)
1513 def get_gatt_attributes_map(self, address):
1514 """Return a JSON formated string of the GATT attributes of a device,
1515 keyed by UUID
1516 @param address: a string of the MAC address of the device
1517
1518 @return: JSON formated string, stored the nested structure of the
1519 attributes. Each attribute has 'path' and
1520 ['characteristics' | 'descriptors'], which store their object path and
1521 children respectively.
1522
1523 """
1524 attribute_map = dict()
1525
1526 device_object_path = self._get_device_path(address)
1527 service_map = self._get_service_map(device_object_path)
1528
1529 servs = dict()
1530 attribute_map['services'] = servs
1531
1532 for uuid, path in service_map.items():
1533
1534 servs[uuid] = dict()
1535 serv = servs[uuid]
1536
1537 serv['path'] = path
1538 serv['characteristics'] = dict()
1539 chrcs = serv['characteristics']
1540
1541 chrcs_map = self._get_characteristic_map(path)
1542 for uuid, path in chrcs_map.items():
1543 chrcs[uuid] = dict()
1544 chrc = chrcs[uuid]
1545
1546 chrc['path'] = path
1547 chrc['descriptors'] = dict()
1548 descs = chrc['descriptors']
1549
1550 descs_map = self._get_descriptor_map(path)
1551
1552 for uuid, path in descs_map.items():
1553 descs[uuid] = dict()
1554 desc = descs[uuid]
1555
1556 desc['path'] = path
1557
1558 return json.dumps(attribute_map)
1559
1560
1561 def _get_gatt_interface(self, uuid, object_path, interface):
1562 """Get dbus interface by uuid
1563 @param uuid: a string of uuid
1564 @param object_path: a string of the object path of the service
1565
1566 @return: a dbus interface
1567 """
1568
1569 return dbus.Interface(
1570 self._system_bus.get_object(
Shijin Abrahamaa52eda2020-01-04 15:45:03 -08001571 self.BLUEZ_SERVICE_NAME, object_path), interface)
howardchungb3752742019-09-18 09:57:14 +08001572
1573
1574 def get_gatt_service_property(self, object_path, property_name):
1575 """Get property from a service attribute
1576 @param object_path: a string of the object path of the service
1577 @param property_name: a string of a property, ex: 'Value', 'UUID'
1578
1579 @return: the property if success,
1580 none otherwise
1581
1582 """
1583 return self.get_gatt_attribute_property(
1584 object_path, self.BLUEZ_GATT_SERV_IFACE, property_name)
1585
1586
1587 def get_gatt_characteristic_property(self, object_path, property_name):
1588 """Get property from a characteristic attribute
1589 @param object_path: a string of the object path of the characteristic
1590 @param property_name: a string of a property, ex: 'Value', 'UUID'
1591
1592 @return: the property if success,
1593 none otherwise
1594
1595 """
1596 return self.get_gatt_attribute_property(
1597 object_path, self.BLUEZ_GATT_CHAR_IFACE, property_name)
1598
1599
1600 def get_gatt_descriptor_property(self, object_path, property_name):
1601 """Get property from descriptor attribute
1602 @param object_path: a string of the object path of the descriptor
1603 @param property_name: a string of a property, ex: 'Value', 'UUID'
1604
1605 @return: the property if success,
1606 none otherwise
1607
1608 """
1609 return self.get_gatt_attribute_property(
1610 object_path, self.BLUEZ_GATT_DESC_IFACE, property_name)
1611
1612
1613 @xmlrpc_server.dbus_safe(None)
1614 def get_gatt_attribute_property(self, object_path, interface,
1615 property_name):
1616 """Get property from attribute
1617 @param object_path: a string of the bject path
1618 @param property_name: a string of a property, ex: 'Value', 'UUID'
1619
1620 @return: the property if success,
1621 none otherwise
1622
1623 """
1624 gatt_object = self._system_bus.get_object(
Shijin Abrahamaa52eda2020-01-04 15:45:03 -08001625 self.BLUEZ_SERVICE_NAME, object_path)
howardchungb3752742019-09-18 09:57:14 +08001626 prop = self._get_dbus_object_property(gatt_object, interface,
1627 property_name)
1628 logging.info(prop)
1629 if isinstance(prop, dbus.ByteArray):
1630 return _dbus_byte_array_to_b64_string(prop)
1631 if isinstance(prop, dbus.Boolean):
1632 return bool(prop)
1633 if isinstance(prop, dbus.String):
1634 return str(prop)
1635 if isinstance(prop, dbus.ObjectPath):
1636 return str(prop)
1637 if isinstance(prop, dbus.Array):
1638 return list(map(str, prop))
1639 return prop
1640
1641
1642 @xmlrpc_server.dbus_safe(None)
1643 def gatt_characteristic_read_value(self, uuid, object_path):
1644 """Perform method ReadValue on a characteristic attribute
1645 @param uuid: a string of uuid
1646 @param object_path: a string of the object path of the characteristic
1647
1648 @return: base64 string of dbus bytearray
1649 """
1650
1651 dbus_interface = self._get_gatt_interface(uuid, object_path,
1652 self.BLUEZ_GATT_CHAR_IFACE)
1653 value = dbus_interface.ReadValue(dbus.Dictionary({}, signature='sv'))
1654 return _dbus_byte_array_to_b64_string(value)
1655
1656
1657 @xmlrpc_server.dbus_safe(None)
1658 def gatt_descriptor_read_value(self, uuid, object_path):
1659 """Perform method ReadValue on a descriptor attribute
1660 @param uuid: a string of uuid
1661 @param object_path: a string of the object path of the descriptor
1662
1663 @return: base64 string of dbus bytearray
1664 """
1665
1666 dbus_interface = self._get_gatt_interface(uuid, object_path,
1667 self.BLUEZ_GATT_DESC_IFACE)
1668 value = dbus_interface.ReadValue(dbus.Dictionary({}, signature='sv'))
1669 return _dbus_byte_array_to_b64_string(value)
1670
1671
1672 @xmlrpc_server.dbus_safe(False)
1673 def _get_attribute_map(self, object_path, dbus_interface):
1674 """Gets a map of object paths under an object path.
1675
1676 Walks the object tree, and returns a map of UUIDs to object paths for
1677 all resolved gatt object.
1678
1679 @param object_path: The object path of the attribute to retrieve
1680 gatt UUIDs and paths from.
1681
1682 @returns: A dictionary of object paths, keyed by UUID.
1683
1684 """
1685 attr_map = {}
1686
1687 if object_path:
1688 objects = self._bluez.GetManagedObjects(
1689 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=False)
1690
1691 for path, ifaces in objects.iteritems():
1692 if (dbus_interface in ifaces and
1693 path.startswith(object_path)):
1694 uuid = ifaces[dbus_interface]['UUID'].lower()
1695 attr_map[uuid] = path
1696
1697 else:
1698 logging.warning('object_path %s is not valid', object_path)
1699
1700 return attr_map
1701
1702
1703 def _get_service_map(self, device_path):
1704 """Gets a map of service paths for a device."""
1705 return self._get_attribute_map(device_path, self.BLUEZ_GATT_SERV_IFACE)
1706
1707
1708 def _get_characteristic_map(self, serv_path):
1709 """Gets a map of characteristic paths for a service."""
1710 return self._get_attribute_map(serv_path, self.BLUEZ_GATT_CHAR_IFACE)
1711
1712
1713 def _get_descriptor_map(self, chrc_path):
1714 """Gets a map of descriptor paths for a characteristic."""
1715 return self._get_attribute_map(chrc_path, self.BLUEZ_GATT_DESC_IFACE)
1716
1717
1718 @xmlrpc_server.dbus_safe(None)
1719 def _get_dbus_object_property(self, dbus_object, dbus_interface,
1720 dbus_property):
1721 """Get the property in an object.
1722
1723 @param dbus_object: a dbus object
1724 @param dbus_property: a dbus property of the dbus object, as a string
1725
1726 @return: dbus type object if it success, e.g. dbus.Boolean, dbus.String,
1727 none otherwise
1728
1729 """
1730 return dbus_object.Get(dbus_interface,
1731 dbus_property,
1732 dbus_interface=dbus.PROPERTIES_IFACE)
1733
1734
Dane Pollockaee75642017-06-09 12:56:28 -07001735 @xmlrpc_server.dbus_safe(False)
1736 def get_characteristic_map(self, address):
1737 """Gets a map of characteristic paths for a device.
1738
1739 Walks the object tree, and returns a map of uuids to object paths for
1740 all resolved gatt characteristics.
1741
1742 @param address: The MAC address of the device to retrieve
1743 gatt characteristic uuids and paths from.
1744
1745 @returns: A dictionary of characteristic paths, keyed by uuid.
1746
1747 """
1748 device_path = self._get_device_path(address)
1749 char_map = {}
1750
1751 if device_path:
1752 objects = self._bluez.GetManagedObjects(
1753 dbus_interface=self.BLUEZ_MANAGER_IFACE, byte_arrays=False)
1754
1755 for path, ifaces in objects.iteritems():
howardchung96173862019-11-26 17:20:04 +08001756 if (self.BLUEZ_GATT_CHAR_IFACE in ifaces and
Dane Pollockaee75642017-06-09 12:56:28 -07001757 path.startswith(device_path)):
howardchung96173862019-11-26 17:20:04 +08001758 uuid = ifaces[self.BLUEZ_GATT_CHAR_IFACE]['UUID'].lower()
Dane Pollockaee75642017-06-09 12:56:28 -07001759 char_map[uuid] = path
1760 else:
1761 logging.warning('Device %s not in object tree.', address)
1762
1763 return char_map
1764
1765
howardchungb3752742019-09-18 09:57:14 +08001766 @xmlrpc_server.dbus_safe(None)
Dane Pollockaee75642017-06-09 12:56:28 -07001767 def _get_char_object(self, uuid, address):
1768 """Gets a characteristic object.
1769
howardchungb3752742019-09-18 09:57:14 +08001770 Gets a characteristic object for a given UUID and address.
Dane Pollockaee75642017-06-09 12:56:28 -07001771
howardchungb3752742019-09-18 09:57:14 +08001772 @param uuid: The UUID of the characteristic, as a string.
Dane Pollockaee75642017-06-09 12:56:28 -07001773 @param address: The MAC address of the remote device.
1774
1775 @returns: A dbus interface for the characteristic if the uuid/address
1776 is in the object tree.
1777 None if the address/uuid is not found in the object tree.
1778
1779 """
1780 path = self.get_characteristic_map(address).get(uuid)
1781 if not path:
1782 return None
1783 return dbus.Interface(
Shijin Abrahamaa52eda2020-01-04 15:45:03 -08001784 self._system_bus.get_object(self.BLUEZ_SERVICE_NAME, path),
howardchungb3752742019-09-18 09:57:14 +08001785 self.BLUEZ_GATT_CHAR_IFACE)
Dane Pollockaee75642017-06-09 12:56:28 -07001786
1787
1788 @xmlrpc_server.dbus_safe(None)
1789 def read_characteristic(self, uuid, address):
1790 """Reads the value of a gatt characteristic.
1791
1792 Reads the current value of a gatt characteristic. Base64 endcoding is
1793 used for compatibility with the XML RPC interface.
1794
1795 @param uuid: The uuid of the characteristic to read, as a string.
1796 @param address: The MAC address of the remote device.
1797
1798 @returns: A b64 encoded version of a byte array containing the value
1799 if the uuid/address is in the object tree.
1800 None if the uuid/address was not found in the object tree, or
1801 if a DBus exception was raised by the read operation.
1802
1803 """
1804 char_obj = self._get_char_object(uuid, address)
1805 if char_obj is None:
1806 return None
howardchungb3752742019-09-18 09:57:14 +08001807 value = char_obj.ReadValue(dbus.Dictionary({}, signature='sv'))
Dane Pollockaee75642017-06-09 12:56:28 -07001808 return _dbus_byte_array_to_b64_string(value)
1809
1810
1811 @xmlrpc_server.dbus_safe(None)
1812 def write_characteristic(self, uuid, address, value):
1813 """Performs a write operation on a gatt characteristic.
1814
1815 Writes to a GATT characteristic on a remote device. Base64 endcoding is
1816 used for compatibility with the XML RPC interface.
1817
1818 @param uuid: The uuid of the characteristic to write to, as a string.
1819 @param address: The MAC address of the remote device, as a string.
1820 @param value: A byte array containing the data to write.
1821
1822 @returns: True if the write operation does not raise an exception.
1823 None if the uuid/address was not found in the object tree, or
1824 if a DBus exception was raised by the write operation.
1825
1826 """
1827 char_obj = self._get_char_object(uuid, address)
1828 if char_obj is None:
1829 return None
1830 dbus_value = _b64_string_to_dbus_byte_array(value)
howardchungb3752742019-09-18 09:57:14 +08001831 char_obj.WriteValue(dbus_value, dbus.Dictionary({}, signature='sv'))
Dane Pollockaee75642017-06-09 12:56:28 -07001832 return True
1833
1834
1835 @xmlrpc_server.dbus_safe(False)
1836 def is_characteristic_path_resolved(self, uuid, address):
1837 """Checks whether a characteristic is in the object tree.
1838
1839 Checks whether a characteristic is curently found in the object tree.
1840
1841 @param uuid: The uuid of the characteristic to search for.
1842 @param address: The MAC address of the device on which to search for
1843 the characteristic.
1844
1845 @returns: True if the characteristic is found.
1846 False if the characteristic path is not found.
1847
1848 """
1849 return bool(self.get_characteristic_map(address).get(uuid))
1850
1851
Archie Pusakaa31b15b2019-11-06 18:13:05 +08001852 @xmlrpc_server.dbus_safe(False)
1853 def get_connection_info(self, address):
1854 """Get device connection info.
1855
1856 @param address: The MAC address of the device.
1857
1858 @returns: On success, a JSON-encoded tuple of:
1859 ( RSSI, transmit_power, max_transmit_power )
1860 None otherwise.
1861
1862 """
1863 path = self._get_device_path(address)
1864 if path is None:
1865 return None
1866
1867 try:
1868 plugin_device = dbus.Interface(
1869 self._system_bus.get_object(
Shijin Abrahamaa52eda2020-01-04 15:45:03 -08001870 self.BLUEZ_SERVICE_NAME,
Archie Pusakaa31b15b2019-11-06 18:13:05 +08001871 path),
1872 self.BLUEZ_PLUGIN_DEVICE_IFACE)
1873 connection_info = plugin_device.GetConnInfo()
1874 return json.dumps(connection_info)
1875 except Exception as e:
1876 logging.error('get_connection_info: %s', e)
1877 except:
1878 logging.error('get_connection_info: unexpected error')
1879 return None
1880
1881
Scott James Remnant4dcd73f2013-07-22 15:00:24 -07001882if __name__ == '__main__':
1883 logging.basicConfig(level=logging.DEBUG)
Scott James Remnant1c72d7a2013-07-29 15:00:04 -07001884 handler = logging.handlers.SysLogHandler(address='/dev/log')
Christopher Wiley9fd7f462013-10-10 20:06:28 -07001885 formatter = logging.Formatter(
Scott James Remnant8d2cbf32013-11-12 11:00:25 -08001886 'bluetooth_device_xmlrpc_server: [%(levelname)s] %(message)s')
Christopher Wiley9fd7f462013-10-10 20:06:28 -07001887 handler.setFormatter(formatter)
Scott James Remnant4dcd73f2013-07-22 15:00:24 -07001888 logging.getLogger().addHandler(handler)
Scott James Remnant8d2cbf32013-11-12 11:00:25 -08001889 logging.debug('bluetooth_device_xmlrpc_server main...')
Scott James Remnant4dcd73f2013-07-22 15:00:24 -07001890 server = xmlrpc_server.XmlRpcServer(
1891 'localhost',
Scott James Remnant8d2cbf32013-11-12 11:00:25 -08001892 constants.BLUETOOTH_DEVICE_XMLRPC_SERVER_PORT)
1893 server.register_delegate(BluetoothDeviceXmlRpcDelegate())
Scott James Remnant4dcd73f2013-07-22 15:00:24 -07001894 server.run()