LV04-天猫蓝牙Mesh项目-02-进阶开发-05-蓝牙Mesh配网流程

本文主要是天猫蓝牙Mesh项目——进阶开发 蓝牙Mesh配网流程 的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows版本 windows11
Ubuntu版本 Ubuntu22.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license))
点击查看本文参考资料
分类 网址 说明
官方网站 阿里云 阿里云官网主页
阿里生活物联平台 生活物联网平台(飞燕平台)主页
AliGenie 天猫精灵开放平台AliGenie主页
阿里物联网平台 阿里物联网平台主页
Bluetooth 技术网站 蓝牙协议规范什么的可以来这里找
Telink Telink | Chips for a Smarter IoT (telink-semi.com)
Telink中文官网
开发手册 AliOS Things开发指南 AliOS Things开发指南,这里是最新版本,可以直接从官网找到
AliOS Things开发指南 AliOS Things应用开发指南,这里应该是3.3版本的完整开发文档
AliOS Things开发指南(3.0) AliOS Things应用开发指南,这里应该是3.0版本的完整开发文档
生活物联网平台开发文档 生活物联网平台(飞燕平台)开发文档
《设备端开发指南》
Wi-Fi IoT品类定义与功能开发 天猫精灵IoT开放平台——Wi-Fi IoT品类定义与功能开发
硬件平台 mk3080 WiFi开发板 WiFi开发板使用指南-阿里云开发者社区
esp8266开发板 一个教程:ESP8266-NodeMCU开发板详解-太极创客 (taichi-maker.com)
TLSR8258 Datasheet Datasheet for Telink BLE + IEEE802.15.4 MultiStandard Wireless SoC TLSR8258
参考资料 AliOS Things 3.0 应用开发指南 这个只是一篇参考文章,里面是一些环境搭建相关的,可以参考
IP知识百科 - 华为 (huawei.com) IP的一些相关知识点
点击查看相关文件下载
分类 网址 说明
蓝牙规范相关文档 Core Specification 5.2 核心规格 5.2,该规范定义了创建可互操作的Bluetooth 设备所需的技术。
《Core_v5.2.pdf》
Mesh Model(v1.1) 本Bluetooth 规范定义了模型(以及它们所需的状态和消息),这些模型用于在mesh 网络中的节点上执行基本功能,超出了Bluetooth Mesh 配置文件 规范中定义的基础模型。
本规范包括定义跨设备类型标准功能的通用模型,以及支持关键mesh 场景的模型,如照明控制、传感器、时间和场景。
《MshMDL_v1.1.pdf》
Mesh Profile(v1.0.1) 该Bluetooth 规范定义了基本要求,以实现可互操作的mesh 网络解决方案,用于Bluetooth 低能量无线技术。
《MshPRFv1.0.1.pdf》
Mesh Device Properties 本规范包含Bluetooth Mesh 配置文件 和Bluetooth Mesh 模型规范所要求的设备属性的定义。
但是跟之前的有些区别,我主要看的之前的版本:《MMeshDeviceProperties_v1.2.pdf》
GATT Specification Supplement GATT Specification Supplement | Bluetooth® Technology Website。
好像可以在线看:《GATT Specification Supplement》
Assigned Numbers GATT的一些类型定义可以在这里找。
AliOS Things alios-things/AliOS-Things Gitee上的AliOSThings SDK源码仓库
alibaba/AliOS-Things GitHub上的AliOSThings SDK源码仓库
天猫精灵蓝牙Mesh协议栈 alibaba-archive/genie-bt-mesh-stack GitHub上的天猫精灵蓝牙Mesh协议栈源码仓库。
之前是在alibaba/genie-bt-mesh-stack这个仓库。
写笔记的时候最新提交为faf523618a6a2560090fc423222b9db80984bb7a
蓝牙Mesh设备开发指南 阿里云生活服务平台开发手册——蓝牙设备开发一节中的内容

