List of changes: * New component esp_local_ctrl added * Example added under examples/protocols/esp_local_ctrl * Documentation added under protocols/esp_local_ctrl * Demo client side app esp_local_ctrl.py added under examples/protocols/esp_local_ctrl/scripts * protocomm_ble : protocomm_ble_config_t given struct name for allowing forward declaration * esp_prov/transport_softap renamed to transport_http * transport_http module supports verification of server certificate * transport_http module performs name resolution before connection
206 lines
9.2 KiB
ReStructuredText
206 lines
9.2 KiB
ReStructuredText
ESP Local Control
|
|
=================
|
|
|
|
Overview
|
|
--------
|
|
ESP Local Control (**esp_local_ctrl**) component in ESP-IDF provides capability to control an ESP device over Wi-Fi + HTTPS or BLE. It provides access to application defined **properties** that are available for reading / writing via a set of configurable handlers.
|
|
|
|
Initialization of the **esp_local_ctrl** service over BLE transport is performed as follows:
|
|
|
|
.. highlight:: c
|
|
|
|
::
|
|
|
|
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
|
|
}
|
|
}
|
|
},
|
|
.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));
|
|
|
|
|
|
Similarly for HTTPS transport:
|
|
|
|
.. highlight:: c
|
|
|
|
::
|
|
|
|
/* Set the configuration */
|
|
httpd_ssl_config_t https_conf = HTTPD_SSL_CONFIG_DEFAULT();
|
|
|
|
/* Load server certificate */
|
|
extern const unsigned char cacert_pem_start[] asm("_binary_cacert_pem_start");
|
|
extern const unsigned char cacert_pem_end[] asm("_binary_cacert_pem_end");
|
|
https_conf.cacert_pem = cacert_pem_start;
|
|
https_conf.cacert_len = cacert_pem_end - cacert_pem_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
|
|
},
|
|
.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));
|
|
|
|
|
|
Creating a property
|
|
===================
|
|
|
|
Now that we know how to start the **esp_local_ctrl** service, let's add a property to it. Each property must have a unique `name` (string), a `type` (e.g. enum), `flags` (bit fields) and `size`.
|
|
|
|
The `size` is to be kept 0, if we want our property value to be of variable length (e.g. if its a string or bytestream). For fixed length property value data-types, like int, float, etc., setting the `size` field to the right value, helps **esp_local_ctrl** to perform internal checks on arguments received with write requests.
|
|
|
|
The interpretation of `type` and `flags` fields is totally upto the application, hence they may be used as enumerations, bitfields, or even simple integers. One way is to use `type` values to classify properties, while `flags` to specify characteristics of a property.
|
|
|
|
Here is an example property which is to function as a timestamp. It is assumed that the application defines `TYPE_TIMESTAMP` and `READONLY`, which are used for setting the `type` and `flags` fields here.
|
|
|
|
.. highlight:: c
|
|
|
|
::
|
|
|
|
/* 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(×tamp);
|
|
|
|
|
|
Also notice that there is a ctx field, which is set to point to some custom `func_get_time()`. This can be used inside the property get / set handlers to retrieve timestamp.
|
|
|
|
Here is an example of `get_prop_values()` handler, which is used for retrieving the timestamp.
|
|
|
|
.. highlight:: c
|
|
|
|
::
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
Here is an example of `set_prop_values()` handler. Notice how we restrict from writing to read-only properties.
|
|
|
|
.. highlight:: c
|
|
|
|
::
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
For complete example see :example:`protocols/esp_local_ctrl`
|
|
|
|
Client Side Implementation
|
|
==========================
|
|
|
|
The client side implementation will have establish a protocomm session with the device first, over the supported mode of transport, and then send and receive protobuf messages understood by the **esp_local_ctrl** service. The service will translate these messages into requests and then call the appropriate handlers (set / get). Then, the generated response for each handler is again packed into a protobuf message and transmitted back to the client.
|
|
|
|
See below the various protobuf messages understood by the **esp_local_ctrl** service:
|
|
|
|
1. `get_prop_count` : This should simply return the total number of properties supported by the service
|
|
2. `get_prop_values` : This accepts an array of indices and should return the information (name, type, flags) and values of the properties corresponding to those indices
|
|
3. `set_prop_values` : This accepts an array of indices and an array of new values, which are used for setting the values of the properties corresponding to the indices
|
|
|
|
Note that indices may or may not be the same for a property, across multiple sessions. Therefore, the client must only use the names of the properties to uniquely identify them. So, every time a new session is established, the client should first call `get_prop_count` and then `get_prop_values`, hence form an index to name mapping for all properties. Now when calling `set_prop_values` for a set of properties, it must first convert the names to indexes, using the created mapping. As emphasized earlier, the client must refresh the index to name mapping every time a new session is established with the same device.
|
|
|
|
The various protocomm endpoints provided by **esp_local_ctrl** are listed below:
|
|
|
|
.. list-table:: Endpoints provided by ESP Local Control
|
|
:widths: 10 25 50
|
|
:header-rows: 1
|
|
|
|
* - Endpoint Name (BLE + GATT Server)
|
|
- URI (HTTPS Server + mDNS)
|
|
- Description
|
|
* - esp_local_ctrl/version
|
|
- https://<mdns-hostname>.local/esp_local_ctrl/version
|
|
- Endpoint used for retrieving version string
|
|
* - esp_local_ctrl/control
|
|
- https://<mdns-hostname>.local/esp_local_ctrl/control
|
|
- Endpoint used for sending / receiving control messages
|
|
|
|
|
|
API Reference
|
|
-------------
|
|
|
|
.. include:: /_build/inc/esp_local_ctrl.inc
|