new module

This commit is contained in:
2025-12-09 11:48:31 +00:00
parent 4820d9111e
commit e6e2622a95
98 changed files with 5349 additions and 8607 deletions

View File

@@ -0,0 +1,7 @@
set(srcs "src/scheduler_types.c" "src/scheduler.c" "src/scheduler_events.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "src"
PRIV_REQUIRES nvs_flash esp_timer
REQUIRES esp_event evse)

View File

@@ -0,0 +1,2 @@
version: "2.6.0"
description: Authentication component

View File

@@ -0,0 +1,18 @@
// components/scheduler/include/scheduler.h
#pragma once
#include <stdbool.h>
#include "scheduler_types.h"
#ifdef __cplusplus
extern "C" {
#endif
void scheduler_init(void);
void scheduler_set_config(const sched_config_t *cfg);
sched_config_t scheduler_get_config(void);
bool scheduler_is_allowed_now(void);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,17 @@
// scheduler_events.h
#pragma once
#include "esp_event.h"
#include <stdbool.h>
ESP_EVENT_DECLARE_BASE(SCHED_EVENTS);
typedef enum
{
SCHED_EVENT_INIT = 0, // envia estado inicial
SCHED_EVENT_WINDOW_CHANGED, // allowed_now mudou
} sched_event_id_t;
typedef struct
{
bool allowed_now;
} sched_event_state_t;

View File

@@ -0,0 +1,31 @@
// components/scheduler/include/scheduler_types.h
#pragma once
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
SCHED_MODE_DISABLED = 0, // não faz gating
SCHED_MODE_SIMPLE, // por ex: janela diária única
SCHED_MODE_WEEKLY // por ex: janelas por dia da semana
} sched_mode_t;
typedef struct {
bool enabled; // gating ativo?
sched_mode_t mode;
// exemplo bem simples: uma janela diária [start_min..end_min[
// minutos desde meia-noite, 0..1439
uint16_t start_min;
uint16_t end_min;
} sched_config_t;
const char *sched_mode_to_str(sched_mode_t mode);
bool sched_mode_from_str(const char *s, sched_mode_t *out);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,251 @@
// components/scheduler/src/scheduler.c
#include "scheduler.h"
#include "scheduler_types.h"
#include "scheduler_events.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "nvs.h"
#include <time.h>
#include <string.h>
static const char *TAG = "scheduler";
/* ===== Estado interno ===== */
static sched_config_t s_cfg = {
.enabled = false,
.mode = SCHED_MODE_DISABLED,
.start_min = 0,
.end_min = 24 * 60};
static bool s_allowed_now = true;
static TaskHandle_t s_task_handle = NULL;
/* ===== NVS ===== */
#define NVS_NAMESPACE "scheduler"
#define NVS_KEY_ENABLED "enabled"
#define NVS_KEY_MODE "mode"
#define NVS_KEY_START_MIN "start_min"
#define NVS_KEY_END_MIN "end_min"
static void load_config_from_nvs(sched_config_t *out)
{
if (!out)
return;
*out = s_cfg; // defaults
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "No scheduler namespace in NVS (%s), using defaults",
esp_err_to_name(err));
return;
}
uint8_t u8;
uint16_t u16;
if (nvs_get_u8(h, NVS_KEY_ENABLED, &u8) == ESP_OK)
{
out->enabled = (u8 != 0);
}
if (nvs_get_u8(h, NVS_KEY_MODE, &u8) == ESP_OK)
{
if (u8 <= (uint8_t)SCHED_MODE_SIMPLE)
{
out->mode = (sched_mode_t)u8;
}
}
if (nvs_get_u16(h, NVS_KEY_START_MIN, &u16) == ESP_OK)
{
out->start_min = u16;
}
if (nvs_get_u16(h, NVS_KEY_END_MIN, &u16) == ESP_OK)
{
out->end_min = u16;
}
nvs_close(h);
ESP_LOGI(TAG, "Loaded from NVS: enabled=%d mode=%d start=%u end=%u",
out->enabled, out->mode,
(unsigned)out->start_min, (unsigned)out->end_min);
}
static void save_config_to_nvs(const sched_config_t *cfg)
{
if (!cfg)
return;
nvs_handle_t h;
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_open failed: %s", esp_err_to_name(err));
return;
}
err = nvs_set_u8(h, NVS_KEY_ENABLED, cfg->enabled ? 1 : 0);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u8(enabled) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_set_u8(h, NVS_KEY_MODE, (uint8_t)cfg->mode);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u8(mode) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_set_u16(h, NVS_KEY_START_MIN, cfg->start_min);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u16(start_min) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_set_u16(h, NVS_KEY_END_MIN, cfg->end_min);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_set_u16(end_min) failed: %s", esp_err_to_name(err));
goto out;
}
err = nvs_commit(h);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "nvs_commit failed: %s", esp_err_to_name(err));
}
else
{
ESP_LOGI(TAG, "Scheduler config saved to NVS");
}
out:
nvs_close(h);
}
/* ===== Lógica de janelas ===== */
static bool compute_allowed_now(const sched_config_t *cfg)
{
if (!cfg->enabled || cfg->mode == SCHED_MODE_DISABLED)
{
// se desativado, não limita nada
return true;
}
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)
{
// Ex: 08:0018:00
return (now_min >= cfg->start_min && now_min < cfg->end_min);
}
else
{
// janela que passa pela meia-noite, ex: 22:0006:00
return (now_min >= cfg->start_min || now_min < cfg->end_min);
}
}
static void scheduler_recompute_and_emit(void)
{
bool new_allowed = compute_allowed_now(&s_cfg);
if (new_allowed != s_allowed_now)
{
s_allowed_now = new_allowed;
ESP_LOGI(TAG, "allowed_now changed -> %d", s_allowed_now);
sched_event_state_t ev = {
.allowed_now = s_allowed_now};
esp_event_post(SCHED_EVENTS,
SCHED_EVENT_WINDOW_CHANGED,
&ev,
sizeof(ev),
portMAX_DELAY);
}
}
/* ===== Task do scheduler ===== */
static void scheduler_task(void *arg)
{
const TickType_t period = pdMS_TO_TICKS(60000); // 60s
ESP_LOGI(TAG, "Scheduler task started");
for (;;)
{
scheduler_recompute_and_emit();
vTaskDelay(period);
}
}
/* ===== API pública ===== */
void scheduler_init(void)
{
// 1) carregar config
load_config_from_nvs(&s_cfg);
// 2) calcular estado inicial
s_allowed_now = compute_allowed_now(&s_cfg);
// 3) enviar evento INIT
sched_event_state_t ev = {
.allowed_now = s_allowed_now};
esp_event_post(SCHED_EVENTS,
SCHED_EVENT_INIT,
&ev,
sizeof(ev),
portMAX_DELAY);
ESP_LOGI(TAG, "Init: allowed_now=%d", s_allowed_now);
// 4) criar a task
if (s_task_handle == NULL)
{
xTaskCreate(
scheduler_task,
"scheduler_task",
4 * 1024,
NULL,
3, // prioridade razoável
&s_task_handle);
}
}
void scheduler_set_config(const sched_config_t *cfg)
{
if (!cfg)
return;
s_cfg = *cfg;
save_config_to_nvs(&s_cfg);
// recomputa imediatamente para refletir mudança
scheduler_recompute_and_emit();
}
sched_config_t scheduler_get_config(void)
{
return s_cfg;
}
bool scheduler_is_allowed_now(void)
{
return s_allowed_now;
}

View File

@@ -0,0 +1,4 @@
// scheduler_events.c
#include "scheduler_events.h"
ESP_EVENT_DEFINE_BASE(SCHED_EVENTS);

View File

@@ -0,0 +1,22 @@
// components/scheduler/src/scheduler_types.c
#include "scheduler_types.h"
#include <strings.h>
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";
}
}
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; }
return false;
}