BlueZ 学习之GATT Server开发

发布于:2025-09-05 ⋅ 阅读:(15) ⋅ 点赞:(0)

Linux下,使用C语言开发一个简单的GATT Server,我的Ubuntu上跑的BlueZ版本是5.79,使用的GLib库版本是2.85.2,这里我使用GLib里的D‑Bus模块(gdbus)来实现与BlueZ通信。也可以使用其他的D‑Bus库。BlueZ 官方推荐通过 D-Bus 进行通信和控制,如果是要使用原始的hci接口来实现,可以参考BlueZ源码目录下的tools/btgatt-server.c 文件。

我这里使用的是usb蓝牙,支持经典蓝牙和BLE(低功耗蓝牙)模式,我直接通过bluez工具来开启BLE功能和BLE广播,常用的一些控制命令如下,做下记录:

### 经典蓝牙 ###

bluetoothctl                        # 运行 bluetoothctl
power on                            # 打开蓝牙适配器(开启蓝牙功能)
agent on                            # 启用配对助手(允许输入或确认配对码等操作)
scan on                             # 开始扫描附近的蓝牙设备
scan off                            # 停止扫描
pair 94:87:E0:8C:D5:94              # 与指定MAC地址的设备进行配对
connect 94:87:E0:8C:D5:94           # 连接到已配对的指定设备
trust 94:87:E0:8C:D5:94             # 设置信任该设备(以后自动连接无需确认)

sudo systemctl restart bluetooth    # 重启蓝牙服务
remove 94:87:E0:8C:D5:94            # 清除旧配对信息


### BLE ###

通过 bluetoothctl 控制

bluetoothctl power on          # 开启蓝牙适配器
bluetoothctl discoverable on   # 让本设备对其他设备可见
bluetoothctl pairable on       # 允许其他设备与本设备配对
bluetoothctl advertise on      # 开启BLE广播(让本设备可以被BLE客户端发现)

通过 btmgmt 控制

sudo btmgmt power on         # 开启蓝牙
sudo btmgmt le on            # 开启BLE(低功耗蓝牙)功能
sudo btmgmt advertising on   # 开启BLE广播
sudo btmgmt connectable on   # 允许连接
sudo btmgmt info             # 查看适配器支持和当前状态

源码如下 gatt-server.c:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <locale.h>

#include "gio/gio.h"

// ================== 蓝牙 GATT 应用的一些常量定义 ==================
#define BLUEZ_BUS_NAME "org.bluez"            // BlueZ 的 DBus 服务名
#define ADAPTER_PATH "/org/bluez/hci0"        // 默认蓝牙适配器路径(hci0)
#define APP_PATH "/org/example"               // 应用根路径
#define SERVICE_PATH APP_PATH "/service0"     // 自定义 GATT 服务路径
#define CHAR_READ_PATH SERVICE_PATH "/char0"  // 自定义 "只读" 特征路径
#define CHAR_WRITE_PATH SERVICE_PATH "/char1" // 自定义 "可写" 特征路径

// UUID 定义(随便写的示例 UUID)
static const gchar *SERVICE_UUID = "12345678-1234-5678-1234-56789abcdef0";
static const gchar *CHAR_READ_UUID = "12345678-1234-5678-1234-56789abcdef1";
static const gchar *CHAR_WRITE_UUID = "12345678-1234-5678-1234-56789abcdef2";

// 特征的访问权限标志
static const gchar *flags_read[] = {"read", NULL};
static const gchar *flags_write[] = {"write", NULL};

// ================== Introspection XML 定义 ==================
// 这些 XML 定义了 DBus 接口(ObjectManager、Service、Characteristic)

// ObjectManager 接口(用于告诉 BlueZ 我们有哪些对象)
static const gchar *object_manager_xml =
    "<node>"
    "  <interface name='org.freedesktop.DBus.ObjectManager'>"
    "    <method name='GetManagedObjects'>"
    "      <arg name='objects' type='a{oa{sa{sv}}}' direction='out'/>"
    "    </method>"
    "  </interface>"
    "</node>";

// GATT Service 接口
static const gchar *service_xml =
    "<node>"
    "  <interface name='org.bluez.GattService1'>"
    "    <property name='UUID' type='s' access='read'/>"
    "    <property name='Primary' type='b' access='read'/>"
    "  </interface>"
    "</node>";

// GATT Characteristic 接口(包含读写方法)
static const gchar *char_xml =
    "<node>"
    "  <interface name='org.bluez.GattCharacteristic1'>"
    "    <property name='UUID' type='s' access='read'/>"
    "    <property name='Service' type='o' access='read'/>"
    "    <property name='Flags' type='as' access='read'/>"
    "    <method name='ReadValue'>"
    "      <arg name='options' type='a{sv}' direction='in'/>"
    "      <arg name='value' type='ay' direction='out'/>"
    "    </method>"
    "    <method name='WriteValue'>"
    "      <arg name='value' type='ay' direction='in'/>"
    "      <arg name='options' type='a{sv}' direction='in'/>"
    "    </method>"
    "  </interface>"
    "</node>";

// ================== 全局只读特征的内容 ==================
#define READ_VALUE_MAX_LEN 256
static guint8 read_value_buffer[READ_VALUE_MAX_LEN] = "Hello"; // 初始内容
static gsize read_value_len = 5;                               // 当前长度
static pthread_mutex_t read_value_mutex = PTHREAD_MUTEX_INITIALIZER;

// 一个线程,用来从终端输入更新 "只读特征" 的内容
void *stdin_input_thread(void *arg)
{
    char input[READ_VALUE_MAX_LEN];
    while (1)
    {
        printf("请输入新的只读特征内容(按回车确认):\n");
        if (fgets(input, sizeof(input), stdin))
        {
            size_t len = strlen(input);
            if (len > 0 && input[len - 1] == '\n')
                input[len - 1] = '\0'; // 去掉换行

            pthread_mutex_lock(&read_value_mutex);
            read_value_len = strlen(input);
            if (read_value_len > READ_VALUE_MAX_LEN)
                read_value_len = READ_VALUE_MAX_LEN;
            memcpy(read_value_buffer, input, read_value_len);
            pthread_mutex_unlock(&read_value_mutex);

            printf("已更新只读特征内容:%s\n", input);
        }
    }
    return NULL;
}

