BlueZ Part 7: Understanding DBUS – Get and GetAll properties – (6)

Introduction:

In our previous post, we explored the DBUS Set property using the org.freedesktop.DBus.Properties interface to control the power state of a BlueZ adapter, focusing on simple data types like the boolean Powered property. Now, we’re shifting our attention to the GetAll method, which allows us to retrieve all properties of a specified interface in a single call.

But there’s more—this time, we’re tackling complex types, such as arrays. Specifically, we’ll be looking at the UUIDs property of the BlueZ adapter interface, which lists the services supported by the adapter. Join us as we dive into retrieving and handling these complex properties, enhancing our understanding of DBUS and BlueZ interactions.

Get complex property:

As like our previous blog, we will directly jump into the code,

#include <gio/gio.h>
#include <glib.h>
#include <stdio.h>

#define BLUEZ_BUS_NAME			"org.bluez"
#define ADAPTER_INTERFACE		"org.bluez.Adapter1"
#define DBUS_PROPERTIES_INTERFACE	"org.freedesktop.DBus.Properties"
#define DBUS_METHOD_GET			"Get"
#define ADAPTER_UUIDS_PROPERTY		"UUIDs"
#define DEFAULT_ADAPTER_PATH		"/org/bluez/hci0"

static void get_uuids_from_adapter(const char *adapter_object_path)
{
    GDBusConnection *connection;
    GError *error = NULL;
    GVariant *reply;
    GVariant *uuids_variant;
    const gchar **uuids;
    int i;

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

    /* Call the Get method on the adapter's 'org.freedesktop.DBus.Properties' interface */
    reply = g_dbus_connection_call_sync(
            connection,
            BLUEZ_BUS_NAME,                     /* Bus name */
            adapter_object_path,                /* Object path */
            DBUS_PROPERTIES_INTERFACE,          /* Interface name */
            DBUS_METHOD_GET,                    /* Method name */
            g_variant_new("(ss)", ADAPTER_INTERFACE, ADAPTER_UUIDS_PROPERTY), /* Parameters */
            G_VARIANT_TYPE("(v)"),              /* Expected reply type */
            G_DBUS_CALL_FLAGS_NONE,             /* Call flags */
            -1,                                 /* Timeout (default) */
            NULL,                               /* Cancellable */
            &error                              /* Error */
            );
    if (error) {
        g_printerr("Error calling Get method: %s\n", error->message);
        g_error_free(error);
        g_object_unref(connection);
        return;
    }

    /* Parse the reply to extract the UUIDs */
    g_variant_get(reply, "(v)", &uuids_variant);
    g_variant_get(uuids_variant, "^a&s", &uuids);

    /* Print the UUIDs */
    for (i = 0; uuids[i] != NULL; i++)
        g_print("UUID[%d]: %s\n", i, uuids[i]);

    g_variant_unref(uuids_variant);
    g_variant_unref(reply);
    g_object_unref(connection);
}

int main(void)
{
    const char *adapter_path = DEFAULT_ADAPTER_PATH;

    /* Call the function to get UUIDs */
    get_uuids_from_adapter(adapter_path);

    return 0;
}

In here, the unique part is the array parsing and extracting the UUID’s to view it. All the remaining part of the code are mostly covered in one way or another in our previous blog posts.

As seen in our previous blog,

$ busctl introspect org.bluez /org/bluez/hci0 org.freedesktop.DBus.Properties 
NAME                            TYPE      SIGNATURE RESULT/VALUE FLAGS
.Get                            method    ss        v            -
.GetAll                         method    s         a{sv}        -
.Set                            method    ssv       -            -
.PropertiesChanged              signal    sa{sv}as  -            -

g_variant_get(reply, “(v)”, &uuids_variant);

  • This line extracts a variant from the reply message.
  • Return value of Get is a variant (which could contain anything like a complex type system).
  • The reply is typically a GVariant object containing the response from a DBUS method call.
  • The format string "(v)" indicates that we expect the reply to contain a single variant, which is then stored in uuids_variant.
