new release
This commit is contained in:
@@ -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:00–18: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:00–06: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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user