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,10 @@
set(component_srcs "src/esp_schedule.c"
"src/esp_schedule_nvs.c")
idf_component_register(SRCS "${component_srcs}"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src"
PRIV_REQUIRES "rmaker_common"
REQUIRES "nvs_flash")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-function")

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,50 @@
# ESP Scheduling
[![Component Registry](https://components.espressif.com/components/espressif/esp_schedule/badge.svg)](https://components.espressif.com/components/espressif/esp_schedule)
This component is used internally by ESP RainMaker to implement schedules.
> Note: By default, the time is w.r.t. UTC. If the timezone has been set, then the time is w.r.t. the specified timezone.
## Test code:
```
#include <esp_schedule.h>
void app_schedule_trigger_cb(esp_schedule_handle_t handle, void *priv_data)
{
printf("priv_data: %.*s\n", (char *)priv_data);
}
static char *priv_data_global = "from app";
void app_schedule_set()
{
esp_schedule_config_t schedule_config = {
.name = "test",
.trigger.type = ESP_SCHEDULE_TYPE_DAYS_OF_WEEK,
.trigger.hours = 13,
.trigger.minutes = 30,
.trigger.day.repeat_days = ESP_SCHEDULE_DAY_MONDAY | ESP_SCHEDULE_DAY_THURSDAY,
.trigger_cb = app_schedule_trigger_cb,
.priv_data = priv_data_global,
};
esp_schedule_create(&schedule_config);
}
void app_schedule_init()
{
uint8_t schedule_count;
esp_schedule_handle_t *schedule_list = esp_schedule_init(true, NULL, &schedule_count);
if (schedule_count > 0 && schedule_list != NULL) {
ESP_LOGI(TAG, "Found %d schedule(s) in NVS.", schedule_count);
for (size_t i = 0; i < schedule_count; i++) {
esp_schedule_config_t schedule_config;
esp_schedule_get(schedule_list[i], &schedule_config);
schedule_config.trigger_cb = app_schedule_trigger_cb;
schedule_config.priv_data = priv_data_global;
esp_schedule_edit(schedule_list[i], &schedule_config);
}
}
}
```

View File

@@ -0,0 +1,5 @@
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := src
CFLAGS += -Wno-unused-function

View File

@@ -0,0 +1,9 @@
## IDF Component Manager Manifest File
version: "1.2.0"
description: ESP Schedules, used in RainMaker
url: https://github.com/espressif/esp-rainmaker/tree/master/components/esp_schedule
repository: https://github.com/espressif/esp-rainmaker.git
issues: https://github.com/espressif/esp-rainmaker/issues
dependencies:
espressif/rmaker_common:
version: "~1.4.2"

View File

@@ -0,0 +1,236 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <time.h>
/** Schedule Handle */
typedef void *esp_schedule_handle_t;
/** Maximum length of the schedule name allowed. This value cannot be more than 16 as it is used for NVS key. */
#define MAX_SCHEDULE_NAME_LEN 16
/** Callback for schedule trigger
*
* This callback is called when the schedule is triggered.
*
* @param[in] handle Schedule handle.
* @param[in] priv_data Pointer to the private data passed while creating/editing the schedule.
*/
typedef void (*esp_schedule_trigger_cb_t)(esp_schedule_handle_t handle, void *priv_data);
/** Callback for schedule timestamp
*
* This callback is called when the next trigger timestamp of the schedule is changed. This might be useful to check if
* one time schedules have already passed while the device was powered off.
*
* @param[in] handle Schedule handle.
* @param[in] next_timestamp timestamp at which the schedule will trigger next.
* @param[in] priv_data Pointer to the user data passed while creating/editing the schedule.
*/
typedef void (*esp_schedule_timestamp_cb_t)(esp_schedule_handle_t handle, uint32_t next_timestamp, void *priv_data);
/** Schedule type */
typedef enum esp_schedule_type {
ESP_SCHEDULE_TYPE_INVALID = 0,
ESP_SCHEDULE_TYPE_DAYS_OF_WEEK,
ESP_SCHEDULE_TYPE_DATE,
ESP_SCHEDULE_TYPE_RELATIVE,
} esp_schedule_type_t;
/** Schedule days. Used for ESP_SCHEDULE_TYPE_DAYS_OF_WEEK. */
typedef enum esp_schedule_days {
ESP_SCHEDULE_DAY_ONCE = 0,
ESP_SCHEDULE_DAY_EVERYDAY = 0b1111111,
ESP_SCHEDULE_DAY_MONDAY = 1 << 0,
ESP_SCHEDULE_DAY_TUESDAY = 1 << 1,
ESP_SCHEDULE_DAY_WEDNESDAY = 1 << 2,
ESP_SCHEDULE_DAY_THURSDAY = 1 << 3,
ESP_SCHEDULE_DAY_FRIDAY = 1 << 4,
ESP_SCHEDULE_DAY_SATURDAY = 1 << 5,
ESP_SCHEDULE_DAY_SUNDAY = 1 << 6,
} esp_schedule_days_t;
/** Schedule months. Used for ESP_SCHEDULE_TYPE_DATE. */
typedef enum esp_schedule_months {
ESP_SCHEDULE_MONTH_ONCE = 0,
ESP_SCHEDULE_MONTH_ALL = 0b1111111,
ESP_SCHEDULE_MONTH_JANUARY = 1 << 0,
ESP_SCHEDULE_MONTH_FEBRUARY = 1 << 1,
ESP_SCHEDULE_MONTH_MARCH = 1 << 2,
ESP_SCHEDULE_MONTH_APRIL = 1 << 3,
ESP_SCHEDULE_MONTH_MAY = 1 << 4,
ESP_SCHEDULE_MONTH_JUNE = 1 << 5,
ESP_SCHEDULE_MONTH_JULY = 1 << 6,
ESP_SCHEDULE_MONTH_AUGUST = 1 << 7,
ESP_SCHEDULE_MONTH_SEPTEMBER = 1 << 8,
ESP_SCHEDULE_MONTH_OCTOBER = 1 << 9,
ESP_SCHEDULE_MONTH_NOVEMBER = 1 << 10,
ESP_SCHEDULE_MONTH_DECEMBER = 1 << 11,
} esp_schedule_months_t;
/** Trigger details of the schedule */
typedef struct esp_schedule_trigger {
/** Type of schedule */
esp_schedule_type_t type;
/** Hours in 24 hour format. Accepted values: 0-23 */
uint8_t hours;
/** Minutes in the given hour. Accepted values: 0-59. */
uint8_t minutes;
/** For type ESP_SCHEDULE_TYPE_DAYS_OF_WEEK */
struct {
/** 'OR' list of esp_schedule_days_t */
uint8_t repeat_days;
} day;
/** For type ESP_SCHEDULE_TYPE_DATE */
struct {
/** Day of the month. Accepted values: 1-31. */
uint8_t day;
/* 'OR' list of esp_schedule_months_t */
uint16_t repeat_months;
/** Year */
uint16_t year;
/** If the schedule is to be repeated every year. */
bool repeat_every_year;
} date;
/** For type ESP_SCHEDULE_TYPE_SECONDS */
int relative_seconds;
/** Used for passing the next schedule timestamp for
* ESP_SCHEDULE_TYPE_RELATIVE */
time_t next_scheduled_time_utc;
} esp_schedule_trigger_t;
/** Schedule Validity
* Start and end time within which the schedule will be applicable.
*/
typedef struct esp_schedule_validity {
/* Start time as UTC timestamp */
time_t start_time;
/* End time as UTC timestamp */
time_t end_time;
} esp_schedule_validity_t;
/** Schedule config */
typedef struct esp_schedule_config {
/** Name of the schedule. This is like a primary key for the schedule. This is required. +1 for NULL termination. */
char name[MAX_SCHEDULE_NAME_LEN + 1];
/** Trigger details */
esp_schedule_trigger_t trigger;
/** Trigger callback */
esp_schedule_trigger_cb_t trigger_cb;
/** Timestamp callback */
esp_schedule_timestamp_cb_t timestamp_cb;
/** Private data associated with the schedule. This will be passed to callbacks. */
void *priv_data;
/** Validity of schedules. */
esp_schedule_validity_t validity;
} esp_schedule_config_t;
/** Initialize ESP Schedule
*
* This initializes ESP Schedule. This must be called first before calling any of the other APIs.
* This API also gets all the schedules from NVS (if it has been enabled).
*
* Note: After calling this API, the pointers to the callbacks should be updated for all the schedules by calling
* esp_schedule_get() followed by esp_schedule_edit() with the correct callbacks.
*
* @param[in] enable_nvs If NVS is to be enabled or not.
* @param[in] nvs_partition (Optional) The NVS partition to be used. If NULL is passed, the default partition is used.
* @param[out] schedule_count Number of active schedules found in NVS.
*
* @return Array of schedule handles if any schedules have been found.
* @return NULL if no schedule is found in NVS (or if NVS is not enabled).
*/
esp_schedule_handle_t *esp_schedule_init(bool enable_nvs, char *nvs_partition, uint8_t *schedule_count);
/** Create Schedule
*
* This API can be used to create a new schedule. The schedule still needs to be enabled using
* esp_schedule_enable().
*
* @param[in] schedule_config Configuration of the schedule to be created.
*
* @return Schedule handle if successfully created.
* @return NULL in case of error.
*/
esp_schedule_handle_t esp_schedule_create(esp_schedule_config_t *schedule_config);
/** Remove Schedule
*
* This API can be used to remove an existing schedule.
*
* @param[in] handle Schedule handle for the schedule to be removed.
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t esp_schedule_delete(esp_schedule_handle_t handle);
/** Edit Schedule
*
* This API can be used to edit an existing schedule.
* The schedule name should be same as when the schedule was created. The complete config must be provided
* or the previously stored config might be over-written.
*
* Note: If a schedule is edited when it is on-going, the new changes will not be reflected.
* You will need to disable the schedule, edit it, and then enable it again.
*
* @param[in] handle Schedule handle for the schedule to be edited.
* @param[in] schedule_config Configuration of the schedule to be edited.
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t esp_schedule_edit(esp_schedule_handle_t handle, esp_schedule_config_t *schedule_config);
/** Enable Schedule
*
* This API can be used to enable an existing schedule.
* It can be used to enable a schedule after it has been created using esp_schedule_create()
* or if the schedule has been disabled using esp_schedule_disable().
*
* @param[in] handle Schedule handle for the schedule to be enabled.
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t esp_schedule_enable(esp_schedule_handle_t handle);
/** Disable Schedule
*
* This API can be used to disable an on-going schedule.
* It does not remove the schedule, just stops it. The schedule can be enabled again using
* esp_schedule_enable().
*
* @param[in] handle Schedule handle for the schedule to be disabled.
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t esp_schedule_disable(esp_schedule_handle_t handle);
/** Get Schedule
*
* This API can be used to get details of an existing schedule.
* The schedule_config is populated with the schedule details.
*
* @param[in] handle Schedule handle.
* @param[out] schedule_config Details of the schedule whose handle is passed.
*
* @return ESP_OK on success.
* @return error in case of failure.
*/
esp_err_t esp_schedule_get(esp_schedule_handle_t handle, esp_schedule_config_t *schedule_config);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,600 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <inttypes.h>
#include <esp_log.h>
#include <esp_sntp.h>
#include <esp_rmaker_utils.h>
#include "esp_schedule_internal.h"
static const char *TAG = "esp_schedule";
#define SECONDS_TILL_2020 ((2020 - 1970) * 365 * 24 * 3600)
#define SECONDS_IN_DAY (60 * 60 * 24)
static bool init_done = false;
static int esp_schedule_get_no_of_days(esp_schedule_trigger_t *trigger, struct tm *current_time, struct tm *schedule_time)
{
/* for day, monday = 0, sunday = 6. */
int next_day = 0;
/* struct tm has tm_wday with sunday as 0. Whereas we have monday as 0. Converting struct tm to our format */
int today = ((current_time->tm_wday + 7 - 1) % 7);
esp_schedule_days_t today_bit = 1 << today;
uint8_t repeat_days = trigger->day.repeat_days;
int current_seconds = (current_time->tm_hour * 60 + current_time->tm_min) * 60 + current_time->tm_sec;
int schedule_seconds = (schedule_time->tm_hour * 60 + schedule_time->tm_min) * 60;
/* Handling for one time schedule */
if (repeat_days == ESP_SCHEDULE_DAY_ONCE) {
if (schedule_seconds > current_seconds) {
/* The schedule is today and is yet to go off */
return 0;
} else {
/* The schedule is tomorrow */
return 1;
}
}
/* Handling for repeating schedules */
/* Check if it is today */
if ((repeat_days & today_bit)) {
if (schedule_seconds > current_seconds) {
/* The schedule is today and is yet to go off. */
return 0;
}
}
/* Check if it is this week or next week */
if ((repeat_days & (today_bit ^ 0xFF)) > today_bit) {
/* Next schedule is yet to come in this week */
next_day = ffs(repeat_days & (0xFF << (today + 1))) - 1;
return (next_day - today);
} else {
/* First scheduled day of the next week */
next_day = ffs(repeat_days) - 1;
if (next_day == today) {
/* Same day, next week */
return 7;
}
return (7 - today + next_day);
}
ESP_LOGE(TAG, "No of days could not be found. This should not happen.");
return 0;
}
static uint8_t esp_schedule_get_next_month(esp_schedule_trigger_t *trigger, struct tm *current_time, struct tm *schedule_time)
{
int current_seconds = (current_time->tm_hour * 60 + current_time->tm_min) * 60 + current_time->tm_sec;
int schedule_seconds = (schedule_time->tm_hour * 60 + schedule_time->tm_min) * 60;
/* +1 is because struct tm has months starting from 0, whereas we have them starting from 1 */
uint8_t current_month = current_time->tm_mon + 1;
/* -1 because month_bit starts from 0b1. So for January, it should be 1 << 0. And current_month starts from 1. */
uint16_t current_month_bit = 1 << (current_month - 1);
uint8_t next_schedule_month = 0;
uint16_t repeat_months = trigger->date.repeat_months;
/* Check if month is not specified */
if (repeat_months == ESP_SCHEDULE_MONTH_ONCE) {
if (trigger->date.day == current_time->tm_mday) {
/* The schedule day is same. Check if time has already passed */
if (schedule_seconds > current_seconds) {
/* The schedule is today and is yet to go off */
return current_month;
} else {
/* Today's time has passed */
return (current_month + 1);
}
} else if (trigger->date.day > current_time->tm_mday) {
/* The day is yet to come in this month */
return current_month;
} else {
/* The day has passed in the current month */
return (current_month + 1);
}
}
/* Check if schedule is not this year itself, it is in future. */
if (trigger->date.year > (current_time->tm_year + 1900)) {
/* Find first schedule month of next year */
next_schedule_month = ffs(repeat_months);
/* Year will be handled by the caller. So no need to add any additional months */
return next_schedule_month;
}
/* Check if schedule is this month and is yet to come */
if (current_month_bit & repeat_months) {
if (trigger->date.day == current_time->tm_mday) {
/* The schedule day is same. Check if time has already passed */
if (schedule_seconds > current_seconds) {
/* The schedule is today and is yet to go off */
return current_month;
}
}
if (trigger->date.day > current_time->tm_mday) {
/* The day is yet to come in this month */
return current_month;
}
}
/* Check if schedule is this year */
if ((repeat_months & (current_month_bit ^ 0xFFFF)) > current_month_bit) {
/* Next schedule month is yet to come in this year */
next_schedule_month = ffs(repeat_months & (0xFFFF << (current_month)));
return next_schedule_month;
}
/* Check if schedule is for this year and does not repeat */
if (!trigger->date.repeat_every_year) {
if (trigger->date.year <= (current_time->tm_year + 1900)) {
ESP_LOGE(TAG, "Schedule does not repeat next year, but get_next_month has been called.");
return 0;
}
}
/* Schedule is not this year */
/* Find first schedule month of next year */
next_schedule_month = ffs(repeat_months);
/* +12 because the schedule is next year */
return (next_schedule_month + 12);
}
static uint16_t esp_schedule_get_next_year(esp_schedule_trigger_t *trigger, struct tm *current_time, struct tm *schedule_time)
{
uint16_t current_year = current_time->tm_year + 1900;
uint16_t schedule_year = trigger->date.year;
if (schedule_year > current_year) {
return schedule_year;
}
/* If the schedule is set to repeat_every_year, we return the current year */
/* If the schedule has already passed in this year, we still return current year, as the additional months will be handled in get_next_month */
return current_year;
}
static uint32_t esp_schedule_get_next_schedule_time_diff(const char *schedule_name, esp_schedule_trigger_t *trigger)
{
struct tm current_time, schedule_time;
time_t now;
char time_str[64];
int32_t time_diff;
/* Get current time */
time(&now);
/* Handling ESP_SCHEDULE_TYPE_RELATIVE first since it doesn't require any
* computation based on days, hours, minutes, etc.
*/
if (trigger->type == ESP_SCHEDULE_TYPE_RELATIVE) {
/* If next scheduled time is already set, just compute the difference
* between current time and next scheduled time and return that diff.
*/
time_t target;
if (trigger->next_scheduled_time_utc > 0) {
target = (time_t)trigger->next_scheduled_time_utc;
time_diff = difftime(target, now);
} else {
target = now + (time_t)trigger->relative_seconds;
time_diff = trigger->relative_seconds;
}
localtime_r(&target, &schedule_time);
trigger->next_scheduled_time_utc = mktime(&schedule_time);
/* Print schedule time */
memset(time_str, 0, sizeof(time_str));
strftime(time_str, sizeof(time_str), "%c %z[%Z]", &schedule_time);
ESP_LOGI(TAG, "Schedule %s will be active on: %s. DST: %s", schedule_name, time_str, schedule_time.tm_isdst ? "Yes" : "No");
return time_diff;
}
localtime_r(&now, &current_time);
/* Get schedule time */
localtime_r(&now, &schedule_time);
schedule_time.tm_sec = 0;
schedule_time.tm_min = trigger->minutes;
schedule_time.tm_hour = trigger->hours;
mktime(&schedule_time);
/* Adjust schedule day */
if (trigger->type == ESP_SCHEDULE_TYPE_DAYS_OF_WEEK) {
int no_of_days = 0;
no_of_days = esp_schedule_get_no_of_days(trigger, &current_time, &schedule_time);
schedule_time.tm_sec += no_of_days * SECONDS_IN_DAY;
}
if (trigger->type == ESP_SCHEDULE_TYPE_DATE) {
schedule_time.tm_mday = trigger->date.day;
schedule_time.tm_mon = esp_schedule_get_next_month(trigger, &current_time, &schedule_time) - 1;
schedule_time.tm_year = esp_schedule_get_next_year(trigger, &current_time, &schedule_time) - 1900;
if (schedule_time.tm_mon < 0) {
ESP_LOGE(TAG, "Invalid month found: %d. Setting it to next month.", schedule_time.tm_mon);
schedule_time.tm_mon = current_time.tm_mon + 1;
}
if (schedule_time.tm_mon >= 12) {
schedule_time.tm_year += schedule_time.tm_mon / 12;
schedule_time.tm_mon = schedule_time.tm_mon % 12;
}
}
mktime(&schedule_time);
/* Adjust time according to DST */
time_t dst_adjust = 0;
if (!current_time.tm_isdst && schedule_time.tm_isdst) {
dst_adjust = -3600;
} else if (current_time.tm_isdst && !schedule_time.tm_isdst ) {
dst_adjust = 3600;
}
ESP_LOGD(TAG, "DST adjust seconds: %lld", (long long) dst_adjust);
schedule_time.tm_sec += dst_adjust;
mktime(&schedule_time);
/* Print schedule time */
memset(time_str, 0, sizeof(time_str));
strftime(time_str, sizeof(time_str), "%c %z[%Z]", &schedule_time);
ESP_LOGI(TAG, "Schedule %s will be active on: %s. DST: %s", schedule_name, time_str, schedule_time.tm_isdst ? "Yes" : "No");
/* Calculate difference */
time_diff = difftime((mktime(&schedule_time)), mktime(&current_time));
/* For one time schedules to check for expiry after a reboot. If NVS is enabled, this should be stored in NVS. */
trigger->next_scheduled_time_utc = mktime(&schedule_time);
return time_diff;
}
static bool esp_schedule_is_expired(esp_schedule_trigger_t *trigger)
{
time_t current_timestamp = 0;
struct tm current_time = {0};
time(&current_timestamp);
localtime_r(&current_timestamp, &current_time);
if (trigger->type == ESP_SCHEDULE_TYPE_RELATIVE) {
if (trigger->next_scheduled_time_utc > 0 && trigger->next_scheduled_time_utc <= current_timestamp) {
/* Relative seconds based schedule has expired */
return true;
} else if (trigger->next_scheduled_time_utc == 0) {
/* Schedule has been disabled , so it is as good as expired. */
return true;
}
} else if (trigger->type == ESP_SCHEDULE_TYPE_DAYS_OF_WEEK) {
if (trigger->day.repeat_days == ESP_SCHEDULE_DAY_ONCE) {
if (trigger->next_scheduled_time_utc > 0 && trigger->next_scheduled_time_utc <= current_timestamp) {
/* One time schedule has expired */
return true;
} else if (trigger->next_scheduled_time_utc == 0) {
/* Schedule has been disabled , so it is as good as expired. */
return true;
}
}
} else if (trigger->type == ESP_SCHEDULE_TYPE_DATE) {
if (trigger->date.repeat_months == 0) {
if (trigger->next_scheduled_time_utc > 0 && trigger->next_scheduled_time_utc <= current_timestamp) {
/* One time schedule has expired */
return true;
} else {
return false;
}
}
if (trigger->date.repeat_every_year == true) {
return false;
}
struct tm schedule_time = {0};
localtime_r(&current_timestamp, &schedule_time);
schedule_time.tm_sec = 0;
schedule_time.tm_min = trigger->minutes;
schedule_time.tm_hour = trigger->hours;
schedule_time.tm_mday = trigger->date.day;
/* For expiry, just check the last month of the repeat_months. */
/* '-1' because struct tm has months starting from 0 and we have months starting from 1. */
schedule_time.tm_mon = fls(trigger->date.repeat_months) - 1;
/* '-1900' because struct tm has number of years after 1900 */
schedule_time.tm_year = trigger->date.year - 1900;
time_t schedule_timestamp = mktime(&schedule_time);
if (schedule_timestamp < current_timestamp) {
return true;
}
} else {
/* Invalid type. Mark as expired */
return true;
}
return false;
}
static void esp_schedule_stop_timer(esp_schedule_t *schedule)
{
xTimerStop(schedule->timer, portMAX_DELAY);
}
static void esp_schedule_start_timer(esp_schedule_t *schedule)
{
time_t current_time = 0;
time(&current_time);
if (current_time < SECONDS_TILL_2020) {
ESP_LOGE(TAG, "Time is not updated");
return;
}
schedule->next_scheduled_time_diff = esp_schedule_get_next_schedule_time_diff(schedule->name, &schedule->trigger);
ESP_LOGI(TAG, "Starting a timer for %"PRIu32" seconds for schedule %s", schedule->next_scheduled_time_diff, schedule->name);
if (schedule->timestamp_cb) {
schedule->timestamp_cb((esp_schedule_handle_t)schedule, schedule->trigger.next_scheduled_time_utc, schedule->priv_data);
}
xTimerStop(schedule->timer, portMAX_DELAY);
xTimerChangePeriod(schedule->timer, (schedule->next_scheduled_time_diff * 1000) / portTICK_PERIOD_MS, portMAX_DELAY);
}
static void esp_schedule_common_timer_cb(TimerHandle_t timer)
{
void *priv_data = pvTimerGetTimerID(timer);
if (priv_data == NULL) {
return;
}
esp_schedule_t *schedule = (esp_schedule_t *)priv_data;
time_t now;
time(&now);
struct tm validity_time;
char time_str[64] = {0};
if (schedule->validity.start_time != 0) {
if (now < schedule->validity.start_time) {
memset(time_str, 0, sizeof(time_str));
localtime_r(&schedule->validity.start_time, &validity_time);
strftime(time_str, sizeof(time_str), "%c %z[%Z]", &validity_time);
ESP_LOGW(TAG, "Schedule %s skipped. It will be active only after: %s. DST: %s.", schedule->name, time_str, validity_time.tm_isdst ? "Yes" : "No");
/* TODO: Start the timer such that the next time it triggeres, it will be within the valid window.
* Currently, it will just keep triggering and then get skipped if not in valid range.
*/
goto restart_schedule;
}
}
if (schedule->validity.end_time != 0) {
if (now > schedule->validity.end_time) {
localtime_r(&schedule->validity.end_time, &validity_time);
strftime(time_str, sizeof(time_str), "%c %z[%Z]", &validity_time);
ESP_LOGW(TAG, "Schedule %s skipped. It can't be active after: %s. DST: %s.", schedule->name, time_str, validity_time.tm_isdst ? "Yes" : "No");
/* Return from here will ensure that the timer does not start again for this schedule */
return;
}
}
ESP_LOGI(TAG, "Schedule %s triggered", schedule->name);
if (schedule->trigger_cb) {
schedule->trigger_cb((esp_schedule_handle_t)schedule, schedule->priv_data);
}
restart_schedule:
if (esp_schedule_is_expired(&schedule->trigger)) {
/* Not deleting the schedule here. Just not starting it again. */
return;
}
esp_schedule_start_timer(schedule);
}
static void esp_schedule_delete_timer(esp_schedule_t *schedule)
{
xTimerDelete(schedule->timer, portMAX_DELAY);
}
static void esp_schedule_create_timer(esp_schedule_t *schedule)
{
if (esp_schedule_nvs_is_enabled()) {
/* This is just used for calculating next_scheduled_time_utc for ESP_SCHEDULE_DAY_ONCE (in case of ESP_SCHEDULE_TYPE_DAYS_OF_WEEK) or for ESP_SCHEDULE_MONTH_ONCE (in case of ESP_SCHEDULE_TYPE_DATE), and only used when NVS is enabled. And if NVS is enabled, time will already be synced and the time will be correctly calculated. */
schedule->next_scheduled_time_diff = esp_schedule_get_next_schedule_time_diff(schedule->name, &schedule->trigger);
}
/* Temporarily setting the timer for 1 (anything greater than 0) tick. This will get changed when xTimerChangePeriod() is called. */
schedule->timer = xTimerCreate("schedule", 1, pdFALSE, (void *)schedule, esp_schedule_common_timer_cb);
}
esp_err_t esp_schedule_get(esp_schedule_handle_t handle, esp_schedule_config_t *schedule_config)
{
if (schedule_config == NULL) {
return ESP_ERR_INVALID_ARG;
}
if (handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_schedule_t *schedule = (esp_schedule_t *)handle;
strcpy(schedule_config->name, schedule->name);
schedule_config->trigger.type = schedule->trigger.type;
schedule_config->trigger.hours = schedule->trigger.hours;
schedule_config->trigger.minutes = schedule->trigger.minutes;
if (schedule->trigger.type == ESP_SCHEDULE_TYPE_DAYS_OF_WEEK) {
schedule_config->trigger.day.repeat_days = schedule->trigger.day.repeat_days;
} else if (schedule->trigger.type == ESP_SCHEDULE_TYPE_DATE) {
schedule_config->trigger.date.day = schedule->trigger.date.day;
schedule_config->trigger.date.repeat_months = schedule->trigger.date.repeat_months;
schedule_config->trigger.date.year = schedule->trigger.date.year;
schedule_config->trigger.date.repeat_every_year = schedule->trigger.date.repeat_every_year;
}
schedule_config->trigger_cb = schedule->trigger_cb;
schedule_config->timestamp_cb = schedule->timestamp_cb;
schedule_config->priv_data = schedule->priv_data;
schedule_config->validity = schedule->validity;
return ESP_OK;
}
esp_err_t esp_schedule_enable(esp_schedule_handle_t handle)
{
if (handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_schedule_t *schedule = (esp_schedule_t *)handle;
esp_schedule_start_timer(schedule);
return ESP_OK;
}
esp_err_t esp_schedule_disable(esp_schedule_handle_t handle)
{
if (handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_schedule_t *schedule = (esp_schedule_t *)handle;
esp_schedule_stop_timer(schedule);
/* Disabling a schedule should also reset the next_scheduled_time.
* It would be re-computed after enabling.
*/
schedule->trigger.next_scheduled_time_utc = 0;
return ESP_OK;
}
static esp_err_t esp_schedule_set(esp_schedule_t *schedule, esp_schedule_config_t *schedule_config)
{
/* Setting everything apart from name. */
schedule->trigger.type = schedule_config->trigger.type;
if (schedule->trigger.type == ESP_SCHEDULE_TYPE_RELATIVE) {
schedule->trigger.relative_seconds = schedule_config->trigger.relative_seconds;
schedule->trigger.next_scheduled_time_utc = schedule_config->trigger.next_scheduled_time_utc;
} else {
schedule->trigger.hours = schedule_config->trigger.hours;
schedule->trigger.minutes = schedule_config->trigger.minutes;
if (schedule->trigger.type == ESP_SCHEDULE_TYPE_DAYS_OF_WEEK) {
schedule->trigger.day.repeat_days = schedule_config->trigger.day.repeat_days;
} else if (schedule->trigger.type == ESP_SCHEDULE_TYPE_DATE) {
schedule->trigger.date.day = schedule_config->trigger.date.day;
schedule->trigger.date.repeat_months = schedule_config->trigger.date.repeat_months;
schedule->trigger.date.year = schedule_config->trigger.date.year;
schedule->trigger.date.repeat_every_year = schedule_config->trigger.date.repeat_every_year;
}
}
schedule->trigger_cb = schedule_config->trigger_cb;
schedule->timestamp_cb = schedule_config->timestamp_cb;
schedule->priv_data = schedule_config->priv_data;
schedule->validity = schedule_config->validity;
esp_schedule_nvs_add(schedule);
return ESP_OK;
}
esp_err_t esp_schedule_edit(esp_schedule_handle_t handle, esp_schedule_config_t *schedule_config)
{
if (handle == NULL || schedule_config == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_schedule_t *schedule = (esp_schedule_t *)handle;
if (strncmp(schedule->name, schedule_config->name, sizeof(schedule->name)) != 0) {
ESP_LOGE(TAG, "Schedule name mismatch. Expected: %s, Passed: %s", schedule->name, schedule_config->name);
return ESP_FAIL;
}
/* Editing a schedule with relative time should also reset it. */
if (schedule->trigger.type == ESP_SCHEDULE_TYPE_RELATIVE) {
schedule->trigger.next_scheduled_time_utc = 0;
}
esp_schedule_set(schedule, schedule_config);
ESP_LOGD(TAG, "Schedule %s edited", schedule->name);
return ESP_OK;
}
esp_err_t esp_schedule_delete(esp_schedule_handle_t handle)
{
if (handle == NULL) {
return ESP_ERR_INVALID_ARG;
}
esp_schedule_t *schedule = (esp_schedule_t *)handle;
ESP_LOGI(TAG, "Deleting schedule %s", schedule->name);
if (schedule->timer) {
esp_schedule_stop_timer(schedule);
esp_schedule_delete_timer(schedule);
}
esp_schedule_nvs_remove(schedule);
free(schedule);
return ESP_OK;
}
esp_schedule_handle_t esp_schedule_create(esp_schedule_config_t *schedule_config)
{
if (schedule_config == NULL) {
return NULL;
}
if (strlen(schedule_config->name) <= 0) {
ESP_LOGE(TAG, "Set schedule failed. Please enter a unique valid name for the schedule.");
return NULL;
}
if (schedule_config->trigger.type == ESP_SCHEDULE_TYPE_INVALID) {
ESP_LOGE(TAG, "Schedule type is invalid.");
return NULL;
}
esp_schedule_t *schedule = (esp_schedule_t *)MEM_CALLOC_EXTRAM(1, sizeof(esp_schedule_t));
if (schedule == NULL) {
ESP_LOGE(TAG, "Could not allocate handle");
return NULL;
}
strlcpy(schedule->name, schedule_config->name, sizeof(schedule->name));
esp_schedule_set(schedule, schedule_config);
esp_schedule_create_timer(schedule);
ESP_LOGD(TAG, "Schedule %s created", schedule->name);
return (esp_schedule_handle_t)schedule;
}
esp_schedule_handle_t *esp_schedule_init(bool enable_nvs, char *nvs_partition, uint8_t *schedule_count)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 0)
if (!esp_sntp_enabled()) {
ESP_LOGI(TAG, "Initializing SNTP");
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_init();
}
#else
if (!sntp_enabled()) {
ESP_LOGI(TAG, "Initializing SNTP");
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, "pool.ntp.org");
sntp_init();
}
#endif
if (!enable_nvs) {
return NULL;
}
/* Wait for time to be updated here */
/* Below this is initialising schedules from NVS */
esp_schedule_nvs_init(nvs_partition);
/* Get handle list from NVS */
esp_schedule_handle_t *handle_list = NULL;
*schedule_count = 0;
handle_list = esp_schedule_nvs_get_all(schedule_count);
if (handle_list == NULL) {
ESP_LOGI(TAG, "No schedules found in NVS");
return NULL;
}
ESP_LOGI(TAG, "Schedules found in NVS: %"PRIu8, *schedule_count);
/* Start/Delete the schedules */
esp_schedule_t *schedule = NULL;
for (size_t handle_count = 0; handle_count < *schedule_count; handle_count++) {
schedule = (esp_schedule_t *)handle_list[handle_count];
schedule->trigger_cb = NULL;
schedule->timer = NULL;
/* Check for ONCE and expired schedules and delete them. */
if (esp_schedule_is_expired(&schedule->trigger)) {
/* This schedule has already expired. */
ESP_LOGI(TAG, "Schedule %s does not repeat and has already expired. Deleting it.", schedule->name);
esp_schedule_delete((esp_schedule_handle_t)schedule);
/* Removing the schedule from the list */
handle_list[handle_count] = handle_list[*schedule_count - 1];
(*schedule_count)--;
handle_count--;
continue;
}
esp_schedule_create_timer(schedule);
esp_schedule_start_timer(schedule);
}
init_done = true;
return handle_list;
}

View File

@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <freertos/FreeRTOS.h>
#include <freertos/timers.h>
#include <esp_schedule.h>
typedef struct esp_schedule {
char name[MAX_SCHEDULE_NAME_LEN + 1];
esp_schedule_trigger_t trigger;
uint32_t next_scheduled_time_diff;
TimerHandle_t timer;
esp_schedule_trigger_cb_t trigger_cb;
esp_schedule_timestamp_cb_t timestamp_cb;
void *priv_data;
esp_schedule_validity_t validity;
} esp_schedule_t;
esp_err_t esp_schedule_nvs_add(esp_schedule_t *schedule);
esp_err_t esp_schedule_nvs_remove(esp_schedule_t *schedule);
esp_schedule_handle_t *esp_schedule_nvs_get_all(uint8_t *schedule_count);
bool esp_schedule_nvs_is_enabled(void);
esp_err_t esp_schedule_nvs_init(char *nvs_partition);

View File

@@ -0,0 +1,296 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include <time.h>
#include <esp_log.h>
#include <nvs.h>
#include "esp_schedule_internal.h"
static const char *TAG = "esp_schedule_nvs";
#define ESP_SCHEDULE_NVS_NAMESPACE "schd"
#define ESP_SCHEDULE_COUNT_KEY "schd_count"
static char *esp_schedule_nvs_partition = NULL;
static bool nvs_enabled = false;
esp_err_t esp_schedule_nvs_add(esp_schedule_t *schedule)
{
if (!nvs_enabled) {
ESP_LOGD(TAG, "NVS not enabled. Not adding to NVS.");
return ESP_ERR_INVALID_STATE;
}
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open_from_partition(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS open failed with error %d", err);
return err;
}
/* Check if this is new schedule or editing an existing schedule */
size_t buf_size;
bool editing_schedule = true;
err = nvs_get_blob(nvs_handle, schedule->name, NULL, &buf_size);
if (err != ESP_OK) {
if (err == ESP_ERR_NVS_NOT_FOUND) {
editing_schedule = false;
} else {
ESP_LOGE(TAG, "NVS get failed with error %d", err);
nvs_close(nvs_handle);
return err;
}
} else {
ESP_LOGI(TAG, "Updating the existing schedule %s", schedule->name);
}
err = nvs_set_blob(nvs_handle, schedule->name, schedule, sizeof(esp_schedule_t));
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS set failed with error %d", err);
nvs_close(nvs_handle);
return err;
}
if (editing_schedule == false) {
uint8_t schedule_count;
err = nvs_get_u8(nvs_handle, ESP_SCHEDULE_COUNT_KEY, &schedule_count);
if (err != ESP_OK) {
if (err == ESP_ERR_NVS_NOT_FOUND) {
schedule_count = 0;
} else {
ESP_LOGE(TAG, "NVS get failed with error %d", err);
nvs_close(nvs_handle);
return err;
}
}
schedule_count++;
err = nvs_set_u8(nvs_handle, ESP_SCHEDULE_COUNT_KEY, schedule_count);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS set failed for schedule count with error %d", err);
nvs_close(nvs_handle);
return err;
}
}
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
ESP_LOGI(TAG, "Schedule %s added in NVS", schedule->name);
return ESP_OK;
}
esp_err_t esp_schedule_nvs_remove_all(void)
{
if (!nvs_enabled) {
ESP_LOGD(TAG, "NVS not enabled. Not removing from NVS.");
return ESP_ERR_INVALID_STATE;
}
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open_from_partition(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS open failed with error %d", err);
return err;
}
err = nvs_erase_all(nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS erase all keys failed with error %d", err);
nvs_close(nvs_handle);
return err;
}
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
ESP_LOGI(TAG, "All schedules removed from NVS");
return ESP_OK;
}
esp_err_t esp_schedule_nvs_remove(esp_schedule_t *schedule)
{
if (!nvs_enabled) {
ESP_LOGD(TAG, "NVS not enabled. Not removing from NVS.");
return ESP_ERR_INVALID_STATE;
}
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open_from_partition(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS open failed with error %d", err);
return err;
}
err = nvs_erase_key(nvs_handle, schedule->name);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS erase key failed with error %d", err);
nvs_close(nvs_handle);
return err;
}
uint8_t schedule_count;
err = nvs_get_u8(nvs_handle, ESP_SCHEDULE_COUNT_KEY, &schedule_count);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS get failed for schedule count with error %d", err);
nvs_close(nvs_handle);
return err;
}
schedule_count--;
err = nvs_set_u8(nvs_handle, ESP_SCHEDULE_COUNT_KEY, schedule_count);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS set failed for schedule count with error %d", err);
nvs_close(nvs_handle);
return err;
}
nvs_commit(nvs_handle);
nvs_close(nvs_handle);
ESP_LOGI(TAG, "Schedule %s removed from NVS", schedule->name);
return ESP_OK;
}
static uint8_t esp_schedule_nvs_get_count(void)
{
if (!nvs_enabled) {
ESP_LOGD(TAG, "NVS not enabled. Not getting count from NVS.");
return 0;
}
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open_from_partition(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS open failed with error %d", err);
return 0;
}
uint8_t schedule_count;
err = nvs_get_u8(nvs_handle, ESP_SCHEDULE_COUNT_KEY, &schedule_count);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS get failed for schedule count with error %d", err);
nvs_close(nvs_handle);
return 0;
}
nvs_close(nvs_handle);
ESP_LOGI(TAG, "Schedules in NVS: %d", schedule_count);
return schedule_count;
}
static esp_schedule_handle_t esp_schedule_nvs_get(char *nvs_key)
{
if (!nvs_enabled) {
ESP_LOGD(TAG, "NVS not enabled. Not getting from NVS.");
return NULL;
}
size_t buf_size;
nvs_handle_t nvs_handle;
esp_err_t err = nvs_open_from_partition(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_READONLY, &nvs_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS open failed with error %d", err);
return NULL;
}
err = nvs_get_blob(nvs_handle, nvs_key, NULL, &buf_size);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS get failed with error %d", err);
nvs_close(nvs_handle);
return NULL;
}
esp_schedule_t *schedule = (esp_schedule_t *)malloc(buf_size);
if (schedule == NULL) {
ESP_LOGE(TAG, "Could not allocate handle");
nvs_close(nvs_handle);
return NULL;
}
err = nvs_get_blob(nvs_handle, nvs_key, schedule, &buf_size);
if (err != ESP_OK) {
ESP_LOGE(TAG, "NVS get failed with error %d", err);
nvs_close(nvs_handle);
free(schedule);
return NULL;
}
nvs_close(nvs_handle);
ESP_LOGI(TAG, "Schedule %s found in NVS", schedule->name);
return (esp_schedule_handle_t) schedule;
}
esp_schedule_handle_t *esp_schedule_nvs_get_all(uint8_t *schedule_count)
{
if (!nvs_enabled) {
ESP_LOGD(TAG, "NVS not enabled. Not Initialising NVS.");
return NULL;
}
*schedule_count = esp_schedule_nvs_get_count();
if (*schedule_count == 0) {
ESP_LOGI(TAG, "No Entries found in NVS");
return NULL;
}
esp_schedule_handle_t *handle_list = (esp_schedule_handle_t *)malloc(sizeof(esp_schedule_handle_t) * (*schedule_count));
if (handle_list == NULL) {
ESP_LOGE(TAG, "Could not allocate schedule list");
*schedule_count = 0;
return NULL;
}
int handle_count = 0;
nvs_entry_info_t nvs_entry;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
nvs_iterator_t nvs_iterator = NULL;
esp_err_t err = nvs_entry_find(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_TYPE_BLOB, &nvs_iterator);
if (err != ESP_OK) {
ESP_LOGE(TAG, "No entry found in NVS");
return NULL;;
}
while (err == ESP_OK) {
nvs_entry_info(nvs_iterator, &nvs_entry);
ESP_LOGI(TAG, "Found schedule in NVS with key: %s", nvs_entry.key);
handle_list[handle_count] = esp_schedule_nvs_get(nvs_entry.key);
if (handle_list[handle_count] != NULL) {
/* Increase count only if nvs_get was successful */
handle_count++;
}
err = nvs_entry_next(&nvs_iterator);
}
nvs_release_iterator(nvs_iterator);
#else
nvs_iterator_t nvs_iterator = nvs_entry_find(esp_schedule_nvs_partition, ESP_SCHEDULE_NVS_NAMESPACE, NVS_TYPE_BLOB);
if (nvs_iterator == NULL) {
ESP_LOGE(TAG, "No entry found in NVS");
return NULL;;
}
while (nvs_iterator != NULL) {
nvs_entry_info(nvs_iterator, &nvs_entry);
ESP_LOGI(TAG, "Found schedule in NVS with key: %s", nvs_entry.key);
handle_list[handle_count] = esp_schedule_nvs_get(nvs_entry.key);
if (handle_list[handle_count] != NULL) {
/* Increase count only if nvs_get was successful */
handle_count++;
}
nvs_iterator = nvs_entry_next(nvs_iterator);
}
#endif
*schedule_count = handle_count;
ESP_LOGI(TAG, "Found %d schedules in NVS", *schedule_count);
return handle_list;
}
bool esp_schedule_nvs_is_enabled(void)
{
return nvs_enabled;
}
esp_err_t esp_schedule_nvs_init(char *nvs_partition)
{
if (nvs_enabled) {
ESP_LOGI(TAG, "NVS already enabled");
return ESP_OK;
}
if (nvs_partition) {
esp_schedule_nvs_partition = strndup(nvs_partition, strlen(nvs_partition));
} else {
esp_schedule_nvs_partition = strndup("nvs", strlen("nvs"));
}
if (esp_schedule_nvs_partition == NULL) {
ESP_LOGE(TAG, "Could not allocate nvs_partition");
return ESP_ERR_NO_MEM;
}
nvs_enabled = true;
return ESP_OK;
}