低功耗蓝牙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

相关文章

Python进阶之Excel基本操作介绍

《Python进阶之Excel基本操作介绍》在现实中,很多工作都需要与数据打交道,Excel作为常用的数据处理工具,一直备受人们的青睐,本文主要为大家介绍了一些Python中Excel的基本操作,希望... 目录概述写入使用 xlwt使用 XlsxWriter读取修改概述在现实中,很多工作都需要与数据打交

基于Python开发电脑定时关机工具

《基于Python开发电脑定时关机工具》这篇文章主要为大家详细介绍了如何基于Python开发一个电脑定时关机工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 简介2. 运行效果3. 相关源码1. 简介这个程序就像一个“忠实的管家”,帮你按时关掉电脑,而且全程不需要你多做

java脚本使用不同版本jdk的说明介绍

《java脚本使用不同版本jdk的说明介绍》本文介绍了在Java中执行JavaScript脚本的几种方式,包括使用ScriptEngine、Nashorn和GraalVM,ScriptEngine适用... 目录Java脚本使用不同版本jdk的说明1.使用ScriptEngine执行javascript2.

Redis主从/哨兵机制原理分析

《Redis主从/哨兵机制原理分析》本文介绍了Redis的主从复制和哨兵机制,主从复制实现了数据的热备份和负载均衡,而哨兵机制可以监控Redis集群,实现自动故障转移,哨兵机制通过监控、下线、选举和故... 目录一、主从复制1.1 什么是主从复制1.2 主从复制的作用1.3 主从复制原理1.3.1 全量复制

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

Python实现NLP的完整流程介绍

《Python实现NLP的完整流程介绍》这篇文章主要为大家详细介绍了Python实现NLP的完整流程,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 编程安装和导入必要的库2. 文本数据准备3. 文本预处理3.1 小写化3.2 分词(Tokenizatio

Redis主从复制的原理分析

《Redis主从复制的原理分析》Redis主从复制通过将数据镜像到多个从节点,实现高可用性和扩展性,主从复制包括初次全量同步和增量同步两个阶段,为优化复制性能,可以采用AOF持久化、调整复制超时时间、... 目录Redis主从复制的原理主从复制概述配置主从复制数据同步过程复制一致性与延迟故障转移机制监控与维

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

基于Qt开发一个简单的OFD阅读器

《基于Qt开发一个简单的OFD阅读器》这篇文章主要为大家详细介绍了如何使用Qt框架开发一个功能强大且性能优异的OFD阅读器,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 目录摘要引言一、OFD文件格式解析二、文档结构解析三、页面渲染四、用户交互五、性能优化六、示例代码七、未来发展方向八、结论摘要

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步