目录
一、什么是 BLE Mesh Bearer Layer?
二、ADV Bearer Layer
2.1 广播承载的特点
2.2 adv bearer layer是如何工作的?
2.3 广播承载层的数据包格式
2.4 如何实现Mesh Message的接收和解析?
三、GATT Bearer Layer
3.1 GATT承载的特点
3.2 Proxy PDU
3.3 Proxy filtering
3.4 Proxy Server/Client
3.5 如何建立GATT连接
四、资料获取
蓝牙Mesh协议中负责数据传输的层,建立在BLE的物理层和链路层之上;
提供了两种承载方式:广播承载和GATT承载。
利用BLE的广播和扫描功能,在3个广播信道上发送和接收Mesh消息;
可以实现多对多的通信,不需要建立连接,也不需要维护路由表;
数据传输速率较低,且受到广播间隔和扫描窗口的限制。
adv bearer layer是蓝牙mesh网络中的一种承载层,它使用广播通道进行数据传输。adv bearer layer的工作原理如下:
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 代码,您需要以下几个步骤:
下面是一个基于 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代码,您需要以下几个步骤:
下面是一个基于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);
}
要接收和解析Mesh Message,您需要以下几个步骤:
利用BLE的连接和GATT服务,在37个数据信道上发送和接收Mesh消息;
可以实现高速的数据传输,且可以兼容不支持Mesh的BLE设备;
需要建立连接,且只能实现一对一的通信。
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 |
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的不同而不同。 |
Proxy Client:只能通过GATT连接进行通信,而无法使用ADV进行mesh通信的节点。
Proxy Server:能够在ADV Bearer和GATT Bearer之间进行转换的节点。
GATT bearer可以使那些不支持广播承载的设备也能加入到Mesh网络中,GATT bearer使用Proxy protocol通过GATT连接在设备之间转发、接收Proxy PDUs 。要建立GATT连接,您需要以下几个步骤:
下面是一个基于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
感谢各位用户点赞、分享、在看,这些行为让知识得以更加广泛地传播,从而让更多人受益。
请在转载作品时注明出处,严禁抄袭行为。