Linux下,使用C語言開發一個簡單的GATT Server,我的Ubuntu上跑的BlueZ版本是5.79,使用的GLib庫版本是2.85.2,這里我直接使用GLib里的D?Bus來實現與BlueZ通信。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};// 注冊 ObjectManagerg_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;}// 注冊 Serviceg_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 Characteristicg_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 Characteristicg_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);// 異步調用 RegisterApplicationg_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;
}