BlueZ Part 8: Understanding DBUS – PropertiesChanged – (7)

Introduction:

In our previous blog, we delved into DBus methods like Get and GetAll, unraveling the complexities of DBus type systems and how to decode them effectively. These methods allowed us to query the state of various properties on DBus interfaces, but this was just the beginning. Now, we’re going to take things a step further by moving from static queries to dynamic event handling. This shift opens up new possibilities for real-time interaction with Bluetooth devices and other system components.

Today, we’ll focus on one of the key features of DBus: asynchronous signal handling. Specifically, we’ll explore how the PropertiesChanged signal works, allowing us to receive automatic updates whenever a property changes on the BlueZ Bluetooth adapter or related interfaces. This is particularly useful for scenarios like connecting to a new Bluetooth device, where we’ll receive notifications immediately when the connection is established or when any property is updated.

PropertiesChanged:

As always, we will start with code,

#include <gio/gio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BLUEZ_BUS_NAME			"org.bluez"
#define ADAPTER_INTERFACE		"org.bluez.Adapter1"
#define ADAPTER_OBJECT_PATH		"/org/bluez/hci0"
#define PROPERTIES_CHANGED_SIGNAL	"PropertiesChanged"
#define ALIAS_PROPERTY			"Alias"

static void on_properties_changed(GDBusConnection *connection,
                      const gchar *sender_name,
                      const gchar *object_path,
                      const gchar *interface_name,
                      const gchar *signal_name,
                      GVariant *parameters,
                      gpointer user_data)
{
    (void)connection;
    (void)sender_name;
    (void)object_path;
    (void)interface_name;
    (void)signal_name;
    (void)user_data;

    GVariantIter *properties_iter;
    const gchar *key;
    GVariant *value;

    g_variant_get(parameters, "(sa{sv}as)", NULL, &properties_iter, NULL);

    while (g_variant_iter_next(properties_iter, "{sv}", &key, &value)) {
        if (g_strcmp0(key, ALIAS_PROPERTY) == 0) {
            const gchar *alias;
            alias = g_variant_get_string(value, NULL);
            printf("Property Changed: %s -> %s\n", key, alias);
        }
        g_variant_unref(value);
    }

    g_variant_iter_free(properties_iter);
}

static void set_alias_property(GDBusConnection *connection, const gchar *alias)
{
    GError *error = NULL;
    GVariant *result;

    result = g_dbus_connection_call_sync(connection,
            BLUEZ_BUS_NAME,
            ADAPTER_OBJECT_PATH,
            "org.freedesktop.DBus.Properties",
            "Set",
            g_variant_new("(ssv)",
                ADAPTER_INTERFACE,
                ALIAS_PROPERTY,
                g_variant_new_string(alias)),
            NULL,
            G_DBUS_CALL_FLAGS_NONE,
            -1,
            NULL,
            &error);

    if (error != NULL) {
        fprintf(stderr, "Error setting alias: %s\n", error->message);
        g_clear_error(&error);
    } else
        printf("Alias set to: %s\n", alias);

    g_variant_unref(result);
}

int main(int argc, char **argv)
{
    GDBusConnection *connection;
    GError *error = NULL;
    guint subscription_id;

    if (argc != 2) {
        fprintf(stderr, "%s <alias name>", argv[0]);
        return 1;
    }

    connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error);
    if (error != NULL) {
        fprintf(stderr, "Error connecting to system bus: %s\n", error->message);
        g_clear_error(&error);
        return 1;
    }

    subscription_id = g_dbus_connection_signal_subscribe(connection,
            BLUEZ_BUS_NAME,
            "org.freedesktop.DBus.Properties",
            PROPERTIES_CHANGED_SIGNAL,
            ADAPTER_OBJECT_PATH,
            ADAPTER_INTERFACE,
            G_DBUS_SIGNAL_FLAGS_NONE,
            on_properties_changed,
            NULL,
            NULL);

    /* Set the alias property */
    set_alias_property(connection, argv[1]);

    GMainLoop *loop = g_main_loop_new(NULL, FALSE);
    g_main_loop_run(loop);

    g_dbus_connection_signal_unsubscribe(connection, subscription_id);
    g_main_loop_unref(loop);
    g_object_unref(connection);

    return 1;
}

