ESP32 BLE MESH Vendor MODEL详解

最近在MESH的开发中,遇到了很多问题,于是开始把目光放到了Vendor模型(自定义模型)上

ESP32官方对于Vendor Model有两个例程,一个vendor server,一个vendor client

于是做此文记录关于vendor模型的学习(本文会默认已经看过前面provisioner和onoff的文章,一些ble mesh的细节会简略描述)

首先先看vendor client,首先是大段的宏定义

#define CID_ESP             0x02E5
#define PROV_OWN_ADDR       0x0001
#define MSG_SEND_TTL        3
#define MSG_TIMEOUT         0
#define MSG_ROLE            ROLE_PROVISIONER
#define COMP_DATA_PAGE_0    0x00
#define APP_KEY_IDX         0x0000
#define APP_KEY_OCTET       0x12
#define COMP_DATA_1_OCTET(msg, offset)      (msg[offset])
#define COMP_DATA_2_OCTET(msg, offset)      (msg[offset + 1] << 8 | msg[offset])
#define ESP_BLE_MESH_VND_MODEL_ID_CLIENT    0x0000
#define ESP_BLE_MESH_VND_MODEL_ID_SERVER    0x0001
#define ESP_BLE_MESH_VND_MODEL_OP_SEND      ESP_BLE_MESH_MODEL_OP_3(0x00, CID_ESP)
#define ESP_BLE_MESH_VND_MODEL_OP_STATUS    ESP_BLE_MESH_MODEL_OP_3(0x01, CID_ESP)

这里面主要是以下内容:

  • CID_ESP 0x02E5:Espressif 的公司 ID,用于 Vendor 模型。
  • PROV_OWN_ADDR 0x0001:Provisioner 的单播地址。
  • MSG_SEND_TTL 3:消息的生存时间(TTL),表示消息最多转发 3 次。
  • MSG_TIMEOUT 0:消息超时时间,0 表示无超时限制。
  • MSG_ROLE :ROLE_PROVISIONER:设备角色为 Provisioner(配置者)。
  • COMP_DATA_PAGE_0 0x00:Composition Data 的页面编号。
  • APP_KEY_IDX 0x0000:应用密钥索引。
  • APP_KEY_OCTET 0x12:应用密钥的一个字节值。
  • COMP_DATA_1_OCTET 和 COMP_DATA_2_OCTET:宏用于解析 Composition Data 的 1 字节和 2 字节数据。
  • ESP_BLE_MESH_VND_MODEL_ID_CLIENT 0x0000 和 ESP_BLE_MESH_VND_MODEL_ID_SERVER 0x0001:定义 Vendor 模型的客户端和服务端 ID。
  • ESP_BLE_MESH_VND_MODEL_OP_SEND 和 ESP_BLE_MESH_VND_MODEL_OP_STATUS:定义 Vendor 模型的操作码(opcode),用于发送和接收状态消息。

接下来,按照main函数运行的数据,开始分析

首先是ble_mesh_init

static esp_err_t ble_mesh_init(void)
{
    uint8_t match[2] = { 0x32, 0x10 };
    esp_err_t err;

    prov_key.net_idx = ESP_BLE_MESH_KEY_PRIMARY;
    prov_key.app_idx = APP_KEY_IDX;
    memset(prov_key.app_key, APP_KEY_OCTET, sizeof(prov_key.app_key));

    esp_ble_mesh_register_prov_callback(example_ble_mesh_provisioning_cb);
    esp_ble_mesh_register_config_client_callback(example_ble_mesh_config_client_cb);
    esp_ble_mesh_register_custom_model_callback(example_ble_mesh_custom_model_cb);

    err = esp_ble_mesh_init(&provision, &composition);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to initialize mesh stack");
        return err;
    }

    err = esp_ble_mesh_client_model_init(&vnd_models[0]);
    if (err) {
        ESP_LOGE(TAG, "Failed to initialize vendor client");
        return err;
    }

    err = esp_ble_mesh_provisioner_set_dev_uuid_match(match, sizeof(match), 0x0, false);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set matching device uuid");
        return err;
    }

    err = esp_ble_mesh_provisioner_prov_enable((esp_ble_mesh_prov_bearer_t)(ESP_BLE_MESH_PROV_ADV | ESP_BLE_MESH_PROV_GATT));
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to enable mesh provisioner");
        return err;
    }

    err = esp_ble_mesh_provisioner_add_local_app_key(prov_key.app_key, prov_key.net_idx, prov_key.app_idx);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to add local AppKey");
        return err;
    }

    ESP_LOGI(TAG, "ESP BLE Mesh Provisioner initialized");

    return ESP_OK;
}

