基于BlueZ 5.48编写GDBus BLE客户端:用gdbus-codegen生成代理与骨架
我之前在做BlueZ 5.48的BLE广告客户端时踩过不少坑,下面是完整的实现步骤,亲测可用:
首先得把完整的org.bluez.LEAdvertisement1接口XML补全,BlueZ 5.48的标准定义里还包含几个关键属性,我先整理好完整的XML内容,保存在le-advertisement.xml文件中:
<node> <interface name="org.bluez.LEAdvertisement1"> <method name="Release"> <annotation name="org.freedesktop.DBus.Method.NoReply" value="true"/> </method> <property name="Type" type="s" access="read"/> <property name="ServiceUUIDs" type="as" access="read"/> <property name="ManufacturerData" type="a{qv}" access="read"/> <property name="SolicitUUIDs" type="as" access="read"/> <property name="ServiceData" type="a{sv}" access="read"/> <property name="IncludeTxPower" type="b" access="read"/> <annotation name="org.freedesktop.DBus.Properties.PropertiesChanged" value="const"/> </interface> </node>
第一步:用gdbus-codegen生成代理和骨架代码
BlueZ的GDBus客户端依赖自动生成的DBus接口绑定,用gdbus-codegen工具可以直接生成C语言的代理(用于调用BlueZ服务)和骨架(用于实现自己的广告对象)。执行以下命令:
gdbus-codegen \ --generate-c-code le-advertisement \ --c-namespace Bluez \ --interface-prefix org.bluez. \ le-advertisement.xml
这条命令会生成两个文件:le-advertisement.h和le-advertisement.c,里面包含了BluezLEAdvertisement1相关的代理和骨架结构体、方法、属性操作函数。
第二步:编写客户端实现代码
接下来写实际的客户端逻辑,主要流程是:初始化GDBus连接、创建自己的LEAdvertisement对象、设置广告属性、通过BlueZ的LEAdvertisingManager1注册广告,以及处理Release方法调用(当BlueZ释放广告时触发)。
以下是完整的示例代码(保存为ble-advert-client.c):
#include <stdio.h> #include <stdlib.h> #include <gio/gio.h> #include "le-advertisement.h" #define BLUEZ_SERVICE_NAME "org.bluez" #define ADVERTISING_MANAGER_PATH "/org/bluez/hci0" #define ADVERTISEMENT_PATH "/org/example/LEAdvertisement" static void release_callback(BluezLEAdvertisement1 *skeleton, GAsyncResult *res, gpointer user_data) { GError *error = NULL; bluez_le_advertisement1_call_release_finish(skeleton, res, &error); if (error) { g_printerr("Release call failed: %s\n", error->message); g_error_free(error); } else { g_print("Advertisement released by BlueZ\n"); } } static void on_advertisement_registered(GObject *source_object, GAsyncResult *res, gpointer user_data) { GDBusProxy *manager_proxy = G_DBUS_PROXY(source_object); GError *error = NULL; g_dbus_proxy_call_finish(manager_proxy, res, &error); if (error) { g_printerr("Failed to register advertisement: %s\n", error->message); g_error_free(error); g_main_loop_quit(user_data); return; } g_print("Advertisement registered successfully!\n"); } int main(int argc, char *argv[]) { GMainLoop *loop; GDBusConnection *connection; BluezLEAdvertisement1 *advert_skeleton; GDBusProxy *advert_manager_proxy; GError *error = NULL; // 初始化GMainLoop loop = g_main_loop_new(NULL, FALSE); // 连接到系统总线 connection = g_bus_get_sync(G_BUS_TYPE_SYSTEM, NULL, &error); if (!connection) { g_printerr("Failed to connect to system bus: %s\n", error->message); g_error_free(error); return EXIT_FAILURE; } // 创建LEAdvertisement1骨架对象 advert_skeleton = bluez_le_advertisement1_skeleton_new(); // 设置广告属性 g_object_set(advert_skeleton, "type", "peripheral", "service-uuids", g_strv_new("0000180D-0000-1000-8000-00805F9B34FB", NULL), "include-tx-power", TRUE, NULL); // 导出广告对象到总线 if (!g_dbus_interface_skeleton_export(G_DBUS_INTERFACE_SKELETON(advert_skeleton), connection, ADVERTISEMENT_PATH, &error)) { g_printerr("Failed to export advertisement: %s\n", error->message); g_error_free(error); goto cleanup; } // 获取LEAdvertisingManager1代理 advert_manager_proxy = g_dbus_proxy_new_sync(connection, G_DBUS_PROXY_FLAGS_NONE, NULL, BLUEZ_SERVICE_NAME, ADVERTISING_MANAGER_PATH, "org.bluez.LEAdvertisingManager1", NULL, &error); if (!advert_manager_proxy) { g_printerr("Failed to create advertising manager proxy: %s\n", error->message); g_error_free(error); goto cleanup; } // 注册广告 g_dbus_proxy_call(advert_manager_proxy, "RegisterAdvertisement", g_variant_new("(oa{sv})", ADVERTISEMENT_PATH, NULL), G_DBUS_CALL_FLAGS_NONE, -1, NULL, on_advertisement_registered, loop); // 运行主循环 g_main_loop_run(loop); cleanup: g_object_unref(advert_manager_proxy); g_object_unref(advert_skeleton); g_object_unref(connection); g_main_loop_unref(loop); return EXIT_SUCCESS; }
第三步:编译和运行
编译时需要链接GIO和BlueZ的相关库,用以下命令:
gcc -o ble-advert-client ble-advert-client.c le-advertisement.c `pkg-config --cflags --libs gio-2.0`
运行前需要确保BlueZ服务正在运行,并且你的用户有足够的权限(可以用sudo运行,或者配置DBus权限文件):
sudo ./ble-advert-client
如果一切正常,你会看到Advertisement registered successfully!的提示,此时可以用bluetoothctl扫描到你的BLE广告。
关键注意事项
- 广告路径
ADVERTISEMENT_PATH必须符合DBus对象路径规范,一般用自定义的命名空间(比如/org/example/...) - BlueZ 5.48要求
Type属性必须设置为"peripheral"或"broadcast" - 如果需要添加厂商数据或服务数据,直接在
g_object_set里补充对应的属性即可,比如"manufacturer-data", g_variant_new("{qv}", 0x0001, g_variant_new_bytestring("test")), - 当BlueZ不再需要广告时(比如关闭蓝牙),会调用
Release方法,你可以在回调里做资源清理
内容的提问来源于stack exchange,提问作者sam

