| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- 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
- }
- }
- },
- .proto_sec = {
- .version = PROTOCOM_SEC0,
- .custom_handle = NULL,
- .pop = 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));
- 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 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,
- .pop = 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));
- You may set security for transport in ESP local control using following options:
- 1. `PROTOCOM_SEC2`: specifies that SRP6a based key exchange and end to end encryption based on AES-GCM is used. This is the most preffered option as it adds a robust security with Augmented PAKE protocol i.e. SRP6a.
- 2. `PROTOCOM_SEC1`: specifies that Curve25519 based key exchange and end to end encryption based on AES-CTR is used.
- 3. `PROTOCOM_SEC0`: specifies that data will be exchanged as a plain text (no security).
- 4. `PROTOCOM_SEC_CUSTOM`: you can define your own security requirement. Please note that you will also have to provide `custom_handle` of type `protocomm_security_t *` in this context.
- .. note:: The respective security schemes need to be enabled through the project configuration menu. Please refer to the Enabling protocom security version section in :doc:`Protocol Communication </api-reference/provisioning/protocomm>` for more details.
- 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-file:: inc/esp_local_ctrl.inc
|