2018-01-12 04:52:10 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
|
|
|
|
import logging
|
2018-01-17 15:20:46 +01:00
|
|
|
from gi.repository import GObject, Gio, GLib
|
2018-01-12 04:52:10 +01:00
|
|
|
|
2018-01-15 15:53:32 +01:00
|
|
|
logger = logging.getLogger('tuhi.ble')
|
2018-01-12 04:52:10 +01:00
|
|
|
|
|
|
|
ORG_BLUEZ_GATTCHARACTERISTIC1 = 'org.bluez.GattCharacteristic1'
|
|
|
|
ORG_BLUEZ_GATTSERVICE1 = 'org.bluez.GattService1'
|
|
|
|
ORG_BLUEZ_DEVICE1 = 'org.bluez.Device1'
|
|
|
|
ORG_BLUEZ_ADAPTER1 = 'org.bluez.Adapter1'
|
|
|
|
|
2018-01-12 20:21:05 +01:00
|
|
|
|
2018-01-12 04:52:10 +01:00
|
|
|
class BlueZCharacteristic(GObject.Object):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-15 03:09:43 +01:00
|
|
|
Abstraction for a org.bluez.GattCharacteristic1 object.
|
|
|
|
|
|
|
|
Use start_notify() to receive notifications about the characteristics.
|
|
|
|
Hook up a property with connect_property() first.
|
2018-01-12 04:52:10 +01:00
|
|
|
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
def __init__(self, obj):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-15 03:09:43 +01:00
|
|
|
:param obj: the org.bluez.GattCharacteristic1 DBus proxy object
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
self.obj = obj
|
|
|
|
self.objpath = obj.get_object_path()
|
|
|
|
self.interface = obj.get_interface(ORG_BLUEZ_GATTCHARACTERISTIC1)
|
|
|
|
assert(self.interface is not None)
|
|
|
|
|
|
|
|
self.uuid = self.interface.get_cached_property('UUID').unpack()
|
|
|
|
assert(self.uuid is not None)
|
|
|
|
|
|
|
|
self._property_callbacks = {}
|
|
|
|
self.interface.connect('g-properties-changed',
|
|
|
|
self._on_properties_changed)
|
|
|
|
|
|
|
|
def connect_property(self, propname, callback):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
Connect the property with the given name to the callback function
|
|
|
|
provide. When the property chages, callback is invoked as:
|
|
|
|
|
|
|
|
callback(propname, value)
|
2018-01-15 03:09:43 +01:00
|
|
|
|
|
|
|
The common way is connect_property('Value', do_something) to get
|
|
|
|
notified about Value changes on this characteristic.
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
self._property_callbacks[propname] = callback
|
|
|
|
|
|
|
|
def start_notify(self):
|
|
|
|
self.interface.StartNotify()
|
|
|
|
|
|
|
|
def write_value(self, data):
|
|
|
|
return self.interface.WriteValue('(aya{sv})', data, {})
|
|
|
|
|
|
|
|
def _on_properties_changed(self, obj, properties, invalidated_properties):
|
|
|
|
properties = properties.unpack()
|
|
|
|
for name, value in properties.items():
|
|
|
|
try:
|
|
|
|
self._property_callbacks[name](name, value)
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def __repr__(self):
|
2018-01-29 11:38:14 +01:00
|
|
|
return f'Characteristic {self.uuid}:{self.objpath}'
|
2018-01-12 04:52:10 +01:00
|
|
|
|
|
|
|
|
|
|
|
class BlueZDevice(GObject.Object):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
Abstraction for a org.bluez.Device1 object
|
|
|
|
|
|
|
|
The device initializes itself based on the given object manager and
|
2018-01-15 03:09:43 +01:00
|
|
|
object, specifically: it resolves its services and gatt characteristics.
|
|
|
|
The device resolves itself where possible, if one of its
|
|
|
|
services/characteristics comes in late, call resolve().
|
2018-01-12 04:52:10 +01:00
|
|
|
|
2018-01-15 03:09:43 +01:00
|
|
|
To connect to the real device, call connect_to_device(). The 'connected'
|
|
|
|
and 'disconnected' signals are emitted when the connection is
|
|
|
|
established.
|
2018-01-12 20:21:05 +01:00
|
|
|
|
2018-01-15 03:09:43 +01:00
|
|
|
The device's characteristics are in self.characteristics[uuid]
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
__gsignals__ = {
|
2018-01-29 12:04:51 +01:00
|
|
|
'connected':
|
2018-02-01 12:02:05 +01:00
|
|
|
(GObject.SignalFlags.RUN_FIRST, None, ()),
|
2018-01-29 12:04:51 +01:00
|
|
|
'disconnected':
|
2018-02-01 12:02:05 +01:00
|
|
|
(GObject.SignalFlags.RUN_FIRST, None, ()),
|
2018-01-29 12:04:51 +01:00
|
|
|
'updated':
|
2018-02-01 12:02:05 +01:00
|
|
|
(GObject.SignalFlags.RUN_FIRST, None, ()),
|
2018-01-12 04:52:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, om, obj):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-15 03:09:43 +01:00
|
|
|
:param om: The ObjectManager for name org.bluez path /
|
|
|
|
:param obj: The org.bluez.Device1 DBus proxy object
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
GObject.Object.__init__(self)
|
|
|
|
self.objpath = obj.get_object_path()
|
|
|
|
self.obj = obj
|
|
|
|
self.interface = obj.get_interface(ORG_BLUEZ_DEVICE1)
|
|
|
|
assert(self.interface is not None)
|
|
|
|
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.debug(f'Device {self.objpath} - {self.address} - {self.name}')
|
2018-01-12 04:52:10 +01:00
|
|
|
|
|
|
|
self.characteristics = {}
|
|
|
|
self.resolve(om)
|
|
|
|
self.interface.connect('g-properties-changed', self._on_properties_changed)
|
2018-01-17 02:34:54 +01:00
|
|
|
if self.connected:
|
2018-01-12 04:52:10 +01:00
|
|
|
self.emit('connected')
|
|
|
|
|
2018-02-01 12:02:05 +01:00
|
|
|
@GObject.Property
|
2018-01-15 03:21:38 +01:00
|
|
|
def name(self):
|
2018-01-23 03:01:39 +01:00
|
|
|
try:
|
|
|
|
return self.interface.get_cached_property('Name').unpack()
|
|
|
|
except AttributeError:
|
|
|
|
return 'UNKNOWN'
|
2018-01-15 03:21:38 +01:00
|
|
|
|
2018-02-01 12:02:05 +01:00
|
|
|
@GObject.Property
|
2018-01-15 03:21:38 +01:00
|
|
|
def address(self):
|
|
|
|
return self.interface.get_cached_property('Address').unpack()
|
|
|
|
|
2018-02-01 12:02:05 +01:00
|
|
|
@GObject.Property
|
2018-01-15 03:21:38 +01:00
|
|
|
def uuids(self):
|
|
|
|
return self.interface.get_cached_property('UUIDs').unpack()
|
|
|
|
|
2018-02-01 12:02:05 +01:00
|
|
|
@GObject.Property
|
2018-01-15 03:21:38 +01:00
|
|
|
def vendor_id(self):
|
|
|
|
md = self.interface.get_cached_property('ManufacturerData')
|
2018-02-09 11:24:11 +01:00
|
|
|
if md is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
return next(iter(dict(md)))
|
|
|
|
except StopIteration:
|
|
|
|
# dict is empty
|
|
|
|
pass
|
|
|
|
|
2018-01-15 03:21:38 +01:00
|
|
|
return None
|
|
|
|
|
2018-02-01 12:02:05 +01:00
|
|
|
@GObject.Property
|
2018-01-17 02:34:54 +01:00
|
|
|
def connected(self):
|
|
|
|
return (self.interface.get_cached_property('Connected').unpack() and
|
|
|
|
self.interface.get_cached_property('ServicesResolved').unpack())
|
|
|
|
|
2018-02-09 06:43:04 +01:00
|
|
|
@GObject.Property
|
|
|
|
def manufacturer_data(self):
|
2018-01-17 15:44:16 +01:00
|
|
|
md = self.interface.get_cached_property('ManufacturerData')
|
2018-02-09 06:43:04 +01:00
|
|
|
if md is None:
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
return next(iter(dict(md).values()))
|
|
|
|
except StopIteration:
|
|
|
|
# dict is empty
|
|
|
|
pass
|
|
|
|
|
2018-01-17 15:44:16 +01:00
|
|
|
return None
|
|
|
|
|
2018-01-12 04:52:10 +01:00
|
|
|
def resolve(self, om):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
Resolve the GattServices and GattCharacteristics. This function does
|
|
|
|
not need to be called for existing objects but if a device comes in
|
|
|
|
at runtime not all services may have been resolved by the time the
|
|
|
|
org.bluez.Device1 shows up.
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
objects = om.get_objects()
|
|
|
|
self._resolve_gatt_services(objects)
|
|
|
|
|
|
|
|
def _resolve_gatt_services(self, objects):
|
|
|
|
self.gatt_services = []
|
|
|
|
for obj in objects:
|
|
|
|
i = obj.get_interface(ORG_BLUEZ_GATTSERVICE1)
|
|
|
|
if i is None:
|
|
|
|
continue
|
|
|
|
|
|
|
|
device = i.get_cached_property('Device').get_string()
|
|
|
|
if device != self.objpath:
|
|
|
|
continue
|
|
|
|
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.debug(f'GattService1: {obj.get_object_path()} for device {device}')
|
2018-01-12 04:52:10 +01:00
|
|
|
self.gatt_services.append(obj)
|
|
|
|
self._resolve_gatt_characteristics(obj, objects)
|
|
|
|
|
|
|
|
def _resolve_gatt_characteristics(self, service_obj, objects):
|
|
|
|
for obj in objects:
|
|
|
|
i = obj.get_interface(ORG_BLUEZ_GATTCHARACTERISTIC1)
|
|
|
|
if i is None:
|
|
|
|
continue
|
|
|
|
|
|
|
|
service = i.get_cached_property('Service').get_string()
|
|
|
|
if service != service_obj.get_object_path():
|
|
|
|
continue
|
|
|
|
|
|
|
|
chrc = BlueZCharacteristic(obj)
|
|
|
|
if chrc.uuid in self.characteristics:
|
|
|
|
continue
|
|
|
|
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.debug(f'GattCharacteristic: {chrc.uuid} for service {service}')
|
2018-01-12 04:52:10 +01:00
|
|
|
|
|
|
|
self.characteristics[chrc.uuid] = chrc
|
|
|
|
|
|
|
|
def connect_device(self):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
Connect to the bluetooth device via bluez. This function is
|
|
|
|
asynchronous and returns immediately.
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
i = self.obj.get_interface(ORG_BLUEZ_DEVICE1)
|
2018-01-17 02:34:54 +01:00
|
|
|
if self.connected:
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.info(f'{self.address}: Device is already connected')
|
2018-01-12 04:52:10 +01:00
|
|
|
self.emit('connected')
|
|
|
|
return
|
|
|
|
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.info(f'{self.address}: Connecting')
|
2018-01-12 04:52:10 +01:00
|
|
|
i.Connect(result_handler=self._on_connect_result)
|
|
|
|
|
|
|
|
def _on_connect_result(self, obj, result, user_data):
|
2018-01-23 03:10:07 +01:00
|
|
|
if (isinstance(result, GLib.Error) and
|
|
|
|
result.domain == 'g-io-error-quark' and
|
|
|
|
result.code == Gio.IOErrorEnum.DBUS_ERROR and
|
|
|
|
Gio.dbus_error_get_remote_error(result) == 'org.bluez.Error.Failed' and
|
|
|
|
'Operation already in progress' in result.message):
|
2019-06-05 07:00:57 +02:00
|
|
|
logger.debug(f'{self.address}: Already connecting')
|
2018-01-23 03:10:07 +01:00
|
|
|
elif isinstance(result, Exception):
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.error(f'Connection failed: {result}')
|
2018-01-12 04:52:10 +01:00
|
|
|
|
2018-01-15 15:17:43 +01:00
|
|
|
def disconnect_device(self):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-15 15:17:43 +01:00
|
|
|
Disconnect the bluetooth device via bluez. This function is
|
|
|
|
asynchronous and returns immediately.
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-15 15:17:43 +01:00
|
|
|
i = self.obj.get_interface(ORG_BLUEZ_DEVICE1)
|
|
|
|
if not i.get_cached_property('Connected').get_boolean():
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.info(f'{self.address}: Device is already disconnected')
|
2018-01-15 15:17:43 +01:00
|
|
|
self.emit('disconnected')
|
|
|
|
return
|
|
|
|
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.info(f'{self.address}: Disconnecting')
|
2018-01-15 15:17:43 +01:00
|
|
|
i.Disconnect(result_handler=self._on_disconnect_result)
|
|
|
|
|
|
|
|
def _on_disconnect_result(self, obj, result, user_data):
|
|
|
|
if isinstance(result, Exception):
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.error(f'Disconnection failed: {result}')
|
2018-01-15 15:17:43 +01:00
|
|
|
|
2018-01-12 04:52:10 +01:00
|
|
|
def _on_properties_changed(self, obj, properties, invalidated_properties):
|
|
|
|
properties = properties.unpack()
|
|
|
|
|
|
|
|
if 'Connected' in properties:
|
|
|
|
if properties['Connected']:
|
|
|
|
logger.info('Connection established')
|
|
|
|
else:
|
|
|
|
logger.info('Disconnected')
|
2018-01-15 03:07:48 +01:00
|
|
|
self.emit('disconnected')
|
2018-02-09 06:35:04 +01:00
|
|
|
if 'ServicesResolved' in properties:
|
2018-01-17 02:34:54 +01:00
|
|
|
if properties['ServicesResolved']:
|
|
|
|
self.emit('connected')
|
2018-02-09 06:35:04 +01:00
|
|
|
if 'RSSI' in properties:
|
2018-01-17 16:12:29 +01:00
|
|
|
self.emit('updated')
|
2018-02-09 06:43:04 +01:00
|
|
|
if 'ManufacturerData' in properties:
|
|
|
|
self.notify('manufacturer-data')
|
2018-01-12 04:52:10 +01:00
|
|
|
|
|
|
|
def connect_gatt_value(self, uuid, callback):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
Connects Value property changes of the given GATT Characteristics
|
|
|
|
UUID to the callback.
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
try:
|
|
|
|
chrc = self.characteristics[uuid]
|
|
|
|
chrc.connect_property('Value', callback)
|
|
|
|
chrc.start_notify()
|
|
|
|
except KeyError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def __repr__(self):
|
2018-01-29 11:38:14 +01:00
|
|
|
return f'Device {self.name}:{self.objpath}'
|
2018-01-12 04:52:10 +01:00
|
|
|
|
2018-01-12 20:21:05 +01:00
|
|
|
|
2018-01-12 04:52:10 +01:00
|
|
|
class BlueZDeviceManager(GObject.Object):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
Manager object that connects to org.bluez's root object and handles the
|
2018-01-15 02:50:52 +01:00
|
|
|
devices.
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
__gsignals__ = {
|
2018-01-29 12:04:51 +01:00
|
|
|
'device-added':
|
2018-02-11 11:57:26 +01:00
|
|
|
(GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
|
2018-01-29 12:04:51 +01:00
|
|
|
'device-updated':
|
2018-02-01 12:02:05 +01:00
|
|
|
(GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_PYOBJECT,)),
|
2018-01-29 12:04:51 +01:00
|
|
|
'discovery-started':
|
2018-02-01 12:02:05 +01:00
|
|
|
(GObject.SignalFlags.RUN_FIRST, None, ()),
|
2018-01-29 12:04:51 +01:00
|
|
|
'discovery-stopped':
|
2018-02-01 12:02:05 +01:00
|
|
|
(GObject.SignalFlags.RUN_FIRST, None, ()),
|
2018-01-12 04:52:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
|
|
GObject.Object.__init__(self, **kwargs)
|
|
|
|
self.devices = []
|
2018-01-19 16:46:25 +01:00
|
|
|
self._discovery = False
|
2018-01-12 04:52:10 +01:00
|
|
|
|
|
|
|
def connect_to_bluez(self):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-15 03:09:43 +01:00
|
|
|
Connect to bluez's DBus interface. Once called, devices will be
|
|
|
|
resolved as they come in. The device-added signal is emitted for
|
|
|
|
each device.
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-12 04:52:10 +01:00
|
|
|
self._om = Gio.DBusObjectManagerClient.new_for_bus_sync(
|
2018-01-12 20:21:05 +01:00
|
|
|
Gio.BusType.SYSTEM,
|
|
|
|
Gio.DBusObjectManagerClientFlags.NONE,
|
|
|
|
'org.bluez',
|
|
|
|
'/',
|
|
|
|
None,
|
|
|
|
None,
|
|
|
|
None)
|
2018-01-12 04:52:10 +01:00
|
|
|
self._om.connect('object-added', self._on_om_object_added)
|
|
|
|
self._om.connect('object-removed', self._on_om_object_removed)
|
|
|
|
|
|
|
|
# We rely on nested object paths, so let's sort the objects by
|
|
|
|
# object path length and process them in order, this way we're
|
|
|
|
# guaranteed that the objects we need already exist.
|
2018-01-12 20:21:05 +01:00
|
|
|
for obj in self._om.get_objects():
|
2018-02-11 11:57:26 +01:00
|
|
|
self._process_object(obj)
|
2018-01-12 04:52:10 +01:00
|
|
|
|
2018-01-17 15:20:46 +01:00
|
|
|
def _discovery_timeout_expired(self):
|
|
|
|
self.stop_discovery()
|
|
|
|
return False
|
|
|
|
|
|
|
|
def start_discovery(self, timeout=0):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-17 15:20:46 +01:00
|
|
|
Start discovery mode, terminating after the specified timeout (in
|
|
|
|
seconds). If timeout is 0, no timeout is imposed and the discovery
|
|
|
|
mode stays on.
|
|
|
|
|
|
|
|
This emits the discovery-started signal
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
|
|
|
self.emit('discovery-started')
|
2018-01-19 16:46:25 +01:00
|
|
|
if self._discovery:
|
|
|
|
return
|
|
|
|
|
|
|
|
self._discovery = True
|
|
|
|
|
2018-01-17 15:20:46 +01:00
|
|
|
for obj in self._om.get_objects():
|
|
|
|
i = obj.get_interface(ORG_BLUEZ_ADAPTER1)
|
|
|
|
if i is None:
|
|
|
|
continue
|
|
|
|
|
2018-01-19 16:47:09 +01:00
|
|
|
# remove the duplicate data filter so we get notifications as they come in
|
|
|
|
i.SetDiscoveryFilter('(a{sv})', {'DuplicateData': GLib.Variant.new_boolean(False)})
|
|
|
|
|
2018-01-17 15:20:46 +01:00
|
|
|
objpath = obj.get_object_path()
|
2018-01-16 02:08:21 +01:00
|
|
|
try:
|
|
|
|
i.StartDiscovery()
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.debug(f'{objpath}: Discovery started (timeout {timeout})')
|
2018-01-16 02:08:21 +01:00
|
|
|
except GLib.Error as e:
|
|
|
|
if (e.domain == 'g-io-error-quark' and
|
|
|
|
e.code == Gio.IOErrorEnum.DBUS_ERROR and
|
|
|
|
Gio.dbus_error_get_remote_error(e) == 'org.bluez.Error.InProgress'):
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.debug(f'{objpath}: Already listening')
|
2018-01-17 15:20:46 +01:00
|
|
|
|
|
|
|
if timeout > 0:
|
|
|
|
GObject.timeout_add_seconds(timeout, self._discovery_timeout_expired)
|
|
|
|
|
|
|
|
# FIXME: Any errors up to here should trigger discovery-stopped
|
|
|
|
# signal with the status code
|
|
|
|
|
|
|
|
def stop_discovery(self):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-17 15:20:46 +01:00
|
|
|
Stop an ongoing discovery mode. Any errors are logged but ignored.
|
|
|
|
|
|
|
|
This emits the discovery-stopped signal
|
2018-01-29 12:04:51 +01:00
|
|
|
'''
|
2018-01-19 16:46:25 +01:00
|
|
|
if not self._discovery:
|
|
|
|
return
|
|
|
|
|
|
|
|
self._discovery = False
|
|
|
|
|
2018-01-17 15:20:46 +01:00
|
|
|
for obj in self._om.get_objects():
|
|
|
|
i = obj.get_interface(ORG_BLUEZ_ADAPTER1)
|
|
|
|
if i is None:
|
|
|
|
continue
|
|
|
|
|
|
|
|
objpath = obj.get_object_path()
|
|
|
|
try:
|
|
|
|
i.StopDiscovery()
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.debug(f'{objpath}: Discovery stopped')
|
2018-01-17 15:20:46 +01:00
|
|
|
except GLib.Error as e:
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.debug(f'{objpath}: Failed to stop discovery ({e})')
|
2018-01-17 15:20:46 +01:00
|
|
|
|
2018-01-19 16:47:09 +01:00
|
|
|
# reset the discovery filters
|
|
|
|
i.SetDiscoveryFilter('(a{sv})', {})
|
|
|
|
|
2018-01-29 12:04:51 +01:00
|
|
|
self.emit('discovery-stopped')
|
2018-01-17 15:20:46 +01:00
|
|
|
|
2018-01-17 16:12:29 +01:00
|
|
|
def _on_device_updated(self, device):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''Callback for Device's properties-changed'''
|
2018-02-16 01:18:27 +01:00
|
|
|
# logger.debug(f'Object updated: {device.name}')
|
2018-01-17 16:12:29 +01:00
|
|
|
|
2018-01-29 12:04:51 +01:00
|
|
|
self.emit('device-updated', device)
|
2018-01-17 16:12:29 +01:00
|
|
|
|
2018-01-12 04:52:10 +01:00
|
|
|
def _on_om_object_added(self, om, obj):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''Callback for ObjectManager's object-added'''
|
2018-01-12 04:52:10 +01:00
|
|
|
objpath = obj.get_object_path()
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.debug(f'Object added: {objpath}')
|
2018-01-22 10:39:23 +01:00
|
|
|
needs_resolve = self._process_object(obj, event=True)
|
2018-01-12 04:52:10 +01:00
|
|
|
|
2018-01-12 20:21:05 +01:00
|
|
|
# we had at least one characteristic added, need to resolve the
|
2018-01-12 04:52:10 +01:00
|
|
|
# devices.
|
|
|
|
# FIXME: this isn't the most efficient way...
|
|
|
|
if needs_resolve:
|
|
|
|
for d in self.devices:
|
|
|
|
d.resolve(om)
|
|
|
|
|
|
|
|
def _on_om_object_removed(self, om, obj):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''Callback for ObjectManager's object-removed'''
|
2018-01-12 04:52:10 +01:00
|
|
|
objpath = obj.get_object_path()
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.debug(f'Object removed: {objpath}')
|
2018-01-12 04:52:10 +01:00
|
|
|
|
2018-01-22 10:39:23 +01:00
|
|
|
def _process_object(self, obj, event=True):
|
2018-01-29 12:04:51 +01:00
|
|
|
'''Process a single DBusProxyObject'''
|
2018-01-12 04:52:10 +01:00
|
|
|
|
|
|
|
if obj.get_interface(ORG_BLUEZ_ADAPTER1) is not None:
|
|
|
|
self._process_adapter(obj)
|
|
|
|
elif obj.get_interface(ORG_BLUEZ_DEVICE1) is not None:
|
2018-02-11 11:57:26 +01:00
|
|
|
self._process_device(obj)
|
2018-01-12 04:52:10 +01:00
|
|
|
elif obj.get_interface(ORG_BLUEZ_GATTCHARACTERISTIC1) is not None:
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
def _process_adapter(self, obj):
|
|
|
|
objpath = obj.get_object_path()
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.debug(f'Adapter: {objpath}')
|
2018-01-12 04:52:10 +01:00
|
|
|
|
2018-02-11 11:57:26 +01:00
|
|
|
def _process_device(self, obj):
|
2018-01-12 04:52:10 +01:00
|
|
|
dev = BlueZDevice(self._om, obj)
|
|
|
|
self.devices.append(dev)
|
2018-01-29 12:04:51 +01:00
|
|
|
dev.connect('updated', self._on_device_updated)
|
2018-02-11 11:57:26 +01:00
|
|
|
self.emit('device-added', dev)
|
2018-01-12 04:52:10 +01:00
|
|
|
|
|
|
|
def _process_characteristic(self, obj):
|
|
|
|
objpath = obj.get_object_path()
|
2018-01-29 11:38:14 +01:00
|
|
|
logger.debug(f'Characteristic {objpath}')
|