diff --git a/cli b/cli index e61d952..85e7124 160000 --- a/cli +++ b/cli @@ -1 +1 @@ -Subproject commit e61d9528c152ceb345d7a37535e8ffb2d004e832 +Subproject commit 85e712416121ea739e320e301058ebc97ca862f8 diff --git a/components/esp_rainmaker/CMakeLists.txt b/components/esp_rainmaker/CMakeLists.txt index 2af9e66..987723a 100644 --- a/components/esp_rainmaker/CMakeLists.txt +++ b/components/esp_rainmaker/CMakeLists.txt @@ -10,7 +10,9 @@ set(core_srcs "src/core/esp_rmaker_core.c" "src/core/esp_rmaker_user_mapping.pb-c.c" "src/core/esp_rmaker_user_mapping.c" "src/core/esp_rmaker_schedule.c" - "src/core/esp_rmaker_scenes.c") + "src/core/esp_rmaker_scenes.c" + "src/core/esp_rmaker_cmd_resp_manager.c" + ) set(priv_req protobuf-c json_parser json_generator wifi_provisioning nvs_flash esp_http_client app_update esp-tls mbedtls esp_https_ota console esp_local_ctrl esp_https_server mdns esp_schedule efuse) diff --git a/components/esp_rainmaker/Kconfig.projbuild b/components/esp_rainmaker/Kconfig.projbuild index 2a4a947..6c7f9a6 100644 --- a/components/esp_rainmaker/Kconfig.projbuild +++ b/components/esp_rainmaker/Kconfig.projbuild @@ -242,4 +242,24 @@ menu "ESP RainMaker Config" endmenu + menu "ESP RainMaker Command-Response" + + config ESP_RMAKER_CMD_RESP_ENABLE + bool "Enable Command-Response Module" + default y + help + Enable the ESP RainMaker Command-Response module for semi-synchronous communication. Please refer the RainMaker documents + for additional information. + + config ESP_RMAKER_CMD_RESP_TEST_ENABLE + bool "Enable Command-Response Testing" + default n + depends on ESP_RMAKER_CMD_RESP_ENABLE + help + Enable testing for Command-Response module. This enables triggering commands and parsing response from the node itself, + rather than receiving the commands from cloud. C API or the serial console can be used to trigger the commands. + This should be enabled only while testing commands, but should always be disabled in production firmware. + + endmenu + endmenu diff --git a/components/esp_rainmaker/include/esp_rmaker_core.h b/components/esp_rainmaker/include/esp_rmaker_core.h index eb01e6d..4fb9527 100644 --- a/components/esp_rainmaker/include/esp_rmaker_core.h +++ b/components/esp_rainmaker/include/esp_rmaker_core.h @@ -931,6 +931,20 @@ bool esp_rmaker_local_ctrl_service_started(void); * @return error on failure */ esp_err_t esp_rmaker_ota_enable_default(void); + +/* + * Send a command to self (TESTING only) + * + * This is to be passed as an argument to esp_rmaker_cmd_resp_test_send(). + * + * @param[in] cmd The TLV encoded command data. + * @param[in] cmd_len Length of the command data. + * @param[in] priv_data Private data passed to esp_rmaker_cmd_resp_test_send(). + * + * @return ESP_OK on success + * @return error on failure + */ +esp_err_t esp_rmaker_test_cmd_resp(const void *cmd, size_t cmd_len, void *priv_data); #ifdef __cplusplus } #endif diff --git a/components/esp_rainmaker/src/console/esp_rmaker_commands.c b/components/esp_rainmaker/src/console/esp_rmaker_commands.c index 913d0f1..23cd7d7 100644 --- a/components/esp_rainmaker/src/console/esp_rmaker_commands.c +++ b/components/esp_rainmaker/src/console/esp_rmaker_commands.c @@ -34,6 +34,7 @@ #include #include #include +#include #include @@ -402,6 +403,31 @@ static void register_time_commands() esp_console_cmd_register(&tz_set_cmd); } +static int cmd_resp_cli_handler(int argc, char *argv[]) +{ + if (argc != 5) { + printf("Usage: cmd \n"); + return -1; + } + char *req_id = argv[1]; + uint8_t user_role = atoi(argv[2]); + uint16_t cmd = atoi(argv[3]); + esp_rmaker_cmd_resp_test_send(req_id, user_role, cmd, (void *)argv[4], strlen(argv[4]), esp_rmaker_test_cmd_resp, NULL); + return 0; +} + +static void register_cmd_resp_command() +{ + const esp_console_cmd_t cmd_resp_cmd = { + .command = "cmd", + .help = "Send command to command-response module. Usage cmd ", + .func = &cmd_resp_cli_handler, + }; + ESP_LOGI(TAG, "Registering command: %s", cmd_resp_cmd.command); + esp_console_cmd_register(&cmd_resp_cmd); +} + + void register_commands() { register_generic_debug_commands(); @@ -410,4 +436,5 @@ void register_commands() register_get_node_id(); register_wifi_prov(); register_time_commands(); + register_cmd_resp_command(); } diff --git a/components/esp_rainmaker/src/core/esp_rmaker_cmd_resp_manager.c b/components/esp_rainmaker/src/core/esp_rmaker_cmd_resp_manager.c new file mode 100644 index 0000000..55ceee2 --- /dev/null +++ b/components/esp_rainmaker/src/core/esp_rmaker_cmd_resp_manager.c @@ -0,0 +1,113 @@ +// Copyright 2020 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 +#include +#include +#include +#include +#include "esp_rmaker_internal.h" + +#define TO_NODE_TOPIC_SUFFIX "to-node" +#define FROM_NODE_TOPIC_SUFFIX "from-node" + +static const char *TAG = "esp_rmaker_cmd_resp"; + +#ifdef CONFIG_ESP_RMAKER_CMD_RESP_TEST_ENABLE + +/* These are for testing purpose only */ +static void esp_rmaker_resp_callback(const char *topic, void *payload, size_t payload_len, void *priv_data) +{ + esp_rmaker_cmd_resp_parse_response(payload, payload_len, priv_data); + +} + +esp_err_t esp_rmaker_test_cmd_resp(const void *cmd, size_t cmd_len, void *priv_data) +{ + if (!cmd) { + ESP_LOGE(TAG, "No command data to send."); + return ESP_ERR_INVALID_ARG; + } + char publish_topic[100]; + snprintf(publish_topic, sizeof(publish_topic), "node/%s/%s", esp_rmaker_get_node_id(), TO_NODE_TOPIC_SUFFIX); + return esp_rmaker_mqtt_publish(publish_topic, cmd, cmd_len, RMAKER_MQTT_QOS1, NULL); +} + +static esp_err_t esp_rmaker_cmd_resp_test_enable(void) +{ + char subscribe_topic[100]; + snprintf(subscribe_topic, sizeof(subscribe_topic), "node/%s/%s", + esp_rmaker_get_node_id(), FROM_NODE_TOPIC_SUFFIX); + esp_err_t err = esp_rmaker_mqtt_subscribe(subscribe_topic, esp_rmaker_resp_callback, RMAKER_MQTT_QOS1, NULL); + if(err != ESP_OK) { + ESP_LOGE(TAG, "Failed to subscribe to %s. Error %d", subscribe_topic, err); + return ESP_FAIL; + } + ESP_LOGI(TAG, "Command-Response test support enabled."); + return ESP_OK; +} + +#else +esp_err_t esp_rmaker_test_cmd_resp(const void *cmd, size_t cmd_len, void *priv_data) +{ + ESP_LOGE(TAG, "Please enable CONFIG_ESP_RMAKER_CMD_RESP_TEST_ENABLE to use this."); + return ESP_FAIL; +} +#endif /* !CONFIG_ESP_RMAKER_CMD_RESP_TEST_ENABLE */ + +#ifdef CONFIG_ESP_RMAKER_CMD_RESP_ENABLE + +static void esp_rmaker_cmd_callback(const char *topic, void *payload, size_t payload_len, void *priv_data) +{ + void *output = NULL; + size_t output_len = 0; + /* Any command data received is directly sent to the command response framework and on success, + * the response (if any) is sent back to the MQTT Broker. + */ + if (esp_rmaker_cmd_response_handler(payload, payload_len, &output, &output_len) == ESP_OK) { + if (output) { + char publish_topic[100]; + snprintf(publish_topic, sizeof(publish_topic), "node/%s/%s", esp_rmaker_get_node_id(), FROM_NODE_TOPIC_SUFFIX); + if (esp_rmaker_mqtt_publish(publish_topic, output, output_len, RMAKER_MQTT_QOS1, NULL) != ESP_OK) { + ESP_LOGE(TAG, "Failed to publish reponse."); + } + free(output); + } else { + ESP_LOGE(TAG, "No output generated by command-response handler."); + } + } +} + +esp_err_t esp_rmaker_cmd_response_enable(void) +{ + ESP_LOGI(TAG, "Enabling Command-Response Module."); + char subscribe_topic[100]; + snprintf(subscribe_topic, sizeof(subscribe_topic), "node/%s/%s", + esp_rmaker_get_node_id(), TO_NODE_TOPIC_SUFFIX); + esp_err_t err = esp_rmaker_mqtt_subscribe(subscribe_topic, esp_rmaker_cmd_callback, RMAKER_MQTT_QOS1, NULL); + if(err != ESP_OK) { + ESP_LOGE(TAG, "Failed to subscribe to %s. Error %d", subscribe_topic, err); + return ESP_FAIL; + } +#ifdef CONFIG_ESP_RMAKER_CMD_RESP_TEST_ENABLE + esp_rmaker_cmd_resp_test_enable(); +#endif /* CONFIG_ESP_RMAKER_CMD_RESP_TEST_ENABLE */ + return ESP_OK; +} +#else +esp_err_t esp_rmaker_cmd_response_enable(void) +{ + ESP_LOGW(TAG, "Command-Response Module not enabled. Set CONFIG_ESP_RMAKER_CMD_RESP_ENABLE=y to use this."); + return ESP_OK; +} +#endif /* !CONFIG_ESP_RMAKER_CMD_RESP_ENABLE */ diff --git a/components/esp_rainmaker/src/core/esp_rmaker_core.c b/components/esp_rainmaker/src/core/esp_rmaker_core.c index 57dcf58..5d7a802 100644 --- a/components/esp_rainmaker/src/core/esp_rmaker_core.c +++ b/components/esp_rainmaker/src/core/esp_rmaker_core.c @@ -137,8 +137,9 @@ static void esp_rmaker_event_handler(void* arg, esp_event_base_t event_base, } else if (event_base == RMAKER_EVENT && (event_id == RMAKER_EVENT_USER_NODE_MAPPING_DONE || event_id == RMAKER_EVENT_USER_NODE_MAPPING_RESET)) { - esp_rmaker_params_mqtt_init(); esp_event_handler_unregister(RMAKER_EVENT, event_id, &esp_rmaker_event_handler); + esp_rmaker_params_mqtt_init(); + esp_rmaker_cmd_response_enable(); } } @@ -324,6 +325,11 @@ static void esp_rmaker_task(void *data) ESP_LOGE(TAG, "Aborting!!!"); goto rmaker_end; } + err = esp_rmaker_cmd_response_enable(); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Aborting!!!"); + goto rmaker_end; + } } else { /* If network is connected without even starting the user-node mapping workflow, * it could mean that some incorrect app was used to provision the device. Even diff --git a/components/esp_rainmaker/src/core/esp_rmaker_internal.h b/components/esp_rainmaker/src/core/esp_rmaker_internal.h index 641a2e5..92cb1e7 100644 --- a/components/esp_rainmaker/src/core/esp_rmaker_internal.h +++ b/components/esp_rainmaker/src/core/esp_rmaker_internal.h @@ -116,3 +116,4 @@ static inline esp_err_t esp_rmaker_post_event(esp_rmaker_event_t event_id, void* return esp_event_post(RMAKER_EVENT, event_id, data, data_size, portMAX_DELAY); } esp_rmaker_state_t esp_rmaker_get_state(void); +esp_err_t esp_rmaker_cmd_response_enable(void); diff --git a/components/rmaker_common b/components/rmaker_common index b40a39e..96374c5 160000 --- a/components/rmaker_common +++ b/components/rmaker_common @@ -1 +1 @@ -Subproject commit b40a39e06fc449beed9f57ced2417e9a01aad287 +Subproject commit 96374c52ee1e663f17772ee2ded9f9a3dc32abf3 diff --git a/docs/Doxyfile b/docs/Doxyfile index 3bfc4bf..688b010 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -25,6 +25,7 @@ INPUT = \ ../components/esp_rainmaker/include/esp_rmaker_core.h \ ../components/esp_rainmaker/include/esp_rmaker_user_mapping.h \ ../components/esp_rainmaker/include/esp_rmaker_schedule.h \ + ../components/esp_rainmaker/include/esp_rmaker_scenes.h \ ## RainMaker Standard Types ../components/esp_rainmaker/include/esp_rmaker_standard_types.h \ ../components/esp_rainmaker/include/esp_rmaker_standard_params.h \ @@ -40,7 +41,9 @@ INPUT = \ ../components/rmaker_common/include/esp_rmaker_common_events.h \ ../components/rmaker_common/include/esp_rmaker_factory.h \ ../components/rmaker_common/include/esp_rmaker_work_queue.h \ - ../components/rmaker_common/include/esp_rmaker_utils.h + ../components/rmaker_common/include/esp_rmaker_utils.h \ + ../components/rmaker_common/include/esp_rmaker_cmd_resp.h \ + ../components/rmaker_common/include/esp_rmaker_mqtt_glue.h ## Get warnings for functions that have no documentation for their parameters or return value ##