$ busctl introspect org.bluez /org/bluez/hci0 org.bluez.Adapter1
NAME                  TYPE      SIGNATURE RESULT/VALUE                             FLAGS
.GetDiscoveryFilters  method    -         as                                       -
.RemoveDevice         method    o         -                                        -
.SetDiscoveryFilter   method    a{sv}     -                                        -
.StartDiscovery       method    -         -                                        -
.StopDiscovery        method    -         -                                        -
.Address              property  s         "A8:93:4A:DB:EA:16"                      emits-change
.AddressType          property  s         "public"                                 emits-change
.Alias                property  s         "parthiban"                              emits-change writable
.Class                property  u         7078156                                  emits-change
.Discoverable         property  b         false                                    emits-change writable
.DiscoverableTimeout  property  u         180                                      emits-change writable
.Discovering          property  b         false                                    emits-change
.ExperimentalFeatures property  as        -                                        emits-change
.Manufacturer         property  q         93                                       emits-change
.Modalias             property  s         "usb:v1D6Bp0246d054D"                    emits-change
.Name                 property  s         "parthiban"                              emits-change
.Pairable             property  b         true                                     emits-change writable
.PairableTimeout      property  u         0                                        emits-change writable
.PowerState           property  s         "on"                                     emits-change
.Powered              property  b         true                                     emits-change writable
.Roles                property  as        2 "central" "peripheral"                 emits-change
.UUIDs                property  as        12 "0000110e-0000-1000-8000-00805f9b34f… emits-change
.Version              property  y         10                                       emits-change

g_variant_get(uuids_variant, “^a&s”, &uuids);

Breaking Down the Format String:

  • ^: The caret (^) is a special modifier in GLib’s format strings. It indicates that the data being pointed to by the extracted pointer should not be freed automatically when the GVariant is freed. This is important for efficiency when dealing with large data or data that will be used beyond the lifetime of the GVariant. In this case, it means that the array of strings (uuids) won’t be automatically freed, and you will need to manage its memory manually.
  • a: The lowercase a stands for “array.” It tells g_variant_get that the next part of the format string describes the type of elements within an array. In this context, it indicates that the value being extracted is an array of items.
  • &s: The &s portion specifies that each element in the array is a string (s) and that the string is returned as a pointer to a constant character array (char*). The & here modifies the string type to indicate that the extracted string should be a pointer to the string data within the GVariant structure. This means the strings themselves are not copied but are pointers to the data inside the GVariant.

Summary of ^a&s:

  • ^: Do not free the array automatically.
  • a: Array of elements.
  • &s: Pointer to constant strings (C strings).

Why Use ^ and &?

  • Efficiency: Using ^ allows the program to avoid unnecessary copying or freeing of data. This is particularly useful when dealing with large arrays or when you want to maintain control over memory management.
  • Pointer Semantics: The & ensures that the function returns a pointer to the internal data of the GVariant, rather than copying the string data. This is more efficient when dealing with read-only data that doesn’t need to be modified or duplicated.

What Happens with g_variant_get(uuids_variant, "^a&s", &uuids)?

  • The function extracts an array of strings from uuids_variant.
  • The uuids pointer is set to point to this array.
  • The strings themselves are not copied; instead, uuids points directly to the strings stored within the GVariant.
  • The array itself and the strings in it will not be automatically freed when the GVariant is freed, giving you control over when and how to free this memory if needed.

This approach is both memory-efficient and ensures that you maintain control over the lifecycle of the extracted data. To add, you also use different approach to parse the array of strings, which might be useful in different scenarios.

GetAll adapter properties:

#include <gio/gio.h>
#include <glib.h>
#include <stdio.h>

#define BLUEZ_BUS_NAME			"org.bluez"
#define ADAPTER_INTERFACE		"org.bluez.Adapter1"
#define DBUS_PROPERTIES_INTERFACE	"org.freedesktop.DBus.Properties"
#define DBUS_METHOD_GET_ALL		"GetAll"
#define DEFAULT_ADAPTER_PATH		"/org/bluez/hci0"

static GDBusConnection* connect_to_system_bus(GError **error)
{
    GDBusConnection *connection;

    connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, error);
    return connection;
}

static GVariant* call_get_all_properties(GDBusConnection *connection, const char *adapter_object_path, GError **error)
{
    GVariant *reply;

    reply = g_dbus_connection_call_sync(
            connection,
            BLUEZ_BUS_NAME,                     /* Bus name */
            adapter_object_path,                /* Object path */
            DBUS_PROPERTIES_INTERFACE,          /* Interface name */
            DBUS_METHOD_GET_ALL,                /* Method name */
            g_variant_new("(s)", ADAPTER_INTERFACE), /* Parameters */
            G_VARIANT_TYPE("(a{sv})"),          /* Expected reply type */
            G_DBUS_CALL_FLAGS_NONE,             /* Call flags */
            -1,                                 /* Timeout (default) */
            NULL,                               /* Cancellable */
            error                               /* Error */
            );

    return reply;
}

