Skip to content

Commit ad0666e

Browse files
committed
feat: esp_schedule v2.0.0
1 parent 75da889 commit ad0666e

30 files changed

+3085
-1112
lines changed

.build-test-rules.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ esp_schedule/examples/get_started:
3939
- if: ((IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR >= 1) or (IDF_VERSION_MAJOR > 5)) and SOC_WIFI_SUPPORTED == 1
4040
reason: Network provisioning component has dependencies IDF >= 5.1; example only supports Wi-Fi enabled targets
4141

42+
esp_schedule/test_app:
43+
disable:
44+
- if: IDF_VERSION_MAJOR < 5
45+
reason: Example relies on WHOLE_ARCHIVE component property which was introduced in IDF v5.0
46+
4247
catch2/examples/catch2-test:
4348
enable:
4449
- if: INCLUDE_DEFAULT == 1 or IDF_TARGET == "linux"

esp_schedule/CMakeLists.txt

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,22 @@
1-
set(component_srcs "src/esp_schedule.c"
2-
"src/esp_schedule_nvs.c")
1+
include(${CMAKE_CURRENT_LIST_DIR}/esp_schedule_variables.cmake)
32

4-
idf_component_register(SRCS "${component_srcs}"
5-
INCLUDE_DIRS "include"
6-
PRIV_INCLUDE_DIRS "src"
7-
PRIV_REQUIRES nvs_flash esp_netif)
3+
# Include ESP-IDF specific glue sources and include directories
4+
list(APPEND ESP_SCHEDULE_SRCS "glue/esp/time.c"
5+
"glue/esp/timer.c")
6+
list(APPEND ESP_SCHEDULE_INCLUDE_DIRS "include/esp")
7+
list(APPEND ESP_SCHEDULE_PRIV_INCLUDE_DIRS "glue/esp")
8+
list(APPEND ESP_SCHEDULE_PRIV_REQUIRES "esp_netif")
9+
10+
# Conditionally include NVS sources and dependencies
11+
if(CONFIG_ESP_SCHEDULE_ENABLE_NVS)
12+
list(APPEND ESP_SCHEDULE_SRCS "src/esp_schedule_nvs.c" "glue/esp/nvs.c")
13+
endif()
14+
15+
idf_component_register(SRCS ${ESP_SCHEDULE_SRCS}
16+
INCLUDE_DIRS ${ESP_SCHEDULE_INCLUDE_DIRS}
17+
PRIV_INCLUDE_DIRS ${ESP_SCHEDULE_PRIV_INCLUDE_DIRS}
18+
PRIV_REQUIRES ${ESP_SCHEDULE_PRIV_REQUIRES})
19+
20+
if(CONFIG_ESP_SCHEDULE_ENABLE_NVS)
21+
idf_component_optional_requires(PRIVATE nvs_flash)
22+
endif()

esp_schedule/Kconfig

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
11
menu "ESP Schedule Configuration"
22

3+
config ESP_SCHEDULE_ENABLE_NVS
4+
bool "Enable NVS Persistence"
5+
default y
6+
help
7+
Enable NVS (Non-Volatile Storage) persistence for schedules.
8+
9+
This allows schedules to be saved to flash memory and automatically
10+
restored after device reboot or power cycle.
11+
12+
Disabling this option will:
13+
- Remove NVS-related API functions (esp_schedule_create_nvs, etc.)
14+
- Save memory by excluding NVS persistence code
15+
- Reduce binary size and remove nvs_flash dependency
16+
17+
If disabled, only esp_schedule_create_default() will be available.
18+
319
config ESP_SCHEDULE_ENABLE_DAYLIGHT
420
bool "Enable Daylight (Sunrise/Sunset) Schedules"
521
default y

esp_schedule/README.md

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,192 @@ See the comprehensive example in [`examples/get_started/`](examples/get_started/
2626
- **Schedule Management** - Create, edit, enable, and disable schedules
2727

2828
The example includes detailed documentation, build instructions, and demonstrates all schedule types with practical use cases.
29+
30+
## Triggers as a list
31+
32+
Schedules can now contain multiple triggers. Instead of a single `trigger`, use `triggers` with a list and count. Each entry is an `esp_schedule_trigger_t` and the scheduler will trigger on the union of all entries.
33+
34+
```
35+
#include "esp_schedule.h"
36+
37+
static void my_trigger_cb(esp_schedule_handle_t handle, void *priv_data) {
38+
// Handle trigger
39+
}
40+
41+
void app_create_multi_trigger_schedule(void) {
42+
static esp_schedule_trigger_t trigger_list[3];
43+
44+
// 1) Every Monday and Thursday at 13:30
45+
trigger_list[0].type = ESP_SCHEDULE_TYPE_DAYS_OF_WEEK;
46+
trigger_list[0].hours = 13;
47+
trigger_list[0].minutes = 30;
48+
trigger_list[0].day.repeat_days = ESP_SCHEDULE_DAY_MONDAY | ESP_SCHEDULE_DAY_THURSDAY;
49+
50+
// 2) Date-based: 19:30 on the 20th day of any month
51+
trigger_list[1].type = ESP_SCHEDULE_TYPE_DATE;
52+
trigger_list[1].hours = 19;
53+
trigger_list[1].minutes = 30;
54+
trigger_list[1].date.day = 20; // 20th of the month
55+
trigger_list[1].date.repeat_months = 0; // any month
56+
trigger_list[1].date.year = 0; // any year
57+
trigger_list[1].date.repeat_every_year = true; // keep repeating
58+
59+
// 3) Sunrise with 15 minutes offset before, only on weekends
60+
#if CONFIG_ESP_SCHEDULE_ENABLE_DAYLIGHT
61+
trigger_list[2].type = ESP_SCHEDULE_TYPE_SUNRISE;
62+
trigger_list[2].hours = 0; // ignored by solar types
63+
trigger_list[2].minutes = 0; // ignored by solar types
64+
trigger_list[2].day.repeat_days = ESP_SCHEDULE_DAY_SATURDAY | ESP_SCHEDULE_DAY_SUNDAY;
65+
trigger_list[2].date.day = 0; // use day-of-week pattern
66+
trigger_list[2].date.repeat_months = 0; // any month
67+
trigger_list[2].date.year = 0; // any year
68+
trigger_list[2].date.repeat_every_year = true;
69+
trigger_list[2].solar.latitude = 37.7749; // San Francisco
70+
trigger_list[2].solar.longitude = -122.4194;
71+
trigger_list[2].solar.offset_minutes = -15; // 15 minutes before sunrise
72+
#endif
73+
74+
esp_schedule_config_t cfg = { 0 };
75+
strncpy(cfg.name, "multi", sizeof(cfg.name) - 1);
76+
cfg.triggers.list = trigger_list;
77+
cfg.triggers.count = sizeof(trigger_list) / sizeof(trigger_list[0]);
78+
cfg.trigger_cb = my_trigger_cb;
79+
80+
esp_schedule_handle_t h = esp_schedule_create(&cfg);
81+
(void)h;
82+
}
83+
```
84+
85+
## Date-based triggers (ESP_SCHEDULE_TYPE_DATE)
86+
87+
Date-based triggers provide a flexible way to express calendar patterns using a combination of:
88+
89+
- **Time of day**: `hours` and `minutes` (24-hour format)
90+
- **Day-of-week mask**: `day.repeat_days` using `esp_schedule_days_t`
91+
- **Day-of-month**: `date.day` (1–31)
92+
- **Months-of-year mask**: `date.repeat_months` using `esp_schedule_months_t`
93+
- **Specific year**: `date.year` (4-digit), with `date.repeat_every_year`
94+
95+
Rules:
96+
- **At least one** of day-of-week (`day.repeat_days`) or day-of-month (`date.day`) should be set.
97+
- Day-of-week and day-of-month are combined using **OR**: a date matches if it is on one of the specified weekdays OR it is the specified day of the month.
98+
- Months and year act as additional filters. If a months mask is set, the date must be within those months. If a year is set (non-zero), only that specific year will match. Use `repeat_every_year = true` when you want the pattern to continue in subsequent years.
99+
100+
Examples (equivalent to common JSON patterns):
101+
102+
```c
103+
// 05:00 every Monday and Wednesday
104+
esp_schedule_trigger_t t1 = {
105+
.type = ESP_SCHEDULE_TYPE_DATE,
106+
.hours = 5, .minutes = 0,
107+
};
108+
t1.day.repeat_days = ESP_SCHEDULE_DAY_MONDAY | ESP_SCHEDULE_DAY_WEDNESDAY;
109+
t1.date.day = 0; // not using day-of-month
110+
t1.date.repeat_months = 0; // any month
111+
t1.date.year = 0; // any year
112+
t1.date.repeat_every_year = true;
113+
114+
// 19:30 every 20th of the month
115+
esp_schedule_trigger_t t2 = {
116+
.type = ESP_SCHEDULE_TYPE_DATE,
117+
.hours = 19, .minutes = 30,
118+
};
119+
t2.day.repeat_days = 0; // any day-of-week
120+
t2.date.day = 20; // 20th
121+
t2.date.repeat_months = 0; // any month
122+
t2.date.year = 0; // any year
123+
t2.date.repeat_every_year = true;
124+
125+
// 14:03 on (Tue..Sat) OR on the 14th, limited to Jan..Apr
126+
esp_schedule_trigger_t t3 = {
127+
.type = ESP_SCHEDULE_TYPE_DATE,
128+
.hours = 14, .minutes = 3,
129+
};
130+
t3.day.repeat_days = ESP_SCHEDULE_DAY_TUESDAY | ESP_SCHEDULE_DAY_WEDNESDAY |
131+
ESP_SCHEDULE_DAY_THURSDAY | ESP_SCHEDULE_DAY_FRIDAY |
132+
ESP_SCHEDULE_DAY_SATURDAY;
133+
t3.date.day = 14; // OR 14th of the month
134+
t3.date.repeat_months = ESP_SCHEDULE_MONTH_JANUARY | ESP_SCHEDULE_MONTH_FEBRUARY |
135+
ESP_SCHEDULE_MONTH_MARCH | ESP_SCHEDULE_MONTH_APRIL;
136+
t3.date.year = 0; // any year
137+
t3.date.repeat_every_year = true;
138+
139+
// (one-shot) 00:00 on 9 August 2035
140+
esp_schedule_trigger_t t4 = {
141+
.type = ESP_SCHEDULE_TYPE_DATE,
142+
.hours = 0, .minutes = 0,
143+
};
144+
t4.day.repeat_days = 0; // not used
145+
t4.date.day = 9; // 9th
146+
t4.date.repeat_months = ESP_SCHEDULE_MONTH_AUGUST;
147+
t4.date.year = 2035; // specific year
148+
t4.date.repeat_every_year = false; // one-shot in that year
149+
```
150+
151+
> Tip: Prefer using the `esp_schedule_days_t` and `esp_schedule_months_t` constants instead of raw bit values. This keeps code readable and portable.
152+
153+
## Solar triggers with the same flexibility
154+
155+
Solar triggers (`ESP_SCHEDULE_TYPE_SUNRISE` / `ESP_SCHEDULE_TYPE_SUNSET`) support the same filtering patterns as date-based triggers:
156+
157+
- Use `day.repeat_days` for day-of-week filters.
158+
- Use `date.day`, `date.repeat_months`, and `date.year` for specific date/month/year patterns.
159+
- Provide `solar.latitude`, `solar.longitude`, and an optional `solar.offset_minutes` to shift from the exact sunrise/sunset time.
160+
161+
Examples:
162+
163+
```c
164+
// Sunrise every Monday, Wednesday 15 minutes before event
165+
esp_schedule_trigger_t s1 = { .type = ESP_SCHEDULE_TYPE_SUNRISE };
166+
s1.day.repeat_days = ESP_SCHEDULE_DAY_MONDAY | ESP_SCHEDULE_DAY_WEDNESDAY;
167+
s1.date.day = 0; // using day-of-week pattern
168+
s1.date.repeat_months = 0; // any month
169+
s1.date.year = 0;
170+
s1.date.repeat_every_year = true;
171+
s1.solar.latitude = 37.7749;
172+
s1.solar.longitude = -122.4194;
173+
s1.solar.offset_minutes = -15;
174+
175+
// Sunset on the 15th day of Feb and Nov, every year, 10 minutes after event
176+
esp_schedule_trigger_t s2 = { .type = ESP_SCHEDULE_TYPE_SUNSET };
177+
s2.day.repeat_days = 0; // not using day-of-week filter
178+
s2.date.day = 15; // 15th of the month
179+
s2.date.repeat_months = ESP_SCHEDULE_MONTH_FEBRUARY | ESP_SCHEDULE_MONTH_NOVEMBER;
180+
s2.date.year = 0; // any year
181+
s2.date.repeat_every_year = true;
182+
s2.solar.latitude = 52.5200;
183+
s2.solar.longitude = 13.4050;
184+
s2.solar.offset_minutes = 10;
185+
```
186+
187+
These filters are applied when computing the next valid sunrise/sunset for your location. If both day-of-week and day-of-month are specified, they are combined using **OR**; month and year remain additional filters.
188+
189+
## Glue Layers
190+
191+
This component makes use of the following glue abstraction layers under `glue`:
192+
- `glue_log.h`: Logging
193+
- `glue_mem.h`: Memory allocation
194+
- `glue_nvs.h`: Non-Volatile Storage
195+
- `glue_time.h`: Time provider and synchronization
196+
- `glue_timer.h`: Timer implementation
197+
198+
### As an ESP-IDF component
199+
200+
When using this component normally, the default glue implementations are used:
201+
- Logging: `glue/esp/glue_log_impl.h`
202+
- Memory allocation: `glue/esp/glue_mem_impl.h`
203+
- Non-Volatile Storage: `glue/esp/nvs.c`
204+
- Time provider and synchronization: `glue/esp/time.c`
205+
- Timer implementation: `glue/esp/timer.c`
206+
207+
### Custom glue implementations
208+
209+
If the underlying implementations are required to be changed, then you would need to implement a custom `CMakeLists.txt` for this component:
210+
1. Common non-glue sources and include directories can be included using `esp_schedule_variables.cmake`.
211+
2. Append your glue sources and include directories to the variables provided.
212+
3. Use the variables to build your target library (e.g., passing them to `idf_component_register`).
213+
4. (Optional) If you wish to **enable NVS**, and the configuration system used is *not compatible with Kconfig files*:
214+
- You must specify `#define CONFIG_ESP_SCHEDULE_ENABLE_NVS 1` **very early at the top-level include file**.
215+
- Not doing so will *disable NVS by default*.
216+
217+
The [default implementation](#as-an-esp-idf-component) does this in this component's `CMakeLists.txt` with the default glue implementations.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# This file is used to set the common non-glue variables for the esp_schedule component.
2+
# Include this file and extend the variables to add glue sources and include directories.
3+
4+
# Source files
5+
set(ESP_SCHEDULE_SRCS "${CMAKE_CURRENT_LIST_DIR}/src/esp_schedule.c")
6+
7+
# Include directories
8+
set(ESP_SCHEDULE_INCLUDE_DIRS "${CMAKE_CURRENT_LIST_DIR}/include/common")
9+
10+
# Private include directories
11+
set(ESP_SCHEDULE_PRIV_INCLUDE_DIRS "${CMAKE_CURRENT_LIST_DIR}/src"
12+
"${CMAKE_CURRENT_LIST_DIR}/glue")
13+
14+
# Private requirements
15+
set(ESP_SCHEDULE_PRIV_REQUIRES "esp_daylight")

0 commit comments

Comments
 (0)