前言
当配网器配置了多个节点设备后,需要对各个节点状态信息进行更新管理,如何将设备与其订阅地址、单播地址信息关联,对节点的功能定义进行分类
BLE Mesh组件配置
esp32 ble mesh组件源码位于esp-idf_old\components\bt\esp_ble_mesh
,使用命令make menuconfig启动配置工具,进入ble mesh组件的配置页面,路径Component config-->ESP BLE Mesh Support-->
- 使能配网器,可以配置最大支持的配网数,默认为10个
- 配置PB-ADV与PB-GATT同时支持的配网数量
节点管理
节点状态信息
参考配网器工程,路径idf\examples\bluetooth\esp_ble_mesh\ble_mesh_provisioner
下,定义了上层应用的节点信息,与协议栈中保存的节点信息一一对应
typedef struct {
uint8_t uuid[16];//uuid
uint16_t unicast;//单播地址
uint8_t elem_num;//元素个数
uint8_t onoff;//开关状态
} esp_ble_mesh_node_info_t;
//默认十个节点信息缓存
static esp_ble_mesh_node_info_t nodes[CONFIG_BLE_MESH_MAX_PROV_NODES] = {
[0 ... (CONFIG_BLE_MESH_MAX_PROV_NODES - 1)] = {
.unicast = ESP_BLE_MESH_ADDR_UNASSIGNED,
.elem_num = 0,
.onoff = LED_OFF,
}
};
更新与保持节点信息
- 当配网完成后,系统触发事件ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT,调用接口prov_complete更新与保持节点信息,同时给节点设置一个名称保存
static esp_err_t prov_complete(int node_idx, const esp_ble_mesh_octet16_t uuid,
uint16_t unicast, uint8_t elem_num, uint16_t net_idx)
{
esp_ble_mesh_client_common_param_t common = {0};
esp_ble_mesh_cfg_client_get_state_t get_state = {0};
esp_ble_mesh_node_info_t *node = NULL;
char name[11] = {0};
int err;
ESP_LOGI(TAG, "node index: 0x%x, unicast address: 0x%02x, element num: %d, netkey index: 0x%02x",
node_idx, unicast, elem_num, net_idx);
ESP_LOGI(TAG, "device uuid: %s", bt_hex(uuid, 16));
sprintf(name, "%s%d", "NODE-", node_idx); // NODE-x(0 1 2..)
err = esp_ble_mesh_provisioner_set_node_name(node_idx, name); //设置一个名称;底层栈写入nvs
if (err) {
ESP_LOGE(TAG, "%s: Set node name failed", __func__);
return ESP_FAIL;
}
err = example_ble_mesh_store_node_info(uuid, unicast, elem_num, LED_OFF);//同步保存到上层应用
if (err) {
ESP_LOGE(TAG, "%s: Store node info failed", __func__);
return ESP_FAIL;
}
node = example_ble_mesh_get_node_info(unicast);//重新读取节点信息
if (!node) {
ESP_LOGE(TAG, "%s: Get node info failed", __func__);
return ESP_FAIL;
}
example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET);
get_state.comp_data_get.page = COMP_DATA_PAGE_0;
err = esp_ble_mesh_config_client_get_state(&common, &get_state);//发起模型配置
if (err) {
ESP_LOGE(TAG, "%s: Send config comp data get failed", __func__);
return ESP_FAIL;
}
return ESP_OK;
}
- 单节点状态发生改变时,上层应用也会同步更新状态(对于节点状态有掉电保存要求的,可以写到flash中)
static esp_err_t example_ble_mesh_store_node_info(const uint8_t uuid[16], uint16_t unicast,
uint8_t elem_num, uint8_t onoff_state)
{
int i;
if (!uuid || !ESP_BLE_MESH_ADDR_IS_UNICAST(unicast)) {
return ESP_ERR_INVALID_ARG;
}
/* Judge if the device has been provisioned before */
for (i = 0; i < ARRAY_SIZE(nodes); i++) {
if (!memcmp(nodes[i].uuid, uuid, 16)) { //匹配uuid
ESP_LOGW(TAG, "%s: reprovisioned device 0x%04x", __func__, unicast);
nodes[i].unicast = unicast;//更新地址
nodes[i].elem_num = elem_num;//更新元素
nodes[i].onoff = onoff_state;//更新开关状态
return ESP_OK;
}
}
for (i = 0; i < ARRAY_SIZE(nodes); i++) {
if (nodes[i].unicast == ESP_BLE_MESH_ADDR_UNASSIGNED) {//新增节点
memcpy(nodes[i].uuid, uuid, 16);
nodes[i].unicast = unicast;
nodes[i].elem_num = elem_num;
nodes[i].onoff = onoff_state;
return ESP_OK;
}
}
return ESP_FAIL;
}
获取节点信息
- 应用层中的nodes获取节点,通过匹配地址是否在节点元素中
static esp_ble_mesh_node_info_t *example_ble_mesh_get_node_info(uint16_t unicast)//通过地址获取
{
int i;
if (!ESP_BLE_MESH_ADDR_IS_UNICAST(unicast)) {
return NULL;
}
for (i = 0; i < ARRAY_SIZE(nodes); i++) {
if (nodes[i].unicast <= unicast && /*匹配地址在节点元素中*/
nodes[i].unicast + nodes[i].elem_num > unicast) {
return &nodes[i];
}
}
return NULL;
}
- 通过协议栈的接口获取节点信息,可以通过节点uuid、节点命名、节点地址
esp_err_t err = ESP_OK;
esp_ble_mesh_node_t *node_info = NULL;
if(type == UUID_GET)//通过uuid获取
node_info = esp_ble_mesh_provisioner_get_node_with_uuid(uuid);
else if(type == NAME_GET)//通过名称获取
node_info = esp_ble_mesh_provisioner_get_node_with_name(name);
else if(type == UNICASE_GET) //通过单播地址获取
node_info = esp_ble_mesh_provisioner_get_node_with_addr(unicast);
if (node_info == NULL)
{
ESP_LOGE(TAG, "can't get node info");
return;
}
总结
- 上层的节点信息与协议栈中的需要保持同步
- 使用vendor模型,由于帧数据自定义,将节点的信息插入到数据帧中,考究设计上省略上层的数据存储,开发更多样;