static void print_adapter_properties(GVariant *properties)
{
    GVariantIter *iter;
    GVariant *value;
    gchar *key;

    g_variant_get(properties, "(a{sv})", &iter);
    while (g_variant_iter_loop(iter, "{&sv}", &key, &value))
    {
        gchar *value_str = g_variant_print(value, TRUE);
        g_print("Property: %s = %s\n", key, value_str);
        g_free(value_str);
    }
    g_variant_iter_free(iter);
}

static void get_all_adapter_properties(const char *adapter_object_path)
{
    GDBusConnection *connection;
    GError *error = NULL;
    GVariant *reply;

    connection = connect_to_system_bus(&error);
    if (error)
    {
        g_printerr("Error connecting to system bus: %s\n", error->message);
        g_error_free(error);
        return;
    }

    /* Call the GetAll method */
    reply = call_get_all_properties(connection, adapter_object_path, &error);
    if (error)
    {
        g_printerr("Error calling GetAll method: %s\n", error->message);
        g_error_free(error);
        g_object_unref(connection);
        return;
    }

    print_adapter_properties(reply);

    g_variant_unref(reply);
    g_object_unref(connection);
}

int main(void)
{
    const char *adapter_path = DEFAULT_ADAPTER_PATH;

    get_all_adapter_properties(adapter_path);

    return 0;
}

To be specific again, GetAll returns values in a{sv}, so explain that part in detail

g_variant_get(properties, "(a{sv})", &iter);

g_variant_get(properties, "(a{sv})", &iter);:

  • g_variant_get is a function from the GLib library used to extract data from a GVariant object.
  • The properties variable is expected to be a GVariant containing the result of the GetAll DBUS method call, which returns all properties of a specified interface.
  • The format string "(a{sv})" is used to specify the type of data in the properties variable:
    • a{sv}: This represents an array (a) of dictionary entries, where each entry is a key-value pair.
      • The key (s) is a string representing the property name.
      • The value (v) is a variant type that can hold any data type, representing the property value.
    • The parentheses () around a{sv} indicate that this is a single tuple containing the array of dictionary entries.
  • The &iter argument is a pointer to a GVariantIter, which is used to iterate over the dictionary entries (key-value pairs) in the array.

 

while (g_variant_iter_loop(iter, "{&sv}", &key, &value))
{
    gchar *value_str = g_variant_print(value, TRUE);
    g_print("Property: %s = %s\n", key, value_str);
    g_free(value_str);
}
  • while (g_variant_iter_loop(iter, "{&sv}", &key, &value)):
    • g_variant_iter_loop is a function that iterates over the elements in the GVariantIter (iter), extracting each key-value pair.
    • The format string "{&sv}" specifies the structure of each element in the dictionary:
      • &s: The key is a string (s) and is passed as a pointer.
      • v: The value is a variant (v), which can hold any type of data.
    • The key and value pointers are used to store the current key and value being iterated over.
    • The loop continues until all key-value pairs in the dictionary have been processed.
  • gchar *value_str = g_variant_print(value, TRUE);:
    • g_variant_print is a GLib function that converts a GVariant to a human-readable string representation.
    • value is the variant that holds the property value.
    • The TRUE argument specifies that the string representation should be as human-readable as possible.
    • The result is stored in value_str, a dynamically allocated string that represents the property value.
  • g_print("Property: %s = %s\n", key, value_str);:
    • g_print is a GLib function similar to printf, used for formatted output.
    • This line prints the property name (key) and its corresponding value (value_str) to the console.
  • g_free(value_str);:
    • g_free is used to free the memory allocated for value_str after it has been printed, preventing memory leaks.

Conclusion:

With this, we’ve thoroughly explored the Get, GetAll, and Set methods within the DBUS properties interface using BlueZ’s Adapter interface. These fundamentals provide a solid foundation for interacting with and managing Bluetooth adapters via DBUS. As we move forward, the next major step will be to delve into the PropertiesChanged signal and the ObjectManager interface, which will further enhance our understanding of how DBUS signals changes and manages objects in a more dynamic and responsive way.

Written by

1 Comment

  • BlueZ Part 7: Understanding DBUS – PropertiesChanged – (7) – Linumiz August 18, 2024 at 5:01 pm

    […] our previous blog, we delved into DBus methods like Get and GetAll, unraveling the complexities of DBus type systems […]

    Reply
  • Please Post Your Comments & Reviews

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