Open usvi opened 1 year ago
The documentation is very bad for beginners. My best bet is to take this:
https://git.kernel.org/pub/scm/bluetooth/bluez.git/tree/client
And slaughter it until it works.
Is this even real? The client is built on some kind of dbus wrappers (gdbus.h) which is not even published?
I have no idea what I am doing anymore
29637 sudo apt-get install libdbus-glib-1-dev 29640 sudo apt-get remove --purge libdbus-glib-1-dev 29641 sudo apt-get install libdbus-glib-1-dev 29644 sudo apt-get remove --purge libdbus-glib-1-dev 29683 apt-cache search glib-dev 29684 apt-cache search libglib-dev 29685 apt-cache search libglib 29686 sudo apt-get libglib2.0-dev 29687 sudo apt-get install libglib2.0-dev 29689 sudo apt-get remove libglib2.0-dev --purge 29692 sudo apt-get install libglib2.0-dev 29702 pkg-config --cflags glib-2.0 29703 pkg-config --libs glib-2.0 29723 pkg-config --cflags glib-2.0 34266 pkg-config --cflags glib-2.0 34269 history | grep glib 34270 sudo apt-get install libglib2.0 34271 history | grep glib
https://dbus.freedesktop.org/doc/dbus-tutorial.html
"GLib APIs
The recommended GLib API for D-Bus is GDBus, which has been distributed with GLib since version 2.26. It is not documented here. See the GLib documentation for details of how to use GDBus.
An older API, dbus-glib, also exists. It is deprecated and should not be used in new code. Whenever possible, porting existing code from dbus-glib to GDBus is also recommended."
It looks like GLib GDBus has nothing to do with bluez gdbus.h. Look what this says:
https://dbus.freedesktop.org/doc/dbus-glib/
"
dbus-glib is a deprecated API for use of D-Bus from GLib applications. Do not use it in new code.
Since version 2.26, GLib's accompanying GIO library provides a high-level API for D-Bus, "GDBus", based on an independent reimplementation of the D-Bus protocol. The maintainers of D-Bus recommend that GLib applications should use GDBus instead of dbus-glib."
So what is needed is checking out gio.h , then porting bluez client code to it.
Probably other good gio examples also exist.
This looks worth taking a look:
dbus-monitor --system "type='signal',sender='org.bluez'"
Where does the initial come from?
~$ bluetoothctl [NEW] Controller 00:15:83:FF:A6:DE ESLD [default] Agent registered [bluetooth]# quit Agent unregistered [DEL] Controller 00:15:83:FF:A6:DE ESLD [default]
static void print_adapter(GDBusProxy *proxy, const char *description): bt_shell_printf("%s%s%sController %s %s %s\n", description ? "[" : "", description ? : "", description ? "] " : "", address, name, default_ctrl && default_ctrl->proxy == proxy ? "[default]" : ""); static void adapter_added(GDBusProxy *proxy) { struct adapter *adapter; adapter = find_ctrl(ctrl_list, g_dbus_proxy_get_path(proxy)); if (!adapter) adapter = adapter_new(proxy); adapter->proxy = proxy; print_adapter(proxy, COLORED_NEW); bt_shell_set_env(g_dbus_proxy_get_path(proxy), proxy); } static void proxy_added(GDBusProxy *proxy, void *user_data) { const char *interface; interface = g_dbus_proxy_get_interface(proxy); if (!strcmp(interface, "org.bluez.Device1")) { device_added(proxy); } else if (!strcmp(interface, "org.bluez.Adapter1")) { adapter_added(proxy); main.c: if (endpoint_option) bt_shell_set_env("AUTO_REGISTER_ENDPOINT", (void *)endpoint_option); admin_add_submenu(); player_add_submenu(); client = g_dbus_client_new(dbus_conn, "org.bluez", "/org/bluez"); g_dbus_client_set_connect_watch(client, connect_handler, NULL); g_dbus_client_set_disconnect_watch(client, disconnect_handler, NULL); g_dbus_client_set_signal_watch(client, message_handler, NULL); g_dbus_client_set_proxy_handlers(client, proxy_added, proxy_removed, property_changed, NULL); g_dbus_client_set_ready_watch(client, client_ready, NULL); status = bt_shell_run();
Newer example has:
g_dbus_connection_call(con, "org.bluez", / TODO Find the adapter path runtime / "/org/bluez/hci0", "org.bluez.Adapter1", method, param, NULL, G_DBUS_CALL_FLAGS_NONE, -1, NULL, method_cb, (void *)method);
The interface being:
void g_dbus_connection_call(GDBusConnection connection, const gchar bus_name, const gchar object_path, const gchar interface_name, const gchar method_name, GVariant parameters, const GVariantType reply_type, GDBusCallFlags flags, gint timeout_msec, GCancellable cancellable, GAsyncReadyCallback callback, gpointer user_data);
Maybe call GetManagedObjects on / ?
Ah finally, this is a proxy:
https://dbus.freedesktop.org/doc/dbus-tutorial.html
A proxy object is a convenient native object created to represent a remote object in another process. The low-level DBus API involves manually creating a method call message, sending it, then manually receiving and processing the method reply message. Higher-level bindings provide proxies as an alternative. Proxies look like a normal native object; but when you invoke a method on the proxy object, the binding converts it into a DBus method call message, waits for the reply message, unpacks the return value, and returns it from the native method..
In pseudocode, programming without proxies might look like this:
Message message = new Message("/remote/object/path", "MethodName", arg1, arg2);
Connection connection = getBusConnection();
connection.send(message);
Message reply = connection.waitForReply(message);
if (reply.isError()) {
} else {
Object returnValue = reply.getReturnValue();
}
Programming with proxies might look like this:
Proxy proxy = new Proxy(getBusConnection(), "/remote/object/path");
Object returnValue = proxy.MethodName(arg1, arg2);
dbus-send --system --print-reply --type=method_call --dest='org.bluez' '/' org.freedesktop.DBus.ObjectManager.GetManagedObjects
Another thing from the guy:
https://gist.github.com/parthitce/300b91186bcefbb8ed4798c8eab8d265
Interesting. All calls are "by default" asynchronous, requiring callbacks. Synchronous calls are used with _sync variants.
See https://www.freedesktop.org/software/gstreamer-sdk/data/docs/latest/gio/GDBusProxy.html
Now the question quickly becomes: What should we qualify as interfaces? The library init should have a syntax. Maybe:
Listen On parameter: "+CURRENT+ADDED-hci1+00:15:83:FF:A6:DE"
Minus denies, plus accepts, last has effect. So it will be a chain. Maybe even a linked list.
Update the list on start and on remove and on add. Always subscribe only on individual /org/bluez/hciX devices.
On startup:
On change:
For first, we can just make the hci/MAC lister.
Bt adapter has this:
org.bluez.Adapter1
A device seen via adapter has this:
org.bluez.Device1
Output of terminal dbus command when 2 adapters connected
method return time=1672609063.209344 sender=:1.289 -> destination=:1.3051 serial=489 reply_serial=2 array [ dict entry( object path "/org/bluez" array [ dict entry( string "org.freedesktop.DBus.Introspectable" array [ ] ) dict entry( string "org.bluez.AgentManager1" array [ ] ) dict entry( string "org.bluez.ProfileManager1" array [ ] ) ] ) dict entry( object path "/org/bluez/hci1" array [ dict entry( string "org.freedesktop.DBus.Introspectable" array [ ] ) dict entry( string "org.bluez.Adapter1" array [ dict entry( string "Address" variant string "48:51:C5:72:67:75" ) dict entry( string "AddressType" variant string "public" ) dict entry( string "Name" variant string "ESLD #2" ) dict entry( string "Alias" variant string "ESLD #2" ) dict entry( string "Class" variant uint32 786432 ) dict entry( string "Powered" variant boolean true ) dict entry( string "Discoverable" variant boolean false ) dict entry( string "DiscoverableTimeout" variant uint32 180 ) dict entry( string "Pairable" variant boolean true ) dict entry( string "PairableTimeout" variant uint32 0 ) dict entry( string "Discovering" variant boolean false ) dict entry( string "UUIDs" variant array [ string "00001112-0000-1000-8000-00805f9b34fb" string "00001801-0000-1000-8000-00805f9b34fb" string "0000110e-0000-1000-8000-00805f9b34fb" string "00001800-0000-1000-8000-00805f9b34fb" string "00001200-0000-1000-8000-00805f9b34fb" string "0000110c-0000-1000-8000-00805f9b34fb" string "0000110a-0000-1000-8000-00805f9b34fb" string "0000110b-0000-1000-8000-00805f9b34fb" string "00001108-0000-1000-8000-00805f9b34fb" ] ) dict entry( string "Modalias" variant string "usb:v1D6Bp0246d0530" ) ] ) dict entry( string "org.freedesktop.DBus.Properties" array [ ] ) dict entry( string "org.bluez.GattManager1" array [ ] ) dict entry( string "org.bluez.LEAdvertisingManager1" array [ dict entry( string "ActiveInstances" variant byte 0 ) dict entry( string "SupportedInstances" variant byte 5 ) dict entry( string "SupportedIncludes" variant array [ string "tx-power" string "appearance" string "local-name" ] ) ] ) dict entry( string "org.bluez.Media1" array [ ] ) dict entry( string "org.bluez.NetworkServer1" array [ ] ) ] ) dict entry( object path "/org/bluez/hci0" array [ dict entry( string "org.freedesktop.DBus.Introspectable" array [ ] ) dict entry( string "org.bluez.Adapter1" array [ dict entry( string "Address" variant string "00:15:83:FF:A6:DE" ) dict entry( string "AddressType" variant string "public" ) dict entry( string "Name" variant string "ESLD" ) dict entry( string "Alias" variant string "ESLD" ) dict entry( string "Class" variant uint32 786432 ) dict entry( string "Powered" variant boolean true ) dict entry( string "Discoverable" variant boolean false ) dict entry( string "DiscoverableTimeout" variant uint32 180 ) dict entry( string "Pairable" variant boolean true ) dict entry( string "PairableTimeout" variant uint32 0 ) dict entry( string "Discovering" variant boolean false ) dict entry( string "UUIDs" variant array [ string "00001112-0000-1000-8000-00805f9b34fb" string "00001801-0000-1000-8000-00805f9b34fb" string "0000110e-0000-1000-8000-00805f9b34fb" string "00001800-0000-1000-8000-00805f9b34fb" string "00001200-0000-1000-8000-00805f9b34fb" string "0000110c-0000-1000-8000-00805f9b34fb" string "0000110a-0000-1000-8000-00805f9b34fb" string "0000110b-0000-1000-8000-00805f9b34fb" string "00001108-0000-1000-8000-00805f9b34fb" ] ) dict entry( string "Modalias" variant string "usb:v1D6Bp0246d0530" ) ] ) dict entry( string "org.freedesktop.DBus.Properties" array [ ] ) dict entry( string "org.bluez.GattManager1" array [ ] ) dict entry( string "org.bluez.LEAdvertisingManager1" array [ dict entry( string "ActiveInstances" variant byte 0 ) dict entry( string "SupportedInstances" variant byte 5 ) dict entry( string "SupportedIncludes" variant array [ string "tx-power" string "appearance" string "local-name" ] ) ] ) dict entry( string "org.bluez.Media1" array [ ] ) dict entry( string "org.bluez.NetworkServer1" array [ ] ) ] ) ]
dbus-send --system --print-reply --type=method_call --dest='org.bluez' '/org/bluez/hci0' org.bluez.Adapter1.GetDiscoveryFilters method return time=1672614657.780228 sender=:1.289 -> destination=:1.3119 serial=687 reply_serial=2 array [ string "UUIDs" string "RSSI" string "Pathloss" string "Transport" string "DuplicateData" ]
I guess I also need org.freedesktop.DBus.ObjectManager.InterfacesAdded
client->added_watch = g_dbus_add_signal_watch(connection, service, client->root_path, DBUS_INTERFACE_OBJECT_MANAGER, "InterfacesAdded", interfaces_added, client, NULL);
Remember, we possibly need org.bluez.Device1
I think we are kind of finishing on this soon.
Extremely helpful command:
sudo dbus-monitor --system "type='signal',sender='org.bluez'"
And finally, here is our magic:
signal time=1672772401.041013 sender=:1.3493 -> destination=(null destination) serial=76 path=/org/bluez/hci0/dev_CD_99_36_8E_6E_70; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged string "org.bluez.Device1" array [ dict entry( string "ManufacturerData" variant array [ dict entry( uint16 1177 variant array of bytes [ 05 11 b0 2d 9a c9 d7 ff cc ff f8 04 08 c3 76 4a 14 bf cd 99 36 8e 6e 70 ] ) ] ) ] array [ ]
Lets analyze memory usage of different backends.
"Full" program with Glib Gio:
==69008== ==69008== HEAP SUMMARY: ==69008== in use at exit: 127,068 bytes in 1,536 blocks ==69008== total heap usage: 5,408 allocs, 3,872 frees, 310,602 bytes allocated ==69008== ==69008== LEAK SUMMARY: ==69008== definitely lost: 48 bytes in 1 blocks ==69008== indirectly lost: 8,978 bytes in 248 blocks ==69008== possibly lost: 2,400 bytes in 26 blocks ==69008== still reachable: 104,770 bytes in 1,180 blocks ==69008== of which reachable via heuristic: ==69008== length64 : 768 bytes in 15 blocks ==69008== newarray : 1,728 bytes in 28 blocks ==69008== suppressed: 0 bytes in 0 blocks ==69008== Rerun with --leak-check=full to see details of leaked memory ==69008== ==69008== For counts of detected and suppressed errors, rerun with: -v ==69008== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Only system bus connect and release, Glib / Gio:
==69194== LEAK SUMMARY: ==69194== definitely lost: 0 bytes in 0 blocks ==69194== indirectly lost: 0 bytes in 0 blocks ==69194== possibly lost: 2,400 bytes in 26 blocks ==69194== still reachable: 100,941 bytes in 1,103 blocks ==69194== of which reachable via heuristic: ==69194== length64 : 768 bytes in 15 blocks ==69194== newarray : 1,728 bytes in 28 blocks ==69194== suppressed: 0 bytes in 0 blocks ==69194== ==69194== For counts of detected and suppressed errors, rerun with: -v ==69194== ERROR SUMMARY: 26 errors from 26 contexts (suppressed: 0 from 0)
Only system bus connect and release, libdbus-1:
==69469== LEAK SUMMARY: ==69469== definitely lost: 0 bytes in 0 blocks ==69469== indirectly lost: 0 bytes in 0 blocks ==69469== possibly lost: 0 bytes in 0 blocks ==69469== still reachable: 10,220 bytes in 81 blocks ==69469== suppressed: 0 bytes in 0 blocks ==69469== ==69469== For counts of detected and suppressed errors, rerun with: -v ==69469== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Libsystemd sd-bus:
==69536== LEAK SUMMARY: ==69536== definitely lost: 0 bytes in 0 blocks ==69536== indirectly lost: 0 bytes in 0 blocks ==69536== possibly lost: 0 bytes in 0 blocks ==69536== still reachable: 7,736 bytes in 12 blocks ==69536== suppressed: 0 bytes in 0 blocks ==69536== ==69536== For counts of detected and suppressed errors, rerun with: -v ==69536== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
To me it looks like glib is total garbage, libdbus-1 doing much better (but lots of blocks used) and then finally sd-bus not being perfect but best of the available ones. Maybe going with sd-bus.
Ah, realized sd-bus is probably so deeply married to systemd that better to use libdbus-1.
Everybody voted for the example above, but this has the correct behavior of calling dbus_connection_get_dispatch_status() when you are done setting everything up and then triggering a dispatch if (status == DBUS_DISPATCH_DATA_REMAINS). – TaborKelly Feb 28, 2019 at 2:07 "
"These next two operations require reading messages from the bus and handling them. There is not a simple way in the C API as of 0.50 to do this. Because DBUS is designed to use OO bindings and an event based model all the methods that handle the messages on the wire use callbacks. I have submitted a patch which should make it into the next release which allows messages to be read and marshalled from the wire in a non-blocking operation. This is the dbus_connection_read_write() method, which is required to use the code on this page. "
We should make our own messages for startup and shutdown. And the startup message would set everything up.
Ah, realized sd-bus is probably so deeply married to systemd that better to use libdbus-1.
Actually, this does not matter. I'm using bluez, so I am in anyways married to Linux at this point. Only thing is if I can do proper teardown.
Ah, realized sd-bus is probably so deeply married to systemd that better to use libdbus-1.
Actually, this does not matter. I'm using bluez, so I am in anyways married to Linux at this point. Only thing is if I can do proper teardown.
Actually there is value in doing it with libdbus-1. It would be a demonstration of custom event loop, very minimal in aspects.
"ou are missing the event loop which is otherwise available by default if you choose to go with one of the bindings. When you get a call to add_watch, libdbus expects that the application will attach an IO handler to it. The IOHandler added by application will watch for an activity on the fd (filedescriptor) queried for the watch. Whenever there is an activity on that file descriptor, the IOHandler will trigger a callback with appropriate flags that you need to convert to DBUS flags before calling dbus_watch_handle.
Suggest that you use glib if you don't know how to use event loops. I am able to get it if I use libUV or libEV as low footprint event loop."
Seems it is easier to start from the core and then work upwards in call chain.
signal time=1688937441.975826 sender=:1.3423 -> destination=(null destination) serial=190 path=/; interface=org.freedesktop.DBus.ObjectManager; member=InterfacesAdded
object path "/org/bluez/hci0"
array [
dict entry(
string "org.freedesktop.DBus.Introspectable"
array [
]
)
dict entry(
string "org.bluez.Adapter1"
array [
dict entry(
string "Address"
variant string "00:15:83:FF:A6:DE"
)
dict entry(
string "AddressType"
variant string "public"
)
dict entry(
string "Name"
variant string "ESLD"
)
dict entry(
string "Alias"
variant string "ESLD"
)
dict entry(
string "Class"
variant uint32 0
)
dict entry(
string "Powered"
variant boolean false
)
dict entry(
string "Discoverable"
variant boolean false
)
dict entry(
string "DiscoverableTimeout"
variant uint32 180
)
dict entry(
string "Pairable"
variant boolean false
)
dict entry(
string "PairableTimeout"
variant uint32 0
)
dict entry(
string "Discovering"
variant boolean false
)
dict entry(
string "UUIDs"
variant array [
string "00001112-0000-1000-8000-00805f9b34fb"
string "00001108-0000-1000-8000-00805f9b34fb"
string "00001801-0000-1000-8000-00805f9b34fb"
string "0000110e-0000-1000-8000-00805f9b34fb"
string "00001200-0000-1000-8000-00805f9b34fb"
string "0000110c-0000-1000-8000-00805f9b34fb"
string "00001800-0000-1000-8000-00805f9b34fb"
]
)
dict entry(
string "Modalias"
variant string "usb:v1D6Bp0246d0530"
)
]
)
dict entry(
string "org.freedesktop.DBus.Properties"
array [
]
)
dict entry(
string "org.bluez.GattManager1"
array [
]
)
dict entry(
string "org.bluez.LEAdvertisingManager1"
array [
dict entry(
string "ActiveInstances"
variant byte 0
)
dict entry(
string "SupportedInstances"
variant byte 5
)
dict entry(
string "SupportedIncludes"
variant array [
string "tx-power"
string "appearance"
string "local-name"
]
)
]
)
dict entry(
string "org.bluez.Media1"
array [
]
)
dict entry(
string "org.bluez.NetworkServer1"
array [
]
)
]
Marking this down:
"DBUS_WATCH_READABLE
As in POLLIN.
DBUS_WATCH_WRITABLE
As in POLLOUT.
DBUS_WATCH_ERROR
As in POLLERR (can't watch for this, but can be present in current state passed to dbus_watch_handle()).
DBUS_WATCH_HANGUP
As in POLLHUP (can't watch for it, but can be present in current state passed to dbus_watch_handle()). "
"dbus_watch_get_flags() unsigned int dbus_watch_get_flags ( DBusWatch * watch )
Gets flags from DBusWatchFlags indicating what conditions should be monitored on the file descriptor.
The flags returned will only contain DBUS_WATCH_READABLE and DBUS_WATCH_WRITABLE, never DBUS_WATCH_HANGUP or DBUS_WATCH_ERROR; all watches implicitly include a watch for hangups, errors, and other exceptional conditions."
Example use of wake up functionality:
114 : /* dbus_connection_wakeup_main
115 : * D-BUS makes a callback to the wakeup_main function when
116 : * it has data available for dispatching.
117 : * In order to avoid blocking, this function will create a now()
118 : * timed event to perform the dispatch during the next iteration
119 : * through the mainloop
120 : */
121 0 : static void sbus_conn_wakeup_main(void *data)
122 : {
123 : struct sbus_connection *conn;
124 : struct timeval tv;
125 : struct tevent_timer *te;
126 :
127 0 : conn = talloc_get_type(data, struct sbus_connection);
128 :
129 0 : tv = tevent_timeval_current();
130 :
131 : /* D-BUS calls this function when it is time to do a dispatch */
132 0 : te = tevent_add_timer(conn->ev, conn, tv, sbus_dispatch, conn);
133 0 : if (te == NULL) {
134 0 : DEBUG(SSSDBG_OP_FAILURE,"Could not add dispatch event!\n");
135 : /* TODO: Calling exit here is bad */
136 0 : exit(1);
137 : }
138 0 : }
We need to make a simple DBUS Bluez connection from the .so file