首先是UUID的匹配,这次修改了万年不变的0xdd 0xdd,不多赘述

然后是配置net key和app key

接下来注册三个回调,整个mesh的大头也是在这里

    esp_ble_mesh_register_prov_callback(example_ble_mesh_provisioning_cb);
    esp_ble_mesh_register_config_client_callback(example_ble_mesh_config_client_cb);
    esp_ble_mesh_register_custom_model_callback(example_ble_mesh_custom_model_cb);

首先第一个是配网回调函数,这块和之前onoff的provisioning类似

static void example_ble_mesh_provisioning_cb(esp_ble_mesh_prov_cb_event_t event,
                                             esp_ble_mesh_prov_cb_param_t *param)
{
    switch (event) {
    case ESP_BLE_MESH_PROV_REGISTER_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROV_REGISTER_COMP_EVT, err_code %d", param->prov_register_comp.err_code);
        mesh_example_info_restore(); /* Restore proper mesh example info */
        break;
    case ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT, err_code %d", param->provisioner_prov_enable_comp.err_code);
        break;
    case ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT, err_code %d", param->provisioner_prov_disable_comp.err_code);
        break;
    case ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT");
        recv_unprov_adv_pkt(param->provisioner_recv_unprov_adv_pkt.dev_uuid, param->provisioner_recv_unprov_adv_pkt.addr,
                            param->provisioner_recv_unprov_adv_pkt.addr_type, param->provisioner_recv_unprov_adv_pkt.oob_info,
                            param->provisioner_recv_unprov_adv_pkt.adv_type, param->provisioner_recv_unprov_adv_pkt.bearer);
        break;
    case ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT, bearer %s",
            param->provisioner_prov_link_open.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT");
        break;
    case ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT, bearer %s, reason 0x%02x",
            param->provisioner_prov_link_close.bearer == ESP_BLE_MESH_PROV_ADV ? "PB-ADV" : "PB-GATT", param->provisioner_prov_link_close.reason);
        break;
    case ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT:
        prov_complete(param->provisioner_prov_complete.node_idx, param->provisioner_prov_complete.device_uuid,
                      param->provisioner_prov_complete.unicast_addr, param->provisioner_prov_complete.element_num,
                      param->provisioner_prov_complete.netkey_idx);
        break;
    case ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_ADD_UNPROV_DEV_COMP_EVT, err_code %d", param->provisioner_add_unprov_dev_comp.err_code);
        break;
    case ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_SET_DEV_UUID_MATCH_COMP_EVT, err_code %d", param->provisioner_set_dev_uuid_match_comp.err_code);
        break;
    case ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT, err_code %d", param->provisioner_set_node_name_comp.err_code);
        if (param->provisioner_set_node_name_comp.err_code == 0) {
            const char *name = esp_ble_mesh_provisioner_get_node_name(param->provisioner_set_node_name_comp.node_index);
            if (name) {
                ESP_LOGI(TAG, "Node %d name %s", param->provisioner_set_node_name_comp.node_index, name);
            }
        }
        break;
    case ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_ADD_LOCAL_APP_KEY_COMP_EVT, err_code %d", param->provisioner_add_app_key_comp.err_code);
        if (param->provisioner_add_app_key_comp.err_code == 0) {
            prov_key.app_idx = param->provisioner_add_app_key_comp.app_idx;
            esp_err_t err = esp_ble_mesh_provisioner_bind_app_key_to_local_model(PROV_OWN_ADDR, prov_key.app_idx,
                    ESP_BLE_MESH_VND_MODEL_ID_CLIENT, CID_ESP);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Failed to bind AppKey to vendor client");
            }
        }
        break;
    case ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_BIND_APP_KEY_TO_MODEL_COMP_EVT, err_code %d", param->provisioner_bind_app_key_to_model_comp.err_code);
        break;
    case ESP_BLE_MESH_PROVISIONER_STORE_NODE_COMP_DATA_COMP_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_PROVISIONER_STORE_NODE_COMP_DATA_COMP_EVT, err_code %d", param->provisioner_store_node_comp_data_comp.err_code);
        break;
    default:
        break;
    }
}

