new release

This commit is contained in:
2026-05-15 12:20:47 +01:00
parent 286028b6a8
commit d0d431daf2
440 changed files with 2776 additions and 2462 deletions

View File

@@ -14,55 +14,90 @@
#include <time.h>
#include <string.h>
#include <stdio.h>
static const char *TAG = "scheduler";
/* ===== Limites ===== */
#define SCHED_MIN_CURRENT_A 6
#define SCHED_MAX_CURRENT_A 80
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
/* ===== Estado interno ===== */
static sched_config_t s_cfg = {
.enabled = false,
.mode = SCHED_MODE_DISABLED,
.start_min = 0,
.end_min = 24 * 60};
.window_count = 0,
};
static bool s_allowed_now = true;
static uint16_t s_current_limit_a = 0;
static TaskHandle_t s_task_handle = NULL;
/* ===== Storage ===== */
#define NVS_NAMESPACE "scheduler"
#define NVS_KEY_ENABLED "enabled" // u8
#define NVS_KEY_MODE "mode" // u8
#define NVS_KEY_START_MIN "start_min" // u16
#define NVS_KEY_END_MIN "end_min" // u16
#define NVS_KEY_ENABLED "enabled" // u8
#define NVS_KEY_MODE "mode" // u8
#define NVS_KEY_WCOUNT "wcount" // u8
static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); }
// por janela i: w0_s, w0_e, w0_a ...
static void make_wkey(char *out, size_t out_sz, int i, const char *suffix)
{
if (!out || out_sz == 0)
return;
snprintf(out, out_sz, "w%d_%s", i, suffix);
}
static uint16_t clamp_u16(uint16_t v, uint16_t lo, uint16_t hi)
{
if (v < lo)
return lo;
if (v > hi)
return hi;
return v;
}
static bool cfg_sanitize(sched_config_t *cfg)
{
if (!cfg)
return false;
bool changed = false;
// enabled é bool
cfg->enabled = cfg->enabled ? true : false;
// mode válido
if (cfg->mode > SCHED_MODE_SIMPLE)
if (cfg->mode > SCHED_MODE_DAILY_MULTI)
{
cfg->mode = SCHED_MODE_DISABLED;
changed = true;
}
// start/end em minutos (0..1440)
if (cfg->start_min > (24 * 60))
if (cfg->window_count > SCHED_MAX_WINDOWS)
{
cfg->start_min = 0;
cfg->window_count = SCHED_MAX_WINDOWS;
changed = true;
}
if (cfg->end_min > (24 * 60))
for (int i = 0; i < cfg->window_count && i < SCHED_MAX_WINDOWS; i++)
{
cfg->end_min = 24 * 60;
changed = true;
sched_window_t *w = &cfg->windows[i];
if (w->start_min > 1440)
{
w->start_min = 0;
changed = true;
}
if (w->end_min > 1440)
{
w->end_min = 1440;
changed = true;
}
uint16_t olda = w->current_a;
w->current_a = clamp_u16(w->current_a, SCHED_MIN_CURRENT_A, SCHED_MAX_CURRENT_A);
if (w->current_a != olda)
changed = true;
}
return changed;
@@ -73,9 +108,7 @@ static void load_config_from_storage(sched_config_t *out)
if (!out)
return;
// defaults
*out = s_cfg;
bool needs_flush = false;
uint8_t u8 = 0;
@@ -85,9 +118,7 @@ static void load_config_from_storage(sched_config_t *out)
// enabled
err = storage_get_u8_sync(NVS_NAMESPACE, NVS_KEY_ENABLED, &u8, TO_TICKS_MS(800));
if (err == ESP_OK && u8 <= 1)
{
out->enabled = (u8 != 0);
}
else
{
out->enabled = false;
@@ -99,10 +130,8 @@ static void load_config_from_storage(sched_config_t *out)
// mode
err = storage_get_u8_sync(NVS_NAMESPACE, NVS_KEY_MODE, &u8, TO_TICKS_MS(800));
if (err == ESP_OK && u8 <= (uint8_t)SCHED_MODE_SIMPLE)
{
if (err == ESP_OK && u8 <= (uint8_t)SCHED_MODE_DAILY_MULTI)
out->mode = (sched_mode_t)u8;
}
else
{
out->mode = SCHED_MODE_DISABLED;
@@ -112,54 +141,83 @@ static void load_config_from_storage(sched_config_t *out)
esp_err_to_name(err));
}
// start_min
err = storage_get_u16_sync(NVS_NAMESPACE, NVS_KEY_START_MIN, &u16, TO_TICKS_MS(800));
if (err == ESP_OK && u16 <= (24 * 60))
{
out->start_min = u16;
}
// wcount
err = storage_get_u8_sync(NVS_NAMESPACE, NVS_KEY_WCOUNT, &u8, TO_TICKS_MS(800));
if (err == ESP_OK && u8 <= SCHED_MAX_WINDOWS)
out->window_count = u8;
else
{
out->start_min = 0;
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_START_MIN, out->start_min);
out->window_count = 0;
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_WCOUNT, 0);
needs_flush = true;
ESP_LOGW(TAG, "Missing/invalid start_min (%s) -> default=0 (persisted)",
ESP_LOGW(TAG, "Missing/invalid wcount (%s) -> default=0 (persisted)",
esp_err_to_name(err));
}
// end_min
err = storage_get_u16_sync(NVS_NAMESPACE, NVS_KEY_END_MIN, &u16, TO_TICKS_MS(800));
if (err == ESP_OK && u16 <= (24 * 60))
// windows
for (int i = 0; i < out->window_count && i < SCHED_MAX_WINDOWS; i++)
{
out->end_min = u16;
}
else
{
out->end_min = 24 * 60;
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_END_MIN, out->end_min);
needs_flush = true;
ESP_LOGW(TAG, "Missing/invalid end_min (%s) -> default=1440 (persisted)",
esp_err_to_name(err));
char key[16];
make_wkey(key, sizeof(key), i, "s");
err = storage_get_u16_sync(NVS_NAMESPACE, key, &u16, TO_TICKS_MS(800));
if (err == ESP_OK && u16 <= 1440)
out->windows[i].start_min = u16;
else
{
out->windows[i].start_min = 0;
(void)storage_set_u16_async(NVS_NAMESPACE, key, 0);
needs_flush = true;
}
make_wkey(key, sizeof(key), i, "e");
err = storage_get_u16_sync(NVS_NAMESPACE, key, &u16, TO_TICKS_MS(800));
if (err == ESP_OK && u16 <= 1440)
out->windows[i].end_min = u16;
else
{
out->windows[i].end_min = 1440;
(void)storage_set_u16_async(NVS_NAMESPACE, key, 1440);
needs_flush = true;
}
make_wkey(key, sizeof(key), i, "a");
err = storage_get_u16_sync(NVS_NAMESPACE, key, &u16, TO_TICKS_MS(800));
if (err == ESP_OK && u16 >= SCHED_MIN_CURRENT_A && u16 <= SCHED_MAX_CURRENT_A)
out->windows[i].current_a = u16;
else
{
out->windows[i].current_a = 16;
(void)storage_set_u16_async(NVS_NAMESPACE, key, 16);
needs_flush = true;
}
}
// sanity final (e persistir se ajustou algo)
if (cfg_sanitize(out))
{
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_ENABLED, out->enabled ? 1 : 0);
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_MODE, (uint8_t)out->mode);
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_START_MIN, out->start_min);
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_END_MIN, out->end_min);
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_WCOUNT, out->window_count);
for (int i = 0; i < out->window_count && i < SCHED_MAX_WINDOWS; i++)
{
char key[16];
make_wkey(key, sizeof(key), i, "s");
(void)storage_set_u16_async(NVS_NAMESPACE, key, out->windows[i].start_min);
make_wkey(key, sizeof(key), i, "e");
(void)storage_set_u16_async(NVS_NAMESPACE, key, out->windows[i].end_min);
make_wkey(key, sizeof(key), i, "a");
(void)storage_set_u16_async(NVS_NAMESPACE, key, out->windows[i].current_a);
}
needs_flush = true;
}
if (needs_flush)
{
(void)storage_flush_sync(TO_TICKS_MS(2000));
}
ESP_LOGI(TAG, "Loaded: enabled=%d mode=%d start=%u end=%u",
out->enabled, (int)out->mode,
(unsigned)out->start_min, (unsigned)out->end_min);
ESP_LOGI(TAG, "Loaded: enabled=%d mode=%s wcount=%u",
(int)out->enabled, sched_mode_to_str(out->mode), (unsigned)out->window_count);
}
static void save_config_to_storage(const sched_config_t *cfg)
@@ -167,71 +225,102 @@ static void save_config_to_storage(const sched_config_t *cfg)
if (!cfg)
return;
// Debounced writes
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_ENABLED, cfg->enabled ? 1 : 0);
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_MODE, (uint8_t)cfg->mode);
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_START_MIN, cfg->start_min);
(void)storage_set_u16_async(NVS_NAMESPACE, NVS_KEY_END_MIN, cfg->end_min);
(void)storage_set_u8_async(NVS_NAMESPACE, NVS_KEY_WCOUNT, cfg->window_count);
// opcional: flush imediato
// (void)storage_flush_async();
for (int i = 0; i < cfg->window_count && i < SCHED_MAX_WINDOWS; i++)
{
char key[16];
make_wkey(key, sizeof(key), i, "s");
(void)storage_set_u16_async(NVS_NAMESPACE, key, cfg->windows[i].start_min);
make_wkey(key, sizeof(key), i, "e");
(void)storage_set_u16_async(NVS_NAMESPACE, key, cfg->windows[i].end_min);
make_wkey(key, sizeof(key), i, "a");
(void)storage_set_u16_async(NVS_NAMESPACE, key, cfg->windows[i].current_a);
}
}
/* ===== Lógica de janelas ===== */
static bool compute_allowed_now(const sched_config_t *cfg)
static bool in_window(uint16_t start, uint16_t end, uint16_t now_min)
{
if (start <= end)
return (now_min >= start && now_min < end);
return (now_min >= start || now_min < end); // cruza meia-noite
}
static void compute_allowed_and_current_now(const sched_config_t *cfg, bool *allowed, uint16_t *curr_a)
{
if (!cfg || !allowed || !curr_a)
return;
if (!cfg->enabled || cfg->mode == SCHED_MODE_DISABLED)
{
// se desativado, não limita nada
return true;
*allowed = true; // scheduler desligado => não bloqueia
*curr_a = 0;
return;
}
// enabled + DAILY_MULTI: fora de todas as janelas => bloqueia (opção 1)
time_t now = time(NULL);
struct tm tm_now = {0};
localtime_r(&now, &tm_now);
uint16_t now_min = (uint16_t)(tm_now.tm_hour * 60 + tm_now.tm_min);
// janela [start_min, end_min)
if (cfg->start_min <= cfg->end_min)
if (cfg->window_count == 0)
{
// Ex: 08:0018:00
return (now_min >= cfg->start_min && now_min < cfg->end_min);
*allowed = false;
*curr_a = 0;
return;
}
else
bool any = false;
uint16_t best = 0xFFFF; // overlaps => min por segurança
for (int i = 0; i < cfg->window_count && i < SCHED_MAX_WINDOWS; i++)
{
// janela que passa pela meia-noite, ex: 22:0006:00
return (now_min >= cfg->start_min || now_min < cfg->end_min);
const sched_window_t *w = &cfg->windows[i];
if (in_window(w->start_min, w->end_min, now_min))
{
any = true;
if (w->current_a < best)
best = w->current_a;
}
}
*allowed = any;
*curr_a = any ? best : 0;
}
static void scheduler_recompute_and_emit(void)
{
bool new_allowed = compute_allowed_now(&s_cfg);
bool new_allowed = true;
uint16_t new_curr = 0;
if (new_allowed != s_allowed_now)
compute_allowed_and_current_now(&s_cfg, &new_allowed, &new_curr);
if (new_allowed != s_allowed_now || new_curr != s_current_limit_a)
{
s_allowed_now = new_allowed;
ESP_LOGI(TAG, "allowed_now changed -> %d", s_allowed_now);
s_current_limit_a = new_curr;
ESP_LOGI(TAG, "allowed_now=%d current_limit=%uA",
(int)s_allowed_now, (unsigned)s_current_limit_a);
sched_event_state_t ev = {
.allowed_now = s_allowed_now};
(void)esp_event_post(SCHED_EVENTS,
SCHED_EVENT_WINDOW_CHANGED,
&ev,
sizeof(ev),
portMAX_DELAY);
.allowed_now = s_allowed_now,
.current_limit_a = s_current_limit_a};
(void)esp_event_post(SCHED_EVENTS, SCHED_EVENT_WINDOW_CHANGED,
&ev, sizeof(ev), portMAX_DELAY);
}
}
/* ===== Task do scheduler ===== */
static void scheduler_task(void *arg)
{
(void)arg;
const TickType_t period = pdMS_TO_TICKS(60000); // 60s
const TickType_t period = pdMS_TO_TICKS(60000);
ESP_LOGI(TAG, "Scheduler task started");
for (;;)
{
scheduler_recompute_and_emit();
@@ -239,46 +328,33 @@ static void scheduler_task(void *arg)
}
}
/* ===== API pública ===== */
void scheduler_init(void)
{
// 0) garante storage
esp_err_t se = storage_service_init();
if (se != ESP_OK)
{
ESP_LOGE(TAG, "storage_service_init failed: %s (using defaults in RAM)", esp_err_to_name(se));
// segue com defaults
}
else
{
// 1) carregar config
load_config_from_storage(&s_cfg);
}
// 2) calcular estado inicial
s_allowed_now = compute_allowed_now(&s_cfg);
compute_allowed_and_current_now(&s_cfg, &s_allowed_now, &s_current_limit_a);
// 3) enviar evento INIT
sched_event_state_t ev = {
.allowed_now = s_allowed_now};
(void)esp_event_post(SCHED_EVENTS,
SCHED_EVENT_INIT,
&ev,
sizeof(ev),
portMAX_DELAY);
.allowed_now = s_allowed_now,
.current_limit_a = s_current_limit_a};
(void)esp_event_post(SCHED_EVENTS, SCHED_EVENT_INIT,
&ev, sizeof(ev), portMAX_DELAY);
ESP_LOGI(TAG, "Init: allowed_now=%d", s_allowed_now);
ESP_LOGI(TAG, "Init: allowed_now=%d current_limit=%uA",
(int)s_allowed_now, (unsigned)s_current_limit_a);
// 4) criar a task
if (s_task_handle == NULL)
{
(void)xTaskCreate(
scheduler_task,
"scheduler_task",
4 * 1024,
NULL,
3,
&s_task_handle);
(void)xTaskCreate(scheduler_task, "scheduler_task", 4 * 1024,
NULL, 3, &s_task_handle);
}
}
@@ -291,8 +367,6 @@ void scheduler_set_config(const sched_config_t *cfg)
(void)cfg_sanitize(&s_cfg);
save_config_to_storage(&s_cfg);
// recomputa imediatamente para refletir mudança
scheduler_recompute_and_emit();
}
@@ -305,3 +379,8 @@ bool scheduler_is_allowed_now(void)
{
return s_allowed_now;
}
uint16_t scheduler_get_current_limit_now_a(void)
{
return s_current_limit_a;
}

View File

@@ -4,19 +4,37 @@
const char *sched_mode_to_str(sched_mode_t mode)
{
switch (mode) {
case SCHED_MODE_DISABLED: return "disabled";
case SCHED_MODE_SIMPLE: return "simple";
case SCHED_MODE_WEEKLY: return "weekly";
default: return "disabled";
switch (mode)
{
case SCHED_MODE_DISABLED:
return "disabled";
case SCHED_MODE_DAILY_MULTI:
return "daily_multi";
default:
return "disabled";
}
}
bool sched_mode_from_str(const char *s, sched_mode_t *out)
{
if (!s || !out) return false;
if (!strcasecmp(s, "disabled")) { *out = SCHED_MODE_DISABLED; return true; }
if (!strcasecmp(s, "simple")) { *out = SCHED_MODE_SIMPLE; return true; }
if (!strcasecmp(s, "weekly")) { *out = SCHED_MODE_WEEKLY; return true; }
if (!s || !out)
return false;
if (!strcasecmp(s, "disabled"))
{
*out = SCHED_MODE_DISABLED;
return true;
}
if (!strcasecmp(s, "daily_multi"))
{
*out = SCHED_MODE_DAILY_MULTI;
return true;
}
if (!strcasecmp(s, "multi"))
{
*out = SCHED_MODE_DAILY_MULTI;
return true;
}
return false;
}
}