// ================== DBus 方法调用处理 ==================
static void on_method_call(GDBusConnection *conn,
                           const gchar *sender,
                           const gchar *object_path,
                           const gchar *interface_name,
                           const gchar *method_name,
                           GVariant *parameters,
                           GDBusMethodInvocation *invocation,
                           gpointer user_data)
{
    // 打印调试信息
    g_print("\n*** METHOD CALL RECEIVED ***\n");
    g_print("Sender: %s\n", sender);
    g_print("Object Path: %s\n", object_path);
    g_print("Interface: %s\n", interface_name);
    g_print("Method: %s\n", method_name);
    g_print("*****************************\n");

    // ============= 处理 GetManagedObjects(告诉 BlueZ 我们有哪些对象)=============
    if (g_strcmp0(interface_name, "org.freedesktop.DBus.ObjectManager") == 0 &&
        g_strcmp0(method_name, "GetManagedObjects") == 0)
    {
        g_print("*** HANDLING GetManagedObjects ***\n");

        GVariantBuilder objects_builder;
        g_variant_builder_init(&objects_builder, G_VARIANT_TYPE("a{oa{sa{sv}}}"));

        // 添加 GATT Service 对象
        {
            GVariantBuilder service_props;
            g_variant_builder_init(&service_props, G_VARIANT_TYPE("a{sv}"));
            g_variant_builder_add(&service_props, "{sv}", "UUID", g_variant_new_string(SERVICE_UUID));
            g_variant_builder_add(&service_props, "{sv}", "Primary", g_variant_new_boolean(TRUE));

            GVariantBuilder service_interfaces;
            g_variant_builder_init(&service_interfaces, G_VARIANT_TYPE("a{sa{sv}}"));
            g_variant_builder_add(&service_interfaces, "{s@a{sv}}", "org.bluez.GattService1",
                                  g_variant_builder_end(&service_props));

            g_variant_builder_add(&objects_builder, "{o@a{sa{sv}}}", SERVICE_PATH,
                                  g_variant_builder_end(&service_interfaces));
        }

        // 添加 "只读特征" 对象
        {
            GVariantBuilder char_read_props;
            g_variant_builder_init(&char_read_props, G_VARIANT_TYPE("a{sv}"));
            g_variant_builder_add(&char_read_props, "{sv}", "UUID", g_variant_new_string(CHAR_READ_UUID));
            g_variant_builder_add(&char_read_props, "{sv}", "Service", g_variant_new_object_path(SERVICE_PATH));
            g_variant_builder_add(&char_read_props, "{sv}", "Flags", g_variant_new_strv(flags_read, -1));

            GVariantBuilder char_read_interfaces;
            g_variant_builder_init(&char_read_interfaces, G_VARIANT_TYPE("a{sa{sv}}"));
            g_variant_builder_add(&char_read_interfaces, "{s@a{sv}}", "org.bluez.GattCharacteristic1",
                                  g_variant_builder_end(&char_read_props));

            g_variant_builder_add(&objects_builder, "{o@a{sa{sv}}}", CHAR_READ_PATH,
                                  g_variant_builder_end(&char_read_interfaces));
        }

        // 添加 "可写特征" 对象
        {
            GVariantBuilder char_write_props;
            g_variant_builder_init(&char_write_props, G_VARIANT_TYPE("a{sv}"));
            g_variant_builder_add(&char_write_props, "{sv}", "UUID", g_variant_new_string(CHAR_WRITE_UUID));
            g_variant_builder_add(&char_write_props, "{sv}", "Service", g_variant_new_object_path(SERVICE_PATH));
            g_variant_builder_add(&char_write_props, "{sv}", "Flags", g_variant_new_strv(flags_write, -1));

            GVariantBuilder char_write_interfaces;
            g_variant_builder_init(&char_write_interfaces, G_VARIANT_TYPE("a{sa{sv}}"));
            g_variant_builder_add(&char_write_interfaces, "{s@a{sv}}", "org.bluez.GattCharacteristic1",
                                  g_variant_builder_end(&char_write_props));

            g_variant_builder_add(&objects_builder, "{o@a{sa{sv}}}", CHAR_WRITE_PATH,
                                  g_variant_builder_end(&char_write_interfaces));
        }

        // 返回对象列表
        GVariant *result = g_variant_new("(@a{oa{sa{sv}}})", g_variant_builder_end(&objects_builder));
        g_dbus_method_invocation_return_value(invocation, result);
        g_print("*** GetManagedObjects completed ***\n");
        return;
    }

    // ============= 处理 ReadValue 请求(读取只读特征的值)=============
    if (g_strcmp0(interface_name, "org.bluez.GattCharacteristic1") == 0 &&
        g_strcmp0(method_name, "ReadValue") == 0)
    {
        g_print("ReadValue called on %s\n", object_path);

        GVariant *value;
        pthread_mutex_lock(&read_value_mutex);
        value = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE,
                                          read_value_buffer, read_value_len, sizeof(guint8));
        pthread_mutex_unlock(&read_value_mutex);

        g_dbus_method_invocation_return_value(invocation, g_variant_new("(@ay)", value));
        return;
    }

    // ============= 处理 WriteValue 请求(写入数据到可写特征)=============
    if (g_strcmp0(interface_name, "org.bluez.GattCharacteristic1") == 0 &&
        g_strcmp0(method_name, "WriteValue") == 0)
    {
        GVariant *value_variant;
        GVariant *options_variant;
        g_variant_get(parameters, "(@ay@a{sv})", &value_variant, &options_variant);

        gsize len;
        const guint8 *data = g_variant_get_fixed_array(value_variant, &len, sizeof(guint8));

        g_print("WriteValue called on %s, data: ", object_path);
        for (gsize i = 0; i < len; i++)
        {
            g_print("%02X ", data[i]);
        }

        g_print(" string: %.*s\n", (int)len, (const char *)data);

        g_variant_unref(value_variant);
        g_variant_unref(options_variant);
        g_dbus_method_invocation_return_value(invocation, NULL);
        return;
    }

    // 未知方法
    g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
                                          G_DBUS_ERROR_UNKNOWN_METHOD,
                                          "Unknown method: %s", method_name);
}

