From d42f968ff5f1bc2bbec58f4761d471c6bfc2041a Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Sun, 30 Jul 2017 23:32:56 +0200 Subject: [PATCH 01/14] Add python 3 support to avoid syntax error (see #2) --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index c1488b2..346ad58 100644 --- a/setup.py +++ b/setup.py @@ -43,4 +43,5 @@ def get_version(filename): 'Topic :: Software Development :: Libraries', 'Topic :: Communications', ], + use_2to3=True, ) From 2705d0fe0e52b268738cb04c133d9f067b8fe053 Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Fri, 4 Aug 2017 00:58:13 +0200 Subject: [PATCH 02/14] Add BlueZ 5 support --- bt_manager/adapter.py | 92 +++++++++++++++++++++++++++++++++++++++- bt_manager/audio.py | 10 ++--- bt_manager/interface.py | 34 +++++++-------- bt_manager/manager.py | 94 ++++++++++++++++++++++++++++++++++++++--- demo/demo.py | 8 ++-- 5 files changed, 204 insertions(+), 34 deletions(-) diff --git a/bt_manager/adapter.py b/bt_manager/adapter.py index 8f61f6b..edf38d0 100644 --- a/bt_manager/adapter.py +++ b/bt_manager/adapter.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import dbus from interface import BTInterface from manager import BTManager @@ -107,6 +108,18 @@ class BTAdapter(BTInterface): :signal DeviceDisappeared(signal_name, user_arg, device_path): Signal notifying when a device is now out-of-range """ + SIGNAL_PROPERTIES_CHANGED = 'PropertiesChanged' + """ + :signal PropertiesChanged(sig_name, user_arg, prop_name, prop_value): + Signal notifying when a property has changed. (Bluez 5) + """ + SIGNAL_PROPERTY_CHANGED = 'PropertyChanged' + """ + :signal PropertyChanged(sig_name, user_arg, prop_name, prop_value): + Signal notifying when a property has changed. (Bluez 4) + """ + ADAPTER_INTERFACE_BLUEZ4 = 'org.bluez.Adapter' + ADAPTER_INTERFACE_BLUEZ5 = 'org.bluez.Adapter1' def __init__(self, adapter_path=None, adapter_id=None): manager = BTManager() @@ -115,12 +128,89 @@ def __init__(self, adapter_path=None, adapter_id=None): adapter_path = manager.default_adapter() else: adapter_path = manager.find_adapter(adapter_id) - BTInterface.__init__(self, adapter_path, 'org.bluez.Adapter') + + self._get_version() + if (self._version <= self.BLUEZ4_VERSION): + BTInterface.__init__(self, adapter_path, BTAdapter.ADAPTER_INTERFACE_BLUEZ4) + self._properties = self._interface.GetProperties().keys() + self._register_signal_name(BTAdapter.SIGNAL_PROPERTY_CHANGED) + + else: + BTInterface.__init__(self, adapter_path, BTAdapter.ADAPTER_INTERFACE_BLUEZ5) + self._register_signal_name(BTAdapter.SIGNAL_DEVICE_FOUND) self._register_signal_name(BTAdapter.SIGNAL_DEVICE_REMOVED) self._register_signal_name(BTAdapter.SIGNAL_DEVICE_CREATED) self._register_signal_name(BTAdapter.SIGNAL_DEVICE_DISAPPEARED) + if (self._version > self.BLUEZ4_VERSION): + self._init_properties() + + def _init_properties(self): + self._props_interface = dbus.Interface(self._object, 'org.freedesktop.DBus.Properties') + self._properties = list(self._props_interface.GetAll(BTAdapter.ADAPTER_INTERFACE_BLUEZ5).keys()) + self._register_signal_name(BTAdapter.SIGNAL_PROPERTIES_CHANGED) + + def get_property(self, name=None): + """ + Helper to get a property value by name or all + properties as a dictionary. + + See also :py:meth:`set_property` + + :param str name: defaults to None which means all properties + in the object's dictionary are returned as a dict. + Otherwise, the property name key is used and its value + is returned. + :return: Property value by property key, or a dictionary of + all properties + :raises KeyError: if the property key is not found in the + object's dictionary + :raises dbus.Exception: org.bluez.Error.DoesNotExist + :raises dbus.Exception: org.bluez.Error.InvalidArguments + """ + #BlueZ 4 + if (self._version <= self.BLUEZ4_VERSION): + if (name): + return self._interface.GetProperties()[name] + else: + return self._interface.GetProperties() + + #BlueZ 5 + else: + if (name): + return self._props_interface.Get(BTAdapter.ADAPTER_INTERFACE_BLUEZ5, name) + else: + return self._props_interface.GetAll(BTAdapter.ADAPTER_INTERFACE_BLUEZ5) + + def set_property(self, name, value): + """ + Helper to set a property value by name, translating to correct + dbus type + + See also :py:meth:`get_property` + + :param str name: The property name in the object's dictionary + whose value shall be set. + :param value: Properties new value to be assigned. + :return: + :raises KeyError: if the property key is not found in the + object's dictionary + :raises dbus.Exception: org.bluez.Error.DoesNotExist + :raises dbus.Exception: org.bluez.Error.InvalidArguments + """ + #BlueZ 4 + if (self._version <= self.BLUEZ4_VERSION): + typeof = type(self.get_property(name)) + self._interface.SetProperty(name, + translate_to_dbus_type(typeof, value)) + + #BlueZ 5 + else: + typeof = type(self.get_property(name)) + self._props_interface.Set(BTAdapter.ADAPTER_INTERFACE_BLUEZ5, name, + translate_to_dbus_type(typeof, value)) + def start_discovery(self): """ This method starts the device discovery session. This diff --git a/bt_manager/audio.py b/bt_manager/audio.py index 044445e..6b520c5 100644 --- a/bt_manager/audio.py +++ b/bt_manager/audio.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals import dbus.service -import gobject +from gi.repository import GObject import pprint import os @@ -192,16 +192,16 @@ def _transport_ready_handler(self, fd, cb_condition): def _install_transport_ready(self): if ('r' in self.access_type): - io_event = gobject.IO_IN + io_event = GObject.IO_IN else: - io_event = gobject.IO_OUT + io_event = GObject.IO_OUT - self.tag = gobject.io_add_watch(self.fd, io_event, + self.tag = GObject.io_add_watch(self.fd, io_event, self._transport_ready_handler) def _uninstall_transport_ready(self): if (self.tag): - gobject.source_remove(self.tag) + GObject.source_remove(self.tag) self.tag = None def register_transport_ready_event(self, user_cb, user_arg): diff --git a/bt_manager/interface.py b/bt_manager/interface.py index a0177a9..e2878a5 100644 --- a/bt_manager/interface.py +++ b/bt_manager/interface.py @@ -67,13 +67,26 @@ class BTSimpleInterface: implementation of a bluez interface which has no signals or properties. """ + + BLUEZ_DBUS_OBJECT = 'org.bluez' + BLUEZ4_VERSION = 1.2 + def __init__(self, path, addr): self._dbus_addr = addr self._bus = dbus.SystemBus() - self._object = self._bus.get_object('org.bluez', path) + self._get_version() + self._object = self._bus.get_object(BTSimpleInterface.BLUEZ_DBUS_OBJECT, path) self._interface = dbus.Interface(self._object, addr) self._path = path + def _get_version(self): + if (self._version is None): + self._bus = dbus.SystemBus() + dbus_infos = self._bus.get_object('org.freedesktop.DBus', '/') + interface = dbus.Interface(dbus_infos, 'org.freedesktop.DBus') + self._version = float(interface.GetNameOwner(BTSimpleInterface.BLUEZ_DBUS_OBJECT)[1:]) + #interface.GetConnectionUnixProcessID(BTSimpleInterface.BLUEZ_DBUS_OBJECT) + # This class is not intended to be instantiated directly and should be # sub-classed with a concrete implementation for an interface @@ -92,18 +105,10 @@ class BTInterface(BTSimpleInterface): and properties. """ - SIGNAL_PROPERTY_CHANGED = 'PropertyChanged' - """ - :signal PropertyChanged(sig_name, user_arg, prop_name, prop_value): - Signal notifying when a property has changed. - """ - def __init__(self, path, addr): BTSimpleInterface.__init__(self, path, addr) self._signals = {} self._signal_names = [] - self._properties = self._interface.GetProperties().keys() - self._register_signal_name(BTInterface.SIGNAL_PROPERTY_CHANGED) def _register_signal_name(self, name): """ @@ -185,10 +190,7 @@ def get_property(self, name=None): :raises dbus.Exception: org.bluez.Error.DoesNotExist :raises dbus.Exception: org.bluez.Error.InvalidArguments """ - if (name): - return self._interface.GetProperties()[name] - else: - return self._interface.GetProperties() + raise NotImplementedError("Must override get_property") def set_property(self, name, value): """ @@ -206,9 +208,7 @@ def set_property(self, name, value): :raises dbus.Exception: org.bluez.Error.DoesNotExist :raises dbus.Exception: org.bluez.Error.InvalidArguments """ - typeof = type(self.get_property(name)) - self._interface.SetProperty(name, - translate_to_dbus_type(typeof, value)) + return def __getattr__(self, name): """Override default getattr behaviours to allow DBus object @@ -232,4 +232,4 @@ def __repr__(self): def __str__(self): """Stringify the Dbus interface properties in a nice format""" - return pprint.pformat(self._interface.GetProperties()) + return pprint.pformat(self.get_property()) \ No newline at end of file diff --git a/bt_manager/manager.py b/bt_manager/manager.py index 04e72d9..75398b5 100644 --- a/bt_manager/manager.py +++ b/bt_manager/manager.py @@ -15,11 +15,21 @@ class BTManager(BTInterface): See also :py:class:`.BTAdapter` """ + SIGNAL_INTERFACES_ADDED = 'InterfacesAdded' + """ + :signal InterfacesAdded(signal_name, user_arg, object_path): + Signal notifying when an adapter is added. + """ SIGNAL_ADAPTER_ADDED = 'AdapterAdded' """ :signal AdapterAdded(signal_name, user_arg, object_path): Signal notifying when an adapter is added. """ + SIGNAL_INTERFACES_REMOVED = 'InterfacesRemoved' + """ + :signal InterfacesRemoved(signal_name, user_arg, object_path): + Signal notifying when an adapter is added. + """ SIGNAL_ADAPTER_REMOVED = 'AdapterRemoved' """ :signal AdapterRemoved(signal_name, user_arg, object_path): @@ -34,12 +44,60 @@ class BTManager(BTInterface): :signal DefaultAdapterChanged(signal_name, user_arg, object_path): Signal notifying when the default adapter has been changed. """ + SIGNAL_PROPERTY_CHANGED = 'PropertyChanged' + """ + :signal PropertyChanged(sig_name, user_arg, prop_name, prop_value): + Signal notifying when a property has changed. (Bluez 4) + """ def __init__(self): - BTInterface.__init__(self, '/', 'org.bluez.Manager') - self._register_signal_name(BTManager.SIGNAL_ADAPTER_ADDED) - self._register_signal_name(BTManager.SIGNAL_ADAPTER_REMOVED) - self._register_signal_name(BTManager.SIGNAL_DEFAULT_ADAPTER_CHANGED) + self._get_version() + if (self._version <= self.BLUEZ4_VERSION): + BTInterface.__init__(self, '/', 'org.bluez.Manager') + self._register_signal_name(BTManager.SIGNAL_ADAPTER_ADDED) + self._register_signal_name(BTManager.SIGNAL_ADAPTER_REMOVED) + self._register_signal_name(BTManager.SIGNAL_DEFAULT_ADAPTER_CHANGED) + self._properties = self._interface.GetProperties().keys() + self._register_signal_name(BTManager.SIGNAL_PROPERTY_CHANGED) + + else: + BTInterface.__init__(self, '/', 'org.freedesktop.DBus.ObjectManager') + self._register_signal_name(BTManager.SIGNAL_INTERFACES_ADDED) + self._register_signal_name(BTManager.SIGNAL_INTERFACES_REMOVED) + + def get_property(self, name=None): + """ + Helper to get a property value by name or all + properties as a dictionary. + + See also :py:meth:`set_property` + + :param str name: defaults to None which means all properties + in the object's dictionary are returned as a dict. + Otherwise, the property name key is used and its value + is returned. + :return: Property value by property key, or a dictionary of + all properties + :raises KeyError: if the property key is not found in the + object's dictionary + :raises dbus.Exception: org.bluez.Error.DoesNotExist + :raises dbus.Exception: org.bluez.Error.InvalidArguments + """ + #BlueZ 4 + if (self._version <= self.BLUEZ4_VERSION): + if (name): + return self._interface.GetProperties()[name] + else: + return self._interface.GetProperties() + + #BlueZ 5 + else: + adapters = {} + adapters.Adapters = self.list_adapters() + if (name): + return adapters[name] + else: + return adapters def default_adapter(self): """ @@ -50,7 +108,13 @@ def default_adapter(self): :raises dbus.Exception: org.bluez.Error.InvalidArguments :raises dbus.Exception: org.bluez.Error.NoSuchAdapter """ - return self._interface.DefaultAdapter() + #BlueZ 4 + if (self._version <= self.BLUEZ4_VERSION): + return self._interface.DefaultAdapter() + + #BlueZ 5 + else: + return self.list_adapters().pop() def find_adapter(self, pattern): """ @@ -62,7 +126,14 @@ def find_adapter(self, pattern): :raises dbus.Exception: org.bluez.Error.InvalidArguments :raises dbus.Exception: org.bluez.Error.NoSuchAdapter """ - return self._interface.FindAdapter(pattern) + #BlueZ 4 + if (self._version <= self.BLUEZ4_VERSION): + return self._interface.FindAdapter(pattern) + + #BlueZ 5 + else: + return '' + #foreach( self._interface.GetManagedObjects() as key => obj) if pattern in key or patern = obj['org.bluez.Adapter1']['Address'] def list_adapters(self): """ @@ -74,4 +145,13 @@ def list_adapters(self): :raises dbus.Exception: org.bluez.Error.Failed :raises dbus.Exception: org.bluez.Error.OutOfMemory """ - return self._interface.ListAdapters() + #BlueZ 4 + if (self._version <= self.BLUEZ4_VERSION): + return self._interface.ListAdapters() + + #BlueZ 5 + else: + adapters = list(self._interface.GetManagedObjects().keys()) + # Remove first item (not a real adapter) + del adapters[0] + return adapters diff --git a/demo/demo.py b/demo/demo.py index 5d9437e..5f535c2 100644 --- a/demo/demo.py +++ b/demo/demo.py @@ -5,7 +5,7 @@ import sys import dbus import dbus.mainloop.glib -import gobject +from gi.repository import GObject import signal from collections import namedtuple @@ -762,12 +762,12 @@ def invoke_bt_command(text): def timeout_handler(signum, frame): - while gobject.MainLoop().get_context().pending(): - gobject.MainLoop().get_context().iteration(False) + while GObject.MainLoop().get_context().pending(): + GObject.MainLoop().get_context().iteration(False) dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) -gobject.threads_init() +GObject.threads_init() signal.signal(signal.SIGALRM, timeout_handler) signal.setitimer(signal.ITIMER_REAL, 0.01, 0.01) From 5819256e5973618b8a97802a2260648d98c17176 Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Fri, 4 Aug 2017 01:52:23 +0200 Subject: [PATCH 03/14] Change package version and debug demo --- README.rst | 4 ++++ bt_manager/__init__.py | 2 +- demo/demo.py | 7 ++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index fb676f6..43e2649 100644 --- a/README.rst +++ b/README.rst @@ -30,6 +30,10 @@ Installing the RTP/SBC codec (this must be done first): Note: the default platform is x86 (mmx). To build for a different platform e.g., armv6 then run ``sudo make -C codecs install PLATFORM=armv6`` instead. +Prerequisites + + sudo apt-get install python-pip libffi-dev + Install the python library by running: pip install BT-Manager diff --git a/bt_manager/__init__.py b/bt_manager/__init__.py index b2fecb2..835882f 100644 --- a/bt_manager/__init__.py +++ b/bt_manager/__init__.py @@ -3,7 +3,7 @@ import cffi import os -__version__ = '0.3.1' +__version__ = '0.4.0' if StrictVersion(cffi.__version__) < StrictVersion('0.7'): raise RuntimeError( diff --git a/demo/demo.py b/demo/demo.py index 5f535c2..846ff60 100644 --- a/demo/demo.py +++ b/demo/demo.py @@ -785,9 +785,10 @@ def timeout_handler(signum, frame): adapter.add_signal_receiver(dump_signal, bt_manager.BTAdapter.SIGNAL_DEVICE_FOUND, None) - adapter.add_signal_receiver(dump_signal, - bt_manager.BTAdapter.SIGNAL_PROPERTY_CHANGED, - None) + if (adapter._version <= bt_manager.BTAdapter.BLUEZ4_VERSION): + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_PROPERTY_CHANGED, + None) except dbus.exceptions.DBusException: print 'Unable to complete:', sys.exc_info() From 28bfbc6ac535cd993574dd7ef771dce82aa2bb4b Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Fri, 4 Aug 2017 01:54:59 +0200 Subject: [PATCH 04/14] Add python3 demo --- demo/demo3.py | 800 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 800 insertions(+) create mode 100644 demo/demo3.py diff --git a/demo/demo3.py b/demo/demo3.py new file mode 100644 index 0000000..eb9a6e6 --- /dev/null +++ b/demo/demo3.py @@ -0,0 +1,800 @@ + + +import readline # noqa +import bt_manager +import sys +import dbus +import dbus.mainloop.glib +from gi.repository import GObject +import signal +from collections import namedtuple + + +def dump_signal(signal, *args): + print('\n=========================================================') + print('>>>>>', signal, '<<<<<') + print(args) + print('=========================================================') + + +def agent_event_handler(*args): + print('\n=========================================================') + print('Agent event:', args) + return True + + +def agent_event_request_pin_code(event, device): + print('\n=========================================================') + print('Agent event:', event) + print('Device:', device) + print('Enter PIN 1234 on device') + return dbus.String('1234') + + +def agent_event_request_pass_key(event, device): + print('\n=========================================================') + print('Agent event:', event) + print('Device:', device) + print('Using pass code 1234765') + return dbus.UInt32('1234765') + + +def device_created_ok(*args): + print('\n=========================================================') + print('New Device Paired:', args) + + +def device_created_error(*args): + print('\n=========================================================') + print('Pairing Error:', args) + + +def cmd_help(args): + global cmd_table + + if (len(args)): + cmd_list = [args.pop(0)] + else: + cmd_list = list(cmd_table.keys()) + + for i in cmd_list: + print(i, cmd_table[i].args, ":", cmd_table[i].desc) + + +def list_adapters(args): + print('=========================================================') + try: + print(bt_manager.BTManager().list_adapters()) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def list_devices(args): + print('=========================================================') + global adapter + + try: + print(adapter.list_devices()) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def adapter_info(args): + global adapter + + try: + print('=========================================================') + print(adapter) + print('=========================================================') + cod = bt_manager.BTCoD(adapter.Class) + print('Vendor Name:', bt_manager.VENDORS.get(adapter.Vendor, 'Unknown')) + print('Device Class:', hex(adapter.Class)) + print('Major Service Class:', str(cod.major_service_class)) + print('Major Device Class:', str(cod.major_device_class)) + print('Minor Device Class:', str(cod.minor_device_class)) + print('=========================================================') + uuids = adapter.UUIDs + for i in uuids: + uuid = bt_manager.BTUUID(i) + print(bt_manager.SERVICES.get(uuid.uuid16, uuid)) + print('=========================================================') + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def adapter_get(args): + if (len(args)): + name = args.pop(0) + else: + name = None + + print('=========================================================') + global adapter + + try: + print(adapter.get_property(name=name)) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def adapter_set(args): + if (len(args) >= 2): + name = args.pop(0) + value = args.pop(0) + else: + print('Error: Requires property name and value') + return + + global adapter + + try: + adapter.set_property(name, value) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def device_listen(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Requires device path') + return + + global services + + try: + device = bt_manager.BTDevice(dev_path=dev_path) + device.add_signal_receiver(dump_signal, + bt_manager.BTDevice.SIGNAL_PROPERTY_CHANGED, + None) + services[dev_path] = device + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def device_rm(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Requires device path') + return + + global adapter + + try: + adapter.remove_device(dev_path) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def device_get(args): + if (len(args)): + dev_path = args.pop(0) + if (len(args)): + name = args.pop(0) + else: + name = None + else: + print('Error: Requires device path') + return + + try: + device = bt_manager.BTDevice(dev_path=dev_path) + print('=========================================================') + print(device.get_property(name=name)) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def device_set(args): + if (len(args) >= 2): + dev_path = args.pop(0) + name = args.pop(0) + value = args.pop(0) + else: + print('Error: Requires device path, property name and value') + return + + try: + device = bt_manager.BTDevice(dev_path=dev_path) + device.set_property(name, value) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def device_info(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + device = bt_manager.BTDevice(dev_path=dev_path) + print('=========================================================') + print(device) + print('=========================================================') + cod = bt_manager.BTCoD(device.Class) + print('Vendor Name:', bt_manager.VENDORS.get(device.Vendor, 'Unknown')) + print('Device Class:', hex(device.Class)) + print('Major Service Class:', str(cod.major_service_class)) + print('Major Device Class:', str(cod.major_device_class)) + print('Minor Device Class:', str(cod.minor_device_class)) + print('=========================================================') + uuids = device.UUIDs + for i in uuids: + uuid = bt_manager.BTUUID(i) + print(bt_manager.SERVICES.get(uuid.uuid16, uuid)) + print('=========================================================') + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def device_disconnect(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + device = bt_manager.BTDevice(dev_path=dev_path) + device.disconnect() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def device_discovery(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + device = bt_manager.BTDevice(dev_path=dev_path) + discovery = device.discover_services() + if (discovery): + for rec in list(discovery.keys()): + print('=========================================================') # noqa + print(bt_manager.BTDiscoveryInfo(discovery[rec])) + print('=========================================================') + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def device_create(args): + + global adapter + + if (len(args) >= 2): + path = args.pop(0) + dev_id = args.pop(0) + else: + print('Error: Must provide agent path and device address') + return + + try: + caps = 'DisplayYesNo' + adapter.create_paired_device(dev_id, path, caps, + device_created_ok, + device_created_error) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def discovery_start(args): + + global adapter + + try: + adapter.start_discovery() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def discovery_stop(args): + + global adapter + + try: + adapter.stop_discovery() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def agent_start(args): + + global services, adapter + + if (len(args)): + path = args.pop(0) + else: + print('Error: Must provide agent path e.g., /test/agent') + return + + try: + agent = bt_manager.BTAgent(path=path, + cb_notify_on_request_pin_code=agent_event_request_pin_code, # noqa + cb_notify_on_request_pass_key=agent_event_request_pass_key, # noqa + cb_notify_on_release=agent_event_handler, + cb_notify_on_authorize=agent_event_handler, + cb_notify_on_request_confirmation=agent_event_handler, # noqa + cb_notify_on_confirm_mode_change=agent_event_handler, # noqa + cb_notify_on_cancel=agent_event_handler) + services[path] = agent + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def agent_stop(args): + + global agent, services + + if (len(args)): + path = args.pop(0) + else: + print('Error: Must provide agent path e.g., /test/agent') + return + + try: + adapter.unregister_agent(path) + services[path].remove_from_connection() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def exit_cleanup(args): + sys.exit(0) + + +def sink_info(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + sink = bt_manager.BTAudioSink(dev_path=dev_path) + print('=========================================================') + print(sink) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def sink_connect(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + sink = bt_manager.BTAudioSink(dev_path=dev_path) + sink.connect() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def sink_disconnect(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + sink = bt_manager.BTAudioSink(dev_path=dev_path) + sink.disconnect() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def input_info(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + ip = bt_manager.BTInput(dev_path=dev_path) + print('=========================================================') + print(ip) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def input_connect(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + ip = bt_manager.BTInput(dev_path=dev_path) + ip.connect() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def input_disconnect(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + sink = bt_manager.BTInput(dev_path=dev_path) + sink.disconnect() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def source_info(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + source = bt_manager.BTAudioSource(dev_path=dev_path) + print('=========================================================') + print(source) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def source_connect(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + source = bt_manager.BTAudioSource(dev_path=dev_path) + source.connect() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def source_disconnect(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + source = bt_manager.BTAudioSource(dev_path=dev_path) + source.disconnect() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def control_info(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + ctrl = bt_manager.BTControl(dev_path=dev_path) + print('=========================================================') + print(ctrl) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def control_vol_up(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + ctrl = bt_manager.BTControl(dev_path=dev_path) + ctrl.volume_up() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def control_vol_down(args): + if (len(args)): + dev_path = args.pop(0) + else: + print('Error: Must specify device path') + return + + try: + ctrl = bt_manager.BTControl(dev_path=dev_path) + ctrl.volume_down() + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def media_sbc_sink_start(args): + + global services + + if (len(args)): + path = args.pop(0) + if (len(args)): + dev_path = args.pop(0) + else: + dev_path = None + else: + print('Error: Must provide endpoint path e.g., /test/endpoint/sbc0') + return + + try: + ep = bt_manager.SBCAudioSink(path=path, dev_path=dev_path) + print('=========================================================') + print(repr(ep)) + services[path] = ep + media = bt_manager.BTMedia() + media.register_endpoint(path, ep.get_properties()) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def media_decode_handler(args): + ep = args[0] + fd = args[1] + data = ep.read_transport() + fd.write(data) + + +def media_encode_handler(args): + ep = args[0] + fd = args[1] + data = fd.read(2560) + ep.write_transport(data) + + +def media_decode(args): + + global services + + if (len(args) >= 2): + path = args.pop(0) + filename = args.pop(0) + else: + print('Error: Must provide endpoint path e.g., /test/endpoint/sbc0 and audio storage filename') # noqa + return + + fd = open(filename, 'wb+') + ep = services[path] + ep.register_transport_ready_event(media_decode_handler, (ep, fd)) + + +def media_encode(args): + + global services + + if (len(args) >= 2): + path = args.pop(0) + filename = args.pop(0) + else: + print('Error: Must provide endpoint path e.g., /test/endpoint/sbc0 and audio storage filename') # noqa + return + + fd = open(filename, 'rb') + ep = services[path] + ep.register_transport_ready_event(media_encode_handler, (ep, fd)) + + +def media_sbc_source_start(args): + + global services + + if (len(args)): + path = args.pop(0) + if (len(args)): + dev_path = args.pop(0) + else: + dev_path = None + else: + print('Error: Must provide endpoint path e.g., /test/endpoint/sbc0') + return + + try: + ep = bt_manager.SBCAudioSource(path=path, dev_path=dev_path) + print('=========================================================') + print(repr(ep)) + services[path] = ep + media = bt_manager.BTMedia() + media.register_endpoint(path, ep.get_properties()) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +def media_stop(args): + + global services + + if (len(args)): + path = args.pop(0) + else: + print('Error: Must provide endpoint path e.g., /test/endpoint/sbc0') + return + + try: + ep = services[path] + ep.unregister_transport_ready_event() + ep.close_transport() + ep.remove_from_connection() + media = bt_manager.BTMedia() + media.unregister_endpoint(path) + except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + + +CmdEntry = namedtuple('CmdEntry', 'func desc args') +cmd_table = {'help': CmdEntry(cmd_help, + 'Display a list of commands or get help for a specific command', # noqa + '[command]'), + 'list-adapters': CmdEntry(list_adapters, + 'Provide a list of available BT adapters', # noqa + None), + 'list-devices': CmdEntry(list_devices, + 'Display a list of paired BT devices', + None), + 'adapter-info': CmdEntry(adapter_info, + 'Display information about default BT adapter', # noqa + None), + 'adapter-get': CmdEntry(adapter_get, + 'Get adapter property by name', + ''), + 'adapter-set': CmdEntry(adapter_set, + 'Set adapter property by name, value', + ' '), + 'device-rm': CmdEntry(device_rm, + 'Remove device from adapter', + ''), + 'device-get': CmdEntry(device_get, + 'Get device property by name', + ' '), + 'device-set': CmdEntry(device_set, + 'Set device property by name, value', + ' '), + 'device-info': CmdEntry(device_info, + 'Display information about a paired BT device', # noqa + ''), + 'device-disconnect': CmdEntry(device_disconnect, + 'Disconnect a BT device', + ''), + 'device-discovery': CmdEntry(device_discovery, + 'Run BT device discovery session', + ''), + 'device-listen': CmdEntry(device_listen, + 'Listen for device proeprty change events', # noqa + ''), + 'device-create': CmdEntry(device_create, + 'Create device', + '[dev_id e.g., 11:22:33:44:55:66]'), # noqa + 'discovery-start': CmdEntry(discovery_start, + 'Start device discovery', + None), + 'discovery-stop': CmdEntry(discovery_stop, + 'Stop device discovery', + None), + 'agent-start': CmdEntry(agent_start, + 'Start pairing agent', + ' [dev_id e.g., 11:22:33:44:55:66]'), # noqa + 'agent-stop': CmdEntry(agent_stop, + 'Stop pairing agent', + ''), + 'sink-info': CmdEntry(sink_info, + 'Audio sink properties', + ''), + 'sink-connect': CmdEntry(sink_connect, + 'Audio sink connect', + ''), + 'sink-disconnect': CmdEntry(sink_disconnect, + 'Audio sink connect', + ''), + 'source-info': CmdEntry(source_info, + 'Audio source properties', + ''), + 'source-connect': CmdEntry(source_connect, + 'Audio source connect', + ''), + 'source-disconnect': CmdEntry(source_disconnect, + 'Audio source connect', + ''), + 'control-info': CmdEntry(control_info, + 'Control device info', + ''), + 'control-vol-up': CmdEntry(control_vol_up, + 'Control volume up', + ''), + 'control-vol-down': CmdEntry(control_vol_down, + 'Control volume down', + ''), + 'media-sbc-sink-start': CmdEntry(media_sbc_sink_start, + 'Start media endpoint for SBC audio sink (i.e., connects to a source device)', # noqa + ''), + 'media-decode': CmdEntry(media_decode, + 'Start media decode for SBC audio sink', + ' '), + 'media-encode': CmdEntry(media_encode, + 'Start media encode for SBC audio source', # noqa + ' '), + 'media-sbc-source-start': CmdEntry(media_sbc_source_start, + 'Start media endpoint for SBC audio source (i.e., connects to a sink device)', # noqa + ''), + 'media-stop': CmdEntry(media_stop, + 'Stop media endpoint', + ''), + 'exit': CmdEntry(exit_cleanup, + 'Cleanup and exit', + None), + 'input-info': CmdEntry(input_info, + 'Input device properties', + ''), + 'input-connect': CmdEntry(input_connect, + 'Input device connect', + ''), + 'input-disconnect': CmdEntry(input_disconnect, + 'Input device connect', + ''), + } + + +def invoke_bt_command(text): + if (not text): + return + args = text.split(' ') + cmd = args.pop(0) + cmd_entry = cmd_table.get(cmd) + if (cmd_entry): + cmd_entry.func(args) + else: + print('Error: Command "%s" was not recognized.' % cmd) + + +def timeout_handler(signum, frame): + while GObject.MainLoop().get_context().pending(): + GObject.MainLoop().get_context().iteration(False) + + +dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) +GObject.threads_init() +signal.signal(signal.SIGALRM, timeout_handler) +signal.setitimer(signal.ITIMER_REAL, 0.01, 0.01) + +try: + adapter = bt_manager.BTAdapter() + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_CREATED, + None) + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_REMOVED, + None) + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_DISAPPEARED, + None) + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_FOUND, + None) + if (adapter._version <= bt_manager.BTAdapter.BLUEZ4_VERSION): + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_PROPERTY_CHANGED, + None) +except dbus.exceptions.DBusException: + print('Unable to complete:', sys.exc_info()) + +services = {} + +# Main command processing loop +while True: + text = input("BT> ") + invoke_bt_command(text) From 532273a05abbe68f178ef2080b8d8f4b9da50dbb Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Sat, 5 Aug 2017 07:48:18 +0200 Subject: [PATCH 05/14] Debug for bluez5 --- bt_manager/adapter.py | 17 ++++++++--------- bt_manager/device.py | 28 +++++++++++++++++++++++----- bt_manager/interface.py | 6 ++++-- bt_manager/manager.py | 14 +++++++++----- demo/demo.py | 36 ++++++++++++++++++++++++------------ demo/demo3.py | 35 +++++++++++++++++++++++------------ 6 files changed, 91 insertions(+), 45 deletions(-) diff --git a/bt_manager/adapter.py b/bt_manager/adapter.py index edf38d0..e5adf68 100644 --- a/bt_manager/adapter.py +++ b/bt_manager/adapter.py @@ -122,32 +122,31 @@ class BTAdapter(BTInterface): ADAPTER_INTERFACE_BLUEZ5 = 'org.bluez.Adapter1' def __init__(self, adapter_path=None, adapter_id=None): - manager = BTManager() + self._manager = BTManager() if (adapter_path is None): if (adapter_id is None): - adapter_path = manager.default_adapter() + adapter_path = self._manager.default_adapter() else: - adapter_path = manager.find_adapter(adapter_id) + adapter_path = self._manager.find_adapter(adapter_id) self._get_version() if (self._version <= self.BLUEZ4_VERSION): BTInterface.__init__(self, adapter_path, BTAdapter.ADAPTER_INTERFACE_BLUEZ4) self._properties = self._interface.GetProperties().keys() self._register_signal_name(BTAdapter.SIGNAL_PROPERTY_CHANGED) + self._register_signal_name(BTAdapter.SIGNAL_DEVICE_FOUND) + self._register_signal_name(BTAdapter.SIGNAL_DEVICE_REMOVED) + self._register_signal_name(BTAdapter.SIGNAL_DEVICE_CREATED) + self._register_signal_name(BTAdapter.SIGNAL_DEVICE_DISAPPEARED) else: BTInterface.__init__(self, adapter_path, BTAdapter.ADAPTER_INTERFACE_BLUEZ5) - self._register_signal_name(BTAdapter.SIGNAL_DEVICE_FOUND) - self._register_signal_name(BTAdapter.SIGNAL_DEVICE_REMOVED) - self._register_signal_name(BTAdapter.SIGNAL_DEVICE_CREATED) - self._register_signal_name(BTAdapter.SIGNAL_DEVICE_DISAPPEARED) - if (self._version > self.BLUEZ4_VERSION): self._init_properties() def _init_properties(self): - self._props_interface = dbus.Interface(self._object, 'org.freedesktop.DBus.Properties') + self._props_interface = dbus.Interface(self._object, BTInterface.DBUS_PROPERTIES) self._properties = list(self._props_interface.GetAll(BTAdapter.ADAPTER_INTERFACE_BLUEZ5).keys()) self._register_signal_name(BTAdapter.SIGNAL_PROPERTIES_CHANGED) diff --git a/bt_manager/device.py b/bt_manager/device.py index 3fac979..27e0d73 100644 --- a/bt_manager/device.py +++ b/bt_manager/device.py @@ -101,13 +101,31 @@ class BTDevice(BTGenericDevice): :signal NodeRemoved(signal_name, user_arg, node_path): Signal notifying when a device node has been removed. """ + SIGNAL_PROPERTIES_CHANGED = 'PropertiesChanged' + """ + :signal PropertiesChanged(sig_name, user_arg, prop_name, prop_value): + Signal notifying when a property has changed. (Bluez 5) + """ + DEVICE_INTERFACE_BLUEZ4 = 'org.bluez.Device' + DEVICE_INTERFACE_BLUEZ5 = 'org.bluez.Device1' def __init__(self, *args, **kwargs): - BTGenericDevice.__init__(self, addr='org.bluez.Device', - *args, **kwargs) - self._register_signal_name(BTDevice.SIGNAL_DISCONNECT_REQUESTED) - self._register_signal_name(BTDevice.SIGNAL_NODE_CREATED) - self._register_signal_name(BTDevice.SIGNAL_NODE_REMOVED) + self._get_version() + if (self._version <= self.BLUEZ4_VERSION): + BTGenericDevice.__init__(self, addr=self.DEVICE_INTERFACE_BLUEZ4, + *args, **kwargs) + self._register_signal_name(BTDevice.SIGNAL_DISCONNECT_REQUESTED) + self._register_signal_name(BTDevice.SIGNAL_NODE_CREATED) + self._register_signal_name(BTDevice.SIGNAL_NODE_REMOVED) + + else: + BTGenericDevice.__init__(self, addr=self.DEVICE_INTERFACE_BLUEZ5, + *args, **kwargs) + + def _init_properties(self): + self._props_interface = dbus.Interface(self._object, BTInterface.DBUS_PROPERTIES) + self._properties = list(self._props_interface.GetAll(self.DEVICE_INTERFACE_BLUEZ5).keys()) + self._register_signal_name(self.SIGNAL_PROPERTIES_CHANGED) def discover_services(self, pattern=''): """ diff --git a/bt_manager/interface.py b/bt_manager/interface.py index e2878a5..1c64f23 100644 --- a/bt_manager/interface.py +++ b/bt_manager/interface.py @@ -104,6 +104,7 @@ class BTInterface(BTSimpleInterface): implementation of a bluez interface which has both signals and properties. """ + DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties' def __init__(self, path, addr): BTSimpleInterface.__init__(self, path, addr) @@ -122,7 +123,7 @@ def _register_signal_name(self, name): """ self._signal_names.append(name) - def add_signal_receiver(self, callback_fn, signal, user_arg): + def add_signal_receiver(self, callback_fn, signal, user_arg, addr=None): """ Add a signal receiver callback with user argument @@ -140,11 +141,12 @@ def add_signal_receiver(self, callback_fn, signal, user_arg): not registered """ if (signal in self._signal_names): + addr = self._dbus_addr if addr is None else addr s = Signal(signal, callback_fn, user_arg) self._signals[signal] = s self._bus.add_signal_receiver(s.signal_handler, signal, - dbus_interface=self._dbus_addr, + dbus_interface=addr, path=self._path) else: raise BTSignalNameNotRecognisedException diff --git a/bt_manager/manager.py b/bt_manager/manager.py index 75398b5..0380064 100644 --- a/bt_manager/manager.py +++ b/bt_manager/manager.py @@ -132,8 +132,10 @@ def find_adapter(self, pattern): #BlueZ 5 else: - return '' - #foreach( self._interface.GetManagedObjects() as key => obj) if pattern in key or patern = obj['org.bluez.Adapter1']['Address'] + for (key, object) in list(self._interface.GetManagedObjects()): + if BTAdapter.ADAPTER_INTERFACE_BLUEZ5 in object: + if pattern in key or object[BTAdapter.ADAPTER_INTERFACE_BLUEZ5]['Address'] == pattern: + return key def list_adapters(self): """ @@ -151,7 +153,9 @@ def list_adapters(self): #BlueZ 5 else: - adapters = list(self._interface.GetManagedObjects().keys()) - # Remove first item (not a real adapter) - del adapters[0] + objects = list(self._interface.GetManagedObjects()) + adapters = [] + for (key, object) in objects: + if BTAdapter.ADAPTER_INTERFACE_BLUEZ5 in object: + adapters.push(key) return adapters diff --git a/demo/demo.py b/demo/demo.py index 846ff60..5197602 100644 --- a/demo/demo.py +++ b/demo/demo.py @@ -773,22 +773,34 @@ def timeout_handler(signum, frame): try: adapter = bt_manager.BTAdapter() - adapter.add_signal_receiver(dump_signal, - bt_manager.BTAdapter.SIGNAL_DEVICE_CREATED, - None) - adapter.add_signal_receiver(dump_signal, - bt_manager.BTAdapter.SIGNAL_DEVICE_REMOVED, - None) - adapter.add_signal_receiver(dump_signal, - bt_manager.BTAdapter.SIGNAL_DEVICE_DISAPPEARED, - None) - adapter.add_signal_receiver(dump_signal, - bt_manager.BTAdapter.SIGNAL_DEVICE_FOUND, - None) if (adapter._version <= bt_manager.BTAdapter.BLUEZ4_VERSION): + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_CREATED, + None) + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_REMOVED, + None) + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_DISAPPEARED, + None) + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_FOUND, + None) adapter.add_signal_receiver(dump_signal, bt_manager.BTAdapter.SIGNAL_PROPERTY_CHANGED, None) + else: + adapter._manager.add_signal_receiver(dump_signal, + bt_manager.BTManager.SIGNAL_INTERFACES_ADDED, + None) + adapter._manager.add_signal_receiver(dump_signal, + bt_manager.BTManager.SIGNAL_INTERFACES_REMOVED, + None) + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_PROPERTIES_CHANGED, + None, + addr=BTInterface.DBUS_PROPERTIES) + except dbus.exceptions.DBusException: print 'Unable to complete:', sys.exc_info() diff --git a/demo/demo3.py b/demo/demo3.py index eb9a6e6..7cafec4 100644 --- a/demo/demo3.py +++ b/demo/demo3.py @@ -773,22 +773,33 @@ def timeout_handler(signum, frame): try: adapter = bt_manager.BTAdapter() - adapter.add_signal_receiver(dump_signal, - bt_manager.BTAdapter.SIGNAL_DEVICE_CREATED, - None) - adapter.add_signal_receiver(dump_signal, - bt_manager.BTAdapter.SIGNAL_DEVICE_REMOVED, - None) - adapter.add_signal_receiver(dump_signal, - bt_manager.BTAdapter.SIGNAL_DEVICE_DISAPPEARED, - None) - adapter.add_signal_receiver(dump_signal, - bt_manager.BTAdapter.SIGNAL_DEVICE_FOUND, - None) if (adapter._version <= bt_manager.BTAdapter.BLUEZ4_VERSION): + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_CREATED, + None) + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_REMOVED, + None) + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_DISAPPEARED, + None) + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_DEVICE_FOUND, + None) adapter.add_signal_receiver(dump_signal, bt_manager.BTAdapter.SIGNAL_PROPERTY_CHANGED, None) + else: + adapter._manager.add_signal_receiver(dump_signal, + bt_manager.BTManager.SIGNAL_INTERFACES_ADDED, + None) + adapter._manager.add_signal_receiver(dump_signal, + bt_manager.BTManager.SIGNAL_INTERFACES_REMOVED, + None) + adapter.add_signal_receiver(dump_signal, + bt_manager.BTAdapter.SIGNAL_PROPERTIES_CHANGED, + None) + except dbus.exceptions.DBusException: print('Unable to complete:', sys.exc_info()) From c505cc7d4a04f13388e70968a781f7c2f75268b8 Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Sun, 6 Aug 2017 20:05:27 +0200 Subject: [PATCH 06/14] Add new features --- bt_manager/adapter.py | 49 ++++++++++++++++++++++++++++++++++++------- bt_manager/manager.py | 16 ++++++++------ 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/bt_manager/adapter.py b/bt_manager/adapter.py index e5adf68..765643a 100644 --- a/bt_manager/adapter.py +++ b/bt_manager/adapter.py @@ -120,6 +120,8 @@ class BTAdapter(BTInterface): """ ADAPTER_INTERFACE_BLUEZ4 = 'org.bluez.Adapter' ADAPTER_INTERFACE_BLUEZ5 = 'org.bluez.Adapter1' + DEVICE_INTERFACE_BLUEZ5 = 'org.bluez.Device1' + AGENT_INTERFACE = 'org.bluez.AgentManager1' def __init__(self, adapter_path=None, adapter_id=None): self._manager = BTManager() @@ -141,9 +143,9 @@ def __init__(self, adapter_path=None, adapter_id=None): else: BTInterface.__init__(self, adapter_path, BTAdapter.ADAPTER_INTERFACE_BLUEZ5) - - if (self._version > self.BLUEZ4_VERSION): self._init_properties() + obj = self._bus.get_object(BTSimpleInterface.BLUEZ_DBUS_OBJECT, path) + self._agent_interface = dbus.Interface(obj, self.AGENT_INTERFACE) def _init_properties(self): self._props_interface = dbus.Interface(self._object, BTInterface.DBUS_PROPERTIES) @@ -257,7 +259,15 @@ def find_device(self, dev_id): :raises dbus.Exception: org.bluez.Error.DoesNotExist :raises dbus.Exception: org.bluez.Error.InvalidArguments """ - return self._interface.FindDevice(dev_id) + #BlueZ 4 + if (self._version <= self.BLUEZ4_VERSION): + return self._interface.FindDevice(dev_id) + #BlueZ 5 + else: + for (key, object) in self._interface.GetManagedObjects().items(): + if self.DEVICE_INTERFACE_BLUEZ5 in object: + if object[self.DEVICE_INTERFACE_BLUEZ5]['Address'] == pattern: + return key def list_devices(self): """ @@ -269,7 +279,17 @@ def list_devices(self): :raises dbus.Exception: org.bluez.Error.Failed :raises dbus.Exception: org.bluez.Error.OutOfMemory """ - return self._interface.ListDevices() + #BlueZ 4 + if (self._version <= self.BLUEZ4_VERSION): + return self._interface.ListDevices() + #BlueZ 5 + else: + objects = self._interface.GetManagedObjects().items() + devices = [] + for (key, object) in objects: + if self.DEVICE_INTERFACE_BLUEZ5 in object: + devices.append(key) + return devices def create_paired_device(self, dev_id, agent_path, capability, cb_notify_device, cb_notify_error): @@ -308,11 +328,16 @@ def create_paired_device(self, dev_id, agent_path, :raises dbus.Exception: org.bluez.Error.InvalidArguments :raises dbus.Exception: org.bluez.Error.Failed """ - return self._interface.CreatePairedDevice(dev_id, + #BlueZ 4 + if (self._version <= self.BLUEZ4_VERSION): + return self._interface.CreatePairedDevice(dev_id, agent_path, capability, reply_handler=cb_notify_device, # noqa error_handler=cb_notify_error) # noqa + #BlueZ 5 TODO !? + else: + pass def remove_device(self, dev_path): """ @@ -346,7 +371,12 @@ def register_agent(self, path, capability): :raises dbus.Exception: org.bluez.Error.InvalidArguments :raises dbus.Exception: org.bluez.Error.AlreadyExists """ - return self._interface.RegisterAgent(path, capability) + #BlueZ 4 + if (self._version <= self.BLUEZ4_VERSION): + return self._interface.RegisterAgent(path, capability) + #BlueZ 5 + else: + return self._agent_interface.RegisterAgent(path, capability) def unregister_agent(self, path): """ @@ -359,4 +389,9 @@ def unregister_agent(self, path): :return: :raises dbus.Exception: org.bluez.Error.DoesNotExist """ - return self._interface.UnregisterAgent(path) + #BlueZ 4 + if (self._version <= self.BLUEZ4_VERSION): + return self._interface.UnregisterAgent(path) + #BlueZ 5 + else: + return self._agent_interface.UnregisterAgent(path) diff --git a/bt_manager/manager.py b/bt_manager/manager.py index 0380064..c0e187c 100644 --- a/bt_manager/manager.py +++ b/bt_manager/manager.py @@ -49,6 +49,10 @@ class BTManager(BTInterface): :signal PropertyChanged(sig_name, user_arg, prop_name, prop_value): Signal notifying when a property has changed. (Bluez 4) """ + ADAPTER_INTERFACE_BLUEZ5 = 'org.bluez.Adapter1' + """ + @TODO remove this var, already prensent in BTAdapter + """ def __init__(self): self._get_version() @@ -132,9 +136,9 @@ def find_adapter(self, pattern): #BlueZ 5 else: - for (key, object) in list(self._interface.GetManagedObjects()): - if BTAdapter.ADAPTER_INTERFACE_BLUEZ5 in object: - if pattern in key or object[BTAdapter.ADAPTER_INTERFACE_BLUEZ5]['Address'] == pattern: + for (key, object) in self._interface.GetManagedObjects().items(): + if self.ADAPTER_INTERFACE_BLUEZ5 in object: + if pattern in key or object[self.ADAPTER_INTERFACE_BLUEZ5]['Address'] == pattern: return key def list_adapters(self): @@ -153,9 +157,9 @@ def list_adapters(self): #BlueZ 5 else: - objects = list(self._interface.GetManagedObjects()) + objects = self._interface.GetManagedObjects().items() adapters = [] for (key, object) in objects: - if BTAdapter.ADAPTER_INTERFACE_BLUEZ5 in object: - adapters.push(key) + if self.ADAPTER_INTERFACE_BLUEZ5 in object: + adapters.append(key) return adapters From a1b859c2a3e0f55707231b187ad680ef083cf739 Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Sun, 6 Aug 2017 20:32:11 +0200 Subject: [PATCH 07/14] Debug --- bt_manager/adapter.py | 84 ++++++++++---------- bt_manager/audio.py | 54 +++++++++---- bt_manager/control.py | 77 +++++++++++++++++-- bt_manager/device.py | 166 ++++++++++++++++++++++++++++++++++++---- bt_manager/interface.py | 41 ++++++++-- bt_manager/manager.py | 63 ++++++++------- bt_manager/media.py | 98 ++++++++++++++++++++++-- demo/demo.py | 9 ++- demo/demo3.py | 11 +-- 9 files changed, 471 insertions(+), 132 deletions(-) diff --git a/bt_manager/adapter.py b/bt_manager/adapter.py index 765643a..701da9e 100644 --- a/bt_manager/adapter.py +++ b/bt_manager/adapter.py @@ -1,8 +1,8 @@ from __future__ import unicode_literals import dbus -from interface import BTInterface -from manager import BTManager +from dbus import DBusException +from interface import BTInterface, translate_to_dbus_type class BTAdapter(BTInterface): @@ -108,31 +108,15 @@ class BTAdapter(BTInterface): :signal DeviceDisappeared(signal_name, user_arg, device_path): Signal notifying when a device is now out-of-range """ - SIGNAL_PROPERTIES_CHANGED = 'PropertiesChanged' - """ - :signal PropertiesChanged(sig_name, user_arg, prop_name, prop_value): - Signal notifying when a property has changed. (Bluez 5) - """ - SIGNAL_PROPERTY_CHANGED = 'PropertyChanged' - """ - :signal PropertyChanged(sig_name, user_arg, prop_name, prop_value): - Signal notifying when a property has changed. (Bluez 4) - """ + ADAPTER_INTERFACE_BLUEZ4 = 'org.bluez.Adapter' ADAPTER_INTERFACE_BLUEZ5 = 'org.bluez.Adapter1' DEVICE_INTERFACE_BLUEZ5 = 'org.bluez.Device1' AGENT_INTERFACE = 'org.bluez.AgentManager1' - def __init__(self, adapter_path=None, adapter_id=None): - self._manager = BTManager() - if (adapter_path is None): - if (adapter_id is None): - adapter_path = self._manager.default_adapter() - else: - adapter_path = self._manager.find_adapter(adapter_id) - - self._get_version() - if (self._version <= self.BLUEZ4_VERSION): + def __init__(self, adapter_path): + self.adapter_path = adapter_path + if (self.get_version() <= self.BLUEZ4_VERSION): BTInterface.__init__(self, adapter_path, BTAdapter.ADAPTER_INTERFACE_BLUEZ4) self._properties = self._interface.GetProperties().keys() self._register_signal_name(BTAdapter.SIGNAL_PROPERTY_CHANGED) @@ -142,15 +126,28 @@ def __init__(self, adapter_path=None, adapter_id=None): self._register_signal_name(BTAdapter.SIGNAL_DEVICE_DISAPPEARED) else: - BTInterface.__init__(self, adapter_path, BTAdapter.ADAPTER_INTERFACE_BLUEZ5) + BTInterface.__init__(self, adapter_path, self.ADAPTER_INTERFACE_BLUEZ5) + self._init_agent() + self._init_object_manager() self._init_properties() - obj = self._bus.get_object(BTSimpleInterface.BLUEZ_DBUS_OBJECT, path) - self._agent_interface = dbus.Interface(obj, self.AGENT_INTERFACE) def _init_properties(self): - self._props_interface = dbus.Interface(self._object, BTInterface.DBUS_PROPERTIES) - self._properties = list(self._props_interface.GetAll(BTAdapter.ADAPTER_INTERFACE_BLUEZ5).keys()) - self._register_signal_name(BTAdapter.SIGNAL_PROPERTIES_CHANGED) + self._props_interface = dbus.Interface(self._object, self.DBUS_PROPERTIES) + self._properties = list(self._props_interface.GetAll(self.ADAPTER_INTERFACE_BLUEZ5).keys()) + self._register_signal_name(self.SIGNAL_PROPERTIES_CHANGED) + + def _init_agent(self): + bluez_path = self._bus.get_object(self.BLUEZ_DBUS_OBJECT, '/org/bluez') + self._agent_interface = dbus.Interface(bluez_path, self.AGENT_INTERFACE) + + def _init_object_manager(self): + root_path = self._bus.get_object(self.BLUEZ_DBUS_OBJECT, '/') + self._ojects_interface = dbus.Interface(root_path, self.DBUS_OBJ_MANAGER) + self._register_signal_name(self.SIGNAL_INTERFACES_ADDED) + self._register_signal_name(self.SIGNAL_INTERFACES_REMOVED) + + def get_path(self): + return self.adapter_path def get_property(self, name=None): """ @@ -171,7 +168,7 @@ def get_property(self, name=None): :raises dbus.Exception: org.bluez.Error.InvalidArguments """ #BlueZ 4 - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): if (name): return self._interface.GetProperties()[name] else: @@ -180,9 +177,9 @@ def get_property(self, name=None): #BlueZ 5 else: if (name): - return self._props_interface.Get(BTAdapter.ADAPTER_INTERFACE_BLUEZ5, name) + return self._props_interface.Get(self.ADAPTER_INTERFACE_BLUEZ5, name) else: - return self._props_interface.GetAll(BTAdapter.ADAPTER_INTERFACE_BLUEZ5) + return self._props_interface.GetAll(self.ADAPTER_INTERFACE_BLUEZ5) def set_property(self, name, value): """ @@ -201,16 +198,16 @@ def set_property(self, name, value): :raises dbus.Exception: org.bluez.Error.InvalidArguments """ #BlueZ 4 - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): typeof = type(self.get_property(name)) self._interface.SetProperty(name, - translate_to_dbus_type(typeof, value)) + translate_to_dbus_type(typeof, value)) #BlueZ 5 else: typeof = type(self.get_property(name)) self._props_interface.Set(BTAdapter.ADAPTER_INTERFACE_BLUEZ5, name, - translate_to_dbus_type(typeof, value)) + translate_to_dbus_type(typeof, value)) def start_discovery(self): """ @@ -244,7 +241,7 @@ def stop_discovery(self): """ return self._interface.StopDiscovery() - def find_device(self, dev_id): + def find_device(self, dev_id, property='Address'): """ Returns the object path of device for given address. The device object needs to be first created via @@ -260,14 +257,15 @@ def find_device(self, dev_id): :raises dbus.Exception: org.bluez.Error.InvalidArguments """ #BlueZ 4 - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.FindDevice(dev_id) #BlueZ 5 else: - for (key, object) in self._interface.GetManagedObjects().items(): + for (key, object) in self._ojects_interface.GetManagedObjects().items(): if self.DEVICE_INTERFACE_BLUEZ5 in object: - if object[self.DEVICE_INTERFACE_BLUEZ5]['Address'] == pattern: + if object[self.DEVICE_INTERFACE_BLUEZ5][property] == dev_id: return key + raise DBusException('org.bluez.Error.DoesNotExist') def list_devices(self): """ @@ -280,11 +278,11 @@ def list_devices(self): :raises dbus.Exception: org.bluez.Error.OutOfMemory """ #BlueZ 4 - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.ListDevices() #BlueZ 5 else: - objects = self._interface.GetManagedObjects().items() + objects = self._ojects_interface.GetManagedObjects().items() devices = [] for (key, object) in objects: if self.DEVICE_INTERFACE_BLUEZ5 in object: @@ -329,7 +327,7 @@ def create_paired_device(self, dev_id, agent_path, :raises dbus.Exception: org.bluez.Error.Failed """ #BlueZ 4 - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.CreatePairedDevice(dev_id, agent_path, capability, @@ -372,7 +370,7 @@ def register_agent(self, path, capability): :raises dbus.Exception: org.bluez.Error.AlreadyExists """ #BlueZ 4 - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.RegisterAgent(path, capability) #BlueZ 5 else: @@ -390,7 +388,7 @@ def unregister_agent(self, path): :raises dbus.Exception: org.bluez.Error.DoesNotExist """ #BlueZ 4 - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.UnregisterAgent(path) #BlueZ 5 else: diff --git a/bt_manager/audio.py b/bt_manager/audio.py index 6b520c5..16f6f26 100644 --- a/bt_manager/audio.py +++ b/bt_manager/audio.py @@ -1,7 +1,10 @@ from __future__ import unicode_literals import dbus.service -from gi.repository import GObject +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject import pprint import os @@ -72,8 +75,12 @@ class BTAudioSource(BTAudio): See also: :py:class:`.BTAudio` """ def __init__(self, *args, **kwargs): - BTGenericDevice.__init__(self, addr='org.bluez.AudioSource', + if (self.get_version() <= self.BLUEZ4_VERSION): + BTGenericDevice.__init__(self, addr='org.bluez.AudioSource', *args, **kwargs) + else: + BTGenericDevice.__init__(self, addr=self.DEVICE_INTERFACE_BLUEZ5, + *args, **kwargs) class BTAudioSink(BTAudio): @@ -111,12 +118,16 @@ class BTAudioSink(BTAudio): """ def __init__(self, *args, **kwargs): - BTGenericDevice.__init__(self, addr='org.bluez.AudioSink', - *args, **kwargs) - self._register_signal_name(BTAudioSink.SIGNAL_CONNECTED) - self._register_signal_name(BTAudioSink.SIGNAL_DISCONNECTED) - self._register_signal_name(BTAudioSink.SIGNAL_PLAYING) - self._register_signal_name(BTAudioSink.SIGNAL_STOPPED) + if (self.get_version() <= self.BLUEZ4_VERSION): + BTGenericDevice.__init__(self, addr='org.bluez.AudioSink', + *args, **kwargs) + self._register_signal_name(BTAudioSink.SIGNAL_CONNECTED) + self._register_signal_name(BTAudioSink.SIGNAL_DISCONNECTED) + self._register_signal_name(BTAudioSink.SIGNAL_PLAYING) + self._register_signal_name(BTAudioSink.SIGNAL_STOPPED) + else: + BTGenericDevice.__init__(self, addr=self.DEVICE_INTERFACE_BLUEZ5, + *args, **kwargs) def is_connected(self): """ @@ -126,7 +137,10 @@ def is_connected(self): :return Connected: state of `Connected` attribute :rtype: boolean """ - return self._interface.IsConnected() + if (self.get_version() <= self.BLUEZ4_VERSION): + return self._interface.IsConnected() + else: + return self.get_property('Connected') class SBCAudioCodec(GenericEndpoint): @@ -160,7 +174,7 @@ class SBCAudioCodec(GenericEndpoint): See also: :py:class:`SBCAudioSink` and :py:class:`SBCAudioSource` """ - def __init__(self, uuid, path): + def __init__(self, uuid, path, adapter_id=None, dev_path=None): config = SBCCodecConfig(SBCChannelMode.ALL, SBCSamplingFrequency.ALL, SBCAllocationMethod.ALL, @@ -171,6 +185,8 @@ def __init__(self, uuid, path): caps = SBCAudioCodec._make_config(config) codec = dbus.Byte(A2DP_CODECS['SBC']) delayed_reporting = dbus.Boolean(True) + self.adapter_id = adapter_id + self.dev_path = dev_path self.tag = None self.path = None self.user_cb = None @@ -287,7 +303,7 @@ def _acquire_media_transport(self, path, access_type): Should be called by subclass when it is ready to acquire the media transport file descriptor """ - transport = BTMediaTransport(path=path) + transport = BTMediaTransport(path=path, adapter_id=self.adapter_id, dev_path=self.dev_path) (fd, read_mtu, write_mtu) = transport.acquire(access_type) self.fd = fd.take() # We must do the clean-up later self.write_mtu = write_mtu @@ -304,7 +320,7 @@ def _release_media_transport(self, path, access_type): try: self._uninstall_transport_ready() os.close(self.fd) # Clean-up previously taken fd - transport = BTMediaTransport(path=path) + transport = BTMediaTransport(path=path, adapter_id=self.adapter_id, dev_path=self.dev_path) transport.release(access_type) except: pass @@ -513,9 +529,14 @@ def _notify_media_transport_available(self, path, transport): """ self.source = BTAudioSource(dev_path=path) self.state = self.source.State - self.source.add_signal_receiver(self._property_change_event_handler, + if (self.source.get_version() <= BTGenericDevice.BLUEZ4_VERSION): + self.source.add_signal_receiver(self._property_change_event_handler, BTAudioSource.SIGNAL_PROPERTY_CHANGED, # noqa transport) + else: + self.source.add_signal_receiver(self._property_change_event_handler, + BTAudioSource.SIGNAL_PROPERTIES_CHANGED, # noqa + transport) class SBCAudioSource(SBCAudioCodec): @@ -555,6 +576,11 @@ def _notify_media_transport_available(self, path, transport): """ self.sink = BTAudioSink(dev_path=path) self.state = self.sink.State - self.sink.add_signal_receiver(self._property_change_event_handler, + if (self.source.get_version() <= BTGenericDevice.BLUEZ4_VERSION): + self.sink.add_signal_receiver(self._property_change_event_handler, BTAudioSource.SIGNAL_PROPERTY_CHANGED, # noqa transport) + else: + self.sink.add_signal_receiver(self._property_change_event_handler, + BTAudioSource.SIGNAL_PROPERTIES_CHANGED, # noqa + transport) diff --git a/bt_manager/control.py b/bt_manager/control.py index df79766..353cedb 100644 --- a/bt_manager/control.py +++ b/bt_manager/control.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from device import BTGenericDevice +import dbus class BTControl(BTGenericDevice): @@ -8,15 +9,57 @@ class BTControl(BTGenericDevice): SIGNAL_CONNECTED = 'Connected' SIGNAL_DISCONNECTED = 'Disconnected' + MEDIA_CONTROL_INTERFACE_BLUEZ5 = 'org.bluez.MediaControl1' def __init__(self, *args, **kwargs): - BTGenericDevice.__init__(self, addr='org.bluez.Control', - *args, **kwargs) - self._register_signal_name(BTControl.SIGNAL_CONNECTED) - self._register_signal_name(BTControl.SIGNAL_DISCONNECTED) + if (self.get_version() <= self.BLUEZ4_VERSION): + BTGenericDevice.__init__(self, addr='org.bluez.Control', + *args, **kwargs) + self._register_signal_name(BTControl.SIGNAL_CONNECTED) + self._register_signal_name(BTControl.SIGNAL_DISCONNECTED) + else: + BTGenericDevice.__init__(self, addr=self.MEDIA_CONTROL_INTERFACE_BLUEZ5, + *args, **kwargs) + self._init_properties() + + def _init_properties(self): + self._props_interface = dbus.Interface(self._object, self.DBUS_PROPERTIES) + self._properties = list(self._props_interface.GetAll(self.MEDIA_CONTROL_INTERFACE_BLUEZ5).keys()) + self._register_signal_name(self.SIGNAL_PROPERTIES_CHANGED) + + def get_property(self, name=None): + """ + Helper to get a property value by name or all + properties as a dictionary. + + See also :py:meth:`set_property` + + :param str name: defaults to None which means all properties + in the object's dictionary are returned as a dict. + Otherwise, the property name key is used and its value + is returned. + :return: Property value by property key, or a dictionary of + all properties + :raises dbus.Exception: org.bluez.Error.DoesNotExist + :raises dbus.Exception: org.bluez.Error.InvalidArguments + """ + #BlueZ 4 + if (self.get_version() <= self.BLUEZ4_VERSION): + raise Exception('Not handled with bluez 4') + + #BlueZ 5 + else: + if (name): + return self._props_interface.Get(self.DEVICE_INTERFACE_BLUEZ5, name) + else: + return self._props_interface.GetAll(self.DEVICE_INTERFACE_BLUEZ5) def is_connected(self): - return self._interface.IsConnected() + if (self.get_version() <= self.BLUEZ4_VERSION): + return self._interface.IsConnected() + else: + self.get_property('Connected') + pass def volume_up(self): """Adjust remote volume one step up""" @@ -25,3 +68,27 @@ def volume_up(self): def volume_down(self): """Adjust remote volume one step down""" self._interface.VolumeDown() + + def next(self): + """ """ + self._interface.Next() + + def previous(self): + """ """ + self._interface.Previous() + + def pause(self): + """ """ + self._interface.Pause() + + def play(self): + """ """ + self._interface.Play() + + def rewind(self): + """ """ + self._interface.Rewind() + + def fast_forward(self): + """ """ + self._interface.FastForward() diff --git a/bt_manager/device.py b/bt_manager/device.py index 27e0d73..d426826 100644 --- a/bt_manager/device.py +++ b/bt_manager/device.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals -from interface import BTInterface +import dbus +from interface import BTInterface, translate_to_dbus_type from adapter import BTAdapter from exceptions import BTDeviceNotSpecifiedException @@ -25,6 +26,9 @@ class BTGenericDevice(BTInterface): .. note:: This class should always be sub-classed with a concrete implementation of a bluez interface. """ + DEVICE_INTERFACE_BLUEZ4 = 'org.bluez.Device' + DEVICE_INTERFACE_BLUEZ5 = 'org.bluez.Device1' + def __init__(self, addr, dev_path=None, adapter_path=None, adapter_id=None, dev_id=None): if (dev_path): @@ -40,6 +44,74 @@ def __init__(self, addr, dev_path=None, adapter_path=None, raise BTDeviceNotSpecifiedException BTInterface.__init__(self, path, addr) + if (self.get_version() > self.BLUEZ4_VERSION): + self._init_properties() + + def _init_properties(self): + self._props_interface = dbus.Interface(self._object, BTInterface.DBUS_PROPERTIES) + self._properties = list(self._props_interface.GetAll(self.DEVICE_INTERFACE_BLUEZ5).keys()) + self._register_signal_name(self.SIGNAL_PROPERTIES_CHANGED) + + def get_property(self, name=None): + """ + Helper to get a property value by name or all + properties as a dictionary. + + See also :py:meth:`set_property` + + :param str name: defaults to None which means all properties + in the object's dictionary are returned as a dict. + Otherwise, the property name key is used and its value + is returned. + :return: Property value by property key, or a dictionary of + all properties + :raises KeyError: if the property key is not found in the + object's dictionary + :raises dbus.Exception: org.bluez.Error.DoesNotExist + :raises dbus.Exception: org.bluez.Error.InvalidArguments + """ + #BlueZ 4 + if (self.get_version() <= self.BLUEZ4_VERSION): + if (name): + return self._interface.GetProperties()[name] + else: + return self._interface.GetProperties() + + #BlueZ 5 + else: + if (name): + return self._props_interface.Get(self.DEVICE_INTERFACE_BLUEZ5, name) + else: + return self._props_interface.GetAll(self.DEVICE_INTERFACE_BLUEZ5) + + def set_property(self, name, value): + """ + Helper to set a property value by name, translating to correct + dbus type + + See also :py:meth:`get_property` + + :param str name: The property name in the object's dictionary + whose value shall be set. + :param value: Properties new value to be assigned. + :return: + :raises KeyError: if the property key is not found in the + object's dictionary + :raises dbus.Exception: org.bluez.Error.DoesNotExist + :raises dbus.Exception: org.bluez.Error.InvalidArguments + """ + #BlueZ 4 + if (self.get_version() <= self.BLUEZ4_VERSION): + typeof = type(self.get_property(name)) + self._interface.SetProperty(name, + translate_to_dbus_type(typeof, value)) + + #BlueZ 5 + else: + typeof = type(self.get_property(name)) + self._props_interface.Set(self.DEVICE_INTERFACE_BLUEZ5, name, + translate_to_dbus_type(typeof, value)) + class BTDevice(BTGenericDevice): """ @@ -101,17 +173,9 @@ class BTDevice(BTGenericDevice): :signal NodeRemoved(signal_name, user_arg, node_path): Signal notifying when a device node has been removed. """ - SIGNAL_PROPERTIES_CHANGED = 'PropertiesChanged' - """ - :signal PropertiesChanged(sig_name, user_arg, prop_name, prop_value): - Signal notifying when a property has changed. (Bluez 5) - """ - DEVICE_INTERFACE_BLUEZ4 = 'org.bluez.Device' - DEVICE_INTERFACE_BLUEZ5 = 'org.bluez.Device1' def __init__(self, *args, **kwargs): - self._get_version() - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): BTGenericDevice.__init__(self, addr=self.DEVICE_INTERFACE_BLUEZ4, *args, **kwargs) self._register_signal_name(BTDevice.SIGNAL_DISCONNECT_REQUESTED) @@ -122,11 +186,6 @@ def __init__(self, *args, **kwargs): BTGenericDevice.__init__(self, addr=self.DEVICE_INTERFACE_BLUEZ5, *args, **kwargs) - def _init_properties(self): - self._props_interface = dbus.Interface(self._object, BTInterface.DBUS_PROPERTIES) - self._properties = list(self._props_interface.GetAll(self.DEVICE_INTERFACE_BLUEZ5).keys()) - self._register_signal_name(self.SIGNAL_PROPERTIES_CHANGED) - def discover_services(self, pattern=''): """ This method starts the service discovery to retrieve @@ -145,7 +204,11 @@ def discover_services(self, pattern=''): :raises dbus.Exception: org.bluez.Error.Failed :raises dbus.Exception: org.bluez.Error.InProgress """ - return self._interface.DiscoverServices(pattern) + if (self.get_version() <= self.BLUEZ4_VERSION): + return self._interface.DiscoverServices(pattern) + # BlueZ 5 + else: + raise Exception('Not handled with bluez 5') def cancel_discovery(self): """ @@ -157,7 +220,11 @@ def cancel_discovery(self): :raises dbus.Exception: org.bluez.Error.Failed :raises dbus.Exception: org.bluez.Error.NotAuthorized """ - return self._interface.CancelDiscovery() + if (self.get_version() <= self.BLUEZ4_VERSION): + return self._interface.CancelDiscovery() + # BlueZ 5 + else: + raise Exception('Not handled with bluez 5') def disconnect(self): """ @@ -177,3 +244,68 @@ def disconnect(self): :raises dbus.Exception: org.bluez.Error.NotConnected """ return self._interface.Disconnect() + + def pair(self): + """ + + + :return: + :raises dbus.Exception: org.bluez.Error.NotConnected + :raises Exception: Not handled with bluez 4 + """ + if (self.get_version() > self.BLUEZ4_VERSION): + return self._interface.Pair() + else: + raise Exception('Not handled with bluez 4') + + def cancel_pairing(self): + """ + + + :return: + :raises dbus.Exception: org.bluez.Error.NotConnected + :raises Exception: Not handled with bluez 4 + """ + if (self.get_version() > self.BLUEZ4_VERSION): + return self._interface.CancelPairing() + else: + raise Exception('Not handled with bluez 4') + + def connect(self): + """ + + + :return: + :raises dbus.Exception: org.bluez.Error.NotConnected + :raises Exception: Not handled with bluez 4 + """ + if (self.get_version() > self.BLUEZ4_VERSION): + return self._interface.Connect() + else: + raise Exception('Not handled with bluez 4') + + def connect_profile(self, uuid): + """ + + :param str uuid: Profile UUID + :return: + :raises dbus.Exception: org.bluez.Error.NotConnected + :raises Exception: Not handled with bluez 4 + """ + if (self.get_version() > self.BLUEZ4_VERSION): + return self._interface.ConnectProfile(uuid) + else: + raise Exception('Not handled with bluez 4') + + def disconnect_profile(self, uuid): + """ + + :param str uuid: Profile UUID + :return: + :raises dbus.Exception: org.bluez.Error.NotConnected + :raises Exception: Not handled with bluez 4 + """ + if (self.get_version() > self.BLUEZ4_VERSION): + return self._interface.DisconnectProfile(uuid) + else: + raise Exception('Not handled with bluez 4') diff --git a/bt_manager/interface.py b/bt_manager/interface.py index 1c64f23..619fe20 100644 --- a/bt_manager/interface.py +++ b/bt_manager/interface.py @@ -67,25 +67,51 @@ class BTSimpleInterface: implementation of a bluez interface which has no signals or properties. """ + SIGNAL_INTERFACES_ADDED = 'InterfacesAdded' + """ + :signal InterfacesAdded(signal_name, user_arg, object_path): + Signal notifying when an adapter is added. + """ + SIGNAL_INTERFACES_REMOVED = 'InterfacesRemoved' + """ + :signal InterfacesRemoved(signal_name, user_arg, object_path): + Signal notifying when an adapter is added. + """ + SIGNAL_PROPERTIES_CHANGED = 'PropertiesChanged' + """ + :signal PropertiesChanged(sig_name, user_arg, prop_name, prop_value): + Signal notifying when a property has changed. (Bluez 5) + """ + SIGNAL_PROPERTY_CHANGED = 'PropertyChanged' + """ + :signal PropertyChanged(sig_name, user_arg, prop_name, prop_value): + Signal notifying when a property has changed. (Bluez 4) + """ BLUEZ_DBUS_OBJECT = 'org.bluez' + DBUS_OBJECT = 'org.freedesktop.DBus' BLUEZ4_VERSION = 1.2 def __init__(self, path, addr): self._dbus_addr = addr - self._bus = dbus.SystemBus() - self._get_version() + self._init_bus() + self.get_version() self._object = self._bus.get_object(BTSimpleInterface.BLUEZ_DBUS_OBJECT, path) self._interface = dbus.Interface(self._object, addr) self._path = path - def _get_version(self): - if (self._version is None): - self._bus = dbus.SystemBus() - dbus_infos = self._bus.get_object('org.freedesktop.DBus', '/') - interface = dbus.Interface(dbus_infos, 'org.freedesktop.DBus') + def get_version(self): + if (not hasattr(self, '_version') or self._version is None): + self._init_bus() + dbus_infos = self._bus.get_object(self.DBUS_OBJECT, '/') + interface = dbus.Interface(dbus_infos, self.DBUS_OBJECT) self._version = float(interface.GetNameOwner(BTSimpleInterface.BLUEZ_DBUS_OBJECT)[1:]) #interface.GetConnectionUnixProcessID(BTSimpleInterface.BLUEZ_DBUS_OBJECT) + return self._version + + def _init_bus(self): + if (not hasattr(self, '_bus') or self._bus is None): + self._bus = dbus.SystemBus() # This class is not intended to be instantiated directly and should be @@ -105,6 +131,7 @@ class BTInterface(BTSimpleInterface): and properties. """ DBUS_PROPERTIES = 'org.freedesktop.DBus.Properties' + DBUS_OBJ_MANAGER = 'org.freedesktop.DBus.ObjectManager' def __init__(self, path, addr): BTSimpleInterface.__init__(self, path, addr) diff --git a/bt_manager/manager.py b/bt_manager/manager.py index c0e187c..ff52171 100644 --- a/bt_manager/manager.py +++ b/bt_manager/manager.py @@ -1,6 +1,8 @@ from __future__ import unicode_literals from interface import BTInterface +from adapter import BTAdapter +from dbus import DBusException class BTManager(BTInterface): @@ -15,21 +17,11 @@ class BTManager(BTInterface): See also :py:class:`.BTAdapter` """ - SIGNAL_INTERFACES_ADDED = 'InterfacesAdded' - """ - :signal InterfacesAdded(signal_name, user_arg, object_path): - Signal notifying when an adapter is added. - """ SIGNAL_ADAPTER_ADDED = 'AdapterAdded' """ :signal AdapterAdded(signal_name, user_arg, object_path): Signal notifying when an adapter is added. """ - SIGNAL_INTERFACES_REMOVED = 'InterfacesRemoved' - """ - :signal InterfacesRemoved(signal_name, user_arg, object_path): - Signal notifying when an adapter is added. - """ SIGNAL_ADAPTER_REMOVED = 'AdapterRemoved' """ :signal AdapterRemoved(signal_name, user_arg, object_path): @@ -44,19 +36,9 @@ class BTManager(BTInterface): :signal DefaultAdapterChanged(signal_name, user_arg, object_path): Signal notifying when the default adapter has been changed. """ - SIGNAL_PROPERTY_CHANGED = 'PropertyChanged' - """ - :signal PropertyChanged(sig_name, user_arg, prop_name, prop_value): - Signal notifying when a property has changed. (Bluez 4) - """ - ADAPTER_INTERFACE_BLUEZ5 = 'org.bluez.Adapter1' - """ - @TODO remove this var, already prensent in BTAdapter - """ - def __init__(self): - self._get_version() - if (self._version <= self.BLUEZ4_VERSION): + def __init__(self, adapter_path=None, adapter_id=None): + if (self.get_version() <= self.BLUEZ4_VERSION): BTInterface.__init__(self, '/', 'org.bluez.Manager') self._register_signal_name(BTManager.SIGNAL_ADAPTER_ADDED) self._register_signal_name(BTManager.SIGNAL_ADAPTER_REMOVED) @@ -65,9 +47,17 @@ def __init__(self): self._register_signal_name(BTManager.SIGNAL_PROPERTY_CHANGED) else: - BTInterface.__init__(self, '/', 'org.freedesktop.DBus.ObjectManager') - self._register_signal_name(BTManager.SIGNAL_INTERFACES_ADDED) - self._register_signal_name(BTManager.SIGNAL_INTERFACES_REMOVED) + BTInterface.__init__(self, '/', self.DBUS_OBJ_MANAGER) + self._register_signal_name(self.SIGNAL_INTERFACES_ADDED) + self._register_signal_name(self.SIGNAL_INTERFACES_REMOVED) + + if (adapter_path is None): + if (adapter_id is None): + adapter_path = self.default_adapter() + else: + adapter_path = self.find_adapter(adapter_id) + + self._adapter = BTAdapter(adapter_path) def get_property(self, name=None): """ @@ -88,7 +78,7 @@ def get_property(self, name=None): :raises dbus.Exception: org.bluez.Error.InvalidArguments """ #BlueZ 4 - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): if (name): return self._interface.GetProperties()[name] else: @@ -113,12 +103,15 @@ def default_adapter(self): :raises dbus.Exception: org.bluez.Error.NoSuchAdapter """ #BlueZ 4 - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.DefaultAdapter() #BlueZ 5 else: - return self.list_adapters().pop() + adapters = self.list_adapters() + if (len(adapters) == 0): + raise DBusException('org.bluez.Error.NoSuchAdapter') + return adapters.pop() def find_adapter(self, pattern): """ @@ -131,15 +124,16 @@ def find_adapter(self, pattern): :raises dbus.Exception: org.bluez.Error.NoSuchAdapter """ #BlueZ 4 - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.FindAdapter(pattern) #BlueZ 5 else: for (key, object) in self._interface.GetManagedObjects().items(): - if self.ADAPTER_INTERFACE_BLUEZ5 in object: - if pattern in key or object[self.ADAPTER_INTERFACE_BLUEZ5]['Address'] == pattern: + if BTAdapter.ADAPTER_INTERFACE_BLUEZ5 in object: + if pattern in key or object[BTAdapter.ADAPTER_INTERFACE_BLUEZ5]['Address'] == pattern: return key + raise DBusException('org.bluez.Error.NoSuchAdapter') def list_adapters(self): """ @@ -152,7 +146,7 @@ def list_adapters(self): :raises dbus.Exception: org.bluez.Error.OutOfMemory """ #BlueZ 4 - if (self._version <= self.BLUEZ4_VERSION): + if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.ListAdapters() #BlueZ 5 @@ -160,6 +154,9 @@ def list_adapters(self): objects = self._interface.GetManagedObjects().items() adapters = [] for (key, object) in objects: - if self.ADAPTER_INTERFACE_BLUEZ5 in object: + if BTAdapter.ADAPTER_INTERFACE_BLUEZ5 in object: adapters.append(key) return adapters + + def get_adapter(self): + return self._adapter \ No newline at end of file diff --git a/bt_manager/media.py b/bt_manager/media.py index fd6a16b..bfcd9d8 100644 --- a/bt_manager/media.py +++ b/bt_manager/media.py @@ -34,7 +34,11 @@ def __init__(self, adapter_id=None): adapter_path = manager.default_adapter() else: adapter_path = manager.find_adapter(adapter_id) - BTSimpleInterface.__init__(self, adapter_path, 'org.bluez.Media') + + if (self.get_version() <= self.BLUEZ4_VERSION): + BTSimpleInterface.__init__(self, adapter_path, 'org.bluez.Media') + else: + BTSimpleInterface.__init__(self, adapter_path, 'org.bluez.Media1') def register_endpoint(self, path, properties): """ @@ -78,6 +82,34 @@ def unregister_endpoint(self, path): """ self._interface.UnregisterEndpoint(path) + def register_player(self, path, properties): + """ + Unregister sender end point. + + :param: str path: a freely definable path name for + registering the endpoint e.g., '/player/a2dpsource'. + :param: dict properties: a dictionary defining the + player properties which may contain. + :return: + """ + if (self.get_version() > self.BLUEZ4_VERSION): + self._interface.RegisterPlayer(path, properties) + else: + raise Exception('Not handled with bluez 4') + + def unregister_player(self, path): + """ + Unregister sender end point. + + :param: str path: a freely definable path name previously + used for registering via :py:meth:`register_player` + :return: + """ + if (self.get_version() > self.BLUEZ4_VERSION): + self._interface.UnregisterPlayer(path) + else: + raise Exception('Not handled with bluez 4') + class BTMediaTransport(BTInterface): """ @@ -139,6 +171,9 @@ class BTMediaTransport(BTInterface): * **Routing(str) [readonly]**: Optional. Indicates where is the transport being routed and may be 'HCI' or 'PCM'. """ + + MEDIA_TRANSPORT_INTERFACE_BLUEZ5 = 'org.bluez.MediaTransport1' + def __init__(self, path, fd=None, adapter_id=None, dev_path=None, dev_id=None): if (not path): @@ -148,12 +183,47 @@ def __init__(self, path, fd=None, adapter_id=None, elif (dev_id): if (adapter_id): adapter = BTAdapter(adapter_id) - else: - adapter = BTAdapter() path = adapter.find_device(dev_id) + fd_suffix else: raise BTDeviceNotSpecifiedException - BTInterface.__init__(self, path, 'org.bluez.MediaTransport') + + if (self.get_version() <= self.BLUEZ4_VERSION): + BTInterface.__init__(self, path, 'org.bluez.MediaTransport') + else: + BTInterface.__init__(self, path, self.MEDIA_TRANSPORT_INTERFACE_BLUEZ5) + self._init_properties() + + def _init_properties(self): + self._props_interface = dbus.Interface(self._object, self.DBUS_PROPERTIES) + self._properties = list(self._props_interface.GetAll(self.MEDIA_TRANSPORT_INTERFACE_BLUEZ5).keys()) + self._register_signal_name(self.SIGNAL_PROPERTIES_CHANGED) + + def get_property(self, name=None): + """ + Helper to get a property value by name or all + properties as a dictionary. + + See also :py:meth:`set_property` + + :param str name: defaults to None which means all properties + in the object's dictionary are returned as a dict. + Otherwise, the property name key is used and its value + is returned. + :return: Property value by property key, or a dictionary of + all properties + :raises dbus.Exception: org.bluez.Error.DoesNotExist + :raises dbus.Exception: org.bluez.Error.InvalidArguments + """ + #BlueZ 4 + if (self.get_version() <= self.BLUEZ4_VERSION): + raise Exception('Not handled with bluez 4') + + #BlueZ 5 + else: + if (name): + return self._props_interface.Get(self.DEVICE_INTERFACE_BLUEZ5, name) + else: + return self._props_interface.GetAll(self.DEVICE_INTERFACE_BLUEZ5) def acquire(self, access_type): """ @@ -170,6 +240,24 @@ def acquire(self, access_type): """ return self._interface.Acquire(access_type) + def try_acquire(self, access_type): + """ + Acquire transport file descriptor and the MTU for read + and/or write respectively. Possible access_type: + + * "r" : Read only access + * "w" : Write only access + * "rw": Read and write access + + :param str access_type: as defined above. + :return: A tuple of the form (fd, write_mtu, read_mtu) + :rtype: tuple + """ + if (self.get_version() > self.BLUEZ4_VERSION): + return self._interface.TryAcquire(access_type) + else: + raise Exception('Not handled with bluez 4') + def release(self, access_type): """ Releases file descriptor. @@ -184,7 +272,7 @@ def release(self, access_type): """ return self._interface.Release(access_type) - +#@TODO Change MediaEndpoint regarding BlueZ version class GenericEndpoint(dbus.service.Object): """ Generic media endpoint service object class. diff --git a/demo/demo.py b/demo/demo.py index 5197602..5c99788 100644 --- a/demo/demo.py +++ b/demo/demo.py @@ -5,7 +5,10 @@ import sys import dbus import dbus.mainloop.glib -from gi.repository import GObject +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject import signal from collections import namedtuple @@ -772,7 +775,7 @@ def timeout_handler(signum, frame): signal.setitimer(signal.ITIMER_REAL, 0.01, 0.01) try: - adapter = bt_manager.BTAdapter() + adapter = bt_manager.BTManager().get_adapter() if (adapter._version <= bt_manager.BTAdapter.BLUEZ4_VERSION): adapter.add_signal_receiver(dump_signal, bt_manager.BTAdapter.SIGNAL_DEVICE_CREATED, @@ -799,7 +802,7 @@ def timeout_handler(signum, frame): adapter.add_signal_receiver(dump_signal, bt_manager.BTAdapter.SIGNAL_PROPERTIES_CHANGED, None, - addr=BTInterface.DBUS_PROPERTIES) + addr=bt_manager.BTInterface.DBUS_PROPERTIES) except dbus.exceptions.DBusException: print 'Unable to complete:', sys.exc_info() diff --git a/demo/demo3.py b/demo/demo3.py index 7cafec4..caae9d9 100644 --- a/demo/demo3.py +++ b/demo/demo3.py @@ -535,7 +535,7 @@ def media_sbc_sink_start(args): return try: - ep = bt_manager.SBCAudioSink(path=path, dev_path=dev_path) + ep = bt_manager.SBCAudioSink(path=path) print('=========================================================') print(repr(ep)) services[path] = ep @@ -606,7 +606,7 @@ def media_sbc_source_start(args): return try: - ep = bt_manager.SBCAudioSource(path=path, dev_path=dev_path) + ep = bt_manager.SBCAudioSource(path=path) print('=========================================================') print(repr(ep)) services[path] = ep @@ -772,7 +772,8 @@ def timeout_handler(signum, frame): signal.setitimer(signal.ITIMER_REAL, 0.01, 0.01) try: - adapter = bt_manager.BTAdapter() + manager = bt_manager.BTManager() + adapter = manager.get_adapter() if (adapter._version <= bt_manager.BTAdapter.BLUEZ4_VERSION): adapter.add_signal_receiver(dump_signal, bt_manager.BTAdapter.SIGNAL_DEVICE_CREATED, @@ -790,10 +791,10 @@ def timeout_handler(signum, frame): bt_manager.BTAdapter.SIGNAL_PROPERTY_CHANGED, None) else: - adapter._manager.add_signal_receiver(dump_signal, + manager.add_signal_receiver(dump_signal, bt_manager.BTManager.SIGNAL_INTERFACES_ADDED, None) - adapter._manager.add_signal_receiver(dump_signal, + manager.add_signal_receiver(dump_signal, bt_manager.BTManager.SIGNAL_INTERFACES_REMOVED, None) adapter.add_signal_receiver(dump_signal, From 636f7e0293b79b4ac2871bbff8618b218b3c1535 Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Wed, 23 Aug 2017 07:11:12 +0200 Subject: [PATCH 08/14] Add specific bluez5 codec interface --- bt_manager/__init__.py | 1 + bt_manager/audio.py | 17 +- bt_manager/audio5.py | 458 +++++++++++++++++++++++++++++++++++++++++ bt_manager/media.py | 88 +++++++- demo/demo.py | 10 +- demo/demo3.py | 10 +- 6 files changed, 568 insertions(+), 16 deletions(-) create mode 100644 bt_manager/audio5.py diff --git a/bt_manager/__init__.py b/bt_manager/__init__.py index 835882f..eb570b2 100644 --- a/bt_manager/__init__.py +++ b/bt_manager/__init__.py @@ -23,6 +23,7 @@ from bt_manager.audio import BTAudio, BTAudioSource # noqa from bt_manager.audio import BTAudioSink, SBCAudioCodec # noqa from bt_manager.audio import SBCAudioSource, SBCAudioSink # noqa +from bt_manager.audio5 import SBCAudioSource5, SBCAudioSink5 # noqa from bt_manager.cod import BTCoD # noqa from bt_manager.codecs import * # noqa from bt_manager.control import BTControl # noqa diff --git a/bt_manager/audio.py b/bt_manager/audio.py index 16f6f26..d49ac6a 100644 --- a/bt_manager/audio.py +++ b/bt_manager/audio.py @@ -400,6 +400,7 @@ def _parse_config(config): return SBCCodecConfig(channel_mode, frequency, allocation_method, subbands, block_length, min_bitpool, max_bitpool) + # @TODO Change MediaEndpoint regarding BlueZ version @dbus.service.method("org.bluez.MediaEndpoint", in_signature="", out_signature="") def Release(self): @@ -505,9 +506,9 @@ class SBCAudioSink(SBCAudioCodec): endpoint steps """ def __init__(self, - path='/endpoint/a2dpsink'): + path='/endpoint/a2dpsink', dev_path=None): uuid = dbus.String(SERVICES['AudioSink'].uuid) - SBCAudioCodec.__init__(self, uuid, path) + SBCAudioCodec.__init__(self, uuid, path, dev_path=dev_path) def _property_change_event_handler(self, signal, transport, *args): """ @@ -534,9 +535,7 @@ def _notify_media_transport_available(self, path, transport): BTAudioSource.SIGNAL_PROPERTY_CHANGED, # noqa transport) else: - self.source.add_signal_receiver(self._property_change_event_handler, - BTAudioSource.SIGNAL_PROPERTIES_CHANGED, # noqa - transport) + raise Exception('Use audio5 for bluez5') class SBCAudioSource(SBCAudioCodec): @@ -550,9 +549,9 @@ class SBCAudioSource(SBCAudioCodec): endpoint steps """ def __init__(self, - path='/endpoint/a2dpsource'): + path='/endpoint/a2dpsource', dev_path=None): uuid = dbus.String(SERVICES['AudioSource'].uuid) - SBCAudioCodec.__init__(self, uuid, path) + SBCAudioCodec.__init__(self, uuid, path, dev_path=dev_path) def _property_change_event_handler(self, signal, transport, *args): """ @@ -581,6 +580,4 @@ def _notify_media_transport_available(self, path, transport): BTAudioSource.SIGNAL_PROPERTY_CHANGED, # noqa transport) else: - self.sink.add_signal_receiver(self._property_change_event_handler, - BTAudioSource.SIGNAL_PROPERTIES_CHANGED, # noqa - transport) + raise Exception('Use audio5 for bluez5') diff --git a/bt_manager/audio5.py b/bt_manager/audio5.py new file mode 100644 index 0000000..effce1f --- /dev/null +++ b/bt_manager/audio5.py @@ -0,0 +1,458 @@ +from __future__ import unicode_literals + +import dbus.service +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject +import pprint +import os + +from device import BTGenericDevice +from media import GenericEndpoint5, BTMediaTransport +from audio import BTAudioSink, BTAudioSource +from codecs import SBCChannelMode, SBCSamplingFrequency, \ + SBCAllocationMethod, SBCSubbands, SBCBlocks, A2DP_CODECS, \ + SBCCodecConfig, SBCCodec +from serviceuuids import SERVICES +from exceptions import BTIncompatibleTransportAccessType, \ + BTInvalidConfiguration + + +class SBCAudioCodec(GenericEndpoint5): + """ + SBCAudioCodec is an implementation of a media endpoint that + provides common functionality enabling SBC audio source and + SBC audio sink media endpoints to be established. + + Since certain procedures are specific to whether or not + the endpoint is a source or sink, in particular the trigger + points for when the media transport is acquired/release, + these parts are left to their respective sub-classes. + + SBCAudioCodec handles the following steps in establishing + an endpoint: + + * Populates `properties` with the capabilities of the codec. + * `SelectConfiguration`: computes and returns best SBC codec + configuration parameters based on device capabilities + * `SetConfiguration`: a sub-class notifier function is called + * `ClearConfiguration`: nothing is done + * `Release`: nothing + + In additional to endpoint establishment, the class also has + transport read and write functions which will handle the + required SBC media encoding/decoding and RTP encapsulation. + + The user may also register for `transport ready` events + which allows transport read and write operations to be + properly synchronized. + + See also: :py:class:`SBCAudioSink` and :py:class:`SBCAudioSource` + """ + def __init__(self, uuid, path, adapter_id=None, dev_path=None): + config = SBCCodecConfig(SBCChannelMode.ALL, + SBCSamplingFrequency.ALL, + SBCAllocationMethod.ALL, + SBCSubbands.ALL, + SBCBlocks.ALL, + 2, + 64) + caps = SBCAudioCodec._make_config(config) + codec = dbus.Byte(A2DP_CODECS['SBC']) + delayed_reporting = dbus.Boolean(True) + self.adapter_id = adapter_id + self.dev_path = dev_path + self.tag = None + self.path = None + self.user_cb = None + self.user_arg = None + self.properties = dbus.Dictionary({'UUID': uuid, + 'Codec': codec, + 'DelayReporting': delayed_reporting, + 'Capabilities': caps}) + GenericEndpoint5.__init__(self, path) + + def _transport_ready_handler(self, fd, cb_condition): + """ + Wrapper for calling user callback routine to notify + when transport data is ready to read + """ + if(self.user_cb): + self.user_cb(self.user_arg) + return True + + def _install_transport_ready(self): + if ('r' in self.access_type): + io_event = GObject.IO_IN + else: + io_event = GObject.IO_OUT + + self.tag = GObject.io_add_watch(self.fd, io_event, + self._transport_ready_handler) + + def _uninstall_transport_ready(self): + if (self.tag): + GObject.source_remove(self.tag) + self.tag = None + + def register_transport_ready_event(self, user_cb, user_arg): + """ + Register for transport ready events. The `transport ready` + event is raised via a user callback. If the endpoint + is configured as a source, then the user may then + call :py:meth:`write_transport` in order to send data to + the associated sink. + Otherwise, if the endpoint is configured as a sink, then + the user may call :py:meth:`read_transport` to read + from the associated source instead. + + :param func user_cb: User defined callback function. It + must take one parameter which is the user's callback + argument. + :param user_arg: User defined callback argument. + :return: + + See also: :py:meth:`unregister_transport_ready_event` + """ + self.user_cb = user_cb + self.user_arg = user_arg + + def unregister_transport_ready_event(self): + """ + Unregister previously registered `transport ready` + events. + + See also: :py:meth:`register_transport_ready_event` + """ + self.user_cb = None + + def read_transport(self): + """ + Read data from media transport. + The returned data payload is SBC decoded and has + all RTP encapsulation removed. + + :return data: Payload data that has been decoded, + with RTP encapsulation removed. + :rtype: array{byte} + """ + if ('r' not in self.access_type): + raise BTIncompatibleTransportAccessType + return self.codec.decode(self.fd, self.read_mtu) + + def write_transport(self, data): + """ + Write data to media transport. The data is + encoded using the SBC codec and RTP encapsulated + before being written to the transport file + descriptor. + + :param array{byte} data: Payload data to encode, + encapsulate and send. + """ + if ('w' not in self.access_type): + raise BTIncompatibleTransportAccessType + return self.codec.encode(self.fd, self.write_mtu, data) + + def close_transport(self): + """ + Forcibly close previously acquired media transport. + + .. note:: The user should first make sure any transport + event handlers are unregistered first. + """ + if (self.path): + self._release_media_transport(self.path, + self.access_type) + self.path = None + + def _notify_media_transport_available(self, path, transport): + """ + Subclass should implement this to trigger setup once + a new media transport is available. + """ + pass + + def _acquire_media_transport(self, path, access_type): + """ + Should be called by subclass when it is ready + to acquire the media transport file descriptor + """ + transport = BTMediaTransport(path=path, adapter_id=self.adapter_id, dev_path=self.dev_path) + (fd, read_mtu, write_mtu) = transport.acquire(access_type) + self.fd = fd.take() # We must do the clean-up later + self.write_mtu = write_mtu + self.read_mtu = read_mtu + self.access_type = access_type + self.path = path + self._install_transport_ready() + + def _release_media_transport(self, path, access_type): + """ + Should be called by subclass when it is finished + with the media transport file descriptor + """ + try: + self._uninstall_transport_ready() + os.close(self.fd) # Clean-up previously taken fd + transport = BTMediaTransport(path=path, adapter_id=self.adapter_id, dev_path=self.dev_path) + transport.release(access_type) + except: + pass + + @staticmethod + def _default_bitpool(frequency, channel_mode): + if (frequency == + SBCSamplingFrequency.FREQ_16KHZ or + frequency == + SBCSamplingFrequency.FREQ_32KHZ): + return 53 + elif (frequency == + SBCSamplingFrequency.FREQ_44_1KHZ): + if (channel_mode == + SBCChannelMode.CHANNEL_MODE_MONO or + channel_mode == + SBCChannelMode.CHANNEL_MODE_DUAL): + return 31 + elif (channel_mode == + SBCChannelMode.CHANNEL_MODE_STEREO or + channel_mode == + SBCChannelMode.CHANNEL_MODE_JOINT_STEREO): + return 53 + else: + # TODO: Invalid channel_mode + return 53 + elif (frequency == SBCSamplingFrequency.FREQ_48KHZ): + if (channel_mode == + SBCChannelMode.CHANNEL_MODE_MONO or + channel_mode == + SBCChannelMode.CHANNEL_MODE_DUAL): + return 29 + elif (channel_mode == + SBCChannelMode.CHANNEL_MODE_STEREO or + channel_mode == + SBCChannelMode.CHANNEL_MODE_JOINT_STEREO): + return 51 + else: + # TODO: Invalid channel_mode + return 51 + else: + # TODO: Invalid frequency + return 53 + + @staticmethod + def _make_config(config): + """Helper to turn SBC codec configuration params into a + a2dp_sbc_t structure usable by bluez""" + # The SBC config encoding is taken from a2dp_codecs.h, in particular, + # the a2dp_sbc_t type is converted into a 4-byte array: + # uint8_t channel_mode:4 + # uint8_t frequency:4 + # uint8_t allocation_method:2 + # uint8_t subbands:2 + # uint8_t block_length:4 + # uint8_t min_bitpool + # uint8_t max_bitpool + return dbus.Array([dbus.Byte(config.channel_mode | + (config.frequency << 4)), + dbus.Byte(config.allocation_method | + (config.subbands << 2) | + (config.block_length << 4)), + dbus.Byte(config.min_bitpool), + dbus.Byte(config.max_bitpool)]) + + @staticmethod + def _parse_config(config): + """Helper to turn a2dp_sbc_t structure into a + more usable set of SBC codec configuration params""" + frequency = config[0] >> 4 + channel_mode = config[0] & 0xF + allocation_method = config[1] & 0x03 + subbands = (config[1] >> 2) & 0x03 + block_length = (config[1] >> 4) & 0x0F + min_bitpool = config[2] + max_bitpool = config[3] + return SBCCodecConfig(channel_mode, frequency, allocation_method, + subbands, block_length, min_bitpool, max_bitpool) + + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="", out_signature="") + def Release(self): + pass + + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="", out_signature="") + def ClearConfiguration(self): + pass + + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="ay", out_signature="ay") + def SelectConfiguration(self, caps): + our_caps = SBCAudioCodec._parse_config(self.properties['Capabilities']) + device_caps = SBCAudioCodec._parse_config(caps) + frequency = SBCSamplingFrequency.FREQ_44_1KHZ + + if ((our_caps.channel_mode & device_caps.channel_mode) & + SBCChannelMode.CHANNEL_MODE_JOINT_STEREO): + channel_mode = SBCChannelMode.CHANNEL_MODE_JOINT_STEREO + elif ((our_caps.channel_mode & device_caps.channel_mode) & + SBCChannelMode.CHANNEL_MODE_STEREO): + channel_mode = SBCChannelMode.CHANNEL_MODE_STEREO + elif ((our_caps.channel_mode & device_caps.channel_mode) & + SBCChannelMode.CHANNEL_MODE_DUAL): + channel_mode = SBCChannelMode.CHANNEL_MODE_DUAL + elif ((our_caps.channel_mode & device_caps.channel_mode) & + SBCChannelMode.CHANNEL_MODE_MONO): + channel_mode = SBCChannelMode.CHANNEL_MODE_MONO + else: + raise BTInvalidConfiguration + + if ((our_caps.block_length & device_caps.block_length) & + SBCBlocks.BLOCKS_16): + block_length = SBCBlocks.BLOCKS_16 + elif ((our_caps.block_length & device_caps.block_length) & + SBCBlocks.BLOCKS_12): + block_length = SBCBlocks.BLOCKS_12 + elif ((our_caps.block_length & device_caps.block_length) & + SBCBlocks.BLOCKS_8): + block_length = SBCBlocks.BLOCKS_8 + elif ((our_caps.block_length & device_caps.block_length) & + SBCBlocks.BLOCKS_4): + block_length = SBCBlocks.BLOCKS_4 + else: + raise BTInvalidConfiguration + + if ((our_caps.subbands & device_caps.subbands) & + SBCSubbands.SUBBANDS_8): + subbands = SBCSubbands.SUBBANDS_8 + elif ((our_caps.subbands & device_caps.subbands) & + SBCSubbands.SUBBANDS_4): + subbands = SBCSubbands.SUBBANDS_4 + else: + raise BTInvalidConfiguration + + if ((our_caps.allocation_method & device_caps.allocation_method) & + SBCAllocationMethod.LOUDNESS): + allocation_method = SBCAllocationMethod.LOUDNESS + elif ((our_caps.allocation_method & device_caps.allocation_method) & + SBCAllocationMethod.SNR): + allocation_method = SBCAllocationMethod.SNR + else: + raise BTInvalidConfiguration + + min_bitpool = max(our_caps.min_bitpool, device_caps.min_bitpool) + max_bitpool = min(SBCAudioCodec._default_bitpool(frequency, + channel_mode), + device_caps.max_bitpool) + + selected_config = SBCCodecConfig(channel_mode, + frequency, + allocation_method, + subbands, + block_length, + min_bitpool, + max_bitpool) + + # Create SBC codec based on selected configuration + self.codec = SBCCodec(selected_config) + + dbus_val = SBCAudioCodec._make_config(selected_config) + return dbus_val + + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="oay", out_signature="") + def SetConfiguration(self, transport, config): + self._notify_media_transport_available(config.get('Device'), transport) + + def __repr__(self): + return pprint.pformat(self.__dict__) + + +class SBCAudioSink5(SBCAudioCodec): + """ + SBC audio sink media endpoint + + SBCAudioSink implies the BT adapter takes on the role of + a sink and the external device is the source e.g., + iPhone, media player. + + Refer to :py:class:`SBCAudioCodec` for basic overview of + endpoint steps + """ + def __init__(self, + path='/endpoint/a2dpsink', dev_path=None): + uuid = dbus.String(SERVICES['AudioSink'].uuid) + SBCAudioCodec.__init__(self, uuid, path, dev_path=dev_path) + + def _property_change_event_handler(self, signal, transport, *args): + """ + Handler for property change event. We catch certain state + transitions in order to trigger media transport + acquisition/release + """ + current_state = self.source.State + if (self.state == 'connected' and current_state == 'playing'): + self._acquire_media_transport(transport, 'r') + elif (self.state == 'playing' and current_state == 'connected'): + self._release_media_transport(transport, 'r') + self.state = current_state + + def _notify_media_transport_available(self, path, transport): + """ + Called by the endpoint when a new media transport is + available + """ + self.source = BTAudioSource(dev_path=path) + self.state = self.source.State + if (self.source.get_version() <= BTGenericDevice.BLUEZ4_VERSION): + raise Exception('Use audio5 for bluez5 only') + else: + self.source.add_signal_receiver(self._property_change_event_handler, + BTAudioSource.SIGNAL_PROPERTIES_CHANGED, # noqa + transport) + + +class SBCAudioSource5(SBCAudioCodec): + """ + SBC audio source media endpoint. + + SBCAudioSource implies the adapter takes on the role of + source and the external device is the sink e.g., speaker. + + Refer to :py:class:`SBCAudioCodec` for basic overview of + endpoint steps + """ + def __init__(self, + path='/endpoint/a2dpsource', dev_path=None): + uuid = dbus.String(SERVICES['AudioSource'].uuid) + SBCAudioCodec.__init__(self, uuid, path, dev_path=dev_path) + + def _property_change_event_handler(self, signal, transport, *args): + """ + Handler for property change event. We catch certain state + transitions in order to trigger media transport + acquisition/release + """ + current_state = self.sink.State + if ((self.state == 'disconnected' and current_state == 'connected') or + (self.state == 'connecting' and + current_state == 'connected')): + self._acquire_media_transport(transport, 'w') + elif (self.state == 'connected' and current_state == 'disconnected'): + self._release_media_transport(transport, 'w') + self.state = current_state + + def _notify_media_transport_available(self, path, transport): + """ + Called by the endpoint when a new media transport is + available + """ + self.sink = BTAudioSink(dev_path=path) + self.state = self.sink.State + if (self.source.get_version() <= BTGenericDevice.BLUEZ4_VERSION): + raise Exception('Use audio5 for bluez5 only') + else: + self.sink.add_signal_receiver(self._property_change_event_handler, + BTAudioSource.SIGNAL_PROPERTIES_CHANGED, # noqa + transport) diff --git a/bt_manager/media.py b/bt_manager/media.py index bfcd9d8..b5d986e 100644 --- a/bt_manager/media.py +++ b/bt_manager/media.py @@ -272,10 +272,9 @@ def release(self, access_type): """ return self._interface.Release(access_type) -#@TODO Change MediaEndpoint regarding BlueZ version class GenericEndpoint(dbus.service.Object): """ - Generic media endpoint service object class. + Generic media endpoint service object class for BlueZ 4. .. note:: GenericEndpoint can't be directly instantiated. It should be sub-classed and provides a template for @@ -356,3 +355,88 @@ def SelectConfiguration(self, caps): :rtype: array{byte} """ pass + + +class GenericEndpoint5(dbus.service.Object): + """ + Generic media endpoint service object class for BlueZ 5. + + .. note:: GenericEndpoint can't be directly instantiated. + It should be sub-classed and provides a template for + implementing an endpoint service object. + + :param str path: Freely definable object path for the + media endpoint e.g., '/endpoint/a2dpsink'. + """ + def __init__(self, path): + bus = dbus.SystemBus() + super(GenericEndpoint5, self).__init__(bus, path) + + def get_properties(self): + """ + Returns the properties of the endpoint. These should + be initialized by a suitable subclass implementation + by setting the `properties` class attribute. + + :return properties: dictionary of endpoint's capabilties + :rtype: dict + """ + return self.properties + + # Service object entry points defined below here -- + # you will need to implement these in your subclass + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="", out_signature="") + def Release(self): + """ + Called by bluez to let us know our registration + has been released and the endpoint no longer exists + + :return: + """ + pass + + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="", out_signature="") + def ClearConfiguration(self): + """ + Called by bluez to let us know that the audio + streaming process has been reset, for whatever reason, + and we should now perform clean-up. + + :return: + """ + pass + + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="oay", out_signature="") + def SetConfiguration(self, transport, config): + """ + Provides a path to the media transport to use and + the active configuration that was negotiated. + + :param str transport: Path to a device's file descriptor node + which can be used to acquire the media transport e.g., + '/org/bluez/985/hci0/dev_00_11_67_D2_AB_EE/fd0' + :param array{byte} config: The configuration being used for + the media transport. + + See also: :py:class:`.BTMediaTransport` + """ + pass + + @dbus.service.method("org.bluez.MediaEndpoint1", + in_signature="ay", out_signature="ay") + def SelectConfiguration(self, caps): + """ + Initiates negotiations of the capabilities which should + be resolved by this method. + + :param dict caps: Dictionary of device's capabilities + for resolving capability negotiation by comparing + to own capabilities. + :return config: The resolved configuration + to be used for the media transport. + :rtype: array{byte} + """ + pass \ No newline at end of file diff --git a/demo/demo.py b/demo/demo.py index 5c99788..0bbba04 100644 --- a/demo/demo.py +++ b/demo/demo.py @@ -538,7 +538,10 @@ def media_sbc_sink_start(args): return try: - ep = bt_manager.SBCAudioSink(path=path, dev_path=dev_path) + if (bt_manager.BTManager().get_version() <= bt_manager.BTAdapter.BLUEZ4_VERSION): + ep = bt_manager.SBCAudioSink(path=path) + else: + ep = bt_manager.SBCAudioSink5(path=path) print '=========================================================' print repr(ep) services[path] = ep @@ -609,7 +612,10 @@ def media_sbc_source_start(args): return try: - ep = bt_manager.SBCAudioSource(path=path, dev_path=dev_path) + if (bt_manager.BTManager().get_version() <= bt_manager.BTAdapter.BLUEZ4_VERSION): + ep = bt_manager.SBCAudioSource(path=path) + else: + ep = bt_manager.SBCAudioSource5(path=path) print '=========================================================' print repr(ep) services[path] = ep diff --git a/demo/demo3.py b/demo/demo3.py index caae9d9..f536401 100644 --- a/demo/demo3.py +++ b/demo/demo3.py @@ -535,7 +535,10 @@ def media_sbc_sink_start(args): return try: - ep = bt_manager.SBCAudioSink(path=path) + if (bt_manager.BTManager().get_version() <= bt_manager.BTAdapter.BLUEZ4_VERSION): + ep = bt_manager.SBCAudioSink(path=path) + else: + ep = bt_manager.SBCAudioSink5(path=path) print('=========================================================') print(repr(ep)) services[path] = ep @@ -606,7 +609,10 @@ def media_sbc_source_start(args): return try: - ep = bt_manager.SBCAudioSource(path=path) + if (bt_manager.BTManager().get_version() <= bt_manager.BTAdapter.BLUEZ4_VERSION): + ep = bt_manager.SBCAudioSource(path=path) + else: + ep = bt_manager.SBCAudioSource5(path=path) print('=========================================================') print(repr(ep)) services[path] = ep From 788823d86dc642bde7443d976daa55980b42e1d5 Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Tue, 29 Aug 2017 01:36:36 +0200 Subject: [PATCH 09/14] Add logs --- bt_manager/audio5.py | 4 ++++ bt_manager/media.py | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/bt_manager/audio5.py b/bt_manager/audio5.py index effce1f..d498d93 100644 --- a/bt_manager/audio5.py +++ b/bt_manager/audio5.py @@ -279,16 +279,19 @@ def _parse_config(config): @dbus.service.method("org.bluez.MediaEndpoint1", in_signature="", out_signature="") def Release(self): + print('Callback Release') pass @dbus.service.method("org.bluez.MediaEndpoint1", in_signature="", out_signature="") def ClearConfiguration(self): + print('Callback ClearConfiguration') pass @dbus.service.method("org.bluez.MediaEndpoint1", in_signature="ay", out_signature="ay") def SelectConfiguration(self, caps): + print('Callback SelectConfiguration') our_caps = SBCAudioCodec._parse_config(self.properties['Capabilities']) device_caps = SBCAudioCodec._parse_config(caps) frequency = SBCSamplingFrequency.FREQ_44_1KHZ @@ -363,6 +366,7 @@ def SelectConfiguration(self, caps): @dbus.service.method("org.bluez.MediaEndpoint1", in_signature="oay", out_signature="") def SetConfiguration(self, transport, config): + print('Callback SetConfiguration') self._notify_media_transport_available(config.get('Device'), transport) def __repr__(self): diff --git a/bt_manager/media.py b/bt_manager/media.py index b5d986e..7bbdd39 100644 --- a/bt_manager/media.py +++ b/bt_manager/media.py @@ -394,6 +394,7 @@ def Release(self): :return: """ + print('Callback Release') pass @dbus.service.method("org.bluez.MediaEndpoint1", @@ -406,6 +407,7 @@ def ClearConfiguration(self): :return: """ + print('Callback ClearConfiguration') pass @dbus.service.method("org.bluez.MediaEndpoint1", @@ -423,6 +425,7 @@ def SetConfiguration(self, transport, config): See also: :py:class:`.BTMediaTransport` """ + print('Callback SetConfiguration') pass @dbus.service.method("org.bluez.MediaEndpoint1", @@ -439,4 +442,5 @@ def SelectConfiguration(self, caps): to be used for the media transport. :rtype: array{byte} """ + print('Callback SelectConfiguration') pass \ No newline at end of file From cd2ba1891ed19181aaa037aa76f154f7704dd78d Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Wed, 30 Aug 2017 07:50:15 +0200 Subject: [PATCH 10/14] Filter endpoint connections --- bt_manager/__init__.py | 10 +++++----- bt_manager/audio.py | 7 +++++++ bt_manager/audio5.py | 34 +++++++++++++++++++++++----------- bt_manager/codecs.py | 20 +++++++++++--------- bt_manager/interface.py | 27 ++++++++++++++++++--------- 5 files changed, 64 insertions(+), 34 deletions(-) diff --git a/bt_manager/__init__.py b/bt_manager/__init__.py index eb570b2..bbbddae 100644 --- a/bt_manager/__init__.py +++ b/bt_manager/__init__.py @@ -18,12 +18,7 @@ fh.close() from bt_manager.adapter import BTAdapter # noqa -from bt_manager.agent import BTAgent # noqa from bt_manager.attributes import ATTRIBUTES # noqa -from bt_manager.audio import BTAudio, BTAudioSource # noqa -from bt_manager.audio import BTAudioSink, SBCAudioCodec # noqa -from bt_manager.audio import SBCAudioSource, SBCAudioSink # noqa -from bt_manager.audio5 import SBCAudioSource5, SBCAudioSink5 # noqa from bt_manager.cod import BTCoD # noqa from bt_manager.codecs import * # noqa from bt_manager.control import BTControl # noqa @@ -41,3 +36,8 @@ from bt_manager.uuid import BTUUID, BTUUID16, BTUUID32 # noqa from bt_manager.uuid import BASE_UUID # noqa from bt_manager.vendors import VENDORS # noqa +from bt_manager.audio import BTAudio, BTAudioSource, BTAudioSink # noqa +if (BTSimpleInterface.get_version() < BTSimpleInterface.BLUEZ4_VERSION): + from bt_manager.audio import SBCAudioSource, SBCAudioSink, SBCAudioCodec # noqa +from bt_manager.audio5 import SBCAudioSource5, SBCAudioSink5, SBCAudioCodec # noqa +from bt_manager.agent import BTAgent # noqa diff --git a/bt_manager/audio.py b/bt_manager/audio.py index d49ac6a..3cf6fd7 100644 --- a/bt_manager/audio.py +++ b/bt_manager/audio.py @@ -7,6 +7,7 @@ import gobject as GObject import pprint import os +from syslog import syslog, LOG_INFO from device import BTGenericDevice from media import GenericEndpoint, BTMediaTransport @@ -528,6 +529,9 @@ def _notify_media_transport_available(self, path, transport): Called by the endpoint when a new media transport is available """ + if (self.dev_path != None and path != self.dev_path): + syslog(LOG_INFO, 'Ignored device ' + path + ', different as specified') + return self.source = BTAudioSource(dev_path=path) self.state = self.source.State if (self.source.get_version() <= BTGenericDevice.BLUEZ4_VERSION): @@ -573,6 +577,9 @@ def _notify_media_transport_available(self, path, transport): Called by the endpoint when a new media transport is available """ + if (self.dev_path != None and path != self.dev_path): + syslog(LOG_INFO, 'Ignored device ' + path + ', different as specified') + return self.sink = BTAudioSink(dev_path=path) self.state = self.sink.State if (self.source.get_version() <= BTGenericDevice.BLUEZ4_VERSION): diff --git a/bt_manager/audio5.py b/bt_manager/audio5.py index d498d93..f37b4eb 100644 --- a/bt_manager/audio5.py +++ b/bt_manager/audio5.py @@ -7,6 +7,7 @@ import gobject as GObject import pprint import os +from syslog import syslog, LOG_INFO, LOG_DEBUG from device import BTGenericDevice from media import GenericEndpoint5, BTMediaTransport @@ -167,7 +168,7 @@ def close_transport(self): self.access_type) self.path = None - def _notify_media_transport_available(self, path, transport): + def _notify_media_transport_available(self, path, state, transport): """ Subclass should implement this to trigger setup once a new media transport is available. @@ -279,19 +280,19 @@ def _parse_config(config): @dbus.service.method("org.bluez.MediaEndpoint1", in_signature="", out_signature="") def Release(self): - print('Callback Release') + syslog(LOG_DEBUG, 'Callback Release') pass @dbus.service.method("org.bluez.MediaEndpoint1", in_signature="", out_signature="") def ClearConfiguration(self): - print('Callback ClearConfiguration') + syslog(LOG_DEBUG, 'Callback ClearConfiguration') pass @dbus.service.method("org.bluez.MediaEndpoint1", in_signature="ay", out_signature="ay") def SelectConfiguration(self, caps): - print('Callback SelectConfiguration') + syslog(LOG_DEBUG, 'Callback SelectConfiguration') our_caps = SBCAudioCodec._parse_config(self.properties['Capabilities']) device_caps = SBCAudioCodec._parse_config(caps) frequency = SBCSamplingFrequency.FREQ_44_1KHZ @@ -361,13 +362,18 @@ def SelectConfiguration(self, caps): self.codec = SBCCodec(selected_config) dbus_val = SBCAudioCodec._make_config(selected_config) + syslog(LOG_DEBUG, 'Returned config') + syslog(LOG_DEBUG, repr(dbus_val)) return dbus_val @dbus.service.method("org.bluez.MediaEndpoint1", in_signature="oay", out_signature="") def SetConfiguration(self, transport, config): - print('Callback SetConfiguration') - self._notify_media_transport_available(config.get('Device'), transport) + syslog(LOG_DEBUG, 'Callback SetConfiguration') + syslog(LOG_DEBUG, repr(transport)) + syslog(LOG_DEBUG, repr(config)) + self._notify_media_transport_available(config.get('Device'), config.get('State'), transport) + return def __repr__(self): return pprint.pformat(self.__dict__) @@ -402,13 +408,16 @@ def _property_change_event_handler(self, signal, transport, *args): self._release_media_transport(transport, 'r') self.state = current_state - def _notify_media_transport_available(self, path, transport): + def _notify_media_transport_available(self, path, state, transport): """ Called by the endpoint when a new media transport is available """ + if (self.dev_path != None and path != self.dev_path): + syslog(LOG_INFO, 'Ignored device ' + path + ', different as specified') + return self.source = BTAudioSource(dev_path=path) - self.state = self.source.State + self.state = state if (self.source.get_version() <= BTGenericDevice.BLUEZ4_VERSION): raise Exception('Use audio5 for bluez5 only') else: @@ -447,14 +456,17 @@ def _property_change_event_handler(self, signal, transport, *args): self._release_media_transport(transport, 'w') self.state = current_state - def _notify_media_transport_available(self, path, transport): + def _notify_media_transport_available(self, path, state, transport): """ Called by the endpoint when a new media transport is available """ + if (self.dev_path != None and path != self.dev_path): + syslog(LOG_INFO, 'Ignored device ' + path + ', different as specified') + return self.sink = BTAudioSink(dev_path=path) - self.state = self.sink.State - if (self.source.get_version() <= BTGenericDevice.BLUEZ4_VERSION): + self.state = state + if (self.sink.get_version() <= BTGenericDevice.BLUEZ4_VERSION): raise Exception('Use audio5 for bluez5 only') else: self.sink.add_signal_receiver(self._property_change_event_handler, diff --git a/bt_manager/codecs.py b/bt_manager/codecs.py index 477a450..48ead32 100644 --- a/bt_manager/codecs.py +++ b/bt_manager/codecs.py @@ -96,17 +96,19 @@ def __init__(self, config): import sys try: - self.codec = ffi.verify(b'#include "rtpsbc.h"', - libraries=[b'rtpsbc'], - ext_package=b'rtpsbc') + self.codec = ffi.verify('#include "rtpsbc.h"', + libraries=['rtpsbc'], + ext_package='rtpsbc', + tmpdir='./__pycache__') + + self.config = ffi.new('sbc_t *') + self.ts = ffi.new('unsigned int *', 0) + self.seq_num = ffi.new('unsigned int *', 0) + self._init_sbc_config(config) + self.codec.sbc_init(self.config, 0) except: print 'Exception:', sys.exc_info()[0] - - self.config = ffi.new('sbc_t *') - self.ts = ffi.new('unsigned int *', 0) - self.seq_num = ffi.new('unsigned int *', 0) - self._init_sbc_config(config) - self.codec.sbc_init(self.config, 0) + print 'Have you install codecs ? (cd codecs && sudo make install)' def _init_sbc_config(self, config): """ diff --git a/bt_manager/interface.py b/bt_manager/interface.py index 619fe20..3684eed 100644 --- a/bt_manager/interface.py +++ b/bt_manager/interface.py @@ -90,7 +90,8 @@ class BTSimpleInterface: BLUEZ_DBUS_OBJECT = 'org.bluez' DBUS_OBJECT = 'org.freedesktop.DBus' - BLUEZ4_VERSION = 1.2 + #BLUEZ4_VERSION = 1.2 + BLUEZ4_VERSION = 4 def __init__(self, path, addr): self._dbus_addr = addr @@ -100,14 +101,22 @@ def __init__(self, path, addr): self._interface = dbus.Interface(self._object, addr) self._path = path - def get_version(self): - if (not hasattr(self, '_version') or self._version is None): - self._init_bus() - dbus_infos = self._bus.get_object(self.DBUS_OBJECT, '/') - interface = dbus.Interface(dbus_infos, self.DBUS_OBJECT) - self._version = float(interface.GetNameOwner(BTSimpleInterface.BLUEZ_DBUS_OBJECT)[1:]) - #interface.GetConnectionUnixProcessID(BTSimpleInterface.BLUEZ_DBUS_OBJECT) - return self._version + @staticmethod + def get_version(): + if (not hasattr(BTSimpleInterface, '_version') or BTSimpleInterface._version is None): + from subprocess import Popen, PIPE + from psutil import Process + + dbus_infos = dbus.SystemBus().get_object(BTSimpleInterface.DBUS_OBJECT, '/') + interface = dbus.Interface(dbus_infos, BTSimpleInterface.DBUS_OBJECT) + #BTSimpleInterface._version = float(interface.GetNameOwner(BTSimpleInterface.BLUEZ_DBUS_OBJECT)[1:]) + pid = int(interface.GetConnectionUnixProcessID(BTSimpleInterface.BLUEZ_DBUS_OBJECT)) + + daemon = Process(pid) + #daemon.cwd() should contain "bluetoothd" + cmd = Popen([daemon.exe(),"--version"], stdout=PIPE) + BTSimpleInterface._version = float(cmd.stdout.read()) + return BTSimpleInterface._version def _init_bus(self): if (not hasattr(self, '_bus') or self._bus is None): From 19ab1cd198de591cfb3c9f01e32b4590a48b45d4 Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Fri, 1 Sep 2017 02:21:08 +0200 Subject: [PATCH 11/14] Update dependancies --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 346ad58..718a5d4 100644 --- a/setup.py +++ b/setup.py @@ -26,6 +26,7 @@ def get_version(filename): install_requires=[ 'setuptools', 'cffi >= 0.7', + 'psutil' ], setup_requires=['cffi >= 0.7'], test_suite='nose.collector', From bba8c5478bdeaa47acb072ae3c64e84ce5a756e6 Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Fri, 1 Sep 2017 09:12:28 +0200 Subject: [PATCH 12/14] Remove version test --- bt_manager/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bt_manager/__init__.py b/bt_manager/__init__.py index bbbddae..9d44a11 100644 --- a/bt_manager/__init__.py +++ b/bt_manager/__init__.py @@ -37,7 +37,6 @@ from bt_manager.uuid import BASE_UUID # noqa from bt_manager.vendors import VENDORS # noqa from bt_manager.audio import BTAudio, BTAudioSource, BTAudioSink # noqa -if (BTSimpleInterface.get_version() < BTSimpleInterface.BLUEZ4_VERSION): - from bt_manager.audio import SBCAudioSource, SBCAudioSink, SBCAudioCodec # noqa +from bt_manager.audio import SBCAudioSource, SBCAudioSink, SBCAudioCodec # noqa from bt_manager.audio5 import SBCAudioSource5, SBCAudioSink5, SBCAudioCodec # noqa from bt_manager.agent import BTAgent # noqa From 4d0c1885819f0472b22c6c5083e542adca409877 Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Fri, 1 Sep 2017 23:56:25 +0200 Subject: [PATCH 13/14] Fix code style --- bt_manager/adapter.py | 40 ++++++++++++++++++++++------------------ bt_manager/audio.py | 10 +++++----- bt_manager/audio5.py | 8 ++++---- bt_manager/control.py | 10 ++++++---- bt_manager/device.py | 22 ++++++++++++---------- bt_manager/interface.py | 9 ++++----- bt_manager/manager.py | 18 +++++++++--------- bt_manager/media.py | 7 ++++--- demo/demo.py | 4 ++-- demo/demo3.py | 13 ++++++++----- 10 files changed, 76 insertions(+), 65 deletions(-) diff --git a/bt_manager/adapter.py b/bt_manager/adapter.py index 701da9e..f1fedcf 100644 --- a/bt_manager/adapter.py +++ b/bt_manager/adapter.py @@ -167,14 +167,14 @@ def get_property(self, name=None): :raises dbus.Exception: org.bluez.Error.DoesNotExist :raises dbus.Exception: org.bluez.Error.InvalidArguments """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): if (name): return self._interface.GetProperties()[name] else: return self._interface.GetProperties() - #BlueZ 5 + # BlueZ 5 else: if (name): return self._props_interface.Get(self.ADAPTER_INTERFACE_BLUEZ5, name) @@ -197,17 +197,21 @@ def set_property(self, name, value): :raises dbus.Exception: org.bluez.Error.DoesNotExist :raises dbus.Exception: org.bluez.Error.InvalidArguments """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): typeof = type(self.get_property(name)) - self._interface.SetProperty(name, - translate_to_dbus_type(typeof, value)) + self._interface.SetProperty( + name, + translate_to_dbus_type(typeof, value) + ) - #BlueZ 5 + # BlueZ 5 else: typeof = type(self.get_property(name)) - self._props_interface.Set(BTAdapter.ADAPTER_INTERFACE_BLUEZ5, name, - translate_to_dbus_type(typeof, value)) + self._props_interface.Set( + BTAdapter.ADAPTER_INTERFACE_BLUEZ5, name, + translate_to_dbus_type(typeof, value) + ) def start_discovery(self): """ @@ -256,10 +260,10 @@ def find_device(self, dev_id, property='Address'): :raises dbus.Exception: org.bluez.Error.DoesNotExist :raises dbus.Exception: org.bluez.Error.InvalidArguments """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.FindDevice(dev_id) - #BlueZ 5 + # BlueZ 5 else: for (key, object) in self._ojects_interface.GetManagedObjects().items(): if self.DEVICE_INTERFACE_BLUEZ5 in object: @@ -277,10 +281,10 @@ def list_devices(self): :raises dbus.Exception: org.bluez.Error.Failed :raises dbus.Exception: org.bluez.Error.OutOfMemory """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.ListDevices() - #BlueZ 5 + # BlueZ 5 else: objects = self._ojects_interface.GetManagedObjects().items() devices = [] @@ -326,14 +330,14 @@ def create_paired_device(self, dev_id, agent_path, :raises dbus.Exception: org.bluez.Error.InvalidArguments :raises dbus.Exception: org.bluez.Error.Failed """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.CreatePairedDevice(dev_id, agent_path, capability, reply_handler=cb_notify_device, # noqa error_handler=cb_notify_error) # noqa - #BlueZ 5 TODO !? + # BlueZ 5 TODO !? else: pass @@ -369,10 +373,10 @@ def register_agent(self, path, capability): :raises dbus.Exception: org.bluez.Error.InvalidArguments :raises dbus.Exception: org.bluez.Error.AlreadyExists """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.RegisterAgent(path, capability) - #BlueZ 5 + # BlueZ 5 else: return self._agent_interface.RegisterAgent(path, capability) @@ -387,9 +391,9 @@ def unregister_agent(self, path): :return: :raises dbus.Exception: org.bluez.Error.DoesNotExist """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.UnregisterAgent(path) - #BlueZ 5 + # BlueZ 5 else: return self._agent_interface.UnregisterAgent(path) diff --git a/bt_manager/audio.py b/bt_manager/audio.py index 3cf6fd7..34aec98 100644 --- a/bt_manager/audio.py +++ b/bt_manager/audio.py @@ -2,9 +2,9 @@ import dbus.service try: - from gi.repository import GObject + from gi.repository import GObject except ImportError: - import gobject as GObject + import gobject as GObject import pprint import os from syslog import syslog, LOG_INFO @@ -78,7 +78,7 @@ class BTAudioSource(BTAudio): def __init__(self, *args, **kwargs): if (self.get_version() <= self.BLUEZ4_VERSION): BTGenericDevice.__init__(self, addr='org.bluez.AudioSource', - *args, **kwargs) + *args, **kwargs) else: BTGenericDevice.__init__(self, addr=self.DEVICE_INTERFACE_BLUEZ5, *args, **kwargs) @@ -529,7 +529,7 @@ def _notify_media_transport_available(self, path, transport): Called by the endpoint when a new media transport is available """ - if (self.dev_path != None and path != self.dev_path): + if (self.dev_path is not None and path != self.dev_path): syslog(LOG_INFO, 'Ignored device ' + path + ', different as specified') return self.source = BTAudioSource(dev_path=path) @@ -577,7 +577,7 @@ def _notify_media_transport_available(self, path, transport): Called by the endpoint when a new media transport is available """ - if (self.dev_path != None and path != self.dev_path): + if (self.dev_path is not None and path != self.dev_path): syslog(LOG_INFO, 'Ignored device ' + path + ', different as specified') return self.sink = BTAudioSink(dev_path=path) diff --git a/bt_manager/audio5.py b/bt_manager/audio5.py index f37b4eb..54b5d4d 100644 --- a/bt_manager/audio5.py +++ b/bt_manager/audio5.py @@ -2,9 +2,9 @@ import dbus.service try: - from gi.repository import GObject + from gi.repository import GObject except ImportError: - import gobject as GObject + import gobject as GObject import pprint import os from syslog import syslog, LOG_INFO, LOG_DEBUG @@ -413,7 +413,7 @@ def _notify_media_transport_available(self, path, state, transport): Called by the endpoint when a new media transport is available """ - if (self.dev_path != None and path != self.dev_path): + if (self.dev_path is not None and path != self.dev_path): syslog(LOG_INFO, 'Ignored device ' + path + ', different as specified') return self.source = BTAudioSource(dev_path=path) @@ -461,7 +461,7 @@ def _notify_media_transport_available(self, path, state, transport): Called by the endpoint when a new media transport is available """ - if (self.dev_path != None and path != self.dev_path): + if (self.dev_path is not None and path != self.dev_path): syslog(LOG_INFO, 'Ignored device ' + path + ', different as specified') return self.sink = BTAudioSink(dev_path=path) diff --git a/bt_manager/control.py b/bt_manager/control.py index 353cedb..071a144 100644 --- a/bt_manager/control.py +++ b/bt_manager/control.py @@ -18,13 +18,15 @@ def __init__(self, *args, **kwargs): self._register_signal_name(BTControl.SIGNAL_CONNECTED) self._register_signal_name(BTControl.SIGNAL_DISCONNECTED) else: - BTGenericDevice.__init__(self, addr=self.MEDIA_CONTROL_INTERFACE_BLUEZ5, + BTGenericDevice.__init__(self, + addr=self.MEDIA_CONTROL_INTERFACE_BLUEZ5, *args, **kwargs) self._init_properties() def _init_properties(self): self._props_interface = dbus.Interface(self._object, self.DBUS_PROPERTIES) - self._properties = list(self._props_interface.GetAll(self.MEDIA_CONTROL_INTERFACE_BLUEZ5).keys()) + props = self._props_interface.GetAll(self.MEDIA_CONTROL_INTERFACE_BLUEZ5) + self._properties = list(props.keys()) self._register_signal_name(self.SIGNAL_PROPERTIES_CHANGED) def get_property(self, name=None): @@ -43,11 +45,11 @@ def get_property(self, name=None): :raises dbus.Exception: org.bluez.Error.DoesNotExist :raises dbus.Exception: org.bluez.Error.InvalidArguments """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): raise Exception('Not handled with bluez 4') - #BlueZ 5 + # BlueZ 5 else: if (name): return self._props_interface.Get(self.DEVICE_INTERFACE_BLUEZ5, name) diff --git a/bt_manager/device.py b/bt_manager/device.py index d426826..2d8864b 100644 --- a/bt_manager/device.py +++ b/bt_manager/device.py @@ -70,14 +70,14 @@ def get_property(self, name=None): :raises dbus.Exception: org.bluez.Error.DoesNotExist :raises dbus.Exception: org.bluez.Error.InvalidArguments """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): if (name): return self._interface.GetProperties()[name] else: return self._interface.GetProperties() - #BlueZ 5 + # BlueZ 5 else: if (name): return self._props_interface.Get(self.DEVICE_INTERFACE_BLUEZ5, name) @@ -100,17 +100,19 @@ def set_property(self, name, value): :raises dbus.Exception: org.bluez.Error.DoesNotExist :raises dbus.Exception: org.bluez.Error.InvalidArguments """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): typeof = type(self.get_property(name)) - self._interface.SetProperty(name, - translate_to_dbus_type(typeof, value)) + self._interface.SetProperty( + name, translate_to_dbus_type(typeof, value)) - #BlueZ 5 + # BlueZ 5 else: typeof = type(self.get_property(name)) - self._props_interface.Set(self.DEVICE_INTERFACE_BLUEZ5, name, - translate_to_dbus_type(typeof, value)) + self._props_interface.Set( + self.DEVICE_INTERFACE_BLUEZ5, name, + translate_to_dbus_type(typeof, value) + ) class BTDevice(BTGenericDevice): @@ -177,14 +179,14 @@ class BTDevice(BTGenericDevice): def __init__(self, *args, **kwargs): if (self.get_version() <= self.BLUEZ4_VERSION): BTGenericDevice.__init__(self, addr=self.DEVICE_INTERFACE_BLUEZ4, - *args, **kwargs) + *args, **kwargs) self._register_signal_name(BTDevice.SIGNAL_DISCONNECT_REQUESTED) self._register_signal_name(BTDevice.SIGNAL_NODE_CREATED) self._register_signal_name(BTDevice.SIGNAL_NODE_REMOVED) else: BTGenericDevice.__init__(self, addr=self.DEVICE_INTERFACE_BLUEZ5, - *args, **kwargs) + *args, **kwargs) def discover_services(self, pattern=''): """ diff --git a/bt_manager/interface.py b/bt_manager/interface.py index 3684eed..e96f65c 100644 --- a/bt_manager/interface.py +++ b/bt_manager/interface.py @@ -90,7 +90,6 @@ class BTSimpleInterface: BLUEZ_DBUS_OBJECT = 'org.bluez' DBUS_OBJECT = 'org.freedesktop.DBus' - #BLUEZ4_VERSION = 1.2 BLUEZ4_VERSION = 4 def __init__(self, path, addr): @@ -109,12 +108,12 @@ def get_version(): dbus_infos = dbus.SystemBus().get_object(BTSimpleInterface.DBUS_OBJECT, '/') interface = dbus.Interface(dbus_infos, BTSimpleInterface.DBUS_OBJECT) - #BTSimpleInterface._version = float(interface.GetNameOwner(BTSimpleInterface.BLUEZ_DBUS_OBJECT)[1:]) + # BTSimpleInterface._version = float(interface.GetNameOwner(BTSimpleInterface.BLUEZ_DBUS_OBJECT)[1:]) pid = int(interface.GetConnectionUnixProcessID(BTSimpleInterface.BLUEZ_DBUS_OBJECT)) daemon = Process(pid) - #daemon.cwd() should contain "bluetoothd" - cmd = Popen([daemon.exe(),"--version"], stdout=PIPE) + # daemon.cwd() should contain "bluetoothd" + cmd = Popen([daemon.exe(), "--version"], stdout=PIPE) BTSimpleInterface._version = float(cmd.stdout.read()) return BTSimpleInterface._version @@ -270,4 +269,4 @@ def __repr__(self): def __str__(self): """Stringify the Dbus interface properties in a nice format""" - return pprint.pformat(self.get_property()) \ No newline at end of file + return pprint.pformat(self.get_property()) diff --git a/bt_manager/manager.py b/bt_manager/manager.py index ff52171..e068c1b 100644 --- a/bt_manager/manager.py +++ b/bt_manager/manager.py @@ -77,14 +77,14 @@ def get_property(self, name=None): :raises dbus.Exception: org.bluez.Error.DoesNotExist :raises dbus.Exception: org.bluez.Error.InvalidArguments """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): if (name): return self._interface.GetProperties()[name] else: return self._interface.GetProperties() - #BlueZ 5 + # BlueZ 5 else: adapters = {} adapters.Adapters = self.list_adapters() @@ -102,11 +102,11 @@ def default_adapter(self): :raises dbus.Exception: org.bluez.Error.InvalidArguments :raises dbus.Exception: org.bluez.Error.NoSuchAdapter """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.DefaultAdapter() - #BlueZ 5 + # BlueZ 5 else: adapters = self.list_adapters() if (len(adapters) == 0): @@ -123,11 +123,11 @@ def find_adapter(self, pattern): :raises dbus.Exception: org.bluez.Error.InvalidArguments :raises dbus.Exception: org.bluez.Error.NoSuchAdapter """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.FindAdapter(pattern) - #BlueZ 5 + # BlueZ 5 else: for (key, object) in self._interface.GetManagedObjects().items(): if BTAdapter.ADAPTER_INTERFACE_BLUEZ5 in object: @@ -145,11 +145,11 @@ def list_adapters(self): :raises dbus.Exception: org.bluez.Error.Failed :raises dbus.Exception: org.bluez.Error.OutOfMemory """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): return self._interface.ListAdapters() - #BlueZ 5 + # BlueZ 5 else: objects = self._interface.GetManagedObjects().items() adapters = [] @@ -159,4 +159,4 @@ def list_adapters(self): return adapters def get_adapter(self): - return self._adapter \ No newline at end of file + return self._adapter diff --git a/bt_manager/media.py b/bt_manager/media.py index 7bbdd39..4250cb6 100644 --- a/bt_manager/media.py +++ b/bt_manager/media.py @@ -214,11 +214,11 @@ def get_property(self, name=None): :raises dbus.Exception: org.bluez.Error.DoesNotExist :raises dbus.Exception: org.bluez.Error.InvalidArguments """ - #BlueZ 4 + # BlueZ 4 if (self.get_version() <= self.BLUEZ4_VERSION): raise Exception('Not handled with bluez 4') - #BlueZ 5 + # BlueZ 5 else: if (name): return self._props_interface.Get(self.DEVICE_INTERFACE_BLUEZ5, name) @@ -272,6 +272,7 @@ def release(self, access_type): """ return self._interface.Release(access_type) + class GenericEndpoint(dbus.service.Object): """ Generic media endpoint service object class for BlueZ 4. @@ -443,4 +444,4 @@ def SelectConfiguration(self, caps): :rtype: array{byte} """ print('Callback SelectConfiguration') - pass \ No newline at end of file + pass diff --git a/demo/demo.py b/demo/demo.py index 0bbba04..b098072 100644 --- a/demo/demo.py +++ b/demo/demo.py @@ -6,9 +6,9 @@ import dbus import dbus.mainloop.glib try: - from gi.repository import GObject + from gi.repository import GObject except ImportError: - import gobject as GObject + import gobject as GObject import signal from collections import namedtuple diff --git a/demo/demo3.py b/demo/demo3.py index f536401..f90bc42 100644 --- a/demo/demo3.py +++ b/demo/demo3.py @@ -5,7 +5,10 @@ import sys import dbus import dbus.mainloop.glib -from gi.repository import GObject +try: + from gi.repository import GObject +except ImportError: + import gobject as GObject import signal from collections import namedtuple @@ -536,9 +539,9 @@ def media_sbc_sink_start(args): try: if (bt_manager.BTManager().get_version() <= bt_manager.BTAdapter.BLUEZ4_VERSION): - ep = bt_manager.SBCAudioSink(path=path) + ep = bt_manager.SBCAudioSink(path=path, dev_path=dev_path) else: - ep = bt_manager.SBCAudioSink5(path=path) + ep = bt_manager.SBCAudioSink5(path=path, dev_path=dev_path) print('=========================================================') print(repr(ep)) services[path] = ep @@ -610,9 +613,9 @@ def media_sbc_source_start(args): try: if (bt_manager.BTManager().get_version() <= bt_manager.BTAdapter.BLUEZ4_VERSION): - ep = bt_manager.SBCAudioSource(path=path) + ep = bt_manager.SBCAudioSource(path=path, dev_path=dev_path) else: - ep = bt_manager.SBCAudioSource5(path=path) + ep = bt_manager.SBCAudioSource5(path=path, dev_path=dev_path) print('=========================================================') print(repr(ep)) services[path] = ep From e23b381ee75f977a94b568a8356f37235c2a802b Mon Sep 17 00:00:00 2001 From: Guillaume GILL Date: Sat, 2 Sep 2017 00:14:52 +0200 Subject: [PATCH 14/14] Prepare version 0.4.0 --- .travis.yml | 13 ++++++++++--- README.rst | 5 +++++ setup.py | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 23eb258..de24e29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,18 @@ language: python python: - - "2.7_with_system_site_packages" + - 2.7 + - 3.4 + +virtualenv: + system_site_packages: true + +before_install: + - "sudo apt-get -qq update" install: - - "sudo apt-get install python-dbus python-gobject" - - "sudo pip install coveralls flake8 cffi" + - "sudo apt-get install libffi-dev" + - "sudo pip install --upgrade coverage flake8 cffi pydbus psutil" - "sudo make -C codecs install" script: diff --git a/README.rst b/README.rst index 43e2649..53cb56d 100644 --- a/README.rst +++ b/README.rst @@ -56,6 +56,11 @@ Project resources Changelog ========= +v0.4.0 +------ + +BlueZ 5.x support + v0.3.0 ------ diff --git a/setup.py b/setup.py index 718a5d4..3213cf0 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,8 @@ def get_version(filename): install_requires=[ 'setuptools', 'cffi >= 0.7', - 'psutil' + 'psutil', + 'dbus' ], setup_requires=['cffi >= 0.7'], test_suite='nose.collector',