-
-
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
sigaction
should help register signals and handle in callback in asynchronous way