Except the (sa{sv}as) signature, all other parts of the code should be familiar by now. So we will stick with expanding it,The DBus signal PropertiesChanged typically uses the signature (sa{sv}as). This signature describes the structure of the data passed by the signal, which consists of several nested types. Let’s break it down:

Signature Breakdown

  1. ( ... ): This indicates that the signal carries a tuple (or a structure) containing several values grouped together.
  2. s (string): The first element in the tuple is a single string, which represents the interface name. In the case of BlueZ, this would be something like "org.bluez.Adapter1" or another interface related to Bluetooth properties.
  3. a{sv} (array of dict entries: string -> variant):
    • This is an associative array (or dictionary) where the key is a string (s), and the value is a variant (v).
    • Each key in this dictionary corresponds to the name of a property that has changed (e.g., "Alias", "Powered", "Connected").
    • The value for each key is a variant, which can hold any DBus-supported type. This allows flexibility since properties can have different data types (strings, integers, booleans, etc.). For example, the value for the key "Alias" might be a string, while "Powered" might be a boolean.
  4. as (array of strings):
    • The last part of the tuple is an array of strings, which represents a list of property names that were invalidated. These are properties that have become outdated or removed, and the signal informs the receiver that they are no longer valid. This array might be empty if no properties were invalidated.

Example

Here’s an example of how the data might look for a PropertiesChanged signal with this signature:

("org.bluez.Adapter1", 
 {
   "Alias": <"Linumiz">,
   "Powered": <true>
 }, 
 [])
  • "org.bluez.Adapter1": The interface name where the property change occurred.
  • {"Alias": "Linumiz", "Powered": true}: A dictionary containing the properties that changed, in this case, the Alias was updated to "Linumiz", and Powered was set to true.
  • []: An empty array of invalidated properties, meaning no properties were invalidated during this change.

Recap:

  • s: Interface name.
  • a{sv}: A dictionary of properties that changed (key-value pairs where the key is a string and the value is a variant).
  • as: A list of property names that were invalidated.

This format provides an efficient way to send detailed updates on multiple properties simultaneously while handling different data types and supporting dynamic updates.

UML:

All our code samples, including those related to DBus signal handling and BlueZ properties, are now available on our GitHub repository. Along with the code, we’ve added UML sequence diagrams that visualize the flow of the PropertiesChanged signal and other operations. These UML diagrams are included in .uml format, and we’ve made it easy to generate PNG images using the PlantUML command line. Whether you’re new to PlantUML or an experienced user, you can simply run the command and generate PNG representations of these diagrams for a clearer visual understanding of the process.

@startuml
actor User as u
participant Application as app #LightBlue
participant "DBus Connection" as dbus #LightYellow
participant "BlueZ Adapter" as bluez #LightGreen

u -> app: Run Application
activate app
app -> dbus: Connect to System Bus
activate dbus
dbus -> app: Connection established
deactivate dbus

app -> dbus: Subscribe to PropertiesChanged signal\n using g_dbus_connection_signal_subscribe
activate dbus
dbus -> app: Subscription confirmed
deactivate dbus

app -> dbus: Set Alias("Linumiz")\n using org.freedesktop.DBus.Properties.Set
activate dbus
dbus -> bluez: Set Alias("Linumiz")
activate bluez
bluez -> dbus: Confirm Alias set to "Linumiz"
deactivate bluez
dbus -> app: Return from Set Alias
deactivate dbus

note right of app: Alias set successfully \n in application

bluez -> dbus: Emit PropertiesChanged signal\n for Alias property
activate bluez
dbus -> app: Send PropertiesChanged signal
activate dbus
deactivate bluez
dbus -> app: PropertiesChanged signal received
deactivate dbus

app -> app: Handle PropertiesChanged callback
app -> u: Display "Property Changed: Alias -> Linumiz"
deactivate app
@enduml

Conclusion:

Handling asynchronous signals like PropertiesChanged adds a whole new dimension to working with BlueZ and DBus. Instead of querying for property states, we can now receive real-time updates on changes, enabling us to react dynamically to events like Bluetooth device connections and status changes. This approach not only streamlines our applications but also enhances their responsiveness and interactivity.

Written by

No Comment

Please Post Your Comments & Reviews

Your email address will not be published. Required fields are marked *