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 aGVariant
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 inuuids_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 theGVariant
is freed. This is important for efficiency when dealing with large data or data that will be used beyond the lifetime of theGVariant
. 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 lowercasea
stands for “array.” It tellsg_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 theGVariant
structure. This means the strings themselves are not copied but are pointers to the data inside theGVariant
.
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 theGVariant
, 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 theGVariant
. - 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 aGVariant
object.- The
properties
variable is expected to be aGVariant
containing the result of theGetAll
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 theproperties
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 key (
- The parentheses
()
arounda{sv}
indicate that this is a single tuple containing the array of dictionary entries.
- The
&iter
argument is a pointer to aGVariantIter
, 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 theGVariantIter
(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
andvalue
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 aGVariant
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 toprintf
, 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 forvalue_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.
1 Comment
[…] our previous blog, we delved into DBus methods like Get and GetAll, unraveling the complexities of DBus type systems […]