-
Notifications
You must be signed in to change notification settings - Fork 671
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update to Bluez5/DBus based API #404
Comments
I'll start working on this |
okay so I'm quite bad at this. It seems that on bluez5 you cannot find services as on bluez4 and that everything related to sdp is done through the org.bluez.Profile1 interface. As I understand, you need to register a Profile when doing the server paper as well as when doing the client paper. Moreover, sockets are created each time a new connection is made. This means that the concept of looking up sdp for a port to connect, or creating a socket before advertising is no longer viable. In addition, now python sockets support bluetooth without sdp (ej you have to give it a fixed port) which have left BluetoothSocket a bit obsolete . On the other hand, for running a dbus interface you need a main loop, making every program that uses the module main loop agnostic (or maybe the main loop could be implemented on a thread adding a layer of abstraction ) |
I don't understand which approach to take, seems like the most useful would be to expose all the dbus api through a more pythonic way, but for that pydbus is already there, doesn't it? (althought it seems a bit discontinued). |
@vik0t0r There's also dasbus which looks more maintained than pydbus and looks better than
Also I'd prefer a threading implementation unless we suddenly decide to fully move to e.g. asyncio (which probably isn't a good idea). Also from these I think an API change for how PyBluez handles SDP is probably necessary or it would look pretty ugly. My thought is that we could define a Profile class that somewhat resembles bluez's Profile dbus API, and implement the connection waiting directly on our Profile object. For other platforms we can probably create a socket, block/fire a callback on socket.accept and just pass the socket directly (i.e. the old way but encapsulated in Profile class). Supporting for asyncio for our Profile API would be a plus since... I like it. |
Hm both dasbus and pydbus might not work because
EDIT: This is now being tracked at dasbus-project/dasbus#65. Also upon closer look, we might not need ObjectManager implementation. |
I have been looking at some the issues around Python, D-Bus, and BlueZ. I certainly don't have all the answers and am sharing here in the hope that it will help move things forward for all of us. (Even if it is just to help understand that this isn't a path to go down) I have seen issues with the various D-Bus Python bindings when trying to use them with BlueZ. As a result of this I have been looking into using the PyGObject bindings that most of the newer D-Bus libraries are built on. My thought was that this might be a better option to explore as the BlueZ D-Bus API requires a small subset of the D-Bus functionality. The other libraries are trying to be generic D-Bus bindings and cover the most widely used functionality. Below is an experiment I did to create a BlueZ Serial Port Profile (SPP) Server using PyGObject. import os
from gi.repository import Gio, GLib
# Introspection data for DBus
profile_xml = """
<node>
<interface name="org.bluez.Profile1">
<method name="Release"/>
<method name="NewConnection">
<arg type="o" name="device" direction="in"/>
<arg type="h" name="fd" direction="in"/>
<arg type="a{sv}" name="fd_properties" direction="in"/>
</method>
<method name="RequestDisconnection">
<arg type="o" name="device" direction="in"/>
</method>
</interface>
</node>
"""
class DbusService:
"""Class used to publish a DBus service on to the DBus System Bus"""
def __init__(self, introspection_xml, publish_path):
self.node_info = Gio.DBusNodeInfo.new_for_xml(introspection_xml).interfaces[0]
# start experiment
method_outargs = {}
method_inargs = {}
property_sig = {}
for method in self.node_info.methods:
method_outargs[method.name] = '(' + ''.join([arg.signature for arg in method.out_args]) + ')'
method_inargs[method.name] = tuple(arg.signature for arg in method.in_args)
self.method_inargs = method_inargs
self.method_outargs = method_outargs
self.con = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
self.con.register_object(
publish_path,
self.node_info,
self.handle_method_call,
self.prop_getter,
self.prop_setter)
def handle_method_call(self,
connection: Gio.DBusConnection,
sender: str,
object_path: str,
interface_name: str,
method_name: str,
params: GLib.Variant,
invocation: Gio.DBusMethodInvocation
):
"""
This is the top-level function that handles method calls to
the server.
"""
args = list(params.unpack())
# Handle the case where it is a Unix filedescriptor
for i, sig in enumerate(self.method_inargs[method_name]):
if sig == 'h':
msg = invocation.get_message()
fd_list = msg.get_unix_fd_list()
args[i] = fd_list.get(args[i])
func = self.__getattribute__(method_name)
result = func(*args)
if result is None:
result = ()
else:
result = (result,)
outargs = ''.join([_.signature
for _ in invocation.get_method_info().out_args])
send_result = GLib.Variant(f'({outargs})', result)
invocation.return_value(send_result)
def prop_getter(self,
connection: Gio.DBusConnection,
sender: str,
object: str,
iface: str,
name: str):
"""Return requested values on DBus from Python object"""
py_value = self.__getattribute__(name)
signature = self.node_info.lookup_property(name).signature
if py_value:
return GLib.Variant(signature, py_value)
return None
def prop_setter(self,
connection: Gio.DBusConnection,
sender: str,
object: str,
iface: str,
name: str,
value: GLib.Variant):
"""Set values on Python object from DBus"""
self.__setattr__(name, value.unpack())
return True
class Profile(DbusService):
def __init__(self, introspection_xml, publish_path):
super().__init__(introspection_xml, publish_path)
self.fd = -1
def Release(self):
print('Release')
def NewConnection(self, path, fd, properties):
self.fd = fd
print(f'NewConnection({path}, {self.fd}, {properties})')
for key in properties.keys():
if key == 'Version' or key == 'Features':
print(' %s = 0x%04x' % (key, properties[key]))
else:
print(' %s = %s' % (key, properties[key]))
io_id = GLib.io_add_watch(self.fd,
GLib.PRIORITY_DEFAULT,
GLib.IO_IN | GLib.IO_PRI,
self.io_cb)
def io_cb(self, fd, conditions):
data = os.read(fd, 1024)
print('Callback Data: {0}'.format(data.decode('ascii')))
os.write(fd, bytes(list(reversed(data.rstrip()))) + b'\n')
return True
def RequestDisconnection(self, path):
print('RequestDisconnection(%s)' % (path))
if self.fd > 0:
os.close(self.fd)
self.fd = -1
def main():
obj_mngr = Gio.DBusObjectManagerClient.new_for_bus_sync(
bus_type=Gio.BusType.SYSTEM,
flags=Gio.DBusObjectManagerClientFlags.NONE,
name='org.bluez',
object_path='/',
get_proxy_type_func=None,
get_proxy_type_user_data=None,
cancellable=None,
)
manager = obj_mngr.get_object('/org/bluez').get_interface('org.bluez.ProfileManager1')
adapter = obj_mngr.get_object('/org/bluez/hci0').get_interface('org.freedesktop.DBus.Properties')
mainloop = GLib.MainLoop()
discoverable = adapter.Get('(ss)', 'org.bluez.Adapter1', 'Discoverable')
if not discoverable:
print('Making discoverable...')
adapter.Set('(ssv)', 'org.bluez.Adapter1',
'Discoverable', GLib.Variant.new_boolean(True))
profile_path = '/org/bluez/test/profile'
server_uuid = '00001101-0000-1000-8000-00805f9b34fb'
opts = {
'Version': GLib.Variant.new_uint16(0x0102),
'AutoConnect': GLib.Variant.new_boolean(True),
'Role': GLib.Variant.new_string('server'),
'Name': GLib.Variant.new_string('SerialPort'),
'Service': GLib.Variant.new_string('00001101-0000-1000-8000-00805f9b34fb'),
'RequireAuthentication': GLib.Variant.new_boolean(False),
'RequireAuthorization': GLib.Variant.new_boolean(False),
'Channel': GLib.Variant.new_uint16(1),
}
print('Starting Serial Port Profile...')
profile = Profile(profile_xml, profile_path)
manager.RegisterProfile('(osa{sv})', profile_path, server_uuid, opts)
try:
mainloop.run()
except KeyboardInterrupt:
mainloop.quit()
if __name__ == '__main__':
main() This was a proof-of-concept to see if it was possible. I haven't done a lot of testing with it and I certainly make no claims that it is ready for production use or the required structure for use in the library. This was the client I used to test it with: import socket
server_address = "xx:xx:xx:xx:xx:xx"
server_port = 1
with socket.socket(socket.AF_BLUETOOTH,
socket.SOCK_STREAM,
socket.BTPROTO_RFCOMM) as c:
c.connect((server_address, server_port))
c.send(b"desserts")
print(c.recv(1024).decode()) |
PyBluez is not under active development but we are seeking new contributors
to investigate bugs and submit patches.
System
Issue
PyBluez still uses the old SDPd API pre-Bluez 5, which is deprecated in favor of DBus-based API since long ago. This causes (at least) the SDP registration to be non-functional on modern Linux systems (can be seen when running e.g. the supplied rfcomm-server.py).
The text was updated successfully, but these errors were encountered: