Linux蓝牙之使用C开发轻量级BLE应用
Linux蓝牙之使用C开发轻量级BLE应用
linux蓝牙协议栈bluez(https://www.bluez.org/),对于开发蓝牙应用提供了丰富的蓝牙开发工具和示例。
在Linux下面使用C开发蓝牙应用主要分为以下2种方式:
- 基于bluez的hci/mgmt接口,可更精细控制蓝牙硬件模块,适合开发轻量级BLE应用。
- 基于bluez的DBUS接口,提供了大量的蓝牙上层协议,能更好管理蓝牙,更适合开发需要丰富接口的重应用,比如音频、文件传输等。
这里主要讨论不使用dbus(DBUS对于一些简单的BLE应用,一些简单的Linux设备来说还是太“厚重”了),而是基于hci接口来开发简单的轻量级BLE应用。

具体实现
这里主要实现BLE最重要的两类工作模式:
- 中心设备/主机 (Central)
中心设备可以扫描外围设备,并在发现有外围设备存在后与之建立连接,之后就可以使用外围设备提供的服务(Service)。
一般而言,手机会担任中心设备的角色,利用外围设备提供的数据进行处理或展示等等。小程序提供低功耗蓝牙接口是默认设定手机为中心设备的。 - 外围设备/从机 (Peripheral)
外围设备一直处于广播状态,等待被中心设备搜索和连接,不能主动发起搜索。例如智能手环、传感器等设备。
如果外围设备广播时被设置为不可连接的状态,也被称为广播模式 (Broadcaster),常见的例子是蓝牙信标 (Beacon) 设备。
好像核外bluez协议栈没有针对BLE有独立的c语言api,所以主要思路就是参考核外协议栈bluez里面测试、工具的相关代码实现。
目前参考的是:
- Central端对应
tools/btgatt-client.c - Peripheral端对应
tools/btgatt-server.c
以上都已验证通过,并已在正常使用。
Central端
Central端主要功能包括,对从设备的连接,查到对应UUID的服务,并对相关的特征值进行读写,或者注册notify操作。
整个流程伪代码实现如下,主要都是通过回调方式实现:
static int *ble_client_task_thread(void *arg)
{
int sec = BT_SECURITY_LOW;
uint16_t mtu = 0;
uint8_t dst_type = BDADDR_LE_PUBLIC;
bdaddr_t src_addr, dst_addr;
int dev_id = -1;
int fd = -1;
ble_client_is_running = true;
bacpy(&src_addr, BDADDR_ANY);
if (str2ba("xx:xx:xx:xx:xx:xx", &dst_addr) < 0) {
LOG_ERROR("Invalid remote address: %s\n",
optarg);
return EXIT_FAILURE;
}
//mainloop初始化
mainloop_init();
//连接从机
fd = l2cap_le_att_connect(&src_addr, &dst_addr, dst_type, sec);
if (fd < 0)
return EXIT_FAILURE;
//创建客户端
cli = client_create(fd, mtu);
if (!cli) {
LOG_ERROR("client_create error!\n");
close(fd);
return EXIT_FAILURE;
}
LOG_INFO("Running GATT Client\n");
//print_services(cli);
//运行mainloop
//mainloop_run_with_signal(signal_cb, NULL);
mainloop_run();
LOG_INFO("Shutting down...\n");
//注销操作
client_destroy(cli);
close(fd);
return EXIT_SUCCESS;
}
client_create()主要是ATT、GATT初始化,注册一些GATT的回调函数,
static struct client *client_create(int fd, uint16_t mtu)
{
struct client *cli;
LOG_INFO("ble client_create.\n");
cli = new0(struct client, 1);
if (!cli) {
LOG_ERROR("Failed to allocate memory for client\n");
return NULL;
}
cli->att = bt_att_new(fd, false);
if (!cli->att) {
LOG_ERROR("Failed to initialze ATT transport layer\n");
bt_att_unref(cli->att);
free(cli);
return NULL;
}
if (!bt_att_set_close_on_unref(cli->att, true)) {
LOG_ERROR("Failed to set up ATT transport layer\n");
bt_att_unref(cli->att);
free(cli);
return NULL;
}
if (!bt_att_register_disconnect(cli->att, client_att_disconnect_cb, NULL,
NULL)) {
LOG_ERROR("Failed to set ATT disconnect handler\n");
bt_att_unref(cli->att);
free(cli);
return NULL;
}
cli->fd = fd;
cli->db = gatt_db_new();
if (!cli->db) {
LOG_ERROR("Failed to create GATT database\n");
bt_att_unref(cli->att);
free(cli);
return NULL;
}
cli->gatt = bt_gatt_client_new(cli->db, cli->att, mtu, 0);
if (!cli->gatt) {
LOG_ERROR("Failed to create GATT client\n");
gatt_db_unref(cli->db);
bt_att_unref(cli->att);
free(cli);
return NULL;
}
int ret = gatt_db_register(cli->db, service_added_cb, service_removed_cb,
NULL, NULL);
ret = bt_gatt_client_ready_register(cli->gatt, ready_cb, cli, NULL);
ret = bt_gatt_client_set_service_changed(cli->gatt, service_changed_cb, cli,
NULL);
/* bt_gatt_client already holds a reference */
gatt_db_unref(cli->db);
return cli;
}
上面最重要的就是bt_gatt_client_ready_register注册的回调函数ready_cb,在该函数中可以通过gatt_db_foreach_service函数遍历查找相应UUID的服务,找到后并执行对应的服务函数mpa_service, 在mpa_service就可以对相关特征值进行读写,或者通过bt_gatt_client_register_notify注册notify,这里就不赘述了。
static void ready_cb(bool success, uint8_t att_ecode, void *user_data)
{
struct client *cli = user_data;
if (!success) {
LOG_ERROR("GATT discovery procedures failed - error code: 0x%02x\n",
att_ecode);
return;
}
LOG_INFO("GATT discovery procedures complete\n");
print_services(cli);
bt_uuid_t tmp, uuid;
bt_string_to_uuid(&tmp, "0000fff0-0000-1000-8000-008022221111");
bt_uuid_to_uuid128(&tmp, &uuid);
gatt_db_foreach_service(cli->db, &uuid, mpa_service, cli);
}
Peripheral端
Peripheral端主要功能包括,一直处于广播状态,等待被中心设备搜索和连接,被中心设备进行读写操作或注册通知主动上报特征值。
整个流程伪代码实现如下,主要也都是通过回调方式实现:
static int *ble_server_task_thread(void *arg)
{
bdaddr_t src_addr;
int sec = BT_SECURITY_LOW;
uint8_t src_type = BDADDR_LE_PUBLIC;
int fd;
uint16_t mtu = 0;
struct server *server;
cmd_le_adv(0, 0, NULL);
bacpy(&src_addr, BDADDR_ANY);
//监听等待连接
fd = l2cap_le_att_listen_and_accept(&src_addr, sec, src_type);
if (fd < 0) {
LOG_ERROR("Failed to accept L2CAP ATT connection\n");
return EXIT_FAILURE;
}
//mainloop初始化
mainloop_init();
server = server_create(fd, mtu);
if (!server) {
close(fd);
LOG_ERROR("server_create error!!\n");
return EXIT_FAILURE;
}
gatt_db_foreach_service(server->db, NULL, print_service, server);
LOG_INFO("Running GATT server\n");
//mainloop运行
mainloop_run();
//注销操作
server_destroy(server);
return EXIT_SUCCESS;
}
server_create()主要是ATT、GATT初始化,构建相关服务,
static struct server *server_create(int fd, uint16_t mtu)
{
struct server *server;
size_t name_len = strlen(test_device_name);
server = new0(struct server, 1);
if (!server) {
fprintf(stderr, "Failed to allocate memory for server\n");
return NULL;
}
server->att = bt_att_new(fd, false);
if (!server->att) {
fprintf(stderr, "Failed to initialze ATT transport layer\n");
goto fail;
}
if (!bt_att_set_close_on_unref(server->att, true)) {
fprintf(stderr, "Failed to set up ATT transport layer\n");
goto fail;
}
if (!bt_att_register_disconnect(server->att, server_att_disconnect_cb, NULL,
NULL)) {
fprintf(stderr, "Failed to set ATT disconnect handler\n");
goto fail;
}
server->name_len = name_len + 1;
server->device_name = malloc(name_len + 1);
if (!server->device_name) {
fprintf(stderr, "Failed to allocate memory for device name\n");
goto fail;
}
memcpy(server->device_name, test_device_name, name_len);
server->device_name[name_len] = '\0';
server->fd = fd;
server->db = gatt_db_new();
if (!server->db) {
fprintf(stderr, "Failed to create GATT database\n");
goto fail;
}
server->gatt = bt_gatt_server_new(server->db, server->att, mtu, 0);
if (!server->gatt) {
fprintf(stderr, "Failed to create GATT server\n");
goto fail;
}
//构建服务
populate_db(server);
return server;
fail:
gatt_db_unref(server->db);
free(server->device_name);
bt_att_unref(server->att);
free(server);
return NULL;
}
static void populate_db(struct server *server)
{
//构建生成GAP服务
populate_gap_service(server);
//构建生成GATT服务
populate_gatt_service(server);
//构建生成配置服务
populate_config_service(server);
}
以上三个具体操作都比较类似,主要就是gatt_db_add_service新增服务,然后通过gatt_db_service_add_characteristic去增加特征值,通过gatt_db_service_add_descriptor去增加描述,最后gatt_db_service_set_active激活服务,其中特征值和描述都注册相应的读写回调函数。
这里就不再赘述,感兴趣的可自行去查看代码。
后续计划
- 基于mgmt接口来实现ble基本操作
参考
https://www.cnblogs.com/embedded-linux/p/18332058
https://blog.csdn.net/u014028690/article/details/107246633
https://blog.csdn.net/u014028690/article/details/114132989





