BLE MESH 无痛入门02 Node onoff_client onoff_server 节点概述 例程详解 基于ESP32

前言

最近项目需要,需要学一些关于BLE MESH 的内容,学的比较痛苦,中文互联网里关于BLE MESH的内容很少,而且很多都是从协议栈的角度出发去讲,未免有些枯燥,我又是个英文小白,所以学起来就一脸蒙,现在稍微有些入门了,做个笔记总结一下,也方便后来者学习吧。这个笔记会以一个使用者的角度结合代码来描述。

开发环境

ESP-IDF 版本: v5.1
设备:ESP32S3 开发板 * n
nRF Mesh V3.3.0 APP

onoff_models 例程概述

本文以esp-idf/examples/bluetooth/esp_ble_mesh/onoff_models两个例程介绍ESP BLE MESH 的节点组成和具体代码实现方式。
先说例程包含的内容:
在这里插入图片描述
在这里插入图片描述
一个节点总包含一个及以上元素,每个元素总包含一个或以上模型。先介绍以下这些模型。
模型一:generic_onoff_server
应该是最简单的模型了,用于通用的开关模型服务端(通常用于接收,也可以回传自己的状态)

//回调
static void example_ble_mesh_generic_server_cb(esp_ble_mesh_generic_server_cb_event_t event,
                                               esp_ble_mesh_generic_server_cb_param_t *param)
{
    esp_ble_mesh_gen_onoff_srv_t *srv;
    ESP_LOGI(TAG, "event 0x%02x, opcode 0x%04" PRIx32 ", src 0x%04x, dst 0x%04x",
        event, param->ctx.recv_op, param->ctx.addr, param->ctx.recv_dst);

    switch (event) {
    case ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_SERVER_STATE_CHANGE_EVT");
        if (param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET ||
            param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK) {
            ESP_LOGI(TAG, "onoff 0x%02x", param->value.state_change.onoff_set.onoff);
            example_change_led_state(param->model, &param->ctx, param->value.state_change.onoff_set.onoff);
        }
        break;
    case ESP_BLE_MESH_GENERIC_SERVER_RECV_GET_MSG_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_SERVER_RECV_GET_MSG_EVT");
        if (param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET) {
            srv = param->model->user_data;
            ESP_LOGI(TAG, "onoff 0x%02x", srv->state.onoff);
            example_handle_gen_onoff_msg(param->model, &param->ctx, NULL);
        }
        break;
    case ESP_BLE_MESH_GENERIC_SERVER_RECV_SET_MSG_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_SERVER_RECV_SET_MSG_EVT");
        if (param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET ||
            param->ctx.recv_op == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK) {
            ESP_LOGI(TAG, "onoff 0x%02x, tid 0x%02x", param->value.set.onoff.onoff, param->value.set.onoff.tid);
            if (param->value.set.onoff.op_en) {
                ESP_LOGI(TAG, "trans_time 0x%02x, delay 0x%02x",
                    param->value.set.onoff.trans_time, param->value.set.onoff.delay);
            }
            example_handle_gen_onoff_msg(param->model, &param->ctx, &param->value.set.onoff);
        }
        break;
    default:
        ESP_LOGE(TAG, "Unknown Generic Server event 0x%02x", event);
        break;
    }
}
//回传状态
  esp_ble_mesh_server_model_send_msg(model, ctx,
      ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS, sizeof(srv->state.onoff), &srv->state.onoff);

模型二:generic_onoff_client
用于通用的开关模型客户端(通常用于发送)