这一LV的笔记起始跟前面有所重叠,互相补充吧算是。

一、配网交互流程

配网流程

二、配网过程详解

1. 信标阶段

蓝牙Mesh配网流程-信标阶段

2. 邀请阶段

蓝牙Mesh配网流程-邀请阶段

3. 交换公钥阶段

蓝牙Mesh配网-交换公钥阶段

4. 身份认证阶段

进行身份认证交互前,会进行OOB的一个验证,OOB有四种:

image-20240103224729458

当OOB验证完成后,就进入身份认证的交互流程:

蓝牙Mesh配网-身份认证-交互流程

首先,配网器和未配网设备都会有一个共享密钥和OOB信息,也有可能没有OOB,若是有的话,这个OOB信息会和共享密钥在一起做一次运算,然后共同去加密一些内容,这个内容就是确认值。确认值怎么来的?我们现在有OOB和共享密钥,然后我们再生成一个随机值,这样我们就有了随机值+OOB+共享密钥,然后通过加密算法就得到了确认值。配网器和未配网设备都会有这样一个确认值,我们的配网器会先将确认值发给未配网设备,未配网设备也会将自己的确认值发送给配网器,由于大家的共享密钥和OOB是一样的,那么就可以进行一个反推,这样配网器就可以获取未配网设备生成的随机值,未配网设备就会获得配网器生成的随机值,然后他们互相发给对方进行,确认对方计算的随机值是否与自己生成的随机值一致,若一致,就进入分发配网数据阶段。

这个时候,我们想一下,若是我们没有OOB,那这个时候共享密钥和确认值要是被捕获和监听了,那么它就可以替代原来未配网的设备进行一个身份认证了,这不就很危险?相反,要是有了这个OOB,这个OOB是只有配网器和未配网设备知道的,它并不会被捕获,所以也就无法推算出配网器或者未配网设备生成的随机值。

5. 分发数据阶段

蓝牙Mesh配网-分发配网数据

配网器和未配网设备会再计算出一个会话密钥,这个会话密钥是一次性的。配网分发数据的内容说明如下:

配网分数数据内容

三、配网过程代码分析

1. 蓝牙Mesh的代码在哪?

我们看一下这个目录:network/bluetooth/bt_mesh:

image-20240104073558413

这里面就是蓝牙Mesh相关的代码实现,我们看一下里面的src目录,有以下文件:

image-20240104073651738

其中adv.c文件就是广播包相关的函数实现的源文件。

2. 三种广播包的处理

三种广播包在这里进行处理:network/bluetooth/bt_mesh/src/adv.c中的bt_mesh_scan_cb():

image-20240104074339159

我们找到network/bluetooth/bt_mesh/src/prov.c中的这个bt_mesh_pb_adv_recv()函数,这个函数就是接收到PB-ADV广播包的时候的处理函数,只有在配网的时候会使用此广播包,我们会发现,配网相关的代码都在这个prov.c文件中,我们就主要来看一下这个文件。

3. 配网过程分析

3.1 配网流程回调函数定义

我们看一下这部分代码:

image-20240104075145370
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static const struct {
void (*func)(const u8_t *data);
u16_t len;
} prov_handlers[] = {
{ prov_invite, 1 }, // 邀请阶段
{ prov_capabilities, 11 }, // 配网能力
{ prov_start, 5, }, // 开始配网
{ prov_pub_key, 64 }, // 公钥交换
{ prov_input_complete, 0 }, // 输入OOB完成
{ prov_confirm, 16 }, // 配网认证
{ prov_random, 16 }, // 配网随机值
{ prov_data, 33 }, // 分发数据
{ prov_complete, 0 }, // 分发数据完成
{ prov_failed, 1 }, // 配网失败
};

3.2 bt_mesh_pb_adv_recv()

image-20240104075837526

3.3 gen_prov_recv()

上面判断完Link ID,就会进图下面这个gen_prov_recv()函数:

image-20240104231659737

3.4 gen_prov_ctl()

我们先来看一下这个配网承载控制的函数,因为配网开始的时候我们肯定是要先获取未配网设备的UUID(配网过程信标阶段那个图中有),所以最开始我们收到的应该是配网承载控制这样的一个包,我们点开看一下:

image-20240104232150723

link_open()函数执行完毕的时候,就说明这个时候我们的信标阶段就结束了。

3.5 gen_prov_start()

image-20240104233318657

这个函数就要开始配网了,这里我们算一下就可以知道,这里是不能有分包的,只有不分包的情况下,才会进入这个函数。

3.6 prov_msg_recv()

image-20240104233934464

在这个函数中具体去执行矫情、交换公钥、身份认证这些阶段,一直到配网完成,流程都在这里面。

4. PB-ADV包分析代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
static const struct {
void (*func)(const u8_t *data);
u16_t len;
} prov_handlers[] = {
{ prov_invite, 1 }, //邀请阶段
{ prov_capabilities, 11 }, //配网能力
{ prov_start, 5, }, //开始配网
{ prov_pub_key, 64 }, //公钥交换
{ prov_input_complete, 0 }, //输入OOB完成
{ prov_confirm, 16 }, //配网认证
{ prov_random, 16 }, //配网随机值
{ prov_data, 33 }, //分发数据
{ prov_complete, 0 }, //分发数据完成
{ prov_failed, 1 }, //配网失败
};

static void close_link(u8_t err, u8_t reason)
{
PROV_D(", 10--->0");
#if defined(CONFIG_BT_MESH_PB_GATT)
if (link.conn) {
bt_mesh_pb_gatt_close(link.conn);
return;
}
#endif

#if defined(CONFIG_BT_MESH_PB_ADV)
if (err) {
prov_send_fail_msg(err);
}

link.rx.seg = 0;
bearer_ctl_send(LINK_CLOSE, &reason, sizeof(reason));
#endif

atomic_clear_bit(link.flags, LINK_ACTIVE);

/* Disable Attention Timer if it was set */
if (link.conf_inputs[0]) {
bt_mesh_attention(NULL, 0);
}
}

static void prov_retransmit(struct k_work *work)
{
int i;

BT_DBG("");

if (!atomic_test_bit(link.flags, LINK_ACTIVE)) {
BT_WARN("Link not active");
return;
}

if (k_uptime_get() - link.tx.start > TRANSACTION_TIMEOUT) {
BT_WARN("Giving up transaction");
reset_link();
return;
}

for (i = 0; i < ARRAY_SIZE(link.tx.buf); i++) {
struct net_buf *buf = link.tx.buf[i];

if (!buf) {
break;
}

if (BT_MESH_ADV(buf)->busy) {
continue;
}

BT_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len));

if (i + 1 < ARRAY_SIZE(link.tx.buf) && link.tx.buf[i + 1]) {
bt_mesh_adv_send(buf, NULL, NULL);
} else {
bt_mesh_adv_send(buf, &buf_sent_cb, NULL);
}

}
}

static void link_open(struct prov_rx *rx, struct net_buf_simple *buf)
{
BT_DBG("len %u", buf->len);

if (buf->len < 16) {
BT_ERR("Too short bearer open message (len %u)", buf->len);
return;
}

if (atomic_test_bit(link.flags, LINK_ACTIVE)) {
BT_WARN("Ignoring bearer open: link already active");
return;
}

if (memcmp(buf->data, prov->uuid, 16)) {
BT_DBG("Bearer open message not for us");
return;
}

PROV_D("link_id: 0x%08x, 0--->1", rx->link_id);
if (prov->link_open) {
prov->link_open(BT_MESH_PROV_ADV);
}

link.id = rx->link_id;
atomic_set_bit(link.flags, LINK_ACTIVE);
net_buf_simple_init(link.rx.buf, 0);

bearer_ctl_send(LINK_ACK, NULL, 0);

genie_event(GENIE_EVT_SDK_MESH_PROV_START, &rx->link_id);

link.expect = PROV_INVITE;
}

static void link_ack(struct prov_rx *rx, struct net_buf_simple *buf)
{
BT_DBG("len %u", buf->len);
}

static void link_close(struct prov_rx *rx, struct net_buf_simple *buf)
{
BT_DBG("len %u", buf->len);
reset_link();
}

static void gen_prov_ctl(struct prov_rx *rx, struct net_buf_simple *buf)
{
BT_DBG("op 0x%02x len %u", BEARER_CTL(rx->gpc), buf->len);

switch (BEARER_CTL(rx->gpc)) {
case LINK_OPEN:
link_open(rx, buf);
break;
case LINK_ACK:
if (!atomic_test_bit(link.flags, LINK_ACTIVE)) {
return;
}

link_ack(rx, buf);
break;
case LINK_CLOSE:
if (!atomic_test_bit(link.flags, LINK_ACTIVE)) {
return;
}

link_close(rx, buf);
break;
default:
BT_ERR("Unknown bearer opcode: 0x%02x", BEARER_CTL(rx->gpc));
return;
}
}

static void prov_msg_recv(void)
{
u8_t type = link.rx.buf->data[0];

BT_DBG("type 0x%02x len %u", type, link.rx.buf->len);
BT_DBG("data %s", bt_hex(link.rx.buf->data, link.rx.buf->len));

if (!bt_mesh_fcs_check(link.rx.buf, link.rx.fcs)) {
BT_ERR("Incorrect FCS");
return;
}

gen_prov_ack_send(link.rx.id);
link.rx.prev_id = link.rx.id;
link.rx.id = 0;

if (type != PROV_FAILED && type != link.expect) {
BT_WARN("Unexpected msg 0x%02x != 0x%02x", type, link.expect);
prov_send_fail_msg(PROV_ERR_UNEXP_PDU);
/* added for NODE/PROV/BV-10-C in PTS 7.4.1*/
link.expect = PROV_FAILED;
return;
}

if (type >= ARRAY_SIZE(prov_handlers)) {
BT_ERR("Unknown provisioning PDU type 0x%02x", type);
close_link(PROV_ERR_NVAL_PDU, CLOSE_REASON_FAILED);
return;
}

if (1 + prov_handlers[type].len != link.rx.buf->len) {
BT_ERR("Invalid length %u for type 0x%02x",
link.rx.buf->len, type);
close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED);
return;
}

prov_handlers[type].func(&link.rx.buf->data[1]);
}

static void gen_prov_cont(struct prov_rx *rx, struct net_buf_simple *buf)
{
u8_t seg = CONT_SEG_INDEX(rx->gpc);

BT_DBG("len %u, seg_index %u", buf->len, seg);

if (!link.rx.seg && link.rx.prev_id == rx->xact_id) {
BT_WARN("Resending ack");
gen_prov_ack_send(rx->xact_id);
return;
}

if (rx->xact_id != link.rx.id) {
BT_WARN("Data for unknown transaction (%u != %u)",
rx->xact_id, link.rx.id);
return;
}

if (seg > link.rx.last_seg) {
BT_ERR("Invalid segment index %u", seg);
close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED);
return;
} else if (seg == link.rx.last_seg) {
u8_t expect_len;

expect_len = (link.rx.buf->len - 20 -
(23 * (link.rx.last_seg - 1)));
if (expect_len != buf->len) {
BT_ERR("Incorrect last seg len: %u != %u",
expect_len, buf->len);
close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED);
return;
}
}

if (!(link.rx.seg & MESH_BIT(seg))) {
BT_WARN("Ignoring already received segment");
return;
}

memcpy(XACT_SEG_DATA(seg), buf->data, buf->len);
XACT_SEG_RECV(seg);

