Initial commit.
Some checks failed
Sync remain PRs to Jira / Sync PRs to Jira (push) Has been cancelled

This commit is contained in:
2025-04-30 16:33:57 +03:00
commit 34cf3ec285
193 changed files with 25742 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
name: Push components to Espressif Component Registry
on:
push:
branches:
- master
jobs:
upload_components:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: "recursive"
- name: Upload components to component registry
uses: espressif/upload-components-ci-action@v1
with:
namespace: "espressif"
name: "rmaker_common"
api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }}

View File

@@ -0,0 +1,23 @@
set(srcs "src/work_queue.c" "src/factory.c" "src/time.c" "src/timezone.c" "src/utils.c"
"src/cmd_resp.c" "src/console/rmaker_common_cmds.c" "src/console/rmaker_console.c")
set(priv_req mqtt nvs_flash console nvs_flash esp_wifi driver)
set(requires esp_event)
# esp_timer component was introduced in v4.2
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER "4.1")
list(APPEND priv_req esp_timer)
endif()
#if(CONFIG_ESP_RMAKER_LIB_ESP_MQTT)
list(APPEND srcs "src/esp-mqtt/esp-mqtt-glue.c")
#endif()
if(CONFIG_ESP_RMAKER_MQTT_SEND_USERNAME)
list(APPEND srcs "src/create_APN3_PPI_string.c")
endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS
REQUIRES ${requires}
PRIV_REQUIRES ${priv_req})

View File

@@ -0,0 +1,181 @@
menu "ESP RainMaker Common"
choice ESP_RMAKER_MQTT_GLUE_LIB
bool "MQTT Library"
default ESP_RMAKER_LIB_ESP_MQTT
help
MQTT Library to be used
config ESP_RMAKER_LIB_ESP_MQTT
bool "ESP-MQTT"
config ESP_RMAKER_LIB_AWS_IOT
bool "AWS-IOT"
endchoice
config ESP_RMAKER_MQTT_GLUE_LIB
int
default 1 if ESP_RMAKER_LIB_ESP_MQTT
default 2 if ESP_RMAKER_LIB_AWS_IOT
choice ESP_RMAKER_MQTT_PORT
bool "MQTT Port"
default ESP_RMAKER_MQTT_PORT_443
help
ESP RainMaker MQTT Broker can be connected to either on Port 8883 or port 443.
Port 443 is recommended as it is generally not blocked by any firewalls,
since it is standard HTTPS port.
config ESP_RMAKER_MQTT_PORT_443
bool "443"
config ESP_RMAKER_MQTT_PORT_8883
bool "8883"
endchoice
config ESP_RMAKER_MQTT_PORT
int
default 1 if ESP_RMAKER_MQTT_PORT_443
default 2 if ESP_RMAKER_MQTT_PORT_8883
config ESP_RMAKER_MQTT_PERSISTENT_SESSION
bool "Use Persisent MQTT sessions"
default n
help
Use persistent MQTT sessions. This improves reliability as QOS1 messages missed
out due to some network issue are received after the MQTT reconnects. The broker
caches messages for a period of upto 1 hour. However, a side-effect of this is that
messages can be received at unexpected time. Enable this option only if it suits
your use case. Please read MQTT specs to understand more about persistent sessions
and the cleanSession flag.
config ESP_RMAKER_MQTT_SEND_USERNAME
bool "Send MQTT Username"
default y
help
Send a Username during MQTT Connect. This is generally required only for tracking
the MQTT client types, platform, SDK, etc. in AWS.
config ESP_RMAKER_MQTT_PRODUCT_NAME
string "Product Name"
depends on ESP_RMAKER_MQTT_SEND_USERNAME
default "RMDev"
help
Approved AWS product name. Please get in touch with your Espressif/AWS representative for more info.
config ESP_RMAKER_MQTT_PRODUCT_VERSION
string "Product Version"
depends on ESP_RMAKER_MQTT_SEND_USERNAME
default "1x0"
help
Approved AWS product version. Please get in touch with your Espressif/AWS representative for more info.
config ESP_RMAKER_MQTT_PRODUCT_SKU
string "Product SKU"
depends on ESP_RMAKER_MQTT_SEND_USERNAME
default "EX00"
help
Product SKU. Please get in touch with your Espressif/AWS representative for more info.
config ESP_RMAKER_MQTT_USE_CERT_BUNDLE
bool "Use Certificate Bundle"
default y
help
Use Certificate Bundle for server authentication. Enabling this is recommended to safeguard
against any changes in the server certificates in future. This has an impact on the binary
size as well as heap requirement.
config ESP_RMAKER_MAX_MQTT_SUBSCRIPTIONS
int "Maximum number of MQTT Subscriptions"
default 10
help
This value controls the maximum number of topics that the device can subscribe to.
config ESP_RMAKER_MQTT_KEEP_ALIVE_INTERVAL
int "MQTT Keep Alive Internal"
default 120
range 30 1200
help
MQTT Keep Alive Interval. Note that it can take upto 1.5x of keep alive interval for a device
to be reported by offline by the MQTT Broker. Change this only if required.
choice ESP_RMAKER_NETWORK_PROTOCOL_TYPE
prompt "ESP RainMaker Network Type"
default ESP_RMAKER_NETWORK_OVER_WIFI
help
Network protocol type over which the ESP RainMaker will run.
config ESP_RMAKER_NETWORK_OVER_WIFI
# ESP_WIFI_ENABLED was introduced in IDF v5.0
depends on IDF_TARGET_ESP32 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3 || ESP_WIFI_ENABLED
bool "ESP RainMaker over Wi-Fi"
config ESP_RMAKER_NETWORK_OVER_THREAD
depends on OPENTHREAD_ENABLED
bool "ESP RainMaker over Thread"
endchoice
config ESP_RMAKER_WORK_QUEUE_TASK_STACK
int "ESP RainMaker Work Queue Task stack"
default 4096
help
Stack size for the ESP RainMaker Work Queue Task.
config ESP_RMAKER_WORK_QUEUE_TASK_PRIORITY
int "ESP RainMaker Work Queue Task priority"
default 5
help
Priority for the ESP RainMaker Work Queue Task. Not recommended to be changed
unless you really need it.
config ESP_RMAKER_FACTORY_PARTITION_NAME
string "ESP RainMaker Factory Partition Name"
default "fctry"
help
Factory NVS Partition name which will have the MQTT connectivity credentials.
config ESP_RMAKER_FACTORY_NAMESPACE
string "ESP RainMaker Factory Namespace"
default "rmaker_creds"
help
Namespace in the Factory NVS Partition name which will have the MQTT
connectivity credentials.
config ESP_RMAKER_ENCRYPT_FACTORY_PARTITION
bool "Encrypt Rainmaker Factory partition"
default false
depends on NVS_ENCRYPTION
help
Enable this option if the factory partition is pre-encrypted before flashing and the encryption keys
are flashed in partition with subtype nvs_keys specified by CONFIG_ESP_RMAKER_FACTORY_NVS_KEYS_PARTITION_NAME.
If an unencrypted factory partition is flashed, the device would not be able to read its data and
the partition would be considered empty.
If nvs encryption keys are not flashed onto device, they would be auto-generated and any previous data
in nvs/factory partition would become invalid.
config ESP_RMAKER_FACTORY_NVS_KEYS_PARTITION_NAME
string "ESP Rainmaker Factory NVS keys partition label"
default "nvs_key"
depends on ESP_RMAKER_ENCRYPT_FACTORY_PARTITION
help
Label of the partition of subtype "nvs_keys" used for encrypting/decrypting Rainmaker factory partition.
config ESP_RMAKER_DEF_TIMEZONE
string "Default Timezone"
default "Asia/Shanghai"
help
Default Timezone to use. Eg. "Asia/Shanghai", "America/Los_Angeles".
Check documentation for complete list of valid values. This value
will be used only if no timezone is set using the C APIs.
config ESP_RMAKER_SNTP_SERVER_NAME
string "ESP RainMaker SNTP Server Name"
default "pool.ntp.org"
help
Default SNTP Server which is used for time synchronization.
config ESP_RMAKER_MAX_COMMANDS
int "Maximum commands supported for command-response"
default 10
help
Maximum number of commands supported by the command-response framework
endmenu

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@@ -0,0 +1,9 @@
# ESP RainMaker Common modules
This component consists of some common modules used by ESP RainMaker and ESP RainMaker Diagnostics repos.
Currently, it consists of
- MQTT glue layer
- Timing APIs (SNTP helpers, timezone, etc.)
- Factory NVS helpers
- Work Queue

View File

@@ -0,0 +1,5 @@
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := src
ifdef CONFIG_ESP_RMAKER_LIB_ESP_MQTT
COMPONENT_SRCDIRS += src/esp-mqtt
endif

View File

@@ -0,0 +1,3 @@
version: "1.4.6"
description: ESP RainMaker firmware agent Common component
url: https://github.com/espressif/esp-rainmaker-common

View File

@@ -0,0 +1,184 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <esp_err.h>
#include <esp_event.h>
#ifdef __cplusplus
extern "C"
{
#endif
/* Super Admin User Flag*/
#define ESP_RMAKER_USER_ROLE_SUPER_ADMIN (1 << 0)
/** Primary User Flag */
#define ESP_RMAKER_USER_ROLE_PRIMARY_USER (1 << 1)
/** Secondary User Flag */
#define ESP_RMAKER_USER_ROLE_SECONDARY_USER (1 << 2)
/** RainMaker Command Response TLV8 Types */
typedef enum {
/** Request Id : Variable length string, max 32 characters*/
ESP_RMAKER_TLV_TYPE_REQ_ID = 1,
/** User Role : 1 byte */
ESP_RMAKER_TLV_TYPE_USER_ROLE,
/** Status : 1 byte */
ESP_RMAKER_TLV_TYPE_STATUS,
/** Timestamp : TBD */
ESP_RMAKER_TLV_TYPE_TIMESTAMP,
/** Command : 2 bytes*/
ESP_RMAKER_TLV_TYPE_CMD,
/** Data : Variable length */
ESP_RMAKER_TLV_TYPE_DATA
} esp_rmaker_tlv_type_t;
/* RainMaker Command Response Status */
typedef enum {
/** Success */
ESP_RMAKER_CMD_STATUS_SUCCESS = 0,
/** Generic Failure */
ESP_RMAKER_CMD_STATUS_FAILED,
/** Invalid Command */
ESP_RMAKER_CMD_STATUS_CMD_INVALID,
/** Authentication Failed */
ESP_RMAKER_CMD_STATUS_AUTH_FAIL,
/** Command not found */
ESP_RMAKER_CMD_STATUS_NOT_FOUND,
/** Last status value */
ESP_RMAKER_CMD_STATUS_MAX,
} esp_rmaker_cmd_status_t;
#define REQ_ID_LEN 32
typedef struct {
/** Command id */
uint16_t cmd;
/** Request id */
char req_id[REQ_ID_LEN];
/** User Role */
uint8_t user_role;
} esp_rmaker_cmd_ctx_t;
typedef enum {
/** Standard command: Set Parameters */
ESP_RMAKER_CMD_TYPE_SET_PARAMS = 1,
/** Last Standard command */
ESP_RMAKER_CMD_STANDARD_LAST = 0xfff,
/** Custom commands can start from here */
ESP_RMAKER_CMD_CUSTOM_START = 0x1000
} esp_rmaker_cmd_t;
/** Command Response Handler
*
* If any command data is received from any of the supported transports (which are outside the scope of this core framework),
* this function should be called to handle it and fill in the response.
*
* @param[in] input Pointer to input data.
* @param[in] input_len data len.
* @param[in] output Pointer to output data which should be set by the handler.
* @param[out] output_len Length of output generated.
*
* @return ESP_OK on success.
* @return error on failure.
*/
esp_err_t esp_rmaker_cmd_response_handler(const void *input, size_t input_len, void **output, size_t *output_len);
/** Prototype for Command Handler
*
* The handler to be invoked when a given command is received.
*
* @param[in] in_data Pointer to input data.
* @param[in] in_len data len.
* @param[in] out_data Pointer to output data which should be set by the handler.
* @param[out] out_len Length of output generated.
* @param[in] ctx Command Context.
* @param[in] priv Private data, if specified while registering command.
*
* @return ESP_OK on success.
* @return error on failure.
*/
typedef esp_err_t (*esp_rmaker_cmd_handler_t)(const void *in_data, size_t in_len, void **out_data, size_t *out_len, esp_rmaker_cmd_ctx_t *ctx, void *priv);
/** Register a new command
*
* @param[in] cmd Command Identifier. Custom commands should start beyond ESP_RMAKER_CMD_STANDARD_LAST
* @param[in] access User Access for the command. Can be an OR of the various user role flags like ESP_RMAKER_USER_ROLE_SUPER_ADMIN,
* ESP_RMAKER_USER_ROLE_PRIMARY_USER and ESP_RMAKER_USER_ROLE_SECONDARY_USER
* @param[in] handler The handler to be invoked when the given command is received.
* @param[in] free_on_return Flag to indicate of the framework should free the output after it has been sent as response.
* @param[in] priv Optional private data to be passed to the handler.
*
* @return ESP_OK on success.
* @return error on failure.
*/
esp_err_t esp_rmaker_cmd_register(uint16_t cmd, uint8_t access, esp_rmaker_cmd_handler_t handler, bool free_on_return, void *priv);
/** De-register a command
*
* @param[in] cmd Command Identifier. Custom commands should start beyond ESP_RMAKER_CMD_STANDARD_LAST
*
* @return ESP_OK on success.
* @return error on failure.
*/
esp_err_t esp_rmaker_cmd_deregister(uint16_t cmd);
/* Prepare an empty command response
*
* This can be used to populate the request to be sent to get all pending commands
*
* @param[in] out_data Pointer to output data. This function will allocate memory and set this pointer
* accordingly.
* @param[out] out_len Length of output generated.
*
* @return ESP_OK on success.
* @return error on failure.
*/
esp_err_t esp_rmaker_cmd_prepare_empty_response(void **output, size_t *output_len);
/** Prototype for Command sending function (TESTING only)
*
* @param[in] data Pointer to the data to be sent.
* @param[in] data_len Size of data to be sent.
* @param[in] priv Private data, if applicable.
*
* @return ESP_OK on success.
* @return error on failure.
*/
typedef esp_err_t (*esp_rmaker_cmd_send_t)(const void *data, size_t data_len, void *priv);
/** Send Test command (TESTING only)
*
* @param[in] req_id NULL terminated request id of max 32 characters.
* @param[in] role User Role flag.
* @param[in] cmd Command Identifier.
* @param[in] data Pointer to data for the command.
* @param[in] data_size Size of the data.
* @param[in] cmd_send Transport specific function to send the command data.
* @param[in] priv Private data (if any) to be sent to cmd_send.
*
* @return ESP_OK on success.
* @return error on failure.
*/
esp_err_t esp_rmaker_cmd_resp_test_send(const char *req_id, uint8_t role, uint16_t cmd, const void *data, size_t data_size, esp_rmaker_cmd_send_t cmd_send, void *priv);
/** Parse response (TESTING only)
*
* @param[in] response Pointer to the response received
* @param[in] response_len Length of the response
* @param[in] priv Private data, if any. Can be NULL.
*
* @return ESP_OK on success.
* @return error on failure.
*/
esp_err_t esp_rmaker_cmd_resp_parse_response(const void *response, size_t response_len, void *priv);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <esp_err.h>
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Initialize console
*
* Initializes serial console and adds basic commands.
*
* @return ESP_OK on success.
* @return error in case of failures.
*/
esp_err_t esp_rmaker_common_console_init(void);
/* Reference for adding custom console commands:
#include <esp_console.h>
static int command_console_handler(int argc, char *argv[])
{
// Command code here
}
static void register_console_command()
{
const esp_console_cmd_t cmd = {
.command = "<command_name>",
.help = "<help_details>",
.func = &command_console_handler,
};
ESP_ERROR_CHECK(esp_console_cmd_register(&cmd));
}
*/
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,60 @@
// Copyright 2021 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
#include <stdint.h>
#include <esp_event.h>
#ifdef __cplusplus
extern "C"
{
#endif
/** ESP RainMaker Common Event Base */
ESP_EVENT_DECLARE_BASE(RMAKER_COMMON_EVENT);
typedef enum {
/** Node reboot has been triggered. The associated event data is the time in seconds
* (type: uint8_t) after which the node will reboot. Note that this time may not be
* accurate as the events are received asynchronously.*/
RMAKER_EVENT_REBOOT,
/** Wi-Fi credentials reset. Triggered after calling esp_rmaker_wifi_reset() */
RMAKER_EVENT_WIFI_RESET,
/** Node reset to factory defaults. Triggered after calling esp_rmaker_factory_reset() */
RMAKER_EVENT_FACTORY_RESET,
/** Connected to MQTT Broker */
RMAKER_MQTT_EVENT_CONNECTED,
/** Disconnected from MQTT Broker */
RMAKER_MQTT_EVENT_DISCONNECTED,
/** MQTT message published successfully.
* Event data will contain the message ID (integer) of published message.
*/
RMAKER_MQTT_EVENT_PUBLISHED,
/** POSIX Timezone Changed. Associated data would be NULL terminated POSIX Timezone
* Eg. "PST8PDT,M3.2.0,M11.1.0" */
RMAKER_EVENT_TZ_POSIX_CHANGED,
/** Timezone Changed. Associated data would be NULL terminated Timezone.
* Eg. "America/Los_Angeles"
* Note that whenever this event is received, the RMAKER_EVENT_TZ_POSIX_CHANGED event
* will also be received, but not necessarily vice versa.
*/
RMAKER_EVENT_TZ_CHANGED,
/**
* MQTT message deleted from the outbox if the message couldn't have been sent and acknowledged.
* Event data will contain the message ID (integer) of deleted message.
* Valid only if CONFIG_MQTT_REPORT_DELETED_MESSAGES is enabled.
*/
RMAKER_MQTT_EVENT_MSG_DELETED,
} esp_rmaker_common_event_t;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,73 @@
// Copyright 2021 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
#include <stdint.h>
#include <esp_err.h>
#include <esp_event.h>
#ifdef __cplusplus
extern "C"
{
#endif
/** Initialize Factory NVS
*
* This initializes the Factory NVS partition which will store data
* that should not be cleared even after a reset to factory.
*
* @return ESP_OK on success.
* @return error on failure.
*/
esp_err_t esp_rmaker_factory_init(void);
/** Get value from factory NVS
*
* This will search for the specified key in the Factory NVS partition,
* allocate the required memory to hold it, copy the value and return
* the pointer to it. It is responsibility of the caller to free the
* memory when the value is no more required.
*
* @param[in] key The key of the value to be read from factory NVS.
*
* @return pointer to the value on success.
* @return NULL on failure.
*/
void *esp_rmaker_factory_get(const char *key);
/** Get size of value from factory NVS
*
* This will search for the specified key in the Factory NVS partition,
* and return the size of the value associated with the key.
*
* @param[in] key The key of the value to be read from factory NVS.
*
* @return size of the value on success.
* @return 0 on failure.
*/
size_t esp_rmaker_factory_get_size(const char *key);
/** Set a value in factory NVS
*
* This will write the value for the specified key into factory NVS.
*
* @param[in] key The key for the value to be set in factory NVS.
* @param[in] value Pointer to the value.
* @param[in] len Length of the value.
*
* @return ESP_OK on success.
* @return error on failure.
*/
esp_err_t esp_rmaker_factory_set(const char *key, void *value, size_t len);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,177 @@
// Copyright 2021 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
#include <stdint.h>
#include <esp_err.h>
#include <esp_event.h>
#ifdef __cplusplus
extern "C"
{
#endif
#define RMAKER_MQTT_QOS0 0
#define RMAKER_MQTT_QOS1 1
/** MQTT Connection parameters */
typedef struct {
/** MQTT Host */
char *mqtt_host;
/** Client ID */
char *client_id;
/** Client Certificate in DER format or NULL-terminated PEM format */
char *client_cert;
/** Client Certificate length */
size_t client_cert_len;
/** Client Key in DER format or NULL-terminated PEM format */
char *client_key;
/** Client Key length */
size_t client_key_len;
/** Server Certificate in DER format or NULL-terminated PEM format */
char *server_cert;
/** Server Certificate length */
size_t server_cert_len;
/** Pointer for digital signature peripheral context */
void *ds_data;
} esp_rmaker_mqtt_conn_params_t;
/** MQTT Get Connection Parameters function prototype
*
* @return Pointer to \ref esp_rmaker_mqtt_conn_params_t on success.
* @return NULL on failure.
*/
typedef esp_rmaker_mqtt_conn_params_t *(*esp_rmaker_mqtt_get_conn_params_t)(void);
/** MQTT Subscribe callback prototype
*
* @param[in] topic Topic on which the message was received
* @param[in] payload Data received in the message
* @param[in] payload_len Length of the data
* @param[in] priv_data The private data passed during subscription
*/
typedef void (*esp_rmaker_mqtt_subscribe_cb_t)(const char *topic, void *payload, size_t payload_len, void *priv_data);
/** MQTT Init function prototype
*
* @param[in] conn_params The MQTT connection parameters. If NULL is passed, it should internally use the
* \ref esp_rmaker_mqtt_get_conn_params call if registered.
*
* @return ESP_OK on success.
* @return error in case of any error.
*/
typedef esp_err_t (*esp_rmaker_mqtt_init_t)(esp_rmaker_mqtt_conn_params_t *conn_params);
/** MQTT Deinit function prototype
*
* Call this function after MQTT has disconnected.
*/
typedef void (*esp_rmaker_mqtt_deinit_t)(void);
/** MQTT Connect function prototype
*
* Starts the connection attempts to the MQTT broker.
* This should ideally be called after successful network connection.
*
* @return ESP_OK on success.
* @return error in case of any error.
*/
typedef esp_err_t (*esp_rmaker_mqtt_connect_t)(void);
/** MQTT Disconnect function prototype
*
* Disconnects from the MQTT broker.
*
* @return ESP_OK on success.
* @return error in case of any error.
*/
typedef esp_err_t (*esp_rmaker_mqtt_disconnect_t)(void);
/** MQTT Publish Message function prototype
*
* @param[in] topic The MQTT topic on which the message should be published.
* @param[in] data Data to be published.
* @param[in] data_len Length of the data.
* @param[in] qos Quality of service for the message.
* @param[out] msg_id If a non NULL pointer is passed, the id of the published message will be returned in this.
*
* @return ESP_OK on success.
* @return error in case of any error.
*/
typedef esp_err_t (*esp_rmaker_mqtt_publish_t)(const char *topic, void *data, size_t data_len, uint8_t qos, int *msg_id);
/** MQTT Subscribe function prototype
*
* @param[in] topic The topic to be subscribed to.
* @param[in] cb The callback to be invoked when a message is received on the given topic.
* @param[in] qos Quality of service for the subscription.
* @param[in] priv_data Optional private data to be passed to the callback.
*
* @return ESP_OK on success.
* @return error in case of any error.
*/
typedef esp_err_t (*esp_rmaker_mqtt_subscribe_t)(const char *topic, esp_rmaker_mqtt_subscribe_cb_t cb, uint8_t qos, void *priv_data);
/** MQTT Unsubscribe function prototype
*
* @param[in] topic Topic from which to unsubscribe.
*
* @return ESP_OK on success.
* @return error in case of any error.
*/
typedef esp_err_t (*esp_rmaker_mqtt_unsubscribe_t)(const char *topic);
/** MQTT configuration */
typedef struct {
/** Flag to indicate if the MQTT config setup is done */
bool setup_done;
/** Pointer to the Get MQTT params function. */
esp_rmaker_mqtt_get_conn_params_t get_conn_params;
/** Pointer to MQTT Init function. */
esp_rmaker_mqtt_init_t init;
/** Pointer to MQTT Deinit function. */
esp_rmaker_mqtt_deinit_t deinit;
/** Pointer to MQTT Connect function. */
esp_rmaker_mqtt_connect_t connect;
/** Pointer to MQTQ Disconnect function */
esp_rmaker_mqtt_disconnect_t disconnect;
/** Pointer to MQTT Publish function */
esp_rmaker_mqtt_publish_t publish;
/** Pointer to MQTT Subscribe function */
esp_rmaker_mqtt_subscribe_t subscribe;
/** Pointer to MQTT Unsubscribe function */
esp_rmaker_mqtt_unsubscribe_t unsubscribe;
} esp_rmaker_mqtt_config_t;
/** Setup MQTT Glue
*
* This function initializes MQTT glue layer with all the default functions.
*
* @param[out] mqtt_config Pointer to an allocated MQTT configuration structure.
*
* @return ESP_OK on success.
* @return error in case of any error.
*/
esp_err_t esp_rmaker_mqtt_glue_setup(esp_rmaker_mqtt_config_t *mqtt_config);
/* Get the ESP AWS PPI String
*
* @return pointer to a NULL terminated PPI string on success.
* @return NULL in case of any error.
*/
const char *esp_get_aws_ppi(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,211 @@
// Copyright 2021 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
#include <esp_idf_version.h>
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
#include <esp_sntp.h>
#else
#include <sntp.h>
#endif
#include <stdint.h>
#include <esp_err.h>
#include <esp_heap_caps.h>
#include <sdkconfig.h>
#ifdef __cplusplus
extern "C"
{
#endif
#if (CONFIG_SPIRAM_SUPPORT && (CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC))
#define MEM_ALLOC_EXTRAM(size) heap_caps_malloc_prefer(size, 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL)
#define MEM_CALLOC_EXTRAM(num, size) heap_caps_calloc_prefer(num, size, 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL)
#define MEM_REALLOC_EXTRAM(ptr, size) heap_caps_realloc_prefer(ptr, size, 2, MALLOC_CAP_DEFAULT | MALLOC_CAP_SPIRAM, MALLOC_CAP_DEFAULT | MALLOC_CAP_INTERNAL)
#else
#define MEM_ALLOC_EXTRAM(size) malloc(size)
#define MEM_CALLOC_EXTRAM(num, size) calloc(num, size)
#define MEM_REALLOC_EXTRAM(ptr, size) realloc(ptr, size)
#endif
typedef struct esp_rmaker_time_config {
/** If not specified, then 'CONFIG_ESP_RMAKER_SNTP_SERVER_NAME' is used as the SNTP server. */
char *sntp_server_name;
/** Optional callback to invoke, whenever time is synchronised. This will be called
* periodically as per the SNTP polling interval (which is 60min by default).
* If kept NULL, the default callback will be invoked, which will just print the
* current local time.
*/
sntp_sync_time_cb_t sync_time_cb;
} esp_rmaker_time_config_t;
/** Reboot the device after a delay
*
* This API just starts a reboot timer and returns immediately.
* The actual reboot is trigerred asynchronously in the timer callback.
* This is useful if you want to reboot after a delay, to allow other tasks to finish
* their operations (Eg. MQTT publish to indicate OTA success). The \ref RMAKER_EVENT_REBOOT
* event is triggered when the reboot timer is started.
*
* @param[in] seconds Time in seconds after which the device should reboot.
*
* @return ESP_OK on success.
* @return error on failure.
*/
esp_err_t esp_rmaker_reboot(int8_t seconds);
/** Reset Wi-Fi credentials and (optionally) reboot
*
* This will reset just the Wi-Fi credentials and (optionally) trigger a reboot.
* This is useful when you want to keep all the entries in NVS memory
* intact, but just change the Wi-Fi credentials. The \ref RMAKER_EVENT_WIFI_RESET
* event is triggered when this API is called. The actual reset will happen after a
* delay if reset_seconds is not zero.
*
* @note This reset and reboot operations will happen asynchronously depending
* on the values passed to the API.
*
* @param[in] reset_seconds Time in seconds after which the reset should get triggered.
* This will help other modules take some actions before the device actually resets.
* If set to zero, the operation would be performed immediately.
* @param[in] reboot_seconds Time in seconds after which the device should reboot. If set
* to negative value, the device will not reboot at all.
*
* @return ESP_OK on success.
* @return error on failure.
*/
esp_err_t esp_rmaker_wifi_reset(int8_t reset_seconds, int8_t reboot_seconds);
/** Reset to factory defaults and reboot
*
* This will clear entire NVS partition and (optionally) trigger a reboot.
* The \ref RMAKER_EVENT_FACTORY_RESET event is triggered when this API is called.
* The actual reset will happen after a delay if reset_seconds is not zero.
*
* @note This reset and reboot operations will happen asynchronously depending
* on the values passed to the API.
*
* @param[in] reset_seconds Time in seconds after which the reset should get triggered.
* This will help other modules take some actions before the device actually resets.
* If set to zero, the operation would be performed immediately.
* @param[in] reboot_seconds Time in seconds after which the device should reboot. If set
* to negative value, the device will not reboot at all.
*
* @return ESP_OK on success.
* @return error on failure.
*/
esp_err_t esp_rmaker_factory_reset(int8_t reset_seconds, int8_t reboot_seconds);
/** Initialize time synchronization
*
* This API initializes SNTP for time synchronization.
*
* @param[in] config Configuration to be used for SNTP time synchronization. The default configuration is used if NULL is passed.
*
* @return ESP_OK on success
* @return error on failure
*/
esp_err_t esp_rmaker_time_sync_init(esp_rmaker_time_config_t *config);
/** Check if current time is updated
*
* This API checks if the current system time is updated against the reference time of 1-Jan-2019.
*
* @return true if time is updated
* @return false if time is not updated
*/
bool esp_rmaker_time_check(void);
/** Wait for time synchronization
*
* This API waits for the system time to be updated against the reference time of 1-Jan-2019.
* This is a blocking call.
*
* @param[in] ticks_to_wait Number of ticks to wait for time synchronization. Accepted values: 0 to portMAX_DELAY.
*
* @return ESP_OK on success
* @return error on failure
*/
esp_err_t esp_rmaker_time_wait_for_sync(uint32_t ticks_to_wait);
/** Set POSIX timezone
*
* Set the timezone (TZ environment variable) as per the POSIX format
* specified in the [GNU libc documentation](https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html).
* Eg. For China: "CST-8"
* For US Pacific Time (including daylight saving information): "PST8PDT,M3.2.0,M11.1.0"
*
* @param[in] tz_posix NULL terminated TZ POSIX string
*
* @return ESP_OK on success
* @return error on failure
*/
esp_err_t esp_rmaker_time_set_timezone_posix(const char *tz_posix);
/** Set timezone location string
*
* Set the timezone as a user friendly location string.
* Check [here](https://rainmaker.espressif.com/docs/time-service.html) for a list of valid values.
*
* Eg. For China: "Asia/Shanghai"
* For US Pacific Time: "America/Los_Angeles"
*
* @note Setting timezone using this API internally also sets the POSIX timezone string.
*
* @param[in] tz NULL terminated Timezone location string
*
* @return ESP_OK on success
* @return error on failure
*/
esp_err_t esp_rmaker_time_set_timezone(const char *tz);
/** Get the current POSIX timezone
*
* This fetches the current timezone in POSIX format, read from NVS.
*
* @return Pointer to a NULL terminated POSIX timezone string on success.
* Freeing this is the responsibility of the caller.
* @return NULL on failure.
*/
char *esp_rmaker_time_get_timezone_posix(void);
/** Get the current timezone
*
* This fetches the current timezone in POSIX format, read from NVS.
*
* @return Pointer to a NULL terminated timezone string on success.
* Freeing this is the responsibility of the caller.
* @return NULL on failure.
*/
char *esp_rmaker_time_get_timezone(void);
/** Get printable local time string
*
* Get a printable local time string, with information of timezone and Daylight Saving.
* Eg. "Tue Sep 1 09:04:38 2020 -0400[EDT], DST: Yes"
* "Tue Sep 1 21:04:04 2020 +0800[CST], DST: No"
*
*
* @param[out] buf Pointer to a pre-allocated buffer into which the time string will
* be populated.
* @param[in] buf_len Length of the above buffer.
*
* @return ESP_OK on success
* @return error on failure
*/
esp_err_t esp_rmaker_get_local_time_str(char *buf, size_t buf_len);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,82 @@
// Copyright 2021 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
#include <stdint.h>
#include <esp_err.h>
#include <esp_event.h>
#ifdef __cplusplus
extern "C"
{
#endif
/** Prototype for ESP RainMaker Work Queue Function
*
* @param[in] priv_data The private data associated with the work function.
*/
typedef void (*esp_rmaker_work_fn_t)(void *priv_data);
/** Initializes the Work Queue
*
* This initializes the work queue, which is basically a mechanism to run
* tasks in the context of a dedicated thread. You can start queueing tasks
* after this, but they will get executed only after calling
* esp_rmaker_work_queue_start().
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t esp_rmaker_work_queue_init(void);
/** De-initialize the Work Queue
*
* This de-initializes the work queue. Note that the work queue needs to
* be stopped using esp_rmaker_work_queue_stop() before calling this.
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t esp_rmaker_work_queue_deinit(void);
/** Start the Work Queue
*
* This starts the Work Queue thread which then starts executing the tasks queued.
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t esp_rmaker_work_queue_start(void);
/** Stop the Work Queue
*
* This stops a running Work Queue.
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t esp_rmaker_work_queue_stop(void);
/** Queue execution of a function in the Work Queue's context
*
* This API queues a work function for execution in the Work Queue Task's context.
*
* @param[in] work_fn The Work function to be queued.
* @param[in] priv_data Private data to be passed to the work function.
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t esp_rmaker_work_queue_add_task(esp_rmaker_work_fn_t work_fn, void *priv_data);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,429 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sdkconfig.h>
#include <string.h>
#include <inttypes.h>
#include <esp_log.h>
#include <esp_rmaker_cmd_resp.h>
#include <esp_rmaker_utils.h>
#define RMAKER_MAX_CMD CONFIG_ESP_RMAKER_MAX_COMMANDS
static const char *TAG = "esp_rmaker_common_cmd_resp";
typedef struct {
uint16_t cmd;
uint8_t access;
bool free_on_return;
esp_rmaker_cmd_handler_t handler;
void *priv;
} esp_rmaker_cmd_info_t;
typedef struct {
uint8_t *bufptr;
int bufsize;
int curlen;
} esp_rmaker_tlv_data_t;
static esp_rmaker_cmd_info_t *esp_rmaker_cmd_list[RMAKER_MAX_CMD];
/* Get uint16 from Little Endian data buffer */
static uint16_t get_u16_le(const void *val_ptr)
{
const uint8_t *p = (const uint8_t *) val_ptr;
uint16_t val;
val = (uint16_t)p[0];
val |= (uint16_t)p[1] << 8;
return val;
}
/* Put uint16 in Little Endian ordering into data buffer */
static void put_u16_le(void *val_ptr, const uint16_t val)
{
uint8_t *p = (uint8_t *) val_ptr;
p[0] = (uint8_t)val & 0xff;
p[1] = (uint8_t)(val >> 8) & 0xff;
}
/* Initialise TLV Data */
static void esp_rmaker_tlv_data_init(esp_rmaker_tlv_data_t *tlv_data, uint8_t *buf, int buf_size)
{
tlv_data->bufptr = buf;
tlv_data->bufsize = buf_size;
tlv_data->curlen = 0;
}
/* Get length of data, for given type.
*
* Returns length of data on success and -1 if the TLV was not found
*/
static int esp_rmaker_get_tlv_length(const uint8_t *buf, int buflen, uint8_t type)
{
if (!buf ) {
return -1;
}
int curlen = 0;
int val_len = 0;
bool found = false;
while (buflen > 0) {
if (buf[curlen] == type) {
uint8_t len = buf[curlen + 1];
if ((buflen - len) < 2) {
return -1;
}
val_len += len;
if (len < 255) {
return val_len;
} else {
found = true;
}
} else if (found) {
return val_len;
}
/* buf[curlen +1] will give the Length */
buflen -= (2 + buf[curlen + 1]);
curlen += (2 + buf[curlen + 1]);
}
if (found) {
return val_len;
}
return -1;
}
/* Get the value for the given type.
*
* Returns length of data on success and -1 if the TLV was not found
*/
static int esp_rmaker_get_value_from_tlv(const uint8_t *buf, int buflen, uint8_t type, void *val, int val_size)
{
if (!buf || !val) {
return -1;
}
int curlen = 0;
int val_len = 0;
bool found = false;
while (buflen > 0) {
if (buf[curlen] == type) {
uint8_t len = buf[curlen + 1];
if ((val_size < len) || ((buflen - len) < 2)) {
return -1;
}
memcpy(val + val_len, &buf[curlen + 2], len);
val_len += len;
val_size -= len;
if (len < 255) {
return val_len;
} else {
found = true;
}
} else if (found) {
return val_len;
}
/* buf[curlen +1] will give the Length */
buflen -= (2 + buf[curlen + 1]);
curlen += (2 + buf[curlen + 1]);
}
if (found) {
return val_len;
}
return -1;
}
/* Add a TLV to the TLV buffer */
static int esp_rmaker_add_tlv(esp_rmaker_tlv_data_t *tlv_data, uint8_t type, int len, const void *val)
{
if (!tlv_data->bufptr || ((len + 2) > (tlv_data->bufsize - tlv_data->curlen))) {
return -1;
}
if (len > 0 && val == NULL) {
return -1;
}
uint8_t *buf_ptr = (uint8_t *)val;
int orig_len = tlv_data->curlen;
do {
tlv_data->bufptr[tlv_data->curlen++] = type;
int tmp_len;
if (len > 255) {
tmp_len = 255;
} else {
tmp_len = len;
}
tlv_data->bufptr[tlv_data->curlen++] = tmp_len;
memcpy(&tlv_data->bufptr[tlv_data->curlen], buf_ptr, tmp_len);
tlv_data->curlen += tmp_len;
buf_ptr += tmp_len;
len -= tmp_len;
} while (len);
return tlv_data->curlen - orig_len;
}
/* Get user role string from flag. Useful for printing */
const char *esp_rmaker_get_user_role_string(uint8_t user_role)
{
switch (user_role) {
case ESP_RMAKER_USER_ROLE_SUPER_ADMIN:
return "Admin";
case ESP_RMAKER_USER_ROLE_PRIMARY_USER:
return "Primary";
case ESP_RMAKER_USER_ROLE_SECONDARY_USER:
return "Secondary";
default:
return "Invalid Role";
}
}
/* Prepare the response TLV8 which includes
*
* Request Id
* Status
* Command Id
* Response Data
*/
static esp_err_t esp_rmaker_cmd_prepare_response(esp_rmaker_cmd_ctx_t *cmd_ctx, uint8_t status, void *response, size_t response_size, void **output, size_t *output_len)
{
size_t publish_size = response_size + 100; /* +100 for rest of metadata. TODO: Do exact calculation */
void *publish_data = MEM_CALLOC_EXTRAM(1, publish_size);
if (!publish_data) {
ESP_LOGE(TAG, "Failed to allocate buffer of size %d for response.", response_size + 100);
return ESP_ERR_NO_MEM;
}
esp_rmaker_tlv_data_t tlv_data;
esp_rmaker_tlv_data_init(&tlv_data, publish_data, publish_size);
if (strlen(cmd_ctx->req_id)) {
esp_rmaker_add_tlv(&tlv_data, ESP_RMAKER_TLV_TYPE_REQ_ID, strlen(cmd_ctx->req_id), cmd_ctx->req_id);
}
esp_rmaker_add_tlv(&tlv_data, ESP_RMAKER_TLV_TYPE_STATUS, sizeof(status), &status);
uint8_t cmd_buf[2];
put_u16_le(cmd_buf, cmd_ctx->cmd);
esp_rmaker_add_tlv(&tlv_data, ESP_RMAKER_TLV_TYPE_CMD, sizeof(cmd_buf), cmd_buf);
if (response != NULL && response_size != 0) {
esp_rmaker_add_tlv(&tlv_data, ESP_RMAKER_TLV_TYPE_DATA, response_size, response);
}
ESP_LOGD(TAG, "Generated response of size %d for cmd %d", tlv_data.curlen, cmd_ctx->cmd);
*output = publish_data;
*output_len = tlv_data.curlen;
return ESP_OK;
}
esp_err_t esp_rmaker_cmd_prepare_empty_response(void **output, size_t *output_len)
{
size_t publish_size = 6; /* unit16 cmd = 0 (4 bytes in TLV), req_id = empty (2 bytes) */
void *publish_data = MEM_CALLOC_EXTRAM(1, publish_size);
if (!publish_data) {
return ESP_ERR_NO_MEM;
}
esp_rmaker_tlv_data_t tlv_data;
esp_rmaker_tlv_data_init(&tlv_data, publish_data, publish_size);
esp_rmaker_add_tlv(&tlv_data, ESP_RMAKER_TLV_TYPE_REQ_ID, 0, NULL);
uint8_t cmd_buf[2];
put_u16_le(cmd_buf, 0);
esp_rmaker_add_tlv(&tlv_data, ESP_RMAKER_TLV_TYPE_CMD, sizeof(cmd_buf), cmd_buf);
*output = publish_data;
*output_len = tlv_data.curlen;
ESP_LOGD(TAG, "Generated empty response for requesting pending commands.");
return ESP_OK;
}
/* Register a new command with its handler
*/
esp_err_t esp_rmaker_cmd_register(uint16_t cmd, uint8_t access, esp_rmaker_cmd_handler_t handler, bool free_on_return, void *priv)
{
int i;
for (i = 0; i < RMAKER_MAX_CMD; i++) {
if (esp_rmaker_cmd_list[i] && (esp_rmaker_cmd_list[i]->cmd == cmd)) {
ESP_LOGE(TAG, "Handler for command %d already exists.", cmd);
return ESP_FAIL;
}
}
for (i = 0; i < RMAKER_MAX_CMD; i++) {
if (!esp_rmaker_cmd_list[i]) {
esp_rmaker_cmd_info_t *cmd_info = calloc(1, sizeof(esp_rmaker_cmd_info_t));
if (!cmd_info) {
ESP_LOGE(TAG, "Could not allocate memory for cmd %d", cmd);
return ESP_ERR_NO_MEM;
}
cmd_info->cmd = cmd;
cmd_info->access = access;
cmd_info->free_on_return = free_on_return;
cmd_info->handler = handler;
cmd_info->priv = priv;
esp_rmaker_cmd_list[i] = cmd_info;
ESP_LOGI(TAG, "Registered command %d", cmd);
return ESP_OK;
}
}
ESP_LOGE(TAG, "No space to add command %d", cmd);
return ESP_ERR_NO_MEM;
}
/* Find the command infor for given command
*
* Returns pointer to the info if found and NULL on error
*/
static esp_rmaker_cmd_info_t *esp_rmaker_get_cmd_info(uint16_t cmd)
{
int i;
for (i = 0; i < RMAKER_MAX_CMD; i++) {
if (esp_rmaker_cmd_list[i] && (esp_rmaker_cmd_list[i]->cmd == cmd)) {
ESP_LOGI(TAG, "Handler found for command %d.", cmd);
return esp_rmaker_cmd_list[i];
}
}
ESP_LOGE(TAG, "No handler found for command %d.", cmd);
return NULL;
}
/* De-register given command */
esp_err_t esp_rmaker_cmd_deregister(uint16_t cmd)
{
int i;
for (i = 0; i < RMAKER_MAX_CMD; i++) {
if (esp_rmaker_cmd_list[i] && (esp_rmaker_cmd_list[i]->cmd == cmd)) {
free(esp_rmaker_cmd_list[i]);
esp_rmaker_cmd_list[i] = NULL;
return ESP_OK;
}
}
ESP_LOGE(TAG, "Cannot unregister command %d as it wasn't registered.", cmd);
return ESP_ERR_INVALID_ARG;
}
/* Main command response handling function.
*
* It parses the rceived data to find the command and other metadata and
* prepares the response to be sent
*/
esp_err_t esp_rmaker_cmd_response_handler(const void *input, size_t input_len, void **output, size_t *output_len)
{
esp_rmaker_cmd_ctx_t cmd_ctx = {0};
/* Read request id, user role and command, since these are mandatory fields */
esp_rmaker_get_value_from_tlv(input, input_len, ESP_RMAKER_TLV_TYPE_REQ_ID, &cmd_ctx.req_id, sizeof(cmd_ctx.req_id));
esp_rmaker_get_value_from_tlv(input, input_len, ESP_RMAKER_TLV_TYPE_USER_ROLE, &cmd_ctx.user_role, sizeof(cmd_ctx.user_role));
uint8_t cmd_buf[2] = {0};
esp_rmaker_get_value_from_tlv(input, input_len, ESP_RMAKER_TLV_TYPE_CMD, cmd_buf, sizeof(cmd_buf));
cmd_ctx.cmd = get_u16_le(cmd_buf);
if (strlen(cmd_ctx.req_id) == 0 || cmd_ctx.user_role == 0 || cmd_ctx.cmd == 0) {
ESP_LOGE(TAG, "Request id, user role or command id cannot be 0");
return esp_rmaker_cmd_prepare_response(&cmd_ctx, ESP_RMAKER_CMD_STATUS_CMD_INVALID, NULL, 0, output, output_len);
}
ESP_LOGI(TAG, "Got Req. Id: %s, Role = %s, Cmd = %d", cmd_ctx.req_id,
esp_rmaker_get_user_role_string(cmd_ctx.user_role), cmd_ctx.cmd);
/* Search for the command info and handle it if found */
esp_rmaker_cmd_info_t *cmd_info = esp_rmaker_get_cmd_info(cmd_ctx.cmd);
if (cmd_info) {
if (cmd_info->access & cmd_ctx.user_role) {
void *data = NULL;
int data_size = esp_rmaker_get_tlv_length(input, input_len, ESP_RMAKER_TLV_TYPE_DATA);
if (data_size > 0) {
/* TODO: If data size < 255, can just use the pointer to input */
data = MEM_CALLOC_EXTRAM(1, data_size);
if (!data) {
ESP_LOGE(TAG, "Failed to allocate buffer of size %d for data.", data_size);
return ESP_ERR_NO_MEM;
}
esp_rmaker_get_value_from_tlv(input, input_len, ESP_RMAKER_TLV_TYPE_DATA, data, data_size);
} else {
/* It is not mandatory to have data for a given command. So, just throwing a warning */
ESP_LOGW(TAG, "No data received for the command.");
data_size = 0;
}
void *response;
size_t response_size = 0;
esp_err_t err = cmd_info->handler(data, data_size, &response, &response_size, &cmd_ctx, cmd_info->priv);
if (err == ESP_OK) {
err = esp_rmaker_cmd_prepare_response(&cmd_ctx, ESP_RMAKER_CMD_STATUS_SUCCESS, response, response_size, output, output_len);
} else {
err = esp_rmaker_cmd_prepare_response(&cmd_ctx, ESP_RMAKER_CMD_STATUS_FAILED, NULL, 0, output, output_len);
}
if (response && cmd_info->free_on_return) {
ESP_LOGI(TAG, "Freeing response buffer.");
free(response);
}
return err;
} else {
return esp_rmaker_cmd_prepare_response(&cmd_ctx, ESP_RMAKER_CMD_STATUS_AUTH_FAIL, NULL, 0, output, output_len);
}
}
return esp_rmaker_cmd_prepare_response(&cmd_ctx, ESP_RMAKER_CMD_STATUS_NOT_FOUND, NULL, 0, output, output_len);
}
/****************************************** Testing Functions ******************************************/
static const char *cmd_status[] = {
[ESP_RMAKER_CMD_STATUS_SUCCESS] = "Success",
[ESP_RMAKER_CMD_STATUS_FAILED] = "Fail",
[ESP_RMAKER_CMD_STATUS_CMD_INVALID] = "Invalid command data",
[ESP_RMAKER_CMD_STATUS_AUTH_FAIL] = "Auth fail",
[ESP_RMAKER_CMD_STATUS_NOT_FOUND] = "Command not found",
};
/* Send test command */
esp_err_t esp_rmaker_cmd_resp_test_send(const char *req_id, uint8_t role, uint16_t cmd, const void *data,
size_t data_size, esp_rmaker_cmd_send_t cmd_send, void *priv_data)
{
if (!cmd_send) {
ESP_LOGE(TAG, "No callback to trigger the command.");
return ESP_ERR_INVALID_ARG;
}
uint8_t cmd_data[200];
esp_rmaker_tlv_data_t tlv_data;
esp_rmaker_tlv_data_init(&tlv_data, cmd_data, sizeof(cmd_data));
if (req_id) {
esp_rmaker_add_tlv(&tlv_data, ESP_RMAKER_TLV_TYPE_REQ_ID, strlen(req_id), req_id);
}
esp_rmaker_add_tlv(&tlv_data, ESP_RMAKER_TLV_TYPE_USER_ROLE, sizeof(role), &role);
uint8_t cmd_buf[2];
put_u16_le(cmd_buf, cmd);
esp_rmaker_add_tlv(&tlv_data, ESP_RMAKER_TLV_TYPE_CMD, sizeof(cmd_buf), cmd_buf);
if (data != NULL && data_size != 0) {
esp_rmaker_add_tlv(&tlv_data, ESP_RMAKER_TLV_TYPE_DATA, data_size, data);
}
ESP_LOGI(TAG, "Sending command of size %d for cmd %d", tlv_data.curlen, cmd);
return cmd_send(cmd_data, tlv_data.curlen, priv_data);
}
/* Parse response */
esp_err_t esp_rmaker_cmd_resp_parse_response(const void *response, size_t response_len, void *priv_data)
{
if (!response) {
ESP_LOGE(TAG, "NULL response. Cannot parse.");
return ESP_ERR_INVALID_ARG;
}
char req_id[REQ_ID_LEN] = {0};
if (esp_rmaker_get_value_from_tlv(response, response_len, ESP_RMAKER_TLV_TYPE_REQ_ID, req_id, sizeof(req_id)) > 0) {
ESP_LOGI(TAG, "RESP: Request Id: %s", req_id);
}
uint16_t cmd;
uint8_t cmd_buf[2];
if (esp_rmaker_get_value_from_tlv(response, response_len, ESP_RMAKER_TLV_TYPE_CMD, cmd_buf, sizeof(cmd_buf)) > 0) {
cmd = get_u16_le(cmd_buf);
ESP_LOGI(TAG, "RESP: Command: %" PRIu16, cmd);
}
uint8_t status;
if (esp_rmaker_get_value_from_tlv(response, response_len, ESP_RMAKER_TLV_TYPE_STATUS, &status, sizeof(status)) > 0) {
ESP_LOGI(TAG, "RESP: Status: %" PRIu8 ": %s", status, cmd_status[status]);
}
char resp_data[200];
int resp_size = esp_rmaker_get_value_from_tlv(response, response_len, ESP_RMAKER_TLV_TYPE_DATA, resp_data, sizeof(resp_data));
if (resp_size > 0) {
resp_data[resp_size] = 0;
ESP_LOGI(TAG, "RESP: Data: %s", resp_data);
}
return ESP_OK;
}

View File

@@ -0,0 +1,359 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <ctype.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_timer.h>
#include <nvs_flash.h>
#include <esp_log.h>
#include <esp_console.h>
#include <lwip/sockets.h>
#include <esp_system.h>
#include <argtable3/argtable3.h>
#include <esp_heap_caps.h>
#ifdef CONFIG_HEAP_TRACING
#include <esp_heap_trace.h>
#endif
#include <esp_rmaker_utils.h>
#include "rmaker_console_internal.h"
static const char *TAG = "esp_rmaker_commands";
static int reboot_cli_handler(int argc, char *argv[])
{
/* Just to go to the next line */
printf("\n");
esp_restart();
return 0;
}
static int up_time_cli_handler(int argc, char *argv[])
{
printf("%s: Uptime of the device: %lld milliseconds\n", TAG, esp_timer_get_time() / 1000);
return 0;
}
static int task_dump_cli_handler(int argc, char *argv[])
{
#ifndef CONFIG_FREERTOS_USE_TRACE_FACILITY
printf("%s: To use this utility enable: Component config --> FreeRTOS --> Enable FreeRTOS trace facility\n", TAG);
#else
int num_of_tasks = uxTaskGetNumberOfTasks();
TaskStatus_t *task_array = MEM_CALLOC_EXTRAM(num_of_tasks, sizeof(TaskStatus_t));
if (!task_array) {
ESP_LOGE(TAG, "Memory allocation for task list failed.");
return -1;
}
num_of_tasks = uxTaskGetSystemState(task_array, num_of_tasks, NULL);
printf("%s: \tName\tNumber\tPriority\tStackWaterMark\n", TAG);
for (int i = 0; i < num_of_tasks; i++) {
printf("%16s\t%d\t%d\t%" PRIu32 "\n",
task_array[i].pcTaskName,
task_array[i].xTaskNumber,
task_array[i].uxCurrentPriority,
task_array[i].usStackHighWaterMark);
}
free(task_array);
#endif
return 0;
}
static int cpu_dump_cli_handler(int argc, char *argv[])
{
#ifndef CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
printf("%s: To use this utility enable: Component config --> FreeRTOS --> Enable FreeRTOS to collect run time stats\n", TAG);
#else
char *buf = MEM_CALLOC_EXTRAM(1, 2 * 1024);
if (!buf) {
ESP_LOGE(TAG, "Memory allocation for cpu dump failed.");
return -1;
}
vTaskGetRunTimeStats(buf);
printf("%s: Run Time Stats:\n%s\n", TAG, buf);
free(buf);
#endif
return 0;
}
static int mem_dump_cli_handler(int argc, char *argv[])
{
printf("\tDescription\tInternal\tSPIRAM\n");
printf("Current Free Memory\t%d\t\t%d\n",
heap_caps_get_free_size(MALLOC_CAP_8BIT) - heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
printf("Largest Free Block\t%d\t\t%d\n",
heap_caps_get_largest_free_block(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL),
heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM));
printf("Min. Ever Free Size\t%d\t\t%d\n",
heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL),
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
return 0;
}
static int sock_dump_cli_handler(int argc, char *argv[])
{
#if LWIP_IPV4
int i, ret, used_sockets = 0;
struct sockaddr_in local_sock, peer_sock;
socklen_t local_sock_len = sizeof(struct sockaddr_in), peer_sock_len = sizeof(struct sockaddr_in);
char local_ip_addr[16], peer_ip_addr[16];
unsigned int local_port, peer_port;
int sock_type;
socklen_t sock_type_len;
#define TOTAL_NUM_SOCKETS MEMP_NUM_NETCONN
printf("sock_fd\tprotocol\tlocal_addr\t\tpeer_addr\n");
for (i = LWIP_SOCKET_OFFSET; i < LWIP_SOCKET_OFFSET + TOTAL_NUM_SOCKETS; i++) {
memset(&local_sock, 0, sizeof(struct sockaddr_in));
memset(&peer_sock, 0, sizeof(struct sockaddr_in));
local_sock_len = sizeof(struct sockaddr);
peer_sock_len = sizeof(struct sockaddr);
memset(local_ip_addr, 0, sizeof(local_ip_addr));
memset(peer_ip_addr, 0, sizeof(peer_ip_addr));
local_port = 0;
peer_port = 0;
sock_type = 0;
sock_type_len = sizeof(int);
ret = getsockname(i, (struct sockaddr *)&local_sock, &local_sock_len);
if (ret >= 0) {
used_sockets++;
inet_ntop(AF_INET, &local_sock.sin_addr, local_ip_addr, sizeof(local_ip_addr));
local_port = ntohs(local_sock.sin_port);
getsockopt(i, SOL_SOCKET, SO_TYPE, &sock_type, &sock_type_len);
printf("%d\t%d:%s\t%16s:%d", i, sock_type, sock_type == SOCK_STREAM ? "tcp" : sock_type == SOCK_DGRAM ? "udp" : "raw", local_ip_addr, local_port);
ret = getpeername(i, (struct sockaddr *)&peer_sock, &peer_sock_len);
if (ret >= 0) {
inet_ntop(AF_INET, &peer_sock.sin_addr, peer_ip_addr, sizeof(peer_ip_addr));
peer_port = ntohs(peer_sock.sin_port);
printf("\t%16s:%d", peer_ip_addr, peer_port);
}
printf("\n");
}
}
printf("Remaining sockets: %d\n", TOTAL_NUM_SOCKETS - used_sockets);
#else
printf("%s: To use this utility enable: Component config --> LWIP --> Enable IPv4 \n", TAG);
#endif /* LWIP_IPV4 */
return 0;
}
#ifdef CONFIG_HEAP_TRACING
static int heap_trace_records;
static heap_trace_record_t *heap_trace_records_buf;
static int cli_heap_trace_start()
{
if (!heap_trace_records_buf) {
// this buffer is required to be in internal
heap_trace_records_buf = malloc(heap_trace_records * sizeof(heap_trace_record_t));
if (!heap_trace_records_buf) {
printf("%s: Failed to allocate records buffer\n", TAG);
return -1;
}
if (heap_trace_init_standalone(heap_trace_records_buf, heap_trace_records) != ESP_OK) {
printf("%s: Failed to initialise tracing\n", TAG);
goto error1;
}
}
if (heap_trace_start(HEAP_TRACE_LEAKS) != ESP_OK) {
printf("%s: Failed to start heap trace\n", TAG);
goto error2;
}
return 0;
error2:
heap_trace_init_standalone(NULL, 0);
error1:
free(heap_trace_records_buf);
heap_trace_records_buf = NULL;
return -1;
}
static int cli_heap_trace_stop()
{
if (!heap_trace_records_buf) {
printf("%s: Tracing not started?\n", TAG);
return 0;
}
heap_trace_stop();
heap_trace_dump();
heap_trace_init_standalone(NULL, 0);
free(heap_trace_records_buf);
heap_trace_records_buf = NULL;
return 0;
}
#endif
static int heap_trace_cli_handler(int argc, char *argv[])
{
int ret = 0;
#ifndef CONFIG_HEAP_TRACING
printf("%s: To use this utility enable: Component config --> Heap memory debugging --> Enable heap tracing\n", TAG);
#else
if (argc < 2) {
printf("%s: Incorrect arguments\n", TAG);
return -1;
}
if (strcmp(argv[1], "start") == 0) {
#define DEFAULT_HEAP_TRACE_RECORDS 200
if (argc != 3) {
heap_trace_records = DEFAULT_HEAP_TRACE_RECORDS;
} else {
heap_trace_records = atoi(argv[2]);
}
printf("%s: Using a buffer to trace %d records\n", TAG, heap_trace_records);
ret = cli_heap_trace_start();
} else if (strcmp(argv[1], "stop") == 0) {
ret = cli_heap_trace_stop();
} else {
printf("%s: Invalid argument:%s:\n", TAG, argv[1]);
ret = -1;
}
#endif
return ret;
}
static int register_generic_debug_commands()
{
const esp_console_cmd_t debug_commands[] = {
{
.command = "reboot",
.help = "",
.func = reboot_cli_handler,
},
{
.command = "up-time",
.help = "Get the device up time in milliseconds.",
.func = up_time_cli_handler,
},
{
.command = "mem-dump",
.help = "Get the available memory.",
.func = mem_dump_cli_handler,
},
{
.command = "task-dump",
.help = "Get the list of all the running tasks.",
.func = task_dump_cli_handler,
},
{
.command = "cpu-dump",
.help = "Get the CPU utilisation by all the runninng tasks.",
.func = cpu_dump_cli_handler,
},
{
.command = "sock-dump",
.help = "Get the list of all the active sockets.",
.func = sock_dump_cli_handler,
},
{
.command = "heap-trace",
.help = "Start or stop heap tracing. Usage: heap-trace <start|stop> <bufer_size>",
.func = heap_trace_cli_handler,
},
};
int cmds_num = sizeof(debug_commands) / sizeof(esp_console_cmd_t);
int i;
for (i = 0; i < cmds_num; i++) {
ESP_LOGI(TAG, "Registering command: %s", debug_commands[i].command);
esp_console_cmd_register(&debug_commands[i]);
}
return 0;
}
static int reset_to_factory_handler(int argc, char** argv)
{
#define RESET_DELAY 2
#define REBOOT_DELAY 2
printf("%s: Resetting to Factory Defaults...\n", TAG);
esp_rmaker_factory_reset(RESET_DELAY, REBOOT_DELAY);
return ESP_OK;
}
static void register_reset_to_factory()
{
const esp_console_cmd_t cmd = {
.command = "reset-to-factory",
.help = "Reset the board to factory defaults",
.func = &reset_to_factory_handler,
};
ESP_LOGI(TAG, "Registering command: %s", cmd.command);
esp_console_cmd_register(&cmd);
}
static int local_time_cli_handler(int argc, char *argv[])
{
char local_time[64];
if (esp_rmaker_get_local_time_str(local_time, sizeof(local_time)) == ESP_OK) {
printf("%s: Current local time: %s\n", TAG, local_time);
} else {
printf("%s: Current local time (truncated): %s\n", TAG, local_time);
}
return ESP_OK;
}
static int tz_set_cli_handler(int argc, char *argv[])
{
if (argc < 2) {
printf("%s: Invalid Usage.\n", TAG);
return ESP_ERR_INVALID_ARG;
}
if (strcmp(argv[1], "posix") == 0) {
if (argv[2]) {
esp_rmaker_time_set_timezone_posix(argv[2]);
} else {
printf("%s: Invalid Usage.\n", TAG);
return ESP_ERR_INVALID_ARG;
}
} else {
esp_rmaker_time_set_timezone(argv[1]);
}
return ESP_OK;
}
static void register_time_commands()
{
const esp_console_cmd_t local_time_cmd = {
.command = "local-time",
.help = "Get the local time of device.",
.func = &local_time_cli_handler,
};
ESP_LOGI(TAG, "Registering command: %s", local_time_cmd.command);
esp_console_cmd_register(&local_time_cmd);
const esp_console_cmd_t tz_set_cmd = {
.command = "tz-set",
.help = "Set Timezone. Usage: tz-set [posix] <tz_string>.",
.func = &tz_set_cli_handler,
};
ESP_LOGI(TAG, "Registering command: %s", tz_set_cmd.command);
esp_console_cmd_register(&tz_set_cmd);
}
void esp_rmaker_common_register_commands()
{
static bool registered = false;
if (registered) {
ESP_LOGI(TAG, "Skipping already registered commands");
return;
}
register_generic_debug_commands();
register_reset_to_factory();
register_time_commands();
registered = true;
}

View File

@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include <esp_log.h>
#include <esp_console.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <driver/uart.h>
#include <esp_rmaker_common_console.h>
#include "rmaker_console_internal.h"
static const char *TAG = "esp_rmaker_console";
static int stop;
static void scli_task(void *arg)
{
int uart_num = 0;
uint8_t linebuf[256];
int i, cmd_ret;
esp_err_t ret;
QueueHandle_t uart_queue;
uart_event_t event;
bool first_done = false;
ESP_LOGI(TAG, "Initialising UART on port %d", uart_num);
uart_driver_install(uart_num, 256, 0, 8, &uart_queue, 0);
/* Initialize the console */
esp_console_config_t console_config = {
.max_cmdline_args = 8,
.max_cmdline_length = 256,
};
esp_console_init(&console_config);
esp_console_register_help_command();
while (!stop) {
if (first_done) {
uart_write_bytes(uart_num, "\n>> ", 4);
} else {
first_done = true;
}
memset(linebuf, 0, sizeof(linebuf));
i = 0;
do {
ret = xQueueReceive(uart_queue, (void * )&event, (TickType_t)portMAX_DELAY);
if (ret != pdPASS) {
if(stop == 1) {
break;
} else {
continue;
}
}
if (event.type == UART_DATA) {
while (uart_read_bytes(uart_num, (uint8_t *) &linebuf[i], 1, 0)) {
if (linebuf[i] == '\r') {
uart_write_bytes(uart_num, "\r\n", 2);
} else {
uart_write_bytes(uart_num, (char *) &linebuf[i], 1);
}
i++;
}
}
} while ((i < 255) && linebuf[i-1] != '\r');
if (stop) {
break;
}
/* Remove the truncating \r\n */
linebuf[strlen((char *)linebuf) - 1] = '\0';
/* Just to go to the next line */
printf("\n");
ret = esp_console_run((char *) linebuf, &cmd_ret);
if (cmd_ret != 0) {
printf("%s: Console command failed with error: %d\n", TAG, cmd_ret);
cmd_ret = 0;
}
if (ret < 0) {
printf("%s: Console dispatcher error\n", TAG);
break;
}
}
ESP_LOGE(TAG, "Stopped CLI");
vTaskDelete(NULL);
}
static esp_err_t scli_init()
{
static bool cli_started = false;
if (cli_started) {
return ESP_OK;
}
#define SCLI_STACK_SIZE 4096
if (xTaskCreate(&scli_task, "console_task", SCLI_STACK_SIZE, NULL, 3, NULL) != pdPASS) {
ESP_LOGE(TAG, "Couldn't create thread");
return ESP_ERR_NO_MEM;
}
cli_started = true;
return ESP_OK;
}
esp_err_t esp_rmaker_common_console_init()
{
esp_err_t ret = scli_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Couldn't initialise console");
return ret;
}
esp_rmaker_common_register_commands();
return ESP_OK;
}

View File

@@ -0,0 +1,9 @@
/*
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
void esp_rmaker_common_register_commands(void);

View File

@@ -0,0 +1,246 @@
/*
* APN3 PPI string generator - sample code
*
* NOTE: All code is provided as sample code for informational purposes only, and should not be used for any testing or production workloads.
* All code is provided “as is” and AWS expressly disclaims all warranties, including, without limitation: any implied warranties of
* noninfringement, merchantability, or fitness for a particular purpose; any warranty that operation of the code will be error-free
* or free of harmful components; or any warranty arising out of any course of dealing or usage of trade. In no event shall AWS or
* any of its affiliates be liable for any damages arising out of the use of this code, including, without limitation, any direct,
* indirect, special, incidental or consequential damages.
*/
#include <stdio.h>
#include <string.h>
#include <sdkconfig.h>
#include <esp_wifi.h>
#include <esp_log.h>
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_THREAD
#include <esp_mac.h>
#endif
static const char *TAG = "aws_ppi";
#define PPI_STRING_GEN_VERSION 0.4
#define MAX_PPI_STRING_LEN 64
#define MAX_LEN_FIELDS_1_2_3 37 /* max length of fields ProductName + ProductUID + ProductVersion */
#define PPI_PREFIX_1 "?"
#define PPI_PREFIX_1_ALT "&"
#define PPI_PREFIX_2 "Platform=APN3|"
#define PPI_RESERVED "A0|" /* reserved for AWS use */
#define PPI_PLATFORM_AWS "AZ|"
#define PPI_PLATFORM_RAINMAKER "RM|"
#define PPI_PLATFORM PPI_PLATFORM_RAINMAKER
#define PPI_SILICON_SKU_CODE CONFIG_ESP_RMAKER_MQTT_PRODUCT_SKU
#define PPI_PRODUCT_NAME CONFIG_ESP_RMAKER_MQTT_PRODUCT_NAME
#define PPI_PRODUCT_VERSION CONFIG_ESP_RMAKER_MQTT_PRODUCT_VERSION
#define PPI_SEPARATOR_CHAR '|'
#define PPI_SEPARATOR "|"
#define PPI_FIXED_PART PPI_PREFIX_1 PPI_PREFIX_2 PPI_RESERVED PPI_PLATFORM PPI_SILICON_SKU_CODE PPI_SEPARATOR PPI_PRODUCT_NAME PPI_SEPARATOR PPI_PRODUCT_VERSION PPI_SEPARATOR
#define VALID 1
#define INVALID 0
/*
* platform_get_product_name is a stub function to be customized for each platform.
* It must return a pointer to the product name string.
* Can use a #define instead of a function in most cases.
*/
char __attribute__((weak)) *platform_get_product_name()
{
return(PPI_PRODUCT_NAME);
}
/*
* platform_get_product_UID is a stub function to be customized for each platform.
* It must return a pointer to the product UID string.
*/
char __attribute__((weak)) *platform_get_product_UID()
{
static char mac_str[13];
#if defined(CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI)
uint8_t eth_mac[6];
esp_err_t err = esp_wifi_get_mac(WIFI_IF_STA, eth_mac);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Could not fetch MAC address. Please initialise Wi-Fi first");
return NULL;
}
snprintf(mac_str, sizeof(mac_str), "%02X%02X%02X%02X%02X%02X",
eth_mac[0], eth_mac[1], eth_mac[2], eth_mac[3], eth_mac[4], eth_mac[5]);
#elif defined(CONFIG_ESP_RMAKER_NETWORK_OVER_THREAD) /* CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI */
uint8_t base_mac[6];
esp_err_t err = esp_read_mac(base_mac, ESP_MAC_BASE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Could not fetch base mac");
return NULL;
}
snprintf(mac_str, sizeof(mac_str), "%02X%02X%02X%02X%02X%02X",
base_mac[0], base_mac[1], base_mac[2], base_mac[3], base_mac[4], base_mac[5]);
#endif /* CONFIG_ESP_RMAKER_NETWORK_OVER_THREAD */
return mac_str;
}
/*
* platform_get_product_version is a stub function to be customized for each platform.
* It must return a pointer to the product version string.
* Can use a #define instead of a function in most cases.
*/
char __attribute__((weak)) *platform_get_product_version()
{
return(PPI_PRODUCT_VERSION);
}
/*
* platform_get_silicom_sku_code is a stub function to be customized for each platform.
* It must return a pointer to a valid Silicon SKU code.
* Can use a #define instead of a function in most cases.
*/
char __attribute__((weak)) *platform_get_silicon_sku_code()
{
return(PPI_SILICON_SKU_CODE);
}
/*
* validate_sku_code takes as input the 4 character SKU code and validates it.
*
* Returns
* VALID if valid, else INVALID
*/
int validate_sku_code(char *silicon_sku_code)
{
int retval = INVALID;
typedef struct sku_item_t {
/* entry in sku code lookup table */
char *skucode; /* code corresponding to sku name */
} sku_item;
static sku_item skutable[] =
{
/*
* codes for partner 1
*/
{ "ES00" }, /* Older Default */
{ "EX00" }, /* Default */
{ "EX01" }, /* ESP32-C3, including 8685 */
{ "EX02" }, /* ESP32-C2, including 8684 */
{ "EX03" }, /* ESP32-S3 */
{ "EX04" }, /* ESP32 */
{ "EX05" }, /* ESP32-C5 */
{ "EX06" }, /* ESP32-S2 */
/*
* add new entries above in any order
*/
{ NULL } /* this must be the last entry */
};
/*
* linear search for a matching silicon sku code entry
*/
for (sku_item *p = skutable; p->skucode != NULL; p++)
{
if (strcmp(p->skucode, silicon_sku_code) == 0)
{ /* found match */
retval = VALID;
break;
}
}
return (retval);
}
/*
* validate_platform_inputs
*
* Returns
* VALID if inputs are valid, INVALID otherwise
*/
int validate_platform_inputs(char *product_name, char *product_uid, char *product_version, char *silicon_sku_code)
{
int retval = INVALID;
if ( (strlen(product_name) + strlen(product_uid) + strlen(product_version)) <= MAX_LEN_FIELDS_1_2_3)
{ /* field 1,2,3 length check passed */
if (validate_sku_code(silicon_sku_code) == VALID)
{
retval = VALID;
}
else
{ /* invalid sku code */
ESP_LOGE(TAG, "Error: Invalid sku code");
}
}
else
{ /* invalid string length */
ESP_LOGE(TAG, "Error: Invalid field(s) length");
}
return(retval);
}
/*
* create_APN_PPI_string creates the string that must be passed in the UserName field of the MQTT Connect
* packet as required by the IAP Module Incentive program. Details are given in the APN3 IoT Acceleration
* Module Incentive Program Guide-Tech-<partnername> document.
*
* Inputs:
* product_name = pointer to a string holding the product name.
* product_uid = pointer to a string holding the product UID (unique id)
* product_version = version number of the product.
* silicon_sku_code = pointer to a string holding a valid SKU code as specified in the guide.
*
* Return values
* NULL if there is an error and the PPI string could not be generated, otherwise the PPI string is returned.
*/
char * create_APN_PPI_string(char *product_name, char *product_uid, char *product_version, char *silicon_sku_code)
{
char *retval = NULL;
static char ppistring[MAX_PPI_STRING_LEN + 1];
ESP_LOGD(TAG, "PPI string generator v%2.1f", PPI_STRING_GEN_VERSION);
/*
* Validate inputs
*/
if (validate_platform_inputs(product_name, product_uid, product_version, silicon_sku_code))
{ /* valid inputs - build the string */
snprintf(ppistring, sizeof(ppistring),"%s%s%s%s%s%s%s%s%s%s%s",
PPI_PREFIX_1, PPI_PREFIX_2, PPI_RESERVED, PPI_PLATFORM, silicon_sku_code,
PPI_SEPARATOR, product_name, PPI_SEPARATOR, product_version, PPI_SEPARATOR,
product_uid);
retval = ppistring;
}
else
{ /* error - some inputs are not valid */
ESP_LOGE(TAG, "Error: Some inputs are not valid");
}
return(retval);
}
/*
* main - setup values for your platform and invoke string generator.
*/
const char __attribute__((weak)) *esp_get_aws_ppi(void)
{
char *ppistring = NULL;
ppistring = create_APN_PPI_string( platform_get_product_name(),
platform_get_product_UID(),
platform_get_product_version(),
platform_get_silicon_sku_code() );
if (ppistring != NULL)
{
ESP_LOGD(TAG, "PPI inputs pass all checks. String value is shown below:\n\n%s", ppistring);
}
else
{ /* error */
ESP_LOGE(TAG, "Error creating PPI string");
}
return ppistring;
}

View File

@@ -0,0 +1,477 @@
// Copyright 2021 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 <sdkconfig.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_log.h>
#include <mqtt_client.h>
#include <esp_event.h>
#include <esp_rmaker_common_events.h>
#include <esp_rmaker_mqtt_glue.h>
#include <esp_idf_version.h>
#include <esp_rmaker_utils.h>
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 1, 0)
// Features supported in 4.1+
#ifdef CONFIG_ESP_RMAKER_MQTT_PORT_443
#define ESP_RMAKER_MQTT_USE_PORT_443
#endif
#else
#ifdef CONFIG_ESP_RMAKER_MQTT_PORT_443
#warning "Certificate Bundle not supported below IDF v4.4. Using provided certificate instead."
#endif
#endif /* !IDF4.1 */
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
// Features supported in 4.4+
#ifdef CONFIG_ESP_RMAKER_MQTT_USE_CERT_BUNDLE
#define ESP_RMAKER_MQTT_USE_CERT_BUNDLE
#include <esp_crt_bundle.h>
#endif
#else
#ifdef CONFIG_ESP_RMAKER_MQTT_USE_CERT_BUNDLE
#warning "Certificate Bundle not supported below 4.4. Using provided certificate instead."
#endif
#endif /* !IDF4.4 */
static const char *TAG = "esp_mqtt_glue";
#define MAX_MQTT_SUBSCRIPTIONS CONFIG_ESP_RMAKER_MAX_MQTT_SUBSCRIPTIONS
typedef struct {
char *topic;
esp_rmaker_mqtt_subscribe_cb_t cb;
void *priv;
} esp_mqtt_glue_subscription_t;
typedef struct {
esp_mqtt_client_handle_t mqtt_client;
esp_rmaker_mqtt_conn_params_t *conn_params;
esp_mqtt_glue_subscription_t *subscriptions[MAX_MQTT_SUBSCRIPTIONS];
} esp_mqtt_glue_data_t;
esp_mqtt_glue_data_t *mqtt_data;
typedef struct {
char *data;
char *topic;
} esp_mqtt_glue_long_data_t;
static void esp_mqtt_glue_deinit(void);
static void esp_mqtt_glue_subscribe_callback(const char *topic, int topic_len, const char *data, int data_len)
{
esp_mqtt_glue_subscription_t **subscriptions = mqtt_data->subscriptions;
int i;
for (i = 0; i < MAX_MQTT_SUBSCRIPTIONS; i++) {
if (subscriptions[i]) {
if ((strncmp(topic, subscriptions[i]->topic, topic_len) == 0)
&& (topic_len == strlen(subscriptions[i]->topic))) {
subscriptions[i]->cb(subscriptions[i]->topic, (void *)data, data_len, subscriptions[i]->priv);
}
}
}
}
static esp_err_t esp_mqtt_glue_subscribe(const char *topic, esp_rmaker_mqtt_subscribe_cb_t cb, uint8_t qos, void *priv_data)
{
if ( !mqtt_data || !topic || !cb) {
return ESP_FAIL;
}
int i;
for (i = 0; i < MAX_MQTT_SUBSCRIPTIONS; i++) {
if (!mqtt_data->subscriptions[i]) {
esp_mqtt_glue_subscription_t *subscription = calloc(1, sizeof(esp_mqtt_glue_subscription_t));
if (!subscription) {
return ESP_FAIL;
}
subscription->topic = strdup(topic);
if (!subscription->topic) {
free(subscription);
return ESP_FAIL;
}
int ret = esp_mqtt_client_subscribe(mqtt_data->mqtt_client, subscription->topic, qos);
if (ret < 0) {
free(subscription->topic);
free(subscription);
return ESP_FAIL;
}
subscription->priv = priv_data;
subscription->cb = cb;
mqtt_data->subscriptions[i] = subscription;
ESP_LOGD(TAG, "Subscribed to topic: %s", topic);
return ESP_OK;
}
}
return ESP_FAIL;
}
static void unsubscribe_helper(esp_mqtt_glue_subscription_t **subscription)
{
if (subscription && *subscription) {
if (esp_mqtt_client_unsubscribe(mqtt_data->mqtt_client, (*subscription)->topic) < 0) {
ESP_LOGW(TAG, "Could not unsubscribe from topic: %s", (*subscription)->topic);
}
free((*subscription)->topic);
free(*subscription);
*subscription = NULL;
}
}
static esp_err_t esp_mqtt_glue_unsubscribe(const char *topic)
{
if (!mqtt_data || !topic) {
return ESP_FAIL;
}
esp_mqtt_glue_subscription_t **subscriptions = mqtt_data->subscriptions;
int i;
for (i = 0; i < MAX_MQTT_SUBSCRIPTIONS; i++) {
if (subscriptions[i]) {
if (strncmp(topic, subscriptions[i]->topic, strlen(topic)) == 0) {
unsubscribe_helper(&subscriptions[i]);
return ESP_OK;
}
}
}
return ESP_FAIL;
}
static esp_err_t esp_mqtt_glue_publish(const char *topic, void *data, size_t data_len, uint8_t qos, int *msg_id)
{
if (!mqtt_data || !topic || !data) {
return ESP_FAIL;
}
ESP_LOGD(TAG, "Publishing to %s", topic);
int ret = esp_mqtt_client_publish(mqtt_data->mqtt_client, topic, data, data_len, qos, 0);
if (ret < 0) {
ESP_LOGE(TAG, "MQTT Publish failed");
return ESP_FAIL;
}
if (msg_id) {
*msg_id = ret;
}
return ESP_OK;
}
static esp_mqtt_glue_long_data_t *esp_mqtt_glue_free_long_data(esp_mqtt_glue_long_data_t *long_data)
{
if (long_data) {
if (long_data->topic) {
free(long_data->topic);
}
if (long_data->data) {
free(long_data->data);
}
free(long_data);
}
return NULL;
}
static esp_mqtt_glue_long_data_t *esp_mqtt_glue_manage_long_data(esp_mqtt_glue_long_data_t *long_data,
esp_mqtt_event_handle_t event)
{
if (event->topic) {
/* This is new data. Free any earlier data, if present. */
esp_mqtt_glue_free_long_data(long_data);
long_data = calloc(1, sizeof(esp_mqtt_glue_long_data_t));
if (!long_data) {
ESP_LOGE(TAG, "Could not allocate memory for esp_mqtt_glue_long_data_t");
return NULL;
}
long_data->data = MEM_CALLOC_EXTRAM(1, event->total_data_len);
if (!long_data->data) {
ESP_LOGE(TAG, "Could not allocate %d bytes for received data.", event->total_data_len);
return esp_mqtt_glue_free_long_data(long_data);
}
long_data->topic = strndup(event->topic, event->topic_len);
if (!long_data->topic) {
ESP_LOGE(TAG, "Could not allocate %d bytes for received topic.", event->topic_len);
return esp_mqtt_glue_free_long_data(long_data);
}
}
if (long_data) {
memcpy(long_data->data + event->current_data_offset, event->data, event->data_len);
if ((event->current_data_offset + event->data_len) == event->total_data_len) {
esp_mqtt_glue_subscribe_callback(long_data->topic, strlen(long_data->topic),
long_data->data, event->total_data_len);
return esp_mqtt_glue_free_long_data(long_data);
}
}
return long_data;
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data)
#else
static esp_err_t mqtt_event_handler(esp_mqtt_event_handle_t event)
#endif
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
esp_mqtt_event_handle_t event = event_data;
#else
uint32_t event_id = event->event_id;
#endif
switch (event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "MQTT Connected");
/* Resubscribe to all topics after reconnection */
for (int i = 0; i < MAX_MQTT_SUBSCRIPTIONS; i++) {
if (mqtt_data->subscriptions[i]) {
esp_mqtt_client_subscribe(event->client, mqtt_data->subscriptions[i]->topic, 1);
}
}
esp_event_post(RMAKER_COMMON_EVENT, RMAKER_MQTT_EVENT_CONNECTED, NULL, 0, portMAX_DELAY);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGW(TAG, "MQTT Disconnected. Will try reconnecting in a while...");
esp_event_post(RMAKER_COMMON_EVENT, RMAKER_MQTT_EVENT_DISCONNECTED, NULL, 0, portMAX_DELAY);
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGD(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_UNSUBSCRIBED:
ESP_LOGD(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGD(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event->msg_id);
esp_event_post(RMAKER_COMMON_EVENT, RMAKER_MQTT_EVENT_PUBLISHED, &event->msg_id, sizeof(event->msg_id), portMAX_DELAY);
break;
#ifdef CONFIG_MQTT_REPORT_DELETED_MESSAGES
case MQTT_EVENT_DELETED:
ESP_LOGD(TAG, "MQTT_EVENT_DELETED, msg_id=%d", event->msg_id);
esp_event_post(RMAKER_COMMON_EVENT, RMAKER_MQTT_EVENT_MSG_DELETED, &event->msg_id, sizeof(event->msg_id), portMAX_DELAY);
break;
#endif /* CONFIG_MQTT_REPORT_DELETED_MESSAGES */
case MQTT_EVENT_DATA: {
ESP_LOGD(TAG, "MQTT_EVENT_DATA");
static esp_mqtt_glue_long_data_t *long_data;
/* Topic can be NULL, for data longer than the MQTT buffer */
if (event->topic) {
ESP_LOGD(TAG, "TOPIC=%.*s\r\n", event->topic_len, event->topic);
}
ESP_LOGD(TAG, "DATA=%.*s\r\n", event->data_len, event->data);
if (event->data_len == event->total_data_len) {
/* If long_data still exists, it means there was some issue getting the
* long data, and so, it needs to be freed up.
*/
if (long_data) {
long_data = esp_mqtt_glue_free_long_data(long_data);
}
esp_mqtt_glue_subscribe_callback(event->topic, event->topic_len, event->data, event->data_len);
} else {
long_data = esp_mqtt_glue_manage_long_data(long_data, event);
}
break;
}
case MQTT_EVENT_ERROR:
ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
break;
default:
ESP_LOGD(TAG, "Other event id:%d", event->event_id);
break;
}
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
return ESP_OK;
#endif
}
static esp_err_t esp_mqtt_glue_connect(void)
{
if (!mqtt_data) {
return ESP_FAIL;
}
ESP_LOGI(TAG, "Connecting to %s", mqtt_data->conn_params->mqtt_host);
esp_err_t ret = esp_mqtt_client_start(mqtt_data->mqtt_client);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_mqtt_client_start() failed with err = %d", ret);
return ret;
}
return ESP_OK;
}
static void esp_mqtt_glue_unsubscribe_all(void)
{
if (!mqtt_data) {
return;
}
int i;
for (i = 0; i < MAX_MQTT_SUBSCRIPTIONS; i++) {
if (mqtt_data->subscriptions[i]) {
unsubscribe_helper(&(mqtt_data->subscriptions[i]));
}
}
}
static esp_err_t esp_mqtt_glue_disconnect(void)
{
if (!mqtt_data) {
return ESP_FAIL;
}
esp_mqtt_glue_unsubscribe_all();
esp_err_t err = esp_mqtt_client_stop(mqtt_data->mqtt_client);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to disconnect from MQTT");
} else {
ESP_LOGI(TAG, "MQTT Disconnected.");
}
return err;
}
#ifdef ESP_RMAKER_MQTT_USE_PORT_443
static const char *alpn_protocols[] = { "x-amzn-mqtt-ca", NULL };
#endif /* ESP_RMAKER_MQTT_USE_PORT_443 */
static esp_err_t esp_mqtt_glue_init(esp_rmaker_mqtt_conn_params_t *conn_params)
{
#ifdef CONFIG_ESP_RMAKER_MQTT_SEND_USERNAME
const char *username = esp_get_aws_ppi();
ESP_LOGI(TAG, "AWS PPI: %s", username);
#endif
if (mqtt_data) {
ESP_LOGE(TAG, "MQTT already initialized");
return ESP_OK;
}
if (!conn_params) {
ESP_LOGE(TAG, "Connection params are mandatory for esp_mqtt_glue_init");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Initialising MQTT");
mqtt_data = calloc(1, sizeof(esp_mqtt_glue_data_t));
if (!mqtt_data) {
ESP_LOGE(TAG, "Failed to allocate memory for esp_mqtt_glue_data_t");
return ESP_ERR_NO_MEM;
}
mqtt_data->conn_params = conn_params;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
const esp_mqtt_client_config_t mqtt_client_cfg = {
.broker = {
.address = {
.hostname = conn_params->mqtt_host,
#ifdef ESP_RMAKER_MQTT_USE_PORT_443
.port = 443,
#else
.port = 8883,
#endif
.transport = MQTT_TRANSPORT_OVER_SSL,
},
.verification = {
#ifdef ESP_RMAKER_MQTT_USE_PORT_443
.alpn_protos = alpn_protocols,
#endif
#ifdef ESP_RMAKER_MQTT_USE_CERT_BUNDLE
.crt_bundle_attach = esp_crt_bundle_attach,
#else
.certificate = (const char *)conn_params->server_cert,
.certificate_len = conn_params->server_cert_len,
#endif
}
},
.credentials = {
#ifdef CONFIG_ESP_RMAKER_MQTT_SEND_USERNAME
.username = username,
#endif
.client_id = (const char *)conn_params->client_id,
.authentication = {
.certificate = (const char *)conn_params->client_cert,
.certificate_len = conn_params->client_cert_len,
.key = (const char *)conn_params->client_key,
.key_len = conn_params->client_key_len,
.ds_data = conn_params->ds_data
},
},
.session = {
.keepalive = CONFIG_ESP_RMAKER_MQTT_KEEP_ALIVE_INTERVAL,
#ifdef CONFIG_ESP_RMAKER_MQTT_PERSISTENT_SESSION
.disable_clean_session = 1,
#endif /* CONFIG_ESP_RMAKER_MQTT_PERSISTENT_SESSION */
},
};
#else
const esp_mqtt_client_config_t mqtt_client_cfg = {
.host = conn_params->mqtt_host,
#ifdef ESP_RMAKER_MQTT_USE_PORT_443
.port = 443,
.alpn_protos = alpn_protocols,
#else
.port = 8883,
#endif /* !ESP_RMAKER_MQTT_USE_PORT_443 */
#ifdef ESP_RMAKER_MQTT_USE_CERT_BUNDLE
.crt_bundle_attach = esp_crt_bundle_attach,
#else
.cert_pem = (const char *)conn_params->server_cert,
.cert_len = conn_params->server_cert_len,
#endif
.client_cert_pem = (const char *)conn_params->client_cert,
.client_cert_len = conn_params->client_cert_len,
.client_key_pem = (const char *)conn_params->client_key,
.client_key_len = conn_params->client_key_len,
.client_id = (const char *)conn_params->client_id,
.keepalive = CONFIG_ESP_RMAKER_MQTT_KEEP_ALIVE_INTERVAL,
.event_handle = mqtt_event_handler,
.transport = MQTT_TRANSPORT_OVER_SSL,
#ifdef CONFIG_ESP_RMAKER_MQTT_PERSISTENT_SESSION
.disable_clean_session = 1,
#endif /* CONFIG_ESP_RMAKER_MQTT_PERSISTENT_SESSION */
#ifdef CONFIG_ESP_RMAKER_MQTT_SEND_USERNAME
.username = username,
#endif
};
#endif
mqtt_data->mqtt_client = esp_mqtt_client_init(&mqtt_client_cfg);
if (!mqtt_data->mqtt_client) {
ESP_LOGE(TAG, "esp_mqtt_client_init failed");
esp_mqtt_glue_deinit();
return ESP_FAIL;
}
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
esp_mqtt_client_register_event(mqtt_data->mqtt_client , ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
#endif
return ESP_OK;
}
static void esp_mqtt_glue_deinit(void)
{
esp_mqtt_glue_unsubscribe_all();
if (mqtt_data && mqtt_data->mqtt_client) {
esp_mqtt_client_destroy(mqtt_data->mqtt_client);
}
if (mqtt_data) {
free(mqtt_data);
mqtt_data = NULL;
}
}
esp_err_t esp_rmaker_mqtt_glue_setup(esp_rmaker_mqtt_config_t *mqtt_config)
{
mqtt_config->init = esp_mqtt_glue_init;
mqtt_config->deinit = esp_mqtt_glue_deinit;
mqtt_config->connect = esp_mqtt_glue_connect;
mqtt_config->disconnect = esp_mqtt_glue_disconnect;
mqtt_config->publish = esp_mqtt_glue_publish;
mqtt_config->subscribe = esp_mqtt_glue_subscribe;
mqtt_config->unsubscribe = esp_mqtt_glue_unsubscribe;
mqtt_config->setup_done = true;
return ESP_OK;
}

View File

@@ -0,0 +1,120 @@
// Copyright 2021 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 <string.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <nvs.h>
#include <esp_rmaker_utils.h>
#include <esp_partition.h>
static const char *TAG = "esp_rmaker_fctry";
#define RMAKER_FACTORY_NAMESPACE CONFIG_ESP_RMAKER_FACTORY_NAMESPACE
#define RMAKER_FACTORY_PART CONFIG_ESP_RMAKER_FACTORY_PARTITION_NAME
esp_err_t esp_rmaker_factory_init(void)
{
static bool esp_rmaker_storage_init_done;
if (esp_rmaker_storage_init_done) {
ESP_LOGW(TAG, "ESP RainMaker Storage already initialized");
return ESP_OK;
}
esp_err_t err = ESP_OK;
#ifdef CONFIG_ESP_RMAKER_ENCRYPT_FACTORY_PARTITION
const char *nvs_keys_partition_name = CONFIG_ESP_RMAKER_FACTORY_NVS_KEYS_PARTITION_NAME;
if (strlen(nvs_keys_partition_name) == 0) {
nvs_keys_partition_name = NULL;
}
ESP_LOGI(TAG, "Initialising factory partition in secure mode.");
const esp_partition_t* key_part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS_KEYS, nvs_keys_partition_name);
if (!key_part) {
ESP_LOGE(TAG, "Partition with subtype nvs_keys not found");
return ESP_FAIL;
}
nvs_sec_cfg_t cfg = {};
err = nvs_flash_read_security_cfg(key_part, &cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to read NVS security cfg: 0x%x", err);
return err;
}
err = nvs_flash_secure_init_partition(RMAKER_FACTORY_PART, &cfg);
#else
err = nvs_flash_init_partition(RMAKER_FACTORY_PART);
#endif
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS Flash init failed");
} else {
esp_rmaker_storage_init_done = true;
}
return err;
}
void *esp_rmaker_factory_get(const char *key)
{
nvs_handle handle;
esp_err_t err;
if ((err = nvs_open_from_partition(RMAKER_FACTORY_PART, RMAKER_FACTORY_NAMESPACE,
NVS_READONLY, &handle)) != ESP_OK) {
ESP_LOGD(TAG, "NVS open for %s %s %s failed with error %d", RMAKER_FACTORY_PART, RMAKER_FACTORY_NAMESPACE, key, err);
return NULL;
}
size_t required_size = 0;
if ((err = nvs_get_blob(handle, key, NULL, &required_size)) != ESP_OK) {
ESP_LOGD(TAG, "Failed to read key %s with error %d size %d", key, err, required_size);
nvs_close(handle);
return NULL;
}
void *value = MEM_CALLOC_EXTRAM(required_size + 1, 1); /* + 1 for NULL termination */
if (value) {
nvs_get_blob(handle, key, value, &required_size);
}
nvs_close(handle);
return value;
}
size_t esp_rmaker_factory_get_size(const char *key)
{
nvs_handle handle;
esp_err_t err;
if ((err = nvs_open_from_partition(RMAKER_FACTORY_PART, RMAKER_FACTORY_NAMESPACE,
NVS_READONLY, &handle)) != ESP_OK) {
ESP_LOGD(TAG, "NVS open for %s %s %s failed with error %d", RMAKER_FACTORY_PART, RMAKER_FACTORY_NAMESPACE, key, err);
return 0;
}
size_t required_size = 0;
if ((err = nvs_get_blob(handle, key, NULL, &required_size)) != ESP_OK) {
ESP_LOGD(TAG, "Failed to read key %s with error %d size %d", key, err, required_size);
}
nvs_close(handle);
return required_size;
}
esp_err_t esp_rmaker_factory_set(const char *key, void *value, size_t len)
{
nvs_handle handle;
esp_err_t err;
if ((err = nvs_open_from_partition(RMAKER_FACTORY_PART, RMAKER_FACTORY_NAMESPACE,
NVS_READWRITE, &handle)) != ESP_OK) {
ESP_LOGE(TAG, "NVS open failed with error %d", err);
return ESP_FAIL;
}
if ((err = nvs_set_blob(handle, key, value, len)) != ESP_OK) {
ESP_LOGE(TAG, "Failed to write key %s with error %d size %d", key, err, len);
nvs_close(handle);
return ESP_FAIL;
}
nvs_commit(handle);
nvs_close(handle);
return ESP_OK;
}

View File

@@ -0,0 +1,277 @@
// Copyright 2021 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 <esp_idf_version.h>
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
#include <esp_sntp.h>
#else
#include <lwip/apps/sntp.h>
#endif
#include <string.h>
#include <inttypes.h>
#include <esp_log.h>
#include <nvs.h>
#include <esp_rmaker_utils.h>
#include <esp_rmaker_common_events.h>
#include <esp_idf_version.h>
static const char *TAG = "esp_rmaker_time";
#define ESP_RMAKER_NVS_PART_NAME "nvs"
#define ESP_RMAKER_NVS_TIME_NAMESPACE "rmaker_time"
#define ESP_RMAKER_TZ_POSIX_NVS_NAME "tz_posix"
#define ESP_RMAKER_TZ_NVS_NAME "tz"
#define REF_TIME 1546300800 /* 01-Jan-2019 00:00:00 */
static bool init_done = false;
extern const char *esp_rmaker_tz_db_get_posix_str(const char *name);
#define ESP_RMAKER_DEF_TZ CONFIG_ESP_RMAKER_DEF_TIMEZONE
int esp_setenv(const char *name, const char *value, int rewrite)
{
/* IDF version lower than v4.4.3 not support parse bracket POSIX TZ in newlib.
Wrap setenv function to convert bracket POSIX TZ, such as <+08>-8 to TZ-08 */
#if ESP_IDF_VERSION <= ESP_IDF_VERSION_VAL(4, 4, 3)
#define ESP_TZNAME_MIN 3
#define ESP_TZNAME_MAX 10
if (value) {
const char *tzenv = value;
if (*tzenv == '<') {
++ tzenv;
char tzname[ESP_TZNAME_MIN + 1] = {0};
char real_value[6] = {0};
int n = 0;
if (sscanf(tzenv, "%10[-+0-9A-Za-z]%n", tzname, &n) <= 0 || n < ESP_TZNAME_MIN || n > ESP_TZNAME_MAX || '>' != tzenv[n]) {
ESP_LOGW(TAG, "Failed to convert Posix TZ %s", value);
goto exit;
}
tzname[0] = (tzname[0] == '-') ? '+' : '-';
sprintf(real_value, "TZ%s", tzname);
ESP_LOGI(TAG, "Real Posix TZ is %s", real_value);
return setenv(name, real_value, rewrite);
}
}
exit:
#endif
return setenv(name, value, rewrite);
}
esp_err_t esp_rmaker_get_local_time_str(char *buf, size_t buf_len)
{
struct tm timeinfo;
char strftime_buf[64];
time_t now;
time(&now);
localtime_r(&now, &timeinfo);
strftime(strftime_buf, sizeof(strftime_buf), "%c %z[%Z]", &timeinfo);
size_t print_size = snprintf(buf, buf_len, "%s, DST: %s", strftime_buf, timeinfo.tm_isdst ? "Yes" : "No");
if (print_size >= buf_len) {
ESP_LOGE(TAG, "Buffer size %d insufficient for localtime string. Required size: %d", buf_len, print_size);
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
static esp_err_t esp_rmaker_print_current_time(void)
{
char local_time[64];
if (esp_rmaker_get_local_time_str(local_time, sizeof(local_time)) == ESP_OK) {
if (esp_rmaker_time_check() == false) {
ESP_LOGI(TAG, "Time not synchronised yet.");
}
ESP_LOGI(TAG, "The current time is: %s.", local_time);
return ESP_OK;
}
return ESP_FAIL;
}
static char *__esp_rmaker_time_get_nvs(const char *key)
{
char *val = NULL;
nvs_handle handle;
esp_err_t err = nvs_open_from_partition(ESP_RMAKER_NVS_PART_NAME, ESP_RMAKER_NVS_TIME_NAMESPACE, NVS_READONLY, &handle);
if (err != ESP_OK) {
return NULL;
}
size_t len = 0;
if ((err = nvs_get_blob(handle, key, NULL, &len)) == ESP_OK) {
val = MEM_CALLOC_EXTRAM(1, len + 1); /* +1 for NULL termination */
if (val) {
nvs_get_blob(handle, key, val, &len);
}
}
nvs_close(handle);
return val;
}
static esp_err_t __esp_rmaker_time_set_nvs(const char *key, const char *val)
{
nvs_handle handle;
esp_err_t err = nvs_open_from_partition(ESP_RMAKER_NVS_PART_NAME, ESP_RMAKER_NVS_TIME_NAMESPACE, NVS_READWRITE, &handle);
if (err != ESP_OK) {
return err;
}
err = nvs_set_blob(handle, key, val, strlen(val));
nvs_commit(handle);
nvs_close(handle);
return err;
}
char *esp_rmaker_time_get_timezone_posix(void)
{
return __esp_rmaker_time_get_nvs(ESP_RMAKER_TZ_POSIX_NVS_NAME);
}
char *esp_rmaker_time_get_timezone(void)
{
return __esp_rmaker_time_get_nvs(ESP_RMAKER_TZ_NVS_NAME);
}
esp_err_t esp_rmaker_time_set_timezone_posix(const char *tz_posix)
{
esp_err_t err = __esp_rmaker_time_set_nvs(ESP_RMAKER_TZ_POSIX_NVS_NAME, tz_posix);
if (err == ESP_OK) {
esp_setenv("TZ", tz_posix, 1);
tzset();
esp_event_post(RMAKER_COMMON_EVENT, RMAKER_EVENT_TZ_POSIX_CHANGED,
(void *)tz_posix, strlen(tz_posix) + 1, portMAX_DELAY);
esp_rmaker_print_current_time();
}
return err;
}
esp_err_t esp_rmaker_time_set_timezone(const char *tz)
{
const char *tz_posix = esp_rmaker_tz_db_get_posix_str(tz);
if (!tz_posix) {
return ESP_ERR_INVALID_ARG;
}
esp_err_t err = esp_rmaker_time_set_timezone_posix(tz_posix);
if (err == ESP_OK) {
esp_event_post(RMAKER_COMMON_EVENT, RMAKER_EVENT_TZ_CHANGED, (void *)tz,
strlen(tz) + 1, portMAX_DELAY);
err = __esp_rmaker_time_set_nvs(ESP_RMAKER_TZ_NVS_NAME, tz);
}
return err;
}
esp_err_t esp_rmaker_timezone_enable(void)
{
char *tz_posix = esp_rmaker_time_get_timezone_posix();
if (tz_posix) {
esp_setenv("TZ", tz_posix, 1);
tzset();
free(tz_posix);
} else {
if (strlen(ESP_RMAKER_DEF_TZ) > 0) {
const char *tz_def = esp_rmaker_tz_db_get_posix_str(ESP_RMAKER_DEF_TZ);
if (tz_def) {
esp_setenv("TZ", tz_def, 1);
tzset();
return ESP_OK;
} else {
ESP_LOGE(TAG, "Invalid Timezone %s specified.", ESP_RMAKER_DEF_TZ);
return ESP_ERR_INVALID_ARG;
}
}
}
return ESP_OK;
}
static void esp_rmaker_time_sync_cb(struct timeval *tv)
{
ESP_LOGI(TAG, "SNTP Synchronised.");
esp_rmaker_print_current_time();
}
esp_err_t esp_rmaker_time_sync_init(esp_rmaker_time_config_t *config)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
if (esp_sntp_enabled()) {
#else
if (sntp_enabled()) {
#endif
ESP_LOGI(TAG, "SNTP already initialized.");
init_done = true;
return ESP_OK;
}
char *sntp_server_name;
if (!config || !config->sntp_server_name) {
sntp_server_name = CONFIG_ESP_RMAKER_SNTP_SERVER_NAME;
} else {
sntp_server_name = config->sntp_server_name;
}
ESP_LOGI(TAG, "Initializing SNTP. Using the SNTP server: %s", sntp_server_name);
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
esp_sntp_setservername(0, sntp_server_name);
esp_sntp_init();
#else
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, sntp_server_name);
sntp_init();
#endif
if (config && config->sync_time_cb) {
sntp_set_time_sync_notification_cb(config->sync_time_cb);
} else {
sntp_set_time_sync_notification_cb(esp_rmaker_time_sync_cb);
}
esp_rmaker_timezone_enable();
init_done = true;
return ESP_OK;
}
bool esp_rmaker_time_check(void)
{
time_t now;
time(&now);
if (now > REF_TIME) {
return true;
}
return false;
}
#define DEFAULT_TICKS (2000 / portTICK_PERIOD_MS) /* 2 seconds in ticks */
esp_err_t esp_rmaker_time_wait_for_sync(uint32_t ticks_to_wait)
{
if (!init_done) {
ESP_LOGW(TAG, "Time sync not initialized using 'esp_rmaker_time_sync_init'");
}
ESP_LOGW(TAG, "Waiting for time to be synchronized. This may take time.");
uint32_t ticks_remaining = ticks_to_wait;
uint32_t ticks = DEFAULT_TICKS;
while (ticks_remaining > 0) {
if (esp_rmaker_time_check() == true) {
break;
}
ESP_LOGD(TAG, "Time not synchronized yet. Retrying...");
ticks = ticks_remaining < DEFAULT_TICKS ? ticks_remaining : DEFAULT_TICKS;
ticks_remaining -= ticks;
vTaskDelay(ticks);
}
/* Check if ticks_to_wait expired and time is not synchronized yet. */
if (esp_rmaker_time_check() == false) {
ESP_LOGE(TAG, "Time not synchronized within the provided ticks: %" PRIu32, ticks_to_wait);
return ESP_FAIL;
}
/* Get current time */
esp_rmaker_print_current_time();
return ESP_OK;
}

View File

@@ -0,0 +1,516 @@
// MIT License
//
// Copyright (c) 2020 Nayar Systems
// Copyright (c) 2020 Jacob Lambert
// Copyright 2021 Espressif Systems (Shanghai) PTE LTD
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Original code taken from: https://github.com/jdlambert/micro_tz_db
// which was forked from https://github.com/nayarsystems/posix_tz_db
#include <stdio.h>
typedef struct {
const char *name;
const char *posix_str;
} esp_rmaker_tz_db_pair_t;
static const esp_rmaker_tz_db_pair_t esp_rmaker_tz_db_tzs[] = {
{"Africa/Abidjan", "GMT0"},
{"Africa/Accra", "GMT0"},
{"Africa/Addis_Ababa", "EAT-3"},
{"Africa/Algiers", "CET-1"},
{"Africa/Asmara", "EAT-3"},
{"Africa/Bamako", "GMT0"},
{"Africa/Bangui", "WAT-1"},
{"Africa/Banjul", "GMT0"},
{"Africa/Bissau", "GMT0"},
{"Africa/Blantyre", "CAT-2"},
{"Africa/Brazzaville", "WAT-1"},
{"Africa/Bujumbura", "CAT-2"},
{"Africa/Cairo", "EET-2"},
{"Africa/Casablanca", "<+01>-1"},
{"Africa/Ceuta", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Africa/Conakry", "GMT0"},
{"Africa/Dakar", "GMT0"},
{"Africa/Dar_es_Salaam", "EAT-3"},
{"Africa/Djibouti", "EAT-3"},
{"Africa/Douala", "WAT-1"},
{"Africa/El_Aaiun", "<+01>-1"},
{"Africa/Freetown", "GMT0"},
{"Africa/Gaborone", "CAT-2"},
{"Africa/Harare", "CAT-2"},
{"Africa/Johannesburg", "SAST-2"},
{"Africa/Juba", "EAT-3"},
{"Africa/Kampala", "EAT-3"},
{"Africa/Khartoum", "CAT-2"},
{"Africa/Kigali", "CAT-2"},
{"Africa/Kinshasa", "WAT-1"},
{"Africa/Lagos", "WAT-1"},
{"Africa/Libreville", "WAT-1"},
{"Africa/Lome", "GMT0"},
{"Africa/Luanda", "WAT-1"},
{"Africa/Lubumbashi", "CAT-2"},
{"Africa/Lusaka", "CAT-2"},
{"Africa/Malabo", "WAT-1"},
{"Africa/Maputo", "CAT-2"},
{"Africa/Maseru", "SAST-2"},
{"Africa/Mbabane", "SAST-2"},
{"Africa/Mogadishu", "EAT-3"},
{"Africa/Monrovia", "GMT0"},
{"Africa/Nairobi", "EAT-3"},
{"Africa/Ndjamena", "WAT-1"},
{"Africa/Niamey", "WAT-1"},
{"Africa/Nouakchott", "GMT0"},
{"Africa/Ouagadougou", "GMT0"},
{"Africa/Porto-Novo", "WAT-1"},
{"Africa/Sao_Tome", "GMT0"},
{"Africa/Tripoli", "EET-2"},
{"Africa/Tunis", "CET-1"},
{"Africa/Windhoek", "CAT-2"},
{"America/Adak", "HST10HDT,M3.2.0,M11.1.0"},
{"America/Anchorage", "AKST9AKDT,M3.2.0,M11.1.0"},
{"America/Anguilla", "AST4"},
{"America/Antigua", "AST4"},
{"America/Araguaina", "<-03>3"},
{"America/Argentina/Buenos_Aires", "<-03>3"},
{"America/Argentina/Catamarca", "<-03>3"},
{"America/Argentina/Cordoba", "<-03>3"},
{"America/Argentina/Jujuy", "<-03>3"},
{"America/Argentina/La_Rioja", "<-03>3"},
{"America/Argentina/Mendoza", "<-03>3"},
{"America/Argentina/Rio_Gallegos", "<-03>3"},
{"America/Argentina/Salta", "<-03>3"},
{"America/Argentina/San_Juan", "<-03>3"},
{"America/Argentina/San_Luis", "<-03>3"},
{"America/Argentina/Tucuman", "<-03>3"},
{"America/Argentina/Ushuaia", "<-03>3"},
{"America/Aruba", "AST4"},
{"America/Asuncion", "<-04>4<-03>,M10.1.0/0,M3.4.0/0"},
{"America/Atikokan", "EST5"},
{"America/Bahia", "<-03>3"},
{"America/Bahia_Banderas", "CST6CDT,M4.1.0,M10.5.0"},
{"America/Barbados", "AST4"},
{"America/Belem", "<-03>3"},
{"America/Belize", "CST6"},
{"America/Blanc-Sablon", "AST4"},
{"America/Boa_Vista", "<-04>4"},
{"America/Bogota", "<-05>5"},
{"America/Boise", "MST7MDT,M3.2.0,M11.1.0"},
{"America/Cambridge_Bay", "MST7MDT,M3.2.0,M11.1.0"},
{"America/Campo_Grande", "<-04>4"},
{"America/Cancun", "EST5"},
{"America/Caracas", "<-04>4"},
{"America/Cayenne", "<-03>3"},
{"America/Cayman", "EST5"},
{"America/Chicago", "CST6CDT,M3.2.0,M11.1.0"},
{"America/Chihuahua", "MST7MDT,M4.1.0,M10.5.0"},
{"America/Costa_Rica", "CST6"},
{"America/Creston", "MST7"},
{"America/Cuiaba", "<-04>4"},
{"America/Curacao", "AST4"},
{"America/Danmarkshavn", "GMT0"},
{"America/Dawson", "MST7"},
{"America/Dawson_Creek", "MST7"},
{"America/Denver", "MST7MDT,M3.2.0,M11.1.0"},
{"America/Detroit", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Dominica", "AST4"},
{"America/Edmonton", "MST7MDT,M3.2.0,M11.1.0"},
{"America/Eirunepe", "<-05>5"},
{"America/El_Salvador", "CST6"},
{"America/Fortaleza", "<-03>3"},
{"America/Fort_Nelson", "MST7"},
{"America/Glace_Bay", "AST4ADT,M3.2.0,M11.1.0"},
{"America/Godthab", "<-03>3<-02>,M3.5.0/-2,M10.5.0/-1"},
{"America/Goose_Bay", "AST4ADT,M3.2.0,M11.1.0"},
{"America/Grand_Turk", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Grenada", "AST4"},
{"America/Guadeloupe", "AST4"},
{"America/Guatemala", "CST6"},
{"America/Guayaquil", "<-05>5"},
{"America/Guyana", "<-04>4"},
{"America/Halifax", "AST4ADT,M3.2.0,M11.1.0"},
{"America/Havana", "CST5CDT,M3.2.0/0,M11.1.0/1"},
{"America/Hermosillo", "MST7"},
{"America/Indiana/Indianapolis", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Indiana/Knox", "CST6CDT,M3.2.0,M11.1.0"},
{"America/Indiana/Marengo", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Indiana/Petersburg", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Indiana/Tell_City", "CST6CDT,M3.2.0,M11.1.0"},
{"America/Indiana/Vevay", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Indiana/Vincennes", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Indiana/Winamac", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Inuvik", "MST7MDT,M3.2.0,M11.1.0"},
{"America/Iqaluit", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Jamaica", "EST5"},
{"America/Juneau", "AKST9AKDT,M3.2.0,M11.1.0"},
{"America/Kentucky/Louisville", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Kentucky/Monticello", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Kralendijk", "AST4"},
{"America/La_Paz", "<-04>4"},
{"America/Lima", "<-05>5"},
{"America/Los_Angeles", "PST8PDT,M3.2.0,M11.1.0"},
{"America/Lower_Princes", "AST4"},
{"America/Maceio", "<-03>3"},
{"America/Managua", "CST6"},
{"America/Manaus", "<-04>4"},
{"America/Marigot", "AST4"},
{"America/Martinique", "AST4"},
{"America/Matamoros", "CST6CDT,M3.2.0,M11.1.0"},
{"America/Mazatlan", "MST7MDT,M4.1.0,M10.5.0"},
{"America/Menominee", "CST6CDT,M3.2.0,M11.1.0"},
{"America/Merida", "CST6CDT,M4.1.0,M10.5.0"},
{"America/Metlakatla", "AKST9AKDT,M3.2.0,M11.1.0"},
{"America/Mexico_City", "CST6CDT,M4.1.0,M10.5.0"},
{"America/Miquelon", "<-03>3<-02>,M3.2.0,M11.1.0"},
{"America/Moncton", "AST4ADT,M3.2.0,M11.1.0"},
{"America/Monterrey", "CST6CDT,M4.1.0,M10.5.0"},
{"America/Montevideo", "<-03>3"},
{"America/Montreal", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Montserrat", "AST4"},
{"America/Nassau", "EST5EDT,M3.2.0,M11.1.0"},
{"America/New_York", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Nipigon", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Nome", "AKST9AKDT,M3.2.0,M11.1.0"},
{"America/Noronha", "<-02>2"},
{"America/North_Dakota/Beulah", "CST6CDT,M3.2.0,M11.1.0"},
{"America/North_Dakota/Center", "CST6CDT,M3.2.0,M11.1.0"},
{"America/North_Dakota/New_Salem", "CST6CDT,M3.2.0,M11.1.0"},
{"America/Ojinaga", "MST7MDT,M3.2.0,M11.1.0"},
{"America/Panama", "EST5"},
{"America/Pangnirtung", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Paramaribo", "<-03>3"},
{"America/Phoenix", "MST7"},
{"America/Port-au-Prince", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Port_of_Spain", "AST4"},
{"America/Porto_Velho", "<-04>4"},
{"America/Puerto_Rico", "AST4"},
{"America/Punta_Arenas", "<-03>3"},
{"America/Rainy_River", "CST6CDT,M3.2.0,M11.1.0"},
{"America/Rankin_Inlet", "CST6CDT,M3.2.0,M11.1.0"},
{"America/Recife", "<-03>3"},
{"America/Regina", "CST6"},
{"America/Resolute", "CST6CDT,M3.2.0,M11.1.0"},
{"America/Rio_Branco", "<-05>5"},
{"America/Santarem", "<-03>3"},
{"America/Santiago", "<-04>4<-03>,M9.1.6/24,M4.1.6/24"},
{"America/Santo_Domingo", "AST4"},
{"America/Sao_Paulo", "<-03>3"},
{"America/Scoresbysund", "<-01>1<+00>,M3.5.0/0,M10.5.0/1"},
{"America/Sitka", "AKST9AKDT,M3.2.0,M11.1.0"},
{"America/St_Barthelemy", "AST4"},
{"America/St_Johns", "NST3:30NDT,M3.2.0,M11.1.0"},
{"America/St_Kitts", "AST4"},
{"America/St_Lucia", "AST4"},
{"America/St_Thomas", "AST4"},
{"America/St_Vincent", "AST4"},
{"America/Swift_Current", "CST6"},
{"America/Tegucigalpa", "CST6"},
{"America/Thule", "AST4ADT,M3.2.0,M11.1.0"},
{"America/Thunder_Bay", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Tijuana", "PST8PDT,M3.2.0,M11.1.0"},
{"America/Toronto", "EST5EDT,M3.2.0,M11.1.0"},
{"America/Tortola", "AST4"},
{"America/Vancouver", "PST8PDT,M3.2.0,M11.1.0"},
{"America/Whitehorse", "MST7"},
{"America/Winnipeg", "CST6CDT,M3.2.0,M11.1.0"},
{"America/Yakutat", "AKST9AKDT,M3.2.0,M11.1.0"},
{"America/Yellowknife", "MST7MDT,M3.2.0,M11.1.0"},
{"Antarctica/Casey", "<+08>-8"},
{"Antarctica/Davis", "<+07>-7"},
{"Antarctica/DumontDUrville", "<+10>-10"},
{"Antarctica/Macquarie", "<+11>-11"},
{"Antarctica/Mawson", "<+05>-5"},
{"Antarctica/McMurdo", "NZST-12NZDT,M9.5.0,M4.1.0/3"},
{"Antarctica/Palmer", "<-03>3"},
{"Antarctica/Rothera", "<-03>3"},
{"Antarctica/Syowa", "<+03>-3"},
{"Antarctica/Troll", "<+00>0<+02>-2,M3.5.0/1,M10.5.0/3"},
{"Antarctica/Vostok", "<+06>-6"},
{"Arctic/Longyearbyen", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Asia/Aden", "<+03>-3"},
{"Asia/Almaty", "<+06>-6"},
{"Asia/Amman", "EET-2EEST,M3.5.4/24,M10.5.5/1"},
{"Asia/Anadyr", "<+12>-12"},
{"Asia/Aqtau", "<+05>-5"},
{"Asia/Aqtobe", "<+05>-5"},
{"Asia/Ashgabat", "<+05>-5"},
{"Asia/Atyrau", "<+05>-5"},
{"Asia/Baghdad", "<+03>-3"},
{"Asia/Bahrain", "<+03>-3"},
{"Asia/Baku", "<+04>-4"},
{"Asia/Bangkok", "<+07>-7"},
{"Asia/Barnaul", "<+07>-7"},
{"Asia/Beirut", "EET-2EEST,M3.5.0/0,M10.5.0/0"},
{"Asia/Bishkek", "<+06>-6"},
{"Asia/Brunei", "<+08>-8"},
{"Asia/Chita", "<+09>-9"},
{"Asia/Choibalsan", "<+08>-8"},
{"Asia/Colombo", "<+0530>-5:30"},
{"Asia/Damascus", "EET-2EEST,M3.5.5/0,M10.5.5/0"},
{"Asia/Dhaka", "<+06>-6"},
{"Asia/Dili", "<+09>-9"},
{"Asia/Dubai", "<+04>-4"},
{"Asia/Dushanbe", "<+05>-5"},
{"Asia/Famagusta", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Asia/Gaza", "EET-2EEST,M3.5.5/0,M10.5.6/1"},
{"Asia/Hebron", "EET-2EEST,M3.5.5/0,M10.5.6/1"},
{"Asia/Ho_Chi_Minh", "<+07>-7"},
{"Asia/Hong_Kong", "HKT-8"},
{"Asia/Hovd", "<+07>-7"},
{"Asia/Irkutsk", "<+08>-8"},
{"Asia/Jakarta", "WIB-7"},
{"Asia/Jayapura", "WIT-9"},
{"Asia/Jerusalem", "IST-2IDT,M3.4.4/26,M10.5.0"},
{"Asia/Kabul", "<+0430>-4:30"},
{"Asia/Kamchatka", "<+12>-12"},
{"Asia/Karachi", "PKT-5"},
{"Asia/Kathmandu", "<+0545>-5:45"},
{"Asia/Khandyga", "<+09>-9"},
{"Asia/Kolkata", "IST-5:30"},
{"Asia/Krasnoyarsk", "<+07>-7"},
{"Asia/Kuala_Lumpur", "<+08>-8"},
{"Asia/Kuching", "<+08>-8"},
{"Asia/Kuwait", "<+03>-3"},
{"Asia/Macau", "CST-8"},
{"Asia/Magadan", "<+11>-11"},
{"Asia/Makassar", "WITA-8"},
{"Asia/Manila", "PST-8"},
{"Asia/Muscat", "<+04>-4"},
{"Asia/Nicosia", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Asia/Novokuznetsk", "<+07>-7"},
{"Asia/Novosibirsk", "<+07>-7"},
{"Asia/Omsk", "<+06>-6"},
{"Asia/Oral", "<+05>-5"},
{"Asia/Phnom_Penh", "<+07>-7"},
{"Asia/Pontianak", "WIB-7"},
{"Asia/Pyongyang", "KST-9"},
{"Asia/Qatar", "<+03>-3"},
{"Asia/Qyzylorda", "<+05>-5"},
{"Asia/Riyadh", "<+03>-3"},
{"Asia/Sakhalin", "<+11>-11"},
{"Asia/Samarkand", "<+05>-5"},
{"Asia/Seoul", "KST-9"},
{"Asia/Shanghai", "CST-8"},
{"Asia/Singapore", "<+08>-8"},
{"Asia/Srednekolymsk", "<+11>-11"},
{"Asia/Taipei", "CST-8"},
{"Asia/Tashkent", "<+05>-5"},
{"Asia/Tbilisi", "<+04>-4"},
{"Asia/Tehran", "<+0330>-3:30<+0430>,J79/24,J263/24"},
{"Asia/Thimphu", "<+06>-6"},
{"Asia/Tokyo", "JST-9"},
{"Asia/Tomsk", "<+07>-7"},
{"Asia/Ulaanbaatar", "<+08>-8"},
{"Asia/Urumqi", "<+06>-6"},
{"Asia/Ust-Nera", "<+10>-10"},
{"Asia/Vientiane", "<+07>-7"},
{"Asia/Vladivostok", "<+10>-10"},
{"Asia/Yakutsk", "<+09>-9"},
{"Asia/Yangon", "<+0630>-6:30"},
{"Asia/Yekaterinburg", "<+05>-5"},
{"Asia/Yerevan", "<+04>-4"},
{"Atlantic/Azores", "<-01>1<+00>,M3.5.0/0,M10.5.0/1"},
{"Atlantic/Bermuda", "AST4ADT,M3.2.0,M11.1.0"},
{"Atlantic/Canary", "WET0WEST,M3.5.0/1,M10.5.0"},
{"Atlantic/Cape_Verde", "<-01>1"},
{"Atlantic/Faroe", "WET0WEST,M3.5.0/1,M10.5.0"},
{"Atlantic/Madeira", "WET0WEST,M3.5.0/1,M10.5.0"},
{"Atlantic/Reykjavik", "GMT0"},
{"Atlantic/South_Georgia", "<-02>2"},
{"Atlantic/Stanley", "<-03>3"},
{"Atlantic/St_Helena", "GMT0"},
{"Australia/Adelaide", "ACST-9:30ACDT,M10.1.0,M4.1.0/3"},
{"Australia/Brisbane", "AEST-10"},
{"Australia/Broken_Hill", "ACST-9:30ACDT,M10.1.0,M4.1.0/3"},
{"Australia/Currie", "AEST-10AEDT,M10.1.0,M4.1.0/3"},
{"Australia/Darwin", "ACST-9:30"},
{"Australia/Eucla", "<+0845>-8:45"},
{"Australia/Hobart", "AEST-10AEDT,M10.1.0,M4.1.0/3"},
{"Australia/Lindeman", "AEST-10"},
{"Australia/Lord_Howe", "<+1030>-10:30<+11>-11,M10.1.0,M4.1.0"},
{"Australia/Melbourne", "AEST-10AEDT,M10.1.0,M4.1.0/3"},
{"Australia/Perth", "AWST-8"},
{"Australia/Sydney", "AEST-10AEDT,M10.1.0,M4.1.0/3"},
{"Europe/Amsterdam", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Andorra", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Astrakhan", "<+04>-4"},
{"Europe/Athens", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Europe/Belgrade", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Berlin", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Bratislava", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Brussels", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Bucharest", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Europe/Budapest", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Busingen", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Chisinau", "EET-2EEST,M3.5.0,M10.5.0/3"},
{"Europe/Copenhagen", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Dublin", "IST-1GMT0,M10.5.0,M3.5.0/1"},
{"Europe/Gibraltar", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Guernsey", "GMT0BST,M3.5.0/1,M10.5.0"},
{"Europe/Helsinki", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Europe/Isle_of_Man", "GMT0BST,M3.5.0/1,M10.5.0"},
{"Europe/Istanbul", "<+03>-3"},
{"Europe/Jersey", "GMT0BST,M3.5.0/1,M10.5.0"},
{"Europe/Kaliningrad", "EET-2"},
{"Europe/Kiev", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Europe/Kirov", "<+03>-3"},
{"Europe/Lisbon", "WET0WEST,M3.5.0/1,M10.5.0"},
{"Europe/Ljubljana", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/London", "GMT0BST,M3.5.0/1,M10.5.0"},
{"Europe/Luxembourg", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Madrid", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Malta", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Mariehamn", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Europe/Minsk", "<+03>-3"},
{"Europe/Monaco", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Moscow", "MSK-3"},
{"Europe/Oslo", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Paris", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Podgorica", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Prague", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Riga", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Europe/Rome", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Samara", "<+04>-4"},
{"Europe/San_Marino", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Sarajevo", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Saratov", "<+04>-4"},
{"Europe/Simferopol", "MSK-3"},
{"Europe/Skopje", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Sofia", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Europe/Stockholm", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Tallinn", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Europe/Tirane", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Ulyanovsk", "<+04>-4"},
{"Europe/Uzhgorod", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Europe/Vaduz", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Vatican", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Vienna", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Vilnius", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Europe/Volgograd", "<+04>-4"},
{"Europe/Warsaw", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Zagreb", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Europe/Zaporozhye", "EET-2EEST,M3.5.0/3,M10.5.0/4"},
{"Europe/Zurich", "CET-1CEST,M3.5.0,M10.5.0/3"},
{"Indian/Antananarivo", "EAT-3"},
{"Indian/Chagos", "<+06>-6"},
{"Indian/Christmas", "<+07>-7"},
{"Indian/Cocos", "<+0630>-6:30"},
{"Indian/Comoro", "EAT-3"},
{"Indian/Kerguelen", "<+05>-5"},
{"Indian/Mahe", "<+04>-4"},
{"Indian/Maldives", "<+05>-5"},
{"Indian/Mauritius", "<+04>-4"},
{"Indian/Mayotte", "EAT-3"},
{"Indian/Reunion", "<+04>-4"},
{"Pacific/Apia", "<+13>-13<+14>,M9.5.0/3,M4.1.0/4"},
{"Pacific/Auckland", "NZST-12NZDT,M9.5.0,M4.1.0/3"},
{"Pacific/Bougainville", "<+11>-11"},
{"Pacific/Chatham", "<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45"},
{"Pacific/Chuuk", "<+10>-10"},
{"Pacific/Easter", "<-06>6<-05>,M9.1.6/22,M4.1.6/22"},
{"Pacific/Efate", "<+11>-11"},
{"Pacific/Enderbury", "<+13>-13"},
{"Pacific/Fakaofo", "<+13>-13"},
{"Pacific/Fiji", "<+12>-12<+13>,M11.2.0,M1.2.3/99"},
{"Pacific/Funafuti", "<+12>-12"},
{"Pacific/Galapagos", "<-06>6"},
{"Pacific/Gambier", "<-09>9"},
{"Pacific/Guadalcanal", "<+11>-11"},
{"Pacific/Guam", "ChST-10"},
{"Pacific/Honolulu", "HST10"},
{"Pacific/Kiritimati", "<+14>-14"},
{"Pacific/Kosrae", "<+11>-11"},
{"Pacific/Kwajalein", "<+12>-12"},
{"Pacific/Majuro", "<+12>-12"},
{"Pacific/Marquesas", "<-0930>9:30"},
{"Pacific/Midway", "SST11"},
{"Pacific/Nauru", "<+12>-12"},
{"Pacific/Niue", "<-11>11"},
{"Pacific/Norfolk", "<+11>-11<+12>,M10.1.0,M4.1.0/3"},
{"Pacific/Noumea", "<+11>-11"},
{"Pacific/Pago_Pago", "SST11"},
{"Pacific/Palau", "<+09>-9"},
{"Pacific/Pitcairn", "<-08>8"},
{"Pacific/Pohnpei", "<+11>-11"},
{"Pacific/Port_Moresby", "<+10>-10"},
{"Pacific/Rarotonga", "<-10>10"},
{"Pacific/Saipan", "ChST-10"},
{"Pacific/Tahiti", "<-10>10"},
{"Pacific/Tarawa", "<+12>-12"},
{"Pacific/Tongatapu", "<+13>-13"},
{"Pacific/Wake", "<+12>-12"},
{"Pacific/Wallis", "<+12>-12"}
};
static char lower(char start) {
if ('A' <= start && start <= 'Z') {
return start - 'A' + 'a';
}
return start;
}
/**
* Basically strcmp, but accounting for spaces that have become underscores
* @param[in] target - the 0-terminated string on the left hand side of the comparison
* @param[in] other - the 0-terminated string on the right hand side of the comparison
* @return > 0 if target comes before other alphabetically,
* ==0 if they're the same,
* < 0 if other comes before target alphabetically
* (we don't expect NULL arguments, but, -1 if either is NULL)
**/
static int tz_name_cmp(const char * target, const char * other)
{
if (!target || !other) {
return -1;
}
while (*target) {
if (lower(*target) != lower(*other)) {
break;
}
do {
target++;
} while (*target == '_');
do {
other++;
} while (*other == '_');
}
return lower(*target) - lower(*other);
}
const char *esp_rmaker_tz_db_get_posix_str(const char *name)
{
int lo = 0, hi = sizeof(esp_rmaker_tz_db_tzs) / sizeof(esp_rmaker_tz_db_pair_t);
while (lo < hi) {
int mid = (lo + hi) / 2;
esp_rmaker_tz_db_pair_t mid_pair = esp_rmaker_tz_db_tzs[mid];
int comparison = tz_name_cmp(name, mid_pair.name);
if (comparison == 0) {
return mid_pair.posix_str;
} else if (comparison < 0) {
hi = mid;
} else {
lo = mid + 1;
}
}
return NULL;
}

View File

@@ -0,0 +1,174 @@
// Copyright 2021 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 <stdint.h>
#include <freertos/FreeRTOS.h>
#include <freertos/timers.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <nvs_flash.h>
#include <esp_rmaker_common_events.h>
ESP_EVENT_DEFINE_BASE(RMAKER_COMMON_EVENT);
static TimerHandle_t reboot_timer;
static TimerHandle_t reset_timer;
static void esp_rmaker_reboot_cb(TimerHandle_t handle)
{
xTimerDelete(reboot_timer, 10);
reboot_timer = NULL;
esp_restart();
}
esp_err_t esp_rmaker_reboot(int8_t seconds)
{
/* If specified time is 0, reboot immediately */
if (seconds == 0) {
esp_event_post(RMAKER_COMMON_EVENT, RMAKER_EVENT_REBOOT, &seconds, sizeof(seconds), portMAX_DELAY);
esp_restart();
return ESP_OK;
} else if (reboot_timer) {
/* If reboot timer already exists, it means that a reboot operation is already in progress.
* So, just return an error from here.
*/
return ESP_FAIL;
}
reboot_timer = xTimerCreate("rmaker_reboot_tm", (seconds * 1000) / portTICK_PERIOD_MS,
pdFALSE, NULL, esp_rmaker_reboot_cb);
if (reboot_timer) {
if (xTimerStart(reboot_timer, 10) != pdTRUE) {
xTimerDelete(reboot_timer, 10);
reboot_timer = NULL;
return ESP_FAIL;
}
} else {
return ESP_ERR_NO_MEM;
}
esp_event_post(RMAKER_COMMON_EVENT, RMAKER_EVENT_REBOOT, &seconds, sizeof(seconds), portMAX_DELAY);
return ESP_OK;
}
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
static esp_err_t __esp_rmaker_wifi_reset(int8_t reboot_seconds)
{
esp_wifi_restore();
if (reboot_seconds >= 0) {
esp_rmaker_reboot(reboot_seconds);
}
return ESP_OK;
}
#endif /* CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI */
static esp_err_t __esp_rmaker_factory_reset(int8_t reboot_seconds)
{
nvs_flash_deinit();
nvs_flash_erase();
if (reboot_seconds >= 0) {
esp_rmaker_reboot(reboot_seconds);
}
return ESP_OK;
}
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
static void esp_rmaker_wifi_reset_cb(TimerHandle_t handle)
{
/* (Hack) Using the timer id as reboot seconds */
int8_t *reboot_seconds = (int8_t *)pvTimerGetTimerID(handle);
if (reboot_seconds) {
__esp_rmaker_wifi_reset((int8_t)*reboot_seconds);
free(reboot_seconds);
} else {
__esp_rmaker_wifi_reset(0);
}
xTimerDelete(reset_timer, 10);
reset_timer = NULL;
}
#endif /* CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI */
static void esp_rmaker_factory_reset_cb(TimerHandle_t handle)
{
/* (Hack) Using the timer id as reboot seconds */
int8_t *reboot_seconds = (int8_t *)pvTimerGetTimerID(handle);
if (reboot_seconds) {
__esp_rmaker_factory_reset((int8_t)*reboot_seconds);
free(reboot_seconds);
} else {
__esp_rmaker_factory_reset(0);
}
xTimerDelete(reset_timer, 10);
reset_timer = NULL;
}
static esp_err_t esp_rmaker_start_reset_timer(int8_t reset_seconds, int8_t reboot_seconds, TimerCallbackFunction_t cb)
{
/* If reset timer already exists, it means that a reset operation is already in progress.
* So, just return an error from here.
*/
if (reset_timer) {
return ESP_FAIL;
}
/* (Hack) Passing the reboot delay as timer id */
int8_t *reboot_delay = calloc(1, sizeof(int8_t));
if (reboot_delay) {
*reboot_delay = reboot_seconds;
}
reset_timer = xTimerCreate("rmaker_reset_tm", (reset_seconds * 1000) / portTICK_PERIOD_MS,
pdFALSE, (void *)reboot_delay, cb);
if (reset_timer) {
if (xTimerStart(reset_timer, 10) != pdTRUE) {
xTimerDelete(reset_timer, 10);
reset_timer = NULL;
return ESP_FAIL;
}
} else {
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
esp_err_t esp_rmaker_wifi_reset(int8_t reset_seconds, int8_t reboot_seconds)
{
#ifdef CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI
esp_err_t err = ESP_FAIL;
/* If reset time is 0, just do it right away */
if (reset_seconds == 0) {
err = __esp_rmaker_wifi_reset(reboot_seconds);
} else {
/* Else start a timer so that the task gets performed after the specified delay */
err = esp_rmaker_start_reset_timer(reset_seconds, reboot_seconds, esp_rmaker_wifi_reset_cb);
}
if (err == ESP_OK) {
esp_event_post(RMAKER_COMMON_EVENT, RMAKER_EVENT_WIFI_RESET, NULL, 0, portMAX_DELAY);
}
return ESP_OK;
#else
return ESP_ERR_NOT_SUPPORTED;
#endif /* CONFIG_ESP_RMAKER_NETWORK_OVER_WIFI */
}
esp_err_t esp_rmaker_factory_reset(int8_t reset_seconds, int8_t reboot_seconds)
{
esp_err_t err = ESP_FAIL;
/* If reset time is 0, just do it right away */
if (reset_seconds == 0) {
err = __esp_rmaker_factory_reset(reboot_seconds);
} else {
/* Else start a timer so that the task gets performed after the specified delay */
err = esp_rmaker_start_reset_timer(reset_seconds, reboot_seconds, esp_rmaker_factory_reset_cb);
}
if (err == ESP_OK) {
esp_event_post(RMAKER_COMMON_EVENT, RMAKER_EVENT_FACTORY_RESET, NULL, 0, portMAX_DELAY);
}
return ESP_OK;
}

View File

@@ -0,0 +1,148 @@
// Copyright 2021 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 <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <esp_err.h>
#include <esp_log.h>
#include <esp_rmaker_work_queue.h>
#define ESP_RMAKER_TASK_QUEUE_SIZE 8
#define ESP_RMAKER_TASK_STACK CONFIG_ESP_RMAKER_WORK_QUEUE_TASK_STACK
#define ESP_RMAKER_TASK_PRIORITY CONFIG_ESP_RMAKER_WORK_QUEUE_TASK_PRIORITY
static const char *TAG = "esp_rmaker_work_queue";
typedef enum {
WORK_QUEUE_STATE_DEINIT = 0,
WORK_QUEUE_STATE_INIT_DONE,
WORK_QUEUE_STATE_RUNNING,
WORK_QUEUE_STATE_STOP_REQUESTED,
} esp_rmaker_work_queue_state_t;
typedef struct {
esp_rmaker_work_fn_t work_fn;
void *priv_data;
} esp_rmaker_work_queue_entry_t;
static QueueHandle_t work_queue;
static esp_rmaker_work_queue_state_t queue_state;
static void esp_rmaker_handle_work_queue(void)
{
esp_rmaker_work_queue_entry_t work_queue_entry;
/* 2 sec delay to prevent spinning */
BaseType_t ret = xQueueReceive(work_queue, &work_queue_entry, 2000 / portTICK_PERIOD_MS);
while (ret == pdTRUE) {
work_queue_entry.work_fn(work_queue_entry.priv_data);
ret = xQueueReceive(work_queue, &work_queue_entry, 0);
}
}
static void esp_rmaker_work_queue_task(void *param)
{
ESP_LOGI(TAG, "RainMaker Work Queue task started.");
while (queue_state != WORK_QUEUE_STATE_STOP_REQUESTED) {
esp_rmaker_handle_work_queue();
}
ESP_LOGI(TAG, "Stopping Work Queue task");
queue_state = WORK_QUEUE_STATE_INIT_DONE;
vTaskDelete(NULL);
}
esp_err_t esp_rmaker_work_queue_add_task(esp_rmaker_work_fn_t work_fn, void *priv_data)
{
if (!work_queue) {
ESP_LOGE(TAG, "Cannot enqueue function as Work Queue hasn't been created.");
return ESP_ERR_INVALID_STATE;
}
esp_rmaker_work_queue_entry_t work_queue_entry = {
.work_fn = work_fn,
.priv_data = priv_data,
};
if (xQueueSend(work_queue, &work_queue_entry, 0) == pdTRUE) {
return ESP_OK;
}
return ESP_FAIL;
}
esp_err_t esp_rmaker_work_queue_init(void)
{
if (queue_state != WORK_QUEUE_STATE_DEINIT) {
ESP_LOGW(TAG, "Work Queue already initialiased/started.");
return ESP_OK;
}
work_queue = xQueueCreate(ESP_RMAKER_TASK_QUEUE_SIZE, sizeof(esp_rmaker_work_queue_entry_t));
if (!work_queue) {
ESP_LOGE(TAG, "Failed to create Work Queue.");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Work Queue created.");
queue_state = WORK_QUEUE_STATE_INIT_DONE;
return ESP_OK;
}
esp_err_t esp_rmaker_work_queue_deinit(void)
{
if (queue_state != WORK_QUEUE_STATE_STOP_REQUESTED) {
esp_rmaker_work_queue_stop();
}
while (queue_state == WORK_QUEUE_STATE_STOP_REQUESTED) {
ESP_LOGI(TAG, "Waiting for esp_rmaker_work_queue being stopped...");
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
if (queue_state == WORK_QUEUE_STATE_DEINIT) {
return ESP_OK;
} else if (queue_state != WORK_QUEUE_STATE_INIT_DONE) {
ESP_LOGE(TAG, "Cannot deinitialize Work Queue as the task is still running.");
return ESP_ERR_INVALID_STATE;
} else {
vQueueDelete(work_queue);
work_queue = NULL;
queue_state = WORK_QUEUE_STATE_DEINIT;
}
ESP_LOGI(TAG, "esp_rmaker_work_queue was successfully deinitialized");
return ESP_OK;
}
esp_err_t esp_rmaker_work_queue_start(void)
{
if (queue_state == WORK_QUEUE_STATE_RUNNING) {
ESP_LOGW(TAG, "Work Queue already started.");
return ESP_OK;
}
if (queue_state != WORK_QUEUE_STATE_INIT_DONE) {
ESP_LOGE(TAG, "Failed to start Work Queue as it wasn't initialized.");
return ESP_ERR_INVALID_STATE;
}
if (xTaskCreate(&esp_rmaker_work_queue_task, "rmaker_queue_task", ESP_RMAKER_TASK_STACK,
NULL, ESP_RMAKER_TASK_PRIORITY, NULL) != pdPASS) {
ESP_LOGE(TAG, "Couldn't create RainMaker work queue task");
return ESP_FAIL;
}
queue_state = WORK_QUEUE_STATE_RUNNING;
return ESP_OK;
}
esp_err_t esp_rmaker_work_queue_stop(void)
{
if (queue_state == WORK_QUEUE_STATE_RUNNING) {
queue_state = WORK_QUEUE_STATE_STOP_REQUESTED;
}
return ESP_OK;
}