-
-
Save parthitce/5b83143d9ac0416024322e698d3ff415 to your computer and use it in GitHub Desktop.
| /* | |
| * bluez_adapter_filter.c - Set discovery filter, Scan for bluetooth devices | |
| * - Control three discovery filter parameter from command line, | |
| * - auto/bredr/le | |
| * - RSSI (0:very close range to -100:far away) | |
| * - UUID (only one as of now) | |
| * Example run: ./bin/bluez_adapter_filter bredr 100 00001105-0000-1000-8000-00805f9b34fb | |
| * - This example scans for new devices after powering the adapter, if any devices | |
| * appeared in /org/hciX/dev_XX_YY_ZZ_AA_BB_CC, it is monitered using "InterfaceAdded" | |
| * signal and all the properties of the device is printed | |
| * - Device will be removed immediately after it appears in InterfacesAdded signal, so | |
| * InterfacesRemoved will be called which quits the main loop | |
| * gcc `pkg-config --cflags glib-2.0 gio-2.0` -Wall -Wextra -o ./bin/bluez_adapter_filter ./bluez_adapter_filter.c `pkg-config --libs glib-2.0 gio-2.0` | |
| */ | |
| #include <glib.h> | |
| #include <gio/gio.h> | |
| GDBusConnection *con; | |
| static void bluez_property_value(const gchar *key, GVariant *value) | |
| { | |
| const gchar *type = g_variant_get_type_string(value); | |
| g_print("\t%s : ", key); | |
| switch(*type) { | |
| case 'o': | |
| case 's': | |
| g_print("%s\n", g_variant_get_string(value, NULL)); | |
| break; | |
| case 'b': | |
| g_print("%d\n", g_variant_get_boolean(value)); | |
| break; | |
| case 'u': | |
| g_print("%d\n", g_variant_get_uint32(value)); | |
| break; | |
| case 'a': | |
| /* TODO Handling only 'as', but not array of dicts */ | |
| if(g_strcmp0(type, "as")) | |
| break; | |
| g_print("\n"); | |
| const gchar *uuid; | |
| GVariantIter i; | |
| g_variant_iter_init(&i, value); | |
| while(g_variant_iter_next(&i, "s", &uuid)) | |
| g_print("\t\t%s\n", uuid); | |
| break; | |
| default: | |
| g_print("Other\n"); | |
| break; | |
| } | |
| } | |
| typedef void (*method_cb_t)(GObject *, GAsyncResult *, gpointer); | |
| static int bluez_adapter_call_method(const char *method, GVariant *param, method_cb_t method_cb) | |
| { | |
| GError *error = NULL; | |
| 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, | |
| &error); | |
| if(error != NULL) | |
| return 1; | |
| return 0; | |
| } | |
| static void bluez_get_discovery_filter_cb(GObject *con, | |
| GAsyncResult *res, | |
| gpointer data) | |
| { | |
| (void)data; | |
| GVariant *result = NULL; | |
| result = g_dbus_connection_call_finish((GDBusConnection *)con, res, NULL); | |
| if(result == NULL) | |
| g_print("Unable to get result for GetDiscoveryFilter\n"); | |
| if(result) { | |
| result = g_variant_get_child_value(result, 0); | |
| bluez_property_value("GetDiscoveryFilter", result); | |
| } | |
| g_variant_unref(result); | |
| } | |
| static void bluez_device_appeared(GDBusConnection *sig, | |
| const gchar *sender_name, | |
| const gchar *object_path, | |
| const gchar *interface, | |
| const gchar *signal_name, | |
| GVariant *parameters, | |
| gpointer user_data) | |
| { | |
| (void)sig; | |
| (void)sender_name; | |
| (void)object_path; | |
| (void)interface; | |
| (void)signal_name; | |
| (void)user_data; | |
| GVariantIter *interfaces; | |
| const char *object; | |
| const gchar *interface_name; | |
| GVariant *properties; | |
| //int rc; | |
| g_variant_get(parameters, "(&oa{sa{sv}})", &object, &interfaces); | |
| while(g_variant_iter_next(interfaces, "{&s@a{sv}}", &interface_name, &properties)) { | |
| if(g_strstr_len(g_ascii_strdown(interface_name, -1), -1, "device")) { | |
| g_print("[ %s ]\n", object); | |
| const gchar *property_name; | |
| GVariantIter i; | |
| GVariant *prop_val; | |
| g_variant_iter_init(&i, properties); | |
| while(g_variant_iter_next(&i, "{&sv}", &property_name, &prop_val)) | |
| bluez_property_value(property_name, prop_val); | |
| g_variant_unref(prop_val); | |
| } | |
| g_variant_unref(properties); | |
| } | |
| /* | |
| rc = bluez_adapter_call_method("RemoveDevice", g_variant_new("(o)", object)); | |
| if(rc) | |
| g_print("Not able to remove %s\n", object); | |
| */ | |
| return; | |
| } | |
| #define BT_ADDRESS_STRING_SIZE 18 | |
| static void bluez_device_disappeared(GDBusConnection *sig, | |
| const gchar *sender_name, | |
| const gchar *object_path, | |
| const gchar *interface, | |
| const gchar *signal_name, | |
| GVariant *parameters, | |
| gpointer user_data) | |
| { | |
| (void)sig; | |
| (void)sender_name; | |
| (void)object_path; | |
| (void)interface; | |
| (void)signal_name; | |
| GVariantIter *interfaces; | |
| const char *object; | |
| const gchar *interface_name; | |
| char address[BT_ADDRESS_STRING_SIZE] = {'\0'}; | |
| g_variant_get(parameters, "(&oas)", &object, &interfaces); | |
| while(g_variant_iter_next(interfaces, "s", &interface_name)) { | |
| if(g_strstr_len(g_ascii_strdown(interface_name, -1), -1, "device")) { | |
| int i; | |
| char *tmp = g_strstr_len(object, -1, "dev_") + 4; | |
| for(i = 0; *tmp != '\0'; i++, tmp++) { | |
| if(*tmp == '_') { | |
| address[i] = ':'; | |
| continue; | |
| } | |
| address[i] = *tmp; | |
| } | |
| g_print("\nDevice %s removed\n", address); | |
| g_main_loop_quit((GMainLoop *)user_data); | |
| } | |
| } | |
| return; | |
| } | |
| static void bluez_signal_adapter_changed(GDBusConnection *conn, | |
| const gchar *sender, | |
| const gchar *path, | |
| const gchar *interface, | |
| const gchar *signal, | |
| GVariant *params, | |
| void *userdata) | |
| { | |
| (void)conn; | |
| (void)sender; | |
| (void)path; | |
| (void)interface; | |
| (void)userdata; | |
| GVariantIter *properties = NULL; | |
| GVariantIter *unknown = NULL; | |
| const char *iface; | |
| const char *key; | |
| GVariant *value = NULL; | |
| const gchar *signature = g_variant_get_type_string(params); | |
| if(strcmp(signature, "(sa{sv}as)") != 0) { | |
| g_print("Invalid signature for %s: %s != %s", signal, signature, "(sa{sv}as)"); | |
| goto done; | |
| } | |
| g_variant_get(params, "(&sa{sv}as)", &iface, &properties, &unknown); | |
| while(g_variant_iter_next(properties, "{&sv}", &key, &value)) { | |
| if(!g_strcmp0(key, "Powered")) { | |
| if(!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) { | |
| g_print("Invalid argument type for %s: %s != %s", key, | |
| g_variant_get_type_string(value), "b"); | |
| goto done; | |
| } | |
| g_print("Adapter is Powered \"%s\"\n", g_variant_get_boolean(value) ? "on" : "off"); | |
| } | |
| if(!g_strcmp0(key, "Discovering")) { | |
| if(!g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) { | |
| g_print("Invalid argument type for %s: %s != %s", key, | |
| g_variant_get_type_string(value), "b"); | |
| goto done; | |
| } | |
| g_print("Adapter scan \"%s\"\n", g_variant_get_boolean(value) ? "on" : "off"); | |
| } | |
| } | |
| done: | |
| if(properties != NULL) | |
| g_variant_iter_free(properties); | |
| if(value != NULL) | |
| g_variant_unref(value); | |
| } | |
| static int bluez_adapter_set_property(const char *prop, GVariant *value) | |
| { | |
| GVariant *result; | |
| GError *error = NULL; | |
| result = g_dbus_connection_call_sync(con, | |
| "org.bluez", | |
| "/org/bluez/hci0", | |
| "org.freedesktop.DBus.Properties", | |
| "Set", | |
| g_variant_new("(ssv)", "org.bluez.Adapter1", prop, value), | |
| NULL, | |
| G_DBUS_CALL_FLAGS_NONE, | |
| -1, | |
| NULL, | |
| &error); | |
| if(error != NULL) | |
| return 1; | |
| g_variant_unref(result); | |
| return 0; | |
| } | |
| static int bluez_set_discovery_filter(char **argv) | |
| { | |
| int rc; | |
| GVariantBuilder *b = g_variant_builder_new(G_VARIANT_TYPE_VARDICT); | |
| g_variant_builder_add(b, "{sv}", "Transport", g_variant_new_string(argv[1])); | |
| g_variant_builder_add(b, "{sv}", "RSSI", g_variant_new_int16(-g_ascii_strtod(argv[2], NULL))); | |
| g_variant_builder_add(b, "{sv}", "DuplicateData", g_variant_new_boolean(FALSE)); | |
| GVariantBuilder *u = g_variant_builder_new(G_VARIANT_TYPE_STRING_ARRAY); | |
| g_variant_builder_add(u, "s", argv[3]); | |
| g_variant_builder_add(b, "{sv}", "UUIDs", g_variant_builder_end(u)); | |
| GVariant *device_dict = g_variant_builder_end(b); | |
| g_variant_builder_unref(u); | |
| g_variant_builder_unref(b); | |
| rc = bluez_adapter_call_method("SetDiscoveryFilter", g_variant_new_tuple(&device_dict, 1), NULL); | |
| if(rc) { | |
| g_print("Not able to set discovery filter\n"); | |
| return 1; | |
| } | |
| rc = bluez_adapter_call_method("GetDiscoveryFilters", | |
| NULL, | |
| bluez_get_discovery_filter_cb); | |
| if(rc) { | |
| g_print("Not able to get discovery filter\n"); | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| GMainLoop *loop; | |
| int rc; | |
| guint prop_changed; | |
| guint iface_added; | |
| guint iface_removed; | |
| con = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, NULL); | |
| if(con == NULL) { | |
| g_print("Not able to get connection to system bus\n"); | |
| return 1; | |
| } | |
| loop = g_main_loop_new(NULL, FALSE); | |
| prop_changed = g_dbus_connection_signal_subscribe(con, | |
| "org.bluez", | |
| "org.freedesktop.DBus.Properties", | |
| "PropertiesChanged", | |
| NULL, | |
| "org.bluez.Adapter1", | |
| G_DBUS_SIGNAL_FLAGS_NONE, | |
| bluez_signal_adapter_changed, | |
| NULL, | |
| NULL); | |
| iface_added = g_dbus_connection_signal_subscribe(con, | |
| "org.bluez", | |
| "org.freedesktop.DBus.ObjectManager", | |
| "InterfacesAdded", | |
| NULL, | |
| NULL, | |
| G_DBUS_SIGNAL_FLAGS_NONE, | |
| bluez_device_appeared, | |
| loop, | |
| NULL); | |
| iface_removed = g_dbus_connection_signal_subscribe(con, | |
| "org.bluez", | |
| "org.freedesktop.DBus.ObjectManager", | |
| "InterfacesRemoved", | |
| NULL, | |
| NULL, | |
| G_DBUS_SIGNAL_FLAGS_NONE, | |
| bluez_device_disappeared, | |
| loop, | |
| NULL); | |
| rc = bluez_adapter_set_property("Powered", g_variant_new("b", TRUE)); | |
| if(rc) { | |
| g_print("Not able to enable the adapter\n"); | |
| goto fail; | |
| } | |
| if(argc > 3) { | |
| rc = bluez_set_discovery_filter(argv); | |
| if(rc) | |
| goto fail; | |
| } | |
| rc = bluez_adapter_call_method("StartDiscovery", NULL, NULL); | |
| if(rc) { | |
| g_print("Not able to scan for new devices\n"); | |
| goto fail; | |
| } | |
| g_main_loop_run(loop); | |
| if(argc > 3) { | |
| rc = bluez_adapter_call_method("SetDiscoveryFilter", NULL, NULL); | |
| if(rc) | |
| g_print("Not able to remove discovery filter\n"); | |
| } | |
| rc = bluez_adapter_call_method("StopDiscovery", NULL, NULL); | |
| if(rc) | |
| g_print("Not able to stop scanning\n"); | |
| g_usleep(100); | |
| rc = bluez_adapter_set_property("Powered", g_variant_new("b", FALSE)); | |
| if(rc) | |
| g_print("Not able to disable the adapter\n"); | |
| fail: | |
| g_dbus_connection_signal_unsubscribe(con, prop_changed); | |
| g_dbus_connection_signal_unsubscribe(con, iface_added); | |
| g_dbus_connection_signal_unsubscribe(con, iface_removed); | |
| g_object_unref(con); | |
| return 0; | |
| } |
I did some research and wrote a patch to allow SIGINT/SIGTERM:
diff -Naru a/bluez_adapter_filter.c b/bluez_adapter_filter.c
--- a/bluez_adapter_filter.c 2022-07-14 10:02:02.149982120 -0500
+++ b/bluez_adapter_filter.c 2022-07-14 09:50:01.549310011 -0500
@@ -13,6 +13,7 @@
* gcc `pkg-config --cflags glib-2.0 gio-2.0` -Wall -Wextra -o ./bin/bluez_adapter_filter ./bluez_adapter_filter.c `pkg-config --libs glib-2.0 gio-2.0`
*/
#include <glib.h>
+#include <glib-unix.h>
#include <gio/gio.h>
GDBusConnection *con;
@@ -278,6 +279,17 @@
return 0;
}
+gboolean TerminationsSignalCallback(gpointer data) {
+ g_print("Received a signal to terminate the daemon\n");
+ GMainLoop* loop = (GMainLoop*)(data);
+ g_main_loop_quit(loop);
+ /*
+ * This function can return false to remove this signal handler as we are
+ * quitting the main loop anyway.
+ */
+ return FALSE;
+}
+
int main(int argc, char **argv)
{
GMainLoop *loop;
@@ -345,6 +357,15 @@
goto fail;
}
+ /*
+ * It is unsafe to use most functions in UNIX signal handling
+ * functions, so it is best to use library functions designed
+ * for the purpose to terminate the program. Otherwise locking
+ * issues and re-entrancy problems occur.
+ * https://man7.org/linux/man-pages/man7/signal-safety.7.html
+ */
+ g_unix_signal_add(SIGINT,TerminationsSignalCallback,loop);
+ g_unix_signal_add(SIGTERM,TerminationsSignalCallback,loop);
g_main_loop_run(loop);
if(argc > 3) {
rc = bluez_adapter_call_method("SetDiscoveryFilter", NULL, NULL);
Also, when it prints out an entry RSSI is always "Other"
@jaklug You can fix this by adding the following to the bluez_property_value() function:
case 'n':
g_print("%d\n", g_variant_get_int16(value));
break;
(It's pretty simple and I'm not sure why it wasn't in all of the examples.)
and RSSI filter never works.
@parthitce I also confirmed that the RSSI filter never works. I also think the TransportType filter doesn't work.
I commented out the code for UUID-based filtration, since I didn't want it, and then I run as:
./bluez_adapter_filter bredr 60
And I see stuff like:
Address : 7B:6A:81:2C:BF:BB
AddressType : random
Alias : 7B-6A-81-2C-BF-BB
Paired : 0
Trusted : 0
Blocked : 0
LegacyPairing : 0
RSSI : -81
Connected : 0
UUIDs :
Adapter : /org/bluez/hci0
ManufacturerData : TxPower : 2
ServicesResolved : 0
AdvertisingFlags : [ /org/bluez/hci0/dev_4D_BC_A6_33_7E_54 ]
Address : 4D:BC:A6:33:7E:54
AddressType : random
Alias : 4D-BC-A6-33-7E-54
Paired : 0
Trusted : 0
Blocked : 0
LegacyPairing : 0
RSSI : -80
Connected : 0
UUIDs :
Adapter : /org/bluez/hci0
ManufacturerData : TxPower : 2
ServicesResolved : 0
AdvertisingFlags : [ /org/bluez/hci0/dev_0E_03_A1_D3_E6_53 ]
Address : 0E:03:A1:D3:E6:53
AddressType : random
Alias : 0E-03-A1-D3-E6-53
Paired : 0
Trusted : 0
Blocked : 0
LegacyPairing : 0
RSSI : -42
Connected : 0
UUIDs :
Adapter : /org/bluez/hci0
ManufacturerData : ServicesResolved : 0
AdvertisingFlags :
Which includes both non-BD/EDR randomized LE devices, and RSSIs that are lower than my filter threshold of 60 (I also tried "-60" on the CLI but that yielded the same results.)
@9293746 My bad, RSSI dbus type needs to be checked in the code IMO.
@jaklug
sigactionshould help register signals and handle in callback in asynchronous way