-
Notifications
You must be signed in to change notification settings - Fork 6
第4章 Bluetooth LE从机篇 千里之行,始于足下
- 4.1 Bluetooth LE地址类型
- 4.2 Bluetooth LE广播的基础概念
- 4.3 传统广播
-
4.4 扩展广播
- 4.4.1 工具软件
- 4.4.2 工作流程
- 4.4.3 实验一,可连接不可扫描非定向Bluetooth LE扩展广播
- 4.4.4 实验二,可连接不可扫描定向Bluetooth LE扩展广播
- 4.4.5 实验三,不可连接不可扫描定向Bluetooth LE扩展广播
- 4.4.6 实验四,不可连接不可扫描非定向Bluetooth LE链式扩展广播
- 4.4.7 实验五,不可连接不可扫描非定向Bluetooth LE周期扩展广播
- 4.4.8 实验六,不可连接不可扫描非定向Bluetooth LE链式周期扩展广播
- 4.4.9 实验七,不可连接不可扫描非定向Bluetooth LE匿名扩展广播
- 4.4.10 实验八,广播集
- 4.4.11 结语
- 4.5 Profile
在Bluetooth LE领域中,地址是每个Bluetooth LE设备不可或缺的重要组成部分,通过这个地址就可以找到并识别出这个Bluetooth LE设备,不然根本无法分清如果周边存在多个Bluetooth LE设备的话;那么,本章内容将细述Bluetooth LE地址类型及其作用。
众所周知,Bluetooth LE地址分成公有地址和随机地址,而随机地址又分静态地址和私有地址,同时私有地址又再分为可解析私有地址和不可解析私有地址,它们之间的关系如下图所示:
Bluetooth LE的公有地址,就类似于我们日常生活中的身份证号码,是全球唯一的且不可改变的,其具备如下几个特性:
-
为了保证Bluetooth LE地址的全球唯一性,其需要向IEEE购买,然后IEEE组织就会对应地分配公有地址给买家;
-
如果想要查询某Bluetooth LE设备的公有地址是哪家公司的,则可以点此链接查询,如ESP32-C3的每个芯片都自带有一个公有地址:
-
全球唯一且存在于Bluetooth LE设备的整个生命周期,都不会改变;
-
总长度为6个字节,其组成结构如下图所示:
- Company ID:IEEE分配的(最高有效位)
- Company Assigned:公司内部分配的(最低有效位)
从上述的ESP32-C3的公有地址可以看出,高24bits由于具备全球唯一性,所以可以被捉包器识别出来是哪一家公司,而低24bits则是乐鑫公司自己分配的;
除了公有地址类型之外,还有一个随机地址类型,其又分为静态地址(Static Address) 和 私有地址(Private Address),它们之间主要通过最高的2位有效位来区分,具体如下所示:
同样,其总长度也是48bits,但是最高的2位有效位是0b11
,组成结构如下图所示:
该地址类型的主要特点如下:
- 静态地址的随机部分至少有一个bit是0和1;
- 用户在满足上述条件的情况下,可以自行配置Bluetooth LE设备的静态地址;
- 静态地址在上电之后,不可中途改变;
- 只能在重新上电之后,才能改变静态地址的内容 (非强制性,也可以不改变);
- 如果Bluetooth LE设备的静态地址改变了,那么如果对端设备存放了之前的静态地址,这就会导致重连旧的地址时失败;
BLE的私有地址又分为不可解析私有地址(Non-Resolvable Private Address) 和 可解析私有地址(Resolvable Private Address),它们之间的主要区别如下:
该类型的地址的长度大小同样为48bits,最高的2位有效位是0b10
,其组成结构如下图所示:
该类型的地址所对应的特点如下:
- 上图中显示的随机部分 (prand) 的内容至少有一个bit是0和1;
- 该地址会周期性变化,蓝牙规范推荐15分钟更新一次,但不要超过1小时
其中,hash = ah(IRK, prand)
,prand则是设备本地随机产生的24bits数据,而IRK值可以是在设备本地随机产生或者在设备生产时分配。
顾名思义,就是说该私有地址不可以被解析,其组成结构如下图所示:
同样,其总长度为48bits,但是最高的2位有效位是0b00
,其对应的特点如下:
- 随机部分的内容至少有一个bit是0和1;
- 不可解析私有地址不能与公有地址相同;
- 该地址会周期性变化;蓝牙规范推荐15分钟更新一次,但不要超过1小时
介绍完上述不同类型的地址之后,我相信读者更多地想知道如何使用这些类型的地址,或者说在什么场景下使用这些类型的地址。
该类型的地址是全球唯一且固定的,理论上重复的概率基本为0,因为它们是由IEEE组织分配给买家的,适用于为保证BLE设备地址的唯一性,方便其他人辩别,但缺点是这些地址是需要付费的。
该类型的地址,是为了方便各Bluetooth LE设备生产商自行生成自己的Bluetooth LE地址;因为这种类型的地址是免费的,不需要向IEEE组织购买,同时在一定范围内还能保证地址的唯一性。
可解析私有地址的目的是防止恶意第三方跟踪蓝牙设备,同时仍允许一个或多个受信任方识别感兴趣的Bluetooth LE设备。而可解析的意思就是说:
Bluetooth LE设备自身产生的IRK与受信任方分享之后,当受信任方的Bluetooth LE设备接收到可解析私有地址,就会提取出
prand
值再与对端设备分享的IRK
,进行ah计算得出一个localHash
值,与可解析私有地址中的hash
值进行比对,如果匹配上了那么对等设备的身份就已经解析。
其中,受信任方就是绑定的设备,而在绑定期间每个Bluetooth LE设备,均会储存对端设备的IRK到被称为可解析列表中;换句话说,为了解析可解析私有地址的设备需要与其进行绑定,获取得到IRK才可以解析。
相较于可解析私有地址,该类型的地址是不可解析的,所以这种类型的地址不太常用,但是有时会在一些Beacon的应用上使用;此类地址的唯一目的是防止任何其他Bluetooth LE设备进行跟踪。
Bluetooth LE设备的地址必须是公有地址或者静态地址,而不可解析和可解析地址是可选的。换句话说,即使使用了不可解析或者可解析地址,Bluetooth LE设备还必须仍然存在公有地址或者静态地址,也就是此时Bluetooth LE设备有两种类型的地址,因为不可解析和可解析地址仅用于解决隐私问题。
同时,我们无法直接从地址上判断,当前这个地址是上述的哪一种类型的地址,还需要结合公有地址和随机地址类型来组合判断。
众所周知,当我们在接触Bluetooth LE时,最先接触到的就是Bluetooth LE广播;自蓝牙规范v5.0开始,就对Bluetooth LE的广播特性做了大量地升级并新增了很多相关的特性,如扩展广播、周期广播、链式广播、广播集等;因此,本章内容将着重讲解Bluetooth LE广播的基础概念,如广播事件、广播PDUs、广播PHYs、广播间隔等等,让大家对Bluetooth LE广播有一个更加深入的了解。
一般我们常见到的都是不同类型的广播包,但很少有人会提及什么是广播事件、广播事件与广播包又是什么关系?其实它们都是Bluetooth LE广播的重要组成部分;截止目前蓝牙规范v5.3,一共有如下3种广播事件:
- 传播广播事件
- 扩展广播事件
- 周期广播事件
接下来的子章节将给大家细述广播事件的具体内容。
所谓传统广播事件(Advertising Event),指的是在广播通道 (37,38,39) 上发送的一个或多个广播 PDUs,而日常所说的广播包,其实就是由不同的广播事件组成;所以,它们之间的关系是如下:
从上图中,我们可以很清楚地看到该广播事件是由3个ADV_IND广播PDUs组成,而多个广播事件则组成了平时我们所看到的广播包,其中两个广播事件之间的时间间隔叫广播间隔。
扩展广播事件(Extended Advertising Event),指的是由在广播通道(37,38,39)上发送的一个或多个ADV_EXT_IND PDUs,以及在次级广播通道(0~36)上发送的子集PDUs组成。
跟传统广播事件不同的是,扩展广播事件除了在广播通道(37,38,39)发送PDUs,还在次级广播通道(0~36)上也发送PDUs。通过上图可知,该扩展广播事件由3个ADV_EXT_IND加上1个AUX_ADV_IND广播PDUs组成;但是,如果一个AUX_ADV_IND不足以放下剩余的广播PDUs,那么就会在AUX_ADV_IND后面接着发AUX_CHAIN_IND,直至所有的广播PDUs发送完成,才算扩展广播事件结束。
周期广播事件(Periodic Advertising Event),指的是由在次级广播通道(0~36)上发送的AUX_SYNC_IND和其子集PDUs组成。
由上图中,我们可以看出该周期广播事件开始于第一个AUX_SYNC_IND,而止于其子集的最后一个AUX_CHAIN_IND发送完成。
我们或多或少都知道,有的Bluetooth LE广播是可以连接,而有的则无法与其建立连接;原因就在于Bluetooth LE广播是由多个广播事件组成的,而广播事件的类型又是多种多样的,从而组合成不同类型的Bluetooth LE广播;其中,不同类型的广播事件则由下述的不同类型组成:
-
可连接
表示可以响应连接请求
-
不可连接
表示不响应连接请求
-
可扫描
表示可以响应扫描请求
-
不可扫描
表示不响应扫描请求
-
定向
表示只响应指定设备的请求
-
非定向
表示可以响应任意设备的请求
而上述的这些类型,组合成不同类型的广播事件,如下:
- 可连接可扫描非定向广播事件
- 可连接不可扫描非定向广播事件
- 可连接不可扫描定向广播事件
- 高占空比
- 低占空比
- 不可连接不可扫描非定向广播事件
- 不可连接不可扫描定向广播事件
- 不可连接可扫描非定向广播事件
- 不可连接可扫描定向广播事件
截止蓝牙规范v5.3,上述的广播事件类型已经覆盖了所有的广播事件,包括不同广播事件所组合成的周期广播、扩展广播和链式广播。
为了阐述可连接与不可连的区别,这里以可连接可扫描非定向广播事件为例,当发送完广播PDU并经过150±2us之后,如果接收到连接请求时,则马上进入连接态;而对于不可连接的广播事件,要么经过150±2us之后直接无视连接请求,要么发送完当前的广播PDU,然后马上切换到下一个广播信道,具体如下图所示:
这里需要强调的是,当接收到连接请求并响应的话,那么该广播事件就马上终止,即便还有广播PDU没有发送出去,如上图中39广播信道的广播PDU则不需要再发送了。
在只考虑扫描请求的前提下,可扫描与不可扫描的最大区别就是,不可扫描广播事件在发送完广播PDU之后,紧接着就切换到下一个广播信道了;而可扫描广播事件,则需要在150±2us之后等待可能到来的扫描请求,如果有的话还要回复扫描响应包;所以一般不可扫描广播事件会比可扫描广播事件的功耗更低,具体如下所示:
蓝牙规范规定,如果有扫描请求PDU的话,那么在发送广播PDU之后,就会在150±2us之后到来;同理,在150±2us之后也会将扫描响应PDU回复对端设备。
顾名思义,该类型的广播事件则具有一定的指向性,只响应指定目标地址设备的连接请求或者扫描请求**(不可连接不可扫描定向广播事件除外)**,对于不是指定设备的请求一概忽视;其中,定向广播事件还分低占空比和高占空比两种模式,低占空比模式除了需要携带指定设备的地址之外,在时序上与其他的跟普通广播事件是一样的,而高占空比模式则如下图所示:
可以看出,高占空比模式下广播包的密度非常高,常用于快速重连,最长也只维持1.28秒,否则会占用有限的广播信道资源。而且高占空比的广播事件仅适用于可连接不可扫描定向广播事件。
所谓广播过滤策略,指的是链路层(Link Layer)是否处理扫描请求和/或连接请求,其作用域及策略分别如下:
- 作用域: 不可连接不可扫描、可扫描定向以及可连接定向等广播事件除外
- 策略:
- 链路层处理所有的扫描和连接请求(默认)
- 链路层处理所有的连接请求和仅处理筛选列表(Filter Accept List)中的扫描请求
- 链路层处理所有的扫描请求和仅处理筛选列表(Filter Accept List)中的连接请求
- 链路层仅处理仅处理筛选列表(Filter Accept List)中的连接和扫描请求
也就是说对于定向广播事件,广播过滤策略是无效的,否则定向就变得没有意义了。
从上述的不同广播事件中可知,两个广播事件之间的时间间隔叫广播间隔,但蓝牙规范针对广播间隔有如下的要求:
-
非定向广播和低占空比的定向广播
范围为20ms ~ 10.21s
-
周期广播
范围为7.5 ms ~ 81.91875 s
-
高占空比的定向广播
小于等于3.75ms且广播总时长不超过1.28s,仅限可连接不可扫描定向广播事件
其中,我们使用频次比较高的就是非定向广播和低占空比的定向广播 (为了让广播更加地离散,它们还会在配置的广播间隔值上加上0~10ms的随机延时) ,而且它们涵盖了部分扩展广播;但是,周期广播和高占空比的定向广播则不会有0~10ms的随机延时的机制,至于它们之间的关系在下面的章节细述。
由上述的4.2.1 广播事件和4.2.1.4 广播事件类型可知,广播事件是由多个广播PDUs组成,但是不同类型的广播事件,其所对应的广播PDUs是不一样的。换句话说,有些广播PDUs仅适用于一部分类型的广播事件,它们之间的关系如下:
从上表中,可以很清楚地看到总共有如下几种广播PDUs:
-
传播广播PDUs
- ADV_IND
- ADV_DIRECT_IND
- ADV_NONCONN_IND
- ADV_SCAN_IND
-
扩展广播PDUs
- ADV_EXT_IND
- AUX_ADV_IND
- AUX_SYNC_IND
- AUX_CHAIN_IND
其中,AUX_SYNC_IND和AUX_CHAIN_IND只能适用于不可连接不可扫描的广播事件,而且所有的扩展广播PDUs均不适用于可连接可扫描的非定向广播事件。同时,SCAN_REQ、AUX_SCAN_REQ、CONNECT_IND、AUX_CONNECT_REQ等请求PDUs,也是只适用于特定的广播事件,如由AUX_ADV_IND广播PDU组成的可连接不可扫描非定向广播事件,其仅响应AUX_CONNECT_REQ请求PDU。
在蓝牙规范v4.2之前(包括v4.2)的广播,我们可以称之为传统广播;而其所对应的PDUs的组成结构也比较简单,如下图所示:
从上图中可以看出,除了可连接不可扫描定向广播事件不携带广播数据之外,其他的广播事件均携带广播数据;其中,每个传统广播PDU仅对应唯一的一个广播事件。
扩展广播是从蓝牙规范v5.0开始才新增的内容,所以蓝牙规范v5.0开始新增的广播,我们称之为扩展广播,其组成结构相比于传统广播则要复杂很多,如下图所示:
上述的表格中,很清楚地展示了所有的扩展广播均起于ADV_EXT_IND,也就是说AUX_ADV_IND、AUX_SYNC_IND、AUX_CHAIN_IND都是跟在其后面的,如果有的话。那么,它们之间组成的扩展广播,就有如下几种组合:
-
ADV_EXT_IND,不携带任何辅助广播PDU
此时,ADV_EXT_IND不携带任何的辅助数据,只在37,38,39三个广播信道上发送ADV_EXT_INDs,这种类型的扩展广播很少用。
-
ADV_EXT_IND--->AUX_ADV_IND,只携带一个辅助广播PDU
在37,38,39三个广播信道上发送的ADV_EXT_INDs,携带有下一个在数据通道传输的辅助广播PDU信道、隔多长时间在数据通道传输辅助广播PDU等信息,紧接着就会在指定的数据通道上继续传输辅助广播PDU,详情可以参考4.4.3 实验一,可连接不可扫描非定向Bluetooth LE扩展广播,巩固上述的了解。
-
ADV_EXT_IND--->AUX_ADV_IND--->AUX_CHAIN_IND……,携带一个辅助广播PDU + 若干个链式广播PDUs
上面的图示,可以很清楚地看到当需要发送的辅助广播PDUs,一个辅助PDU不够填充的话,可以接着以AUX_CHAIN_IND继续在数据通道上传输直至所有的辅助广播PDUs发送完成,详情可以参考4.4.6 实验四,不可连接不可扫描非定向Bluetooth LE链式扩展广播了解更多的内容。
-
ADV_EXT_IND--->AUX_ADV_IND--->AUX_SYNC_IND,只携带一个辅助广播PDU + 一个周期广播PDU
所谓周期扩展广播间隔,其实就是两个AUX_SYNC_IND之间的时间间隔,所以其可以小于正常的广播间隔,也就是图示中所述的广播间隔,但也可以大于正常的广播间隔;比如广播间隔是100ms,那么周期扩展广播也可以是25ms,但也可以是150ms,只不过此时广播间隔只有一个AUX_SYNC_IND,详情可以参考4.4.7 实验五,不可连接不可扫描非定向Bluetooth LE周期扩展广播。
-
ADV_EXT_IND--->AUX_ADV_IND--->AUX_SYNC_IND--->AUX_CHAIN_IND……,只携带一个辅助广播PDU + 一个周期广播PDU + 若干个链式广播PDUs
同理,当一个周期广播PDU不够填充的话,可以接着以AUX_CHAIN_IND继续在数据通道上传输直至所有的辅助广播PDUs发送完成,详情可以参考4.4.8 实验六,不可连接不可扫描非定向Bluetooth LE链式周期扩展广播。
通过前面的内容,我们知道蓝牙规范v5.0之后,新增了2M和CODED PHY,所以传统的广播就不适用于新的PHY,但是扩展广播则支持新的PHY,它们之间的关系如下:
上表中可以看出辅助广播PDU、周期广播PDU、链式广播PDU支持所有的PHY,而其他类型的PDUs仅支持1M的PHY。
平时我们通过手机app所看到的不同广播包,它们都是解析之后呈现给用户看的,实际上它们在空中的包要比看到的复杂很多。同时,为了更好地了解广播包的具体构造,以便在后续篇章中改写广播包内容时,不知道从哪里入手,接下来的章节将分别对传统和扩展广播的帧格式进行剖析。
传统广播包的帧格式相对比较简单,主要的广播数据是由不同的广播数据结构组成,而广播数据结构则是由不同的长度+对应长度的数据组成,而最后的数据则是由不同的广播数据类型+广播数据内容组成,具体如下:
需要注意的是,传统的可连接不可扫描定向广播是不携带广播数据内容的,它只有广播地址和目标地址。
同样的,扩展广播除了新增的扩展头长度、广播模式、扩展头等字段之外,剩下的广播数据字段的组成结构与传统广播包是一样的,只是支持的广播数据内容要比传播广播包多的多;此外,扩展广播包中的扩展头里面,还可以携带一定数量的额外的控制层广播数据,最终的帧数据格式如下图所示:
虽说用户可以自由地更改广播数据内容,但这不意味着可以随便填充任意内容的广播数据,它仍然需要符合一定的规则,这也就是该小节中所说的广播数据类型,如下图所示:
上表中清楚地表明,当前只支持上述数据类型的广播数据内容变更,如数据类型为本地名称、广播间隔等等。但凡表格中没有的数据类型,如果强行更改则会出现无法识别的问题。
截止目前蓝牙规范v5.3,涉及到的相关广播内容在本文中均有覆盖,其中有广播事件及其类型、广播PDUs、广播PHYs、广播包帧格式等内容,以及它们之间的关联;而且还着重讲解了蓝牙规范v5.0新增的扩展广播相关内容,为下一篇章内容的理解和后续的实验打下坚实的基础。
前面的两篇文章讲解了Bluetooth LE广播的一些基本概念,让大家对广播有一个初步的了解;那么,本篇文章将着重讲解如何实例化传统广播,从上述的4.2.1.4 广播事件类型可知,传统广播分别由如下几个广播事件组成:
- 可连接可扫描非定向Bluetooth LE广播事件
- 可连接不可扫描定向Bluetooth LE广播事件
- 不可连接不可扫描非定向Bluetooth LE广播事件
- 不可连接可扫描非定向Bluetooth LE广播事件
为实现上述的Bluetooth LE传统广播事件,将会使用到如下所示的硬件和软件:
- 硬件平台
- HX-DK-商开发板 * 1
- USB 转 Type-C数据线 * 1
- Bluetooth LE抓包器 * 1
- 软件平台
- Visual Studio Code v1.71.2
- Espressif IDF插件 v1.5.0
- ESP-IDF
- master分支,commit ID:02605f1a31be4233b97e83a0460bb458f277dfea
- IDF工具包
- esptool-4.3
- esp-coredump-1.4
- riscv32-esp-elf-gdb-11.2_20220823
- riscv32-esp-elf-2022r1-11.2.0
- cmake-3.24.0
- openocd-esp32-v0.11.0-esp32-20220706
- ninja-1.10.2
- idf-exe-1.0.3
- ccache-4.6.2-windows-x86_64
- idf5.1_py3.9_env
如果读者们在按照本文中提及的方法或步骤,实现自己的Bluetooth LE传统广播事件时,遇到无法正常工作的问题,请务必参考上述的硬件平台或者软件平台的软件的版本是否一致,否则可以忽略。
Bluetooth LE传统广播事件的整个实现流程比较简洁,如下图所示:
从图中可以很清楚地看到,只需要简单地对controller层和host层进行初始化并使能即可实现Bluetooth协议栈的初始化,然后初始化自定义的广播参数,即可实现发送指定类型的广播包;其中 ble_adv_init() 函数就是主要用来配置广播事件的相关参数。同时,为了重点突出不同传统广播包之间的区别,所以本篇文章中的传统广播包数据只填充设备名。
从4.2 Bluetooth LE广播的基础概念章节可知,让设备发出想要的广播包内容,需要对广播参数和内容进行配置,然后再调用相应的Bluetooth LE广播API函数,才能将广播包按照指定的内容和方式发送出去。而本实例为了让用户可以从手机App中快速找到设备,所以将设备名配置为中文名,代码如下:
-
广播数据包
/* 设备名为:我是谁? */ char device_name[] = {0xE6,0x88,0x91,0xE6,0x98,0xAF,0xE8,0xB0,0x81,0x3F};
-
扫描响应数据包
/* 响应扫描请求的数据包内容 */ static uint8_t gs_m_scan_rsp_data[] = { /* 设备名:HX-DK-商 */ 0x0A, 0x09, 0x48, 0x58, 0x2D, 0x44, 0x4B, 0x2D, 0xE5, 0x95, 0x86, };
为了让手机app可以识别出中文的设备名,需要将其转换为UTF8编码格式,链接如下:
其中,我是谁?
的UTF8编码就是E68891E698AFE8B0813F
,扫描响应数据包中的设备名也是如此,即48582D444B2DE59586
;从4.2.4.1 传统广播包中可知,0x0A表示该广播数据类型的长度 (广播数据+广播数据类型),而0x09则是上述4.2.4.3 广播数据类型中提及的广播数据类型,指的是完整设备名的意思。
该实例将以100ms的时间间隔,向外广播设备名为 我是谁? 的可连接可扫描的非定向广播PDUs;如果接收到扫描请求PDUs的话,则会回复设备名为HX-DK-商的扫描响应包,并且还可以接受任何设备的连接请求,然后建立连接。
配置完广播包的设备名之后,紧接着就配置以怎么样的方式将携带中文设备名的广播包发送出去,如下所示:
/**
* 广播间隔: 100ms(默认值)
* 广播事件类型: 可连接可扫描非定向广播
* MAC地址类型: 公有地址,乐鑫自带的
* 广播信道: 37,38,39
* 广播过滤条件: 无
*/
static esp_ble_adv_params_t ble_adv_params = {
.adv_int_min = ADV_INTERVAL,
.adv_int_max = ADV_INTERVAL,
.adv_type = ADV_TYPE_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
其中,
-
ADV_INTERVAL,配置广播间隔为100ms,但最终的广播间隔会是100ms ± 0~10ms
-
ADV_TYPE_IND,配置的是可连接可扫描的非定向广播事件
-
BLE_ADDR_TYPE_PUBLIC,当前的广播地址类型为公有地址
-
ADV_CHNL_ALL,在3个广播信道上轮询发送广播PDUs,但是也可以选择其中的任何一个或者两个广播信道,取决于用户
-
ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,这里指的是接受任何设备的扫描请求和连接请求,也就是上述4.2.1.4.4 广播过滤策略中提及的策略之一
最后,将配置后的广播参数通过调用esp_ble_gap_start_advertising(&ble_adv_params) API函数即可将广播包按照上述的方式发送出去。
将本篇文章随附的实验一代码工程直接拷贝至任意目录下,然后编译下载至开发板即可实现可连接可扫描非定向Bluetooth LE广播包。如果下载并成功运行,那么我们可以从捉包器中看到下述的内容:
-
广播数据包
-
扫描响应数据包
-
连接请求包
从上述的捉包截图中可以看出,当前的可连接可扫描非定向广播事件是可以响应任何设备的扫描请求和连接请求,这显然与上述4.2.1.4 广播事件类型中描述的内容是完全吻合的,具体的细节请查看实验一代码工程中的捉包log。
同4.3.3.2 广播参数,但是在4.3.3 实验一,可连接可扫描非定向Bluetooth LE广播的基础上,将广播包携带的广播地址改为带隐私的可解析私有地址,即调用如下API函数即可:
ESP_ERROR_CHECK(esp_ble_gap_config_local_privacy(true));
将本章内容随附的实验二代码工程直接拷贝至任意目录下并下载至开发板,即可看到每次上电复位或者15分钟之后,广播地址均会变化且广播地址类型为Random,但是最高的2位却一直是0b10
,捉包的log如下:
从捉包的图中可以看出,广播地址分别为72:6f:0e:86:54:28
、4d:e4:79:67:1f:8a
、77:50:e5:5d:fd:21
,它们的最高2位有效比特均为0b10
(注意,比特0才是最高位),这同样与上述4.1.2.3 可解析私有地址(Resolvable Private Address)的内容完全吻合,详情如实验二代码工程中的捉包log所示。该实验仅仅是阐述Bluetooth LE隐私的作用,实际上只会在Bluetooth LE配对并绑定后才会使用到这个特性。
本实例以20ms的广播间隔,向外广播低占空比的可连接不可扫描的定向广播,需要注意的是该类型的广播是不携带任何广播内容的,只包括广播地址和发起者的地址。
与上述的4.3.3 实验一,可连接可扫描非定向Bluetooth LE广播和4.3.4 实验二,带隐私功能的可连接可扫描非定向Bluetooth LE广播不同,该类型的广播包是不携带任何广播数据的,只有广播地址和目标地址,所以就不需要配置广播数据了,如设备名、UUID等。
/**
* 广播间隔: 20ms(默认值)
* 广播事件类型: 低占空比的可连接不可扫描定向广播
* MAC地址类型: 公有地址,乐鑫自带的
* 广播信道: 37,38,39
* 广播过滤条件: 无
* 对端设备地址: 公有地址类型,7c:df:a1:bb:e6:61
*/
static esp_ble_adv_params_t gs_m_ble_adv_params = {
.adv_int_min = ADV_INTERVAL,
.adv_int_max = ADV_INTERVAL,
.adv_type = ADV_TYPE_DIRECT_IND_LOW,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.peer_addr_type = BLE_ADDR_TYPE_PUBLIC,
.peer_addr = {0x7c,0xdf,0xa1,0xbb,0xe6,0x61},
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
其中,
-
ADV_INTERVAL 配置广播间隔为20ms,但最终的广播间隔会是20ms ± 0~10ms,这也符合上述的4.2.1.5 广播间隔中提及的范围
-
ADV_TYPE_DIRECT_IND_LOW 表示当前是可连接不可扫描的定向广播事件
-
BLE_ADDR_TYPE_PUBLIC 当前的广播地址和目标地址类型都是公有地址
-
peer_addr 指定的目标地址,这里是另外一块HX-DK-商开发板的MAC地址
-
ADV_CHNL_ALL 在3个广播信道上轮询发送广播PDUs,但是也可以选择其中的任何一个或者两个广播信道,取决于用户
-
ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY 这里指的是接受任何设备的扫描请求和连接请求,也就是上述4.2.1.4.4 广播过滤策略中提及的策略之一
最后,将配置后的广播参数通过调用esp_ble_gap_start_advertising(&ble_adv_params) API函数即可将广播包按照上述的方式发送出去。
将随附的实验三代码工程直接拷贝至任意目录下并下载至开发板,然后就会向外广播低占空比的可连接不可扫描定向Bluetooth LE广播事件;此时,如果不是该广播所指定的目标地址设备向其发送连接请求则直接被无视,否则马上建立链接,具体的捉包Log如下:
从上图可知,当目标地址7c:df:a1:bb:e6:61
设备发起连接请求时,则马上就建立了链接;而非目标地址的设备发起连接请求时,则没有任何反应,广播设备则继续广播;该现象也是完全符合上述4.2.1.4.3 定向与非定向中提及的内容,详情如实验三代码中的捉包log所示。
本实例以小于等于3.75ms的广播间隔,向外广播高占空比的可连接不可扫描的定向广播,并持续1.28s 之后就停止广播,需要注意的是该类型的广播是不携带任何广播内容的,只包括广播地址和发起者的地址。
跟4.3.5.2 广播参数中的配置基本是一样的,但是广播类型这里是ADV_TYPE_DIRECT_IND_HIGH,具体如下:
/**
* 广播间隔: 20ms(默认值),这个必须要配置,但用不上
* 广播事件类型: 高占空比的可连接不可扫描定向广播
* MAC地址类型: 公有地址,乐鑫自带的
* 广播信道: 37,38,39
* 广播过滤条件: 无
* 对端设备地址: 公有地址类型,7c:df:a1:bb:e6:61
*/
static esp_ble_adv_params_t gs_m_ble_adv_params = {
.adv_int_min = ADV_INTERVAL, //高占空比的最大广播间隔是3.75ms,但是此处的值用不上,但又比须符合常规广播的间隔要求,
.adv_int_max = ADV_INTERVAL, //即大于等于20ms
.adv_type = ADV_TYPE_DIRECT_IND_HIGH,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.peer_addr_type = BLE_ADDR_TYPE_PUBLIC,
.peer_addr = {0x7c,0xdf,0xa1,0xbb,0xe6,0x61},
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
从上述的代码注释可知,这里的广播间隔一定要配置且必须符合常规广播的要求,也就是4.2.1.5 广播间隔中提及的非定向广播和低占空比的定向广播,如果配置为20ms以下的话就会报错:
E (637) BT_BTC: Invalid advertisting interval parameters. E (637) Non-scannable-connectable ADV Demo in HDC mode: Adv start failed: ERROR 1
但实际的广播间隔不受这个参数控制,最终还是受Bluetooth LE协议栈内部控制,也就是说协议栈内部会去调度这个广播间隔以及广播总时长。
将随附的实验四代码工程直接拷贝至任意目录下并下载至开发板,然后就会向外广播高占空比的可连接不可扫描定向Bluetooth LE广播事件;此时,如果不是该广播所指定的目标地址设备向其发送连接请求则直接被无视,否则马上建立链接;同时,广播间隔不宜大于3.75ms且总时长不会超过1.28s,具体的捉包Log如下:
- 广播间隔
- 广播总时长
从上述的捉包log可知,广播间隔为12.100322-12.098135=2.187ms
,而广播总长为13.377687-12.098135=1.279552s
,这完全符合4.2.1.4.3 定向与非定向中提及的小于等于3.75ms且广播总时长小于等于1.28s,详情如实验四代码中的捉包log所示。
以100ms的广播间隔,向外广播设备名为 “红旭无线” 的不可连接不可扫描的非定向广播数据,期间不响应任何设备的连接请求和扫描请求;因此,在同等条件下,该类型的广播会比其他响应连接请求或扫描请求的广播的功耗要低,如4.2.1.4.2 可扫描与不可扫描中提及的内容,不可连接不可扫描的非定向广播发送完广播PDUs之后,就会马上切换到下一信道而无须等待可能到来的连接请求或者扫描请求。
与4.3.3 实验一,可连接可扫描非定向Bluetooth LE广播中的配置基本上是一样的,但是广播类型这里是ADV_TYPE_NONCONN_IND,也就是不可连接不可扫描广播的意思,具体如下:
/**
* 广播间隔: 100ms(默认值)
* 广播事件类型: 不可连接不可扫描非定向广播
* MAC地址类型: 公有地址,乐鑫自带的
* 广播信道: 37,38,39
* 广播过滤条件: 无
*/
static esp_ble_adv_params_t ble_adv_params = {
.adv_int_min = ADV_INTERVAL,
.adv_int_max = ADV_INTERVAL,
.adv_type = ADV_TYPE_NONCONN_IND,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.channel_map = ADV_CHNL_ALL,
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
};
具体的各参数详情,请查阅4.3.3.2 广播参数;其中,广播数据所携带的设备名则变成:
/* 设备名为红旭无线 */
char device_name[] = {0xE7, 0xBA, 0xA2, 0xE6, 0x97, 0xAD, 0xE6, 0x97, 0xA0, 0xE7, 0xBA, 0xBF};
同样,也是将红旭无线转换为UFT-8的编码,这样当手机app在扫描时,就可以看到中文名的设备名了。
将随附的实验五代码工程直接拷贝至任意目录下并下载至开发板,然后就会向外广播设备名为红旭无线的不可连接不可扫描的非定向Bluetooth LE广播事件,期间将无视任意设备的连接请求或者扫描请求,也没办法接收这些请求,因为在当前的广播信道发送完广播PDU之后,就立马切换到下一个广播信道,不会再打开接收窗口来接收可能到来的请求,该类型的广播的捉包log如下:
与4.2.1.4.2 可扫描与不可扫描中提及的不可连接不可扫描广播事件是一一对应的,更多详情请参考实验五代码中的捉包log。
跟4.3.3 实验一,可连接可扫描非定向Bluetooth LE广播的功能简介是基本一样的,唯一不同的就是该类型的广播不响应连接请求。
除了广播类型变成ADV_TYPE_SCAN_IND,也就是不可连接可扫描广播的意思之外,其他的参数与4.3.3 实验一,可连接可扫描非定向Bluetooth LE广播中的配置是完全一样的。
将随附的实验六代码工程直接拷贝至任意目录下并下载至开发板,然后就会向外广播不可连接可扫描的非定向Bluetooth LE广播事件,期间将无视任意设备的连接请求但会响应扫描请求,该类型的广播的捉包log如下:
- 广播数据包
- 扫描响应数据包
更多详情请参考实验六代码中的捉包log。
在4.3.3 实验一,可连接可扫描非定向Bluetooth LE广播的基本上,将指定设备的MAC地址添加至白名单中,只响应白名单的设备的连接请求和扫描请求,拒绝任何非白名单中的设备的连接请求和扫描请求。
除了广播过滤策略变成ADV_FILTER_ALLOW_SCAN_WLST_CON_WLST之外,其他的参数与4.3.3 实验一,可连接可扫描非定向Bluetooth LE广播中的配置是完全一样的。此外,还广播过滤策略的函数,如下所示:
/* 将公有地址为7c:df:a1:bb:e6:61加入到白名单中 */
esp_bd_addr_t m_bd_addr={0x7c,0xdf,0xa1,0xbb,0xe6,0x61};
if(esp_ble_gap_update_whitelist(true,m_bd_addr,BLE_WL_ADDR_TYPE_PUBLIC) == ESP_OK)
{
ESP_LOGI(DEMO_TAG, "whitelist updated successfully");
}
else
{
ESP_LOGI(DEMO_TAG, "whitelist updated failed");
}
其中,7c:df:a1:bb:e6:61
就是增加至白名单的设备之一。
将随附的实验七代码工程直接拷贝至任意目录下并下载至开发板,然后就会向外广播可连接可扫描的非定向Bluetooth LE广播事件,期间将只响应白名单中的设备的连接请求和扫描请求,该类型的广播的捉包log如下:
- 无效的扫描请求
- 无效的连接请求
- 有效的连接请求
从上述的捉包log可知,非白名单的设备发送的请求均被直接忽略,只响应白名单内的设备的请求,更多详情请参考实验七代码中的捉包log。
本章内容主要是讲述了如何利用现有的API函数,实现传统广播事件、广播过滤策略以及隐私的功能,加深以及巩固了对传统广播事件和上述前面几个章节相关内容的了解。
扩展广播是Bluetooth LE自v5.0开始的新型广播,其可以将原本单个传统广播所携带的有效广播数据载荷从31字节扩展至248字节,也就是常说的8倍广播,我们可以从上面章节的4.2.4.1 传统广播包和4.2.4.2 扩展广播包内容中,看到它们的不同之处。其中,伴随也引入了以下新的广播事件:
- 可连接不可扫描非定向Bluetooth LE广播事件
- 不可连接不可扫描定向Bluetooth LE广播事件
- 不可连接可扫描定向Bluetooth LE广播事件
而之前的部分传统广播事件也可以支持部分扩展广播,如下所示:
- 可连接不可扫描定向Bluetooth LE广播事件
- 不可连接不可扫描非定向Bluetooth LE广播事件
- 不可连接可扫描非定向Bluetooth LE广播事件
具体的对应关系如下图所示:
为了加强对扩展广播的理解,本篇内容接下来将通过以下不同的实验来阐述扩展广播的新特性。
与上述的4.3.1 工具软件中描述的内容一模一样。
实现扩展广播的代码工作流程与上述的4.3.2 代码剖析描述完全一样,唯一不同的就是传统广播的初始化变成扩展广播的初始化;同理,为了简化并突出扩展广播的特性,接下来的实验将只使用到完整设备名和制造商自定义数据这两种广播数据类型,可以查阅4.2.4.3 广播数据类型章节了解更多的内容。
以100ms的广播间隔,向外广播设备名为 “红旭无线” 的可连接不可扫描的非定向扩展广播,并携带有40字节的自定义字符串信息:HX_Nonscannable_connectable_ext_adv_demo,但最多不能超过248字节的广播有效载荷量。
相较于传统广播参数,扩展广播参数的配置会稍微复杂一点,但是万变不离其中,主要分为下述的三个步骤:
-
配置扩展广播参数
/** * 广播间隔: 100ms(默认值) * 广播事件类型: 可连接不可扫描定向广播,仅对扩展广播有效 * MAC地址类型: 公共地址,乐鑫自带的 * 广播信道: 37,38,39 * 广播过滤条件: 无 * 首要物理层: 1M * 次要物理层: 2M */ static esp_ble_gap_ext_adv_params_t m_gs_ble_ext_adv_params = { .channel_map = ADV_CHNL_ALL, .filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, .interval_min = ADV_INTERVAL, .interval_max = ADV_INTERVAL, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, .primary_phy = ESP_BLE_GAP_PRI_PHY_1M, .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE | ESP_BLE_GAP_SET_EXT_ADV_PROP_INCLUDE_TX_PWR, .secondary_phy = ESP_BLE_GAP_PHY_2M, .sid = 0, .max_skip = 0, .scan_req_notif = false, .tx_power = 9, };
其中,上述各参数的含义如下:
- channel_map,发送ADV_EXT_IND PDUs广播的物理信道:当前是在37,38,39三个广播信道上,发送广播ADV_EXT_IND PDUs
- filter_policy,广播过滤策略:这里是响应任意设备的扫描请求和连接请求
- interval_min、interval_max:广播事件的时间间隔,这里最小最大的广播间隔均为100ms
- own_addr_type,MAC地址类型:本地设备的地址类型为公有地址
- primary_phy,主要PHY:1M PHY
- type,广播类型:该示例的广播类型为可连接不可扫描的非定向Bluetooth LE扩展广播,其中ESP_BLE_GAP_SET_EXT_ADV_PROP_INCLUDE_TX_PWR表示在广播包中携带发射功率值
- secondary_phy,辅助PHY:辅助广播数据包的PHY,这里为2M PHY
- sid,广播集ID:这里是0
- max_skip,发送 AUX_ADV_IND 之前可以跳过的广播事件的最大数量
- scan_req_notif,收到扫描请求PDU时,是否发送扫描请求通知:本示例是不发送扫描请求通知
- tx_power,在广播物理信道上传输的广播包的最大功率,控制层会选择小于或等于主机层指定的功率值:本示例为+9dBm
从上述的参数中可以看出,比4.3.3.2 广播参数中的传统广播参数新增了一些参数,最后调用esp_ble_gap_ext_adv_set_params函数即可完成扩展广播的参数配置。
-
配置扩展广播数据
/* 扩展广播原始数据 */ static uint8_t raw_ext_adv_data_1m[] = { /* Flag */ 0x02, 0x01, 0x06, /* 设备名:红旭无线 */ 0x0D, 0x09, 0xE7, 0xBA, 0xA2, 0xE6, 0x97, 0xAD, 0xE6, 0x97, 0xA0, 0xE7, 0xBA, 0xBF, /* 制作商自定义数据 */ 0x29, 0xFF, 'H', 'X', '_', 'N', 'o', 'n', 's', 'c', 'a', 'n', 'n', 'a', 'b', 'l', 'e', '_', 'c', 'o', 'n', 'n', 'e', 'c', 't', 'a', 'b', 'l', 'e', '_', 'e', 'x', 't', '_', 'a', 'd', 'v', '_', 'd', 'e', 'm', 'o'};
手动构造扩展广播原始数据,这里仅填充了完整设备名和制造商自定义数据这两种广播数据类型的数据,最后调用esp_ble_gap_config_ext_adv_data_raw函数即可完成扩展广播数据的设置。
-
配置扩展广播的广播时长
完成上述的二个步骤之后,最后配置该扩展广播的广播时长,如下:
/** * 广播时长: 0,表示一直广播,除非停止 * 实例: 0,表示当前是实例0 * 最大的扩展广播事件数: 0,表示一直都有扩展广播事件,除非超时或者停止 */ static esp_ble_gap_ext_adv_t m_gs_ble_ext_adv = { .duration = 0, .instance = 0, .max_events = 0, };
其中,上述参数的具体含义如下:
- duration,扩展广播的持续时间:单位为10ms,范围为1(10ms)~65535(655,350ms),0表示一直广播除非主动停止
- instance,实例:当前为0,如果有多个扩展广播,则在0的基础上加1,也就是说表示第几个扩展广播
- max_events,在终止扩展广播之前,可以发送的最大扩展广播事件数,即便扩展广播的持续时间还未超时:范围为1~255,0表示可以一直发,除非超时
最后调用esp_ble_gap_ext_adv_start函数,就可以将上述配置的扩展广播数据,按照配置的参数和时长广播出去了。
将随附的实验一代码工程直接拷贝至任意目录下并下载至开发板,此时就可以看到可连接不可扫描非定向的Bluetooth LE扩展广播,从手机app (手机的蓝牙需要支持蓝牙5.0) 中看到该扩展广播的内容如下:
而捉包Log的信息如下:
-
扩展广播间隔 上图中,我们可以看到扩展广播间隔 =
27.467361
-27.362360
≌105ms
,其中5ms
是附加的随机时延; -
辅助广播数据 上图中可以看到,所有的扩展广播数据在
数据信道24
上传输且超过了31字节,内容也跟我们配置的内容一致且采用的2M PHY来传输广播数据,这就是扩展广播相较于传统广播而新增加的新特性; -
辅助连接请求 上述是在
数据信道17
发起的连接请求,而对传统广播的设备只能在37、38、39中的任意一个信道中发送连接请求,这是因为所有的辅助广播数据均在数据信道上传输,所以想要与发送扩展广播的设备建立连接,只能在对应的数据信道上发送连接请求; -
辅助广播数据的偏移量
上图中我们可以看到,在37、38、39传统广播信道上的ADV_EXT_IND都会携带Aux Pointer这样的参数,而这个参数则主要指示了即将在数据信道上传输的辅助广播数据信息,如:
- 哪一个数据信道
- 1M PHY?2M PHY还是CODED PHY?
- 辅助广播数据将在多少us后在数据信道上传输
其中,上图红色方框的37广播通道的ADV_EXT_IND,表示辅助广播数据将在1200us后在数据信道0上开始传输,而捉包log中的时间差
7.109793-7.108593=0.0012s=1.2ms=1200us
也刚好验证了这一点;
更多的详情,请参考实验一中附带的捉包log。
以100ms的广播间隔,向外广播设备名为 “红旭无线” 的可连接不可扫描的定向扩展广播,并携带有40字节的自定义字符串信息:HX_Nonscannable_connectable_ext_adv_demo且只响应指定设备的连接请求,无视任何非指定设备的连接请求。
除了指定设备的MAC地址以及从非定向变为定向之外,其他的基本上与上述的4.4.3.2 广播参数的一致。
将随附的实验二代码工程直接拷贝至任意目录下并下载至开发板,此时开发板就会向外广播可连接不可扫描定向的Bluetooth LE扩展广播;但是,如果指定的设备并不是手机的蓝牙MAC地址的话,那么在使用手机APP扫描广播时,底层就已经过滤掉该广播了,所以并不会显示该广播包的内容;然而,使用捉包器则是可以发现该广播包的内容,如下图所示:
从4.4.4.1 功能简介可知,该类型的广播只允许指定的设备与其建立连接,如下图所示:
其中,db:88:d3:a2:4b:72
就是指定设备的MAC地址,当db:88:d3:a2:4b:72
向开发板7c:df:a1:bb:c7:29
发送连接请求时,则马上建立连接,而非指定的设备在协议栈底层就已经自动过滤了该广播,即便强制发起连接请求也会被无视的;更多的详情,请参考实验二中附带的捉包log。
以100ms的时间间隔,向外广播设备名为 “红旭无线” 的不可连接不可扫描的定向广播并携带有48字节的自定义字符串信息:HX is showing u how to implement ext_adv feature;这里所说的定向与上述4.2.1.4.3 定向与非定向所述的是不同的,该类型的扩展广播用于只向指定的设备传送广播内容,并不是只响应指定设备的请求,但这不意味着其不能被其他非指定设备所扫描得到,其仍然可以被其他扫描者扫描得到,只不过人家觉得不是给它的数据,协议栈在底层直接过滤掉了。
基本上与上述的4.4.3.2 广播参数的一致,只不过扩展广播类型变为下述的配置:
ESP_BLE_GAP_SET_EXT_ADV_PROP_DIRECTED | ESP_BLE_GAP_SET_EXT_ADV_PROP_INCLUDE_TX_PWR
将随附的实验三代码工程直接拷贝至任意目录下并下载至开发板,此时开发板就会向外广播不可连接不可扫描定向的Bluetooth LE扩展广播;这里指定的设备是小编本人手机的蓝牙MAC地址,所以当使用手机APP扫描时,是可以在扫描列表中找到该广播内容的,如下图所示:
然而,如果指定的不是小编本人的手机,那么手机APP列表中就查找不到该广播内容,但是对于捉包器而言则是不会去判断是定向还是非定向,它会将所有扫描到的广播内容均显示出来,如下图所示:
这里,7c:df:a1:bb:c7:29
是开发板的Bluetooth LE的MAC地址,而e4:05:f8:0d:b3:9c
则是小编手机的蓝牙MAC地址,可以看出开发板不断地发送广播包,但目标地址都是小编手机的蓝牙;更多的详情,请参考实验三中附带的捉包log。
如上述4.4 扩展广播中提及的,一包扩展广播的最大有效广播数据载荷量是248字节,如果需要发送的广播数据长度超过248字节,则需要使用4.2.2.2 扩展广播PDU中描述的链式扩展广播,只有Bluetooth LE v5.0及其之后的规范支持该特性且最长的广播数据长度不能超过1650字节。
链式扩展广播,其广播事件不能是可连接和可扫描类型,也就是说只能是不可连接不可扫描的广播事件类型,这里以100ms的广播间隔,向外广播设备名为 “红旭无线” 的不可连接不可扫描的非定向链式扩展广播,并携带总共1650字节长度的广播数据为例,相较于传统广播的31字节足足多了1619个字节。
基本上与上述的4.4.3.2 广播参数的一致,只不过扩展广播类型变为下述的配置:
ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED
此外,广播数据除了完整设备名之外,新增到总共1650字节,关键代码如下:
/* BLE 5.0所支持的最大广播数据长度为1650字节 */
#define MAX_ADV_DATA_LEN 1650
/* 配置除设备名之外,剩下的链式扩展广播原始数据 */
for (uint16_t i = 0; i < MAX_ADV_DATA_LEN-17; i++)
{
raw_ext_adv_data_2m[i+17] = i;
}
将随附的实验四代码工程直接拷贝至任意目录下并下载至开发板,此时开发板就会向外广播,总长度为1650字节的不可连接不可扫描非定向的Bluetooth LE链式扩展广播,详情如下:
其中,上图中的标识含义如下:
- 我们可以看出,总长度为1650字节的链式扩展广播的广播间隔为
4313.178729-4313.071855≌107ms
;其中7ms
是随机时延,为了降低广播信道碰撞的概率,增加链路的鲁棒性; - 1650字节总长度的广播数据,被分成了7包扩展广播包,分别为
242、248、248、248、248、248、168
,至于为什么第一包只能携带242字节的广播有效载荷量,这是因为广播地址占用了6个字节; - 从捉包log中的最后一列可以看到,如果辅助广播包放不下的时候,就会将剩余的数据以链式广播包的形式继续传输出去直至全部发送完成;
- 这里用的是2M PHY来传输扩展广播包的全部内容;
从上述的4.2.1.5 广播间隔可知,非定向广播和低占空比的定向广播最小的广播间隔为20ms,通过上述的图示可以计算得出1650字节的广播数据发送完成,使用2M PHY只需要4313.188181-4313.178729≌9.5ms
,远小于20ms;接下来,我们看看如果采用1M PHY和 CODED PHY的话,是否仍然可以采用20ms的广播间隔:
-
1M PHY
当采用1M PHY来传送1650字节的链式扩展广播时,所需要的时间为
7.600734-7.584982≌15.8ms
,也小于20ms的最小广播间隔;因此,无须担心广播间隔不够导致丢包的问题; -
CODED PHY
上图中可以看出,当前的链式扩展广播采用了S=8的CODED PHY,将所有的1650字节的广播数据发送完成,需要
5.618959-5.513604≌105ms
;因此,此时20ms的广播间隔将不允许被使用,应至少要大于105ms,否则就会出现丢广播包的情况;
更多的详情,请参考实验四中附带的捉包log。
除了辅助广播、链式广播之外,Bluetooth LE v5.0还新增了一个周期广播,使用该类型的广播的设备可以同时跟若干个接收到该类型广播的Bluetooth LE设备进行数据同步,这在Bluetooth LE v5.0之前是无法想象的,这里以100ms的扩展广播间隔以及25ms、100ms、150ms的周期广播间隔,向外广播周期广播为例;由于周期广播是伴随在辅助广播包之后的,所以辅助广播是必选的,其中辅助广播的设备名是:hx_ext_adv_2m,而周期广播的设备名是:hx_ble5.0_periodic_adv;这里需要强调的是周期扩展广播,其广播事件同样不能是可连接和可扫描类型,也就是说只能是不可连接不可扫描的广播事件类型。
基本上与上述的4.4.3.2 广播参数的一致,只不过扩展广播类型变为下述的配置:
ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED
为了更好地区分辅助广播和周期广播,所以分别将对应的完整广播设备名更改为hx_ext_adv_2m和hx_ble5.0_periodic_adv,关键代码如下:
/* 不可连接不可扫描非定向辅助广播的原始数据 */
static uint8_t raw_ext_adv_data_2m[] = {
/* Flag */
0x02, 0x01, 0x06,
/* 设备名:hx_ext_adv_2m */
0x0E, 0x09, 'h', 'x', '_', 'e', 'x', 't', '_', 'a', 'd', 'v', '_', '2', 'm'};
/* 周期广播的原始数据 */
static uint8_t raw_periodic_adv[] = {
0x02, 0x01, 0x06,
/* 设备名:hx_ble5.0_periodic_adv */
0x17, 0x09, 'h', 'x', '_', 'b', 'l', 'e','5','.','0', '_','p', 'e', 'r', 'i', 'o',
'd', 'i', 'c', '_', 'a', 'd', 'v'};
此外,还需要对周期广播间隔进行额外的配置,关键代码如下:
/**
* 周期广播的间隔: 100ms(默认值)
*/
static esp_ble_gap_periodic_adv_params_t gs_m_periodic_adv_params = {
.interval_min = PERIODIC_ADV_INTERVAL,
.interval_max = PERIODIC_ADV_INTERVAL,
// Do not include TX power
.properties = 0,
};
将随附的实验五代码工程直接拷贝至任意目录下并下载至开发板,此时开发板就会向外广播不可连接不可扫描非定向的Bluetooth LE周期扩展广播,详情如下:
- 100ms扩展广播间隔+25ms周期广播间隔
- 100ms扩展广播间隔+100ms周期广播间隔
- 100ms扩展广播间隔+150ms周期广播间隔
从上述三幅图可以看出,橙色的是带辅助广播数据的扩展广播间隔,均为100ms+10ms的随机时延;而蓝色的则是不同的周期广播间隔,不管大于、小于还是等于带辅助广播数据的扩展广播间隔,周期广播均可以正常工作,只不过是将周期广播穿插在不同的带辅助广播数据扩展广播间隔的中间;更多的详情,请参考实验五中附带的捉包log。
该实验的功能与4.4.6 实验四,不可连接不可扫描非定向Bluetooth LE链式扩展广播是基本上一致的,只不过Bluetooth LE链式扩展广播的链式广播数据是跟在辅助广播数据后面,而Bluetooth LE链式周期广播的链式广播数据则是跟在同步广播数据后面。
基本上与上述的4.4.7.2 广播参数的一致,唯一不同的就是将广播数据的长度扩展至1650字节,与4.4.6.2 广播参数中的描述是一样的。
将随附的实验六代码工程直接拷贝至任意目录下并下载至开发板,此时开发板就会向外广播不可连接不可扫描非定向的Bluetooth LE链式周期扩展广播,详情如下所示:
同理,橙色表示的是带辅助广播数据 (由3x ADV_EXT_IND + 1x AUX_ADV_IND组成) 的扩展广播间隔,均为100ms+10ms的随机时延;而蓝色表示的则是周期广播 (1x AUX_SYNC_IND + 由若干个AUX_CHAIN_IND组成) 间隔,从上图中可以看出密密麻麻的链式广播数据,而且可以看到有部分已经丢失并未捉到,这是因为周期广播间隔太小且所携带的数据太大,而导致捉包器出现漏捕获的情况,这是正常现象;更多的详情,请参考实验六中附带的捉包log。
从Bluetooth LE v5.0开始,广播是可以不携带广播地址的,但仅限于不可连接不可扫描的扩展广播,关于这一点我们也可以从4.2.2.2 扩展广播PDU中得知,其中不可连接不可扫描广播事件的广播地址是可选的;这里以100ms的时间间隔,向外广播设备名为 “红旭无线” 的不可连接不可扫描的非定向匿名扩展广播并携带有48字节的自定义字符串信息:HX is showing u how to implement ext_adv feature为例。
基本上与上述的4.4.3.2 广播参数的一致,只不过扩展广播类型变为下述的配置:
ESP_BLE_GAP_SET_EXT_ADV_PROP_ANON_ADV
将随附的实验七代码工程直接拷贝至任意目录下并下载至开发板,此时开发板就会向外广播不可连接不可扫描非定向的Bluetooth LE匿名扩展广播,详情如下:
从上述的捉包Log可知,此时不管是广播信道还是数据信道,不可连接不可扫描的广播包均不携带广播地址,这与本实验的预期是完全符合的;更多的详情,请参考实验七中附带的捉包log。
在Bluetooth LE v5.0之前,如果想要同时发送几包不同广播数据内容的广播包,则需要不断地去更新广播数据内容,但是从Bluetooth LE v5.0之后起,可以一次性同时发送最大240包不同广播数据内容的不同类型的广播包,简称为广播集。本实验以不同的广播间隔,同时向外广播4组不同类型不同广播数据内容的广播包,简而言之就是4组不同的广播并行工作。
从形式上与上述的4.4.3.2 广播参数是一致的,只不过这里分成了四组不同类型的广播参数,关键代码如下:
-
第一组
/** * 广播间隔: 100ms(默认值) * 广播事件类型: 可连接可扫描非定向广播,ADV_IND * MAC地址类型: 公共地址,乐鑫自带的 * 广播信道: 37,38,39 * 广播过滤条件: 无 * 首要物理层: 1M * 次要物理层: 1M */ static esp_ble_gap_ext_adv_params_t m_gs_ble_legacy_adv_params = { .channel_map = ADV_CHNL_ALL, .filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, .interval_min = ADV_SET1_INTERVAL, .interval_max = ADV_SET1_INTERVAL, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, // .peer_addr = {0xE0, 0xB6, 0x55, 0xBE, 0x1A, 0xB5}, // .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, .primary_phy = ESP_BLE_GAP_PRI_PHY_1M, .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_LEGACY_IND, .secondary_phy = ESP_BLE_GAP_PRI_PHY_1M, .sid = 0, .max_skip = 0, .scan_req_notif = false, // .tx_power = 9, };
-
第二组
/** * 广播间隔: 90ms(默认值) * 广播事件类型: 不可连接不可扫描非定向扩展广播 * MAC地址类型: 公共地址,乐鑫自带的 * 广播信道: 37,38,39 * 广播过滤条件: 无 * 首要物理层: 1M * 次要物理层: 1M */ static esp_ble_gap_ext_adv_params_t m_gs_ble_ext_adv_params_1M = { .channel_map = ADV_CHNL_ALL, .filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, .interval_min = ADV_SET2_INTERVAL, .interval_max = ADV_SET2_INTERVAL, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, // .peer_addr = {0xE0, 0xB6, 0x55, 0xBE, 0x1A, 0xB5}, // .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, .primary_phy = ESP_BLE_GAP_PRI_PHY_1M, .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_NONCONN_NONSCANNABLE_UNDIRECTED, .secondary_phy = ESP_BLE_GAP_PRI_PHY_1M, .sid = 1, .max_skip = 0, .scan_req_notif = false, // .tx_power = 9, };
-
第三组
/** * 广播间隔: 80ms(默认值) * 广播事件类型: 不可连接可扫描非定向扩展广播 * MAC地址类型: 公共地址,乐鑫自带的 * 广播信道: 37,38,39 * 广播过滤条件: 无 * 首要物理层: 1M * 次要物理层: 2M */ static esp_ble_gap_ext_adv_params_t m_gs_ble_ext_adv_params_2M = { .channel_map = ADV_CHNL_ALL, .filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, .interval_min = ADV_SET3_INTERVAL, .interval_max = ADV_SET3_INTERVAL, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, // .peer_addr = {0xE0, 0xB6, 0x55, 0xBE, 0x1A, 0xB5}, // .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, .primary_phy = ESP_BLE_GAP_PRI_PHY_1M, .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_SCANNABLE, .secondary_phy = ESP_BLE_GAP_PHY_2M, .sid = 2, .max_skip = 0, .scan_req_notif = false, // .tx_power = 9, };
-
第四组
/** * 广播间隔: 120ms(默认值) * 广播事件类型: 可连接不可扫描非定向扩展广播 * MAC地址类型: 公共地址,乐鑫自带的 * 广播信道: 37,38,39 * 广播过滤条件: 无 * 首要物理层: 1M * 次要物理层: CODED PHY */ static esp_ble_gap_ext_adv_params_t m_gs_ble_ext_adv_params_coded = { .channel_map = ADV_CHNL_ALL, .filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, .interval_min = ADV_SET4_INTERVAL, .interval_max = ADV_SET4_INTERVAL, .own_addr_type = BLE_ADDR_TYPE_PUBLIC, // .peer_addr = {0xE0, 0xB6, 0x55, 0xBE, 0x1A, 0xB5}, // .peer_addr_type = BLE_ADDR_TYPE_PUBLIC, .primary_phy = ESP_BLE_GAP_PRI_PHY_1M, .type = ESP_BLE_GAP_SET_EXT_ADV_PROP_CONNECTABLE, .secondary_phy = ESP_BLE_GAP_PHY_CODED, .sid = 3, .max_skip = 0, .scan_req_notif = false, // .tx_power = 9, };
从上述的代码片段可知,是四组不同类型、不同广播间隔的广播;至于,对应各自所携带的广播数据,因为比较简单所以这里不花费太多的篇幅,详情可以参考实验十的源码工程。
将随附的实验十代码工程直接拷贝至任意目录下并下载至开发板,此时开发板就会向外广播四组不同类型的广播包,详情如下:
通过上述的捉包Log可以看到,当使用了广播集功能之后,设备可以同时发出四组不同类型、不同广播间隔、不同广播数据的广播,更多的详情可以参考实验十的捉包Log。
本章内容,基本上介绍了Bluetooth LE v5.0的所有广播新特性的实现,是对4.2 Bluetooth LE广播的基础概念章节内容的巩固;其中,不可连接可扫描定向Bluetooth LE广播事件和不可连接可扫描非定向Bluetooth LE广播事件的实现与上述所描述的实验大同小异,所以就不再上述的实验章节中花费太多的篇幅;但是有一点需要注意的是,这两种广播事件类型在传输辅助广播PDU时,Adv Data域是不允许携带内容的,详情可以参考4.2.2.2 扩展广播PDU。
通常,与不同的Bluetooth LE设备进行通讯时,如果对端设备发送过来的数据不认识或者格式不对,那么就将无法处理这一帧数据,而选择丢弃这一包数据。为了避免这种问题,蓝牙规范引入了Profile(这里不对这个单词进行翻译,因为原文读者们反而更加好理解),可以将其理解为 “应用场景、功能、使用方式”,规范了数据的格式和标准,从而可以更容易地保持Bluetooth LE设备之间的兼容,如标准Profile:心率Profile、接近Profile或者自定义的Profile。因此,只要遵循Profile的规范,那么不同应用的Bluetooth LE设备间就可以互相通讯。大家可以通过下述的层次图对这个概念有个初步地了解:
由4.5 Profile可知,Profile是由若干个服务组成,每个服务可以简单地理解为用于实现设备或设备的某些部分的特定功能或特性;比如,要实现点灯功能也可以是一个服务。其中服务又分为两种类型:
- 主要服务(Primary Service)
- 次要服务(Secondary Service)
日常在Bluetooth LE设备上见到的服务,基本上都是主要服务,其提供可单独使用的设备功能的服务。次要服务是提供与主要服务相关联的设备的附加功能的服务,并且至少被设备上的一个主要服务包括;简单地说,就是主要服务用于提供设备的主要功能,而次要服务则提供辅助功能;因此,次要服务是可选的,基本上很少被使用,但是在某些场景下还是非常有用的:
- 减少服务发现的时间
- 当次要服务被多个服务引用时,可以大大减少内存空间
- 当需要兼容旧的设备,但同时又要新增功能,则可以使用次要服务
其中,4.5 Profile章节中的Profile层次图提及的Included Service就等同于Secondary Service,只不过是参照物不同而导致不同的命名。也就是说,当服务在创建时的类型不是Primary Service,那么就叫Secondary Service;而当Secondary Service被其他服务引用时,其他服务就叫它Included Service。
从上图中,我们可以看到Tx Power服务就是Secondary Service,它被Link Loss和Battery Service同时引用,那么这两个服务称Tx Power服务为Included Service;所以,当在进行Include Service查找时,就会同时将Included Service指向Tx Power,如下图所示:
服务定义 (Service Definition) 包含一个服务申明 (Service Declaration) ,还可能包含包括定义 (Include Definition) 和 特征定义 (Characteristic Definition) ,这也就是说,服务定义的顺序如下:
Service Declaration
$\dashrightarrow$ Include Definitions(>=0)$\dashrightarrow$ Characteristic Definitions(>=0)
其中,服务定义在下一个服务申明前或到达最大属性句柄时结束。
众所周知,服务是Bluetooth LE设备不可或缺的重要组成部分,它是蓝牙规范抽象出来的词汇,用于帮助人类更好地理解;然而,对于Bluetooth LE设备而言,它没有 "服务" 这一说,只有属性。属性是Bluetooth LE设备的最小组成单元;因此,服务申明也是由若干个属性组成,如下所示:
属性句柄 | 属性类型 | 属性值 | 属性权限 |
---|---|---|---|
0xNNNN | UUID 0x2800 或者 UUID 0x2801 |
16-bit 标准服务UUID 或者 128-bit UUID 自定义服务UUID |
只读,无须认证或授权 |
下述两幅图可以帮助读者更好地理解服务申请明:
- 16-bit UUID
- 128-bit UUID
从上文可知,服务的UUID分为16-bit和128-bit;其实本质上所有的UUID都是128-bit,但是为了减少数据传输时的开销,所以蓝牙联盟提前定义了一些标准的服务或者特征,而它们全部用16-bit UUID来表示;而那些不是蓝牙联盟定义的服务或者特征UUID,则必须采用128-bit来表示。当然,如果需要将自定义的128-bit的UUID变成16-bit的UUID,则可以向蓝牙联盟申请并支付3000美金即可。
针对蓝牙联盟提前定义的服务或者特征UUID而言,它们128-bit格式的UUID是基于Bluetooth_Base_UUID来显示的,其中Bluetooth_Base_UUID为00000000-0000-1000-8000-00805F9B34FB
,例如:
- 0x180F
$\dashrightarrow$ 电池服务UUID (128-bit UUID: 0000180F-0000-1000-8000-00805F9B34FB) - 0x2A00
$\dashrightarrow$ 设备名称特征UUID (128-bit UUID: 00002A00-0000-1000-8000-00805F9B34FB)
具体的计算公式如下:
128-bit的值 = 16-bit值 * 2^96 + BluetoothBaseUUID
当需要创建自己的服务或特征UUID时,可以借助现成的GUID Generator工具来生成,但应该确保不要与现存的UUID冲突,同时也要避免任何使用包含XXXXXXXX-0000-1000-8000-00805F9B34FB
的UUID,其中XXXXXXXX为任意值。
包括定义 (Include Definition) 仅包含一个包括申明 (Include Declaration),因此其排列顺序如下:
Include Definition
$\dashrightarrow$ Include Declaration
包括定义主要用于在当前的服务中包括另外一个次要服务,这两者服务间的先后顺序如上述的4.5.1.1 服务定义所述,而该定义的功能如4.5.1 服务中的次要服务所提及,其中包括申明的数据格式如下:
属性句柄 | 属性类型 | 属性值 | 属性权限 |
---|---|---|---|
0xNNNN | «Include»的UUID$\dashrightarrow$ 0x2802 | 1. 包含的服务的属性句柄 2. 结束组句柄 3. 服务UUID (只有当服务UUID是16-bit时才存在) |
只读,无须认证或授权 |
其中,属性类型固定为0x2802,而属性值则描述包括的服务的相关信息,有点类似于指针指向这个被包括的服务的位置,详情如下图所示:
由上图可知,被包括的这个服务的属性句柄为11,且该服务最后的句柄值为13,而该服务的UUID为Tx Power$\dashrightarrow$0x2A07,进而可以得出被包括的这个服务只有一个特征,其属性排列顺序如下:
句柄11$\dashrightarrow$Tx Power服务申明,句柄12$\dashrightarrow$特征申明,句柄13$\dashrightarrow$特征Tx Power Level
更多关于特征的相关内容,可以参考下一章节4.5.2 特征。
特征是服务不可或缺的重要组成部分,一个服务由若干个特征组成,从而实现这个服务所需的功能。同样的,对于Bluetooth LE设备而言,特征跟服务一样都是属性,只不过是抽象出来让人类更好理解的词汇;因此,特征也有特征定义,其顺序如下:
Characteristic Declaration
$\dashrightarrow$ Characteristic Value Declaration$\dashrightarrow$ Characteristic Descriptor Declarations(>=0)
而特征定义结束于:
- 下一个特征声明的开始
- 或服务声明的开始
- 或在最大属性句柄 (0xFFFF) 之后
其中,特征声明的数据格式如下:
属性句柄 | 属性类型 | 属性值 | 属性权限 |
---|---|---|---|
0xNNNN | «Characteristic»的UUID$\dashrightarrow$ 0x2803 | 1. 特征属性 2. 特征值属性句柄 3. 特征UUID |
只读,无须认证或授权 |
我们可以看出,特征声明的主要内容是指明了特征值的属性、句柄以及UUID。为了更好地阐述和理解特征声明的数据结构,以下图为例: