目录
一、什么是 BLE Mesh Bearer Layer?
蓝牙Mesh协议中负责数据传输的层,建立在BLE的物理层和链路层之上;
提供了两种承载方式:广播承载和GATT承载。
二、ADV Bearer Layer
2.1 广播承载的特点
利用BLE的广播和扫描功能,在3个广播信道上发送和接收Mesh消息;
可以实现多对多的通信,不需要建立连接,也不需要维护路由表;
数据传输速率较低,且受到广播间隔和扫描窗口的限制。
2.2 adv bearer layer是如何工作的?
adv bearer layer是蓝牙mesh网络中的一种承载层,它使用广播通道进行数据传输。adv bearer layer的工作原理如下:
- adv bearer layer接收来自网络层的数据,将其封装为adv bearer layer数据包,包括长度,AD Type和AD Data三个字段。
- adv bearer layer数据包的AD Type用于区分数据包的类型,有Mesh Message,Mesh Beacon和PB-ADV三种。
- adv bearer layer数据包的AD Data用于存储网络层的数据,如Network PDU,Mesh Beacon或Provisioning PDU等。
- adv bearer layer通过广播方式发送数据包,使用不可连接,不可扫描,不可定向的广播类型。
- adv bearer layer通过扫描方式接收数据包,使用尽可能全窗的扫描事件,以确保不丢失数据。
- adv bearer layer收到数据包后,根据AD Type和消息缓存判断是否需要处理,如果是,就将AD Data解封装并传递给网络层。
2.3 广播承载层的数据包格式
Filed | Size(octs) | Notes |
Length | 1 | 当前数据包总长度 |
AD Type | 1 | 当前数据包为Mesh数据包 |
NetworkPDU | 18-29 | Network层的PDU |
广播包种类 | 广播包类型(AD Type) | 功能说明 |
PB-ADV | 0x29 | 用于传输通用配网PDU,用于设备配网期间的通信。数据长度为24字节。 |
Mesh Message | 0x2A | 用于传输网络层的数据,包括控制和应用消息,数据长度为18-29字节。 |
Mesh Beacon | 0x2B | 用于传输安全网络信标,用于节点的认证和密钥更新。数据长度为21字节。 |
注:广告都是不可连接和不可扫描的非定向广告事件。如果节点在可连接或可扫描的通告事件中接收到广告,则忽略该消息。
1、PB-ADV 是一种配网承载,用于通过广播通道传输配网过程中的数据包。要发送 PB-ADV 代码,您需要以下几个步骤:
- 构造 Generic Provisioning PDU,包含 Provisioning PDU 和 Transaction Number。
- 将 Generic Provisioning PDU 封装到 BLE 广播包中,设置 AD Type 为 0x29,表示 Mesh Provisioning Service。
- 调用 BLE 协议栈的接口,设置广播参数和数据,启动广播。
下面是一个基于 ESP-IDF 的示例代码,用于发送 PB-ADV 代码:
// 定义 Generic Provisioning PDU 的数据结构
typedef struct
{
uint8_t transaction_number; // Transaction Number
uint8_t pdu_type; // Provisioning PDU Type
uint8_t pdu_data[]; // Provisioning PDU Data
} generic_provisioning_pdu_t;
// 定义广播数据的缓冲区
static uint8_t m_adv_data_buffer[BEARER_ADV_DATA_BUFFER_SIZE];
// 定义广播参数
static const ble_gap_adv_params_t m_adv_params =
{
.properties.type = BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED, // 广播类型
.p_peer_addr = NULL, // 不指定对方地址
.interval = UNIT_0_625_MS(BEACON_INTERVAL_MS), // 广播间隔
.duration = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED, // 无限时长
.max_adv_evts = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED, // 无限次数
.channel_mask = {0, 0, 0, 0, 0}, // 不屏蔽任何信道
.filter_policy = BLE_GAP_ADV_FP_ANY, // 不过滤任何设备
.primary_phy = BLE_GAP_PHY_1MBPS, // 物理层速率
.secondary_phy = BLE_GAP_PHY_1MBPS, // 物理层速率
.set_id = 0, // 广播集合ID
.scan_req_notification = 0, // 不通知扫描请求
};
// 定义广播数据
static ble_data_t m_adv_data =
{
.p_data = m_adv_data_buffer, // 指向缓冲区
.len = 0 // 初始长度为0
};
// 构造 Generic Provisioning PDU 的数据
static void generic_provisioning_pdu_build(generic_provisioning_pdu_t * p_pdu)
{
// 设置 Transaction Number,每发送一个 Provisioning PDU 该值累加
static uint8_t transaction_number = 0;
p_pdu->transaction_number = transaction_number++;
// 设置 Provisioning PDU Type,这里假设是 Invite PDU
p_pdu->pdu_type = PROV_INVITE;
// 设置 Provisioning PDU Data,这里假设是 Attention Duration
p_pdu->pdu_data[0] = 0x05;
}
// 启动广播
static void adv_start(void)
{
// 创建一个 Generic Provisioning PDU 的实例
generic_provisioning_pdu_t pdu;
// 构造 PDU 的数据
generic_provisioning_pdu_build(&pdu);
// 设置 AD Type 为 Mesh Provisioning Service
m_adv_data_buffer[0] = AD_TYPE_MESH_PROV; // AD Type
// 拷贝 PDU 的数据到缓冲区
memcpy(&m_adv_data_buffer[1], &pdu, sizeof(generic_provisioning_pdu_t));
// 设置广播数据的长度
m_adv_data.len = 1 + sizeof(generic_provisioning_pdu_t);
// 调用 BLE 协议栈的接口,设置广播数据
uint32_t status = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &m_adv_params);
NRF_MESH_ASSERT(status == NRF_SUCCESS);
// 调用 BLE 协议栈的接口,启动广播
status = sd_ble_gap_adv_start(m_adv_handle, BLE_CONN_CFG_TAG_DEFAULT);
NRF_MESH_ASSERT(status == NRF_SUCCESS);
}
2、Mesh Message是一种用于在Mesh网络中传输应用层数据的广播承载,它可以实现多对多的通信,也是Mesh网络中最常用的数据传输方式。要发送Mesh Message代码,您需要以下几个步骤:
- 构造Access Payload,包含Opcode、Parameters和TransMIC。
- 将Access Payload封装到Upper Transport PDU中,添加AppKey、AID和SegO/AK。
- 将Upper Transport PDU封装到Lower Transport PDU中,添加SZMIC、SeqZero、SegN和Segment。
- 将Lower Transport PDU封装到Network PDU中,添加IVI、NID、CTL、TTL、SEQ、SRC、DST和NetMIC。
- 将Network PDU封装到BLE广播包中,设置AD Type为0x2B,表示Mesh Message。
下面是一个基于Zephyr的示例代码,用于发送Mesh Message代码:
// 定义Access Payload的数据结构
struct bt_mesh_model_op {
const uint32_t opcode; // Opcode
const size_t min_len; // Minimum length of the parameters
void (*const func)(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct net_buf_simple *buf); // Function to be called
};
// 定义Generic OnOff Set消息的Opcode和Parameters
#define BT_MESH_MODEL_OP_GEN_ONOFF_SET BT_MESH_MODEL_OP_2(0x82, 0x02)
#define BT_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK BT_MESH_MODEL_OP_2(0x82, 0x03)
struct bt_mesh_gen_onoff_set {
uint8_t onoff; // OnOff state
uint8_t tid; // Transaction ID
uint8_t trans_time; // Transition time (optional)
uint8_t delay; // Delay (optional)
};
// 定义Generic OnOff Client模型的操作
static const struct bt_mesh_model_op gen_onoff_cli_op[] = {
{ BT_MESH_MODEL_OP_GEN_ONOFF_SET, 2, gen_onoff_set },
{ BT_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK, 2, gen_onoff_set_unack },
BT_MESH_MODEL_OP_END,
};
// 定义Generic OnOff Client模型的结构体
struct bt_mesh_gen_onoff_cli {
struct bt_mesh_model *model;
struct bt_mesh_model_pub pub;
struct bt_mesh_model_ack_ctx ack_ctx;
};
// 定义Generic OnOff Client模型的实例
static struct bt_mesh_gen_onoff_cli gen_onoff_cli;
// 定义Generic OnOff Set消息的发送函数
static int gen_onoff_cli_send(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
bool ack,
uint8_t onoff,
uint8_t tid,
uint8_t trans_time,
uint8_t delay)
{
// 创建一个Access Payload的缓冲区
BT_MESH_MODEL_BUF_DEFINE(msg, BT_MESH_MODEL_OP_GEN_ONOFF_SET,
BT_MESH_GEN_ONOFF_MSG_MAXLEN_SET);
// 设置Opcode
bt_mesh_model_msg_init(&msg, ack ? BT_MESH_MODEL_OP_GEN_ONOFF_SET :
BT_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK);
// 设置Parameters
net_buf_simple_add_u8(&msg, onoff);
net_buf_simple_add_u8(&msg, tid);
if (trans_time || delay) {
net_buf_simple_add_u8(&msg, trans_time);
net_buf_simple_add_u8(&msg, delay);
}
// 调用Mesh协议栈的接口,发送Access Payload
return bt_mesh_model_send(model, ctx, &msg, NULL, NULL);
}
3、Mesh beacon是一种特殊的广播承载,它用来传输Mesh网络的安全和配置信息,有两种类型:Unprovisioned Device beacon和Secure Network beacon。前者用于未入网的设备广播自己的UUID和OOB信息,以便被Provisioner发现和配置;后者用于已入网的设备广播自己的子网ID和安全状态,以便更新密钥和IV Index。
发送Mesh beacon的代码取决于具体的BLE协议栈和Mesh协议栈的实现,不同的厂商或平台可能有不同的API和函数。一般来说,需要先构造Mesh beacon的数据格式,然后调用BLE协议栈的接口,设置广播参数和数据,最后启动广播。
下面是一个基于Nordic nRF5 SDK for Mesh的示例代码,用于发送Unprovisioned Device beacon:
// 定义Unprovisioned Device beacon的数据结构
typedef struct
{
uint8_t beacon_type; // Beacon Type,固定为0x00
uint8_t uuid[NRF_MESH_UUID_SIZE]; // Device UUID
uint16_t oob_info; // OOB Information
uint32_t uri_hash; // URI Hash,可选
} unprov_beacon_t;
// 定义广播数据的缓冲区
static uint8_t m_adv_data_buffer[BEARER_ADV_DATA_BUFFER_SIZE];
// 定义广播参数
static const ble_gap_adv_params_t m_adv_params =
{
.properties.type = BLE_GAP_ADV_TYPE_NONCONNECTABLE_NONSCANNABLE_UNDIRECTED, // 广播类型
.p_peer_addr = NULL, // 不指定对方地址
.interval = UNIT_0_625_MS(BEACON_INTERVAL_MS), // 广播间隔
.duration = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED, // 无限时长
.max_adv_evts = BLE_GAP_ADV_TIMEOUT_GENERAL_UNLIMITED, // 无限次数
.channel_mask = {0, 0, 0, 0, 0}, // 不屏蔽任何信道
.filter_policy = BLE_GAP_ADV_FP_ANY, // 不过滤任何设备
.primary_phy = BLE_GAP_PHY_1MBPS, // 物理层速率
.secondary_phy = BLE_GAP_PHY_1MBPS, // 物理层速率
.set_id = 0, // 广播集合ID
.scan_req_notification = 0, // 不通知扫描请求
};
// 定义广播数据
static ble_data_t m_adv_data =
{
.p_data = m_adv_data_buffer, // 指向缓冲区
.len = 0 // 初始长度为0
};
// 构造Unprovisioned Device beacon的数据
static void unprov_beacon_build(unprov_beacon_t * p_beacon)
{
// 获取设备的UUID
const uint8_t * p_uuid = nrf_mesh_configure_device_uuid_get();
memcpy(p_beacon->uuid, p_uuid, NRF_MESH_UUID_SIZE);
// 设置OOB信息,这里假设使用静态OOB
p_beacon->oob_info = NRF_MESH_PROV_OOB_INFO_SOURCE_STATIC;
// 设置URI Hash,这里假设不使用URI
p_beacon->uri_hash = 0;
}
// 启动广播
static void adv_start(void)
{
// 创建一个Unprovisioned Device beacon的实例
unprov_beacon_t beacon;
// 构造beacon的数据
unprov_beacon_build(&beacon);
// 设置beacon的类型为Mesh Beacon
m_adv_data_buffer[0] = AD_TYPE_MESH_BEACON; // AD Type
m_adv_data_buffer[1] = BEACON_TYPE_UNPROVISIONED_DEVICE; // Beacon Type
// 拷贝beacon的数据到缓冲区
memcpy(&m_adv_data_buffer[2], &beacon, sizeof(unprov_beacon_t));
// 设置广播数据的长度
m_adv_data.len = 2 + sizeof(unprov_beacon_t);
// 调用BLE协议栈的接口,设置广播数据
uint32_t status = sd_ble_gap_adv_set_configure(&m_adv_handle, &m_adv_data, &m_adv_params);
NRF_MESH_ASSERT(status == NRF_SUCCESS);
// 调用BLE协议栈的接口,启动广播
status = sd_ble_gap_adv_start(m_adv_handle, BLE_CONN_CFG_TAG_DEFAULT);
NRF_MESH_ASSERT(status == NRF_SUCCESS);
}
2.4 如何实现Mesh Message的接收和解析?
要接收和解析Mesh Message,您需要以下几个步骤:
- 从BLE广播包中提取Network PDU,检查AD Type是否为0x2B,表示Mesh Message。
- 使用网络密钥(NetKey)派生的解密密钥(EncryptionKey)和隐私密钥(PrivacyKey)对Network PDU进行解密和反混淆,得到网络层的消息。
- 检查网络层消息的目的地址(DST)是否与本地节点的单播地址或订阅地址匹配,如果匹配则继续处理,否则丢弃或中继。
- 使用应用密钥(AppKey)或设备密钥(DevKey)派生的解密密钥(EncryptionKey)对网络层消息的有效载荷(Payload)进行解密和完整性校验,得到上层传输层的消息。
- 检查上层传输层消息的类型(Access Message或Control Message),如果是Access Message,则继续处理,否则丢弃或转发。
- 从Access Message中提取Access Payload,包括Opcode和Parameters,根据Opcode确定消息的模型类型和功能,根据Parameters解析消息的具体内容。
- 根据消息的模型类型和功能,调用相应的模型回调函数,处理消息的内容,如改变状态、发送响应等。
三、GATT Bearer Layer
3.1 GATT承载的特点
利用BLE的连接和GATT服务,在37个数据信道上发送和接收Mesh消息;
可以实现高速的数据传输,且可以兼容不支持Mesh的BLE设备;
需要建立连接,且只能实现一对一的通信。
3.2 Proxy PDU
Filed | Size(bits) | Notes |
SAR | 2 | 0b00:代表该消息为完整消息 0b01:代表该消息为分段消息的第一个分段 0b10:代表该消息为分段消息的中间某一段 0b11:代表该消息为分段消息的最后一个分段 |
Message Type | 6 | 0x00:Network PDU 0x01:Mesh Beacon 0x02:Proxy Configuration 0x03:Provisioning PDU 0x04-0x3F:RFU |
Data | variable | Message |
3.3 Proxy filtering
GATT Bearer layer Proxy filtering是一种用于优化代理服务器和代理客户端之间的数据传输的机制。它可以让代理客户端指定自己感兴趣或不感兴趣的目的地址,从而减少不必要的网络流量和电量消耗。
GATT Bearer layer Proxy filtering有两种类型,分别是:
- 白名单过滤器:只允许白名单中的目的地址通过,其他的目的地址都被拦截。白名单过滤器适用于代理客户端只关注少数目的地址的情况。
- 黑名单过滤器:只拦截黑名单中的目的地址,其他的目的地址都允许通过。黑名单过滤器适用于代理客户端只排除少数目的地址的情况。
代理客户端可以通过发送Proxy Configuration消息来配置代理服务器的过滤器类型和地址列表。Proxy Configuration消息的格式如下:
Field | Size (octets) | Notes |
Opcode | 1 | 表示Proxy Configuration消息的操作码,有六种取值: 0x00(Set Filter Type) 0x01(Add Addresses to Filter) 0x02(Remove Addresses from Filter) 0x03(Filter Status) 0x04(Reserved for Future Use) 0x05-0x3F(Reserved for Vendor Use) |
Parameters | variable | 表示Proxy Configuration消息的参数,根据Opcode的不同而不同。 |
3.4 Proxy Server/Client
Proxy Client:只能通过GATT连接进行通信,而无法使用ADV进行mesh通信的节点。
Proxy Server:能够在ADV Bearer和GATT Bearer之间进行转换的节点。
3.5 如何建立GATT连接
GATT bearer可以使那些不支持广播承载的设备也能加入到Mesh网络中,GATT bearer使用Proxy protocol通过GATT连接在设备之间转发、接收Proxy PDUs 。要建立GATT连接,您需要以下几个步骤:
- 在设备端,实现Mesh Proxy Service,包括两个特征:Mesh Proxy Data In和Mesh Proxy Data Out,用于接收和发送Proxy PDUs。
- 在设备端,启动广播,发送Unprovisioned Device beacon或Secure Network beacon,携带Mesh UUID和Network ID,用于被Provisioner或Proxy Client发现。
- 在Provisioner或Proxy Client端,扫描广播,根据Mesh UUID或Network ID识别Mesh设备,发起GATT连接请求。
- 在设备端,接受GATT连接请求,建立GATT连接,启用Mesh Proxy Data In和Mesh Proxy Data Out的通知。
- 在Provisioner或Proxy Client端,启用Mesh Proxy Data In和Mesh Proxy Data Out的通知,通过它们发送和接收Proxy PDUs。
下面是一个基于Zephyr的示例代码,用于在设备端实现Mesh Proxy Service和建立GATT连接:
// 定义Mesh Proxy Service的UUID
#define BT_UUID_MESH_PROXY_VAL 0x1828
#define BT_UUID_MESH_PROXY BT_UUID_DECLARE_16(BT_UUID_MESH_PROXY_VAL)
// 定义Mesh Proxy Data In特征的UUID
#define BT_UUID_MESH_PROXY_DATA_IN_VAL 0x2ADD
#define BT_UUID_MESH_PROXY_DATA_IN BT_UUID_DECLARE_16(BT_UUID_MESH_PROXY_DATA_IN_VAL)
// 定义Mesh Proxy Data Out特征的UUID
#define BT_UUID_MESH_PROXY_DATA_OUT_VAL 0x2ADE
#define BT_UUID_MESH_PROXY_DATA_OUT BT_UUID_DECLARE_16(BT_UUID_MESH_PROXY_DATA_OUT_VAL)
// 定义Mesh Proxy Data In特征的值
static uint8_t proxy_in_data[31];
// 定义Mesh Proxy Data Out特征的值
static uint8_t proxy_out_data[31];
// 定义Mesh Proxy Data Out特征的描述符
static struct bt_gatt_ccc_cfg proxy_out_ccc[BT_GATT_CCC_MAX] = {};
// 定义Mesh Proxy Data In特征的写入回调函数
static void proxy_in_write(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
// 检查写入的数据长度是否有效
if (len > sizeof(proxy_in_data)) {
return;
}
// 拷贝写入的数据到特征的值
memcpy(proxy_in_data, buf, len);
// 调用Mesh协议栈的接口,处理Proxy PDU
bt_mesh_proxy_recv(conn, proxy_in_data, len);
}
// 定义Mesh Proxy Data Out特征的读取回调函数
static void proxy_out_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value)
{
// 获取当前的GATT连接
struct bt_conn *conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &proxy_addr);
// 检查是否启用了通知
if (value == BT_GATT_CCC_NOTIFY) {
// 调用Mesh协议栈的接口,启用Proxy功能
bt_mesh_proxy_connected(conn);
} else {
// 调用Mesh协议栈的接口,停用Proxy功能
bt_mesh_proxy_disconnected(conn);
}
// 释放GATT连接的引用
bt_conn_unref(conn);
}
// 定义Mesh Proxy Service的属性
static struct bt_gatt_attr attrs[] = {
// Mesh Proxy Service声明
BT_GATT_PRIMARY_SERVICE(BT_UUID_MESH_PROXY),
// Mesh Proxy Data In特征声明
BT_GATT_CHARACTERISTIC(BT_UUID_MESH_PROXY_DATA_IN,
BT_GATT_CHRC_WRITE_WITHOUT_RESP,
BT_GATT_PERM_WRITE,
NULL, proxy_in_write, NULL),
// Mesh Proxy Data Out特征声明
BT_GATT_CHARACTERISTIC(BT_UUID_MESH_PROXY_DATA_OUT,
BT_GATT_CHRC_NOTIFY,
BT_GATT_PERM_NONE,
NULL, NULL, &proxy_out_data),
// Mesh Proxy Data Out特征的描述符声明
BT_GATT_CCC_MANAGED(proxy_out_ccc,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
NULL, proxy_out_ccc_changed, NULL),
};
// 定义Mesh Proxy Service的服务
static struct bt_gatt_service prov_svc = BT_GATT_SERVICE(attrs);
// 注册Mesh Proxy Service
static int proxy_svc_register(void)
{
return bt_gatt_service_register(&prov_svc);
}
// 启动广播,发送Unprovisioned Device beacon或Secure Network beacon
static int proxy_adv_start(void)
{
// 创建一个广播参数的结构体
struct bt_le_adv_param adv_param = {
.options = BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_ONE_TIME,
.interval_min = BT_GAP_ADV_FAST_INT_MIN_2,
.interval_max = BT_GAP_ADV_FAST_INT_MAX_2,
};
// 创建一个广播数据的结构体
struct bt_data adv_data[2] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA(BT_DATA_SVC_DATA16, NULL, 0),
};
// 根据设备的状态,设置广播数据的类型和内容
if (bt_mesh_is_provisioned()) {
// 已入网,发送Secure Network beacon
adv_data[1].data_len = 18;
adv_data[1].data = net_beacon_data;
} else {
// 未入网,发送Unprovisioned Device beacon
adv_data[1].data_len = 16;
adv_data[1].data = unprov_beacon_data;
}
// 调用BLE协议栈的接口,启动广播
return bt_le_adv_start(&adv_param, adv_data, ARRAY_SIZE(adv_data),
NULL, 0);
}
四、资料获取
通过点击以下链接,您可以获取BLE Mesh模块原理图、源代码以及开发资料。链接地址将为您提供详细的文件资料,以供您进行参考和使用。
如果您在使用过程中遇到任何问题或疑虑,欢迎加我QQ ,一起探讨技术问题,我的QQ号是986571840,加的时候请注明CSDN。
BLE Mesh蓝牙组网模块 - 硬创社 (jlc.com)https://x.jlc.com/platform/detail/001d23cba7b64b0d9df5b9b69720fadb
感谢各位用户点赞、分享、在看,这些行为让知识得以更加广泛地传播,从而让更多人受益。
请在转载作品时注明出处,严禁抄袭行为。