众所周知,同一个mesh网络即netkey,netkey_idx相同,appkey,appkey_idx相同。在此前提下,想要实现跨网关控制就比较容易了。
我选用的方案是nrf mesh是网关一,ESP32作为网关二,TLSR8258作为子设备。首先通过nrf mesh将子设备配置到mesh网络中,然后将nrf mesh中的net_key和appkey通过蓝牙辅助配网(蓝牙直连的方式)传给ESP32从而实现配网。
以下主要是ESP32的实现逻辑介绍:
首先我们选用的是蓝牙gatt(实现辅助配网)和蓝牙 mesh(实现控制)共存的方案(没有SDK可以私聊),以下为主函数部分
void provisioner_app_main(void)
{
esp_err_t err;
ESP_LOGI(TAG, "Initializing...");//初始化
err = bluetooth_init();ble协议栈初始化
if (err) {
ESP_LOGE(TAG, "esp32_bluetooth_init failed (err %d)", err);
return;
}
read_mesh_nodes_info();//读取mesh底层节点信息(KV存储读取)
ble_mesh_get_dev_uuid(dev_uuid);//获取设备的uuid(将mac地址加入到uuid中)
/* Initialize the Bluetooth Mesh Subsystem */
err = ble_mesh_init();//ble mesh协议栈初始化
if (err) {
ESP_LOGE(TAG, "Bluetooth mesh init failed (err %d)", err);
}
gatts_demo_main();//gatt服务开启(和mesh分时复用)
}
上述代码通过测试发现,在此SDK中,必须先初始化mesh网络再初始化gatt,很明显不符合我们采用gatt来发送配置信息的需求。因此我们需要设计出新的解决方案,即修改和更新。为了解决这个问题,我们需要先查找到appkey和netkey的设置位置和修改。
对于普通配网器SDK,我们可以看到初始化部分
appkey的绑定很容易找到,可以先追溯一下netkey的绑定位置
esp_ble_mesh_provisioner_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT);
//------------->>>函数内有一个回调参数
BTC_BLE_MESH_ACT_PROVISIONER_ENABLE
//------------->>>根据参数查找到回调函数
//------------->>>回调函数调用下面这个函数
bt_mesh_provisioner_enable(arg->provisioner_enable.bearers);
//------------->>>进入函数内部,找到关于netKey的相关函数
bt_mesh_provisioner_net_create();
//------------->>>在函数内我们可以看到netKey是随机生成的
if (bt_mesh_rand(p_key, 16)) { //生成随机的netkey
BT_ERR("Failed to generate Primary NetKey");
return -EIO;
}
//------------->>>生成netkey之后进行绑定
bt_mesh_net_keys_create(&sub->keys[0], p_key)
由上述追溯可知,我们最简单的方式就是用自己接收到的netKey把上面那个rand变量替换即可。但是,建议是不要修改底层,往应用层修改,于是有了下面的修改方法
//首先定义一个结构体用于存储需要使用的netKey
struct add_local_net_key_args {
uint8_t net_key[16];
uint16_t net_idx;
} add_local_net_key;//定义了一个结构体变量
//然后开始对结构体赋值,因为我们采用蓝牙辅助(随便一个蓝牙助手工具都可以)的形式发送的,所以需要更新netKey
//即收到蓝牙数据时先将netKey存入flash,调用netKey更新函数更新netKey
//蓝牙mesh netkey更新函数
void ble_mesh_updata_net_key(void)//当蓝牙辅助配网完成后,获取到手机发送的Netkey,更新网关net key
{
size_t length=16;
char net_key[16]={0};//产品secret,用于读取
nvs_get_blob(skynvs_handle,MESH_NET_KEY,net_key, &length);//读取appkey
memcpy(add_local_net_key.net_key, net_key, sizeof(add_local_net_key.net_key));
esp_ble_mesh_provisioner_update_local_net_key(add_local_net_key.net_key, prov_key.net_idx);
}
//
//由于我们采用了掉电记忆,所以初始化时也需要解决这个问题,所以做了如下初始化处理
add_local_net_key.net_idx = NET_KEY_IDX;//netkey index 0x0000
char net_key[16]={2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2};//APPkey,用于mesh网络通信加密
nvs_get_blob(skynvs_handle, MESH_NET_KEY,net_key,&length);//KV存储APPKEY,读取失败,原值保持不变
memcpy(add_local_net_key.net_key, net_key, sizeof(add_local_net_key.net_key));
//此函数为原有函数,为确定下面函数的添加位置而写在此处
err = esp_ble_mesh_provisioner_prov_enable(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to enable mesh provisioner (err %d)", err);
return err;
}
const uint8_t*nowNetKey= bt_mesh_provisioner_local_net_key_get(add_local_net_key.net_idx);//查找netkey
if(nowNetKey==NULL){ //未绑定netkey
esp_ble_mesh_provisioner_add_local_net_key(add_local_net_key.net_key,add_local_net_key.net_idx);//绑定netKey
ESP_LOGI(TAG,"%s,line=%d,NET KEY need add",__func__,__LINE__);
}else{ //已经绑定过netkey
printf("now net_key=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",nowNetKey[0],nowNetKey[1],nowNetKey[2],nowNetKey[3],nowNetKey[4],nowNetKey[5],
nowNetKey[6],nowNetKey[7],nowNetKey[8],nowNetKey[9],nowNetKey[10],nowNetKey[11],nowNetKey[12],nowNetKey[13],nowNetKey[14],nowNetKey[15]);
if(memcmp(add_local_net_key.net_key,nowNetKey,sizeof(add_local_net_key.net_key))==0){//目标netkey和实际netKey已经相等
ESP_LOGI(TAG,"%s,line=%d,net key is same",__func__,__LINE__);
//不需要做任何动作
}else{ //netKEY需要更新
esp_ble_mesh_provisioner_update_local_net_key(add_local_net_key.net_key,add_local_net_key.net_idx);//更新netkey
ESP_LOGI(TAG,"%s,line=%d,net key need updata",__func__,__LINE__);
}
}
此时,netKey的更新同步和掉电重启都已经设置完毕,appKey于其类似,基本一模一样的流程(就不再赘述),关于更新谁先谁后都一样可以正常使用。(可能也是我没有测出问题)
此处测试出的问题和坑点就是有个函数和我们调用的函数类似,调用了好像没用,也没有细细研究,有哪个大佬懂的也可以解惑一下
//两个相似的绑定函数
esp_ble_mesh_provisioner_add_local_net_key()
bt_ble_mesh_provisioner_add_local_net_key()
//两个相似的更新函数
esp_ble_mesh_provisioner_update_local_net_key()
bt_ble_mesh_provisioner_update_local_net_key()
怎样验证是否更新正常
由于配网过程中存在netKey和appKey的下发过程,为了大家都便于验证,就直接采用esp32作为网关和节点吧,采用其他芯片作为节点方法类似
网关端:
prov_random()
//----》内部调用
send_prov_data()
//----》
netkey = bt_mesh_provisioner_net_key_get(prov_ctx.curr_net_idx);
//此处调用一个打印函数打印出netkey即可验证网关端下发的是否正确
子设备端:
netKey
prov_data()
//---》函数内部调用
bt_mesh_provision()
//---》在函数开始位置打印netkey即可验证netkey是否和网关一致
BT_ERR("cyy net_key %s", bt_hex(net_key, 16));
//---》下面这个函数是不是很熟悉,就不强调了
bt_mesh_net_create()
appkey,此时,appKey和netKey的验证方式不太一样,因为appKey的绑定是在mesh关系建立以后,我们只需要在mesh配置回调函数打印即可
example_ble_mesh_config_server_cb()
//------------》》》内部有个opcode回调
ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD
//------------》》》只需要在这个回调打印appKey即可
ESP_LOG_BUFFER_HEX("AppKey", param->value.state_change.appkey_add.app_key, 16);
由此,添加和验证完成,正常情况下,已经完成了,如果你还不放心(也可能是控制不了),怕哪里有遗漏,你可以采用下面方法再验证
可以用定时器的方式反复给子节点发送消息验证是否成功,但是这样太麻烦了,因此我们采用更高效的方法。
因为网关代码默认会在添加完成后去控制一下子设备
所以,我们只要控制完成之后再去控制一下就能达成不断控制的效果
case ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT:
switch (opcode) {
case ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET:
node->onoff = param->status_cb.onoff_status.present_onoff;
ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET onoff: 0x%02x", node->onoff);
esp_ble_mesh_generic_client_set_state_t set_state = {0};
node->onoff = param->status_cb.onoff_status.present_onoff;
ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET onoff: 0x%02x", node->onoff);
/* After Generic OnOff Status for Generic OnOff Get is received, Generic OnOff Set will be sent */
example_ble_mesh_set_msg_common(&common, node, onoff_client.model, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET);
set_state.onoff_set.op_en = false;
set_state.onoff_set.onoff = !node->onoff;
set_state.onoff_set.tid = 1;
vTaskDelay(5000/ portTICK_PERIOD_MS);
err = esp_ble_mesh_generic_client_set_state(&common, &set_state);
if (err) {
ESP_LOGE(TAG, "%s: Config Model App Bind failed", __func__);
return;
}
vTaskDelay(5000/ portTICK_PERIOD_MS);
err = esp_ble_mesh_generic_client_set_state(&common, &set_state);
if (err) {
ESP_LOGE(TAG, "%s: Config Model App Bind failed", __func__);
return;
}
break;
至此,互控设计完成,大佬们有什么不同见解希望指正交流。
小博写文章为了帮助更多的人更快解决问题,看完有用记得三连!!还有什么想看的基于ESP32蓝牙mesh或者基于TLSR8258的小问题也可以留言讨论