Преглед изворни кода

Merge branch 'feature/local_ctrl_no_sec' into 'master'

ESP Local Ctrl Feature Added

See merge request idf/esp-idf!5348
Mahavir Jain пре 6 година
родитељ
комит
28f1cdf5ed
39 измењених фајлова са 4700 додато и 6 уклоњено
  1. 1 0
      .flake8
  2. 23 0
      components/esp_local_ctrl/CMakeLists.txt
  3. 11 0
      components/esp_local_ctrl/component.mk
  4. 339 0
      components/esp_local_ctrl/include/esp_local_ctrl.h
  5. 942 0
      components/esp_local_ctrl/proto-c/esp_local_ctrl.pb-c.c
  6. 383 0
      components/esp_local_ctrl/proto-c/esp_local_ctrl.pb-c.h
  7. 30 0
      components/esp_local_ctrl/proto/CMakeLists.txt
  8. 25 0
      components/esp_local_ctrl/proto/README.md
  9. 62 0
      components/esp_local_ctrl/proto/esp_local_ctrl.proto
  10. 7 0
      components/esp_local_ctrl/proto/makefile
  11. 549 0
      components/esp_local_ctrl/python/esp_local_ctrl_pb2.py
  12. 417 0
      components/esp_local_ctrl/src/esp_local_ctrl.c
  13. 298 0
      components/esp_local_ctrl/src/esp_local_ctrl_handler.c
  14. 153 0
      components/esp_local_ctrl/src/esp_local_ctrl_priv.h
  15. 140 0
      components/esp_local_ctrl/src/esp_local_ctrl_transport_ble.c
  16. 128 0
      components/esp_local_ctrl/src/esp_local_ctrl_transport_httpd.c
  17. 1 1
      components/protocomm/include/transports/protocomm_ble.h
  18. 3 0
      docs/Doxyfile
  19. 206 0
      docs/en/api-reference/protocols/esp_local_ctrl.rst
  20. 1 0
      docs/en/api-reference/protocols/index.rst
  21. 1 0
      docs/zh_CN/api-reference/protocols/esp_local_ctrl.rst
  22. 1 0
      docs/zh_CN/api-reference/protocols/index.rst
  23. 10 0
      examples/protocols/esp_local_ctrl/CMakeLists.txt
  24. 9 0
      examples/protocols/esp_local_ctrl/Makefile
  25. 93 0
      examples/protocols/esp_local_ctrl/README.md
  26. 8 0
      examples/protocols/esp_local_ctrl/main/CMakeLists.txt
  27. 20 0
      examples/protocols/esp_local_ctrl/main/Kconfig.projbuild
  28. 111 0
      examples/protocols/esp_local_ctrl/main/app_main.c
  29. 17 0
      examples/protocols/esp_local_ctrl/main/certs/cacert.pem
  30. 28 0
      examples/protocols/esp_local_ctrl/main/certs/prvtkey.pem
  31. 19 0
      examples/protocols/esp_local_ctrl/main/certs/rootCA.pem
  32. 7 0
      examples/protocols/esp_local_ctrl/main/component.mk
  33. 274 0
      examples/protocols/esp_local_ctrl/main/esp_local_ctrl_service.c
  34. 273 0
      examples/protocols/esp_local_ctrl/scripts/esp_local_ctrl.py
  35. 93 0
      examples/protocols/esp_local_ctrl/scripts/proto.py
  36. 1 0
      examples/protocols/esp_local_ctrl/sdkconfig.defaults
  37. 1 1
      tools/esp_prov/esp_prov.py
  38. 1 1
      tools/esp_prov/transport/__init__.py
  39. 14 3
      tools/esp_prov/transport/transport_http.py

+ 1 - 0
.flake8

@@ -160,4 +160,5 @@ exclude =
         components/wifi_provisioning/python/wifi_scan_pb2.py,
         components/wifi_provisioning/python/wifi_config_pb2.py,
         components/wifi_provisioning/python/wifi_constants_pb2.py,
+        components/esp_local_ctrl/python/esp_local_ctrl_pb2.py,
         examples/provisioning/custom_config/components/custom_provisioning/python/custom_config_pb2.py,

+ 23 - 0
components/esp_local_ctrl/CMakeLists.txt

@@ -0,0 +1,23 @@
+set(include_dirs include)
+set(priv_include_dirs proto-c src ../protocomm/proto-c)
+set(srcs  "src/esp_local_ctrl.c"
+          "src/esp_local_ctrl_handler.c"
+          "proto-c/esp_local_ctrl.pb-c.c")
+
+if(CONFIG_ESP_HTTPS_SERVER_ENABLE)
+    list(APPEND srcs
+         "src/esp_local_ctrl_transport_httpd.c")
+endif()
+
+if(CONFIG_BT_ENABLED)
+    if(CONFIG_BT_BLUEDROID_ENABLED)
+        list(APPEND srcs
+             "src/esp_local_ctrl_transport_ble.c")
+    endif()
+endif()
+
+idf_component_register(SRCS "${srcs}"
+                    INCLUDE_DIRS "${include_dirs}"
+                    PRIV_INCLUDE_DIRS "${priv_include_dirs}"
+                    REQUIRES protocomm esp_https_server
+                    PRIV_REQUIRES protobuf-c mdns)

+ 11 - 0
components/esp_local_ctrl/component.mk

@@ -0,0 +1,11 @@
+COMPONENT_SRCDIRS := src proto-c
+COMPONENT_ADD_INCLUDEDIRS := include
+COMPONENT_PRIV_INCLUDEDIRS := src proto-c ../protocomm/proto-c/
+
+ifndef CONFIG_BT_BLUEDROID_ENABLED
+	COMPONENT_OBJEXCLUDE += src/esp_local_ctrl_transport_ble.o
+endif
+
+ifndef CONFIG_ESP_HTTPS_SERVER_ENABLE
+	COMPONENT_OBJEXCLUDE += src/esp_local_ctrl_transport_httpd.o
+endif

+ 339 - 0
components/esp_local_ctrl/include/esp_local_ctrl.h

@@ -0,0 +1,339 @@
+// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <protocomm.h>
+
+/**
+ * @brief   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 {
+    /**
+     * Unique name of property
+     */
+    char *name;
+
+    /**
+     * Type of property. This may be set to application defined enums
+     */
+    uint32_t type;
+
+    /**
+     * 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
+     */
+    size_t size;
+
+    /**
+     * Flags set for this property. This could be a bit field.
+     * A flag may indicate property behavior, e.g. read-only / constant
+     */
+    uint32_t flags;
+
+    /**
+     * 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;
+
+    /**
+     * 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.
+     */
+    void (*ctx_free_fn)(void *ctx);
+} esp_local_ctrl_prop_t;
+
+/**
+ * @brief   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_prop_val {
+    /**
+     * Pointer to memory holding property value
+     */
+    void *data;
+
+    /**
+     * Size of property value
+     */
+    size_t size;
+
+    /**
+     * 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.
+     */
+    void (*free_fn)(void *data);
+} esp_local_ctrl_prop_val_t;
+
+/**
+ * @brief   Handlers for receiving and responding to local
+ *          control commands for getting and setting properties.
+ */
+typedef struct esp_local_ctrl_handlers {
+    /**
+     * @brief Handler function to be implemented for retrieving current
+     *        values of properties
+     *
+     * @note  If any of the properties have fixed sizes, the size field of
+     * corresponding element in `prop_values` need to be set
+     *
+     * @param[in]  props_count  Total elements in the props array
+     * @param[in]  props        Array of properties, the current values for which
+     *                          have been requested by the client
+     * @param[out] prop_values  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[in]  usr_ctx      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 (*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);
+
+    /**
+     * @brief Handler function to be implemented for changing values of properties
+     *
+     * @note  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[in]  props_count  Total elements in the props array
+     * @param[in]  props        Array of properties, the values for which the
+     *                          client requests to change
+     * @param[in]  prop_values  Array of property values, the elements of which
+     *                          need to be used for updating those properties
+     *                          specified by props argument
+     * @param[in]  usr_ctx      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);
+
+    /**
+     * 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;
+
+    /**
+     * Pointer to function which will be internally invoked on `usr_ctx` for
+     * freeing the context resources when `esp_local_ctrl_stop()` is called.
+     */
+    void (*usr_ctx_free_fn)(void *usr_ctx);
+} esp_local_ctrl_handlers_t;
+
+/**
+ * @brief   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 esp_local_ctrl_transport esp_local_ctrl_transport_t;
+
+/**
+ * @brief   Function for obtaining BLE transport mode
+ */
+const esp_local_ctrl_transport_t *esp_local_ctrl_get_transport_ble();
+
+/**
+ * @brief   Function for obtaining HTTPD transport mode
+ */
+const esp_local_ctrl_transport_t *esp_local_ctrl_get_transport_httpd();
+
+#define ESP_LOCAL_CTRL_TRANSPORT_BLE   esp_local_ctrl_get_transport_ble()
+#define ESP_LOCAL_CTRL_TRANSPORT_HTTPD esp_local_ctrl_get_transport_httpd()
+
+/**
+ * @brief   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 protocomm_ble_config esp_local_ctrl_transport_config_ble_t;
+
+/**
+ * @brief   Configuration for transport mode HTTPD
+ *
+ * This is a forward declaration for `httpd_ssl_config_t`.
+ * To use this, application must set CONFIG_ESP_HTTPS_SERVER_ENABLE
+ * and include `esp_https_server.h`
+ */
+typedef struct httpd_ssl_config esp_local_ctrl_transport_config_httpd_t;
+
+/**
+ * @brief   Transport mode (BLE / HTTPD) configuration
+ */
+typedef union {
+    /**
+     * This is same as `protocomm_ble_config_t`. See `protocomm_ble.h` for
+     * available configuration parameters.
+     */
+    esp_local_ctrl_transport_config_ble_t *ble;
+
+    /**
+     * This is same as `httpd_ssl_config_t`. See `esp_https_server.h` for
+     * available configuration parameters.
+     */
+    esp_local_ctrl_transport_config_httpd_t *httpd;
+} esp_local_ctrl_transport_config_t;
+
+/**
+ * @brief   Configuration structure to pass to `esp_local_ctrl_start()`
+ */
+typedef struct esp_local_ctrl_config {
+    /**
+     * Transport layer over which service will be provided
+     */
+    const esp_local_ctrl_transport_t *transport;
+
+    /**
+     * Transport layer over which service will be provided
+     */
+    esp_local_ctrl_transport_config_t transport_config;
+
+    /**
+     * Register handlers for responding to get/set requests on properties
+     */
+    esp_local_ctrl_handlers_t handlers;
+
+    /**
+     * This limits the number of properties that are available at a time
+     */
+    size_t max_properties;
+} esp_local_ctrl_config_t;
+
+/**
+ * @brief   Start local control service
+ *
+ * @param[in] config    Pointer to configuration structure
+ *
+ * @return
+ *  - ESP_OK      : Success
+ *  - ESP_FAIL    : Failure
+ */
+esp_err_t esp_local_ctrl_start(const esp_local_ctrl_config_t *config);
+
+/**
+ * @brief   Stop local control service
+ */
+esp_err_t esp_local_ctrl_stop(void);
+
+/**
+ * @brief   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`
+ *
+ * @param[in] prop    Property description structure
+ *
+ * @return
+ *  - ESP_OK      : Success
+ *  - ESP_FAIL    : Failure
+ */
+esp_err_t esp_local_ctrl_add_property(const esp_local_ctrl_prop_t *prop);
+
+/**
+ * @brief   Remove a property
+ *
+ * This finds a property by name, and releases the internal resources
+ * which are associated with it.
+ *
+ * @param[in] name    Name of the property to remove
+ *
+ * @return
+ *  - ESP_OK      : Success
+ *  - ESP_ERR_NOT_FOUND : Failure
+ */
+esp_err_t esp_local_ctrl_remove_property(const char *name);
+
+/**
+ * @brief   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
+ *
+ * @param[in] name    Name of the property to find
+ *
+ * @return
+ *  - Pointer to property
+ *  - NULL if not found
+ */
+const esp_local_ctrl_prop_t *esp_local_ctrl_get_property(const char *name);
+
+/**
+ * @brief   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.
+ *
+ * @note 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()`.
+ *
+ * @param[in] ep_name   Name of the endpoint
+ * @param[in] handler   Endpoint handler function
+ * @param[in] user_ctx  User data
+ *
+ * @return
+ *  - ESP_OK      : Success
+ *  - ESP_FAIL    : Failure
+ */
+esp_err_t esp_local_ctrl_set_handler(const char *ep_name,
+                                     protocomm_req_handler_t handler,
+                                     void *user_ctx);
+
+#ifdef __cplusplus
+}
+#endif

+ 942 - 0
components/esp_local_ctrl/proto-c/esp_local_ctrl.pb-c.c