第一个事件是配网完成事件

ESP_BLE_MESH_PROV_REGISTER_COMP_EVT

在这里面调用了一个配网完成的存储函数

mesh_example_info_restore();

函数内容如下:

static void mesh_example_info_restore(void)
{
    esp_err_t err = ESP_OK;
    bool exist = false;

    err = ble_mesh_nvs_restore(NVS_HANDLE, NVS_KEY, &store, sizeof(store), &exist);
    if (err != ESP_OK) {
        return;
    }

    if (exist) {
        ESP_LOGI(TAG, "Restore, server_addr 0x%04x, vnd_tid 0x%04x", store.server_addr, store.vnd_tid);
    }
}

第二个事件,是配网功能启动成功的事件

ESP_BLE_MESH_PROVISIONER_PROV_ENABLE_COMP_EVT

这里只添加了一个打印,在实际场景中,可以根据自己的需求添加

第三个事件,是禁用配网功能的事件

ESP_BLE_MESH_PROVISIONER_PROV_DISABLE_COMP_EVT

这里还是只有打印,实际场景中,可以根据需求增加内容,比如在配网完成后,禁用配网,启动其他的功能,比如服务器启动,wifi通信等

第四个事件,是发现未配网广播的事件

ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT

这里和onoff的很像,也是调用了一个函数recv_unprov_adv_pkt

bu'guo不过recv_unprov_adv_pkt的内容有所变化

static void recv_unprov_adv_pkt(uint8_t dev_uuid[ESP_BLE_MESH_OCTET16_LEN], uint8_t addr[BD_ADDR_LEN],
                                esp_ble_mesh_addr_type_t addr_type, uint16_t oob_info,
                                uint8_t adv_type, esp_ble_mesh_prov_bearer_t bearer)
{
    esp_ble_mesh_unprov_dev_add_t add_dev = {0};
    esp_err_t err;

    ESP_LOG_BUFFER_HEX("Device address", addr, BD_ADDR_LEN);
    ESP_LOGI(TAG, "Address type 0x%02x, adv type 0x%02x", addr_type, adv_type);
    ESP_LOG_BUFFER_HEX("Device UUID", dev_uuid, ESP_BLE_MESH_OCTET16_LEN);
    ESP_LOGI(TAG, "oob info 0x%04x, bearer %s", oob_info, (bearer & ESP_BLE_MESH_PROV_ADV) ? "PB-ADV" : "PB-GATT");

    memcpy(add_dev.addr, addr, BD_ADDR_LEN);
    add_dev.addr_type = (esp_ble_mesh_addr_type_t)addr_type;
    memcpy(add_dev.uuid, dev_uuid, ESP_BLE_MESH_OCTET16_LEN);
    add_dev.oob_info = oob_info;
    add_dev.bearer = (esp_ble_mesh_prov_bearer_t)bearer;
    err = esp_ble_mesh_provisioner_add_unprov_dev(&add_dev,
            ADD_DEV_RM_AFTER_PROV_FLAG | ADD_DEV_START_PROV_NOW_FLAG | ADD_DEV_FLUSHABLE_DEV_FLAG);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to start provisioning device");
    }
}