if (!link.rx.seg) {
prov_msg_recv();
}
}

static void gen_prov_ack(struct prov_rx *rx, struct net_buf_simple *buf)
{
BT_DBG("len %u", buf->len);

if (!link.tx.buf[0]) {
return;
}

if (rx->xact_id == link.tx.id) {
prov_clear_tx();
}
}

static void gen_prov_start(struct prov_rx *rx, struct net_buf_simple *buf)
{
if (link.rx.seg) {
BT_WARN("Got Start while there are unreceived segments");
return;
}

if (link.rx.prev_id == rx->xact_id) {
BT_WARN("Resending ack");
gen_prov_ack_send(rx->xact_id);
return;
}

link.rx.buf->len = net_buf_simple_pull_be16(buf);
link.rx.id = rx->xact_id;
link.rx.fcs = net_buf_simple_pull_u8(buf);

BT_DBG("len %u last_seg %u total_len %u fcs 0x%02x", buf->len,
START_LAST_SEG(rx->gpc), link.rx.buf->len, link.rx.fcs);

if (link.rx.buf->len < 1) {
BT_ERR("Ignoring zero-length provisioning PDU");
close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED);
return;
}

if (link.rx.buf->len > link.rx.buf->size) {
BT_ERR("Too large provisioning PDU (%u bytes)",
link.rx.buf->len);
close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED);
return;
}

if (START_LAST_SEG(rx->gpc) > 0 && link.rx.buf->len <= 20) {
BT_ERR("Too small total length for multi-segment PDU");
close_link(PROV_ERR_NVAL_FMT, CLOSE_REASON_FAILED);
return;
}

link.rx.seg = (1 << (START_LAST_SEG(rx->gpc) + 1)) - 1;
link.rx.last_seg = START_LAST_SEG(rx->gpc);
memcpy(link.rx.buf->data, buf->data, buf->len);
XACT_SEG_RECV(0);

if (!link.rx.seg) {
prov_msg_recv();
}
}

static const struct {
void (*const func)(struct prov_rx *rx, struct net_buf_simple *buf);
const u8_t require_link;
const u8_t min_len;
} gen_prov[] = {
{ gen_prov_start, true, 3 }, //开始
{ gen_prov_ack, true, 0 }, //应答
{ gen_prov_cont, true, 0 }, //分包
{ gen_prov_ctl, false, 0 }, //配网承载控制
};

static void gen_prov_recv(struct prov_rx *rx, struct net_buf_simple *buf)
{
if (buf->len < gen_prov[GPCF(rx->gpc)].min_len) {
BT_ERR("Too short GPC message type %u", GPCF(rx->gpc));
return;
}

if (!atomic_test_bit(link.flags, LINK_ACTIVE) &&
gen_prov[GPCF(rx->gpc)].require_link) {
BT_DBG("Ignoring message that requires active link");
return;
}

gen_prov[GPCF(rx->gpc)].func(rx, buf);
}
//接收到PB-ADV广播包进入
void bt_mesh_pb_adv_recv(struct net_buf_simple *buf)
{
struct prov_rx rx;
//确定是否已连接
if (!bt_prov_active() && bt_mesh_is_provisioned()) {
BT_DBG("Ignoring provisioning PDU - already provisioned");
return;
}

if (buf->len < 6) {
BT_WARN("Too short provisioning packet (len %u)", buf->len);
return;
}

rx.link_id = net_buf_simple_pull_be32(buf);
rx.xact_id = net_buf_simple_pull_u8(buf);
rx.gpc = net_buf_simple_pull_u8(buf);

BT_DBG("link_id 0x%08x xact_id %u", rx.link_id, rx.xact_id);

if (atomic_test_bit(link.flags, LINK_ACTIVE) && link.id != rx.link_id) {
BT_DBG("Ignoring mesh beacon for unknown link");
return;
}

gen_prov_recv(&rx, buf);
}