@@ -0,0 +1,942 @@
+/* Generated by the protocol buffer compiler.  DO NOT EDIT! */
+/* Generated from: esp_local_ctrl.proto */
+
+/* Do not generate deprecated warnings for self */
+#ifndef PROTOBUF_C__NO_DEPRECATED
+#define PROTOBUF_C__NO_DEPRECATED
+#endif
+
+#include "esp_local_ctrl.pb-c.h"
+void   cmd_get_property_count__init
+                     (CmdGetPropertyCount         *message)
+{
+  static const CmdGetPropertyCount init_value = CMD_GET_PROPERTY_COUNT__INIT;
+  *message = init_value;
+}
+size_t cmd_get_property_count__get_packed_size
+                     (const CmdGetPropertyCount *message)
+{
+  assert(message->base.descriptor == &cmd_get_property_count__descriptor);
+  return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t cmd_get_property_count__pack
+                     (const CmdGetPropertyCount *message,
+                      uint8_t       *out)
+{
+  assert(message->base.descriptor == &cmd_get_property_count__descriptor);
+  return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t cmd_get_property_count__pack_to_buffer
+                     (const CmdGetPropertyCount *message,
+                      ProtobufCBuffer *buffer)
+{
+  assert(message->base.descriptor == &cmd_get_property_count__descriptor);
+  return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+CmdGetPropertyCount *
+       cmd_get_property_count__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data)
+{
+  return (CmdGetPropertyCount *)
+     protobuf_c_message_unpack (&cmd_get_property_count__descriptor,
+                                allocator, len, data);
+}
+void   cmd_get_property_count__free_unpacked
+                     (CmdGetPropertyCount *message,
+                      ProtobufCAllocator *allocator)
+{
+  if(!message)
+    return;
+  assert(message->base.descriptor == &cmd_get_property_count__descriptor);
+  protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void   resp_get_property_count__init
+                     (RespGetPropertyCount         *message)
+{
+  static const RespGetPropertyCount init_value = RESP_GET_PROPERTY_COUNT__INIT;
+  *message = init_value;
+}
+size_t resp_get_property_count__get_packed_size
+                     (const RespGetPropertyCount *message)
+{
+  assert(message->base.descriptor == &resp_get_property_count__descriptor);
+  return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t resp_get_property_count__pack
+                     (const RespGetPropertyCount *message,
+                      uint8_t       *out)
+{
+  assert(message->base.descriptor == &resp_get_property_count__descriptor);
+  return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t resp_get_property_count__pack_to_buffer
+                     (const RespGetPropertyCount *message,
+                      ProtobufCBuffer *buffer)
+{
+  assert(message->base.descriptor == &resp_get_property_count__descriptor);
+  return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+RespGetPropertyCount *
+       resp_get_property_count__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data)
+{
+  return (RespGetPropertyCount *)
+     protobuf_c_message_unpack (&resp_get_property_count__descriptor,
+                                allocator, len, data);
+}
+void   resp_get_property_count__free_unpacked
+                     (RespGetPropertyCount *message,
+                      ProtobufCAllocator *allocator)
+{
+  if(!message)
+    return;
+  assert(message->base.descriptor == &resp_get_property_count__descriptor);
+  protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void   property_info__init
+                     (PropertyInfo         *message)
+{
+  static const PropertyInfo init_value = PROPERTY_INFO__INIT;
+  *message = init_value;
+}
+size_t property_info__get_packed_size
+                     (const PropertyInfo *message)
+{
+  assert(message->base.descriptor == &property_info__descriptor);
+  return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t property_info__pack
+                     (const PropertyInfo *message,
+                      uint8_t       *out)
+{
+  assert(message->base.descriptor == &property_info__descriptor);
+  return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t property_info__pack_to_buffer
+                     (const PropertyInfo *message,
+                      ProtobufCBuffer *buffer)
+{
+  assert(message->base.descriptor == &property_info__descriptor);
+  return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+PropertyInfo *
+       property_info__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data)
+{
+  return (PropertyInfo *)
+     protobuf_c_message_unpack (&property_info__descriptor,
+                                allocator, len, data);
+}
+void   property_info__free_unpacked
+                     (PropertyInfo *message,
+                      ProtobufCAllocator *allocator)
+{
+  if(!message)
+    return;
+  assert(message->base.descriptor == &property_info__descriptor);
+  protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void   cmd_get_property_values__init
+                     (CmdGetPropertyValues         *message)
+{
+  static const CmdGetPropertyValues init_value = CMD_GET_PROPERTY_VALUES__INIT;
+  *message = init_value;
+}
+size_t cmd_get_property_values__get_packed_size
+                     (const CmdGetPropertyValues *message)
+{
+  assert(message->base.descriptor == &cmd_get_property_values__descriptor);
+  return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t cmd_get_property_values__pack
+                     (const CmdGetPropertyValues *message,
+                      uint8_t       *out)
+{
+  assert(message->base.descriptor == &cmd_get_property_values__descriptor);
+  return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t cmd_get_property_values__pack_to_buffer
+                     (const CmdGetPropertyValues *message,
+                      ProtobufCBuffer *buffer)
+{
+  assert(message->base.descriptor == &cmd_get_property_values__descriptor);
+  return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+CmdGetPropertyValues *
+       cmd_get_property_values__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data)
+{
+  return (CmdGetPropertyValues *)
+     protobuf_c_message_unpack (&cmd_get_property_values__descriptor,
+                                allocator, len, data);
+}
+void   cmd_get_property_values__free_unpacked
+                     (CmdGetPropertyValues *message,
+                      ProtobufCAllocator *allocator)
+{
+  if(!message)
+    return;
+  assert(message->base.descriptor == &cmd_get_property_values__descriptor);
+  protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void   resp_get_property_values__init
+                     (RespGetPropertyValues         *message)
+{
+  static const RespGetPropertyValues init_value = RESP_GET_PROPERTY_VALUES__INIT;
+  *message = init_value;
+}
+size_t resp_get_property_values__get_packed_size
+                     (const RespGetPropertyValues *message)
+{
+  assert(message->base.descriptor == &resp_get_property_values__descriptor);
+  return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t resp_get_property_values__pack
+                     (const RespGetPropertyValues *message,
+                      uint8_t       *out)
+{
+  assert(message->base.descriptor == &resp_get_property_values__descriptor);
+  return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t resp_get_property_values__pack_to_buffer
+                     (const RespGetPropertyValues *message,
+                      ProtobufCBuffer *buffer)
+{
+  assert(message->base.descriptor == &resp_get_property_values__descriptor);
+  return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+RespGetPropertyValues *
+       resp_get_property_values__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data)
+{
+  return (RespGetPropertyValues *)
+     protobuf_c_message_unpack (&resp_get_property_values__descriptor,
+                                allocator, len, data);
+}
+void   resp_get_property_values__free_unpacked
+                     (RespGetPropertyValues *message,
+                      ProtobufCAllocator *allocator)
+{
+  if(!message)
+    return;
+  assert(message->base.descriptor == &resp_get_property_values__descriptor);
+  protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void   property_value__init
+                     (PropertyValue         *message)
+{
+  static const PropertyValue init_value = PROPERTY_VALUE__INIT;
+  *message = init_value;
+}
+size_t property_value__get_packed_size
+                     (const PropertyValue *message)
+{
+  assert(message->base.descriptor == &property_value__descriptor);
+  return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t property_value__pack
+                     (const PropertyValue *message,
+                      uint8_t       *out)
+{
+  assert(message->base.descriptor == &property_value__descriptor);
+  return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t property_value__pack_to_buffer
+                     (const PropertyValue *message,
+                      ProtobufCBuffer *buffer)
+{
+  assert(message->base.descriptor == &property_value__descriptor);
+  return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+PropertyValue *
+       property_value__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data)
+{
+  return (PropertyValue *)
+     protobuf_c_message_unpack (&property_value__descriptor,
+                                allocator, len, data);
+}
+void   property_value__free_unpacked
+                     (PropertyValue *message,
+                      ProtobufCAllocator *allocator)
+{
+  if(!message)
+    return;
+  assert(message->base.descriptor == &property_value__descriptor);
+  protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void   cmd_set_property_values__init
+                     (CmdSetPropertyValues         *message)
+{
+  static const CmdSetPropertyValues init_value = CMD_SET_PROPERTY_VALUES__INIT;
+  *message = init_value;
+}
+size_t cmd_set_property_values__get_packed_size
+                     (const CmdSetPropertyValues *message)
+{
+  assert(message->base.descriptor == &cmd_set_property_values__descriptor);
+  return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t cmd_set_property_values__pack
+                     (const CmdSetPropertyValues *message,
+                      uint8_t       *out)
+{
+  assert(message->base.descriptor == &cmd_set_property_values__descriptor);
+  return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t cmd_set_property_values__pack_to_buffer
+                     (const CmdSetPropertyValues *message,
+                      ProtobufCBuffer *buffer)
+{
+  assert(message->base.descriptor == &cmd_set_property_values__descriptor);
+  return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+CmdSetPropertyValues *
+       cmd_set_property_values__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data)
+{
+  return (CmdSetPropertyValues *)
+     protobuf_c_message_unpack (&cmd_set_property_values__descriptor,
+                                allocator, len, data);
+}
+void   cmd_set_property_values__free_unpacked
+                     (CmdSetPropertyValues *message,
+                      ProtobufCAllocator *allocator)
+{
+  if(!message)
+    return;
+  assert(message->base.descriptor == &cmd_set_property_values__descriptor);
+  protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void   resp_set_property_values__init
+                     (RespSetPropertyValues         *message)
+{
+  static const RespSetPropertyValues init_value = RESP_SET_PROPERTY_VALUES__INIT;
+  *message = init_value;
+}
+size_t resp_set_property_values__get_packed_size
+                     (const RespSetPropertyValues *message)
+{
+  assert(message->base.descriptor == &resp_set_property_values__descriptor);
+  return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t resp_set_property_values__pack
+                     (const RespSetPropertyValues *message,
+                      uint8_t       *out)
+{
+  assert(message->base.descriptor == &resp_set_property_values__descriptor);
+  return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t resp_set_property_values__pack_to_buffer
+                     (const RespSetPropertyValues *message,
+                      ProtobufCBuffer *buffer)
+{
+  assert(message->base.descriptor == &resp_set_property_values__descriptor);
+  return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+RespSetPropertyValues *
+       resp_set_property_values__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data)
+{
+  return (RespSetPropertyValues *)
+     protobuf_c_message_unpack (&resp_set_property_values__descriptor,
+                                allocator, len, data);
+}
+void   resp_set_property_values__free_unpacked
+                     (RespSetPropertyValues *message,
+                      ProtobufCAllocator *allocator)
+{
+  if(!message)
+    return;
+  assert(message->base.descriptor == &resp_set_property_values__descriptor);
+  protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+void   local_ctrl_message__init
+                     (LocalCtrlMessage         *message)
+{
+  static const LocalCtrlMessage init_value = LOCAL_CTRL_MESSAGE__INIT;
+  *message = init_value;
+}
+size_t local_ctrl_message__get_packed_size
+                     (const LocalCtrlMessage *message)
+{
+  assert(message->base.descriptor == &local_ctrl_message__descriptor);
+  return protobuf_c_message_get_packed_size ((const ProtobufCMessage*)(message));
+}
+size_t local_ctrl_message__pack
+                     (const LocalCtrlMessage *message,
+                      uint8_t       *out)
+{
+  assert(message->base.descriptor == &local_ctrl_message__descriptor);
+  return protobuf_c_message_pack ((const ProtobufCMessage*)message, out);
+}
+size_t local_ctrl_message__pack_to_buffer
+                     (const LocalCtrlMessage *message,
+                      ProtobufCBuffer *buffer)
+{
+  assert(message->base.descriptor == &local_ctrl_message__descriptor);
+  return protobuf_c_message_pack_to_buffer ((const ProtobufCMessage*)message, buffer);
+}
+LocalCtrlMessage *
+       local_ctrl_message__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data)
+{
+  return (LocalCtrlMessage *)
+     protobuf_c_message_unpack (&local_ctrl_message__descriptor,
+                                allocator, len, data);
+}
+void   local_ctrl_message__free_unpacked
+                     (LocalCtrlMessage *message,
+                      ProtobufCAllocator *allocator)
+{
+  if(!message)
+    return;
+  assert(message->base.descriptor == &local_ctrl_message__descriptor);
+  protobuf_c_message_free_unpacked ((ProtobufCMessage*)message, allocator);
+}
+#define cmd_get_property_count__field_descriptors NULL
+#define cmd_get_property_count__field_indices_by_name NULL
+#define cmd_get_property_count__number_ranges NULL
+const ProtobufCMessageDescriptor cmd_get_property_count__descriptor =
+{
+  PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+  "CmdGetPropertyCount",
+  "CmdGetPropertyCount",
+  "CmdGetPropertyCount",
+  "",
+  sizeof(CmdGetPropertyCount),
+  0,
+  cmd_get_property_count__field_descriptors,
+  cmd_get_property_count__field_indices_by_name,
+  0,  cmd_get_property_count__number_ranges,
+  (ProtobufCMessageInit) cmd_get_property_count__init,
+  NULL,NULL,NULL    /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor resp_get_property_count__field_descriptors[2] =
+{
+  {
+    "status",
+    1,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_ENUM,
+    0,   /* quantifier_offset */
+    offsetof(RespGetPropertyCount, status),
+    &status__descriptor,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "count",
+    2,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_UINT32,
+    0,   /* quantifier_offset */
+    offsetof(RespGetPropertyCount, count),
+    NULL,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+};
+static const unsigned resp_get_property_count__field_indices_by_name[] = {
+  1,   /* field[1] = count */
+  0,   /* field[0] = status */
+};
+static const ProtobufCIntRange resp_get_property_count__number_ranges[1 + 1] =
+{
+  { 1, 0 },
+  { 0, 2 }
+};
+const ProtobufCMessageDescriptor resp_get_property_count__descriptor =
+{
+  PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+  "RespGetPropertyCount",
+  "RespGetPropertyCount",
+  "RespGetPropertyCount",
+  "",
+  sizeof(RespGetPropertyCount),
+  2,
+  resp_get_property_count__field_descriptors,
+  resp_get_property_count__field_indices_by_name,
+  1,  resp_get_property_count__number_ranges,
+  (ProtobufCMessageInit) resp_get_property_count__init,
+  NULL,NULL,NULL    /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor property_info__field_descriptors[5] =
+{
+  {
+    "status",
+    1,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_ENUM,
+    0,   /* quantifier_offset */
+    offsetof(PropertyInfo, status),
+    &status__descriptor,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "name",
+    2,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_STRING,
+    0,   /* quantifier_offset */
+    offsetof(PropertyInfo, name),
+    NULL,
+    &protobuf_c_empty_string,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "type",
+    3,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_UINT32,
+    0,   /* quantifier_offset */
+    offsetof(PropertyInfo, type),
+    NULL,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "flags",
+    4,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_UINT32,
+    0,   /* quantifier_offset */
+    offsetof(PropertyInfo, flags),
+    NULL,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "value",
+    5,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_BYTES,
+    0,   /* quantifier_offset */
+    offsetof(PropertyInfo, value),
+    NULL,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+};
+static const unsigned property_info__field_indices_by_name[] = {
+  3,   /* field[3] = flags */
+  1,   /* field[1] = name */
+  0,   /* field[0] = status */
+  2,   /* field[2] = type */
+  4,   /* field[4] = value */
+};
+static const ProtobufCIntRange property_info__number_ranges[1 + 1] =
+{
+  { 1, 0 },
+  { 0, 5 }
+};
+const ProtobufCMessageDescriptor property_info__descriptor =
+{
+  PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+  "PropertyInfo",
+  "PropertyInfo",
+  "PropertyInfo",
+  "",
+  sizeof(PropertyInfo),
+  5,
+  property_info__field_descriptors,
+  property_info__field_indices_by_name,
+  1,  property_info__number_ranges,
+  (ProtobufCMessageInit) property_info__init,
+  NULL,NULL,NULL    /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor cmd_get_property_values__field_descriptors[1] =
+{
+  {
+    "indices",
+    1,
+    PROTOBUF_C_LABEL_REPEATED,
+    PROTOBUF_C_TYPE_UINT32,
+    offsetof(CmdGetPropertyValues, n_indices),
+    offsetof(CmdGetPropertyValues, indices),
+    NULL,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+};
+static const unsigned cmd_get_property_values__field_indices_by_name[] = {
+  0,   /* field[0] = indices */
+};
+static const ProtobufCIntRange cmd_get_property_values__number_ranges[1 + 1] =
+{
+  { 1, 0 },
+  { 0, 1 }
+};
+const ProtobufCMessageDescriptor cmd_get_property_values__descriptor =
+{
+  PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+  "CmdGetPropertyValues",
+  "CmdGetPropertyValues",
+  "CmdGetPropertyValues",
+  "",
+  sizeof(CmdGetPropertyValues),
+  1,
+  cmd_get_property_values__field_descriptors,
+  cmd_get_property_values__field_indices_by_name,
+  1,  cmd_get_property_values__number_ranges,
+  (ProtobufCMessageInit) cmd_get_property_values__init,
+  NULL,NULL,NULL    /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor resp_get_property_values__field_descriptors[2] =
+{
+  {
+    "status",
+    1,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_ENUM,
+    0,   /* quantifier_offset */
+    offsetof(RespGetPropertyValues, status),
+    &status__descriptor,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "props",
+    2,
+    PROTOBUF_C_LABEL_REPEATED,
+    PROTOBUF_C_TYPE_MESSAGE,
+    offsetof(RespGetPropertyValues, n_props),
+    offsetof(RespGetPropertyValues, props),
+    &property_info__descriptor,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+};
+static const unsigned resp_get_property_values__field_indices_by_name[] = {
+  1,   /* field[1] = props */
+  0,   /* field[0] = status */
+};
+static const ProtobufCIntRange resp_get_property_values__number_ranges[1 + 1] =
+{
+  { 1, 0 },
+  { 0, 2 }
+};
+const ProtobufCMessageDescriptor resp_get_property_values__descriptor =
+{
+  PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+  "RespGetPropertyValues",
+  "RespGetPropertyValues",
+  "RespGetPropertyValues",
+  "",
+  sizeof(RespGetPropertyValues),
+  2,
+  resp_get_property_values__field_descriptors,
+  resp_get_property_values__field_indices_by_name,
+  1,  resp_get_property_values__number_ranges,
+  (ProtobufCMessageInit) resp_get_property_values__init,
+  NULL,NULL,NULL    /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor property_value__field_descriptors[2] =
+{
+  {
+    "index",
+    1,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_UINT32,
+    0,   /* quantifier_offset */
+    offsetof(PropertyValue, index),
+    NULL,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "value",
+    2,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_BYTES,
+    0,   /* quantifier_offset */
+    offsetof(PropertyValue, value),
+    NULL,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+};
+static const unsigned property_value__field_indices_by_name[] = {
+  0,   /* field[0] = index */
+  1,   /* field[1] = value */
+};
+static const ProtobufCIntRange property_value__number_ranges[1 + 1] =
+{
+  { 1, 0 },
+  { 0, 2 }
+};
+const ProtobufCMessageDescriptor property_value__descriptor =
+{
+  PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+  "PropertyValue",
+  "PropertyValue",
+  "PropertyValue",
+  "",
+  sizeof(PropertyValue),
+  2,
+  property_value__field_descriptors,
+  property_value__field_indices_by_name,
+  1,  property_value__number_ranges,
+  (ProtobufCMessageInit) property_value__init,
+  NULL,NULL,NULL    /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor cmd_set_property_values__field_descriptors[1] =
+{
+  {
+    "props",
+    1,
+    PROTOBUF_C_LABEL_REPEATED,
+    PROTOBUF_C_TYPE_MESSAGE,
+    offsetof(CmdSetPropertyValues, n_props),
+    offsetof(CmdSetPropertyValues, props),
+    &property_value__descriptor,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+};
+static const unsigned cmd_set_property_values__field_indices_by_name[] = {
+  0,   /* field[0] = props */
+};
+static const ProtobufCIntRange cmd_set_property_values__number_ranges[1 + 1] =
+{
+  { 1, 0 },
+  { 0, 1 }
+};
+const ProtobufCMessageDescriptor cmd_set_property_values__descriptor =
+{
+  PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+  "CmdSetPropertyValues",
+  "CmdSetPropertyValues",
+  "CmdSetPropertyValues",
+  "",
+  sizeof(CmdSetPropertyValues),
+  1,
+  cmd_set_property_values__field_descriptors,
+  cmd_set_property_values__field_indices_by_name,
+  1,  cmd_set_property_values__number_ranges,
+  (ProtobufCMessageInit) cmd_set_property_values__init,
+  NULL,NULL,NULL    /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor resp_set_property_values__field_descriptors[1] =
+{
+  {
+    "status",
+    1,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_ENUM,
+    0,   /* quantifier_offset */
+    offsetof(RespSetPropertyValues, status),
+    &status__descriptor,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+};
+static const unsigned resp_set_property_values__field_indices_by_name[] = {
+  0,   /* field[0] = status */
+};
+static const ProtobufCIntRange resp_set_property_values__number_ranges[1 + 1] =
+{
+  { 1, 0 },
+  { 0, 1 }
+};
+const ProtobufCMessageDescriptor resp_set_property_values__descriptor =
+{
+  PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+  "RespSetPropertyValues",
+  "RespSetPropertyValues",
+  "RespSetPropertyValues",
+  "",
+  sizeof(RespSetPropertyValues),
+  1,
+  resp_set_property_values__field_descriptors,
+  resp_set_property_values__field_indices_by_name,
+  1,  resp_set_property_values__number_ranges,
+  (ProtobufCMessageInit) resp_set_property_values__init,
+  NULL,NULL,NULL    /* reserved[123] */
+};
+static const ProtobufCFieldDescriptor local_ctrl_message__field_descriptors[7] =
+{
+  {
+    "msg",
+    1,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_ENUM,
+    0,   /* quantifier_offset */
+    offsetof(LocalCtrlMessage, msg),
+    &local_ctrl_msg_type__descriptor,
+    NULL,
+    0,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "cmd_get_prop_count",
+    10,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_MESSAGE,
+    offsetof(LocalCtrlMessage, payload_case),
+    offsetof(LocalCtrlMessage, cmd_get_prop_count),
+    &cmd_get_property_count__descriptor,
+    NULL,
+    0 | PROTOBUF_C_FIELD_FLAG_ONEOF,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "resp_get_prop_count",
+    11,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_MESSAGE,
+    offsetof(LocalCtrlMessage, payload_case),
+    offsetof(LocalCtrlMessage, resp_get_prop_count),
+    &resp_get_property_count__descriptor,
+    NULL,
+    0 | PROTOBUF_C_FIELD_FLAG_ONEOF,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "cmd_get_prop_vals",
+    12,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_MESSAGE,
+    offsetof(LocalCtrlMessage, payload_case),
+    offsetof(LocalCtrlMessage, cmd_get_prop_vals),
+    &cmd_get_property_values__descriptor,
+    NULL,
+    0 | PROTOBUF_C_FIELD_FLAG_ONEOF,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "resp_get_prop_vals",
+    13,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_MESSAGE,
+    offsetof(LocalCtrlMessage, payload_case),
+    offsetof(LocalCtrlMessage, resp_get_prop_vals),
+    &resp_get_property_values__descriptor,
+    NULL,
+    0 | PROTOBUF_C_FIELD_FLAG_ONEOF,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "cmd_set_prop_vals",
+    14,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_MESSAGE,
+    offsetof(LocalCtrlMessage, payload_case),
+    offsetof(LocalCtrlMessage, cmd_set_prop_vals),
+    &cmd_set_property_values__descriptor,
+    NULL,
+    0 | PROTOBUF_C_FIELD_FLAG_ONEOF,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+  {
+    "resp_set_prop_vals",
+    15,
+    PROTOBUF_C_LABEL_NONE,
+    PROTOBUF_C_TYPE_MESSAGE,
+    offsetof(LocalCtrlMessage, payload_case),
+    offsetof(LocalCtrlMessage, resp_set_prop_vals),
+    &resp_set_property_values__descriptor,
+    NULL,
+    0 | PROTOBUF_C_FIELD_FLAG_ONEOF,             /* flags */
+    0,NULL,NULL    /* reserved1,reserved2, etc */
+  },
+};
+static const unsigned local_ctrl_message__field_indices_by_name[] = {
+  1,   /* field[1] = cmd_get_prop_count */
+  3,   /* field[3] = cmd_get_prop_vals */
+  5,   /* field[5] = cmd_set_prop_vals */
+  0,   /* field[0] = msg */
+  2,   /* field[2] = resp_get_prop_count */
+  4,   /* field[4] = resp_get_prop_vals */
+  6,   /* field[6] = resp_set_prop_vals */
+};
+static const ProtobufCIntRange local_ctrl_message__number_ranges[2 + 1] =
+{
+  { 1, 0 },
+  { 10, 1 },
+  { 0, 7 }
+};
+const ProtobufCMessageDescriptor local_ctrl_message__descriptor =
+{
+  PROTOBUF_C__MESSAGE_DESCRIPTOR_MAGIC,
+  "LocalCtrlMessage",
+  "LocalCtrlMessage",
+  "LocalCtrlMessage",
+  "",
+  sizeof(LocalCtrlMessage),
+  7,
+  local_ctrl_message__field_descriptors,
+  local_ctrl_message__field_indices_by_name,
+  2,  local_ctrl_message__number_ranges,
+  (ProtobufCMessageInit) local_ctrl_message__init,
+  NULL,NULL,NULL    /* reserved[123] */
+};
+static const ProtobufCEnumValue local_ctrl_msg_type__enum_values_by_number[6] =
+{
+  { "TypeCmdGetPropertyCount", "LOCAL_CTRL_MSG_TYPE__TypeCmdGetPropertyCount", 0 },
+  { "TypeRespGetPropertyCount", "LOCAL_CTRL_MSG_TYPE__TypeRespGetPropertyCount", 1 },
+  { "TypeCmdGetPropertyValues", "LOCAL_CTRL_MSG_TYPE__TypeCmdGetPropertyValues", 4 },
+  { "TypeRespGetPropertyValues", "LOCAL_CTRL_MSG_TYPE__TypeRespGetPropertyValues", 5 },
+  { "TypeCmdSetPropertyValues", "LOCAL_CTRL_MSG_TYPE__TypeCmdSetPropertyValues", 6 },
+  { "TypeRespSetPropertyValues", "LOCAL_CTRL_MSG_TYPE__TypeRespSetPropertyValues", 7 },
+};
+static const ProtobufCIntRange local_ctrl_msg_type__value_ranges[] = {
+{0, 0},{4, 2},{0, 6}
+};
+static const ProtobufCEnumValueIndex local_ctrl_msg_type__enum_values_by_name[6] =
+{
+  { "TypeCmdGetPropertyCount", 0 },
+  { "TypeCmdGetPropertyValues", 2 },
+  { "TypeCmdSetPropertyValues", 4 },
+  { "TypeRespGetPropertyCount", 1 },
+  { "TypeRespGetPropertyValues", 3 },
+  { "TypeRespSetPropertyValues", 5 },
+};
+const ProtobufCEnumDescriptor local_ctrl_msg_type__descriptor =
+{
+  PROTOBUF_C__ENUM_DESCRIPTOR_MAGIC,
+  "LocalCtrlMsgType",
+  "LocalCtrlMsgType",
+  "LocalCtrlMsgType",
+  "",
+  6,
+  local_ctrl_msg_type__enum_values_by_number,
+  6,
+  local_ctrl_msg_type__enum_values_by_name,
+  2,
+  local_ctrl_msg_type__value_ranges,
+  NULL,NULL,NULL,NULL   /* reserved[1234] */
+};

+ 383 - 0
components/esp_local_ctrl/proto-c/esp_local_ctrl.pb-c.h

@@ -0,0 +1,383 @@
+/* Generated by the protocol buffer compiler.  DO NOT EDIT! */
+/* Generated from: esp_local_ctrl.proto */
+
+#ifndef PROTOBUF_C_esp_5flocal_5fctrl_2eproto__INCLUDED
+#define PROTOBUF_C_esp_5flocal_5fctrl_2eproto__INCLUDED
+
+#include <protobuf-c/protobuf-c.h>
+
+PROTOBUF_C__BEGIN_DECLS
+
+#if PROTOBUF_C_VERSION_NUMBER < 1003000
+# error This file was generated by a newer version of protoc-c which is incompatible with your libprotobuf-c headers. Please update your headers.
+#elif 1003001 < PROTOBUF_C_MIN_COMPILER_VERSION
+# error This file was generated by an older version of protoc-c which is incompatible with your libprotobuf-c headers. Please regenerate this file with a newer version of protoc-c.
+#endif
+
+#include "constants.pb-c.h"
+
+typedef struct _CmdGetPropertyCount CmdGetPropertyCount;
+typedef struct _RespGetPropertyCount RespGetPropertyCount;
+typedef struct _PropertyInfo PropertyInfo;
+typedef struct _CmdGetPropertyValues CmdGetPropertyValues;
+typedef struct _RespGetPropertyValues RespGetPropertyValues;
+typedef struct _PropertyValue PropertyValue;
+typedef struct _CmdSetPropertyValues CmdSetPropertyValues;
+typedef struct _RespSetPropertyValues RespSetPropertyValues;
+typedef struct _LocalCtrlMessage LocalCtrlMessage;
+
+
+/* --- enums --- */
+
+typedef enum _LocalCtrlMsgType {
+  LOCAL_CTRL_MSG_TYPE__TypeCmdGetPropertyCount = 0,
+  LOCAL_CTRL_MSG_TYPE__TypeRespGetPropertyCount = 1,
+  LOCAL_CTRL_MSG_TYPE__TypeCmdGetPropertyValues = 4,
+  LOCAL_CTRL_MSG_TYPE__TypeRespGetPropertyValues = 5,
+  LOCAL_CTRL_MSG_TYPE__TypeCmdSetPropertyValues = 6,
+  LOCAL_CTRL_MSG_TYPE__TypeRespSetPropertyValues = 7
+    PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(LOCAL_CTRL_MSG_TYPE)
+} LocalCtrlMsgType;
+
+/* --- messages --- */
+
+struct  _CmdGetPropertyCount
+{
+  ProtobufCMessage base;
+};
+#define CMD_GET_PROPERTY_COUNT__INIT \
+ { PROTOBUF_C_MESSAGE_INIT (&cmd_get_property_count__descriptor) \
+     }
+
+
+struct  _RespGetPropertyCount
+{
+  ProtobufCMessage base;
+  Status status;
+  uint32_t count;
+};
+#define RESP_GET_PROPERTY_COUNT__INIT \
+ { PROTOBUF_C_MESSAGE_INIT (&resp_get_property_count__descriptor) \
+    , STATUS__Success, 0 }
+
+
+struct  _PropertyInfo
+{
+  ProtobufCMessage base;
+  Status status;
+  char *name;
+  uint32_t type;
+  uint32_t flags;
+  ProtobufCBinaryData value;
+};
+#define PROPERTY_INFO__INIT \
+ { PROTOBUF_C_MESSAGE_INIT (&property_info__descriptor) \
+    , STATUS__Success, (char *)protobuf_c_empty_string, 0, 0, {0,NULL} }
+
+
+struct  _CmdGetPropertyValues
+{
+  ProtobufCMessage base;
+  size_t n_indices;
+  uint32_t *indices;
+};
+#define CMD_GET_PROPERTY_VALUES__INIT \
+ { PROTOBUF_C_MESSAGE_INIT (&cmd_get_property_values__descriptor) \
+    , 0,NULL }
+
+
+struct  _RespGetPropertyValues
+{
+  ProtobufCMessage base;
+  Status status;
+  size_t n_props;
+  PropertyInfo **props;
+};
+#define RESP_GET_PROPERTY_VALUES__INIT \
+ { PROTOBUF_C_MESSAGE_INIT (&resp_get_property_values__descriptor) \
+    , STATUS__Success, 0,NULL }
+
+
+struct  _PropertyValue
+{
+  ProtobufCMessage base;
+  uint32_t index;
+  ProtobufCBinaryData value;
+};
+#define PROPERTY_VALUE__INIT \
+ { PROTOBUF_C_MESSAGE_INIT (&property_value__descriptor) \
+    , 0, {0,NULL} }
+
+
+struct  _CmdSetPropertyValues
+{
+  ProtobufCMessage base;
+  size_t n_props;
+  PropertyValue **props;
+};
+#define CMD_SET_PROPERTY_VALUES__INIT \
+ { PROTOBUF_C_MESSAGE_INIT (&cmd_set_property_values__descriptor) \
+    , 0,NULL }
+
+
+struct  _RespSetPropertyValues
+{
+  ProtobufCMessage base;
+  Status status;
+};
+#define RESP_SET_PROPERTY_VALUES__INIT \
+ { PROTOBUF_C_MESSAGE_INIT (&resp_set_property_values__descriptor) \
+    , STATUS__Success }
+
+
+typedef enum {
+  LOCAL_CTRL_MESSAGE__PAYLOAD__NOT_SET = 0,
+  LOCAL_CTRL_MESSAGE__PAYLOAD_CMD_GET_PROP_COUNT = 10,
+  LOCAL_CTRL_MESSAGE__PAYLOAD_RESP_GET_PROP_COUNT = 11,
+  LOCAL_CTRL_MESSAGE__PAYLOAD_CMD_GET_PROP_VALS = 12,
+  LOCAL_CTRL_MESSAGE__PAYLOAD_RESP_GET_PROP_VALS = 13,
+  LOCAL_CTRL_MESSAGE__PAYLOAD_CMD_SET_PROP_VALS = 14,
+  LOCAL_CTRL_MESSAGE__PAYLOAD_RESP_SET_PROP_VALS = 15
+    PROTOBUF_C__FORCE_ENUM_TO_BE_INT_SIZE(LOCAL_CTRL_MESSAGE__PAYLOAD)
+} LocalCtrlMessage__PayloadCase;
+
+struct  _LocalCtrlMessage
+{
+  ProtobufCMessage base;
+  LocalCtrlMsgType msg;
+  LocalCtrlMessage__PayloadCase payload_case;
+  union {
+    CmdGetPropertyCount *cmd_get_prop_count;
+    RespGetPropertyCount *resp_get_prop_count;
+    CmdGetPropertyValues *cmd_get_prop_vals;
+    RespGetPropertyValues *resp_get_prop_vals;
+    CmdSetPropertyValues *cmd_set_prop_vals;
+    RespSetPropertyValues *resp_set_prop_vals;
+  };
+};
+#define LOCAL_CTRL_MESSAGE__INIT \
+ { PROTOBUF_C_MESSAGE_INIT (&local_ctrl_message__descriptor) \
+    , LOCAL_CTRL_MSG_TYPE__TypeCmdGetPropertyCount, LOCAL_CTRL_MESSAGE__PAYLOAD__NOT_SET, {0} }
+
+
+/* CmdGetPropertyCount methods */
+void   cmd_get_property_count__init
+                     (CmdGetPropertyCount         *message);
+size_t cmd_get_property_count__get_packed_size
+                     (const CmdGetPropertyCount   *message);
+size_t cmd_get_property_count__pack
+                     (const CmdGetPropertyCount   *message,
+                      uint8_t             *out);
+size_t cmd_get_property_count__pack_to_buffer
+                     (const CmdGetPropertyCount   *message,
+                      ProtobufCBuffer     *buffer);
+CmdGetPropertyCount *
+       cmd_get_property_count__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data);
+void   cmd_get_property_count__free_unpacked
+                     (CmdGetPropertyCount *message,
+                      ProtobufCAllocator *allocator);
+/* RespGetPropertyCount methods */
+void   resp_get_property_count__init
+                     (RespGetPropertyCount         *message);
+size_t resp_get_property_count__get_packed_size
+                     (const RespGetPropertyCount   *message);
+size_t resp_get_property_count__pack
+                     (const RespGetPropertyCount   *message,
+                      uint8_t             *out);
+size_t resp_get_property_count__pack_to_buffer
+                     (const RespGetPropertyCount   *message,
+                      ProtobufCBuffer     *buffer);
+RespGetPropertyCount *
+       resp_get_property_count__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data);
+void   resp_get_property_count__free_unpacked
+                     (RespGetPropertyCount *message,
+                      ProtobufCAllocator *allocator);
+/* PropertyInfo methods */
+void   property_info__init
+                     (PropertyInfo         *message);
+size_t property_info__get_packed_size
+                     (const PropertyInfo   *message);
+size_t property_info__pack
+                     (const PropertyInfo   *message,
+                      uint8_t             *out);
+size_t property_info__pack_to_buffer
+                     (const PropertyInfo   *message,
+                      ProtobufCBuffer     *buffer);
+PropertyInfo *
+       property_info__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data);
+void   property_info__free_unpacked
+                     (PropertyInfo *message,
+                      ProtobufCAllocator *allocator);
+/* CmdGetPropertyValues methods */
+void   cmd_get_property_values__init
+                     (CmdGetPropertyValues         *message);
+size_t cmd_get_property_values__get_packed_size
+                     (const CmdGetPropertyValues   *message);
+size_t cmd_get_property_values__pack
+                     (const CmdGetPropertyValues   *message,
+                      uint8_t             *out);
+size_t cmd_get_property_values__pack_to_buffer
+                     (const CmdGetPropertyValues   *message,
+                      ProtobufCBuffer     *buffer);
+CmdGetPropertyValues *
+       cmd_get_property_values__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data);
+void   cmd_get_property_values__free_unpacked
+                     (CmdGetPropertyValues *message,
+                      ProtobufCAllocator *allocator);
+/* RespGetPropertyValues methods */
+void   resp_get_property_values__init
+                     (RespGetPropertyValues         *message);
+size_t resp_get_property_values__get_packed_size
+                     (const RespGetPropertyValues   *message);
+size_t resp_get_property_values__pack
+                     (const RespGetPropertyValues   *message,
+                      uint8_t             *out);
+size_t resp_get_property_values__pack_to_buffer
+                     (const RespGetPropertyValues   *message,
+                      ProtobufCBuffer     *buffer);
+RespGetPropertyValues *
+       resp_get_property_values__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data);
+void   resp_get_property_values__free_unpacked
+                     (RespGetPropertyValues *message,
+                      ProtobufCAllocator *allocator);
+/* PropertyValue methods */
+void   property_value__init
+                     (PropertyValue         *message);
+size_t property_value__get_packed_size
+                     (const PropertyValue   *message);
+size_t property_value__pack
+                     (const PropertyValue   *message,
+                      uint8_t             *out);
+size_t property_value__pack_to_buffer
+                     (const PropertyValue   *message,
+                      ProtobufCBuffer     *buffer);
+PropertyValue *
+       property_value__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data);
+void   property_value__free_unpacked
+                     (PropertyValue *message,
+                      ProtobufCAllocator *allocator);
+/* CmdSetPropertyValues methods */
+void   cmd_set_property_values__init
+                     (CmdSetPropertyValues         *message);
+size_t cmd_set_property_values__get_packed_size
+                     (const CmdSetPropertyValues   *message);
+size_t cmd_set_property_values__pack
+                     (const CmdSetPropertyValues   *message,
+                      uint8_t             *out);
+size_t cmd_set_property_values__pack_to_buffer
+                     (const CmdSetPropertyValues   *message,
+                      ProtobufCBuffer     *buffer);
+CmdSetPropertyValues *
+       cmd_set_property_values__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data);
+void   cmd_set_property_values__free_unpacked
+                     (CmdSetPropertyValues *message,
+                      ProtobufCAllocator *allocator);
+/* RespSetPropertyValues methods */
+void   resp_set_property_values__init
+                     (RespSetPropertyValues         *message);
+size_t resp_set_property_values__get_packed_size
+                     (const RespSetPropertyValues   *message);
+size_t resp_set_property_values__pack
+                     (const RespSetPropertyValues   *message,
+                      uint8_t             *out);
+size_t resp_set_property_values__pack_to_buffer
+                     (const RespSetPropertyValues   *message,
+                      ProtobufCBuffer     *buffer);
+RespSetPropertyValues *
+       resp_set_property_values__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data);
+void   resp_set_property_values__free_unpacked
+                     (RespSetPropertyValues *message,
+                      ProtobufCAllocator *allocator);
+/* LocalCtrlMessage methods */
+void   local_ctrl_message__init
+                     (LocalCtrlMessage         *message);
+size_t local_ctrl_message__get_packed_size
+                     (const LocalCtrlMessage   *message);
+size_t local_ctrl_message__pack
+                     (const LocalCtrlMessage   *message,
+                      uint8_t             *out);
+size_t local_ctrl_message__pack_to_buffer
+                     (const LocalCtrlMessage   *message,
+                      ProtobufCBuffer     *buffer);
+LocalCtrlMessage *
+       local_ctrl_message__unpack
+                     (ProtobufCAllocator  *allocator,
+                      size_t               len,
+                      const uint8_t       *data);
+void   local_ctrl_message__free_unpacked
+                     (LocalCtrlMessage *message,
+                      ProtobufCAllocator *allocator);
+/* --- per-message closures --- */
+
+typedef void (*CmdGetPropertyCount_Closure)
+                 (const CmdGetPropertyCount *message,
+                  void *closure_data);
+typedef void (*RespGetPropertyCount_Closure)
+                 (const RespGetPropertyCount *message,
+                  void *closure_data);
+typedef void (*PropertyInfo_Closure)
+                 (const PropertyInfo *message,
+                  void *closure_data);
+typedef void (*CmdGetPropertyValues_Closure)
+                 (const CmdGetPropertyValues *message,
+                  void *closure_data);
+typedef void (*RespGetPropertyValues_Closure)
+                 (const RespGetPropertyValues *message,
+                  void *closure_data);
+typedef void (*PropertyValue_Closure)
+                 (const PropertyValue *message,
+                  void *closure_data);
+typedef void (*CmdSetPropertyValues_Closure)
+                 (const CmdSetPropertyValues *message,
+                  void *closure_data);
+typedef void (*RespSetPropertyValues_Closure)
+                 (const RespSetPropertyValues *message,
+                  void *closure_data);
+typedef void (*LocalCtrlMessage_Closure)
+                 (const LocalCtrlMessage *message,
+                  void *closure_data);
+
+/* --- services --- */
+
+
+/* --- descriptors --- */
+
+extern const ProtobufCEnumDescriptor    local_ctrl_msg_type__descriptor;
+extern const ProtobufCMessageDescriptor cmd_get_property_count__descriptor;
+extern const ProtobufCMessageDescriptor resp_get_property_count__descriptor;
+extern const ProtobufCMessageDescriptor property_info__descriptor;
+extern const ProtobufCMessageDescriptor cmd_get_property_values__descriptor;
+extern const ProtobufCMessageDescriptor resp_get_property_values__descriptor;
+extern const ProtobufCMessageDescriptor property_value__descriptor;
+extern const ProtobufCMessageDescriptor cmd_set_property_values__descriptor;
+extern const ProtobufCMessageDescriptor resp_set_property_values__descriptor;
+extern const ProtobufCMessageDescriptor local_ctrl_message__descriptor;
+
+PROTOBUF_C__END_DECLS
+
+
+#endif  /* PROTOBUF_C_esp_5flocal_5fctrl_2eproto__INCLUDED */

+ 30 - 0
components/esp_local_ctrl/proto/CMakeLists.txt

@@ -0,0 +1,30 @@
+cmake_minimum_required(VERSION 3.5)
+
+set(PROTO_COMPILER "protoc")
+set(PROTO_C_COMPILER "protoc-c")
+set(C_OUT_PATH "${CMAKE_CURRENT_LIST_DIR}/../proto-c")
+set(PY_OUT_PATH "${CMAKE_CURRENT_LIST_DIR}/../python")
+set(PROTOCOMM_INCL_PATH "${CMAKE_CURRENT_LIST_DIR}/../../protocomm/proto")
+
+file(GLOB PROTO_FILES
+    LIST_DIRECTORIES false
+    RELATIVE ${CMAKE_CURRENT_LIST_DIR}
+    "*.proto")
+
+add_custom_target(c_proto
+    COMMAND ${PROTO_C_COMPILER} --c_out=${C_OUT_PATH} -I . -I ${PROTOCOMM_INCL_PATH} ${PROTO_FILES}
+    VERBATIM
+    WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
+    )
+
+add_custom_target(python_proto
+    COMMAND ${PROTO_COMPILER} --python_out=${PY_OUT_PATH} -I . -I ${PROTOCOMM_INCL_PATH} ${PROTO_FILES}
+    VERBATIM
+    WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
+    )
+
+add_custom_target(proto ALL
+    DEPENDS c_proto python_proto
+    VERBATIM
+    WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
+    )

+ 25 - 0
components/esp_local_ctrl/proto/README.md

@@ -0,0 +1,25 @@
+# Protobuf files for defining ESP Local Control message structures
+
+The proto files under this directory are used by esp_local_ctrl for defining protobuf messages which are sent and received over protocomm transport layer. These proto files cannot be used directly and have to be compiled into C and Python files. The generated C files are used by esp_local_ctrl itself to create, delete and manipulate transaction packets. The generated Python files can be used by python based applications for implementing client side interface to esp_local_ctrl service running on a device.
+
+Note : These proto files are not automatically compiled during the build process.
+
+# Compilation
+
+Compilation requires protoc (Protobuf Compiler) and protoc-c (Protobuf C Compiler) installed. Since the generated files are to remain the same, as long as the proto files are not modified, therefore the generated files are already available under `components/esp_local_ctrl/proto-c` and `components/esp_local_ctrl/python` directories, and thus running `cmake` / `make` (and installing the Protobuf compilers) is optional.
+
+If using `cmake` follow the below steps. If using `make`, jump to Step 2 directly.
+
+## Step 1 (Only for cmake)
+
+When using cmake, first create a build directory and call cmake from inside:
+
+```
+mkdir build
+cd build
+cmake ..
+```
+
+## Step 2
+
+Simply run `make` to generate the respective C and Python files. The newly created files will overwrite those under `components/esp_local_ctrl/proto-c` and `components/esp_local_ctrl/python`

+ 62 - 0
components/esp_local_ctrl/proto/esp_local_ctrl.proto

@@ -0,0 +1,62 @@
+syntax = "proto3";
+
+import "constants.proto";
+
+message CmdGetPropertyCount {
+}
+
+message RespGetPropertyCount {
+    Status status = 1;
+    uint32 count = 2;
+}
+
+message PropertyInfo {
+    Status status = 1;
+    string name = 2;
+    uint32 type = 3;
+    uint32 flags = 4;
+    bytes  value = 5;
+}
+
+message CmdGetPropertyValues {
+    repeated uint32 indices = 1;
+}
+
+message RespGetPropertyValues {
+    Status status = 1;
+    repeated PropertyInfo props = 2;
+}
+
+message PropertyValue {
+    uint32 index = 1;
+    bytes value = 2;
+}
+
+message CmdSetPropertyValues {
+    repeated PropertyValue props = 1;
+}
+
+message RespSetPropertyValues {
+    Status status = 1;
+}
+
+enum LocalCtrlMsgType {
+    TypeCmdGetPropertyCount = 0;
+    TypeRespGetPropertyCount = 1;
+    TypeCmdGetPropertyValues = 4;
+    TypeRespGetPropertyValues = 5;
+    TypeCmdSetPropertyValues = 6;
+    TypeRespSetPropertyValues = 7;
+}
+
+message LocalCtrlMessage {
+    LocalCtrlMsgType msg = 1;
+    oneof payload {
+        CmdGetPropertyCount cmd_get_prop_count = 10;
+        RespGetPropertyCount resp_get_prop_count = 11;
+        CmdGetPropertyValues cmd_get_prop_vals = 12;
+        RespGetPropertyValues resp_get_prop_vals = 13;
+        CmdSetPropertyValues cmd_set_prop_vals = 14;
+        RespSetPropertyValues resp_set_prop_vals = 15;
+    }
+}

+ 7 - 0
components/esp_local_ctrl/proto/makefile

@@ -0,0 +1,7 @@
+all: c_proto python_proto
+
+c_proto: *.proto
+	@protoc-c --c_out=../proto-c/ -I . -I ../../protocomm/proto/ *.proto
+
+python_proto: *.proto
+	@protoc --python_out=../python/ -I . -I ../../protocomm/proto/ *.proto

+ 549 - 0
components/esp_local_ctrl/python/esp_local_ctrl_pb2.py

@@ -0,0 +1,549 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: esp_local_ctrl.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+import constants_pb2 as constants__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='esp_local_ctrl.proto',
+  package='',
+  syntax='proto3',
+  serialized_options=None,
+  serialized_pb=_b('\n\x14\x65sp_local_ctrl.proto\x1a\x0f\x63onstants.proto\"\x15\n\x13\x43mdGetPropertyCount\">\n\x14RespGetPropertyCount\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12\r\n\x05\x63ount\x18\x02 \x01(\r\"a\n\x0cPropertyInfo\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04type\x18\x03 \x01(\r\x12\r\n\x05\x66lags\x18\x04 \x01(\r\x12\r\n\x05value\x18\x05 \x01(\x0c\"\'\n\x14\x43mdGetPropertyValues\x12\x0f\n\x07indices\x18\x01 \x03(\r\"N\n\x15RespGetPropertyValues\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\x12\x1c\n\x05props\x18\x02 \x03(\x0b\x32\r.PropertyInfo\"-\n\rPropertyValue\x12\r\n\x05index\x18\x01 \x01(\r\x12\r\n\x05value\x18\x02 \x01(\x0c\"5\n\x14\x43mdSetPropertyValues\x12\x1d\n\x05props\x18\x01 \x03(\x0b\x32\x0e.PropertyValue\"0\n\x15RespSetPropertyValues\x12\x17\n\x06status\x18\x01 \x01(\x0e\x32\x07.Status\"\xfb\x02\n\x10LocalCtrlMessage\x12\x1e\n\x03msg\x18\x01 \x01(\x0e\x32\x11.LocalCtrlMsgType\x12\x32\n\x12\x63md_get_prop_count\x18\n \x01(\x0b\x32\x14.CmdGetPropertyCountH\x00\x12\x34\n\x13resp_get_prop_count\x18\x0b \x01(\x0b\x32\x15.RespGetPropertyCountH\x00\x12\x32\n\x11\x63md_get_prop_vals\x18\x0c \x01(\x0b\x32\x15.CmdGetPropertyValuesH\x00\x12\x34\n\x12resp_get_prop_vals\x18\r \x01(\x0b\x32\x16.RespGetPropertyValuesH\x00\x12\x32\n\x11\x63md_set_prop_vals\x18\x0e \x01(\x0b\x32\x15.CmdSetPropertyValuesH\x00\x12\x34\n\x12resp_set_prop_vals\x18\x0f \x01(\x0b\x32\x16.RespSetPropertyValuesH\x00\x42\t\n\x07payload*\xc7\x01\n\x10LocalCtrlMsgType\x12\x1b\n\x17TypeCmdGetPropertyCount\x10\x00\x12\x1c\n\x18TypeRespGetPropertyCount\x10\x01\x12\x1c\n\x18TypeCmdGetPropertyValues\x10\x04\x12\x1d\n\x19TypeRespGetPropertyValues\x10\x05\x12\x1c\n\x18TypeCmdSetPropertyValues\x10\x06\x12\x1d\n\x19TypeRespSetPropertyValues\x10\x07\x62\x06proto3')
+  ,
+  dependencies=[constants__pb2.DESCRIPTOR,])
+
+_LOCALCTRLMSGTYPE = _descriptor.EnumDescriptor(
+  name='LocalCtrlMsgType',
+  full_name='LocalCtrlMsgType',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='TypeCmdGetPropertyCount', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TypeRespGetPropertyCount', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TypeCmdGetPropertyValues', index=2, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TypeRespGetPropertyValues', index=3, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TypeCmdSetPropertyValues', index=4, number=6,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='TypeRespSetPropertyValues', index=5, number=7,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=883,
+  serialized_end=1082,
+)
+_sym_db.RegisterEnumDescriptor(_LOCALCTRLMSGTYPE)
+
+LocalCtrlMsgType = enum_type_wrapper.EnumTypeWrapper(_LOCALCTRLMSGTYPE)
+TypeCmdGetPropertyCount = 0
+TypeRespGetPropertyCount = 1
+TypeCmdGetPropertyValues = 4
+TypeRespGetPropertyValues = 5
+TypeCmdSetPropertyValues = 6
+TypeRespSetPropertyValues = 7
+
+
+
+_CMDGETPROPERTYCOUNT = _descriptor.Descriptor(
+  name='CmdGetPropertyCount',
+  full_name='CmdGetPropertyCount',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=41,
+  serialized_end=62,
+)
+
+
+_RESPGETPROPERTYCOUNT = _descriptor.Descriptor(
+  name='RespGetPropertyCount',
+  full_name='RespGetPropertyCount',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='status', full_name='RespGetPropertyCount.status', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='count', full_name='RespGetPropertyCount.count', index=1,
+      number=2, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=64,
+  serialized_end=126,
+)
+
+
+_PROPERTYINFO = _descriptor.Descriptor(
+  name='PropertyInfo',
+  full_name='PropertyInfo',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='status', full_name='PropertyInfo.status', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='name', full_name='PropertyInfo.name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='type', full_name='PropertyInfo.type', index=2,
+      number=3, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='flags', full_name='PropertyInfo.flags', index=3,
+      number=4, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='PropertyInfo.value', index=4,
+      number=5, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b(""),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=128,
+  serialized_end=225,
+)
+
+
+_CMDGETPROPERTYVALUES = _descriptor.Descriptor(
+  name='CmdGetPropertyValues',
+  full_name='CmdGetPropertyValues',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='indices', full_name='CmdGetPropertyValues.indices', index=0,
+      number=1, type=13, cpp_type=3, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=227,
+  serialized_end=266,
+)
+
+
+_RESPGETPROPERTYVALUES = _descriptor.Descriptor(
+  name='RespGetPropertyValues',
+  full_name='RespGetPropertyValues',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='status', full_name='RespGetPropertyValues.status', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='props', full_name='RespGetPropertyValues.props', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=268,
+  serialized_end=346,
+)
+
+
+_PROPERTYVALUE = _descriptor.Descriptor(
+  name='PropertyValue',
+  full_name='PropertyValue',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='index', full_name='PropertyValue.index', index=0,
+      number=1, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='PropertyValue.value', index=1,
+      number=2, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b(""),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=348,
+  serialized_end=393,
+)
+
+
+_CMDSETPROPERTYVALUES = _descriptor.Descriptor(
+  name='CmdSetPropertyValues',
+  full_name='CmdSetPropertyValues',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='props', full_name='CmdSetPropertyValues.props', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=395,
+  serialized_end=448,
+)
+
+
+_RESPSETPROPERTYVALUES = _descriptor.Descriptor(
+  name='RespSetPropertyValues',
+  full_name='RespSetPropertyValues',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='status', full_name='RespSetPropertyValues.status', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=450,
+  serialized_end=498,
+)
+
+
+_LOCALCTRLMESSAGE = _descriptor.Descriptor(
+  name='LocalCtrlMessage',
+  full_name='LocalCtrlMessage',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='msg', full_name='LocalCtrlMessage.msg', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='cmd_get_prop_count', full_name='LocalCtrlMessage.cmd_get_prop_count', index=1,
+      number=10, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='resp_get_prop_count', full_name='LocalCtrlMessage.resp_get_prop_count', index=2,
+      number=11, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='cmd_get_prop_vals', full_name='LocalCtrlMessage.cmd_get_prop_vals', index=3,
+      number=12, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='resp_get_prop_vals', full_name='LocalCtrlMessage.resp_get_prop_vals', index=4,
+      number=13, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='cmd_set_prop_vals', full_name='LocalCtrlMessage.cmd_set_prop_vals', index=5,
+      number=14, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='resp_set_prop_vals', full_name='LocalCtrlMessage.resp_set_prop_vals', index=6,
+      number=15, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='payload', full_name='LocalCtrlMessage.payload',
+      index=0, containing_type=None, fields=[]),
+  ],
+  serialized_start=501,
+  serialized_end=880,
+)
+
+_RESPGETPROPERTYCOUNT.fields_by_name['status'].enum_type = constants__pb2._STATUS
+_PROPERTYINFO.fields_by_name['status'].enum_type = constants__pb2._STATUS
+_RESPGETPROPERTYVALUES.fields_by_name['status'].enum_type = constants__pb2._STATUS
+_RESPGETPROPERTYVALUES.fields_by_name['props'].message_type = _PROPERTYINFO
+_CMDSETPROPERTYVALUES.fields_by_name['props'].message_type = _PROPERTYVALUE
+_RESPSETPROPERTYVALUES.fields_by_name['status'].enum_type = constants__pb2._STATUS
+_LOCALCTRLMESSAGE.fields_by_name['msg'].enum_type = _LOCALCTRLMSGTYPE
+_LOCALCTRLMESSAGE.fields_by_name['cmd_get_prop_count'].message_type = _CMDGETPROPERTYCOUNT
+_LOCALCTRLMESSAGE.fields_by_name['resp_get_prop_count'].message_type = _RESPGETPROPERTYCOUNT
+_LOCALCTRLMESSAGE.fields_by_name['cmd_get_prop_vals'].message_type = _CMDGETPROPERTYVALUES
+_LOCALCTRLMESSAGE.fields_by_name['resp_get_prop_vals'].message_type = _RESPGETPROPERTYVALUES
+_LOCALCTRLMESSAGE.fields_by_name['cmd_set_prop_vals'].message_type = _CMDSETPROPERTYVALUES
+_LOCALCTRLMESSAGE.fields_by_name['resp_set_prop_vals'].message_type = _RESPSETPROPERTYVALUES
+_LOCALCTRLMESSAGE.oneofs_by_name['payload'].fields.append(
+  _LOCALCTRLMESSAGE.fields_by_name['cmd_get_prop_count'])
+_LOCALCTRLMESSAGE.fields_by_name['cmd_get_prop_count'].containing_oneof = _LOCALCTRLMESSAGE.oneofs_by_name['payload']
+_LOCALCTRLMESSAGE.oneofs_by_name['payload'].fields.append(
+  _LOCALCTRLMESSAGE.fields_by_name['resp_get_prop_count'])
+_LOCALCTRLMESSAGE.fields_by_name['resp_get_prop_count'].containing_oneof = _LOCALCTRLMESSAGE.oneofs_by_name['payload']
+_LOCALCTRLMESSAGE.oneofs_by_name['payload'].fields.append(
+  _LOCALCTRLMESSAGE.fields_by_name['cmd_get_prop_vals'])
+_LOCALCTRLMESSAGE.fields_by_name['cmd_get_prop_vals'].containing_oneof = _LOCALCTRLMESSAGE.oneofs_by_name['payload']
+_LOCALCTRLMESSAGE.oneofs_by_name['payload'].fields.append(
+  _LOCALCTRLMESSAGE.fields_by_name['resp_get_prop_vals'])
+_LOCALCTRLMESSAGE.fields_by_name['resp_get_prop_vals'].containing_oneof = _LOCALCTRLMESSAGE.oneofs_by_name['payload']
+_LOCALCTRLMESSAGE.oneofs_by_name['payload'].fields.append(
+  _LOCALCTRLMESSAGE.fields_by_name['cmd_set_prop_vals'])
+_LOCALCTRLMESSAGE.fields_by_name['cmd_set_prop_vals'].containing_oneof = _LOCALCTRLMESSAGE.oneofs_by_name['payload']
+_LOCALCTRLMESSAGE.oneofs_by_name['payload'].fields.append(
+  _LOCALCTRLMESSAGE.fields_by_name['resp_set_prop_vals'])
+_LOCALCTRLMESSAGE.fields_by_name['resp_set_prop_vals'].containing_oneof = _LOCALCTRLMESSAGE.oneofs_by_name['payload']
+DESCRIPTOR.message_types_by_name['CmdGetPropertyCount'] = _CMDGETPROPERTYCOUNT
+DESCRIPTOR.message_types_by_name['RespGetPropertyCount'] = _RESPGETPROPERTYCOUNT
+DESCRIPTOR.message_types_by_name['PropertyInfo'] = _PROPERTYINFO
+DESCRIPTOR.message_types_by_name['CmdGetPropertyValues'] = _CMDGETPROPERTYVALUES
+DESCRIPTOR.message_types_by_name['RespGetPropertyValues'] = _RESPGETPROPERTYVALUES
+DESCRIPTOR.message_types_by_name['PropertyValue'] = _PROPERTYVALUE
+DESCRIPTOR.message_types_by_name['CmdSetPropertyValues'] = _CMDSETPROPERTYVALUES
+DESCRIPTOR.message_types_by_name['RespSetPropertyValues'] = _RESPSETPROPERTYVALUES
+DESCRIPTOR.message_types_by_name['LocalCtrlMessage'] = _LOCALCTRLMESSAGE
+DESCRIPTOR.enum_types_by_name['LocalCtrlMsgType'] = _LOCALCTRLMSGTYPE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+CmdGetPropertyCount = _reflection.GeneratedProtocolMessageType('CmdGetPropertyCount', (_message.Message,), dict(
+  DESCRIPTOR = _CMDGETPROPERTYCOUNT,
+  __module__ = 'esp_local_ctrl_pb2'
+  # @@protoc_insertion_point(class_scope:CmdGetPropertyCount)
+  ))
+_sym_db.RegisterMessage(CmdGetPropertyCount)
+
+RespGetPropertyCount = _reflection.GeneratedProtocolMessageType('RespGetPropertyCount', (_message.Message,), dict(
+  DESCRIPTOR = _RESPGETPROPERTYCOUNT,
+  __module__ = 'esp_local_ctrl_pb2'
+  # @@protoc_insertion_point(class_scope:RespGetPropertyCount)
+  ))
+_sym_db.RegisterMessage(RespGetPropertyCount)
+
+PropertyInfo = _reflection.GeneratedProtocolMessageType('PropertyInfo', (_message.Message,), dict(
+  DESCRIPTOR = _PROPERTYINFO,
+  __module__ = 'esp_local_ctrl_pb2'
+  # @@protoc_insertion_point(class_scope:PropertyInfo)
+  ))
+_sym_db.RegisterMessage(PropertyInfo)
+
+CmdGetPropertyValues = _reflection.GeneratedProtocolMessageType('CmdGetPropertyValues', (_message.Message,), dict(
+  DESCRIPTOR = _CMDGETPROPERTYVALUES,
+  __module__ = 'esp_local_ctrl_pb2'
+  # @@protoc_insertion_point(class_scope:CmdGetPropertyValues)
+  ))
+_sym_db.RegisterMessage(CmdGetPropertyValues)
+
+RespGetPropertyValues = _reflection.GeneratedProtocolMessageType('RespGetPropertyValues', (_message.Message,), dict(
+  DESCRIPTOR = _RESPGETPROPERTYVALUES,
+  __module__ = 'esp_local_ctrl_pb2'
+  # @@protoc_insertion_point(class_scope:RespGetPropertyValues)
+  ))
+_sym_db.RegisterMessage(RespGetPropertyValues)
+
+PropertyValue = _reflection.GeneratedProtocolMessageType('PropertyValue', (_message.Message,), dict(
+  DESCRIPTOR = _PROPERTYVALUE,
+  __module__ = 'esp_local_ctrl_pb2'
+  # @@protoc_insertion_point(class_scope:PropertyValue)
+  ))
+_sym_db.RegisterMessage(PropertyValue)
+
+CmdSetPropertyValues = _reflection.GeneratedProtocolMessageType('CmdSetPropertyValues', (_message.Message,), dict(
+  DESCRIPTOR = _CMDSETPROPERTYVALUES,
+  __module__ = 'esp_local_ctrl_pb2'
+  # @@protoc_insertion_point(class_scope:CmdSetPropertyValues)
+  ))
+_sym_db.RegisterMessage(CmdSetPropertyValues)
+
+RespSetPropertyValues = _reflection.GeneratedProtocolMessageType('RespSetPropertyValues', (_message.Message,), dict(
+  DESCRIPTOR = _RESPSETPROPERTYVALUES,
+  __module__ = 'esp_local_ctrl_pb2'
+  # @@protoc_insertion_point(class_scope:RespSetPropertyValues)
+  ))
+_sym_db.RegisterMessage(RespSetPropertyValues)
+
+LocalCtrlMessage = _reflection.GeneratedProtocolMessageType('LocalCtrlMessage', (_message.Message,), dict(
+  DESCRIPTOR = _LOCALCTRLMESSAGE,
+  __module__ = 'esp_local_ctrl_pb2'
+  # @@protoc_insertion_point(class_scope:LocalCtrlMessage)
+  ))
+_sym_db.RegisterMessage(LocalCtrlMessage)
+
+
+# @@protoc_insertion_point(module_scope)

+ 417 - 0
components/esp_local_ctrl/src/esp_local_ctrl.c

@@ -0,0 +1,417 @@
+// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdio.h>
+#include <string.h>
+#include <esp_err.h>
+#include <esp_log.h>
+
+#include <protocomm.h>
+#include <protocomm_security0.h>
+
+#include <esp_local_ctrl.h>
+#include "esp_local_ctrl_priv.h"
+#include "esp_local_ctrl.pb-c.h"
+
+#define ESP_LOCAL_CTRL_VERSION "v1.0"
+
+struct inst_ctx {
+    protocomm_t *pc;
+    esp_local_ctrl_config_t config;
+    esp_local_ctrl_prop_t **props;
+    size_t props_count;
+};
+
+struct inst_ctx *local_ctrl_inst_ctx;
+
+static const char *TAG = "esp_local_ctrl";
+
+esp_err_t esp_local_ctrl_start(const esp_local_ctrl_config_t *config)
+{
+    esp_err_t ret;
+
+    if (!config) {
+        ESP_LOGE(TAG, "NULL configuration provided");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (!config->transport) {
+        ESP_LOGE(TAG, "No transport provided");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (config->max_properties == 0) {
+        ESP_LOGE(TAG, "max_properties must be greater than 0");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (!config->handlers.get_prop_values ||
+        !config->handlers.set_prop_values) {
+        ESP_LOGE(TAG, "Handlers cannot be null");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    if (local_ctrl_inst_ctx) {
+        ESP_LOGW(TAG, "Service already active");
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    local_ctrl_inst_ctx = calloc(1, sizeof(struct inst_ctx));
+    if (!local_ctrl_inst_ctx) {
+        ESP_LOGE(TAG, "Failed to allocate memory for instance");
+        return ESP_ERR_NO_MEM;
+    }
+    memcpy(&local_ctrl_inst_ctx->config, config, sizeof(local_ctrl_inst_ctx->config));
+
+    local_ctrl_inst_ctx->props = calloc(local_ctrl_inst_ctx->config.max_properties,
+                                        sizeof(esp_local_ctrl_prop_t *));
+    if (!local_ctrl_inst_ctx->props) {
+        ESP_LOGE(TAG, "Failed to allocate memory for properties");
+        free(local_ctrl_inst_ctx);
+        local_ctrl_inst_ctx = NULL;
+        return ESP_ERR_NO_MEM;
+    }
+
+    /* Since the config structure will be different for different transport modes, each transport may
+     * implement a `copy_config()` function, which accepts a configuration structure as input and
+     * creates a copy of that, which can be kept in the context structure of the `esp_local_ctrl` instance.
+     * This copy can be later be freed using `free_config()` */
+    if (config->transport->copy_config) {
+        ret = config->transport->copy_config(&local_ctrl_inst_ctx->config.transport_config,
+                                             &config->transport_config);
+        if (ret != ESP_OK) {
+            esp_local_ctrl_stop();
+            return ret;
+        }
+    }
+
+    /* For a selected transport mode, endpoints may need to be declared prior to starting the
+     * `esp_local_ctrl` service, e.g. in case of BLE. By declaration it means that the transport layer
+     * allocates some resources for an endpoint, and later, after service has started, a handler
+     * is assigned for that endpoint */
+    if (config->transport->declare_ep) {
+        /* UUIDs are 16bit unique IDs for each endpoint. This may or may not be relevant for
+         * a chosen transport. We reserve all values from FF50 to FFFF for the internal endpoints.
+         * The remaining endpoints can be used by the application for its own custom endpoints */
+        uint16_t start_uuid = 0xFF50;
+        ret = config->transport->declare_ep(&local_ctrl_inst_ctx->config.transport_config,
+                                            "esp_local_ctrl/version", start_uuid++);
+        if (ret != ESP_OK) {
+            esp_local_ctrl_stop();
+            return ret;
+        }
+        ret = config->transport->declare_ep(&local_ctrl_inst_ctx->config.transport_config,
+                                            "esp_local_ctrl/session", start_uuid++);
+        if (ret != ESP_OK) {
+            esp_local_ctrl_stop();
+            return ret;
+        }
+        ret = config->transport->declare_ep(&local_ctrl_inst_ctx->config.transport_config,
+                                            "esp_local_ctrl/control", start_uuid++);
+        if (ret != ESP_OK) {
+            esp_local_ctrl_stop();
+            return ret;
+        }
+    }
+
+    local_ctrl_inst_ctx->pc = protocomm_new();
+    if (!local_ctrl_inst_ctx->pc) {
+        ESP_LOGE(TAG, "Failed to create new protocomm instance");
+        esp_local_ctrl_stop();
+        return ESP_FAIL;
+    }
+
+    if (config->transport->start_service) {
+        ret = config->transport->start_service(local_ctrl_inst_ctx->pc,
+                                               &local_ctrl_inst_ctx->config.transport_config);
+        if (ret != ESP_OK) {
+            esp_local_ctrl_stop();
+            return ret;
+        }
+    }
+
+    ret = protocomm_set_version(local_ctrl_inst_ctx->pc, "esp_local_ctrl/version",
+                                ESP_LOCAL_CTRL_VERSION);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "Failed to set version endpoint");
+        esp_local_ctrl_stop();
+        return ret;
+    }
+
+    ret = protocomm_set_security(local_ctrl_inst_ctx->pc, "esp_local_ctrl/session",
+                                 &protocomm_security0, NULL);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "Failed to set session endpoint");
+        esp_local_ctrl_stop();
+        return ret;
+    }
+
+    ret = protocomm_add_endpoint(local_ctrl_inst_ctx->pc, "esp_local_ctrl/control",
+                                 esp_local_ctrl_data_handler, NULL);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "Failed to set control endpoint");
+        esp_local_ctrl_stop();
+        return ret;
+    }
+    return ESP_OK;
+}
+
+esp_err_t esp_local_ctrl_stop(void)
+{
+    if (local_ctrl_inst_ctx) {
+        if (local_ctrl_inst_ctx->config.transport->free_config) {
+            local_ctrl_inst_ctx->config.transport->free_config(&local_ctrl_inst_ctx->config.transport_config);
+        }
+        if (local_ctrl_inst_ctx->pc) {
+            if (local_ctrl_inst_ctx->config.transport->stop_service) {
+                local_ctrl_inst_ctx->config.transport->stop_service(local_ctrl_inst_ctx->pc);
+            }
+            protocomm_delete(local_ctrl_inst_ctx->pc);
+        }
+        if (local_ctrl_inst_ctx->config.handlers.usr_ctx_free_fn) {
+            local_ctrl_inst_ctx->config.handlers.usr_ctx_free_fn(
+                local_ctrl_inst_ctx->config.handlers.usr_ctx);
+        }
+
+        /* Iterate through all properties one by one and free them */
+        for (uint32_t i = 0; i < local_ctrl_inst_ctx->config.max_properties; i++) {
+            if (local_ctrl_inst_ctx->props[i] == NULL) {
+                continue;
+            }
+            /* Release memory allocated for property data */
+            free(local_ctrl_inst_ctx->props[i]->name);
+            if (local_ctrl_inst_ctx->props[i]->ctx_free_fn) {
+                local_ctrl_inst_ctx->props[i]->ctx_free_fn(local_ctrl_inst_ctx->props[i]->ctx);
+            }
+            free(local_ctrl_inst_ctx->props[i]);
+        }
+        free(local_ctrl_inst_ctx->props);
+        free(local_ctrl_inst_ctx);
+        local_ctrl_inst_ctx = NULL;
+    }
+    return ESP_OK;
+}
+
+static int esp_local_ctrl_get_property_index(const char *name)
+{
+    if (!local_ctrl_inst_ctx || !name) {
+        return -1;
+    }
+
+    /* Iterate through all properties one by one
+     * and find the one with matching name */
+    for (uint32_t i = 0; i < local_ctrl_inst_ctx->props_count; i++) {
+        if (strcmp(local_ctrl_inst_ctx->props[i]->name, name) == 0) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+esp_err_t esp_local_ctrl_add_property(const esp_local_ctrl_prop_t *prop)
+{
+    if (!local_ctrl_inst_ctx) {
+        ESP_LOGE(TAG, "Service not running");
+        return ESP_ERR_INVALID_STATE;
+    }
+    if (!prop || !prop->name) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    if (esp_local_ctrl_get_property_index(prop->name) >= 0) {
+        ESP_LOGE(TAG, "Property with name %s exists", prop->name);
+        return ESP_ERR_INVALID_STATE;
+    }
+
+    if (local_ctrl_inst_ctx->config.max_properties
+        == local_ctrl_inst_ctx->props_count) {
+        ESP_LOGE(TAG, "Max properties limit reached. Cannot add property %s", prop->name);
+        return ESP_ERR_NO_MEM;
+    }
+
+    uint32_t i = local_ctrl_inst_ctx->props_count;
+    local_ctrl_inst_ctx->props[i] = calloc(1, sizeof(esp_local_ctrl_prop_t));
+    if (!local_ctrl_inst_ctx->props[i]) {
+        ESP_LOGE(TAG, "Failed to allocate memory for new property %s", prop->name);
+        return ESP_ERR_NO_MEM;
+    }
+    local_ctrl_inst_ctx->props[i]->name = strdup(prop->name);
+    if (!local_ctrl_inst_ctx->props[i]->name) {
+        ESP_LOGE(TAG, "Failed to allocate memory for property data %s", prop->name);
+        free(local_ctrl_inst_ctx->props[i]);
+        local_ctrl_inst_ctx->props[i] = NULL;
+        return ESP_ERR_NO_MEM;
+    }
+    local_ctrl_inst_ctx->props[i]->type  = prop->type;
+    local_ctrl_inst_ctx->props[i]->size  = prop->size;
+    local_ctrl_inst_ctx->props[i]->flags = prop->flags;
+    local_ctrl_inst_ctx->props[i]->ctx   = prop->ctx;
+    local_ctrl_inst_ctx->props[i]->ctx_free_fn = prop->ctx_free_fn;
+    local_ctrl_inst_ctx->props_count++;
+    return ESP_OK;
+}
+
+
+esp_err_t esp_local_ctrl_remove_property(const char *name)
+{
+    int idx = esp_local_ctrl_get_property_index(name);
+    if (idx < 0) {
+        ESP_LOGE(TAG, "Property %s not found", name);
+        return ESP_ERR_NOT_FOUND;
+    }
+
+    /* Release memory allocated for property data */
+    if (local_ctrl_inst_ctx->props[idx]->ctx_free_fn) {
+        local_ctrl_inst_ctx->props[idx]->ctx_free_fn(
+            local_ctrl_inst_ctx->props[idx]->ctx);
+    }
+    free(local_ctrl_inst_ctx->props[idx]->name);
+    free(local_ctrl_inst_ctx->props[idx]);
+    local_ctrl_inst_ctx->props[idx++] = NULL;
+
+    /* Move the following properties forward, so that there is
+     * no empty space between two properties */
+    for (uint32_t i = idx; i < local_ctrl_inst_ctx->props_count; i++) {
+        if (local_ctrl_inst_ctx->props[i] == NULL) {
+            break;
+        }
+        local_ctrl_inst_ctx->props[i-1] = local_ctrl_inst_ctx->props[i];
+    }
+    local_ctrl_inst_ctx->props_count--;
+    return ESP_OK;
+}
+
+const esp_local_ctrl_prop_t *esp_local_ctrl_get_property(const char *name)
+{
+    int idx = esp_local_ctrl_get_property_index(name);
+    if (idx < 0) {
+        ESP_LOGE(TAG, "Property %s not found", name);
+        return NULL;
+    }
+
+    return local_ctrl_inst_ctx->props[idx];
+}
+
+esp_err_t esp_local_ctrl_get_prop_count(size_t *count)
+{
+    if (!local_ctrl_inst_ctx) {
+        ESP_LOGE(TAG, "Service not running");
+        return ESP_ERR_INVALID_STATE;
+    }
+    if (!count) {
+        return ESP_ERR_INVALID_ARG;
+    }
+    *count = local_ctrl_inst_ctx->props_count;
+    return ESP_OK;
+}
+
+esp_err_t esp_local_ctrl_get_prop_values(size_t total_indices, uint32_t *indices,
+                                         esp_local_ctrl_prop_t *props,
+                                         esp_local_ctrl_prop_val_t *values)
+{
+    if (!local_ctrl_inst_ctx) {
+        ESP_LOGE(TAG, "Service not running");
+        return ESP_ERR_INVALID_STATE;
+    }
+    if (!indices || !props || !values) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    /* Convert indices to names */
+    for (size_t i = 0; i < total_indices; i++) {
+        if (indices[i] >= local_ctrl_inst_ctx->props_count) {
+            ESP_LOGE(TAG, "Invalid property index %d", indices[i]);
+            return ESP_ERR_INVALID_ARG;
+        }
+        props[i].name  = local_ctrl_inst_ctx->props[indices[i]]->name;
+        props[i].type  = local_ctrl_inst_ctx->props[indices[i]]->type;
+        props[i].flags = local_ctrl_inst_ctx->props[indices[i]]->flags;
+        props[i].size  = local_ctrl_inst_ctx->props[indices[i]]->size;
+        props[i].ctx   = local_ctrl_inst_ctx->props[indices[i]]->ctx;
+    }
+
+    esp_local_ctrl_handlers_t *h = &local_ctrl_inst_ctx->config.handlers;
+    esp_err_t ret = h->get_prop_values(total_indices, props, values, h->usr_ctx);
+
+    /* Properties with fixed sizes need to be checked */
+    for (size_t i = 0; i < total_indices; i++) {
+        if (local_ctrl_inst_ctx->props[indices[i]]->size != 0) {
+            values[i].size = local_ctrl_inst_ctx->props[indices[i]]->size;
+        }
+    }
+    return ret;
+}
+
+esp_err_t esp_local_ctrl_set_prop_values(size_t total_indices, uint32_t *indices,
+                                         const esp_local_ctrl_prop_val_t *values)
+{
+   if (!local_ctrl_inst_ctx) {
+        ESP_LOGE(TAG, "Service not running");
+        return ESP_ERR_INVALID_STATE;
+    }
+    if (!indices || !values) {
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    esp_local_ctrl_prop_t *props = calloc(total_indices,
+                                          sizeof(esp_local_ctrl_prop_t));
+    if (!props) {
+        ESP_LOGE(TAG, "Unable to allocate memory for properties array");
+        return ESP_ERR_NO_MEM;
+    }
+    for (size_t i = 0; i < total_indices; i++) {
+        if (indices[i] >= local_ctrl_inst_ctx->props_count) {
+            ESP_LOGE(TAG, "Invalid property index %d", indices[i]);
+            free(props);
+            return ESP_ERR_INVALID_ARG;
+        }
+
+        /* Properties with fixed sizes need to be checked */
+        if ((local_ctrl_inst_ctx->props[indices[i]]->size != values[i].size) &&
+            (local_ctrl_inst_ctx->props[indices[i]]->size != 0)) {
+            ESP_LOGE(TAG, "Invalid property size %d. Expected %d",
+                     values[i].size, local_ctrl_inst_ctx->props[indices[i]]->size);
+            free(props);
+            return ESP_ERR_INVALID_ARG;
+        }
+
+        props[i].name  = local_ctrl_inst_ctx->props[indices[i]]->name;
+        props[i].type  = local_ctrl_inst_ctx->props[indices[i]]->type;
+        props[i].flags = local_ctrl_inst_ctx->props[indices[i]]->flags;
+        props[i].size  = local_ctrl_inst_ctx->props[indices[i]]->size;
+        props[i].ctx   = local_ctrl_inst_ctx->props[indices[i]]->ctx;
+    }
+
+    esp_local_ctrl_handlers_t *h = &local_ctrl_inst_ctx->config.handlers;
+    esp_err_t ret = h->set_prop_values(total_indices, props, values, h->usr_ctx);
+
+    free(props);
+    return ret;
+}
+
+esp_err_t esp_local_ctrl_set_handler(const char *ep_name,
+                                     protocomm_req_handler_t handler,
+                                     void *priv_data)
+{
+    esp_err_t ret = ESP_ERR_INVALID_STATE;
+
+    if (local_ctrl_inst_ctx) {
+        ret = protocomm_add_endpoint(local_ctrl_inst_ctx->pc, ep_name,
+                                     handler, priv_data);
+    }
+
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "Failed to register endpoint handler");
+    }
+    return ret;
+}

+ 298 - 0
components/esp_local_ctrl/src/esp_local_ctrl_handler.c

@@ -0,0 +1,298 @@
+// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdio.h>
+#include <string.h>
+#include <esp_err.h>
+#include <esp_log.h>
+
+#include "esp_local_ctrl.h"
+#include "esp_local_ctrl_priv.h"
+#include "esp_local_ctrl.pb-c.h"
+
+#define SAFE_ALLOCATION(type, var)                  \
+    type *var = (type *) malloc(sizeof(type));      \
+    if (!var) {                                     \
+        ESP_LOGE(TAG, "Error allocating memory");   \
+        return ESP_ERR_NO_MEM;                      \
+    }
+
+static const char* TAG = "esp_local_ctrl_handler";
+
+typedef struct esp_local_ctrl_cmd {
+    int cmd_num;
+    esp_err_t (*command_handler)(LocalCtrlMessage *req,
+                                 LocalCtrlMessage *resp, void **ctx);
+} esp_local_ctrl_cmd_t;
+
+static esp_err_t cmd_get_prop_count_handler(LocalCtrlMessage *req,
+                                            LocalCtrlMessage *resp, void **ctx);
+
+static esp_err_t cmd_get_prop_vals_handler(LocalCtrlMessage *req,
+                                           LocalCtrlMessage *resp, void **ctx);
+
+static esp_err_t cmd_set_prop_vals_handler(LocalCtrlMessage *req,
+                                           LocalCtrlMessage *resp, void **ctx);
+
+static esp_local_ctrl_cmd_t cmd_table[] = {
+    {
+        .cmd_num = LOCAL_CTRL_MSG_TYPE__TypeCmdGetPropertyCount,
+        .command_handler = cmd_get_prop_count_handler
+    },
+    {
+        .cmd_num = LOCAL_CTRL_MSG_TYPE__TypeCmdGetPropertyValues,
+        .command_handler = cmd_get_prop_vals_handler
+    },
+    {
+        .cmd_num = LOCAL_CTRL_MSG_TYPE__TypeCmdSetPropertyValues,
+        .command_handler = cmd_set_prop_vals_handler
+    }
+};
+
+static uint16_t err_to_status(esp_err_t err)
+{
+    uint16_t status;
+    switch (err) {
+        case ESP_OK:
+            status = STATUS__Success;
+            break;
+        case ESP_ERR_INVALID_ARG:
+            status = STATUS__InvalidArgument;
+            break;
+        case ESP_ERR_INVALID_STATE:
+            status = STATUS__InvalidProto;
+            break;
+        default:
+            status = STATUS__InternalError;
+    }
+    return status;
+}
+
+static esp_err_t cmd_get_prop_count_handler(LocalCtrlMessage *req,
+                                            LocalCtrlMessage *resp, void **ctx)
+{
+    SAFE_ALLOCATION(RespGetPropertyCount, resp_payload);
+    resp_get_property_count__init(resp_payload);
+
+    size_t prop_count = 0;
+    resp_payload->status = err_to_status(esp_local_ctrl_get_prop_count(&prop_count));
+    resp_payload->count = prop_count;
+    resp->payload_case = LOCAL_CTRL_MESSAGE__PAYLOAD_RESP_GET_PROP_COUNT;
+    resp->resp_get_prop_count = resp_payload;
+    ESP_LOGD(TAG, "Got properties count %d", prop_count);
+    return ESP_OK;
+}
+
+typedef void (*prop_val_free_fn_t)(void *val);
+
+static esp_err_t cmd_get_prop_vals_handler(LocalCtrlMessage *req,
+                                           LocalCtrlMessage *resp, void **ctx)
+{
+    SAFE_ALLOCATION(RespGetPropertyValues, resp_payload);
+    resp_get_property_values__init(resp_payload);
+
+    esp_local_ctrl_prop_val_t *vals = calloc(req->cmd_get_prop_vals->n_indices,
+                                             sizeof(esp_local_ctrl_prop_val_t));
+    esp_local_ctrl_prop_t *descs = calloc(req->cmd_get_prop_vals->n_indices,
+                                          sizeof(esp_local_ctrl_prop_t));
+    prop_val_free_fn_t *free_fns = calloc(req->cmd_get_prop_vals->n_indices,
+                                          sizeof(prop_val_free_fn_t));
+    resp_payload->props = calloc(req->cmd_get_prop_vals->n_indices,
+                                 sizeof(PropertyInfo *));
+    if (!vals || !descs || !free_fns || !resp_payload->props) {
+        ESP_LOGE(TAG, "Failed to allocate memory for getting values");
+        free(vals);
+        free(descs);
+        free(free_fns);
+        free(resp_payload->props);
+        free(resp_payload);
+        return ESP_ERR_NO_MEM;
+    }
+
+    esp_err_t ret = esp_local_ctrl_get_prop_values(req->cmd_get_prop_vals->n_indices,
+                                                   req->cmd_get_prop_vals->indices,
+                                                   descs, vals);
+    resp_payload->status = err_to_status(ret);
+    if (ret == ESP_OK) {
+        resp_payload->n_props = 0;
+        for (size_t i = 0; i < req->cmd_get_prop_vals->n_indices; i++) {
+            resp_payload->props[i] = malloc(sizeof(PropertyInfo));
+            if (!resp_payload->props[i]) {
+                resp_payload->status = STATUS__InternalError;
+                break;
+            }
+            resp_payload->n_props++;
+            property_info__init(resp_payload->props[i]);
+            resp_payload->props[i]->name  = descs[i].name;
+            resp_payload->props[i]->type  = descs[i].type;
+            resp_payload->props[i]->flags = descs[i].flags;
+            resp_payload->props[i]->value.data = vals[i].data;
+            resp_payload->props[i]->value.len  = vals[i].size;
+            free_fns[i] = vals[i].free_fn;
+        }
+    }
+    resp->payload_case = LOCAL_CTRL_MESSAGE__PAYLOAD_RESP_GET_PROP_VALS;
+    resp->resp_get_prop_vals = resp_payload;
+    (*ctx) = (void *)free_fns;
+    free(vals);
+    free(descs);
+
+    /* Unless it's a fatal error, always return ESP_OK, otherwise
+     * the underlying connection will be closed by protocomm */
+    return ESP_OK;
+}
+
+static esp_err_t cmd_set_prop_vals_handler(LocalCtrlMessage *req,
+                                           LocalCtrlMessage *resp, void **ctx)
+{
+    SAFE_ALLOCATION(RespSetPropertyValues, resp_payload);
+    resp_set_property_values__init(resp_payload);
+
+    uint32_t *idxs = calloc(req->cmd_set_prop_vals->n_props, sizeof(uint32_t));
+    esp_local_ctrl_prop_val_t *vals = calloc(req->cmd_set_prop_vals->n_props,
+                                             sizeof(esp_local_ctrl_prop_val_t));
+    if (!idxs || !vals) {
+        ESP_LOGE(TAG, "Failed to allocate memory for setting values");
+        free(idxs);
+        free(vals);
+        return ESP_ERR_NO_MEM;
+    }
+    for (size_t i = 0; i < req->cmd_set_prop_vals->n_props; i++) {
+        idxs[i]      = req->cmd_set_prop_vals->props[i]->index;
+        vals[i].data = req->cmd_set_prop_vals->props[i]->value.data;
+        vals[i].size = req->cmd_set_prop_vals->props[i]->value.len;
+    }
+
+    esp_err_t ret = esp_local_ctrl_set_prop_values(req->cmd_set_prop_vals->n_props,
+                                                   idxs, vals);
+    resp_payload->status = err_to_status(ret);
+    resp->payload_case = LOCAL_CTRL_MESSAGE__PAYLOAD_RESP_SET_PROP_VALS;
+    resp->resp_set_prop_vals = resp_payload;
+    free(idxs);
+    free(vals);
+
+    /* Unless it's a fatal error, always return ESP_OK, otherwise
+     * the underlying connection will be closed by protocomm */
+    return ESP_OK;
+}
+
+static int lookup_cmd_handler(int cmd_id)
+{
+    int i;
+
+    for (i = 0; i < sizeof(cmd_table)/sizeof(esp_local_ctrl_cmd_t); i++) {
+        if (cmd_table[i].cmd_num == cmd_id) {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+static void esp_local_ctrl_command_cleanup(LocalCtrlMessage *resp, void **ctx)
+{
+    if (!resp) {
+        return;
+    }
+
+    switch (resp->msg) {
+        case LOCAL_CTRL_MSG_TYPE__TypeRespGetPropertyCount:
+            free(resp->resp_get_prop_count);
+            break;
+        case LOCAL_CTRL_MSG_TYPE__TypeRespGetPropertyValues: {
+                if (resp->resp_get_prop_vals) {
+                    prop_val_free_fn_t *free_fns = (prop_val_free_fn_t *)(*ctx);
+                    for (size_t i = 0; i < resp->resp_get_prop_vals->n_props; i++) {
+                        if (free_fns[i]) {
+                            free_fns[i](resp->resp_get_prop_vals->props[i]->value.data);
+                        }
+                        free(resp->resp_get_prop_vals->props[i]);
+                    }
+                    free(free_fns);
+                    free(resp->resp_get_prop_vals->props);
+                    free(resp->resp_get_prop_vals);
+                }
+            }
+            break;
+        case LOCAL_CTRL_MSG_TYPE__TypeRespSetPropertyValues:
+            free(resp->resp_set_prop_vals);
+            break;
+        default:
+            ESP_LOGE(TAG, "Unsupported response type in cleanup_handler");
+    }
+    return;
+}
+
+static esp_err_t esp_local_ctrl_command_dispatcher(LocalCtrlMessage *req,
+                                                   LocalCtrlMessage *resp,
+                                                   void **ctx)
+{
+    int cmd_index = lookup_cmd_handler(req->msg);
+    if (cmd_index < 0) {
+        ESP_LOGE(TAG, "Invalid command handler lookup");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    esp_err_t ret = cmd_table[cmd_index].command_handler(req, resp, ctx);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "Error executing command handler");
+        return ret;
+    }
+
+    return ESP_OK;
+}
+
+esp_err_t esp_local_ctrl_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen,
+                                      uint8_t **outbuf, ssize_t *outlen, void *priv_data)
+{
+    void *temp_ctx = NULL;
+    LocalCtrlMessage *req = local_ctrl_message__unpack(NULL, inlen, inbuf);
+    if (!req) {
+        ESP_LOGE(TAG, "Unable to unpack payload data");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    LocalCtrlMessage resp;
+    local_ctrl_message__init(&resp);
+    resp.msg = req->msg + 1; /* Response is request + 1 */
+
+    esp_err_t ret = esp_local_ctrl_command_dispatcher(req, &resp, &temp_ctx);
+    if (ret != ESP_OK) {
+        ESP_LOGE(TAG, "command dispatcher failed");
+        esp_local_ctrl_command_cleanup(&resp, &temp_ctx);
+        local_ctrl_message__free_unpacked(req, NULL);
+        return ESP_FAIL;
+    }
+
+    local_ctrl_message__free_unpacked(req, NULL);
+
+    *outlen = local_ctrl_message__get_packed_size(&resp);
+    if (*outlen <= 0) {
+        ESP_LOGE(TAG, "Invalid encoding for response");
+        esp_local_ctrl_command_cleanup(&resp, &temp_ctx);
+        return ESP_FAIL;
+    }
+
+    *outbuf = (uint8_t *) malloc(*outlen);
+    if (!*outbuf) {
+        ESP_LOGE(TAG, "System out of memory");
+        esp_local_ctrl_command_cleanup(&resp, &temp_ctx);
+        return ESP_ERR_NO_MEM;
+    }
+
+    local_ctrl_message__pack(&resp, *outbuf);
+    esp_local_ctrl_command_cleanup(&resp, &temp_ctx);
+    ESP_LOG_BUFFER_HEX_LEVEL(TAG, *outbuf, *outlen, ESP_LOG_DEBUG);
+    return ESP_OK;
+}

+ 153 - 0
components/esp_local_ctrl/src/esp_local_ctrl_priv.h

@@ -0,0 +1,153 @@
+// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <esp_err.h>
+#include <protocomm.h>
+#include <esp_local_ctrl.h>
+
+/**
+ * @brief   `esp_local_ctrl` transport specific structure
+ *
+ * Every supported transport layer should have the following functions
+ * implemented for starting, stopping and configuring a protocomm service
+ */
+struct esp_local_ctrl_transport {
+    /**
+     * Handler for starting a protocomm service as per specified configuration
+     */
+    esp_err_t (*start_service) (protocomm_t *pc,
+                                const esp_local_ctrl_transport_config_t *config);
+
+    /**
+     * Handler for stopping a protocomm service
+     */
+    void (*stop_service) (protocomm_t *pc);
+
+    /**
+     * Handler for creating a copy of the transport specific configuration
+     */
+    esp_err_t (*copy_config) (esp_local_ctrl_transport_config_t *dest_config,
+                              const esp_local_ctrl_transport_config_t *src_config);
+
+    /**
+     * Handler for allocating resources corresponding to a protocomm endpoint.
+     * Usually when adding a new endpoint `protocomm_endpoint_add()` API is used,
+     * but the transport layer may need to perform resource allocation for
+     * each endpoint, prior to starting the protocomm instance. This handler
+     * is useful in that case, as it is called before `start_service()`.
+     */
+    esp_err_t (*declare_ep) (esp_local_ctrl_transport_config_t *config,
+                             const char *ep_name, uint16_t ep_uuid);
+
+    /**
+     * Handler for freeing a transport specific configuration
+     */
+    void (*free_config) (esp_local_ctrl_transport_config_t *config);
+};
+
+/**
+ * @brief   Protocomm handler for `esp_local_ctrl`
+ *
+ * This is the handler which is responsible for processing incoming requests
+ * over a protocomm channel, then invokes one of the following functions
+ * depending upon the request type:
+ * - `esp_local_ctrl_get_prop_count()`
+ * - `esp_local_ctrl_get_prop_values()`
+ * -` esp_local_ctrl_set_prop_values()`
+ * The output of the above functions are used to form the response messages
+ * corresponding to request types. The formed response messages are packed and
+ * sent back via the protocomm channel.
+ *
+ * @param[in]  session_id   A number to identify an ongoing session between
+ *                          device and client
+ * @param[in]  inbuf        Buffer which holds serialized / packed request data
+ * @param[in]  inlen        Length of input buffer
+ * @param[out] outbuf       Buffer which holds serialized / packed response data
+ * @param[out] outlen       Length of output buffer
+ * @param[in]  priv_data    Private data associated with `esp_local_ctrl` endpoint
+ *
+ * @return
+ *  - ESP_OK      : Success
+ *  - ESP_FAIL    : Failure
+ */
+esp_err_t esp_local_ctrl_data_handler(uint32_t session_id, const uint8_t *inbuf, ssize_t inlen,
+                                      uint8_t **outbuf, ssize_t *outlen, void *priv_data);
+
+/**
+ * @brief   Use this for obtaining total number of properties registered
+ *          with `esp_local_ctrl` service
+ *
+ * @param[out] count   Pointer to variable where the result is to be stored
+ *
+ * @return
+ *  - ESP_OK      : Success
+ *  - ESP_FAIL    : Failure
+ */
+esp_err_t esp_local_ctrl_get_prop_count(size_t *count);
+
+/**
+ * @brief   Get descriptions and values of multiple properties at the same time.
+ *          The properties are requested by indices. This internally calls the
+ *          `get_prop_values` handler specified in the `esp_local_ctrl_handlers_t`
+ *          structure. Since `get_prop_values` accepts property structure, the
+ *          indices are first converted to the corresponding `esp_local_ctrl_prop_t`
+ *          internally.
+ *
+ * @param[in]  total_indices   The number of elements in the `indices` array argument
+ * @param[in]  indices         An array of indices, that specify which properties to get
+ * @param[out] props           A pre-allocated array of empty property structures, elements of
+ *                             which are to be populated with names, types and flags of those
+ *                             properties which correspond to the provided indices
+ * @param[out] values          A pre-allocated array of empty value structures, elements of
+ *                             which are to be populated with values and sizes of those
+ *                             properties which correspond to the provided indices
+ *
+ * @return
+ *  - ESP_OK      : Success
+ *  - ESP_FAIL    : Failure
+ */
+esp_err_t esp_local_ctrl_get_prop_values(size_t total_indices, uint32_t *indices,
+                                         esp_local_ctrl_prop_t *props,
+                                         esp_local_ctrl_prop_val_t *values);
+
+/**
+ * @brief   Set values of multiple properties at the same time. The properties to
+ *          set are specified by indices. This internally calls the `set_prop_values`
+ *          handler specified in the `esp_local_ctrl_handlers_t` structure. Since
+ *          `set_prop_values` accepts property structures, the indices are first
+ *          converted to the corresponding `esp_local_ctrl_prop_t` internally.
+ *
+ * @param[in] total_indices   The number of elements in the `indices` array argument
+ * @param[in] indices         An array of indices, that specify which properties to set
+ * @param[in] values          A array of values. Every value should have the correct
+ *                            size, if it is for setting a fixed size property, else
+ *                            error will be generated and none of the properties will
+ *                            be set to any of the given values
+ *
+ * @return
+ *  - ESP_OK      : Success
+ *  - ESP_FAIL    : Failure
+ */
+esp_err_t esp_local_ctrl_set_prop_values(size_t total_indices, uint32_t *indices,
+                                         const esp_local_ctrl_prop_val_t *values);
+
+#ifdef __cplusplus
+}
+#endif

+ 140 - 0
components/esp_local_ctrl/src/esp_local_ctrl_transport_ble.c

@@ -0,0 +1,140 @@
+// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdio.h>
+#include <string.h>
+#include <esp_err.h>
+#include <esp_log.h>
+
+#include <protocomm_ble.h>
+#include <esp_local_ctrl.h>
+
+#include "esp_local_ctrl_priv.h"
+
+#define LOCAL_CTRL_VERSION "v1.0"
+
+static const char *TAG = "esp_local_ctrl_transport_ble";
+
+static esp_err_t start_ble_transport(protocomm_t *pc, const esp_local_ctrl_transport_config_t *config)
+{
+    if (!config || !config->ble) {
+        ESP_LOGE(TAG, "NULL configuration provided");
+        return ESP_ERR_INVALID_ARG;
+    }
+    return protocomm_ble_start(pc, config->ble);
+}
+
+static void stop_ble_transport(protocomm_t *pc)
+{
+    protocomm_ble_stop(pc);
+}
+
+static esp_err_t copy_ble_config(esp_local_ctrl_transport_config_t *dest_config, const esp_local_ctrl_transport_config_t *src_config)
+{
+    if (!dest_config || !src_config || !src_config->ble) {
+        ESP_LOGE(TAG, "NULL arguments provided");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dest_config->ble = calloc(1, sizeof(protocomm_ble_config_t));
+    if (!dest_config->ble) {
+        ESP_LOGE(TAG, "Failed to allocate memory for BLE transport config");
+        return ESP_ERR_NO_MEM;
+    }
+
+    /* Copy BLE device name */
+    memcpy(dest_config->ble->device_name,
+           src_config->ble->device_name,
+           sizeof(src_config->ble->device_name));
+
+    /* Copy Service UUID */
+    memcpy(dest_config->ble->service_uuid,
+           src_config->ble->service_uuid,
+           sizeof(src_config->ble->service_uuid));
+
+    dest_config->ble->nu_lookup_count = 0;
+    if (src_config->ble->nu_lookup_count) {
+        /* Copy any provided name-uuid lookup table */
+        dest_config->ble->nu_lookup = calloc(src_config->ble->nu_lookup_count,
+                                             sizeof(protocomm_ble_name_uuid_t));
+        if (!dest_config->ble->nu_lookup) {
+            ESP_LOGE(TAG, "Failed to allocate memory for BLE characteristic names");
+            free(dest_config->ble);
+            return ESP_ERR_NO_MEM;
+        }
+        for (uint16_t i = 0; i < src_config->ble->nu_lookup_count; i++) {
+            dest_config->ble->nu_lookup[i].uuid = src_config->ble->nu_lookup[i].uuid;
+            if (!src_config->ble->nu_lookup[i].name) {
+                ESP_LOGE(TAG, "Endpoint name cannot be null");
+                return ESP_ERR_INVALID_ARG;
+            }
+            dest_config->ble->nu_lookup[i].name = strdup(src_config->ble->nu_lookup[i].name);
+            if (!dest_config->ble->nu_lookup[i].name) {
+                ESP_LOGE(TAG, "Failed to allocate memory for endpoint name");
+                return ESP_ERR_NO_MEM;
+            }
+            dest_config->ble->nu_lookup_count++;
+        }
+    }
+    return ESP_OK;
+}
+
+static esp_err_t declare_endpoint(esp_local_ctrl_transport_config_t *config, const char *ep_name, uint16_t ep_uuid)
+{
+    if (!config || !config->ble) {
+        ESP_LOGE(TAG, "NULL configuration provided");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    protocomm_ble_name_uuid_t *nu_lookup = realloc(config->ble->nu_lookup,
+                                                   (config->ble->nu_lookup_count + 1)
+                                                   * sizeof(protocomm_ble_name_uuid_t));
+    if (!nu_lookup) {
+        ESP_LOGE(TAG, "Failed to allocate memory for new endpoint entry");
+        return ESP_ERR_NO_MEM;
+    }
+    config->ble->nu_lookup = nu_lookup;
+    nu_lookup[config->ble->nu_lookup_count].uuid = ep_uuid;
+    nu_lookup[config->ble->nu_lookup_count].name = strdup(ep_name);
+    if (!nu_lookup[config->ble->nu_lookup_count].name) {
+        ESP_LOGE(TAG, "Failed to allocate memory for new endpoint name");
+        return ESP_ERR_NO_MEM;
+    }
+    config->ble->nu_lookup_count++;
+    return ESP_OK;
+}
+
+static void free_config(esp_local_ctrl_transport_config_t *config)
+{
+    if (config && config->ble) {
+        for (unsigned int i = 0; i < config->ble->nu_lookup_count; i++) {
+            free((void*) config->ble->nu_lookup[i].name);
+        }
+        free(config->ble->nu_lookup);
+        free(config->ble);
+        config->ble = NULL;
+    }
+}
+
+const esp_local_ctrl_transport_t *esp_local_ctrl_get_transport_ble(void)
+{
+    static const esp_local_ctrl_transport_t tp = {
+        .start_service = start_ble_transport,
+        .stop_service  = stop_ble_transport,
+        .copy_config   = copy_ble_config,
+        .declare_ep    = declare_endpoint,
+        .free_config   = free_config
+    };
+    return &tp;
+};

+ 128 - 0
components/esp_local_ctrl/src/esp_local_ctrl_transport_httpd.c

@@ -0,0 +1,128 @@
+// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdio.h>
+#include <string.h>
+#include <esp_err.h>
+#include <esp_log.h>
+
+#include <mdns.h>
+#include <protocomm_httpd.h>
+#include <esp_local_ctrl.h>
+#include <esp_https_server.h>
+
+#include "esp_local_ctrl_priv.h"
+
+#define LOCAL_CTRL_VERSION "v1.0"
+
+static const char *TAG = "esp_local_ctrl_transport_httpd";
+
+static httpd_handle_t server_handle = NULL;
+
+static esp_err_t start_httpd_transport(protocomm_t *pc, const esp_local_ctrl_transport_config_t *config)
+{
+    if (!config || !config->httpd) {
+        ESP_LOGE(TAG, "NULL configuration provided");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    /* Extract configured port */
+    uint16_t port = (
+        config->httpd->transport_mode == HTTPD_SSL_TRANSPORT_SECURE ?
+            config->httpd->port_secure :
+            config->httpd->port_insecure
+    );
+
+    esp_err_t err = mdns_service_add("Local Control Service", "_esp_local_ctrl",
+                                     "_tcp", port, NULL, 0);
+    if (err != ESP_OK) {
+        /* mDNS is not mandatory for provisioning to work,
+         * so print warning and return without failure */
+        ESP_LOGW(TAG, "Error adding mDNS service! Check if mDNS is running");
+    } else {
+        /* Information to identify the roles of the various
+         * protocomm endpoint URIs provided by the service */
+        err |= mdns_service_txt_item_set("_esp_local_ctrl", "_tcp",
+                                         "version_endpoint", "/esp_local_ctrl/version");
+        err |= mdns_service_txt_item_set("_esp_local_ctrl", "_tcp",
+                                         "session_endpoint", "/esp_local_ctrl/session");
+        err |= mdns_service_txt_item_set("_esp_local_ctrl", "_tcp",
+                                         "control_endpoint", "/esp_local_ctrl/control");
+        if (err != ESP_OK) {
+            ESP_LOGE(TAG, "Error adding mDNS service text item");
+        }
+    }
+
+    err = httpd_ssl_start(&server_handle, config->httpd);
+    if (ESP_OK != err) {
+        ESP_LOGE(TAG, "Error starting HTTPS service!");
+        mdns_service_remove("_esp_local_ctrl", "_tcp");
+        return err;
+    }
+
+    protocomm_httpd_config_t pc_conf = {
+        .ext_handle_provided = true,
+        .data = {
+            .handle = &server_handle
+        }
+    };
+
+    return protocomm_httpd_start(pc, &pc_conf);
+}
+
+static void stop_httpd_transport(protocomm_t *pc)
+{
+    mdns_service_remove("_esp_local_ctrl", "_tcp");
+    protocomm_httpd_stop(pc);
+    httpd_ssl_stop(server_handle);
+    server_handle = NULL;
+}
+
+static esp_err_t copy_httpd_config(esp_local_ctrl_transport_config_t *dest_config, const esp_local_ctrl_transport_config_t *src_config)
+{
+    if (!dest_config || !src_config || !src_config->httpd) {
+        ESP_LOGE(TAG, "NULL configuration provided");
+        return ESP_ERR_INVALID_ARG;
+    }
+
+    dest_config->httpd = calloc(1, sizeof(httpd_ssl_config_t));
+    if (!dest_config->httpd) {
+        ESP_LOGE(TAG, "Failed to allocate memory for HTTPD transport config");
+        return ESP_ERR_NO_MEM;
+    }
+    memcpy(dest_config->httpd,
+           src_config->httpd,
+           sizeof(httpd_ssl_config_t));
+    return ESP_OK;
+}
+
+static void free_config(esp_local_ctrl_transport_config_t *config)
+{
+    if (config && config->httpd) {
+        free(config->httpd);
+        config->httpd = NULL;
+    }
+}
+
+const esp_local_ctrl_transport_t *esp_local_ctrl_get_transport_httpd(void)
+{
+    static const esp_local_ctrl_transport_t tp = {
+        .start_service = start_httpd_transport,
+        .stop_service  = stop_httpd_transport,
+        .copy_config   = copy_httpd_config,
+        .declare_ep    = NULL,
+        .free_config   = free_config
+    };
+    return &tp;
+};

+ 1 - 1
components/protocomm/include/transports/protocomm_ble.h

@@ -48,7 +48,7 @@ typedef struct name_uuid {
 /**
  * @brief   Config parameters for protocomm BLE service
  */
-typedef struct {
+typedef struct protocomm_ble_config {
     /**
      * BLE device name being broadcast at the time of provisioning
      */

+ 3 - 0
docs/Doxyfile

@@ -107,8 +107,11 @@ INPUT = \
     ../../components/mdns/include/mdns.h \
     ../../components/esp_http_client/include/esp_http_client.h \
     ../../components/esp_websocket_client/include/esp_websocket_client.h \
+    ## HTTP / HTTPS Server
     ../../components/esp_http_server/include/esp_http_server.h \
     ../../components/esp_https_server/include/esp_https_server.h \
+    ## ESP Local Ctrl
+    ../../components/esp_local_ctrl/include/esp_local_ctrl.h \
     ##
     ## Provisioning - API Reference
     ##

+ 206 - 0
docs/en/api-reference/protocols/esp_local_ctrl.rst

@@ -0,0 +1,206 @@
+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(&timestamp);
+
+
+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

+ 1 - 0
docs/en/api-reference/protocols/index.rst

@@ -14,6 +14,7 @@ Application Protocols
    ASIO <asio>
    ESP-MQTT <mqtt>
    Modbus <modbus>
+   Local Control <esp_local_ctrl>
 
 Code examples for this API section are provided in the :example:`protocols` directory of ESP-IDF examples.
 

+ 1 - 0
docs/zh_CN/api-reference/protocols/esp_local_ctrl.rst

@@ -0,0 +1 @@
+.. include:: ../../../en/api-reference/protocols/esp_local_ctrl.rst

+ 1 - 0
docs/zh_CN/api-reference/protocols/index.rst

@@ -14,6 +14,7 @@
    ASIO <asio>
    ESP-MQTT <mqtt>
    Modbus slave <modbus>
+   Local Control <esp_local_ctrl>
 
 此 API 部分的示例代码在 ESP-IDF 示例工程的 :example:`protocols` 目录下提供。
 

+ 10 - 0
examples/protocols/esp_local_ctrl/CMakeLists.txt

@@ -0,0 +1,10 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+# (Not part of the boilerplate)
+# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
+set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
+
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(esp_local_ctrl)

+ 9 - 0
examples/protocols/esp_local_ctrl/Makefile

@@ -0,0 +1,9 @@
+#
+# This is a project Makefile. It is assumed the directory this Makefile resides in is a
+# project subdirectory.
+#
+
+PROJECT_NAME := esp_local_ctrl
+
+include $(IDF_PATH)/make/project.mk
+

+ 93 - 0
examples/protocols/esp_local_ctrl/README.md

@@ -0,0 +1,93 @@
+# ESP Local Control using HTTPS server
+
+This example creates a `esp_local_ctrl` service over HTTPS transport, for securely controlling the device over local network. In this case the device name is resolved through `mDNS`, which in this example is `my_esp_ctrl_device.local`.
+
+See the `esp_local_ctrl` component documentation for details.
+
+Before using the example, run `make menuconfig` (or `idf.py menuconfig` if using CMake build system) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../README.md) for more details.
+
+## Client Side Implementation
+
+A python test script `scripts/esp_local_ctrl.py` has been provided for as a client side application for controlling the device over the same Wi-Fi network. The script relies on a pre-generated `main/certs/rootCA.pem` to verify the server certificate. The server side private key and certificate can also be found under `main/certs`, namely `prvtkey.pem` and `cacert.pem`.
+
+After configuring the Wi-Fi, flashing and booting the device, run:
+
+```
+python scripts/esp_local_ctrl.py
+```
+Sample output:
+
+```
+python2 scripts/esp_local_ctrl.py
+
+==== Acquiring properties information ====
+
+==== Acquired properties information ====
+
+==== Available Properties ====
+S.N. Name             Type       Flags            Value
+[ 1] timestamp (us)   TIME(us)   Read-Only        168561481
+[ 2] property1        INT32                       123456
+[ 3] property2        BOOLEAN    Read-Only        True
+[ 4] property3        STRING
+
+Select properties to set (0 to re-read, 'q' to quit) : 0
+
+==== Available Properties ====
+S.N. Name             Type       Flags            Value
+[ 1] timestamp (us)   TIME(us)   Read-Only        22380117
+[ 2] property1        INT32                       123456
+[ 3] property2        BOOLEAN    Read-Only        False
+[ 4] property3        STRING
+
+Select properties to set (0 to re-read, 'q' to quit) : 2,4
+Enter value to set for property (property1) : -5555
+Enter value to set for property (property3) : hello world!
+
+==== Available Properties ====
+S.N. Name             Type       Flags            Value
+[ 1] timestamp (us)   TIME(us)   Read-Only        55110859
+[ 2] property1        INT32                       -5555
+[ 3] property2        BOOLEAN    Read-Only        False
+[ 4] property3        STRING                      hello world!
+
+Select properties to set (0 to re-read, 'q' to quit) : q
+Quitting...
+```
+
+The script also allows to connect over BLE, and provide a custom service name. To display the list of supported parameters, run:
+
+```
+python scripts/esp_local_ctrl.py --help
+```
+
+## Certificates
+
+You can generate a new server certificate using the OpenSSL command line tool.
+
+For the purpose of this example, lets generate a rootCA, which we will use to sign the server certificates and which the client will use to verify the server certificate during SSL handshake. You will need to set a password for encrypting the generated `rootkey.pem`.
+
+```
+openssl req -new -x509 -subj "/CN=root" -days 3650 -sha256 -out rootCA.pem -keyout rootkey.pem
+```
+
+Now generate a certificate signing request for the server, along with its private key `prvtkey.pem`.
+
+```
+openssl req -newkey rsa:2048 -nodes -keyout prvtkey.pem -days 3650 -out server.csr -subj "/CN=my_esp_ctrl_device.local"
+```
+
+Now use the previously generated rootCA to process the server's certificate signing request, and generate a signed certificate `cacert.pem`. The password set for encrypting `rootkey.pem` earlier, has to be entered during this step.
+
+```
+openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootkey.pem -CAcreateserial -out cacert.pem -days 500 -sha256
+```
+
+Now that we have `rootCA.pem`, `cacert.pem` and `prvtkey.pem`, copy these into main/certs. Note that only the server related files (`cacert.pem` and `prvtkey.pem`) are embedded into the firmware.
+
+Expiry time and metadata fields can be adjusted in the invocation.
+
+Please see the `openssl` man pages (man `openssl-req`) for more details.
+
+It is **strongly recommended** to not reuse the example certificate in your application;
+it is included only for demonstration.

+ 8 - 0
examples/protocols/esp_local_ctrl/main/CMakeLists.txt

@@ -0,0 +1,8 @@
+set(COMPONENT_SRCS "app_main.c" "esp_local_ctrl_service.c")
+set(COMPONENT_ADD_INCLUDEDIRS ".")
+
+set(COMPONENT_EMBED_TXTFILES
+        "certs/cacert.pem"
+        "certs/prvtkey.pem")
+
+register_component()

+ 20 - 0
examples/protocols/esp_local_ctrl/main/Kconfig.projbuild

@@ -0,0 +1,20 @@
+menu "Example Configuration"
+
+    config ESP_WIFI_SSID
+        string "WiFi SSID"
+        default "myssid"
+        help
+            SSID (network name) for the example to connect to.
+
+    config ESP_WIFI_PASSWORD
+        string "WiFi Password"
+        default "mypassword"
+        help
+            WiFi password (WPA or WPA2) for the example to use.
+
+    config ESP_MAXIMUM_RETRY
+        int "Maximum retry"
+        default 5
+        help
+            Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
+endmenu

+ 111 - 0
examples/protocols/esp_local_ctrl/main/app_main.c

@@ -0,0 +1,111 @@
+/* Local Ctrl Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <string.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/event_groups.h"
+#include "esp_system.h"
+#include "esp_wifi.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "nvs_flash.h"
+
+#include "lwip/err.h"
+#include "lwip/sys.h"
+
+/* The examples use WiFi configuration that you can set via 'make menuconfig'.
+
+   If you'd rather not, just change the below entries to strings with
+   the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
+*/
+#define EXAMPLE_ESP_WIFI_SSID      CONFIG_ESP_WIFI_SSID
+#define EXAMPLE_ESP_WIFI_PASS      CONFIG_ESP_WIFI_PASSWORD
+#define EXAMPLE_ESP_MAXIMUM_RETRY  CONFIG_ESP_MAXIMUM_RETRY
+
+/* FreeRTOS event group to signal when we are connected*/
+static EventGroupHandle_t s_wifi_event_group;
+
+/* The event group allows multiple bits for each event, but we only care about one event
+ * - are we connected to the AP with an IP? */
+const int WIFI_CONNECTED_BIT = BIT0;
+
+static const char *TAG = "local_ctrl_example";
+
+static int s_retry_num = 0;
+
+static void event_handler(void* arg, esp_event_base_t event_base,
+                                int32_t event_id, void* event_data)
+{
+    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
+        esp_wifi_connect();
+    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
+        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
+            esp_wifi_connect();
+            xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
+            s_retry_num++;
+            ESP_LOGI(TAG, "retry to connect to the AP");
+        }
+        ESP_LOGI(TAG,"connect to the AP fail");
+    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
+        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
+        ESP_LOGI(TAG, "got ip:%s",
+                 ip4addr_ntoa(&event->ip_info.ip));
+        s_retry_num = 0;
+        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
+    }
+}
+
+void wifi_init_sta()
+{
+    s_wifi_event_group = xEventGroupCreate();
+
+    tcpip_adapter_init();
+
+    ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
+    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
+
+    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
+    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
+
+    wifi_config_t wifi_config = {
+        .sta = {
+            .ssid = EXAMPLE_ESP_WIFI_SSID,
+            .password = EXAMPLE_ESP_WIFI_PASS
+        },
+    };
+    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
+    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
+    ESP_ERROR_CHECK(esp_wifi_start() );
+
+    ESP_LOGI(TAG, "wifi_init_sta finished.");
+    ESP_LOGI(TAG, "connect to ap SSID:%s password:%s",
+             EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
+}
+
+/* Function responsible for configuring and starting the esp_local_ctrl service.
+ * See local_ctrl_service.c for implementation */
+extern void start_esp_local_ctrl_service(void);
+
+void app_main()
+{
+    //Initialize NVS
+    esp_err_t ret = nvs_flash_init();
+    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
+      ESP_ERROR_CHECK(nvs_flash_erase());
+      ret = nvs_flash_init();
+    }
+    ESP_ERROR_CHECK(ret);
+
+    ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
+    wifi_init_sta();
+    start_esp_local_ctrl_service();
+}

+ 17 - 0
examples/protocols/esp_local_ctrl/main/certs/cacert.pem

@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICuTCCAaECFFnose4o8INWoH5BA5TOCz2e9zAOMA0GCSqGSIb3DQEBCwUAMA8x
+DTALBgNVBAMMBHJvb3QwHhcNMTkwNjI1MTkyMTU4WhcNMjAxMTA2MTkyMTU4WjAj
+MSEwHwYDVQQDDBhteV9lc3BfY3RybF9kZXZpY2UubG9jYWwwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDY2B46AdNfrJRGgHy7cECmEMxOWn8CvygC2g77
+Gog/DWxkqaEksBJt8qQcqGLumv+HfXE4erNPGU+RTNal+zMXHliIMVi2IiVw5uuC
+Tze7cK28HDvC5noED/TWGSJIaCQAUP/GdE0sqCJ1O7W0IhrZBjsmen4d0nPrInCz
+X9YDOfaWxdwnBJ3q0+7ZTSWETbDrKUJ0tgPe6m96j/zRYCtCo2Dpu/pZvPyIvXwT
+zt6enB8cwDtk35KwOrscAJGNqkCRyKaNvOSuHv9/02vpzwqk/J6JbIcXFVNuYSPg
+0wb0iltMqn0IwC3KyaI9gBg0VexMeOhFV/gRt8dvEYehtTB7AgMBAAEwDQYJKoZI
+hvcNAQELBQADggEBAFnKbunoBvKun4hJV4sp0ztDKpjOJevsQp3X36egm4NGCpEj
+cdHxEmAvmeiu/12C6OfvFmZ/QiqNmp2gihpy4DiuxWnI+iC9JjfYuWTsKj+xcVkw
+4IvGZbFtE9YW+XwNWqXPi1urVk9wKpZmCWpWgFWnLwPgIQs16+y3+CQF3vefX9Iy
+aqmYrTYkBpLEXRjYJeU253mvN6FXQgOoPuld1Ph+IO+DUEJr+zeM88xkmjAo37ej
+VkCMXA5HqdT64HuZC1RnnbpP76assgFW2oTycG28jzHSYjuK2q1PIoZtzpW8Sv7i
+jn17E6ryf24r1DVkQByR54rvzl6Qu3M8TJe6EYI=
+-----END CERTIFICATE-----

+ 28 - 0
examples/protocols/esp_local_ctrl/main/certs/prvtkey.pem

@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDY2B46AdNfrJRG
+gHy7cECmEMxOWn8CvygC2g77Gog/DWxkqaEksBJt8qQcqGLumv+HfXE4erNPGU+R
+TNal+zMXHliIMVi2IiVw5uuCTze7cK28HDvC5noED/TWGSJIaCQAUP/GdE0sqCJ1
+O7W0IhrZBjsmen4d0nPrInCzX9YDOfaWxdwnBJ3q0+7ZTSWETbDrKUJ0tgPe6m96
+j/zRYCtCo2Dpu/pZvPyIvXwTzt6enB8cwDtk35KwOrscAJGNqkCRyKaNvOSuHv9/
+02vpzwqk/J6JbIcXFVNuYSPg0wb0iltMqn0IwC3KyaI9gBg0VexMeOhFV/gRt8dv
+EYehtTB7AgMBAAECggEBAJSvM6Kgp9fdVNo2tdAsOxfjQsOjB53RhtTVwhnpamyZ
+fq5TJZwrYqejDWZdC2ECRJ4ZpG2OrK5a85T0s+Whpbl/ZEMWWvaf2T5eCDQUr2lF
+7MqkLVIJiLaKXl4DY990EONqpsbj7hrluqLZ61B1ZiVTQXGz4g/+wt8CgXZtCyiv
+7XOTTmQueugq4f54JBX5isdB7/xLaXV3kycaEK1b6ZVFYB3ii5IKKsX7RK/ksA6O
+fRrQ8702prqphPfbjZ9wPHif/zLiyiF2FG6OX1Y3aZe1npRsvuH2c3M2h+HGAQUR
+3lDxMTNbsE8E+XKZFVAVdMqot2RfxENSHoJHcp1R2YECgYEA9qe1+eOZKd0w5lC1
+PuG6FLAAbK1nuv/ovESEHtILTLFkMijgAqcWjtp1klS86IBJLnjv+GYxZu2n1WI9
+QLnh++NNTjRGCMM2Adf5SBJ/5F85rpgzz7Yur1guqkUQx/2dmErOaWQ4IO304VlM
+vrJB8+XmAiysEgJOkK0Mx8xRVcECgYEA4Q9GBADLryvwjisp/PdTRXOvd7pJRGH3
+SdC1k/nBsmpmbouc0ihqzOiiN0kUjE2yLSlhwxxWBJqNSzOk9z5/LB4TNRqH9gCL
+rUN67FgzwR5H70OblWpcjWRurFq34+ZWEmCG+1qUwZMT7dYe4CiDYnVjcwfUpQwN
+qRpjeMLDrTsCgYEAgo1CRIGzD/WDbGRLinzvgQOnNd6SiOfqx7t8MtP6Jx29as83
+wi+uQO5gTJONaYJ9OZvJaDCu9UvVCZx1z0yT0D7/K+V/LCQm8dLenscr6jR802y7
+/7TuAOEr0fO8bh5Oy8zMc/wXuVY5xwz9EfJH9lA47e23JdESxIDTwuziIAECgYEA
+qKQdPtqpxbUTKDTH3bomN6CcFwcL56XQ+wrdROidb+eyoZsUA5YtkSWwh+TG9Osz
+XAvqKZ2OBx0YSwWD05CNEq3mjqA2yOtXvpkV/wuInGjoVi0+5BMzDu/2zkecC7WJ
+QXP7MVWKqhJfmJQdxrIU4S49OvDfMl15zwDrEI5AugkCgYBn5+/KvrA8GGWD9p1r
+qwjoojGBvCJn1Kj/s+IQYePYeiRe6/eSPGFHRyON9aMGjZmeiqyBA4bW71Wf8Rs1
+X5LSwZhJpCTjO4B92w40u0r86Jxmp5Wz+zHUeM3mO2E6lAF+15YjhxpMT0YOmHFE
++oKD8U6dMjkTqntavBauz8M8fQ==
+-----END PRIVATE KEY-----

+ 19 - 0
examples/protocols/esp_local_ctrl/main/certs/rootCA.pem

@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/zCCAeegAwIBAgIUH0R6q5vbgMnMZgD5r4xSu+WhSMwwDQYJKoZIhvcNAQEL
+BQAwDzENMAsGA1UEAwwEcm9vdDAeFw0xOTA2MjUxOTIxNDVaFw0yOTA2MjIxOTIx
+NDVaMA8xDTALBgNVBAMMBHJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQDGDetEF+4HEOU5uoxvHsYAksmpF1tjw/M+aKtyGuTWInJPDJ3YjjjnF7hb
+ylx5W7Qj4O4N+TmqYkwA4ztq2CXSmX1uc7OOfxU/wED663NoC2P1Mw0fI5fX2518
+WdJeQilYymIOilmdtNqU9ad/3RdSZg+fxL5z9MTidHlUyzJG5LlO1cDiYRRURj9S
+Fc2wWEUCETGA78ADCxKsdf2gBZDcZo/PHNXZc7fi2K18T5UmkRd50aoSLWUNY5tT
+4DsyL19PUJCmtwcoLMT3p3kmepN4C0JByOxWceIvlAbq7+L3zMURWfpBcIqxXvEP
+Y/JXw7GCfTJgjUz1IoHVz/ERNtrnAgMBAAGjUzBRMB0GA1UdDgQWBBSaztoCfcw+
+mBrfMXLCBU8mOyj2BTAfBgNVHSMEGDAWgBSaztoCfcw+mBrfMXLCBU8mOyj2BTAP
+BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCSgkWG4QAblyxXN/R5
+tNLKbzQIbaMj8uXSdcVtNHrNfydA0Sq2M8w7mT7N2axiMAusN3fhgztQvkWCvKdy
+ou++NpFBb11+QJ2chgatLtoR7QPQ2TVlTUObAh2ZSt1jDOqvGQynbYqJ+9N6BKpK
+S8faScaWP78J02TSMiNIvh8iYukZPMdCyJaHw2x0PtCRYVBSlFIwC5dn/sIJgyrV
+g8RAlnsKTCQC3X20AQ851aID6JXDIaTn9pn9PN0XJC+iButpLZM4ZHHZpBtSQZ+d
+6lD0tvS8bysCEkamDMt3z8/ncsytAS08VoFqwXdY3EXF8T+sKSi7+ACJXE/kivwu
+8jvm
+-----END CERTIFICATE-----

+ 7 - 0
examples/protocols/esp_local_ctrl/main/component.mk

@@ -0,0 +1,7 @@
+#
+# "main" pseudo-component makefile.
+#
+# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
+
+COMPONENT_EMBED_TXTFILES := certs/cacert.pem
+COMPONENT_EMBED_TXTFILES += certs/prvtkey.pem

+ 274 - 0
examples/protocols/esp_local_ctrl/main/esp_local_ctrl_service.c

@@ -0,0 +1,274 @@
+/* Local Ctrl Example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <sys/param.h>
+#include <string.h>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+
+#include <mdns.h>
+#include <esp_log.h>
+#include <esp_timer.h>
+#include <esp_local_ctrl.h>
+#include <esp_https_server.h>
+
+static const char *TAG = "control";
+
+#define SERVICE_NAME "my_esp_ctrl_device"
+
+/* Custom allowed property types */
+enum property_types {
+    PROP_TYPE_TIMESTAMP = 0,
+    PROP_TYPE_INT32,
+    PROP_TYPE_BOOLEAN,
+    PROP_TYPE_STRING,
+};
+
+/* Custom flags that can be set for a property */
+enum property_flags {
+    PROP_FLAG_READONLY = (1 << 0)
+};
+
+/********* Handler functions for responding to control requests / commands *********/
+
+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 property : %s", props[i].name);
+        /* For the purpose of this example, to keep things simple
+         * we have set the context pointer of each property to
+         * point to its value (except for timestamp) */
+        switch (props[i].type) {
+            case PROP_TYPE_INT32:
+            case PROP_TYPE_BOOLEAN:
+                /* No need to set size for these types as sizes where
+                 * specified when declaring the properties, unlike for
+                 * string type. */
+                prop_values[i].data = props[i].ctx;
+                break;
+            case PROP_TYPE_TIMESTAMP: {
+                /* Get the time stamp */
+                static int64_t ts = 0;
+                ts = esp_timer_get_time();
+
+                /* Set the current time. Since this is statically
+                 * allocated, we don't need to provide a free_fn */
+                prop_values[i].data = &ts;
+                break;
+            }
+            case PROP_TYPE_STRING: {
+                char **prop3_value = (char **) props[i].ctx;
+                if (*prop3_value == NULL) {
+                    prop_values[i].size = 0;
+                    prop_values[i].data = NULL;
+                } else {
+                    /* We could try dynamically allocating the output value,
+                     * and it should get freed automatically after use, as
+                     * `esp_local_ctrl` internally calls the provided `free_fn` */
+                    prop_values[i].size = strlen(*prop3_value);
+                    prop_values[i].data = strdup(*prop3_value);
+                    if (!prop_values[i].data) {
+                        return ESP_ERR_NO_MEM;
+                    }
+                    prop_values[i].free_fn = free;
+                }
+            }
+            default:
+                break;
+        }
+    }
+    return ESP_OK;
+}
+
+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++) {
+        /* Cannot set the value of a read-only property */
+        if (props[i].flags & PROP_FLAG_READONLY) {
+            ESP_LOGE(TAG, "%s is read-only", props[i].name);
+            return ESP_ERR_INVALID_ARG;
+        }
+        /* For the purpose of this example, to keep things simple
+         * we have set the context pointer of each property to
+         * point to its value (except for timestamp) */
+        switch (props[i].type) {
+            case PROP_TYPE_STRING: {
+                    /* Free the previously set string */
+                    char **prop3_value = (char **) props[i].ctx;
+                    free(*prop3_value);
+                    *prop3_value = NULL;
+
+                    /* Copy the input string */
+                    if (prop_values[i].size) {
+                        *prop3_value = strndup((const char *)prop_values[i].data, prop_values[i].size);
+                        if (*prop3_value == NULL) {
+                            return ESP_ERR_NO_MEM;
+                        }
+                        ESP_LOGI(TAG, "Setting %s value to %s", props[i].name, (const char*)*prop3_value);
+                    }
+                }
+                break;
+            case PROP_TYPE_INT32: {
+                    const int32_t *new_value = (const int32_t *) prop_values[i].data;
+                    ESP_LOGI(TAG, "Setting %s value to %d", props[i].name, *new_value);
+                    memcpy(props[i].ctx, new_value, sizeof(int32_t));
+                }
+                break;
+            case PROP_TYPE_BOOLEAN: {
+                    const bool *value = (const bool *) prop_values[i].data;
+                    ESP_LOGI(TAG, "Setting %s value to %d", props[i].name, *value);
+                    memcpy(props[i].ctx, value, sizeof(bool));
+                }
+                break;
+            default:
+                break;
+        }
+    }
+    return ESP_OK;
+}
+
+/******************************************************************************/
+
+/* A custom free_fn to free a pointer to a string as
+ * well as the string being pointed to */
+static void free_str(void *arg)
+{
+    char **ptr_to_strptr = (char **)arg;
+    if (ptr_to_strptr) {
+        free(*ptr_to_strptr);
+        free(ptr_to_strptr);
+    }
+}
+
+/* Function used by app_main to start the esp_local_ctrl service */
+void start_esp_local_ctrl_service(void)
+{
+    /* 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
+    };
+
+    mdns_init();
+    mdns_hostname_set(SERVICE_NAME);
+
+    /* Start esp_local_ctrl service */
+    ESP_ERROR_CHECK(esp_local_ctrl_start(&config));
+    ESP_LOGI(TAG, "esp_local_ctrl service started with name : %s", SERVICE_NAME);
+
+    /* Create a timestamp property. The client should see this as a read-only property.
+     * Property value is fetched using `esp_timer_get_time()` in the `get_prop_values`
+     * handler */
+    esp_local_ctrl_prop_t timestamp = {
+        .name        = "timestamp (us)",
+        .type        = PROP_TYPE_TIMESTAMP,
+        .size        = sizeof(int64_t),
+        .flags       = PROP_FLAG_READONLY,
+        .ctx         = NULL,
+        .ctx_free_fn = NULL
+    };
+
+    /* Create a writable integer property. Use dynamically allocated memory
+     * for storing its value and pass it as context, so that it can be accessed
+     * inside the set / get handlers. */
+    int32_t *prop1_value = malloc(sizeof(int32_t));
+    assert(prop1_value != NULL);
+
+    /* Initialize the property value */
+    *prop1_value = 123456789;
+
+    /* Populate the property structure accordingly. Since, we would want the memory
+     * occupied by the property value to be freed automatically upon call to
+     * `esp_local_ctrl_stop()` or `esp_local_ctrl_remove_property()`, the `ctx_free_fn`
+     * field will need to be set with the appropriate de-allocation function,
+     * which in this case is simply `free()` */
+    esp_local_ctrl_prop_t property1 = {
+        .name        = "property1",
+        .type        = PROP_TYPE_INT32,
+        .size        = sizeof(int32_t),
+        .flags       = 0,
+        .ctx         = prop1_value,
+        .ctx_free_fn = free
+    };
+
+    /* Create another read-only property. Just for demonstration, we use statically
+     * allocated value. No `ctx_free_fn` needs to be set for this */
+    static bool prop2_value = false;
+
+    esp_local_ctrl_prop_t property2 = {
+        .name        = "property2",
+        .type        = PROP_TYPE_BOOLEAN,
+        .size        = sizeof(bool),
+        .flags       = PROP_FLAG_READONLY,
+        .ctx         = &prop2_value,
+        .ctx_free_fn = NULL
+    };
+
+    /* Create a variable sized property. Its context is a pointer for storing the
+     * pointer to a dynamically allocate string, therefore it will require a
+     * customized free function `free_str()` */
+    char **prop3_value = calloc(1, sizeof(char *));
+    assert(prop3_value != NULL);
+
+    esp_local_ctrl_prop_t property3 = {
+        .name        = "property3",
+        .type        = PROP_TYPE_STRING,
+        .size        = 0, // When zero, this is assumed to be of variable size
+        .flags       = 0,
+        .ctx         = prop3_value,
+        .ctx_free_fn = free_str
+    };
+
+    /* Now register the properties */
+    ESP_ERROR_CHECK(esp_local_ctrl_add_property(&timestamp));
+    ESP_ERROR_CHECK(esp_local_ctrl_add_property(&property1));
+    ESP_ERROR_CHECK(esp_local_ctrl_add_property(&property2));
+    ESP_ERROR_CHECK(esp_local_ctrl_add_property(&property3));
+
+    /* Just for fun, let us keep toggling the value
+     * of the boolean property2, every 1 second */
+    while (1) {
+        vTaskDelay(1000 / portTICK_RATE_MS);
+        prop2_value = !prop2_value;
+    }
+}

+ 273 - 0
examples/protocols/esp_local_ctrl/scripts/esp_local_ctrl.py

@@ -0,0 +1,273 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+from __future__ import print_function
+from future.utils import tobytes
+from builtins import input
+import os
+import sys
+import struct
+import argparse
+
+import proto
+
+try:
+    import esp_prov
+
+except ImportError:
+    idf_path = os.environ['IDF_PATH']
+    sys.path.insert(1, idf_path + "/tools/esp_prov")
+    import esp_prov
+
+
+# Set this to true to allow exceptions to be thrown
+config_throw_except = False
+
+
+# Property types enum
+PROP_TYPE_TIMESTAMP = 0
+PROP_TYPE_INT32     = 1
+PROP_TYPE_BOOLEAN   = 2
+PROP_TYPE_STRING    = 3
+
+
+# Property flags enum
+PROP_FLAG_READONLY = (1 << 0)
+
+
+def prop_typestr(prop):
+    if prop["type"] == PROP_TYPE_TIMESTAMP:
+        return "TIME(us)"
+    elif prop["type"] == PROP_TYPE_INT32:
+        return "INT32"
+    elif prop["type"] == PROP_TYPE_BOOLEAN:
+        return "BOOLEAN"
+    elif prop["type"] == PROP_TYPE_STRING:
+        return "STRING"
+    return "UNKNOWN"
+
+
+def encode_prop_value(prop, value):
+    try:
+        if prop["type"] == PROP_TYPE_TIMESTAMP:
+            return struct.pack('q', value)
+        elif prop["type"] == PROP_TYPE_INT32:
+            return struct.pack('i', value)
+        elif prop["type"] == PROP_TYPE_BOOLEAN:
+            return struct.pack('?', value)
+        elif prop["type"] == PROP_TYPE_STRING:
+            return tobytes(value)
+        return value
+    except struct.error as e:
+        print(e)
+    return None
+
+
+def decode_prop_value(prop, value):
+    try:
+        if prop["type"] == PROP_TYPE_TIMESTAMP:
+            return struct.unpack('q', value)[0]
+        elif prop["type"] == PROP_TYPE_INT32:
+            return struct.unpack('i', value)[0]
+        elif prop["type"] == PROP_TYPE_BOOLEAN:
+            return struct.unpack('?', value)[0]
+        elif prop["type"] == PROP_TYPE_STRING:
+            return value.decode('latin-1')
+        return value
+    except struct.error as e:
+        print(e)
+    return None
+
+
+def str_to_prop_value(prop, strval):
+    try:
+        if prop["type"] == PROP_TYPE_TIMESTAMP:
+            return int(strval)
+        elif prop["type"] == PROP_TYPE_INT32:
+            return int(strval)
+        elif prop["type"] == PROP_TYPE_BOOLEAN:
+            return bool(strval)
+        elif prop["type"] == PROP_TYPE_STRING:
+            return strval
+        return strval
+    except ValueError as e:
+        print(e)
+        return None
+
+
+def prop_is_readonly(prop):
+    return (prop["flags"] & PROP_FLAG_READONLY) is not 0
+
+
+def on_except(err):
+    if config_throw_except:
+        raise RuntimeError(err)
+    else:
+        print(err)
+
+
+def get_transport(sel_transport, service_name):
+    try:
+        tp = None
+        if (sel_transport == 'http'):
+            example_path = os.environ['IDF_PATH'] + "/examples/protocols/esp_local_ctrl"
+            cert_path = example_path + "/main/certs/rootCA.pem"
+            tp = esp_prov.transport.Transport_HTTP(service_name, cert_path)
+        elif (sel_transport == 'ble'):
+            tp = esp_prov.transport.Transport_BLE(
+                devname=service_name, service_uuid='0000ffff-0000-1000-8000-00805f9b34fb',
+                nu_lookup={'esp_local_ctrl/version': '0001',
+                           'esp_local_ctrl/session': '0002',
+                           'esp_local_ctrl/control': '0003'}
+            )
+        return tp
+    except RuntimeError as e:
+        on_except(e)
+        return None
+
+
+def version_match(tp, expected, verbose=False):
+    try:
+        response = tp.send_data('esp_local_ctrl/version', expected)
+        return (response.lower() == expected.lower())
+    except Exception as e:
+        on_except(e)
+        return None
+
+
+def get_all_property_values(tp):
+    try:
+        props = []
+        message = proto.get_prop_count_request()
+        response = tp.send_data('esp_local_ctrl/control', message)
+        count = proto.get_prop_count_response(response)
+        if count == 0:
+            raise RuntimeError("No properties found!")
+        indices = [i for i in range(count)]
+        message = proto.get_prop_vals_request(indices)
+        response = tp.send_data('esp_local_ctrl/control', message)
+        props = proto.get_prop_vals_response(response)
+        if len(props) != count:
+            raise RuntimeError("Incorrect count of properties!")
+        for p in props:
+            p["value"] = decode_prop_value(p, p["value"])
+        return props
+    except RuntimeError as e:
+        on_except(e)
+        return []
+
+
+def set_property_values(tp, props, indices, values, check_readonly=False):
+    try:
+        if check_readonly:
+            for index in indices:
+                if prop_is_readonly(props[index]):
+                    raise RuntimeError("Cannot set value of Read-Only property")
+        message = proto.set_prop_vals_request(indices, values)
+        response = tp.send_data('esp_local_ctrl/control', message)
+        return proto.set_prop_vals_response(response)
+    except RuntimeError as e:
+        on_except(e)
+        return False
+
+
+if __name__ == '__main__':
+    parser = argparse.ArgumentParser(add_help=False)
+
+    parser = argparse.ArgumentParser(description="Control an ESP32 running esp_local_ctrl service")
+
+    parser.add_argument("--version", dest='version', type=str,
+                        help="Protocol version", default='')
+
+    parser.add_argument("--transport", dest='transport', type=str,
+                        help="transport i.e http or ble", default='http')
+
+    parser.add_argument("--name", dest='service_name', type=str,
+                        help="BLE Device Name / HTTP Server hostname or IP", default='')
+
+    parser.add_argument("-v", "--verbose", dest='verbose', help="increase output verbosity", action="store_true")
+    args = parser.parse_args()
+
+    if args.version != '':
+        print("==== Esp_Ctrl Version: " + args.version + " ====")
+
+    if args.service_name == '':
+        args.service_name = 'my_esp_ctrl_device'
+        if args.transport == 'http':
+            args.service_name += '.local'
+
+    obj_transport = get_transport(args.transport, args.service_name)
+    if obj_transport is None:
+        print("---- Invalid transport ----")
+        exit(1)
+
+    if args.version != '':
+        print("\n==== Verifying protocol version ====")
+        if not version_match(obj_transport, args.version, args.verbose):
+            print("---- Error in protocol version matching ----")
+            exit(2)
+        print("==== Verified protocol version successfully ====")
+
+    while True:
+        properties = get_all_property_values(obj_transport)
+        if len(properties) == 0:
+            print("---- Error in reading property values ----")
+            exit(4)
+
+        print("\n==== Available Properties ====")
+        print("{0: >4} {1: <16} {2: <10} {3: <16} {4: <16}".format(
+            "S.N.", "Name", "Type", "Flags", "Value"))
+        for i in range(len(properties)):
+            print("[{0: >2}] {1: <16} {2: <10} {3: <16} {4: <16}".format(
+                i + 1, properties[i]["name"], prop_typestr(properties[i]),
+                ["","Read-Only"][prop_is_readonly(properties[i])],
+                str(properties[i]["value"])))
+
+        select = 0
+        while True:
+            try:
+                inval = input("\nSelect properties to set (0 to re-read, 'q' to quit) : ")
+                if inval.lower() == 'q':
+                    print("Quitting...")
+                    exit(5)
+                invals = inval.split(',')
+                selections = [int(val) for val in invals]
+                if min(selections) < 0 or max(selections) > len(properties):
+                    raise ValueError("Invalid input")
+                break
+            except ValueError as e:
+                print(str(e) + "! Retry...")
+
+        if len(selections) == 1 and selections[0] == 0:
+            continue
+
+        set_values = []
+        set_indices = []
+        for select in selections:
+            while True:
+                inval = input("Enter value to set for property (" + properties[select - 1]["name"] + ") : ")
+                value = encode_prop_value(properties[select - 1],
+                                          str_to_prop_value(properties[select - 1], inval))
+                if value is None:
+                    print("Invalid input! Retry...")
+                    continue
+                break
+            set_values += [value]
+            set_indices += [select - 1]
+
+        if not set_property_values(obj_transport, properties, set_indices, set_values):
+            print("Failed to set values!")

+ 93 - 0
examples/protocols/esp_local_ctrl/scripts/proto.py

@@ -0,0 +1,93 @@
+# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+
+from __future__ import print_function
+from future.utils import tobytes
+import os
+
+
+def _load_source(name, path):
+    try:
+        from importlib.machinery import SourceFileLoader
+        return SourceFileLoader(name, path).load_module()
+    except ImportError:
+        # importlib.machinery doesn't exists in Python 2 so we will use imp (deprecated in Python 3)
+        import imp
+        return imp.load_source(name, path)
+
+
+idf_path = os.environ['IDF_PATH']
+constants_pb2 = _load_source("constants_pb2", idf_path + "/components/protocomm/python/constants_pb2.py")
+local_ctrl_pb2 = _load_source("esp_local_ctrl_pb2", idf_path + "/components/esp_local_ctrl/python/esp_local_ctrl_pb2.py")
+
+
+def get_prop_count_request():
+    req = local_ctrl_pb2.LocalCtrlMessage()
+    req.msg = local_ctrl_pb2.TypeCmdGetPropertyCount
+    payload = local_ctrl_pb2.CmdGetPropertyCount()
+    req.cmd_get_prop_count.MergeFrom(payload)
+    return req.SerializeToString()
+
+
+def get_prop_count_response(response_data):
+    resp = local_ctrl_pb2.LocalCtrlMessage()
+    resp.ParseFromString(tobytes(response_data))
+    if (resp.resp_get_prop_count.status == 0):
+        return resp.resp_get_prop_count.count
+    else:
+        return 0
+
+
+def get_prop_vals_request(indices):
+    req = local_ctrl_pb2.LocalCtrlMessage()
+    req.msg = local_ctrl_pb2.TypeCmdGetPropertyValues
+    payload = local_ctrl_pb2.CmdGetPropertyValues()
+    payload.indices.extend(indices)
+    req.cmd_get_prop_vals.MergeFrom(payload)
+    return req.SerializeToString()
+
+
+def get_prop_vals_response(response_data):
+    resp = local_ctrl_pb2.LocalCtrlMessage()
+    resp.ParseFromString(tobytes(response_data))
+    results = []
+    if (resp.resp_get_prop_vals.status == 0):
+        for prop in resp.resp_get_prop_vals.props:
+            results += [{
+                "name": prop.name,
+                "type": prop.type,
+                "flags": prop.flags,
+                "value": tobytes(prop.value)
+            }]
+    return results
+
+
+def set_prop_vals_request(indices, values):
+    req = local_ctrl_pb2.LocalCtrlMessage()
+    req.msg = local_ctrl_pb2.TypeCmdSetPropertyValues
+    payload = local_ctrl_pb2.CmdSetPropertyValues()
+    for i, v in zip(indices, values):
+        prop = payload.props.add()
+        prop.index = i
+        prop.value = v
+    req.cmd_set_prop_vals.MergeFrom(payload)
+    return req.SerializeToString()
+
+
+def set_prop_vals_response(response_data):
+    resp = local_ctrl_pb2.LocalCtrlMessage()
+    resp.ParseFromString(tobytes(response_data))
+    return (resp.resp_set_prop_vals.status == 0)

+ 1 - 0
examples/protocols/esp_local_ctrl/sdkconfig.defaults

@@ -0,0 +1 @@
+CONFIG_ESP_HTTPS_SERVER_ENABLE=y

+ 1 - 1
tools/esp_prov/esp_prov.py

@@ -61,7 +61,7 @@ def get_transport(sel_transport, softap_endpoint=None, ble_devname=None):
     try:
         tp = None
         if (sel_transport == 'softap'):
-            tp = transport.Transport_Softap(softap_endpoint)
+            tp = transport.Transport_HTTP(softap_endpoint)
         elif (sel_transport == 'ble'):
             # BLE client is now capable of automatically figuring out
             # the primary service from the advertisement data and the

+ 1 - 1
tools/esp_prov/transport/__init__.py

@@ -14,5 +14,5 @@
 #
 
 from .transport_console import *  # noqa: F403, F401
-from .transport_softap import *   # noqa: F403, F401
+from .transport_http import *     # noqa: F403, F401
 from .transport_ble import *      # noqa: F403, F401

+ 14 - 3
tools/esp_prov/transport/transport_softap.py → tools/esp_prov/transport/transport_http.py

@@ -16,14 +16,25 @@
 from __future__ import print_function
 from future.utils import tobytes
 
+import socket
 import http.client
+import ssl
 
 from .transport import Transport
 
 
-class Transport_Softap(Transport):
-    def __init__(self, url):
-        self.conn = http.client.HTTPConnection(url, timeout=30)
+class Transport_HTTP(Transport):
+    def __init__(self, hostname, certfile=None):
+        try:
+            socket.gethostbyname(hostname.split(':')[0])
+        except socket.gaierror:
+            raise RuntimeError("Unable to resolve hostname :" + hostname)
+
+        if certfile is None:
+            self.conn = http.client.HTTPConnection(hostname, timeout=30)
+        else:
+            ssl_ctx = ssl.create_default_context(cafile=certfile)
+            self.conn = http.client.HTTPSConnection(hostname, context=ssl_ctx, timeout=30)
         self.headers = {"Content-type": "application/x-www-form-urlencoded","Accept": "text/plain"}
 
     def _send_post_request(self, path, data):