// ================== 属性读取处理 ==================
static GVariant *on_get_property(GDBusConnection *conn,
                                 const gchar *sender,
                                 const gchar *object_path,
                                 const gchar *interface_name,
                                 const gchar *property_name,
                                 GError **error,
                                 gpointer user_data)
{
    g_print("Get property: %s.%s on %s\n", interface_name, property_name, object_path);

    // 处理 Service 属性
    if (g_strcmp0(interface_name, "org.bluez.GattService1") == 0)
    {
        if (g_strcmp0(property_name, "UUID") == 0)
            return g_variant_new_string(SERVICE_UUID);
        if (g_strcmp0(property_name, "Primary") == 0)
            return g_variant_new_boolean(TRUE);
    }

    // 处理 Characteristic 属性
    if (g_strcmp0(interface_name, "org.bluez.GattCharacteristic1") == 0)
    {
        if (g_strcmp0(object_path, CHAR_READ_PATH) == 0)
        {
            if (g_strcmp0(property_name, "UUID") == 0)
                return g_variant_new_string(CHAR_READ_UUID);
            if (g_strcmp0(property_name, "Service") == 0)
                return g_variant_new_object_path(SERVICE_PATH);
            if (g_strcmp0(property_name, "Flags") == 0)
                return g_variant_new_strv(flags_read, -1);
        }
        if (g_strcmp0(object_path, CHAR_WRITE_PATH) == 0)
        {
            if (g_strcmp0(property_name, "UUID") == 0)
                return g_variant_new_string(CHAR_WRITE_UUID);
            if (g_strcmp0(property_name, "Service") == 0)
                return g_variant_new_object_path(SERVICE_PATH);
            if (g_strcmp0(property_name, "Flags") == 0)
                return g_variant_new_strv(flags_write, -1);
        }
    }

    // 未知属性
    g_set_error(error, G_DBUS_ERROR, G_DBUS_ERROR_UNKNOWN_PROPERTY,
                "Unknown property: %s", property_name);
    return NULL;
}

// ================== 注册回调 ==================
static GMainLoop *main_loop = NULL;
static gboolean registration_success = FALSE;

static void on_register_application_done(GObject *source_object,
                                         GAsyncResult *res,
                                         gpointer user_data)
{
    GError *error = NULL;
    GVariant *result = g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), res, &error);

    if (result)
    {
        g_print("SUCCESS: GATT Application registered!\n");
        g_variant_unref(result);
        registration_success = TRUE;
    }
    else
    {
        g_printerr("RegisterApplication failed: %s\n", error->message);
        g_error_free(error);
        registration_success = FALSE;
    }
}

