ESP 本地控制

[English]

概述

通过 ESP-IDF 的 ESP 本地控制 (esp_local_ctrl) 组件,可使用 HTTPS 或 BLE 协议控制 ESP 设备。通过一系列可配置的处理程序,该组件允许你对应用程序定义的读/写 属性 进行访问。

通过 BLE 传输协议初始化 esp_local_ctrl 的过程如下:

esp_local_ctrl_config_t config = {
    .transport = ESP_LOCAL_CTRL_TRANSPORT_BLE,
    .transport_config = {
        .ble = & (protocomm_ble_config_t) {
            .device_name  = SERVICE_NAME,
            .service_uuid = {
                /* LSB <---------------------------------------
                * ---------------------------------------> MSB */
                0x21, 0xd5, 0x3b, 0x8d, 0xbd, 0x75, 0x68, 0x8a,
                0xb4, 0x42, 0xeb, 0x31, 0x4a, 0x1e, 0x98, 0x3d
            }
        }
    },
    .proto_sec = {
        .version = PROTOCOM_SEC0,
        .custom_handle = NULL,
        .sec_params = NULL,
    },
    .handlers = {
        /* User defined handler functions */
        .get_prop_values = get_property_values,
        .set_prop_values = set_property_values,
        .usr_ctx         = NULL,
        .usr_ctx_free_fn = NULL
    },
    /* Maximum number of properties that may be set */
    .max_properties = 10
};

/* Start esp_local_ctrl service */
ESP_ERROR_CHECK(esp_local_ctrl_start(&config));

同样,对于 HTTP 传输:

/* Set the configuration */
httpd_ssl_config_t https_conf = HTTPD_SSL_CONFIG_DEFAULT();

/* Load server certificate */
extern const unsigned char servercert_start[] asm("_binary_servercert_pem_start");
extern const unsigned char servercert_end[]   asm("_binary_servercert_pem_end");
https_conf.servercert = servercert_start;
https_conf.servercert_len = servercert_end - servercert_start;

/* Load server private key */
extern const unsigned char prvtkey_pem_start[] asm("_binary_prvtkey_pem_start");
extern const unsigned char prvtkey_pem_end[]   asm("_binary_prvtkey_pem_end");
https_conf.prvtkey_pem = prvtkey_pem_start;
https_conf.prvtkey_len = prvtkey_pem_end - prvtkey_pem_start;

esp_local_ctrl_config_t config = {
    .transport = ESP_LOCAL_CTRL_TRANSPORT_HTTPD,
    .transport_config = {
        .httpd = &https_conf
    },
    .proto_sec = {
        .version = PROTOCOM_SEC0,
        .custom_handle = NULL,
        .sec_params = NULL,
    },
    .handlers = {
        /* User defined handler functions */
        .get_prop_values = get_property_values,
        .set_prop_values = set_property_values,
        .usr_ctx         = NULL,
        .usr_ctx_free_fn = NULL
    },
    /* Maximum number of properties that may be set */
    .max_properties = 10
};

/* Start esp_local_ctrl service */
ESP_ERROR_CHECK(esp_local_ctrl_start(&config));

你可以利用以下选项设置 ESP 本地控制的传输安全性:

  1. PROTOCOM_SEC2: 指定使用基于 SRP6a 的密钥交换和基于 AES-GCM 的端到端加密。这一选项通过增强的 PAKE 协议(即 SRP6a)提供了强大的安全保障,是最受欢迎的选项。

  2. PROTOCOM_SEC1: 指定使用基于 Curve25519 的密钥交换和基于 AES-CTR 的端到端加密。

  3. PROTOCOM_SEC0: 指定以明文(无安全性)方式交换数据。

  4. PROTOCOM_SEC_CUSTOM: 自定义安全需求。注意,使用这一选项时,必须提供 protocomm_security_t * 类型的 custom_handle

备注

相应的安全方案需通过项目配置菜单启用。要了解详情,请参考 Protocol Communication 中关于启用 protocomm 安全版本的章节。

创建属性

启用 esp_local_ctrl 后,可以为其添加属性。每个属性必须具有唯一的名称 name (字符串),还需具有类型 type (如枚举)、标记 flags (位域)和大小 size

如果希望属性值的长度可变(例如,字符串或字节流),则 size 值应保持为 0。对于有固定长度属性值的数据类型,如整型、浮点型等,将 size 字段设置为正确的值,有助于 esp_local_ctrl 对接收到的写入请求的参数进行内部检查。

typeflags 字段的含义取决于具体的应用程序,它们可以是枚举、位域、甚至整型。可以使用 type 表示属性,用 flags 指定属性的特征。

例如,以下属性被用作时间戳。此处假设应用程序定义了 TYPE_TIMESTAMPREADONLY 来设置此处的 typeflags 字段:

/* Create a timestamp property */
esp_local_ctrl_prop_t timestamp = {
    .name        = "timestamp",
    .type        = TYPE_TIMESTAMP,
    .size        = sizeof(int32_t),
    .flags       = READONLY,
    .ctx         = func_get_time,
    .ctx_free_fn = NULL
};

/* Now register the property */
esp_local_ctrl_add_property(&timestamp);

另外,此示例中还设置了一个 ctx 字段,指向自定义的 func_get_time(),用于在属性的 get/set 处理程序中检索时间戳。

以下为 get_prop_values() 处理程序的一个示例,用于检索时间戳:

static esp_err_t get_property_values(size_t props_count,
                                     const esp_local_ctrl_prop_t *props,
                                     esp_local_ctrl_prop_val_t *prop_values,
                                     void *usr_ctx)
{
    for (uint32_t i = 0; i < props_count; i++) {
        ESP_LOGI(TAG, "Reading %s", props[i].name);
        if (props[i].type == TYPE_TIMESTAMP) {
            /* Obtain the timer function from ctx */
            int32_t (*func_get_time)(void) = props[i].ctx;

            /* Use static variable for saving the value. This is essential because the value has to be valid even after this function returns. Alternative is to use dynamic allocation and set the free_fn field */
            static int32_t ts = func_get_time();
            prop_values[i].data = &ts;
        }
    }
    return ESP_OK;
}

以下为 set_prop_values() 应用程序的一个示例,注意此示例是如何为只读属性限制写入操作:

static esp_err_t set_property_values(size_t props_count,
                                     const esp_local_ctrl_prop_t *props,
                                     const esp_local_ctrl_prop_val_t *prop_values,
                                     void *usr_ctx)
{
    for (uint32_t i = 0; i < props_count; i++) {
        if (props[i].flags & READONLY) {
            ESP_LOGE(TAG, "Cannot write to read-only property %s", props[i].name);
            return ESP_ERR_INVALID_ARG;
        } else {
            ESP_LOGI(TAG, "Setting %s", props[i].name);

            /* For keeping it simple, lets only log the incoming data */
            ESP_LOG_BUFFER_HEX_LEVEL(TAG, prop_values[i].data,
                                     prop_values[i].size, ESP_LOG_INFO);
        }
    }
    return ESP_OK;
}

完整示例请参见 protocols/esp_local_ctrl

客户端实现

在客户端的实现过程中,首先,通过支持的传输模式与设备建立 protocomm 会话,然后发送并接收 esp_local_ctrl 服务能够处理的 protobuf 信息。 esp_local_ctrl 服务会将这些信息转换为请求,并发起相应的处理程序 (set/get)。接着,为每个处理程序生成的响应会被再次打包到一条 protobuf 信息中,传输回客户端。

以下是 esp_local_ctrl 服务能够处理的各种 protobuf 信息:

  1. get_prop_count : 返回服务支持的属性总数。

  2. get_prop_values : 接受一个索引数组,并返回这些索引相对应的属性信息(名称、类型、标志)和属性值。

  3. set_prop_values : 接受一个索引数组和一个新值数组,用于设置索引对应的属性值。

注意,在多个会话中,一个属性的索引可能相同,也可能不同。因此,客户端必须用唯一的属性名称来识别属性。每次建立新会话时,客户端都应首先调用 get_prop_count,然后调用 get_prop_values,为所有属性建立从索引到名称的映射。为一组属性调用 set_prop_values 时,必须先用创建的映射将名称转换为索引。如前所述,每次使用同一设备建立新会话时,客户端必须刷新该映射。

下面列出了 esp_local_ctrl 服务提供的各种 protocomm 端点:

ESP本地控制服务提供的端点

端点名称 (BLE + GATT 服务器)

URI (HTTPS 服务器 + mDNS)

描述

esp_local_ctrl/version

https://<mdns-hostname>.local/esp_local_ctrl/version

检索版本字符串

esp_local_ctrl/control

https://<mdns-hostname>.local/esp_local_ctrl/control

发送或接收控制信息

API 参考

Header File

  • components/esp_local_ctrl/include/esp_local_ctrl.h

  • This header file can be included with:

    #include "esp_local_ctrl.h"
    
  • This header file is a part of the API provided by the esp_local_ctrl component. To declare that your component depends on esp_local_ctrl, add the following to your CMakeLists.txt:

    REQUIRES esp_local_ctrl
    

    or

    PRIV_REQUIRES esp_local_ctrl
    

Functions

const esp_local_ctrl_transport_t *esp_local_ctrl_get_transport_ble(void)

Function for obtaining BLE transport mode.

const esp_local_ctrl_transport_t *esp_local_ctrl_get_transport_httpd(void)

Function for obtaining HTTPD transport mode.

esp_err_t esp_local_ctrl_start(const esp_local_ctrl_config_t *config)

Start local control service.

参数

config -- [in] Pointer to configuration structure

返回

  • ESP_OK : Success

  • ESP_FAIL : Failure

esp_err_t esp_local_ctrl_stop(void)

Stop local control service.

esp_err_t esp_local_ctrl_add_property(const esp_local_ctrl_prop_t *prop)

Add a new property.

This adds a new property and allocates internal resources for it. The total number of properties that could be added is limited by configuration option max_properties

参数

prop -- [in] Property description structure

返回

  • ESP_OK : Success

  • ESP_FAIL : Failure

esp_err_t esp_local_ctrl_remove_property(const char *name)

Remove a property.

This finds a property by name, and releases the internal resources which are associated with it.

参数

name -- [in] Name of the property to remove

返回

  • ESP_OK : Success

  • ESP_ERR_NOT_FOUND : Failure

const esp_local_ctrl_prop_t *esp_local_ctrl_get_property(const char *name)

Get property description structure by name.

This API may be used to get a property's context structure esp_local_ctrl_prop_t when its name is known

参数

name -- [in] Name of the property to find

返回

  • Pointer to property

  • NULL if not found

esp_err_t esp_local_ctrl_set_handler(const char *ep_name, protocomm_req_handler_t handler, void *user_ctx)

Register protocomm handler for a custom endpoint.

This API can be called by the application to register a protocomm handler for an endpoint after the local control service has started.

备注

In case of BLE transport the names and uuids of all custom endpoints must be provided beforehand as a part of the protocomm_ble_config_t structure set in esp_local_ctrl_config_t, and passed to esp_local_ctrl_start().

参数
  • ep_name -- [in] Name of the endpoint

  • handler -- [in] Endpoint handler function

  • user_ctx -- [in] User data

返回

  • ESP_OK : Success

  • ESP_FAIL : Failure

Unions

union esp_local_ctrl_transport_config_t
#include <esp_local_ctrl.h>

Transport mode (BLE / HTTPD) configuration.

Public Members

esp_local_ctrl_transport_config_ble_t *ble

This is same as protocomm_ble_config_t. See protocomm_ble.h for available configuration parameters.

esp_local_ctrl_transport_config_httpd_t *httpd

This is same as httpd_ssl_config_t. See esp_https_server.h for available configuration parameters.

Structures

struct esp_local_ctrl_prop

Property description data structure, which is to be populated and passed to the esp_local_ctrl_add_property() function.

Once a property is added, its structure is available for read-only access inside get_prop_values() and set_prop_values() handlers.

Public Members

char *name

Unique name of property

uint32_t type

Type of property. This may be set to application defined enums

size_t size

Size of the property value, which:

  • if zero, the property can have values of variable size

  • if non-zero, the property can have values of fixed size only, therefore, checks are performed internally by esp_local_ctrl when setting the value of such a property

uint32_t flags

Flags set for this property. This could be a bit field. A flag may indicate property behavior, e.g. read-only / constant

void *ctx

Pointer to some context data relevant for this property. This will be available for use inside the get_prop_values and set_prop_values handlers as a part of this property structure. When set, this is valid throughout the lifetime of a property, till either the property is removed or the esp_local_ctrl service is stopped.

void (*ctx_free_fn)(void *ctx)

Function used by esp_local_ctrl to internally free the property context when esp_local_ctrl_remove_property() or esp_local_ctrl_stop() is called.

struct esp_local_ctrl_prop_val

Property value data structure. This gets passed to the get_prop_values() and set_prop_values() handlers for the purpose of retrieving or setting the present value of a property.

Public Members

void *data

Pointer to memory holding property value

size_t size

Size of property value

void (*free_fn)(void *data)

This may be set by the application in get_prop_values() handler to tell esp_local_ctrl to call this function on the data pointer above, for freeing its resources after sending the get_prop_values response.

struct esp_local_ctrl_handlers

Handlers for receiving and responding to local control commands for getting and setting properties.

Public Members

esp_err_t (*get_prop_values)(size_t props_count, const esp_local_ctrl_prop_t props[], esp_local_ctrl_prop_val_t prop_values[], void *usr_ctx)

Handler function to be implemented for retrieving current values of properties.

备注

If any of the properties have fixed sizes, the size field of corresponding element in prop_values need to be set

Param props_count

[in] Total elements in the props array

Param props

[in] Array of properties, the current values for which have been requested by the client

Param prop_values

[out] Array of empty property values, the elements of which need to be populated with the current values of those properties specified by props argument

Param usr_ctx

[in] This provides value of the usr_ctx field of esp_local_ctrl_handlers_t structure

Return

Returning different error codes will convey the corresponding protocol level errors to the client :

  • ESP_OK : Success

  • ESP_ERR_INVALID_ARG : InvalidArgument

  • ESP_ERR_INVALID_STATE : InvalidProto

  • All other error codes : InternalError

esp_err_t (*set_prop_values)(size_t props_count, const esp_local_ctrl_prop_t props[], const esp_local_ctrl_prop_val_t prop_values[], void *usr_ctx)

Handler function to be implemented for changing values of properties.

备注

If any of the properties have variable sizes, the size field of the corresponding element in prop_values must be checked explicitly before making any assumptions on the size.

Param props_count

[in] Total elements in the props array

Param props

[in] Array of properties, the values for which the client requests to change

Param prop_values

[in] Array of property values, the elements of which need to be used for updating those properties specified by props argument

Param usr_ctx

[in] This provides value of the usr_ctx field of esp_local_ctrl_handlers_t structure

Return

Returning different error codes will convey the corresponding protocol level errors to the client :

  • ESP_OK : Success

  • ESP_ERR_INVALID_ARG : InvalidArgument

  • ESP_ERR_INVALID_STATE : InvalidProto

  • All other error codes : InternalError

void *usr_ctx

Context pointer to be passed to above handler functions upon invocation. This is different from the property level context, as this is valid throughout the lifetime of the esp_local_ctrl service, and freed only when the service is stopped.

void (*usr_ctx_free_fn)(void *usr_ctx)

Pointer to function which will be internally invoked on usr_ctx for freeing the context resources when esp_local_ctrl_stop() is called.

struct esp_local_ctrl_proto_sec_cfg

Protocom security configs

Public Members

esp_local_ctrl_proto_sec_t version

This sets protocom security version, sec0/sec1 or custom If custom, user must provide handle via proto_sec_custom_handle below

void *custom_handle

Custom security handle if security is set custom via proto_sec above This handle must follow protocomm_security_t signature

const void *pop

Proof of possession to be used for local control. Could be NULL.

const void *sec_params

Pointer to security params (NULL if not needed). This is not needed for protocomm security 0 This pointer should hold the struct of type esp_local_ctrl_security1_params_t for protocomm security 1 and esp_local_ctrl_security2_params_t for protocomm security 2 respectively. Could be NULL.

struct esp_local_ctrl_config

Configuration structure to pass to esp_local_ctrl_start()

Public Members

const esp_local_ctrl_transport_t *transport

Transport layer over which service will be provided

esp_local_ctrl_transport_config_t transport_config

Transport layer over which service will be provided

esp_local_ctrl_proto_sec_cfg_t proto_sec

Security version and POP

esp_local_ctrl_handlers_t handlers

Register handlers for responding to get/set requests on properties

size_t max_properties

This limits the number of properties that are available at a time

Macros

ESP_LOCAL_CTRL_TRANSPORT_BLE
ESP_LOCAL_CTRL_TRANSPORT_HTTPD

Type Definitions

typedef struct esp_local_ctrl_prop esp_local_ctrl_prop_t

Property description data structure, which is to be populated and passed to the esp_local_ctrl_add_property() function.

Once a property is added, its structure is available for read-only access inside get_prop_values() and set_prop_values() handlers.

typedef struct esp_local_ctrl_prop_val esp_local_ctrl_prop_val_t

Property value data structure. This gets passed to the get_prop_values() and set_prop_values() handlers for the purpose of retrieving or setting the present value of a property.

typedef struct esp_local_ctrl_handlers esp_local_ctrl_handlers_t

Handlers for receiving and responding to local control commands for getting and setting properties.

typedef struct esp_local_ctrl_transport esp_local_ctrl_transport_t

Transport mode (BLE / HTTPD) over which the service will be provided.

This is forward declaration of a private structure, implemented internally by esp_local_ctrl.

typedef struct protocomm_ble_config esp_local_ctrl_transport_config_ble_t

Configuration for transport mode BLE.

This is a forward declaration for protocomm_ble_config_t. To use this, application must set CONFIG_BT_BLUEDROID_ENABLED and include protocomm_ble.h.

typedef struct httpd_config esp_local_ctrl_transport_config_httpd_t

Configuration for transport mode HTTPD.

This is a forward declaration for httpd_ssl_config_t (for HTTPS) or httpd_config_t (for HTTP)

typedef enum esp_local_ctrl_proto_sec esp_local_ctrl_proto_sec_t

Security types for esp_local_control.

typedef protocomm_security1_params_t esp_local_ctrl_security1_params_t
typedef protocomm_security2_params_t esp_local_ctrl_security2_params_t
typedef struct esp_local_ctrl_proto_sec_cfg esp_local_ctrl_proto_sec_cfg_t

Protocom security configs

typedef struct esp_local_ctrl_config esp_local_ctrl_config_t

Configuration structure to pass to esp_local_ctrl_start()

Enumerations

enum esp_local_ctrl_proto_sec

Security types for esp_local_control.

Values:

enumerator PROTOCOM_SEC0
enumerator PROTOCOM_SEC1
enumerator PROTOCOM_SEC2
enumerator PROTOCOM_SEC_CUSTOM