//回调
static void example_ble_mesh_generic_client_cb(esp_ble_mesh_generic_client_cb_event_t event,
                                               esp_ble_mesh_generic_client_cb_param_t *param)
{
    ESP_LOGI(TAG, "Generic client, event %u, error code %d, opcode is 0x%04" PRIx32,
        event, param->error_code, param->params->opcode);

    switch (event) {
    case ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_CLIENT_GET_STATE_EVT");
        if (param->params->opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET) {
            ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET, onoff %d", param->status_cb.onoff_status.present_onoff);
        }
        break;
    case ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_CLIENT_SET_STATE_EVT");
        if (param->params->opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET) {
            ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET, onoff %d", param->status_cb.onoff_status.present_onoff);
        }
        break;
    case ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_CLIENT_PUBLISH_EVT");
        break;
    case ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT:
        ESP_LOGI(TAG, "ESP_BLE_MESH_GENERIC_CLIENT_TIMEOUT_EVT");
        if (param->params->opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET) {
            /* If failed to get the response of Generic OnOff Set, resend Generic OnOff Set  */
            example_ble_mesh_send_gen_onoff_set();
        }
        break;
    default:
        break;
    }
}
//发送例子
void example_ble_mesh_send_gen_onoff_set(void)
{
    esp_ble_mesh_generic_client_set_state_t set = {0};
    esp_ble_mesh_client_common_param_t common = {0};
    esp_err_t err = ESP_OK;

    common.opcode = ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK; //设置操作码
    common.model = onoff_client.model; //指定自身发送的模型
    common.ctx.net_idx = store.net_idx; 
    common.ctx.app_idx = store.app_idx;
    common.ctx.addr = 0xFFFF;   /* 发送给所有节点 */
    common.ctx.send_ttl = 3;
    common.msg_timeout = 0;     /* 0 indicates that timeout value from menuconfig will be used */
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0)
    common.msg_role = ROLE_NODE;
#endif

    set.onoff_set.op_en = false;
    set.onoff_set.onoff = store.onoff;
    set.onoff_set.tid = store.tid++;

    err = esp_ble_mesh_generic_client_set_state(&common, &set);
    if (err) {
        ESP_LOGE(TAG, "Send Generic OnOff Set Unack failed");
        return;
    }

    store.onoff = !store.onoff;
    mesh_example_info_store(); /* Store proper mesh example info */
}

模型二:config server (非常关键)
一般所有的节点(Node)都会包含一个config server,这个用于配置节点的信息。
以OnOff Client例程为例它的回调如下:

static void example_ble_mesh_config_server_cb(esp_ble_mesh_cfg_server_cb_event_t event,
                                              esp_ble_mesh_cfg_server_cb_param_t *param)
{
    if (event == ESP_BLE_MESH_CFG_SERVER_STATE_CHANGE_EVT) {
        switch (param->ctx.recv_op) {
        case ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD: //处理添加APP_key事件回调
            ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD");
            ESP_LOGI(TAG, "net_idx 0x%04x, app_idx 0x%04x",
                param->value.state_change.appkey_add.net_idx,
                param->value.state_change.appkey_add.app_idx);
            ESP_LOG_BUFFER_HEX("AppKey", param->value.state_change.appkey_add.app_key, 16);
            break;
        case ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND://处理绑定APP_key事件回调
            ESP_LOGI(TAG, "ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND");
            ESP_LOGI(TAG, "elem_addr 0x%04x, app_idx 0x%04x, cid 0x%04x, mod_id 0x%04x",
                param->value.state_change.mod_app_bind.element_addr,
                param->value.state_change.mod_app_bind.app_idx,
                param->value.state_change.mod_app_bind.company_id,
                param->value.state_change.mod_app_bind.model_id);
            if (param->value.state_change.mod_app_bind.company_id == 0xFFFF &&
                param->value.state_change.mod_app_bind.model_id == ESP_BLE_MESH_MODEL_ID_GEN_ONOFF_CLI) {
                store.app_idx = param->value.state_change.mod_app_bind.app_idx;
                mesh_example_info_store(); /* Store proper mesh example info */
            }
            break;
        default:
            break;
        }
    }
}

实际它能处理的事件远不止如此,基本和节点配置有关的操作都由这个Model实现,有需要的可以在回调的Switch Case中添加对应的事件,具体能实现的功能可以参看如下宏:

#define ESP_BLE_MESH_MODEL_OP_BEACON_SET                            ESP_BLE_MESH_MODEL_OP_2(0x80, 0x0A) /*!< 配置 Beacon 设置 */
#define ESP_BLE_MESH_MODEL_OP_DEFAULT_TTL_SET                       ESP_BLE_MESH_MODEL_OP_2(0x80, 0x0D) /*!< 配置默认 TTL 设置 */
#define ESP_BLE_MESH_MODEL_OP_GATT_PROXY_SET                        ESP_BLE_MESH_MODEL_OP_2(0x80, 0x13) /*!< 配置 GATT 代理设置 */
#define ESP_BLE_MESH_MODEL_OP_RELAY_SET                             ESP_BLE_MESH_MODEL_OP_2(0x80, 0x27) /*!< 配置 Relay 设置 */
#define ESP_BLE_MESH_MODEL_OP_MODEL_PUB_SET                         ESP_BLE_MESH_MODEL_OP_1(0x03)       /*!< 配置模型发布设置 */
#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_ADD                         ESP_BLE_MESH_MODEL_OP_2(0x80, 0x1B) /*!< 配置模型订阅添加 */
#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_ADD            ESP_BLE_MESH_MODEL_OP_2(0x80, 0x20) /*!< 配置模型虚拟地址订阅添加 */
#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE                      ESP_BLE_MESH_MODEL_OP_2(0x80, 0x1C) /*!< 配置模型订阅删除 */
#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_DELETE         ESP_BLE_MESH_MODEL_OP_2(0x80, 0x21) /*!< 配置模型虚拟地址订阅删除 */
#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_OVERWRITE                   ESP_BLE_MESH_MODEL_OP_2(0x80, 0x1E) /*!< 配置模型订阅覆盖 */
#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_VIRTUAL_ADDR_OVERWRITE      ESP_BLE_MESH_MODEL_OP_2(0x80, 0x22) /*!< 配置模型虚拟地址订阅覆盖 */
#define ESP_BLE_MESH_MODEL_OP_NET_KEY_ADD                           ESP_BLE_MESH_MODEL_OP_2(0x80, 0x40) /*!< 配置网络密钥添加 */
#define ESP_BLE_MESH_MODEL_OP_APP_KEY_ADD                           ESP_BLE_MESH_MODEL_OP_1(0x00)       /*!< 配置应用密钥添加 */
#define ESP_BLE_MESH_MODEL_OP_MODEL_APP_BIND                        ESP_BLE_MESH_MODEL_OP_2(0x80, 0x3D) /*!< 配置模型应用绑定 */
#define ESP_BLE_MESH_MODEL_OP_NODE_RESET                            ESP_BLE_MESH_MODEL_OP_2(0x80, 0x49) /*!< 配置节点重置 */
#define ESP_BLE_MESH_MODEL_OP_FRIEND_SET                            ESP_BLE_MESH_MODEL_OP_2(0x80, 0x10) /*!< 配置友好设置 */
#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_PUB_SET                     ESP_BLE_MESH_MODEL_OP_2(0x80, 0x39) /*!< 配置心跳发布设置 */
#define ESP_BLE_MESH_MODEL_OP_HEARTBEAT_SUB_SET                     ESP_BLE_MESH_MODEL_OP_2(0x80, 0x3B) /*!< 配置心跳订阅设置 */
#define ESP_BLE_MESH_MODEL_OP_NET_KEY_UPDATE                        ESP_BLE_MESH_MODEL_OP_2(0x80, 0x45) /*!< 配置网络密钥更新 */
#define ESP_BLE_MESH_MODEL_OP_NET_KEY_DELETE                        ESP_BLE_MESH_MODEL_OP_2(0x80, 0x41) /*!< 配置网络密钥删除 */
#define ESP_BLE_MESH_MODEL_OP_APP_KEY_UPDATE                        ESP_BLE_MESH_MODEL_OP_1(0x01)       /*!< 配置应用密钥更新 */
#define ESP_BLE_MESH_MODEL_OP_APP_KEY_DELETE                        ESP_BLE_MESH_MODEL_OP_2(0x80, 0x00) /*!< 配置应用密钥删除 */
#define ESP_BLE_MESH_MODEL_OP_NODE_IDENTITY_SET                     ESP_BLE_MESH_MODEL_OP_2(0x80, 0x47) /*!< 配置节点身份设置 */
#define ESP_BLE_MESH_MODEL_OP_KEY_REFRESH_PHASE_SET                 ESP_BLE_MESH_MODEL_OP_2(0x80, 0x16) /*!< 配置密钥刷新阶段设置 */
#define ESP_BLE_MESH_MODEL_OP_MODEL_PUB_VIRTUAL_ADDR_SET            ESP_BLE_MESH_MODEL_OP_2(0x80, 0x1A) /*!< 配置模型发布虚拟地址设置 */
#define ESP_BLE_MESH_MODEL_OP_MODEL_SUB_DELETE_ALL                  ESP_BLE_MESH_MODEL_OP_2(0x80, 0x1D) /*!< 配置模型订阅删除所有 */
#define ESP_BLE_MESH_MODEL_OP_MODEL_APP_UNBIND                      ESP_BLE_MESH_MODEL_OP_2(0x80, 0x3F) /*!< 配置模型应用解绑 */
#define ESP_BLE_MESH_MODEL_OP_NETWORK_TRANSMIT_SET                  ESP_BLE_MESH_MODEL_OP_2(0x80, 0x24) /*!< 配置网络传输设置 */

模型的介绍到此为止,具体的用法现在也不必深究,等到要改动代码时,再回头看怎么修改就行。
现在我们回到文章开头的那张脑图,就是这个:
在这里插入图片描述
刚才提到一个节点包含了以上的内容,那他是怎么实现的呢?
在ble_mesh_init有这么一段:

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

跳转过去是这么一段,里面一堆宏减少了代码量,但是严重降低了可读性,这里简单描述功能,有兴趣的同学可以继续跳转查看宏的实现:

static esp_ble_mesh_cfg_srv_t config_server = { //配置模型默认参数
    .relay = ESP_BLE_MESH_RELAY_ENABLED,
    .beacon = ESP_BLE_MESH_BEACON_ENABLED,
#if defined(CONFIG_BLE_MESH_FRIEND)
    .friend_state = ESP_BLE_MESH_FRIEND_ENABLED,
#else
    .friend_state = ESP_BLE_MESH_FRIEND_NOT_SUPPORTED,
#endif
#if defined(CONFIG_BLE_MESH_GATT_PROXY_SERVER)
    .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_ENABLED,
#else
    .gatt_proxy = ESP_BLE_MESH_GATT_PROXY_NOT_SUPPORTED,
#endif
    .default_ttl = 7,
    /* 3 transmissions with 20ms interval */
    .net_transmit = ESP_BLE_MESH_TRANSMIT(2, 20),
    .relay_retransmit = ESP_BLE_MESH_TRANSMIT(2, 20),
};

ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_pub_0, 2 + 3, ROLE_NODE); //设置数据Buffer
static esp_ble_mesh_gen_onoff_srv_t onoff_server_0 = {//配置模型参数
    .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP, 
    .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
};

ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_pub_1, 2 + 3, ROLE_NODE);
static esp_ble_mesh_gen_onoff_srv_t onoff_server_1 = {//配置模型参数
    .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_RSP_BY_APP,
    .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_RSP_BY_APP,
};

ESP_BLE_MESH_MODEL_PUB_DEFINE(onoff_pub_2, 2 + 3, ROLE_NODE);
static esp_ble_mesh_gen_onoff_srv_t onoff_server_2 = {//配置模型参数
    .rsp_ctrl.get_auto_rsp = ESP_BLE_MESH_SERVER_AUTO_RSP,
    .rsp_ctrl.set_auto_rsp = ESP_BLE_MESH_SERVER_RSP_BY_APP,
};

static esp_ble_mesh_model_t root_models[] = {//元素一包含两个模型分别是config_server,onoff_server
    ESP_BLE_MESH_MODEL_CFG_SRV(&config_server), 
    ESP_BLE_MESH_MODEL_GEN_ONOFF_SRV(&onoff_pub_0, &onoff_server_0),
};

static esp_ble_mesh_model_t extend_model_0[] = {
    ESP_BLE_MESH_MODEL_GEN_ONOFF_SRV(&onoff_pub_1, &onoff_server_1),
};

static esp_ble_mesh_model_t extend_model_1[] = {
    ESP_BLE_MESH_MODEL_GEN_ONOFF_SRV(&onoff_pub_2, &onoff_server_2),
};

static esp_ble_mesh_elem_t elements[] = {
    ESP_BLE_MESH_ELEMENT(0, root_models, ESP_BLE_MESH_MODEL_NONE), //元素一包含两个模型
    ESP_BLE_MESH_ELEMENT(0, extend_model_0, ESP_BLE_MESH_MODEL_NONE),//元素二包含一个模型
    ESP_BLE_MESH_ELEMENT(0, extend_model_1, ESP_BLE_MESH_MODEL_NONE),//元素三包含一个模型
};

static esp_ble_mesh_comp_t composition = {
    .cid = CID_ESP,
    .elements = elements,//元素传入指针
    .element_count = ARRAY_SIZE(elements),//元素个数
};

/* Disable OOB security for SILabs Android app */
static esp_ble_mesh_prov_t provision = {  //配网相关设置
    .uuid = dev_uuid,
#if 0
    .output_size = 4,
    .output_actions = ESP_BLE_MESH_DISPLAY_NUMBER,
    .input_actions = ESP_BLE_MESH_PUSH,
    .input_size = 4,
#else
    .output_size = 0,
    .output_actions = 0,
#endif
};

总结

这篇文章介绍了esp-idf/examples/bluetooth/esp_ble_mesh/onoff_models 例程的工作流程,阅读本文章后,希望读者已能够了解以下几项内容:

  1. Node 节点构成及修改方法
  2. 元素中模型的添加与修改
  3. 设置config_client回调事件与处理。
  4. onoff 模型状态设置与状态读取。
`esp_ble_gap_periodic_adv_set_params` 是一个 ESP32 蓝牙库中的函数,用于设置周期性广播参数。周期性广播是一种特殊的广播方式,可以在设定的时间间隔内定期发送广播包。 该函数的原型如下: ```c esp_err_t esp_ble_gap_periodic_adv_set_params(esp_ble_gap_periodic_adv_params_t *adv_params); ``` `adv_params` 是一个结构体,包含了周期性广播的参数信息。具体的结构体定义如下: ```c typedef struct { uint16_t interval_min; /*!< Minimum advertising interval */ uint16_t interval_max; /*!< Maximum advertising interval */ uint8_t adv_type; /*!< Advertising type */ uint8_t own_addr_type; /*!< Own address type */ esp_ble_addr_type_t peer_addr_type; /*!< Peer address type */ uint8_t peer_addr[BLE_BD_ADDR_LEN]; /*!< Peer Bluetooth device address */ uint8_t filter_policy; /*!< Advertising filter policy */ } esp_ble_gap_periodic_adv_params_t; ``` 其中,各个参数的含义如下: - `interval_min` 和 `interval_max`:指定周期性广播的最小和最大间隔(单位为 0.625ms)。 - `adv_type`:指定广播类型,可以是 `ESP_BLE_GAP_ADV_TYPE_ADV_IND`(可发现的非直连广播)或 `ESP_BLE_GAP_ADV_TYPE_ADV_DIRECT_IND_HIGH`(高功率直连广播)等。 - `own_addr_type`:指定本机蓝牙地址类型。 - `peer_addr_type`:指定对方蓝牙地址类型。 - `peer_addr`:对方蓝牙设备的地址。 - `filter_policy`:指定广播包过滤策略,可以是 `ESP_BLE_ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY`(允许任意扫描和任意连接)或 `ESP_BLE_ADV_FILTER_ALLOW_SCAN_WLST_CON_ANY`(允许白名单扫描和任意连接)等。 需要注意的是,该函数只能在 BLE 模式下调用,并且应在初始化蓝牙堆栈之后调用。 希望这个回答能解决你的问题,如果还有其他问题,请随时提问!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值