// ================== 主函数 ==================
int main(int argc, char *argv[])
{
    GError *error = NULL;

    // 设置本地化环境,不然 g_print 输出中文时乱码
    setlocale(LC_ALL, "");

    g_print("Connecting to system bus...\n");
    // 连接到系统总线
    GDBusConnection *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);
        return 1;
    }

    g_print("Parsing introspection XML...\n");
    GDBusNodeInfo *object_manager_info = g_dbus_node_info_new_for_xml(object_manager_xml, NULL);
    GDBusNodeInfo *service_info = g_dbus_node_info_new_for_xml(service_xml, NULL);
    GDBusNodeInfo *char_info = g_dbus_node_info_new_for_xml(char_xml, NULL);

    // 定义虚表(方法回调 + 属性回调)
    static const GDBusInterfaceVTable interface_vtable = {
        .method_call = on_method_call,
        .get_property = on_get_property,
        .set_property = NULL};

    // 注册 ObjectManager
    g_print("Registering ObjectManager at %s\n", APP_PATH);
    guint reg_id1 = g_dbus_connection_register_object(connection, APP_PATH,
                                                      object_manager_info->interfaces[0],
                                                      &interface_vtable, NULL, NULL, &error);
    if (!reg_id1)
    {
        g_printerr("Failed to register ObjectManager: %s\n", error->message);
        return 1;
    }

    // 注册 Service
    g_print("Registering Service at %s\n", SERVICE_PATH);
    guint reg_id2 = g_dbus_connection_register_object(connection, SERVICE_PATH,
                                                      service_info->interfaces[0],
                                                      &interface_vtable, NULL, NULL, &error);
    if (!reg_id2)
    {
        g_printerr("Failed to register Service: %s\n", error->message);
        return 1;
    }

    // 注册 Read Characteristic
    g_print("Registering Read Characteristic at %s\n", CHAR_READ_PATH);
    guint reg_id3 = g_dbus_connection_register_object(connection, CHAR_READ_PATH,
                                                      char_info->interfaces[0],
                                                      &interface_vtable, NULL, NULL, &error);
    if (!reg_id3)
    {
        g_printerr("Failed to register Read Characteristic: %s\n", error->message);
        return 1;
    }

    // 注册 Write Characteristic
    g_print("Registering Write Characteristic at %s\n", CHAR_WRITE_PATH);
    guint reg_id4 = g_dbus_connection_register_object(connection, CHAR_WRITE_PATH,
                                                      char_info->interfaces[0],
                                                      &interface_vtable, NULL, NULL, &error);
    if (!reg_id4)
    {
        g_printerr("Failed to register Write Characteristic: %s\n", error->message);
        return 1;
    }

    // 创建 GATT Manager 的代理
    GDBusProxy *manager_proxy = g_dbus_proxy_new_sync(connection,
                                                      G_DBUS_PROXY_FLAGS_NONE,
                                                      NULL,
                                                      BLUEZ_BUS_NAME,
                                                      ADAPTER_PATH,
                                                      "org.bluez.GattManager1",
                                                      NULL,
                                                      &error);
    if (!manager_proxy)
    {
        g_printerr("Failed to create GattManager1 proxy: %s\n", error->message);
        return 1;
    }

    g_print("Attempting to register GATT application...\n");

    // 启动输入线程(用于动态修改只读特征内容)
    pthread_t tid;
    pthread_create(&tid, NULL, stdin_input_thread, NULL);

    // 创建主循环
    main_loop = g_main_loop_new(NULL, FALSE);

    // 异步调用 RegisterApplication
    g_dbus_proxy_call(manager_proxy, "RegisterApplication",
                      g_variant_new("(oa{sv})", APP_PATH, NULL),
                      G_DBUS_CALL_FLAGS_NONE,
                      -1,
                      NULL,
                      on_register_application_done,
                      NULL);

    // 运行主循环(等待 DBus 事件)
    g_main_loop_run(main_loop);

    // 程序退出时的清理
    g_object_unref(manager_proxy);
    g_dbus_connection_unregister_object(connection, reg_id1);
    g_dbus_connection_unregister_object(connection, reg_id2);
    g_dbus_connection_unregister_object(connection, reg_id3);
    g_dbus_connection_unregister_object(connection, reg_id4);
    g_object_unref(connection);

    g_print("Program finished.\n");
    return registration_success ? 0 : 1;
}