Skip to content

Instantly share code, notes, and snippets.

@parthitce
Last active November 17, 2022 22:58
Show Gist options
  • Save parthitce/5b83143d9ac0416024322e698d3ff415 to your computer and use it in GitHub Desktop.
Save parthitce/5b83143d9ac0416024322e698d3ff415 to your computer and use it in GitHub Desktop.
SetDiscoveryFilter + StartDiscovery: Control discovery filter and scan for new devices
/*
* 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;
}
@parthitce
Copy link
Author

SIGINT and make the program stop early

@jaklug sigaction should help register signals and handle in callback in asynchronous way

@jaklug
Copy link

jaklug commented Jul 14, 2022

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);

@9293746
Copy link

9293746 commented Sep 16, 2022

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.)

@parthitce
Copy link
Author

@9293746 My bad, RSSI dbus type needs to be checked in the code IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment