低功耗蓝牙ble开发(一)——bluez介绍及源码分析

2024-06-15 04:44

本文主要是介绍低功耗蓝牙ble开发(一)——bluez介绍及源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

bluez-5.56源码

https://mirrors.edge.kernel.org/pub/linux/bluetooth/bluez-5.64.tar.xz

0、bluez5开发

​ bluez5主要提供基于HCI和基于DBUS的接口,基于HCI的接口主要用于更细致控制蓝牙硬件模块,而基于DBUS的接口提供大量的蓝牙上层协议,能更好的管理蓝牙。

不使用glib、dbus开发的话,想要开发周边设备可以借鉴btmgmt.c和btgatt-server.c、btgatt-client.c

使用glib、dbus开发的话,可以参考gatt-service.c

user space
APP:上层应⽤程序
Pluseaudio/pipewire:A2DP的组件
Bluetoothd: 蓝⽛守护进程
Bluez: 包括Bluez tool跟Bluez lib

kernel space
内核代码包含以下⼏部分
driver/bluetooth
net/bluetooth
include/net/bluetooth

在应用层,bluetoothd进程管理蓝牙,bluetooctl提供用户操作命令,通过dbus守护进程进行IPC通信,使用基于dbus的bluez开发主要就是调用bluez提供的dbus接口方法以及属性。

1、bluez5源代码组织架构

BlueZ5源代码中有很多目录和文件:

android/ - 用于替代android中bluedroid的android版本bluez源码。

attrib/ - 包含gatttool 源码以及与gatt attribute相关的代码,gatttool程序入口为gatttool.c。

btio/ - 通过的标准socket接口与BlueZ5 kernel模块通信?。

client/ - bluetoothctl源码,用于与 bluetoothd 交互,程序入口为main.c。

doc/ - BlueZ5 API文档。

emulator/ - 与bluetooth虚拟controller工具相关的代码。从字面意思可以推测出它是用来仿真的,通过阅读代码发现它是仿真controller的,通过btdev_create函数来创建一个虚拟的蓝牙controller设备。 kernel也是支持的,"/dev/vhci"这个设备被虚拟为一个虚拟的蓝牙设备。

gdbus/ - BlueZ5自带的内部gdbus源码。

gobex/ - Blue5自带的内部gobex库源码。

lib/ - libbluetooth.so 源码,提供BlueZ4 API,用来支持某些第三方应用。

monitor/ - btmon源码, 程序入口为main.c,用于监视和调试蓝牙流量。

obexd/ - obexd源码,程序入口为src/main.c。

peripheral/ - 与蓝牙ble的GATT相关的代码?。

plugins/ - BlueZ5插件源码(neard,autopair等插件)。包含 BlueZ 插件的源代码,这些插件扩展了 BlueZ 的功能,例如支持不同的蓝牙协议和配置文件。

profiles/ - BlueZ5蓝牙上层协议(a2dp,hid等)源码。每个子目录实现一个特定的蓝牙配置文件。

src/ - bluetoothd源码,程序入口为main.c,包含核心 BlueZ 守护进程 bluetoothd 的源代码。这里是主要的实现逻辑,包括设备管理、服务发现、配置和连接管理等。

​ src/shared/ - 包含在多个模块中共享的源代码。

​ src/adapter - 包含适配器管理相关的代码。

test/ - 测试蓝牙功能的py脚本,通过dbus接口和bluetoothd进行通信。 dbus提供支持python和c语言的接口。

tools/ - Bluez5测试工具集源码。包含各种命令行工具的源代码,例如 hcitoolgatttoolbtmgmt 等。这些工具用于与蓝牙设备进行交互和调试。也算是使用实列代码,可以参照工具的功能参考源码。

unit/ - PTS测试相关的一些代码?。

README / INSTALL - 配置,编译,安装Bluez5的说明。

Makefile.obexd - 定义obexd编译规则,此文件被include于Makefile.am中。

Makefile.plugins - 定义BlueZ5的plugins(neard,autopair等)的编译规则, 此文件被include于Makefile.am中。

Makefile.tools - 定义BlueZ5测试工具集的编译规则,此文件被include于 Makefile.am中。

Makefile.am - 定义了Bluez5的编译规则。用于automake工具,生成 Makefile.in文件。

Makefile.in - 用于configure脚本,生成最终的Makefile文件。

configure.ac - 用于autoconf工具,生成configure脚本。

configure - 配置编译选项,生成最终的Makefile文件,以及config.h文件。

2、bluez5核心代码分析

​ bluez核心代码在src目录下,入口函数是main.c,bluez5编译后会生成bluetoothd可执行文件,加载配置文件bluetooth.conf,下面进入main.c函数开始分析(注:bluez源码很多地方是异步操作的,看源码时经常需要根据关键词到处搜索注册的回调函数在哪里被触发)

(1)src/main.c/main函数分析
int main(int argc, char *argv[])
{……//glib库函数创建一个死循环线程用于处理事件event_loop = g_main_loop_new(NULL, FALSE);//注册信号接收处理ctrl+c等中断信号signal = setup_signalfd();……//gbus总线注册bluez服务端,该函数很重要,后面详细分析if (connect_dbus() < 0) {error("Unable to get on D-Bus");exit(1);}……//bluez控制器初始化,该函数很重要,后面详细分析if (adapter_init() < 0) {error("Adapter handling initialization failed");exit(1);}//bluez设备及应用层协议注册btd_device_init();btd_agent_init();btd_profile_init();……//开始死循环并处理事件g_main_loop_run(event_loop);……
}
(2)src/main.c/connect_dbus函数分析
static int connect_dbus(void)
{……//dbus是linux多进程通信的一种方式,bluez在dbus守护进程上注册了一个连接,用于接收其它bluez用户进程的消息,处理后并返回应答消息。具体的dbus实现原理需要同志们自己找资料理解,因为bluez源码依赖dbus编程。//清除dbus错误标志dbus_error_init(&err);//向总线型dbus守护进程申请名为BLUEZ_NAME ("org.bluez")的连接,其它进程可以使用dbus的dbus_message_new_method_call函数发消息给这个名为BLUEZ_NAME的连接。该函数很重要,后面详细分析conn = g_dbus_setup_bus(DBUS_BUS_SYSTEM, BLUEZ_NAME, &err);……//申请的连接的指针赋值给全局的连接指针set_dbus_connection(conn);//向dbus总线注册监听连接断开的信号,当连接断开时守护进程会发信号给这个已经注册的监听信号,然后回调disconnected_dbus函数处理断开连接g_dbus_set_disconnect_function(conn, disconnected_dbus, NULL, NULL);//向dbus总线注册bluez的管理对象,该对象的对象路径是”/”,用户可以编程调用该接口下面的方法获取bluez创建的所有对象以及每个对象下面的所有方法,该函数很重要,后面详细分析g_dbus_attach_object_manager(conn);……
}
(3)gdbus/mainloop.c/g_dbus_setup_bus函数分析
DBusConnection *g_dbus_setup_bus(DBusBusType type, const char *name,DBusError *error)
{……//向dbus总线申请一个连接conn = dbus_bus_get(type, error);……//向dbus总线申请给该连接起一个名字,其它进程就可以向改名字的连接发送dbus消息了。另外注册了一些dbus的观察函数,具体用处不是特别清楚if (setup_bus(conn, name, error) == FALSE) {……
}
(4)gdbus/object.c/g_dbus_attach_object_manager函数分析
gboolean g_dbus_attach_object_manager(DBusConnection *connection)
{……//向dbus总线的”/”路径的对象注册一个包含该对象的所有数据存放地址的指针,便于程序在其它地方使用dbus_connection_get_object_path_data函数通过”/”路径获取指针。该函数很重要,后面详细分析data = object_path_ref(connection, "/");……//该对象添加名为DBUS_INTERFACE_OBJECT_MANAGER宏定义的接口,接口注册了一个方法和一个信号,当其它进程使用dbus_message_new_method_call函数发消息给该对象的该接口方法时,generic_table里的generic_message会被调用,然后manager_methods会被调用,具体的调用流程后面会详细介绍add_interface(data, DBUS_INTERFACE_OBJECT_MANAGER,manager_methods, manager_signals,NULL, data, NULL);……
}
(5)gdbus/object.c/object_path_ref函数分析
static struct generic_data *object_path_ref(DBusConnection *connection,const char *path) 
{……//判断”/”路径的对象有没有已经申请存放该对象的存储空间,如果已经申请了,返回的data指针不为空if (dbus_connection_get_object_path_data(connection, path,(void *) &data) == TRUE) {……//向dbus总线注册该对象,当其它进程使用dbus_message_new_method_call函数发消息给该对象时,generic_table里的generic_message会被调用if (!dbus_connection_register_object_path(connection, path,&generic_table, data)) {……//把对象添加到对象链表data->objects里,便于程序在其它地方查找所有对象invalidate_parent_data(connection, path);……
}
(6)gdbus/object.c/generic_message函数分析
static DBusHandlerResult generic_message(DBusConnection *connection,DBusMessage *message, void *user_data)
{……Dbus接收从其他进程发来的消息,过滤出消息的接口名字interface = dbus_message_get_interface(message);//根据接口名字查找之前已经注册的对应接口iface = find_interface(data->interfaces, interface);……//检查完参数后开始处理消息return process_message(connection, message, method,iface->user_data);
}
(7)gdbus/object.c/process_message函数分析
static DBusHandlerResult process_message(DBusConnection *connection,DBusMessage *message, const GDBusMethodTable *method,void *iface_user_data)
{ ……//回调已经注册的接口对应的方法,具体使用例子在后面详细介绍reply = method->function(connection, message, iface_user_data);……//发送应答消息g_dbus_send_message(connection, reply);……
}
(8)src/adapter.c/adapter_init函数分析
int adapter_init(void)
{ ……//构造访问hci接口的结构体,该函数很重要,后面详细分析mgmt_master = mgmt_new_default();……//hci接口向内核注册的蓝牙控制器发送命令,收到对应返回命令时调用read_version_complete回调函数进行处理。if (mgmt_send(mgmt_master, MGMT_OP_READ_VERSION,MGMT_INDEX_NONE, 0, NULL,read_version_complete, NULL, NULL) > 0)
}
(9)src/shared/mgmt.c/mgmt_new_default函数分析
 struct mgmt *mgmt_new_default(void)
{……//使用socket打开hci驱动接口,和open访问驱动设备类似。明白这个需要自己去了解linux蓝牙驱动的相关知识,这里不介绍fd = socket(PF_BLUETOOTH, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK,BTPROTO_HCI);……//构造发送hci命令的结构体。该函数很重要,后面详细分析mgmt = mgmt_new(fd);
}
(10)/src/shared/mgmt.c/mgmt_new函数分析
struct mgmt *mgmt_new(int fd)
{……//使用glib库的io注册功能把fd描述符添加监控列表里,详情看bluez5.50/src/shared/io-glib.c/io_new函数的实现mgmt->io = io_new(fd);……//使用glib库的io功能添加监控fd读变化的功能,当hci接口有应答数据时,fd读操作状态改变,can_read_data回调函数被调用。详情看bluez5.50/src/shared/io-glib.c/ io_set_read_handler函数的实现if (!io_set_read_handler(mgmt->io, can_read_data, mgmt, NULL)) 
}
(11)src/shared/mgmt.c/can_read_data函数分析
static bool can_read_data(struct io *io, void *user_data)
{  ……//根据hci接口返回的应答数据的事件类型进行不同处理switch (event) {……//当返回的事件类型是命令完成时执行如下函数request_complete(mgmt, cc->status, opcode, index, length - 3,mgmt->buf + MGMT_HDR_SIZE + 3);……//当返回的事件类型是命令状态时执行如下函数request_complete(mgmt, cs->status, opcode, index, 0, NULL);……//其它事件时执行如下的通知函数,作用是通知已经注册到mgmt->notify_list链表的通知操作process_notify(mgmt, event, index, length,mgmt->buf + MGMT_HDR_SIZE);
}
(12)src/shared/mgmt.c/request_complete函数分析
static void request_complete(struct mgmt *mgmt, uint8_t status,uint16_t opcode, uint16_t index,uint16_t length, const void *param) 
{ ……//执行在hci接口发送命令时已经注册的命令完成回调函数,例如read_version_complete就是一个注册的回调函数request->callback(status, length, param, request->user_data);……//使用glib的io功能把fd的写功能添加进去,当fd能写时,把需要发送的hci命令发送给驱动wakeup_writer(mgmt);
}
(13)src/adapter.c/read_version_complete函数分析
static void read_version_complete(uint8_t status, uint16_t length,const void *param, void *user_data)
{  ……//注册蓝牙控制器个数增加时的通知信号,当驱动发现蓝牙控制器被插入时,发送事件通知命令,process_notify函数会被执行,在该函数中会调用已经注册的index_added函数申请新的适配器存储空间mgmt_register(mgmt_master, MGMT_EV_INDEX_ADDED, MGMT_INDEX_NONE,index_added, NULL, NULL);……//发送hci命令获取蓝牙适配器(蓝牙控制器)的个数,收到响应命令时回调read_index_list_complete函数if (mgmt_send(mgmt_master, MGMT_OP_READ_INDEX_LIST,MGMT_INDEX_NONE, 0, NULL,read_index_list_complete, NULL, NULL) > 0)……
}
(14)src/adapter.c/read_index_list_complete函数分析
static void read_index_list_complete(uint8_t status, uint16_t length,const void *param, void *user_data)
{     ……//获取适配器的个数,然后根据index值注册所有的适配器index_added(index, 0, NULL, NULL);……
}

(15)bluez5.50/src/adapter.c/index_added函数分析

static void index_added(uint16_t index, uint16_t length, const void *param,void *user_data)
{  ……//查找适配器链表adapter_list里有没有编号为index的适配器存在adapter = btd_adapter_lookup(index);……//根据index创建新的适配器adapter = btd_adapter_new(index);……//把新创建的适配器指针添加到适配器链表里,方便其它地方查询adapter_list = g_list_append(adapter_list, adapter);……//发送hci接口命令给驱动层,获取index对应的适配器的详细信息,读取到结果后会回调read_info_complete函数if (mgmt_send(mgmt_master, MGMT_OP_READ_INFO, index, 0, NULL,read_info_complete, adapter, NULL) > 0)……
}
(16)src/adapter.c/read_info_complete函数分析
static void read_info_complete(uint8_t status, uint16_t length,const void *param, void *user_data)
{   ……//设置适配器的bdaddr等,然后为适配器注册dbus对象,对象路径为"/org/bluez/hci%d",对象接口名为"org.bluez.Adapter1",接口方法为adapter_methodserr = adapter_register(adapter);……//注册设备发现函数,当驱动层扫描到新的蓝牙设备时,发送通知给应用层,应用层会触发can_read_data函数,然后调用到process_notify函数,然后调用到mgmt->notify_list链表,而device_found_callback函数是添加在mgmt->notify_list链表上的,所以会被执行mgmt_register(adapter->mgmt, MGMT_EV_DEVICE_FOUND,adapter->dev_id,device_found_callback,adapter, NULL);……
}
(17)src/adapter.c/adapter_register函数分析
static int adapter_register(struct btd_adapter *adapter)
{……//指定适配器注册到dbus总线上的对象的路径adapter->path = g_strdup_printf("/org/bluez/hci%d", adapter->dev_id);//为适配器注册dbus对象,对象路径为"/org/bluez/hci%d",对象接口名为"org.bluez.Adapter1",接口方法为adapter_methods,接口属性为adapter_propertiesif (!g_dbus_register_interface(dbus_conn,adapter->path, ADAPTER_INTERFACE,adapter_methods, NULL,adapter_properties, adapter,adapter_free)) {……//把当前适配器添加到adapters链表adapters = g_slist_append(adapters, adapter);……
}

这篇关于低功耗蓝牙ble开发(一)——bluez介绍及源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1062457

相关文章

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

使用Python开发一个带EPUB转换功能的Markdown编辑器

《使用Python开发一个带EPUB转换功能的Markdown编辑器》Markdown因其简单易用和强大的格式支持,成为了写作者、开发者及内容创作者的首选格式,本文将通过Python开发一个Markd... 目录应用概览代码结构与核心组件1. 初始化与布局 (__init__)2. 工具栏 (setup_t

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

Pytest多环境切换的常见方法介绍

《Pytest多环境切换的常见方法介绍》Pytest作为自动化测试的主力框架,如何实现本地、测试、预发、生产环境的灵活切换,本文总结了通过pytest框架实现自由环境切换的几种方法,大家可以根据需要进... 目录1.pytest-base-url2.hooks函数3.yml和fixture结论你是否也遇到过

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA