You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于BlueZ 5.48编写GDBus BLE客户端:用gdbus-codegen生成代理与骨架

编写适配BlueZ 5.48的GDBus客户端(基于LEAdvertisement1接口)

我之前在做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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:31:19