不过主要还是调用了

err = esp_ble_mesh_provisioner_add_unprov_dev(&add_dev, ADD_DEV_RM_AFTER_PROV_FLAG | ADD_DEV_START_PROV_NOW_FLAG | ADD_DEV_FLUSHABLE_DEV_FLAG);:

进行子节点配网

接下来是通信链路打开和关闭事件,不多做赘述

ESP_BLE_MESH_PROVISIONER_PROV_LINK_OPEN_EVT

ESP_BLE_MESH_PROVISIONER_PROV_LINK_CLOSE_EVT

然后是重头戏,配网完成事件

ESP_BLE_MESH_PROVISIONER_PROV_COMPLETE_EVT

这里还是调用了prov_complete函数,同样也是应用层自己实现的

static esp_err_t prov_complete(uint16_t node_index, const esp_ble_mesh_octet16_t uuid,
                               uint16_t primary_addr, uint8_t element_num, uint16_t net_idx)
{
    esp_ble_mesh_client_common_param_t common = {0};
    esp_ble_mesh_cfg_client_get_state_t get = {0};
    esp_ble_mesh_node_t *node = NULL;
    char name[10] = {'\0'};
    esp_err_t err;

    ESP_LOGI(TAG, "node_index %u, primary_addr 0x%04x, element_num %u, net_idx 0x%03x",
        node_index, primary_addr, element_num, net_idx);
    ESP_LOG_BUFFER_HEX("uuid", uuid, ESP_BLE_MESH_OCTET16_LEN);

    store.server_addr = primary_addr;
    mesh_example_info_store(); /* Store proper mesh example info */

    sprintf(name, "%s%02x", "NODE-", node_index);
    err = esp_ble_mesh_provisioner_set_node_name(node_index, name);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to set node name");
        return ESP_FAIL;
    }

    node = esp_ble_mesh_provisioner_get_node_with_addr(primary_addr);
    if (node == NULL) {
        ESP_LOGE(TAG, "Failed to get node 0x%04x info", primary_addr);
        return ESP_FAIL;
    }

    example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET);
    get.comp_data_get.page = COMP_DATA_PAGE_0;
    err = esp_ble_mesh_config_client_get_state(&common, &get);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to send Config Composition Data Get");
        return ESP_FAIL;
    }

    return ESP_OK;
}

在里面,首先打印了配网节点的信息,我这块同时运行了server和client,配网完成打印信息如下:

I (544166) EXAMPLE: node_index 0, primary_addr 0x0005, element_num 1, net_idx 0x000
I (544166) uuid: 32 10 74 4d bd 61 ef 26 00 00 00 00 00 00 00 00

然后调用mesh_example_info_store存储节点信息,这个也是调用了系统提供的一个示例储存函数

esp_err_t ble_mesh_nvs_store(nvs_handle_t handle, const char *key, const void *data, size_t length)
{
    esp_err_t err = ESP_OK;

    if (key == NULL || data == NULL || length == 0) {
        ESP_LOGE(TAG, "Store, invalid parameter");
        return ESP_ERR_INVALID_ARG;
    }

    err = nvs_set_blob(handle, key, data, length);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Store, nvs_set_blob failed, err %d", err);
        return err;
    }

    err = nvs_commit(handle);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Store, nvs_commit failed, err %d", err);
        return err;
    }

    ESP_LOGI(TAG, "Store, key \"%s\", length %u", key, length);
    ESP_LOG_BUFFER_HEX("EXAMPLE_NVS: Store, data", data, length);
    return err;
}

运行时会打印如下内容

I (544166) EXAMPLE_NVS: Store, key "vendor_client", length 4
I (544176) EXAMPLE_NVS: Store, data: 05 00 00 00
I (544166) EXAMPLE_NVS: Store, key "vendor_client", length 4
I (544176) EXAMPLE_NVS: Store, data: 05 00 00 00
I (544176) EXAMPLE_NVS: Store, data: 05 00 00 00

接下来调用esp_ble_mesh_provisioner_set_node_name设置节点名称

I (544176) EXAMPLE: ESP_BLE_MESH_PROVISIONER_SET_NODE_NAME_COMP_EVT, err_code 0
I (544186) EXAMPLE: Node 0 name NODE-00

然后调用esp_ble_mesh_provisioner_get_node_with_addr获取节点信息

之后将获取的信息调用example_ble_mesh_set_msg_common设置消息参数

static void example_ble_mesh_set_msg_common(esp_ble_mesh_client_common_param_t *common,
                                            esp_ble_mesh_node_t *node,
                                            esp_ble_mesh_model_t *model, uint32_t opcode)
{
    common->opcode = opcode;
    common->model = model;
    common->ctx.net_idx = prov_key.net_idx;
    common->ctx.app_idx = prov_key.app_idx;
    common->ctx.addr = node->unicast_addr;
    common->ctx.send_ttl = MSG_SEND_TTL;
    common->msg_timeout = MSG_TIMEOUT;
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0)
    common->msg_role = MSG_ROLE;
#endif
}

然后通过err = esp_ble_mesh_config_client_get_state(&common, &get);接口,来发送 Composition Data Get 消息,获取节点模型信息。

接下来的几个事件,和provisioner例程的差不多,设置节点名称完成,绑定app key,储存Composition Data,这里不多介绍了,都是打印了一个日志,这些在实际场景应用中的用法,我也没太搞懂

接下来是第二个回调函数

static void example_ble_mesh_config_client_cb(esp_ble_mesh_cfg_client_cb_event_t event,
                                              esp_ble_mesh_cfg_client_cb_param_t *param)
{
    esp_ble_mesh_client_common_param_t common = {0};
    esp_ble_mesh_cfg_client_set_state_t set = {0};
    esp_ble_mesh_node_t *node = NULL;
    esp_err_t err;

    ESP_LOGI(TAG, "Config client, err_code %d, event %u, addr 0x%04x, opcode 0x%04" PRIx32,
        param->error_code, event, param->params->ctx.addr, param->params->opcode);

    if (param->error_code) {
        ESP_LOGE(TAG, "Send config client message failed, opcode 0x%04" PRIx32, param->params->opcode);
        return;
    }

    node = esp_ble_mesh_provisioner_get_node_with_addr(param->params->ctx.addr);
    if (!node) {
        ESP_LOGE(TAG, "Failed to get node 0x%04x info", param->params->ctx.addr);
        return;
    }

    switch (event) {
    case ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT:
        if (param->params->opcode == ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET) {
            ESP_LOG_BUFFER_HEX("Composition data", param->status_cb.comp_data_status.composition_data->data,
                param->status_cb.comp_data_status.composition_data->len);
            example_ble_mesh_parse_node_comp_data(param->status_cb.comp_data_status.composition_data->data,
                param->status_cb.comp_data_status.composition_data->len);
            err = esp_ble_mesh_provisioner_store_node_comp_data(param->params->ctx.addr,
                param->status_cb.comp_data_status.composition_data->data,
                param->status_cb.comp_data_status.composition_data->len);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Failed to store node composition data");
                break;
            }

            example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD);
            set.app_key_add.net_idx = prov_key.net_idx;
            set.app_key_add.app_idx = prov_key.app_idx;
            memcpy(set.app_key_add.app_key, prov_key.app_key, ESP_BLE_MESH_OCTET16_LEN);
            err = esp_ble_mesh_config_client_set_state(&common, &set);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Failed to send Config AppKey Add");
            }
        }
        break;
    case ESP_BLE_MESH_CFG_CLIENT_SET_STATE_EVT:
        if (param->params->opcode == ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD) {
            example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND);
            set.model_app_bind.element_addr = node->unicast_addr;
            set.model_app_bind.model_app_idx = prov_key.app_idx;
            set.model_app_bind.model_id = ESP_BLE_MESH_VND_MODEL_ID_SERVER;
            set.model_app_bind.company_id = CID_ESP;
            err = esp_ble_mesh_config_client_set_state(&common, &set);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Failed to send Config Model App Bind");
            }
        } else if (param->params->opcode == ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND) {
            ESP_LOGW(TAG, "%s, Provision and config successfully", __func__);
        }
        break;
    case ESP_BLE_MESH_CFG_CLIENT_PUBLISH_EVT:
        if (param->params->opcode == ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_STATUS) {
            ESP_LOG_BUFFER_HEX("Composition data", param->status_cb.comp_data_status.composition_data->data,
                param->status_cb.comp_data_status.composition_data->len);
        }
        break;
    case ESP_BLE_MESH_CFG_CLIENT_TIMEOUT_EVT:
        switch (param->params->opcode) {
        case ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET: {
            esp_ble_mesh_cfg_client_get_state_t get = {0};
            example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_COMPOSITION_DATA_GET);
            get.comp_data_get.page = COMP_DATA_PAGE_0;
            err = esp_ble_mesh_config_client_get_state(&common, &get);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Failed to send Config Composition Data Get");
            }
            break;
        }
        case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD:
            example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD);
            set.app_key_add.net_idx = prov_key.net_idx;
            set.app_key_add.app_idx = prov_key.app_idx;
            memcpy(set.app_key_add.app_key, prov_key.app_key, ESP_BLE_MESH_OCTET16_LEN);
            err = esp_ble_mesh_config_client_set_state(&common, &set);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Failed to send Config AppKey Add");
            }
            break;
        case ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND:
            example_ble_mesh_set_msg_common(&common, node, config_client.model, ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND);
            set.model_app_bind.element_addr = node->unicast_addr;
            set.model_app_bind.model_app_idx = prov_key.app_idx;
            set.model_app_bind.model_id = ESP_BLE_MESH_VND_MODEL_ID_SERVER;
            set.model_app_bind.company_id = CID_ESP;
            err = esp_ble_mesh_config_client_set_state(&common, &set);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Failed to send Config Model App Bind");
            }
            break;
        default:
            break;
        }
        break;
    default:
        ESP_LOGE(TAG, "Invalid config client event %u", event);
        break;
    }
}

这个用于处理client相关事件

首先是ESP_BLE_MESH_CFG_CLIENT_GET_STATE_EVT

这个用于获取客户端响应状态,首先洁厕opcode是否为Composition Data并打印,之后调用example_ble_mesh_parse_node_comp_data解析Composition Data

再上一个回调函数中配网完成会调用一次esp_ble_mesh_config_client_get_state,之后进入该事件,打印信息

I (544646) Composition data: e5 02 00 00 00 00 0a 00 03 00 00 00 01 01 00 00
I (544646) Composition data: e5 02 00 00 00 00 0a 00 03 00 00 00 01 01 00 00
I (544646) Composition data: e5 02 01 00
I (544646) EXAMPLE: ********************** Composition Data Start **********************
I (544656) EXAMPLE: * CID 0x02e5, PID 0x0000, VID 0x0000, CRPL 0x000a, Features 0x0003 *
I (544646) EXAMPLE: ********************** Composition Data Start **********************
I (544656) EXAMPLE: * CID 0x02e5, PID 0x0000, VID 0x0000, CRPL 0x000a, Features 0x0003 *
I (544656) EXAMPLE: * CID 0x02e5, PID 0x0000, VID 0x0000, CRPL 0x000a, Features 0x0003 *
I (544666) EXAMPLE: * Loc 0x0000, NumS 0x01, NumV 0x01 *
I (544666) EXAMPLE: * SIG Model ID 0x0000 *
I (544676) EXAMPLE: * Vendor Model ID 0x0001, Company ID 0x02e5 *

example_ble_mesh_parse_node_comp_data的具体内容如下:

static void example_ble_mesh_parse_node_comp_data(const uint8_t *data, uint16_t length)
{
    uint16_t cid, pid, vid, crpl, feat;
    uint16_t loc, model_id, company_id;
    uint8_t nums, numv;
    uint16_t offset;
    int i;

    cid = COMP_DATA_2_OCTET(data, 0);
    pid = COMP_DATA_2_OCTET(data, 2);
    vid = COMP_DATA_2_OCTET(data, 4);
    crpl = COMP_DATA_2_OCTET(data, 6);
    feat = COMP_DATA_2_OCTET(data, 8);
    offset = 10;

    ESP_LOGI(TAG, "********************** Composition Data Start **********************");
    ESP_LOGI(TAG, "* CID 0x%04x, PID 0x%04x, VID 0x%04x, CRPL 0x%04x, Features 0x%04x *", cid, pid, vid, crpl, feat);
    for (; offset < length; ) {
        loc = COMP_DATA_2_OCTET(data, offset);
        nums = COMP_DATA_1_OCTET(data, offset + 2);
        numv = COMP_DATA_1_OCTET(data, offset + 3);
        offset += 4;
        ESP_LOGI(TAG, "* Loc 0x%04x, NumS 0x%02x, NumV 0x%02x *", loc, nums, numv);
        for (i = 0; i < nums; i++) {
            model_id = COMP_DATA_2_OCTET(data, offset);
            ESP_LOGI(TAG, "* SIG Model ID 0x%04x *", model_id);
            offset += 2;
        }
        for (i = 0; i < numv; i++) {
            company_id = COMP_DATA_2_OCTET(data, offset);
            model_id = COMP_DATA_2_OCTET(data, offset + 2);
            ESP_LOGI(TAG, "* Vendor Model ID 0x%04x, Company ID 0x%04x *", model_id, company_id);
            offset += 4;
        }
    }
    ESP_LOGI(TAG, "*********************** Composition Data End ***********************");
}

### ESP32-C3 BLE Mesh 网络配置教程 #### 设备角色定义 为了建立一个BLE Mesh网络,至少需要两个ESP32-C3设备分别担任Provisioner和Node的角色。Provisioner负责初始化网络并将其他节点加入其中;而Node则是接受管理并参与数据交换的一员。 #### 软件环境搭建 确保已安装最新版本的Espressif IoT Development Framework (ESP-IDF),并且能够正常工作于Windows操作系统之上[^3]。对于项目创建而言,推荐采用CMake工具链来加速编译过程,这相较于传统的makefile方式有着显著的速度优势。 #### 配置文件调整 进入`menuconfig`界面完成必要的选项设定,比如开启BLE Mesh协议栈支持等功能特性。此步骤至关重要,它决定了硬件资源分配以及软件行为模式的选择。 #### 初始化代码片段 以下是用于启动BLE Mesh服务的基础框架: ```c #include "nvs_flash.h" #include "esp_bt.h" #include "esp_ble_mesh_provisioning_api.h" #include "esp_ble_mesh_networking_api.h" void app_main(void){ nvs_flash_init(); esp_err_t ret; /* Initialize the Bluetooth NVS */ ret = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT); if(ret != ESP_OK) { printf("Error initializing BT controller\n"); return; } esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); ret = esp_bt_controller_init(&bt_cfg); if(ret != ESP_OK) { printf("BT Controller init failed\n"); return; } // ...继续添加更多初始化逻辑... } ``` 这段程序首先完成了NVS闪存区初始化操作,并释放了经典蓝牙占用的空间以便给低功耗蓝牙让位。接着按照默认参数表设置了控制器实例化条件。 #### Provisioner端核心函数调用 当扮演Provisioner身份时,需执行如下关键动作序列以引导新成员入网: ```c static void provision_device(const uint8_t *uuid, const char *device_name){ esp_ble_mesh_client_model_t root_models[] = { /* 客户端模型数组 */ }; esp_ble_mesh_elem_t elements[] = {{0, ARRAY_SIZE(root_models), root_models}}; esp_ble_mesh_comp_t composition = {elements}; esp_ble_mesh_node_t node_info = {/* 填充目标节点信息结构体*/}; esp_ble_mesh_provisioner_add_unprov_dev(uuid, device_name, strlen(device_name), &node_info); // 发送邀请消息给未认证过的装置尝试将其纳入当前mesh体系内 } // 注册provision成功后的回调处理方法 static void prov_complete(uint16_t net_idx, uint16_t addr, uint8_t status){ if(status == ESP_BLE_MESH_PROV_SUCCESS){ printf("Device has been successfully provisioned!\n"); }else{ printf("Failed to provision device with error code:%d\n",status); } } ``` 上述代码展示了怎样向指定UUID对应的候选对象发出接纳请求,同时指定了成功的响应机制。 #### Node端主要流程控制 针对充当普通节点的情况,则应关注自身的定位声明及其属性上报环节: ```c #define ELEMENT_COUNT 1 #define MODEL_COUNT_PER_ELEMENT 2 const esp_ble_mesh_model_pub_t pub = {/* 出版物设置 */ }; /* Server Models */ const esp_ble_mesh_gen_onoff_srv_t on_off_server = {/* 开关服务器模型实例化 */ }; esp_ble_mesh_model_t models[ELEMENT_COUNT][MODEL_COUNT_PER_ELEMENT]={ [{ESP_BLE_MESH_MODEL_GEN_ONOFF_SRV(&on_off_server)}, {ESP_BLE_MESH_MODEL_CFG_CLI()}, ] }; esp_ble_mesh_elem_t element_list[ELEMENT_COUNT]= {[0]={.model_count=ARRAY_SIZE(models[0]), .models=models[0]}}; esp_ble_mesh_comp_t comp={ .element_count=ELEMENT_COUNT,.element_list=element_list }; void setup_as_node(){ esp_ble_mesh_register_prov_callback(prov_complete); // 设置预验证完毕通知监听器 esp_ble_mesh_set CompositionData(&comp); // 提交组件描述符记录 esp_ble_mesh_start_provisioning(); // 正式激活成为可被探测到的新成员 } ``` 这里说明了一个典型的节点如何准备自己供外部访问,并准备好接收来自Provisioner的消息指令。 #### 数据传输实践案例 最后给出一段基于Generic OnOff Model实现灯控效果的小例子: ```c extern esp_ble_mesh_gen_onoff_srv_t my_light; void handle_generic_onoff_get(esp_ble_mesh_model_t *model, esp_ble_mesh_msg_ctx_t *ctx, esp_ble_mesh_gen_onoff_get_t *msg){ esp_ble_mesh_gen_onoff_status_t rsp={my_light.onoff_state}; esp_ble_mesh_send_rsp(model, ctx, ESP_BLE_MESH_GENERIC_ONOFF_STATUS_MSG,&rsp,sizeof(rsp)); } void handle_generic_onoff_set(esp_ble_mesh_model_t *model, esp_ble_mesh_msg_ctx_t *ctx, esp_ble_mesh_gen_onoff_set_t *msg){ my_light.onoff_state=msg->onoff; update_physical_switch(my_light.onoff_state); // 更新实际开关状态 handle_generic_onoff_get(model,ctx,NULL); // 反馈变更结果给发送方 } ``` 通过这些接口可以轻松模拟灯具类产品的远程操控场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值