diff --git a/components/auth/src/wiegand_reader.c b/components/auth/src/wiegand_reader.c index 8387fd2..80bc595 100755 --- a/components/auth/src/wiegand_reader.c +++ b/components/auth/src/wiegand_reader.c @@ -276,10 +276,7 @@ static void wiegand_sim_task(void *arg) { // lista fixa de idTags simuladas static const char *idtaglist[] = { - "00000041349", - "W2602312345", - "W34ABCDE123", - }; + "127239144069"}; const size_t list_size = sizeof(idtaglist) / sizeof(idtaglist[0]); for (;;) @@ -301,7 +298,7 @@ void initWiegand(void) xTaskCreate(wiegand_task, TAG, configMINIMAL_STACK_SIZE * 4, NULL, 4, NULL); // Para testes, podes ativar o simulador: - // ESP_LOGI(TAG, "Inicializando Wiegand simulado"); - // xTaskCreate(wiegand_sim_task, "WiegandSim", + //ESP_LOGI(TAG, "Inicializando Wiegand simulado"); + //xTaskCreate(wiegand_sim_task, "WiegandSim", // configMINIMAL_STACK_SIZE * 3, NULL, 3, NULL); } diff --git a/components/buzzer/include/buzzer.h b/components/buzzer/include/buzzer.h index 371d1d2..187d1e3 100755 --- a/components/buzzer/include/buzzer.h +++ b/components/buzzer/include/buzzer.h @@ -3,8 +3,7 @@ #include #include -#include "esp_err.h" -#include "buzzer_events.h" // para buzzer_pattern_id_t +#include "buzzer_events.h" // buzzer_pattern_id_t #ifdef __cplusplus extern "C" { @@ -33,7 +32,7 @@ void buzzer_stop(void); /** * @brief Ativa/Desativa globalmente o buzzer (mute). - * Quando desativado, não toca e garante nível/desligado. + * Quando desativado, não toca e garante desligado. */ void buzzer_set_enabled(bool enabled); @@ -52,9 +51,10 @@ void buzzer_set_quiet_hours(bool enabled, uint16_t start_min, uint16_t end_min); /** * @brief Ajusta a frequência PWM (somente PASSIVE/LEDC; timer exclusivo recomendado). - * @return ESP_OK em sucesso; erro se modo inválido ou argumento fora do range. + * @param hz Frequência desejada. + * @return Frequência efetiva configurada (Hz). 0 em erro (modo inválido ou argumento fora do range). */ -esp_err_t buzzer_set_frequency(uint32_t hz); +uint32_t buzzer_set_frequency(uint32_t hz); /** * @brief Define o duty cycle em porcentagem (0..100) para PASSIVE/LEDC. @@ -63,4 +63,4 @@ void buzzer_set_duty_percent(uint8_t pct); #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/components/buzzer/src/buzzer.c b/components/buzzer/src/buzzer.c index 0b04098..1b64649 100755 --- a/components/buzzer/src/buzzer.c +++ b/components/buzzer/src/buzzer.c @@ -1,5 +1,6 @@ // buzzer.c -#include "buzzer_events.h" +#include "buzzer.h" + #include "evse_events.h" #include "auth_events.h" #include "network_events.h" @@ -11,27 +12,26 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" +#include "freertos/event_groups.h" #include "driver/gpio.h" #include "driver/ledc.h" #include #include +#include // ===================== Configuração padrão ===================== #define CONFIG_BUZZER_GPIO GPIO_NUM_27 // 1 = PASSIVE (PWM), 0 = ACTIVE (on/off) -#define CONFIG_BUZZER_MODE_PASSIVE 0 +#define CONFIG_BUZZER_MODE_PASSIVE 1 -#define CONFIG_BUZZER_FREQ_HZ 3500 - -#define CONFIG_BUZZER_DUTY_PCT 70 +#define CONFIG_BUZZER_FREQ_HZ 2400 +#define CONFIG_BUZZER_DUTY_PCT 40 #define CONFIG_BUZZER_QUEUE_LEN 8 - #define CONFIG_BUZZER_TASK_STACK 2048 - #define CONFIG_BUZZER_TASK_PRIO (tskIDLE_PRIORITY + 1) // anti-spam (gap mínimo entre toques) @@ -41,7 +41,6 @@ // quiet hours start (minutos desde 00:00) #define CONFIG_BUZZER_QUIET_START_MIN (22 * 60) - // quiet hours end (minutos desde 00:00) #define CONFIG_BUZZER_QUIET_END_MIN (7 * 60) @@ -113,6 +112,31 @@ static TaskHandle_t s_buzzer_task = NULL; static TickType_t s_last_play_tick = 0; static const TickType_t s_min_gap_ticks = pdMS_TO_TICKS(CONFIG_BUZZER_MIN_GAP_MS); +// STOP / preempt (robusto em dual-core) +static EventGroupHandle_t s_buzzer_eg = NULL; +#define BUZZER_EG_STOP_BIT (1u << 0) + +static bool s_inited = false; + +static inline bool buzzer_should_stop(void) +{ + if (!s_buzzer_eg) + return false; + return (xEventGroupGetBits(s_buzzer_eg) & BUZZER_EG_STOP_BIT) != 0; +} + +static inline void buzzer_clear_stop(void) +{ + if (s_buzzer_eg) + xEventGroupClearBits(s_buzzer_eg, BUZZER_EG_STOP_BIT); +} + +static inline void buzzer_set_stop(void) +{ + if (s_buzzer_eg) + xEventGroupSetBits(s_buzzer_eg, BUZZER_EG_STOP_BIT); +} + // ===================== Padrões (afinados) ===================== // NOTA: os enums BUZZER_PATTERN_* vêm de buzzer_events.h @@ -139,7 +163,8 @@ static const buzzer_step_t pattern_fault[] = { {450, 0} // bloco 2 }; -static const buzzer_pattern_t buzzer_patterns[] = { +// BUG importante corrigido: tamanho explícito = BUZZER_PATTERN_MAX +static const buzzer_pattern_t buzzer_patterns[BUZZER_PATTERN_MAX] = { [BUZZER_PATTERN_PLUGGED] = {pattern_plugged, sizeof(pattern_plugged) / sizeof(buzzer_step_t)}, [BUZZER_PATTERN_UNPLUGGED] = {pattern_unplugged, sizeof(pattern_unplugged) / sizeof(buzzer_step_t)}, [BUZZER_PATTERN_CHARGING] = {pattern_charging, sizeof(pattern_charging) / sizeof(buzzer_step_t)}, @@ -161,38 +186,46 @@ static inline uint32_t duty_from_percent(uint8_t pct, ledc_timer_bit_t res) return (uint32_t)((pct * max) / 100U); } +static inline void buzzer_set_duty_raw(uint32_t duty) +{ + ledc_set_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, duty); + ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel); +} + +static inline void buzzer_set_duty_pct(uint8_t pct) +{ + buzzer_set_duty_raw(duty_from_percent(pct, s_buzzer_cfg.duty_resolution)); +} + +// Fade async (NO_WAIT) para permitir STOP responsivo (checamos por nós) +static inline void buzzer_fade_to_pct_async(uint8_t pct, uint32_t time_ms) +{ + uint32_t duty = duty_from_percent(pct, s_buzzer_cfg.duty_resolution); + ledc_set_fade_with_time(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, duty, time_ms); + ledc_fade_start(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, LEDC_FADE_NO_WAIT); +} + static inline bool is_pattern_valid(buzzer_pattern_id_t id) { return (id > BUZZER_PATTERN_NONE && id < BUZZER_PATTERN_MAX); } -static void buzzer_on(void) +static inline bool is_critical_pattern(buzzer_pattern_id_t id) { - if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE) - { - // Não mudar freq a cada beep; já foi configurada na init. - esp_err_t err1 = ledc_set_duty(s_buzzer_cfg.ledc_speed_mode, - s_buzzer_cfg.ledc_channel, - duty_from_percent(s_buzzer_cfg.duty_percent, s_buzzer_cfg.duty_resolution)); - if (err1 != ESP_OK) - ESP_LOGW(TAG, "ledc_set_duty err=%s", esp_err_to_name(err1)); - esp_err_t err2 = ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel); - if (err2 != ESP_OK) - ESP_LOGW(TAG, "ledc_update_duty err=%s", esp_err_to_name(err2)); - } - else - { - gpio_set_level(s_buzzer_cfg.gpio, 1); - } + return (id == BUZZER_PATTERN_FAULT || id == BUZZER_PATTERN_CARD_DENIED); } static void buzzer_off(void) { + if (!s_inited) + return; + if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE) { esp_err_t err1 = ledc_set_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel, 0); if (err1 != ESP_OK) ESP_LOGW(TAG, "ledc_set_duty(0) err=%s", esp_err_to_name(err1)); + esp_err_t err2 = ledc_update_duty(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_channel); if (err2 != ESP_OK) ESP_LOGW(TAG, "ledc_update_duty err=%s", esp_err_to_name(err2)); @@ -209,6 +242,11 @@ static bool in_quiet_hours(void) return false; time_t now = time(NULL); + + // Se ainda não há tempo real (NTP não acertou), não aplicar quiet hours + if (now < 1700000000) // ~2023-11 + return false; + struct tm lt; if (localtime_r(&now, <) == NULL) return false; @@ -219,36 +257,103 @@ static bool in_quiet_hours(void) if (start == end) return false; // desativado + if (start < end) - { return (minutes >= start && minutes < end); - } else - { // janela cruza meia-noite - return (minutes >= start || minutes < end); - } + return (minutes >= start || minutes < end); // cruza meia-noite } // ===================== Execução do padrão (apenas dentro da task) ===================== static void buzzer_execute(buzzer_pattern_id_t pattern_id) { if (!is_pattern_valid(pattern_id)) - { - ESP_LOGW(TAG, "Invalid buzzer pattern id: %d", pattern_id); return; - } const buzzer_pattern_t *pattern = &buzzer_patterns[pattern_id]; + if (!pattern->steps || pattern->length == 0) + return; + + // Afinar aqui (para “premium”) + const uint32_t FADE_IN_MS = 10; + const uint32_t FADE_OUT_MS = 20; + + // Não limpar STOP aqui — se alguém chamou buzzer_stop(), queremos respeitar já. for (size_t i = 0; i < pattern->length; i++) { - buzzer_on(); - vTaskDelay(pdMS_TO_TICKS(pattern->steps[i].on_ms)); - buzzer_off(); - if (pattern->steps[i].off_ms > 0) + if (buzzer_should_stop()) + break; + + uint32_t on_ms = pattern->steps[i].on_ms; + uint32_t off_ms = pattern->steps[i].off_ms; + + if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE) { - vTaskDelay(pdMS_TO_TICKS(pattern->steps[i].off_ms)); + // Sempre começar em 0 para evitar "click" + buzzer_set_duty_pct(0); + + uint32_t fi = (on_ms > (FADE_IN_MS + FADE_OUT_MS)) ? FADE_IN_MS : (on_ms / 3); + uint32_t fo = (on_ms > (FADE_IN_MS + FADE_OUT_MS)) ? FADE_OUT_MS : (on_ms / 3); + uint32_t sustain = (on_ms > (fi + fo)) ? (on_ms - fi - fo) : 0; + + // Fade in (async) + espera em passos para STOP rápido + if (fi > 0) + buzzer_fade_to_pct_async(s_buzzer_cfg.duty_percent, fi); + for (uint32_t t = 0; t < fi; t += 10) + { + if (buzzer_should_stop()) + break; + vTaskDelay(pdMS_TO_TICKS(10)); + } + + // Sustain + for (uint32_t t = 0; t < sustain; t += 10) + { + if (buzzer_should_stop()) + break; + vTaskDelay(pdMS_TO_TICKS(10)); + } + + if (buzzer_should_stop()) + break; + + // Fade out (async) + espera em passos + if (fo > 0) + buzzer_fade_to_pct_async(0, fo); + for (uint32_t t = 0; t < fo; t += 10) + { + if (buzzer_should_stop()) + break; + vTaskDelay(pdMS_TO_TICKS(10)); + } + + buzzer_set_duty_pct(0); + } + else + { + // ACTIVE (liga/desliga simples) + gpio_set_level(s_buzzer_cfg.gpio, 1); + for (uint32_t t = 0; t < on_ms; t += 10) + { + if (buzzer_should_stop()) + break; + vTaskDelay(pdMS_TO_TICKS(10)); + } + gpio_set_level(s_buzzer_cfg.gpio, 0); + } + + if (buzzer_should_stop()) + break; + + for (uint32_t t = 0; t < off_ms; t += 10) + { + if (buzzer_should_stop()) + break; + vTaskDelay(pdMS_TO_TICKS(10)); } } + + buzzer_off(); } // ===================== Task & Fila ===================== @@ -260,30 +365,31 @@ static void buzzer_task(void *arg) { if (xQueueReceive(s_buzzer_q, &id, portMAX_DELAY) == pdTRUE) { - - ESP_LOGD(TAG, "dequeue pattern %d", id); // na buzzer_task() + ESP_LOGD(TAG, "dequeue pattern %d", id); if (!s_buzzer_ctl.enabled) continue; // Quiet hours: permitir apenas alertas críticos/negativos - if (in_quiet_hours()) + if (in_quiet_hours() && !is_critical_pattern(id)) + continue; + + // Anti-spam global (NÃO aplicar a padrões críticos) + if (!is_critical_pattern(id)) { - if (!(id == BUZZER_PATTERN_FAULT || id == BUZZER_PATTERN_CARD_DENIED)) - { + TickType_t now = xTaskGetTickCount(); + if ((now - s_last_play_tick) < s_min_gap_ticks) continue; - } + s_last_play_tick = now; } - // Anti-spam global - TickType_t now = xTaskGetTickCount(); - if ((now - s_last_play_tick) < s_min_gap_ticks) - { - continue; - } - s_last_play_tick = now; + // Se veio STOP pendente, limpamos antes de começar um novo padrão + buzzer_clear_stop(); buzzer_execute(id); + + // Se alguém pediu stop durante execução, já paramos; limpamos o bit. + buzzer_clear_stop(); } } } @@ -291,17 +397,32 @@ static void buzzer_task(void *arg) // ===================== API pública ===================== void buzzer_play(buzzer_pattern_id_t id) { + ESP_LOGD(TAG, "enqueue pattern %d", id); - ESP_LOGD(TAG, "enqueue pattern %d", id); // dentro de buzzer_play() - - if (!is_pattern_valid(id) || s_buzzer_q == NULL) + if (!s_inited || !is_pattern_valid(id) || s_buzzer_q == NULL) return; - (void)xQueueSend(s_buzzer_q, &id, 0); + + // Críticos: preempt + vão para a frente + if (is_critical_pattern(id)) + { + buzzer_stop(); + if (xQueueSendToFront(s_buzzer_q, &id, pdMS_TO_TICKS(10)) != pdTRUE) + ESP_LOGW(TAG, "buzzer queue full, dropped critical pattern=%d", id); + return; + } + + // Não-críticos: pequeno timeout + log se drop + if (xQueueSend(s_buzzer_q, &id, pdMS_TO_TICKS(10)) != pdTRUE) + ESP_LOGW(TAG, "buzzer queue full, dropped pattern=%d", id); } void buzzer_stop(void) { - // Interrompe imediatamente: esvazia fila e desliga + if (!s_inited) + return; + + // Interrompe imediatamente: sinaliza stop, esvazia fila e desliga + buzzer_set_stop(); if (s_buzzer_q) xQueueReset(s_buzzer_q); buzzer_off(); @@ -328,15 +449,22 @@ void buzzer_set_quiet_hours(bool enabled, uint16_t start_min, uint16_t end_min) s_buzzer_ctl.quiet_end_min = end_min; } -// Opcional: mudar tom dinamicamente (apenas se o timer for exclusivo do buzzer) -esp_err_t buzzer_set_frequency(uint32_t hz) +// ledc_set_freq normalmente devolve a frequência efetiva (0 se falhar) +uint32_t buzzer_set_frequency(uint32_t hz) { + if (!s_inited) + return 0; if (s_buzzer_cfg.mode != BUZZER_MODE_PASSIVE) - return ESP_ERR_INVALID_STATE; + return 0; if (hz < 50 || hz > 20000) - return ESP_ERR_INVALID_ARG; + return 0; + s_buzzer_cfg.freq_hz = hz; - return ledc_set_freq(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_timer, hz); + + uint32_t eff = ledc_set_freq(s_buzzer_cfg.ledc_speed_mode, s_buzzer_cfg.ledc_timer, hz); + if (eff == 0) + ESP_LOGW(TAG, "ledc_set_freq failed (hz=%lu)", (unsigned long)hz); + return eff; } void buzzer_set_duty_percent(uint8_t pct) @@ -382,7 +510,6 @@ static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, voi buzzer_evt.pattern = BUZZER_PATTERN_CHARGING; break; case EVSE_STATE_EVENT_FAULT: - // Preempta qualquer beep em andamento buzzer_stop(); buzzer_evt.pattern = BUZZER_PATTERN_FAULT; break; @@ -406,12 +533,9 @@ static void network_event_handler(void *handler_args, esp_event_base_t base, int { case NETWORK_EVENT_AP_STARTED: case NETWORK_EVENT_STA_CONNECTED: - // Usa padrão de AP_START para indicar rede disponível evt.pattern = BUZZER_PATTERN_AP_START; break; - default: - // Para já, ignorar outros eventos de rede return; } @@ -446,6 +570,12 @@ static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, voi // ===================== Inicialização / Deinit ===================== void buzzer_init(void) { + if (s_inited) + { + ESP_LOGW(TAG, "buzzer_init called twice"); + return; + } + // Config HW if (s_buzzer_cfg.mode == BUZZER_MODE_PASSIVE) { @@ -466,6 +596,11 @@ void buzzer_init(void) .duty = 0, .hpoint = 0}; ESP_ERROR_CHECK(ledc_channel_config(&ccfg)); + + // Evita crash se já estiver instalado (init repetido em app maior) + esp_err_t fe = ledc_fade_func_install(0); + if (fe != ESP_OK && fe != ESP_ERR_INVALID_STATE) + ESP_ERROR_CHECK(fe); } else { @@ -479,14 +614,14 @@ void buzzer_init(void) gpio_set_level(s_buzzer_cfg.gpio, 0); } - buzzer_set_quiet_hours(false, 0, 0); - buzzer_set_enabled(true); - - buzzer_off(); + // STOP event group + s_buzzer_eg = xEventGroupCreate(); + configASSERT(s_buzzer_eg); // Fila + Task s_buzzer_q = xQueueCreate(CONFIG_BUZZER_QUEUE_LEN, sizeof(buzzer_pattern_id_t)); configASSERT(s_buzzer_q != NULL); + BaseType_t ok = xTaskCreatePinnedToCore( buzzer_task, "buzzer_task", @@ -497,6 +632,13 @@ void buzzer_init(void) tskNO_AFFINITY); configASSERT(ok == pdPASS); + // Runtime defaults + buzzer_set_quiet_hours(false, 0, 0); + buzzer_set_enabled(true); + + s_inited = true; + buzzer_off(); + // Handlers ESP_ERROR_CHECK(esp_event_handler_register(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, buzzer_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler, NULL)); @@ -514,24 +656,39 @@ void buzzer_init(void) void buzzer_deinit(void) { + if (!s_inited) + return; + (void)esp_event_handler_unregister(BUZZER_EVENTS, BUZZER_EVENT_PLAY_PATTERN, buzzer_event_handler); (void)esp_event_handler_unregister(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, evse_event_handler); (void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_PROCESSED, auth_event_handler); (void)esp_event_handler_unregister(AUTH_EVENTS, AUTH_EVENT_TAG_SAVED, auth_event_handler); (void)esp_event_handler_unregister(NETWORK_EVENTS, ESP_EVENT_ANY_ID, network_event_handler); + // Para já + buzzer_stop(); + + // Mata task ANTES de apagar a queue (evita uso-after-free raro) + if (s_buzzer_task) + { + vTaskDelete(s_buzzer_task); + s_buzzer_task = NULL; + } + if (s_buzzer_q) { vQueueDelete(s_buzzer_q); s_buzzer_q = NULL; } - if (s_buzzer_task) + if (s_buzzer_eg) { - vTaskDelete(s_buzzer_task); - s_buzzer_task = NULL; + vEventGroupDelete(s_buzzer_eg); + s_buzzer_eg = NULL; } + s_inited = false; buzzer_off(); + ESP_LOGI(TAG, "Buzzer deinitialized"); -} +} \ No newline at end of file diff --git a/components/evse/evse_core.c b/components/evse/evse_core.c index 578dbf8..7432439 100755 --- a/components/evse/evse_core.c +++ b/components/evse/evse_core.c @@ -105,6 +105,7 @@ static void evse_process(void) if (evse_state_get_authorized()) { ESP_LOGW(TAG, "Charging limit reached → revoking authorization"); + evse_session_clear_authorized_tag(); evse_state_set_authorized(false); } } diff --git a/components/evse/evse_manager.c b/components/evse/evse_manager.c index aa74a05..1132f8c 100755 --- a/components/evse/evse_manager.c +++ b/components/evse/evse_manager.c @@ -30,6 +30,7 @@ static SemaphoreHandle_t evse_mutex; // ✅ Proteção para flags partilhadas (event handlers vs task) static portMUX_TYPE s_mgr_mux = portMUX_INITIALIZER_UNLOCKED; static bool auth_enabled = false; +static char s_pending_ocpp_tag[AUTH_TAG_MAX_LEN]; // Estado de pausa controlado pelo Load Balancer static bool lb_paused = false; @@ -41,6 +42,8 @@ static portMUX_TYPE s_sched_mux = portMUX_INITIALIZER_UNLOCKED; #define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo +static uint16_t s_sched_current_a = 0; + static void lb_clear_pause_state(void) { portENTER_CRITICAL(&s_mgr_mux); @@ -61,15 +64,15 @@ bool evse_sched_is_allowed(void) static void evse_manager_handle_auth_on_tick(void) { bool sched_allowed = evse_sched_is_allowed(); - uint32_t err_bits = evse_get_error(); // inclui holdoff interno - bool has_error = (err_bits != 0); + uint32_t err_bits = evse_get_error(); // inclui holdoff interno + bool has_error = (err_bits != 0); bool local_auth_enabled; bool local_lb_paused; portENTER_CRITICAL(&s_mgr_mux); local_auth_enabled = auth_enabled; - local_lb_paused = lb_paused; + local_lb_paused = lb_paused; portEXIT_CRITICAL(&s_mgr_mux); if (local_auth_enabled) @@ -77,6 +80,7 @@ static void evse_manager_handle_auth_on_tick(void) if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A) { ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization."); + evse_session_clear_authorized_tag(); evse_state_set_authorized(false); lb_clear_pause_state(); } @@ -86,6 +90,7 @@ static void evse_manager_handle_auth_on_tick(void) ESP_LOGI(TAG, "[AUTH] error active (err=0x%08" PRIx32 ") → revoking authorization.", err_bits); + evse_session_clear_authorized_tag(); evse_state_set_authorized(false); lb_clear_pause_state(); } @@ -93,12 +98,13 @@ static void evse_manager_handle_auth_on_tick(void) if (!sched_allowed && evse_state_get_authorized()) { ESP_LOGI(TAG, "[SCHED] window closed (auth mode) → revoking authorization."); + evse_session_clear_authorized_tag(); evse_state_set_authorized(false); } } else { - bool limit_hit = evse_is_limit_reached(); + bool limit_hit = evse_is_limit_reached(); bool can_operate = evse_config_is_available() && evse_config_is_enabled(); if ((has_error || limit_hit || !sched_allowed || !can_operate || local_lb_paused) && @@ -114,6 +120,7 @@ static void evse_manager_handle_auth_on_tick(void) !has_error && !limit_hit && !evse_state_get_authorized()) { + evse_session_clear_authorized_tag(); evse_state_set_authorized(true); ESP_LOGI(TAG, "Authentication disabled → forced authorization (schedule ok, no error/limits)."); lb_clear_pause_state(); @@ -134,7 +141,8 @@ static void evse_manager_task(void *arg) static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *data) { (void)arg; - if (base != AUTH_EVENTS || !data) return; + if (base != AUTH_EVENTS || !data) + return; switch (id) { @@ -142,11 +150,30 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da { const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)data; ESP_LOGI(TAG, "Tag %s -> %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED"); + if (evt->authorized) + { + evse_session_set_authorized_tag(evt->tag); + } + else + { + evse_session_clear_authorized_tag(); + } evse_state_set_authorized(evt->authorized); lb_clear_pause_state(); break; } + case AUTH_EVENT_TAG_VERIFY: + { + const auth_tag_verify_event_t *evt = (const auth_tag_verify_event_t *)data; + ESP_LOGI(TAG, "Tag %s -> pending remote/OCPP verification", evt->tag); + portENTER_CRITICAL(&s_mgr_mux); + strncpy(s_pending_ocpp_tag, evt->tag, AUTH_TAG_MAX_LEN - 1); + s_pending_ocpp_tag[AUTH_TAG_MAX_LEN - 1] = '\0'; + portEXIT_CRITICAL(&s_mgr_mux); + break; + } + case AUTH_EVENT_MODE_CHANGED: case AUTH_EVENT_INIT: { @@ -155,8 +182,10 @@ static void on_auth_event(void *arg, esp_event_base_t base, int32_t id, void *da portENTER_CRITICAL(&s_mgr_mux); auth_enabled = (evt->mode != AUTH_MODE_OPEN); + s_pending_ocpp_tag[0] = '\0'; portEXIT_CRITICAL(&s_mgr_mux); + evse_session_clear_authorized_tag(); evse_state_set_authorized(false); lb_clear_pause_state(); break; @@ -170,7 +199,8 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base (void)handler_arg; (void)event_base; - if (!event_data) return; + if (!event_data) + return; if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED) { @@ -211,7 +241,7 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base portENTER_CRITICAL(&s_mgr_mux); was_paused = lb_paused; - prev_auth = lb_prev_authorized; + prev_auth = lb_prev_authorized; portEXIT_CRITICAL(&s_mgr_mux); if (was_paused) @@ -263,27 +293,55 @@ static void on_loadbalancer_event(void *handler_arg, esp_event_base_t event_base static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *data) { (void)arg; - if (base != OCPP_EVENTS) return; + if (base != OCPP_EVENTS) + return; switch (id) { case OCPP_EVENT_AUTHORIZED: - ESP_LOGI(TAG, "[OCPP] Authorized"); + { + char tag[AUTH_TAG_MAX_LEN]; + + portENTER_CRITICAL(&s_mgr_mux); + strncpy(tag, s_pending_ocpp_tag, AUTH_TAG_MAX_LEN - 1); + tag[AUTH_TAG_MAX_LEN - 1] = '\0'; + s_pending_ocpp_tag[0] = '\0'; + portEXIT_CRITICAL(&s_mgr_mux); + + if (tag[0] != '\0') + { + evse_session_set_authorized_tag(tag); + ESP_LOGI(TAG, "[OCPP] Authorized tag=%s", tag); + } + else + { + ESP_LOGI(TAG, "[OCPP] Authorized"); + } + evse_state_set_authorized(true); lb_clear_pause_state(); break; + } case OCPP_EVENT_AUTH_REJECTED: case OCPP_EVENT_AUTH_TIMEOUT: case OCPP_EVENT_REMOTE_STOP: case OCPP_EVENT_STOP_TX: ESP_LOGW(TAG, "[OCPP] Authorization/Stop"); + portENTER_CRITICAL(&s_mgr_mux); + s_pending_ocpp_tag[0] = '\0'; + portEXIT_CRITICAL(&s_mgr_mux); + evse_session_clear_authorized_tag(); evse_state_set_authorized(false); lb_clear_pause_state(); break; case OCPP_EVENT_REMOTE_START: ESP_LOGI(TAG, "[OCPP] RemoteStart"); + portENTER_CRITICAL(&s_mgr_mux); + s_pending_ocpp_tag[0] = '\0'; + portEXIT_CRITICAL(&s_mgr_mux); + evse_session_clear_authorized_tag(); evse_state_set_authorized(true); lb_clear_pause_state(); break; @@ -317,21 +375,31 @@ static void on_ocpp_event(void *arg, esp_event_base_t base, int32_t id, void *da static void on_sched_event(void *arg, esp_event_base_t base, int32_t id, void *data) { (void)arg; - if (base != SCHED_EVENTS || data == NULL) return; + if (base != SCHED_EVENTS || data == NULL) + return; const sched_event_state_t *ev = (const sched_event_state_t *)data; portENTER_CRITICAL(&s_sched_mux); s_sched_allowed = ev->allowed_now; + s_sched_current_a = ev->current_limit_a; portEXIT_CRITICAL(&s_sched_mux); - ESP_LOGI(TAG, "[SCHED] allowed_now=%d", (int)ev->allowed_now); + ESP_LOGI(TAG, "[SCHED] allowed_now=%d current=%uA", + (int)ev->allowed_now, (unsigned)ev->current_limit_a); if (!ev->allowed_now && evse_state_get_authorized()) { ESP_LOGI(TAG, "[SCHED] window closed → stopping session (authorized=false)"); + evse_session_clear_authorized_tag(); evse_state_set_authorized(false); } + + // ✅ aplica corrente quando permitido + if (ev->allowed_now && ev->current_limit_a > 0) + { + evse_set_runtime_charging_current(ev->current_limit_a); + } } void evse_manager_init(void) diff --git a/components/evse/evse_session.c b/components/evse/evse_session.c index 6999554..20b15b9 100644 --- a/components/evse/evse_session.c +++ b/components/evse/evse_session.c @@ -1,4 +1,5 @@ #include +#include #include "evse_session.h" #include "evse_meter.h" #include "freertos/FreeRTOS.h" @@ -25,6 +26,8 @@ static uint64_t watt_microseconds = 0; static evse_session_t last_session; static bool last_session_valid = false; static uint32_t session_counter = 0; +static char authorized_tag[AUTH_TAG_MAX_LEN]; +static char current_session_tag[AUTH_TAG_MAX_LEN]; static portMUX_TYPE session_mux = portMUX_INITIALIZER_UNLOCKED; @@ -52,9 +55,31 @@ void evse_session_init(void) watt_microseconds = 0; last_session_valid = false; session_counter = 0; + authorized_tag[0] = '\0'; + current_session_tag[0] = '\0'; portEXIT_CRITICAL(&session_mux); } +void evse_session_set_authorized_tag(const char *tag) +{ + portENTER_CRITICAL(&session_mux); + if (tag && tag[0] != '\0') + { + strncpy(authorized_tag, tag, AUTH_TAG_MAX_LEN - 1); + authorized_tag[AUTH_TAG_MAX_LEN - 1] = '\0'; + } + else + { + authorized_tag[0] = '\0'; + } + portEXIT_CRITICAL(&session_mux); +} + +void evse_session_clear_authorized_tag(void) +{ + evse_session_set_authorized_tag(NULL); +} + void evse_session_start(void) { TickType_t tick = xTaskGetTickCount(); @@ -67,12 +92,17 @@ void evse_session_start(void) watt_microseconds = 0; session_counter++; uint32_t id = session_counter; + strncpy(current_session_tag, authorized_tag, AUTH_TAG_MAX_LEN - 1); + current_session_tag[AUTH_TAG_MAX_LEN - 1] = '\0'; + char tag[AUTH_TAG_MAX_LEN]; + strncpy(tag, current_session_tag, AUTH_TAG_MAX_LEN - 1); + tag[AUTH_TAG_MAX_LEN - 1] = '\0'; portEXIT_CRITICAL(&session_mux); evse_set_limit_reached(false); - ESP_LOGI(TAG, "Session started (id=%" PRIu32 ") tick=%u us=%" PRId64, - id, (unsigned)tick, now_us); + ESP_LOGI(TAG, "Session started (id=%" PRIu32 ", tag=%s) tick=%u us=%" PRId64, + id, tag[0] ? tag : "-", (unsigned)tick, now_us); evse_session_event_data_t evt = { .type = EVSE_SESSION_EVENT_STARTED, @@ -82,6 +112,8 @@ void evse_session_start(void) .avg_power_w = 0, .is_current = true, }; + strncpy(evt.tag, tag, AUTH_TAG_MAX_LEN - 1); + evt.tag[AUTH_TAG_MAX_LEN - 1] = '\0'; post_session_event(&evt); } @@ -91,6 +123,7 @@ void evse_session_end(void) int64_t start_us; uint64_t w_us; uint32_t id; + char tag[AUTH_TAG_MAX_LEN]; int64_t end_us = esp_timer_get_time(); @@ -106,11 +139,14 @@ void evse_session_end(void) start_us = session_start_us; w_us = watt_microseconds; id = session_counter; + strncpy(tag, current_session_tag, AUTH_TAG_MAX_LEN - 1); + tag[AUTH_TAG_MAX_LEN - 1] = '\0'; session_start_tick = 0; session_start_us = 0; last_tick_us = 0; watt_microseconds = 0; + current_session_tag[0] = '\0'; portEXIT_CRITICAL(&session_mux); uint32_t duration_s = (end_us > start_us) ? (uint32_t)((end_us - start_us) / 1000000LL) : 0; @@ -120,6 +156,9 @@ void evse_session_end(void) portENTER_CRITICAL(&session_mux); last_session.start_tick = start_tick; + last_session.session_id = id; + strncpy(last_session.tag, tag, AUTH_TAG_MAX_LEN - 1); + last_session.tag[AUTH_TAG_MAX_LEN - 1] = '\0'; last_session.duration_s = duration_s; last_session.energy_wh = energy_wh; last_session.avg_power_w = avg_power; @@ -129,8 +168,8 @@ void evse_session_end(void) ESP_LOGI(TAG, "Session ended (id=%" PRIu32 "): duration=%" PRIu32 " s, energy=%" PRIu32 - " Wh, avg_power=%" PRIu32 " W", - id, duration_s, energy_wh, avg_power); + " Wh, avg_power=%" PRIu32 " W, tag=%s", + id, duration_s, energy_wh, avg_power, tag[0] ? tag : "-"); evse_session_event_data_t evt = { .type = EVSE_SESSION_EVENT_FINISHED, @@ -140,6 +179,8 @@ void evse_session_end(void) .avg_power_w = avg_power, .is_current = false, }; + strncpy(evt.tag, tag, AUTH_TAG_MAX_LEN - 1); + evt.tag[AUTH_TAG_MAX_LEN - 1] = '\0'; post_session_event(&evt); } @@ -185,6 +226,8 @@ bool evse_session_get(evse_session_t *out) bool has_current; evse_session_t last_copy; bool last_valid; + uint32_t id; + char tag[AUTH_TAG_MAX_LEN]; int64_t now_us = esp_timer_get_time(); @@ -193,6 +236,9 @@ bool evse_session_get(evse_session_t *out) start_us = session_start_us; w_us = watt_microseconds; has_current = (session_start_tick != 0); + id = session_counter; + strncpy(tag, current_session_tag, AUTH_TAG_MAX_LEN - 1); + tag[AUTH_TAG_MAX_LEN - 1] = '\0'; last_copy = last_session; last_valid = last_session_valid; portEXIT_CRITICAL(&session_mux); @@ -205,6 +251,9 @@ bool evse_session_get(evse_session_t *out) uint32_t avg_power = (duration_s > 0) ? (uint32_t)(watt_seconds / duration_s) : 0; out->start_tick = start_tick; + out->session_id = id; + strncpy(out->tag, tag, AUTH_TAG_MAX_LEN - 1); + out->tag[AUTH_TAG_MAX_LEN - 1] = '\0'; out->duration_s = duration_s; out->energy_wh = energy_wh; out->avg_power_w = avg_power; diff --git a/components/evse/include/evse_events.h b/components/evse/include/evse_events.h index 3bb590f..afa4772 100755 --- a/components/evse/include/evse_events.h +++ b/components/evse/include/evse_events.h @@ -6,6 +6,7 @@ #include #include #include "esp_event.h" +#include "auth_types.h" ESP_EVENT_DECLARE_BASE(EVSE_EVENTS); @@ -45,6 +46,7 @@ typedef struct { evse_session_event_type_t type; ///< STARTED / FINISHED uint32_t session_id; + char tag[AUTH_TAG_MAX_LEN]; uint32_t duration_s; uint32_t energy_wh; uint32_t avg_power_w; diff --git a/components/evse/include/evse_session.h b/components/evse/include/evse_session.h index a8bb27e..fb9f3e7 100644 --- a/components/evse/include/evse_session.h +++ b/components/evse/include/evse_session.h @@ -4,19 +4,32 @@ #include #include #include "freertos/FreeRTOS.h" +#include "auth_types.h" /** * @brief Charging session statistics */ typedef struct { - TickType_t start_tick; ///< tick when session began (debug/trace) - uint32_t duration_s; ///< total duration in seconds (tempo real) - uint32_t energy_wh; ///< total energy consumed in Wh (tempo real) - uint32_t avg_power_w; ///< average power in W - bool is_current; ///< true if session still in progress + TickType_t start_tick; ///< tick when session began (debug/trace) + uint32_t session_id; ///< monotonically increasing session identifier + char tag[AUTH_TAG_MAX_LEN]; ///< RFID/auth tag associated with this session, empty if none + uint32_t duration_s; ///< total duration in seconds (tempo real) + uint32_t energy_wh; ///< total energy consumed in Wh (tempo real) + uint32_t avg_power_w; ///< average power in W + bool is_current; ///< true if session still in progress } evse_session_t; void evse_session_init(void); + +/** + * @brief Stores the tag that authorized the next charging session. + * + * The tag is copied into the session when evse_session_start() is called. + * Passing NULL or an empty string clears the pending tag. + */ +void evse_session_set_authorized_tag(const char *tag); +void evse_session_clear_authorized_tag(void); + void evse_session_start(void); void evse_session_end(void); diff --git a/components/led/src/led.c b/components/led/src/led.c index 57ca936..97187d5 100755 --- a/components/led/src/led.c +++ b/components/led/src/led.c @@ -234,7 +234,7 @@ static void led_apply_state_mode(evse_state_event_t state) { case EVSE_STATE_EVENT_IDLE: // IDLE → verde fixo (claro e visível) - led_apply_pattern(LED_ID_GREEN, LED_PATTERN_ON); + led_apply_pattern(LED_ID_BLUE, LED_PATTERN_ON); break; case EVSE_STATE_EVENT_WAITING: @@ -249,7 +249,7 @@ static void led_apply_state_mode(evse_state_event_t state) case EVSE_STATE_EVENT_FAULT: // FAULT → vermelho a piscar rápido - led_apply_pattern(LED_ID_RED, LED_PATTERN_BLINK_FAST); + led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST); break; default: diff --git a/components/meter_manager/driver/meter_modbus/meter_ea777.c b/components/meter_manager/driver/meter_modbus/meter_ea777.c index 54312b8..d946fc3 100755 --- a/components/meter_manager/driver/meter_modbus/meter_ea777.c +++ b/components/meter_manager/driver/meter_modbus/meter_ea777.c @@ -15,10 +15,10 @@ #define MB_PORT_NUM 1 #define MB_DEV_SPEED 9600 -// Ajuste os pinos conforme seu hardware -#define MB_UART_TXD 21 -#define MB_UART_RXD 22 -#define MB_UART_RTS UART_PIN_NO_CHANGE // sem DE/RE +// Ajuste os pinos conforme seu hardware (evite GPIO2 para RTS/DE/RE se possível) +#define MB_UART_TXD 17 +#define MB_UART_RXD 16 +#define MB_UART_RTS 2 // pino DE/RE do transceiver RS-485 // ===== Timings ===== #define UPDATE_INTERVAL (5000 / portTICK_PERIOD_MS) @@ -292,7 +292,6 @@ static void serial_mdb_ea777_task(void *param) } } -// ============ Init / Start / Stop ============ esp_err_t meter_ea777_init(void) { if (is_initialized) @@ -301,14 +300,12 @@ esp_err_t meter_ea777_init(void) return ESP_ERR_INVALID_STATE; } - // limpa UART se já houver driver if (uart_is_driver_installed(MB_PORT_NUM)) { uart_driver_delete(MB_PORT_NUM); ESP_LOGI(TAG, "UART driver deleted"); } - // destruir master anterior (ignora erro se não estiver init) (void)mbc_master_destroy(); mb_communication_info_t comm = { @@ -320,12 +317,14 @@ esp_err_t meter_ea777_init(void) void *handler = NULL; ESP_ERROR_CHECK(mbc_master_init(MB_PORT_SERIAL_MASTER, &handler)); ESP_ERROR_CHECK(mbc_master_setup(&comm)); + + // >>> RS-485 half duplex ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM, MB_UART_TXD, MB_UART_RXD, - UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE)); + MB_UART_RTS, UART_PIN_NO_CHANGE)); + ESP_ERROR_CHECK(mbc_master_start()); - ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_UART)); - // ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_UART)); + ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX)); vTaskDelay(pdMS_TO_TICKS(50)); ESP_ERROR_CHECK(mbc_master_set_descriptor(device_parameters_ea777, diff --git a/components/meter_manager/driver/meter_zigbee/meter_zigbee.c b/components/meter_manager/driver/meter_zigbee/meter_zigbee.c index 6e76dc4..d7d505e 100755 --- a/components/meter_manager/driver/meter_zigbee/meter_zigbee.c +++ b/components/meter_manager/driver/meter_zigbee/meter_zigbee.c @@ -179,7 +179,7 @@ static void handle_zigbee_frame(const uint8_t *buf, size_t len) } // Verifica se todas as 3 fases foram atualizadas - if (phase_updated[PHASE_L1] && phase_updated[PHASE_L2] && phase_updated[PHASE_L3]) + if (phase_updated[PHASE_L1] /*&& phase_updated[PHASE_L2] && phase_updated[PHASE_L3]*/) { meter_zigbee_post_event(); memset(phase_updated, 0, sizeof(phase_updated)); diff --git a/components/mongoose/.gitmodules b/components/mongoose/.gitmodules old mode 100755 new mode 100644 diff --git a/components/mongoose/CMakeLists.txt b/components/mongoose/CMakeLists.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/idf_component.yml b/components/mongoose/idf_component.yml old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/.clang-format b/components/mongoose/mongoose/.clang-format old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/LICENSE b/components/mongoose/mongoose/LICENSE old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/README.md b/components/mongoose/mongoose/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/README.md b/components/mongoose/mongoose/docs/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/images/mg_commalist.svg b/components/mongoose/mongoose/docs/images/mg_commalist.svg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/images/mg_http_get_request_len.svg b/components/mongoose/mongoose/docs/images/mg_http_get_request_len.svg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/images/mg_http_message.svg b/components/mongoose/mongoose/docs/images/mg_http_message.svg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/images/mg_http_next_multipart.svg b/components/mongoose/mongoose/docs/images/mg_http_next_multipart.svg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/images/mg_http_part.svg b/components/mongoose/mongoose/docs/images/mg_http_part.svg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/images/mg_http_reply.svg b/components/mongoose/mongoose/docs/images/mg_http_reply.svg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/images/mg_iobuf.svg b/components/mongoose/mongoose/docs/images/mg_iobuf.svg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/images/mg_iobuf_add1.svg b/components/mongoose/mongoose/docs/images/mg_iobuf_add1.svg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/images/mg_iobuf_add2.svg b/components/mongoose/mongoose/docs/images/mg_iobuf_add2.svg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/images/mg_iobuf_del.svg b/components/mongoose/mongoose/docs/images/mg_iobuf_del.svg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/docs/images/packed.svg b/components/mongoose/mongoose/docs/images/packed.svg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/captive-dns-server/README.md b/components/mongoose/mongoose/examples/captive-dns-server/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/captive-dns-server/main.c b/components/mongoose/mongoose/examples/captive-dns-server/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/device-dashboard/README.md b/components/mongoose/mongoose/examples/device-dashboard/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/device-dashboard/main.c b/components/mongoose/mongoose/examples/device-dashboard/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/device-dashboard/net.c b/components/mongoose/mongoose/examples/device-dashboard/net.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/device-dashboard/packed_fs.c b/components/mongoose/mongoose/examples/device-dashboard/packed_fs.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/device-dashboard/screenshots/dashboard.png b/components/mongoose/mongoose/examples/device-dashboard/screenshots/dashboard.png old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/device-dashboard/screenshots/login.png b/components/mongoose/mongoose/examples/device-dashboard/screenshots/login.png old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/device-dashboard/web_root/index.html b/components/mongoose/mongoose/examples/device-dashboard/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/device-dashboard/web_root/main.js b/components/mongoose/mongoose/examples/device-dashboard/web_root/main.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/device-dashboard/web_root/preact.min.js b/components/mongoose/mongoose/examples/device-dashboard/web_root/preact.min.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/device-dashboard/web_root/style.css b/components/mongoose/mongoose/examples/device-dashboard/web_root/style.css old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/device-dashboard/web_root/user.png b/components/mongoose/mongoose/examples/device-dashboard/web_root/user.png old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/embedded-filesystem/main.c b/components/mongoose/mongoose/examples/embedded-filesystem/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/embedded-filesystem/packed_fs.c b/components/mongoose/mongoose/examples/embedded-filesystem/packed_fs.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/embedded-filesystem/web_root/index.html b/components/mongoose/mongoose/examples/embedded-filesystem/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/embedded-filesystem/web_root/main.js b/components/mongoose/mongoose/examples/embedded-filesystem/web_root/main.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/embedded-filesystem/web_root/preact.min.js b/components/mongoose/mongoose/examples/embedded-filesystem/web_root/preact.min.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/embedded-filesystem/web_root/style.css b/components/mongoose/mongoose/examples/embedded-filesystem/web_root/style.css old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/device-dashboard/CMakeLists.txt b/components/mongoose/mongoose/examples/esp32/device-dashboard/CMakeLists.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/device-dashboard/README.md b/components/mongoose/mongoose/examples/esp32/device-dashboard/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/device-dashboard/main/CMakeLists.txt b/components/mongoose/mongoose/examples/esp32/device-dashboard/main/CMakeLists.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/device-dashboard/main/main.c b/components/mongoose/mongoose/examples/esp32/device-dashboard/main/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/device-dashboard/main/wifi.c b/components/mongoose/mongoose/examples/esp32/device-dashboard/main/wifi.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/device-dashboard/partitions.csv b/components/mongoose/mongoose/examples/esp32/device-dashboard/partitions.csv old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/uart-bridge/CMakeLists.txt b/components/mongoose/mongoose/examples/esp32/uart-bridge/CMakeLists.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/uart-bridge/README.md b/components/mongoose/mongoose/examples/esp32/uart-bridge/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/uart-bridge/main/CMakeLists.txt b/components/mongoose/mongoose/examples/esp32/uart-bridge/main/CMakeLists.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/uart-bridge/main/cli.c b/components/mongoose/mongoose/examples/esp32/uart-bridge/main/cli.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/uart-bridge/main/main.c b/components/mongoose/mongoose/examples/esp32/uart-bridge/main/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/uart-bridge/main/main.h b/components/mongoose/mongoose/examples/esp32/uart-bridge/main/main.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/uart-bridge/main/uart.c b/components/mongoose/mongoose/examples/esp32/uart-bridge/main/uart.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/uart-bridge/main/wifi.c b/components/mongoose/mongoose/examples/esp32/uart-bridge/main/wifi.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/uart-bridge/make b/components/mongoose/mongoose/examples/esp32/uart-bridge/make old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp32/uart-bridge/partitions.csv b/components/mongoose/mongoose/examples/esp32/uart-bridge/partitions.csv old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp8266/README.md b/components/mongoose/mongoose/examples/esp8266/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp8266/src/main/component.mk b/components/mongoose/mongoose/examples/esp8266/src/main/component.mk old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp8266/src/main/main.c b/components/mongoose/mongoose/examples/esp8266/src/main/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/esp8266/src/main/wifi.c b/components/mongoose/mongoose/examples/esp8266/src/main/wifi.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/file-upload-html-form/README.md b/components/mongoose/mongoose/examples/file-upload-html-form/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/file-upload-html-form/main.c b/components/mongoose/mongoose/examples/file-upload-html-form/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/file-upload-html-form/web_root/index.html b/components/mongoose/mongoose/examples/file-upload-html-form/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/file-upload-multiple-posts/README.md b/components/mongoose/mongoose/examples/file-upload-multiple-posts/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/file-upload-multiple-posts/main.c b/components/mongoose/mongoose/examples/file-upload-multiple-posts/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/file-upload-multiple-posts/web_root/app.js b/components/mongoose/mongoose/examples/file-upload-multiple-posts/web_root/app.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/file-upload-multiple-posts/web_root/index.html b/components/mongoose/mongoose/examples/file-upload-multiple-posts/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/file-upload-single-post/README.md b/components/mongoose/mongoose/examples/file-upload-single-post/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/file-upload-single-post/main.c b/components/mongoose/mongoose/examples/file-upload-single-post/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/file-upload-single-post/web_root/index.html b/components/mongoose/mongoose/examples/file-upload-single-post/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/http-client/README.md b/components/mongoose/mongoose/examples/http-client/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/http-client/main.c b/components/mongoose/mongoose/examples/http-client/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/http-proxy-client/main.c b/components/mongoose/mongoose/examples/http-proxy-client/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/http-restful-server/main.c b/components/mongoose/mongoose/examples/http-restful-server/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/http-restful-server/server.pem b/components/mongoose/mongoose/examples/http-restful-server/server.pem old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/http-reverse-proxy/main.c b/components/mongoose/mongoose/examples/http-reverse-proxy/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/http-server/README.md b/components/mongoose/mongoose/examples/http-server/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/http-server/main.c b/components/mongoose/mongoose/examples/http-server/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/http-streaming-client/main.c b/components/mongoose/mongoose/examples/http-streaming-client/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/huge-response/main.c b/components/mongoose/mongoose/examples/huge-response/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/huge-response/web_root/index.html b/components/mongoose/mongoose/examples/huge-response/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/json-rpc-over-websocket/README.md b/components/mongoose/mongoose/examples/json-rpc-over-websocket/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/json-rpc-over-websocket/main.c b/components/mongoose/mongoose/examples/json-rpc-over-websocket/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/json-rpc-over-websocket/web_root/index.html b/components/mongoose/mongoose/examples/json-rpc-over-websocket/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/json-rpc-over-websocket/web_root/rpc-over-websocket.js b/components/mongoose/mongoose/examples/json-rpc-over-websocket/web_root/rpc-over-websocket.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/live-log/main.c b/components/mongoose/mongoose/examples/live-log/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/live-log/web_root/index.html b/components/mongoose/mongoose/examples/live-log/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/mip-pcap/main.c b/components/mongoose/mongoose/examples/mip-pcap/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/mqtt-client-aws-iot/README.md b/components/mongoose/mongoose/examples/mqtt-client-aws-iot/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/mqtt-client-aws-iot/main.c b/components/mongoose/mongoose/examples/mqtt-client-aws-iot/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/mqtt-client/README.md b/components/mongoose/mongoose/examples/mqtt-client/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/mqtt-client/main.c b/components/mongoose/mongoose/examples/mqtt-client/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/mqtt-over-ws-client/main.c b/components/mongoose/mongoose/examples/mqtt-over-ws-client/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/mqtt-server/README.md b/components/mongoose/mongoose/examples/mqtt-server/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/mqtt-server/main.c b/components/mongoose/mongoose/examples/mqtt-server/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/multi-threaded/main.c b/components/mongoose/mongoose/examples/multi-threaded/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/sntp-time-sync/main.c b/components/mongoose/mongoose/examples/sntp-time-sync/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/socks5-server/main.c b/components/mongoose/mongoose/examples/socks5-server/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/README.md b/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/boot.c b/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/boot.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/link.ld b/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/link.ld old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/main.c b/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/mcu.h b/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/mcu.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/syscalls.c b/components/mongoose/mongoose/examples/stm32/nucleo-f746zg-baremetal/syscalls.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/Legacy/stm32_hal_legacy.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/Legacy/stm32_hal_legacy.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal.c b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_conf.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_conf.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_cortex.c b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_cortex.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_cortex.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_cortex.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_def.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_def.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_gpio.c b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_gpio.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_gpio.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_gpio.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_gpio_ex.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_gpio_ex.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_rcc.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_rcc.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_rcc_ex.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/HAL/stm32f7xx_hal_rcc_ex.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/README.md b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/main.c b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/openocd.cfg b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/openocd.cfg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/FreeRTOSConfig.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/FreeRTOSConfig.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/FreeRTOSIPConfig.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/FreeRTOSIPConfig.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/boot.s b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/boot.s old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/cmsis_gcc.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/cmsis_gcc.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/core_cm7.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/core_cm7.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/core_cmFunc.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/core_cmFunc.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/core_cmInstr.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/core_cmInstr.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/core_cmSimd.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/core_cmSimd.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/device.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/device.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/link.ld b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/link.ld old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/portmacro.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/portmacro.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/stm32f746xx.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/stm32f746xx.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/stm32f7xx.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/stm32f7xx.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/system_stm32f7xx.h b/components/mongoose/mongoose/examples/stm32/stm32-freertos-tcp/stm32f7/system_stm32f7xx.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/stm32/stm32-nucleo-h743z/README.md b/components/mongoose/mongoose/examples/stm32/stm32-nucleo-h743z/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/tcp/main.c b/components/mongoose/mongoose/examples/tcp/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/timers/main.c b/components/mongoose/mongoose/examples/timers/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/timers/web_root/index.html b/components/mongoose/mongoose/examples/timers/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/uart-bridge/README.md b/components/mongoose/mongoose/examples/uart-bridge/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/uart-bridge/main.c b/components/mongoose/mongoose/examples/uart-bridge/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/uart-bridge/net.c b/components/mongoose/mongoose/examples/uart-bridge/net.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/uart-bridge/packed_fs.c b/components/mongoose/mongoose/examples/uart-bridge/packed_fs.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/uart-bridge/screenshots/dashboard.png b/components/mongoose/mongoose/examples/uart-bridge/screenshots/dashboard.png old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/uart-bridge/web_root/index.html b/components/mongoose/mongoose/examples/uart-bridge/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/uart-bridge/web_root/main.js b/components/mongoose/mongoose/examples/uart-bridge/web_root/main.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/uart-bridge/web_root/preact.min.js b/components/mongoose/mongoose/examples/uart-bridge/web_root/preact.min.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/uart-bridge/web_root/style.css b/components/mongoose/mongoose/examples/uart-bridge/web_root/style.css old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/udp-ssdp-search/main.c b/components/mongoose/mongoose/examples/udp-ssdp-search/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/video-stream/images/1.jpg b/components/mongoose/mongoose/examples/video-stream/images/1.jpg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/video-stream/images/2.jpg b/components/mongoose/mongoose/examples/video-stream/images/2.jpg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/video-stream/images/3.jpg b/components/mongoose/mongoose/examples/video-stream/images/3.jpg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/video-stream/images/4.jpg b/components/mongoose/mongoose/examples/video-stream/images/4.jpg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/video-stream/images/5.jpg b/components/mongoose/mongoose/examples/video-stream/images/5.jpg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/video-stream/images/6.jpg b/components/mongoose/mongoose/examples/video-stream/images/6.jpg old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/video-stream/main.c b/components/mongoose/mongoose/examples/video-stream/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/video-stream/web_root/index.html b/components/mongoose/mongoose/examples/video-stream/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/websocket-client/README.md b/components/mongoose/mongoose/examples/websocket-client/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/websocket-client/main.c b/components/mongoose/mongoose/examples/websocket-client/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/websocket-server/README.md b/components/mongoose/mongoose/examples/websocket-server/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/websocket-server/main.c b/components/mongoose/mongoose/examples/websocket-server/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/websocket-server/test.html b/components/mongoose/mongoose/examples/websocket-server/test.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-login/main.c b/components/mongoose/mongoose/examples/webui-login/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-login/web_root/index.html b/components/mongoose/mongoose/examples/webui-login/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-login/web_root/main.js b/components/mongoose/mongoose/examples/webui-login/web_root/main.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-login/web_root/preact.min.js b/components/mongoose/mongoose/examples/webui-login/web_root/preact.min.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-login/web_root/style.css b/components/mongoose/mongoose/examples/webui-login/web_root/style.css old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-login/web_root/user.png b/components/mongoose/mongoose/examples/webui-login/web_root/user.png old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-plain/main.c b/components/mongoose/mongoose/examples/webui-plain/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-plain/web_root/index.html b/components/mongoose/mongoose/examples/webui-plain/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-plain/web_root/style.css b/components/mongoose/mongoose/examples/webui-plain/web_root/style.css old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-preact/web_root/index.html b/components/mongoose/mongoose/examples/webui-preact/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-preact/web_root/main.js b/components/mongoose/mongoose/examples/webui-preact/web_root/main.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-preact/web_root/preact.min.js b/components/mongoose/mongoose/examples/webui-preact/web_root/preact.min.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-preact/web_root/style.css b/components/mongoose/mongoose/examples/webui-preact/web_root/style.css old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-push-rest/main.c b/components/mongoose/mongoose/examples/webui-push-rest/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-push-rest/web_root/index.html b/components/mongoose/mongoose/examples/webui-push-rest/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-push-ws/main.c b/components/mongoose/mongoose/examples/webui-push-ws/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-push-ws/web_root/index.html b/components/mongoose/mongoose/examples/webui-push-ws/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-rest/main.c b/components/mongoose/mongoose/examples/webui-rest/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/webui-rest/web_root/index.html b/components/mongoose/mongoose/examples/webui-rest/web_root/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/http-client/CMakeLists.txt b/components/mongoose/mongoose/examples/zephyr/http-client/CMakeLists.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/http-client/prj.conf b/components/mongoose/mongoose/examples/zephyr/http-client/prj.conf old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/http-client/src/certs.h b/components/mongoose/mongoose/examples/zephyr/http-client/src/certs.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/http-client/src/main.c b/components/mongoose/mongoose/examples/zephyr/http-client/src/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/http-server/CMakeLists.txt b/components/mongoose/mongoose/examples/zephyr/http-server/CMakeLists.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/http-server/prj.conf b/components/mongoose/mongoose/examples/zephyr/http-server/prj.conf old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/http-server/src/certs.h b/components/mongoose/mongoose/examples/zephyr/http-server/src/certs.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/http-server/src/main.c b/components/mongoose/mongoose/examples/zephyr/http-server/src/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/mqtt-aws-client/CMakeLists.txt b/components/mongoose/mongoose/examples/zephyr/mqtt-aws-client/CMakeLists.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/mqtt-aws-client/prj.conf b/components/mongoose/mongoose/examples/zephyr/mqtt-aws-client/prj.conf old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/mqtt-aws-client/src/certs.h b/components/mongoose/mongoose/examples/zephyr/mqtt-aws-client/src/certs.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/mqtt-aws-client/src/main.c b/components/mongoose/mongoose/examples/zephyr/mqtt-aws-client/src/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/websocket-server/CMakeLists.txt b/components/mongoose/mongoose/examples/zephyr/websocket-server/CMakeLists.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/websocket-server/prj.conf b/components/mongoose/mongoose/examples/zephyr/websocket-server/prj.conf old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/websocket-server/src/certs.h b/components/mongoose/mongoose/examples/zephyr/websocket-server/src/certs.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/examples/zephyr/websocket-server/src/main.c b/components/mongoose/mongoose/examples/zephyr/websocket-server/src/main.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/mip/README.md b/components/mongoose/mongoose/mip/README.md old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/mip/driver_stm32.c b/components/mongoose/mongoose/mip/driver_stm32.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/mip/driver_w5500.h b/components/mongoose/mongoose/mip/driver_w5500.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/mip/mip.c b/components/mongoose/mongoose/mip/mip.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/mip/mip.h b/components/mongoose/mongoose/mip/mip.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/mongoose.c b/components/mongoose/mongoose/mongoose.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/mongoose.h b/components/mongoose/mongoose/mongoose.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch.h b/components/mongoose/mongoose/src/arch.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_azurertos.h b/components/mongoose/mongoose/src/arch_azurertos.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_esp32.h b/components/mongoose/mongoose/src/arch_esp32.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_esp8266.h b/components/mongoose/mongoose/src/arch_esp8266.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_freertos_lwip.h b/components/mongoose/mongoose/src/arch_freertos_lwip.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_freertos_tcp.h b/components/mongoose/mongoose/src/arch_freertos_tcp.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_newlib.h b/components/mongoose/mongoose/src/arch_newlib.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_rp2040.h b/components/mongoose/mongoose/src/arch_rp2040.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_rtx.h b/components/mongoose/mongoose/src/arch_rtx.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_rtx_lwip.h b/components/mongoose/mongoose/src/arch_rtx_lwip.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_tirtos.h b/components/mongoose/mongoose/src/arch_tirtos.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_unix.h b/components/mongoose/mongoose/src/arch_unix.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_win32.h b/components/mongoose/mongoose/src/arch_win32.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/arch_zephyr.h b/components/mongoose/mongoose/src/arch_zephyr.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/base64.c b/components/mongoose/mongoose/src/base64.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/base64.h b/components/mongoose/mongoose/src/base64.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/config.h b/components/mongoose/mongoose/src/config.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/dns.c b/components/mongoose/mongoose/src/dns.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/dns.h b/components/mongoose/mongoose/src/dns.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/event.c b/components/mongoose/mongoose/src/event.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/event.h b/components/mongoose/mongoose/src/event.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/fmt.c b/components/mongoose/mongoose/src/fmt.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/fmt.h b/components/mongoose/mongoose/src/fmt.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/fs.c b/components/mongoose/mongoose/src/fs.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/fs.h b/components/mongoose/mongoose/src/fs.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/fs_fat.c b/components/mongoose/mongoose/src/fs_fat.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/fs_packed.c b/components/mongoose/mongoose/src/fs_packed.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/fs_posix.c b/components/mongoose/mongoose/src/fs_posix.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/http.c b/components/mongoose/mongoose/src/http.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/http.h b/components/mongoose/mongoose/src/http.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/iobuf.c b/components/mongoose/mongoose/src/iobuf.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/iobuf.h b/components/mongoose/mongoose/src/iobuf.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/json.c b/components/mongoose/mongoose/src/json.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/json.h b/components/mongoose/mongoose/src/json.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/license.h b/components/mongoose/mongoose/src/license.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/log.c b/components/mongoose/mongoose/src/log.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/log.h b/components/mongoose/mongoose/src/log.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/md5.c b/components/mongoose/mongoose/src/md5.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/md5.h b/components/mongoose/mongoose/src/md5.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/mqtt.c b/components/mongoose/mongoose/src/mqtt.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/mqtt.h b/components/mongoose/mongoose/src/mqtt.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/net.c b/components/mongoose/mongoose/src/net.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/net.h b/components/mongoose/mongoose/src/net.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/rpc.c b/components/mongoose/mongoose/src/rpc.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/rpc.h b/components/mongoose/mongoose/src/rpc.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/sha1.c b/components/mongoose/mongoose/src/sha1.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/sha1.h b/components/mongoose/mongoose/src/sha1.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/sntp.c b/components/mongoose/mongoose/src/sntp.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/sntp.h b/components/mongoose/mongoose/src/sntp.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/sock.c b/components/mongoose/mongoose/src/sock.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/ssi.c b/components/mongoose/mongoose/src/ssi.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/ssi.h b/components/mongoose/mongoose/src/ssi.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/str.c b/components/mongoose/mongoose/src/str.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/str.h b/components/mongoose/mongoose/src/str.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/timer.c b/components/mongoose/mongoose/src/timer.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/timer.h b/components/mongoose/mongoose/src/timer.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/tls.h b/components/mongoose/mongoose/src/tls.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/tls_dummy.c b/components/mongoose/mongoose/src/tls_dummy.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/tls_mbed.c b/components/mongoose/mongoose/src/tls_mbed.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/tls_mbed.h b/components/mongoose/mongoose/src/tls_mbed.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/tls_openssl.c b/components/mongoose/mongoose/src/tls_openssl.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/tls_openssl.h b/components/mongoose/mongoose/src/tls_openssl.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/url.c b/components/mongoose/mongoose/src/url.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/url.h b/components/mongoose/mongoose/src/url.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/util.c b/components/mongoose/mongoose/src/util.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/util.h b/components/mongoose/mongoose/src/util.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/version.h b/components/mongoose/mongoose/src/version.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/ws.c b/components/mongoose/mongoose/src/ws.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/src/ws.h b/components/mongoose/mongoose/src/ws.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/..ddot/index.html b/components/mongoose/mongoose/test/data/..ddot/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/404.html b/components/mongoose/mongoose/test/data/404.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/a.txt b/components/mongoose/mongoose/test/data/a.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/ca.pem b/components/mongoose/mongoose/test/data/ca.pem old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/dredir/index.html b/components/mongoose/mongoose/test/data/dredir/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/dredirgz/index.html.gz b/components/mongoose/mongoose/test/data/dredirgz/index.html.gz old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/empty.js b/components/mongoose/mongoose/test/data/empty.js old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/hello.txt.gz b/components/mongoose/mongoose/test/data/hello.txt.gz old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/index.html b/components/mongoose/mongoose/test/data/index.html old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/range.txt b/components/mongoose/mongoose/test/data/range.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/secret/secret.txt b/components/mongoose/mongoose/test/data/secret/secret.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/ss_ca.pem b/components/mongoose/mongoose/test/data/ss_ca.pem old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/ss_client.pem b/components/mongoose/mongoose/test/data/ss_client.pem old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/ss_server.pem b/components/mongoose/mongoose/test/data/ss_server.pem old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/ssi/f1.txt b/components/mongoose/mongoose/test/data/ssi/f1.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/ssi/index.shtml b/components/mongoose/mongoose/test/data/ssi/index.shtml old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/ssi/nested.shtml b/components/mongoose/mongoose/test/data/ssi/nested.shtml old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/ssi/recurse.shtml b/components/mongoose/mongoose/test/data/ssi/recurse.shtml old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/ws.hex b/components/mongoose/mongoose/test/data/ws.hex old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/data/київ.txt b/components/mongoose/mongoose/test/data/київ.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/croutine.c b/components/mongoose/mongoose/test/freertos-kernel/croutine.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/event_groups.c b/components/mongoose/mongoose/test/freertos-kernel/event_groups.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/FreeRTOS.h b/components/mongoose/mongoose/test/freertos-kernel/include/FreeRTOS.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/StackMacros.h b/components/mongoose/mongoose/test/freertos-kernel/include/StackMacros.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/atomic.h b/components/mongoose/mongoose/test/freertos-kernel/include/atomic.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/croutine.h b/components/mongoose/mongoose/test/freertos-kernel/include/croutine.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/deprecated_definitions.h b/components/mongoose/mongoose/test/freertos-kernel/include/deprecated_definitions.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/event_groups.h b/components/mongoose/mongoose/test/freertos-kernel/include/event_groups.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/list.h b/components/mongoose/mongoose/test/freertos-kernel/include/list.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/message_buffer.h b/components/mongoose/mongoose/test/freertos-kernel/include/message_buffer.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/mpu_prototypes.h b/components/mongoose/mongoose/test/freertos-kernel/include/mpu_prototypes.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/mpu_wrappers.h b/components/mongoose/mongoose/test/freertos-kernel/include/mpu_wrappers.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/portable.h b/components/mongoose/mongoose/test/freertos-kernel/include/portable.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/projdefs.h b/components/mongoose/mongoose/test/freertos-kernel/include/projdefs.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/queue.h b/components/mongoose/mongoose/test/freertos-kernel/include/queue.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/semphr.h b/components/mongoose/mongoose/test/freertos-kernel/include/semphr.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/stack_macros.h b/components/mongoose/mongoose/test/freertos-kernel/include/stack_macros.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/stdint.readme b/components/mongoose/mongoose/test/freertos-kernel/include/stdint.readme old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/stream_buffer.h b/components/mongoose/mongoose/test/freertos-kernel/include/stream_buffer.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/task.h b/components/mongoose/mongoose/test/freertos-kernel/include/task.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/include/timers.h b/components/mongoose/mongoose/test/freertos-kernel/include/timers.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/list.c b/components/mongoose/mongoose/test/freertos-kernel/list.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/portable/GCC/ARM_CM7/ReadMe.txt b/components/mongoose/mongoose/test/freertos-kernel/portable/GCC/ARM_CM7/ReadMe.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/portable/GCC/ARM_CM7/r0p1/port.c b/components/mongoose/mongoose/test/freertos-kernel/portable/GCC/ARM_CM7/r0p1/port.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/portable/GCC/ARM_CM7/r0p1/portmacro.h b/components/mongoose/mongoose/test/freertos-kernel/portable/GCC/ARM_CM7/r0p1/portmacro.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/ReadMe.url b/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/ReadMe.url old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/heap_1.c b/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/heap_1.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/heap_2.c b/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/heap_2.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/heap_3.c b/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/heap_3.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/heap_4.c b/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/heap_4.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/heap_5.c b/components/mongoose/mongoose/test/freertos-kernel/portable/MemMang/heap_5.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/queue.c b/components/mongoose/mongoose/test/freertos-kernel/queue.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/stream_buffer.c b/components/mongoose/mongoose/test/freertos-kernel/stream_buffer.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/tasks.c b/components/mongoose/mongoose/test/freertos-kernel/tasks.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-kernel/timers.c b/components/mongoose/mongoose/test/freertos-kernel/timers.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_ARP.c b/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_ARP.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_DHCP.c b/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_DHCP.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_DNS.c b/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_DNS.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_IP.c b/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_IP.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_Sockets.c b/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_Sockets.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_Stream_Buffer.c b/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_Stream_Buffer.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_TCP_IP.c b/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_TCP_IP.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_TCP_WIN.c b/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_TCP_WIN.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_UDP_IP.c b/components/mongoose/mongoose/test/freertos-tcp/FreeRTOS_UDP_IP.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOSIPConfigDefaults.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOSIPConfigDefaults.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_ARP.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_ARP.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_DHCP.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_DHCP.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_DNS.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_DNS.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_IP.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_IP.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_IP_Private.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_IP_Private.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_Sockets.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_Sockets.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_Stream_Buffer.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_Stream_Buffer.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_TCP_IP.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_TCP_IP.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_TCP_WIN.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_TCP_WIN.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_UDP_IP.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_UDP_IP.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_errno_TCP.h b/components/mongoose/mongoose/test/freertos-tcp/include/FreeRTOS_errno_TCP.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/IPTraceMacroDefaults.h b/components/mongoose/mongoose/test/freertos-tcp/include/IPTraceMacroDefaults.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/NetworkBufferManagement.h b/components/mongoose/mongoose/test/freertos-tcp/include/NetworkBufferManagement.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/include/NetworkInterface.h b/components/mongoose/mongoose/test/freertos-tcp/include/NetworkInterface.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/BufferManagement/BufferAllocation_1.c b/components/mongoose/mongoose/test/freertos-tcp/portable/BufferManagement/BufferAllocation_1.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/BufferManagement/BufferAllocation_2.c b/components/mongoose/mongoose/test/freertos-tcp/portable/BufferManagement/BufferAllocation_2.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/CompilerName/ReadMe.txt b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/CompilerName/ReadMe.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/CompilerName/pack_struct_end.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/CompilerName/pack_struct_end.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/CompilerName/pack_struct_start.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/CompilerName/pack_struct_start.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/GCC/pack_struct_end.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/GCC/pack_struct_end.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/GCC/pack_struct_start.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/GCC/pack_struct_start.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/IAR/pack_struct_end.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/IAR/pack_struct_end.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/IAR/pack_struct_start.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/IAR/pack_struct_start.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/Keil/pack_struct_end.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/Keil/pack_struct_end.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/Keil/pack_struct_start.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/Keil/pack_struct_start.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/MSVC/pack_struct_end.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/MSVC/pack_struct_end.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/MSVC/pack_struct_start.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/MSVC/pack_struct_start.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/Renesas/pack_struct_end.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/Renesas/pack_struct_end.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/Renesas/pack_struct_start.h b/components/mongoose/mongoose/test/freertos-tcp/portable/Compiler/Renesas/pack_struct_start.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/Common/phyHandling.c b/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/Common/phyHandling.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/NetworkInterface.c b/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/NetworkInterface.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/readme.txt b/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/readme.txt old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/stm32f2xx_hal_eth.h b/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/stm32f2xx_hal_eth.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/stm32f4xx_hal_eth.h b/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/stm32f4xx_hal_eth.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/stm32f7xx_hal_eth.h b/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/stm32f7xx_hal_eth.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/stm32fxx_hal_eth.c b/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/stm32fxx_hal_eth.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/stm32fxx_hal_eth.h b/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/STM32Fxx/stm32fxx_hal_eth.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/include/phyHandling.h b/components/mongoose/mongoose/test/freertos-tcp/portable/NetworkInterface/include/phyHandling.h old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/fuzz.c b/components/mongoose/mongoose/test/fuzz.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/mongoose_custom.c b/components/mongoose/mongoose/test/mongoose_custom.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/pack.c b/components/mongoose/mongoose/test/pack.c old mode 100755 new mode 100644 diff --git a/components/mongoose/mongoose/test/unit_test.c b/components/mongoose/mongoose/test/unit_test.c old mode 100755 new mode 100644 diff --git a/components/ocpp/src/ocpp.c b/components/ocpp/src/ocpp.c index c9ff189..a48e363 100755 --- a/components/ocpp/src/ocpp.c +++ b/components/ocpp/src/ocpp.c @@ -1,4 +1,12 @@ // components/ocpp/src/ocpp.c +// Updated for newer MicroOcpp C-facade (init has ocpp201 flag, TxNotification type) +// +// Requested constraints / fixes: +// - In ocpp.c use ONLY storage_set_*_async (no storage_flush_* here) +// - Fix (2) ocpp_task_func must check enabled and "pause" loop when disabled +// - Fix (4) reduce race while ocpp_cleanup_internal runs (cut fast in handlers / stop flag) +// - Fix (5) don't call mg_mgr_free unless mg_mgr_init was called +// - Fix (7) validate energy conversion (NaN/Inf/negative, clamp), and handle unit kWh->Wh #include #include @@ -11,9 +19,11 @@ #include "esp_timer.h" #include "esp_event.h" #include "esp_wifi.h" +#include "esp_mac.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" +#include "freertos/event_groups.h" #include "ocpp.h" #include "ocpp_events.h" @@ -26,10 +36,10 @@ /* MicroOcpp includes */ #include -#include // C-facade of MicroOcpp -#include // WebSocket integration for ESP-IDF +#include "MicroOcpp_c.h" // C-facade of MicroOcpp +#include "MicroOcppMongooseClient_c.h" // WebSocket integration for ESP-IDF -// NEW storage layer +// Storage layer #include "storage_service.h" #define NVS_NAMESPACE "ocpp" @@ -39,34 +49,58 @@ static const char *TAG = "ocpp"; +/** + * Energy unit selection: + * - If meter_manager publishes total_energy in kWh, set to 1 + * - If it publishes in Wh, keep 0 + */ +#ifndef METER_TOTAL_ENERGY_IS_KWH +#define METER_TOTAL_ENERGY_IS_KWH 0 +#endif + +// Bounded timeout for esp_event_post from loop/callback paths +#define OCPP_EVENT_POST_TO pdMS_TO_TICKS(20) + +// Storage read timeouts (sync getters only) +#define STORE_TO pdMS_TO_TICKS(800) + +// Graceful stop wait (ocpp_stop) +#define OCPP_STOP_WAIT_TO pdMS_TO_TICKS(5000) + static bool enabled = false; +static bool s_ocpp201 = false; // keep false unless you really want OCPP 2.0.1 static TaskHandle_t ocpp_task = NULL; static struct mg_mgr mgr; // Event manager -static OCPP_Connection *g_ocpp_conn = NULL; // Para shutdown limpo +static bool s_mgr_inited = false; // FIX (5) +static OCPP_Connection *g_ocpp_conn = NULL; // For clean shutdown static esp_event_handler_instance_t s_auth_verify_inst = NULL; -// Flags refletindo o estado do EVSE (atualizadas por eventos) +// EVSE state flags (updated by events) static volatile bool s_ev_plugged = false; static volatile bool s_ev_ready = false; - static esp_event_handler_instance_t s_evse_state_inst = NULL; -// Flags de config (vindas de EVSE_EVENTS) -static volatile bool s_evse_enabled = true; -static volatile bool s_evse_available = true; - +// EVSE local config flags (from EVSE_EVENTS) +static volatile bool s_evse_enabled_local = true; +static volatile bool s_evse_available_local = true; static esp_event_handler_instance_t s_evse_enable_inst = NULL; static esp_event_handler_instance_t s_evse_available_inst = NULL; -// --- cache de medições vindas de METER_EVENT_DATA_READY --- +// Remote operative flag (ChangeAvailability) from OCPP +static volatile bool s_remote_operative = true; + +// Guard: while stopping/cleanup, handlers should exit fast (FIX 4) +static volatile bool s_stopping = false; + +// --- meter cache from METER_EVENT_DATA_READY --- typedef struct { float vrms[3]; float irms[3]; - int32_t watt[3]; // ativo por fase (W) + int32_t watt[3]; float frequency; float power_factor; @@ -83,140 +117,185 @@ static ocpp_meter_cache_t s_meter = {0}; static portMUX_TYPE s_meter_mux = portMUX_INITIALIZER_UNLOCKED; static esp_event_handler_instance_t s_meter_inst = NULL; -// valor de oferta (A por conector) +// Offered current (A) static float s_current_offered_A = 16.0f; - -static float getCurrentOffered(void) -{ - return s_current_offered_A; -} +static float getCurrentOffered(void) { return s_current_offered_A; } // ----------------------------------------------------------------------------- -// Storage helpers (robustos) +// Graceful stop control // ----------------------------------------------------------------------------- -#define STORE_TO pdMS_TO_TICKS(800) -#define STORE_FLUSH_TO pdMS_TO_TICKS(2000) +static EventGroupHandle_t s_ocpp_ctrl = NULL; +#define OCPP_CTRL_STOP_REQ BIT0 +#define OCPP_CTRL_STOPPED BIT1 -static void storage_init_best_effort(void) +// ----------------------------------------------------------------------------- +// Storage init (idempotent) +// ----------------------------------------------------------------------------- +static void storage_init_once(void) { + static bool s_inited = false; + if (s_inited) + return; + esp_err_t e = storage_service_init(); if (e != ESP_OK) + { ESP_LOGW(TAG, "storage_service_init failed: %s", esp_err_to_name(e)); -} - -static esp_err_t store_flush_best_effort(void) -{ - esp_err_t e = storage_flush_sync(STORE_FLUSH_TO); - if (e != ESP_OK) - ESP_LOGW(TAG, "storage_flush_sync failed: %s", esp_err_to_name(e)); - return e; -} - -static esp_err_t store_set_u8_best_effort(const char *ns, const char *key, uint8_t v) -{ - for (int attempt = 0; attempt < 3; ++attempt) - { - esp_err_t e = storage_set_u8_async(ns, key, v); - if (e == ESP_OK) - return ESP_OK; - - if (e == ESP_ERR_TIMEOUT) - { - (void)store_flush_best_effort(); - vTaskDelay(pdMS_TO_TICKS(10)); - continue; - } - return e; + return; } - return ESP_ERR_TIMEOUT; -} - -static esp_err_t store_set_str_best_effort(const char *ns, const char *key, const char *s) -{ - for (int attempt = 0; attempt < 3; ++attempt) - { - esp_err_t e = storage_set_str_async(ns, key, s ? s : ""); - if (e == ESP_OK) - return ESP_OK; - - if (e == ESP_ERR_TIMEOUT) - { - (void)store_flush_best_effort(); - vTaskDelay(portMAX_DELAY); - continue; - } - return e; - } - return ESP_ERR_TIMEOUT; -} - -// Lê string de forma segura (buffer grande -> truncagem segura para out) -static esp_err_t store_get_str_safe(const char *ns, const char *key, char *out, size_t out_sz) -{ - if (!out || out_sz == 0) - return ESP_ERR_INVALID_ARG; - - out[0] = '\0'; - - char tmp[STORAGE_MAX_VALUE_BYTES + 1]; - memset(tmp, 0, sizeof(tmp)); - - esp_err_t e = storage_get_str_sync(ns, key, tmp, sizeof(tmp), STORE_TO); - if (e == ESP_ERR_NOT_FOUND) - return ESP_OK; - if (e != ESP_OK) - return e; - - size_t n = strnlen(tmp, out_sz - 1); - memcpy(out, tmp, n); - out[n] = '\0'; - - return ESP_OK; + s_inited = true; } // ----------------------------------------------------------------------------- -// Task / Main Loop +// Energy helper (always store internally as Wh) + validation (FIX 7) // ----------------------------------------------------------------------------- -static void ocpp_task_func(void *param) +static inline float energy_to_wh(float v) { - (void)param; +#if METER_TOTAL_ENERGY_IS_KWH + return v * 1000.0f; +#else + return v; +#endif +} - while (true) - { - if (enabled) - { - mg_mgr_poll(&mgr, 100); - ocpp_loop(); +static inline float sanitize_energy_wh(float wh) +{ + if (!isfinite(wh) || wh < 0.0f) + return 0.0f; - bool operative = ocpp_isOperative(); - if (operative != s_evse_enabled) - { - s_evse_enabled = operative; + // Clamp very large values to a sane max to avoid float overflow downstream. + // Choose a conservative ceiling: 1e12 Wh = 1e9 kWh (way above any EVSE lifetime counter) + if (wh > 1.0e12f) + return 1.0e12f; - ocpp_operative_event_t ev = { - .operative = operative, - .timestamp_us = esp_timer_get_time()}; - esp_event_post(OCPP_EVENTS, - OCPP_EVENT_OPERATIVE_UPDATED, - &ev, sizeof(ev), - portMAX_DELAY); - - ESP_LOGI(TAG, "[OCPP] ChangeAvailability remoto → operative=%d", (int)operative); - } - } - else - { - vTaskDelay(pdMS_TO_TICKS(500)); - } - } + return wh; } // ----------------------------------------------------------------------------- -// Storage GETs +// BootNotification credentials builder (JSON for ocpp_initialize_full) +// ----------------------------------------------------------------------------- +static char s_boot_creds[320]; + +static void build_boot_creds_v16(char *out, size_t out_sz, + const char *model, const char *vendor, + const char *fw, const char *sn) +{ + snprintf(out, out_sz, + "{" + "\"chargePointModel\":\"%s\"," + "\"chargePointVendor\":\"%s\"," + "\"firmwareVersion\":\"%s\"," + "\"chargePointSerialNumber\":\"%s\"" + "}", + model ? model : "", + vendor ? vendor : "", + fw ? fw : "", + sn ? sn : ""); +} + +static void build_fw_sn(char *fw_out, size_t fw_sz, char *sn_out, size_t sn_sz) +{ + snprintf(fw_out, fw_sz, "FW-PLXV1.0"); + + uint8_t mac[6] = {0}; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + snprintf(sn_out, sn_sz, "%02X%02X%02X%02X%02X%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + +// ----------------------------------------------------------------------------- +// Cleanup (called from OCPP task for graceful stop or from stop fallback) +// FIX (4): set stopping/enabled/conn early so handlers bail out ASAP +// FIX (5): mg_mgr_free only if mgr_inited +// ----------------------------------------------------------------------------- +static void ocpp_cleanup_internal(void) +{ + ESP_LOGI(TAG, "OCPP cleanup"); + + // Cut fast for handlers + s_stopping = true; + enabled = false; + + // Ensure handlers see "not connected" ASAP + OCPP_Connection *conn = g_ocpp_conn; + g_ocpp_conn = NULL; + + ocpp_deinitialize(); + + if (conn) + { + ocpp_deinitConnection(conn); + } + + if (s_mgr_inited) + { + mg_mgr_free(&mgr); + s_mgr_inited = false; + } + + // Unregister handlers (best-effort) + if (s_auth_verify_inst) + { + esp_err_t e = esp_event_handler_instance_unregister( + AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY, s_auth_verify_inst); + if (e != ESP_OK) + ESP_LOGW(TAG, "unregister AUTH_EVENT_TAG_VERIFY failed: %s", esp_err_to_name(e)); + s_auth_verify_inst = NULL; + } + + if (s_evse_state_inst) + { + esp_err_t e = esp_event_handler_instance_unregister( + EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, s_evse_state_inst); + if (e != ESP_OK) + ESP_LOGW(TAG, "unregister EVSE_EVENT_STATE_CHANGED failed: %s", esp_err_to_name(e)); + s_evse_state_inst = NULL; + } + + if (s_evse_enable_inst) + { + esp_err_t e = esp_event_handler_instance_unregister( + EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED, s_evse_enable_inst); + if (e != ESP_OK) + ESP_LOGW(TAG, "unregister EVSE_EVENT_ENABLE_UPDATED failed: %s", esp_err_to_name(e)); + s_evse_enable_inst = NULL; + } + + if (s_evse_available_inst) + { + esp_err_t e = esp_event_handler_instance_unregister( + EVSE_EVENTS, EVSE_EVENT_AVAILABLE_UPDATED, s_evse_available_inst); + if (e != ESP_OK) + ESP_LOGW(TAG, "unregister EVSE_EVENT_AVAILABLE_UPDATED failed: %s", esp_err_to_name(e)); + s_evse_available_inst = NULL; + } + + if (s_meter_inst) + { + esp_err_t e = esp_event_handler_instance_unregister( + METER_EVENT, METER_EVENT_DATA_READY, s_meter_inst); + if (e != ESP_OK) + ESP_LOGW(TAG, "unregister METER_EVENT_DATA_READY failed: %s", esp_err_to_name(e)); + s_meter_inst = NULL; + } + + // Reset runtime flags/cache + s_remote_operative = true; + + portENTER_CRITICAL(&s_meter_mux); + memset(&s_meter, 0, sizeof(s_meter)); + portEXIT_CRITICAL(&s_meter_mux); + + // Allow future starts + s_stopping = false; +} + +// ----------------------------------------------------------------------------- +// Storage GETs (sync) - config read only // ----------------------------------------------------------------------------- bool ocpp_get_enabled(void) { - storage_init_best_effort(); + storage_init_once(); uint8_t value = 0; esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_OCPP_ENABLED, &value, STORE_TO); @@ -237,14 +316,21 @@ void ocpp_get_server(char *value /* out, size>=64 */) return; value[0] = '\0'; - storage_init_best_effort(); + storage_init_once(); - esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_SERVER, value, 64); + esp_err_t e = storage_get_str_sync(NVS_NAMESPACE, NVS_OCPP_SERVER, value, 64, STORE_TO); + if (e == ESP_ERR_NOT_FOUND) + { + value[0] = '\0'; + return; + } if (e != ESP_OK) { - ESP_LOGW(TAG, "store_get_str_safe(server) failed: %s", esp_err_to_name(e)); + ESP_LOGW(TAG, "storage_get_str_sync(server) failed: %s", esp_err_to_name(e)); value[0] = '\0'; + return; } + value[63] = '\0'; } void ocpp_get_charge_id(char *value /* out, size>=64 */) @@ -253,70 +339,77 @@ void ocpp_get_charge_id(char *value /* out, size>=64 */) return; value[0] = '\0'; - storage_init_best_effort(); + storage_init_once(); - esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value, 64); + esp_err_t e = storage_get_str_sync(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value, 64, STORE_TO); + if (e == ESP_ERR_NOT_FOUND) + { + value[0] = '\0'; + return; + } if (e != ESP_OK) { - ESP_LOGW(TAG, "store_get_str_safe(charge_id) failed: %s", esp_err_to_name(e)); + ESP_LOGW(TAG, "storage_get_str_sync(charge_id) failed: %s", esp_err_to_name(e)); value[0] = '\0'; + return; } + value[63] = '\0'; } // ----------------------------------------------------------------------------- -// Storage SETs +// Storage SETs (async only, per request) // ----------------------------------------------------------------------------- void ocpp_set_enabled(bool value) { - storage_init_best_effort(); + storage_init_once(); ESP_LOGI(TAG, "set enabled %d", value); - esp_err_t e = store_set_u8_best_effort(NVS_NAMESPACE, NVS_OCPP_ENABLED, value ? 1 : 0); + esp_err_t e = storage_set_u8_async(NVS_NAMESPACE, NVS_OCPP_ENABLED, value ? 1 : 0); if (e != ESP_OK) { - ESP_LOGE(TAG, "store_set_u8_best_effort(enabled) failed: %s", esp_err_to_name(e)); + ESP_LOGE(TAG, "storage_set_u8_async(enabled) failed: %s", esp_err_to_name(e)); return; } - (void)store_flush_best_effort(); - enabled = value; + // Apply immediately at runtime + if (value) + ocpp_start(); + else + ocpp_stop(); } void ocpp_set_server(char *value) { - storage_init_best_effort(); + storage_init_once(); ESP_LOGI(TAG, "set server %s", value ? value : "(null)"); - esp_err_t e = store_set_str_best_effort(NVS_NAMESPACE, NVS_OCPP_SERVER, value ? value : ""); + esp_err_t e = storage_set_str_async(NVS_NAMESPACE, NVS_OCPP_SERVER, value ? value : ""); if (e != ESP_OK) { - ESP_LOGE(TAG, "store_set_str_best_effort(server) failed: %s", esp_err_to_name(e)); + ESP_LOGE(TAG, "storage_set_str_async(server) failed: %s", esp_err_to_name(e)); return; } - - (void)store_flush_best_effort(); } void ocpp_set_charge_id(char *value) { - storage_init_best_effort(); + storage_init_once(); ESP_LOGI(TAG, "set charge_id %s", value ? value : "(null)"); - esp_err_t e = store_set_str_best_effort(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value ? value : ""); + esp_err_t e = storage_set_str_async(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value ? value : ""); if (e != ESP_OK) { - ESP_LOGE(TAG, "store_set_str_best_effort(charge_id) failed: %s", esp_err_to_name(e)); + ESP_LOGE(TAG, "storage_set_str_async(charge_id) failed: %s", esp_err_to_name(e)); return; } - - (void)store_flush_best_effort(); } // ----------------------------------------------------------------------------- // Event handlers (AUTH / EVSE / METER) +// FIX (4): bail out if stopping or not enabled // ----------------------------------------------------------------------------- static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, void *event_data) { @@ -324,6 +417,9 @@ static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, vo (void)base; (void)id; + if (s_stopping || !enabled || g_ocpp_conn == NULL) + return; + const auth_tag_verify_event_t *rq = (const auth_tag_verify_event_t *)event_data; if (!rq) return; @@ -342,22 +438,15 @@ static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, vo ESP_LOGI(TAG, "AUTH_EVENT_TAG_VERIFY: tag=%s req_id=%u", idtag, (unsigned)rq->req_id); - if (!enabled || g_ocpp_conn == NULL) - { - ESP_LOGW(TAG, "OCPP not ready (enabled=%d, conn=%p) – ignoring verify", - enabled, (void *)g_ocpp_conn); - return; - } - if (ocpp_isTransactionActive()) { - ESP_LOGI(TAG, "Transaction active -> ocpp_end_transaction(\"%s\")", idtag); - ocpp_endTransaction(idtag, "Local"); + ESP_LOGI(TAG, "Transaction active -> ocpp_endTransaction(\"%s\")", idtag); + (void)ocpp_endTransaction(idtag, "Local"); } else { - ESP_LOGI(TAG, "No active transaction -> ocpp_begin_transaction(\"%s\")", idtag); - ocpp_beginTransaction(idtag); + ESP_LOGI(TAG, "No active transaction -> ocpp_beginTransaction(\"%s\")", idtag); + (void)ocpp_beginTransaction(idtag); } } @@ -367,6 +456,9 @@ static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, voi if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL) return; + if (s_stopping) + return; + const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data; ESP_LOGI(TAG, "EVSE event received: state = %d", (int)evt->state); @@ -397,13 +489,17 @@ static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, voi static void evse_enable_available_handler(void *arg, esp_event_base_t base, int32_t id, void *data) { (void)arg; + if (base != EVSE_EVENTS || data == NULL) return; + if (s_stopping) + return; + if (id == EVSE_EVENT_ENABLE_UPDATED) { const evse_enable_event_data_t *e = (const evse_enable_event_data_t *)data; - s_evse_enabled = e->enabled; + s_evse_enabled_local = e->enabled; ESP_LOGI(TAG, "[EVSE] ENABLE_UPDATED: enabled=%d (ts=%lld)", (int)e->enabled, (long long)e->timestamp_us); return; @@ -412,7 +508,7 @@ static void evse_enable_available_handler(void *arg, esp_event_base_t base, int3 if (id == EVSE_EVENT_AVAILABLE_UPDATED) { const evse_available_event_data_t *e = (const evse_available_event_data_t *)data; - s_evse_available = e->available; + s_evse_available_local = e->available; ESP_LOGI(TAG, "[EVSE] AVAILABLE_UPDATED: available=%d (ts=%lld)", (int)e->available, (long long)e->timestamp_us); return; @@ -426,6 +522,9 @@ static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *d if (base != METER_EVENT || id != METER_EVENT_DATA_READY || !data) return; + if (s_stopping) + return; + const meter_event_data_t *evt = (const meter_event_data_t *)data; if (!evt->source || strcmp(evt->source, "EVSE") != 0) @@ -435,6 +534,8 @@ static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *d float avg_v = (evt->vrms[0] + evt->vrms[1] + evt->vrms[2]) / 3.0f; float sum_i = evt->irms[0] + evt->irms[1] + evt->irms[2]; + float total_wh = sanitize_energy_wh(energy_to_wh(evt->total_energy)); + portENTER_CRITICAL(&s_meter_mux); memcpy(s_meter.vrms, evt->vrms, sizeof(s_meter.vrms)); memcpy(s_meter.irms, evt->irms, sizeof(s_meter.irms)); @@ -443,7 +544,7 @@ static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *d s_meter.watt[2] = evt->watt[2]; s_meter.frequency = evt->frequency; s_meter.power_factor = evt->power_factor; - s_meter.total_energy_Wh = evt->total_energy; + s_meter.total_energy_Wh = total_wh; s_meter.sum_watt = sum_w; s_meter.avg_voltage = avg_v; s_meter.sum_current = sum_i; @@ -454,19 +555,16 @@ static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *d // ----------------------------------------------------------------------------- // MicroOCPP Inputs/CBs // ----------------------------------------------------------------------------- -bool setConnectorPluggedInput(void) -{ - return s_ev_plugged; -} - -bool setEvReadyInput(void) -{ - return s_ev_ready; -} +bool setConnectorPluggedInput(void) { return s_ev_plugged; } +bool setEvReadyInput(void) { return s_ev_ready; } bool setEvseReadyInput(void) { - return s_evse_enabled && s_evse_available; + // If OCPP disabled, EVSE not ready from OCPP perspective + if (!enabled || s_stopping || g_ocpp_conn == NULL) + return false; + + return s_evse_enabled_local && s_evse_available_local && s_remote_operative; } float setPowerMeterInput(void) @@ -480,11 +578,6 @@ float setPowerMeterInput(void) w = s_meter.sum_watt; portEXIT_CRITICAL(&s_meter_mux); - if (!have) - ESP_LOGW(TAG, "[METER] PowerMeterInput: no data (return 0)"); - else - ESP_LOGD(TAG, "[METER] PowerMeterInput: %" PRId32 " W", w); - return (float)w; } @@ -499,20 +592,20 @@ float setEnergyMeterInput(void) wh = s_meter.total_energy_Wh; portEXIT_CRITICAL(&s_meter_mux); - if (!have) - ESP_LOGW(TAG, "[METER] EnergyMeterInput: no data (return 0)"); - else - ESP_LOGD(TAG, "[METER] EnergyMeterInput: (%.1f Wh)", wh); - return wh; } int setEnergyInput(void) { float wh = setEnergyMeterInput(); - int wh_i = (int)lrintf((double)wh); - ESP_LOGD(TAG, "[METER] EnergyInput: %.1f Wh", wh); - return wh_i; + if (!isfinite(wh) || wh < 0.0f) + wh = 0.0f; + + // clamp to int range defensively + if (wh > 2147483000.0f) + wh = 2147483000.0f; + + return (int)lrintf((double)wh); } float setCurrentInput(void) @@ -526,11 +619,6 @@ float setCurrentInput(void) a = s_meter.sum_current; portEXIT_CRITICAL(&s_meter_mux); - if (!have) - ESP_LOGW(TAG, "[METER] CurrentInput: no data (return 0)"); - else - ESP_LOGD(TAG, "[METER] CurrentInput: %.2f A (total)", a); - return a; } @@ -545,55 +633,30 @@ float setVoltageInput(void) v = s_meter.avg_voltage; portEXIT_CRITICAL(&s_meter_mux); - if (!have) - ESP_LOGW(TAG, "[METER] VoltageInput: no data (return 0)"); - else - ESP_LOGD(TAG, "[METER] VoltageInput: %.1f V (avg)", v); - return v; } -float setPowerInput(void) -{ - float w = setPowerMeterInput(); - ESP_LOGD(TAG, "[METER] PowerInput: %.1f W", w); - return w; -} - float setTemperatureInput(void) { - ESP_LOGD(TAG, "TemperatureInput"); return 16.5f; } void setSmartChargingCurrentOutput(float limit) { ESP_LOGI(TAG, "SmartChargingCurrentOutput: %.0f", limit); + // TODO: apply to EVSE/loadbalancer } void setSmartChargingPowerOutput(float limit) { ESP_LOGI(TAG, "SmartChargingPowerOutput: %.0f", limit); + // TODO: apply to EVSE/loadbalancer } void setSmartChargingOutput(float power, float current, int nphases) { ESP_LOGI(TAG, "SmartChargingOutput: P=%.0f W, I=%.0f A, phases=%d", power, current, nphases); -} - -void setGetConfiguration(const char *payload, size_t len) -{ - ESP_LOGI(TAG, "GetConfiguration: %.*s (%u)", (int)len, payload, (unsigned)len); -} - -void setStartTransaction(const char *payload, size_t len) -{ - ESP_LOGI(TAG, "StartTransaction: %.*s (%u)", (int)len, payload, (unsigned)len); -} - -void setChangeConfiguration(const char *payload, size_t len) -{ - ESP_LOGI(TAG, "ChangeConfiguration: %.*s (%u)", (int)len, payload, (unsigned)len); + // TODO: apply to EVSE/loadbalancer } void OnResetExecute(bool state) @@ -602,83 +665,77 @@ void OnResetExecute(bool state) esp_restart(); } -bool setOccupiedInput(void) -{ - ESP_LOGD(TAG, "setOccupiedInput"); - return false; -} - -bool setStartTxReadyInput(void) -{ - ESP_LOGD(TAG, "setStartTxReadyInput"); - return true; -} - -bool setStopTxReadyInput(void) -{ - ESP_LOGD(TAG, "setStopTxReadyInput"); - return true; -} +bool setStopTxReadyInput(void) { return true; } bool setOnResetNotify(bool value) { - ESP_LOGI(TAG, "setOnResetNotify %d", value); + (void)value; return true; } -void notificationOutput(OCPP_Transaction *transaction, enum OCPP_TxNotification txNotification) +// TxNotification callback signature updated: uses TxNotification (new C-facade) +// TxNotification callback signature updated: uses TxNotification (new C-facade) +void notificationOutput(OCPP_Transaction *transaction, TxNotification txNotification) { (void)transaction; - ESP_LOGI(TAG, "TxNotification: %d", txNotification); + ESP_LOGI(TAG, "TxNotification: %d", (int)txNotification); switch (txNotification) { - case Authorized: + case TxNotification_Authorized: // == 1 ESP_LOGI(TAG, "Authorized"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, NULL, 0, portMAX_DELAY); + (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, NULL, 0, OCPP_EVENT_POST_TO); break; - case AuthorizationRejected: + case TxNotification_AuthorizationRejected: ESP_LOGI(TAG, "AuthorizationRejected"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, portMAX_DELAY); + (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, OCPP_EVENT_POST_TO); break; - case AuthorizationTimeout: + case TxNotification_AuthorizationTimeout: ESP_LOGI(TAG, "AuthorizationTimeout"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_TIMEOUT, NULL, 0, portMAX_DELAY); + (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_TIMEOUT, NULL, 0, OCPP_EVENT_POST_TO); break; - case ReservationConflict: + case TxNotification_ReservationConflict: ESP_LOGI(TAG, "ReservationConflict"); + // opcional: criar evento específico se precisares break; - case ConnectionTimeout: + case TxNotification_ConnectionTimeout: ESP_LOGI(TAG, "ConnectionTimeout"); + // opcional break; - case DeAuthorized: + case TxNotification_DeAuthorized: ESP_LOGI(TAG, "DeAuthorized"); + // opcional break; - case RemoteStart: + case TxNotification_RemoteStart: ESP_LOGI(TAG, "RemoteStart"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, NULL, 0, portMAX_DELAY); + (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, NULL, 0, OCPP_EVENT_POST_TO); break; - case RemoteStop: + case TxNotification_RemoteStop: ESP_LOGI(TAG, "RemoteStop"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_STOP, NULL, 0, portMAX_DELAY); + (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_STOP, NULL, 0, OCPP_EVENT_POST_TO); break; - case StartTx: + case TxNotification_StartTx: ESP_LOGI(TAG, "StartTx"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, NULL, 0, portMAX_DELAY); + (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, NULL, 0, OCPP_EVENT_POST_TO); break; - case StopTx: + case TxNotification_StopTx: ESP_LOGI(TAG, "StopTx"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, NULL, 0, portMAX_DELAY); + (void)esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, NULL, 0, OCPP_EVENT_POST_TO); + break; + + case TxNotification_UNDEFINED: + default: + ESP_LOGW(TAG, "TxNotification_UNDEFINED/Unknown: %d", (int)txNotification); break; } } @@ -713,6 +770,59 @@ const char *addErrorCodeInput(void) return ptr; } +// ----------------------------------------------------------------------------- +// Task / Main Loop +// FIX (2): pause when !enabled (no mg_mgr_poll/ocpp_loop while disabled) +// ----------------------------------------------------------------------------- +static void ocpp_task_func(void *param) +{ + (void)param; + + while (true) + { + if (s_ocpp_ctrl && (xEventGroupGetBits(s_ocpp_ctrl) & OCPP_CTRL_STOP_REQ)) + break; + + if (!enabled || s_stopping || g_ocpp_conn == NULL) + { + vTaskDelay(pdMS_TO_TICKS(250)); + continue; + } + + mg_mgr_poll(&mgr, 100); + ocpp_loop(); + + bool operative = ocpp_isOperative(); + if (operative != s_remote_operative) + { + s_remote_operative = operative; + + ocpp_operative_event_t ev = { + .operative = operative, + .timestamp_us = esp_timer_get_time()}; + (void)esp_event_post(OCPP_EVENTS, + OCPP_EVENT_OPERATIVE_UPDATED, + &ev, sizeof(ev), + OCPP_EVENT_POST_TO); + + ESP_LOGI(TAG, "[OCPP] ChangeAvailability remoto → operative=%d", (int)operative); + } + } + + ESP_LOGI(TAG, "OCPP stop requested (task)"); + + ocpp_cleanup_internal(); + + if (s_ocpp_ctrl) + { + xEventGroupClearBits(s_ocpp_ctrl, OCPP_CTRL_STOP_REQ); + xEventGroupSetBits(s_ocpp_ctrl, OCPP_CTRL_STOPPED); + } + + ocpp_task = NULL; + vTaskDelete(NULL); +} + // ----------------------------------------------------------------------------- // Start / Stop OCPP // ----------------------------------------------------------------------------- @@ -726,7 +836,7 @@ void ocpp_start(void) return; } - storage_init_best_effort(); + storage_init_once(); enabled = ocpp_get_enabled(); if (!enabled) @@ -740,6 +850,7 @@ void ocpp_start(void) if (serverstr[0] == '\0') { ESP_LOGW(TAG, "No OCPP server configured. Skipping connection."); + enabled = false; return; } @@ -748,10 +859,31 @@ void ocpp_start(void) if (charge_id[0] == '\0') { ESP_LOGW(TAG, "No OCPP charge_id configured. Skipping connection."); + enabled = false; return; } + if (!s_ocpp_ctrl) + { + s_ocpp_ctrl = xEventGroupCreate(); + if (!s_ocpp_ctrl) + { + ESP_LOGE(TAG, "Failed to create OCPP control event group"); + enabled = false; + return; + } + } + xEventGroupClearBits(s_ocpp_ctrl, OCPP_CTRL_STOP_REQ | OCPP_CTRL_STOPPED); + +#if METER_TOTAL_ENERGY_IS_KWH + ESP_LOGI(TAG, "Meter total_energy unit: kWh (converted to Wh)"); +#else + ESP_LOGI(TAG, "Meter total_energy unit: Wh"); +#endif + + // init mongoose manager mg_mgr_init(&mgr); + s_mgr_inited = true; mg_log_set(MG_LL_ERROR); struct OCPP_FilesystemOpt fsopt = {.use = true, .mount = true, .formatFsOnFail = true}; @@ -765,18 +897,25 @@ void ocpp_start(void) if (!g_ocpp_conn) { ESP_LOGE(TAG, "ocpp_makeConnection failed"); - mg_mgr_free(&mgr); + if (s_mgr_inited) + { + mg_mgr_free(&mgr); + s_mgr_inited = false; + } + enabled = false; return; } + // BootNotification credentials JSON (OCPP 1.6) + char fw[32], sn[32]; + build_fw_sn(fw, sizeof(fw), sn, sizeof(sn)); + build_boot_creds_v16(s_boot_creds, sizeof(s_boot_creds), + "EPower M1", "Plixin", fw, sn); - //chargePointModel: "EPower M1" - //chargePointVendor: "Plixin" - //firmwareVersion: "FW-PLXV1.0" - //chargePointSerialNumber: "SN001" - - ocpp_initialize(g_ocpp_conn, "EPower M1", "Plixin", fsopt, false); + // new signature includes ocpp201 flag + ocpp_initialize_full(g_ocpp_conn, s_boot_creds, fsopt, false, s_ocpp201); + // Inputs / Outputs ocpp_setEvReadyInput(&setEvReadyInput); ocpp_setEvseReadyInput(&setEvseReadyInput); ocpp_setConnectorPluggedInput(&setConnectorPluggedInput); @@ -796,8 +935,12 @@ void ocpp_start(void) ocpp_addErrorCodeInput(&addErrorCodeInput); - xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 3, &ocpp_task); + // SmartCharging outputs + ocpp_setSmartChargingCurrentOutput(&setSmartChargingCurrentOutput); + ocpp_setSmartChargingPowerOutput(&setSmartChargingPowerOutput); + ocpp_setSmartChargingOutput(&setSmartChargingOutput); + // Register event handlers before starting task if (!s_auth_verify_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_register( @@ -819,6 +962,7 @@ void ocpp_start(void) EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED, &evse_enable_available_handler, NULL, &s_evse_enable_inst)); } + if (!s_evse_available_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_register( @@ -833,58 +977,67 @@ void ocpp_start(void) &on_meter_event, NULL, &s_meter_inst)); ESP_LOGI(TAG, "Registered METER_EVENT_DATA_READY listener (EVSE source)"); } + + // Clear stopping flag for runtime + s_stopping = false; + + BaseType_t rc = xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 3, &ocpp_task); + if (rc != pdPASS) + { + ESP_LOGE(TAG, "Failed to create ocpp_task"); + ocpp_task = NULL; + ocpp_cleanup_internal(); + if (s_ocpp_ctrl) + xEventGroupSetBits(s_ocpp_ctrl, OCPP_CTRL_STOPPED); + return; + } } void ocpp_stop(void) { ESP_LOGI(TAG, "Stopping OCPP"); - if (ocpp_task) + // Set stopping early so handlers stop doing work ASAP + s_stopping = true; + enabled = false; + + if (ocpp_task == NULL) { + // Defensive: if conn exists without task, cleanup now + if (g_ocpp_conn != NULL || s_mgr_inited) + { + ESP_LOGW(TAG, "ocpp_task NULL but resources exist; forcing cleanup"); + ocpp_cleanup_internal(); + } + return; + } + + if (!s_ocpp_ctrl) + { + // No control group -> fallback (rare) + ESP_LOGW(TAG, "No control group; forcing stop"); vTaskDelete(ocpp_task); ocpp_task = NULL; + ocpp_cleanup_internal(); + return; } - ocpp_deinitialize(); + // Graceful stop: ask task to stop and wait it to finish cleanup + xEventGroupClearBits(s_ocpp_ctrl, OCPP_CTRL_STOPPED); + xEventGroupSetBits(s_ocpp_ctrl, OCPP_CTRL_STOP_REQ); - if (g_ocpp_conn) - { - ocpp_deinitConnection(g_ocpp_conn); - g_ocpp_conn = NULL; - } + EventBits_t bits = xEventGroupWaitBits( + s_ocpp_ctrl, + OCPP_CTRL_STOPPED, + pdTRUE, + pdTRUE, + OCPP_STOP_WAIT_TO); - mg_mgr_free(&mgr); - - if (s_auth_verify_inst) + if (!(bits & OCPP_CTRL_STOPPED)) { - ESP_ERROR_CHECK(esp_event_handler_instance_unregister( - AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY, s_auth_verify_inst)); - s_auth_verify_inst = NULL; - } - - if (s_evse_state_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_unregister( - EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, s_evse_state_inst)); - s_evse_state_inst = NULL; - } - - if (s_evse_enable_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_unregister( - EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED, s_evse_enable_inst)); - s_evse_enable_inst = NULL; - } - if (s_evse_available_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_unregister( - EVSE_EVENTS, EVSE_EVENT_AVAILABLE_UPDATED, s_evse_available_inst)); - s_evse_available_inst = NULL; - } - if (s_meter_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_unregister( - METER_EVENT, METER_EVENT_DATA_READY, s_meter_inst)); - s_meter_inst = NULL; + ESP_LOGW(TAG, "Timeout waiting OCPP stop; forcing task delete"); + vTaskDelete(ocpp_task); + ocpp_task = NULL; + ocpp_cleanup_internal(); } } diff --git a/components/peripherals/src/ntc_sensor.c b/components/peripherals/src/ntc_sensor.c index 7981127..d7e5d48 100755 --- a/components/peripherals/src/ntc_sensor.c +++ b/components/peripherals/src/ntc_sensor.c @@ -45,7 +45,7 @@ void ntc_sensor_init(void) ntc_config_t ntc_config = { .b_value = 3950, .r25_ohm = 10000, - .fixed_ohm = 4700, + .fixed_ohm = 11000, .vdd_mv = 3300, .circuit_mode = CIRCUIT_MODE_NTC_GND, .atten = ADC_ATTEN_DB_12, diff --git a/components/protocols/include/mqtt.h b/components/protocols/include/mqtt.h index f858bf8..76106bf 100755 --- a/components/protocols/include/mqtt.h +++ b/components/protocols/include/mqtt.h @@ -2,7 +2,8 @@ #pragma once #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif #include @@ -10,106 +11,79 @@ extern "C" { #include "esp_err.h" // Tamanhos máximos esperados pelos getters (buffers do chamador) -#define MQTT_SERVER_MAX_LEN 64 ///< host ou URI do broker -#define MQTT_BASE_TOPIC_MAX_LEN 64 ///< tópico base (ex: "evse") -#define MQTT_USERNAME_MAX_LEN 32 -#define MQTT_PASSWORD_MAX_LEN 64 +#define MQTT_SERVER_MAX_LEN 64 ///< host ou URI do broker +#define MQTT_BASE_TOPIC_MAX_LEN 64 ///< tópico base (ex: "evse") +#define MQTT_USERNAME_MAX_LEN 32 +#define MQTT_PASSWORD_MAX_LEN 64 -/** - * @brief Inicializa o módulo MQTT. - * - * - Carrega configuração da NVS - * - Regista handlers de eventos (AUTH, SCHED, LOADBALANCER, METER, EVSE, NETWORK) - * - Cria a task de telemetria periódica - * - * Não inicia a ligação ao broker imediatamente; isso acontece quando existir IP - * (NETWORK_EVENT_STA_GOT_IP) e `enabled == true`. - */ -esp_err_t mqtt_init(void); + // ============================= + // Limit modes (cmd/evse/limit) + // ============================= + typedef enum + { + LIMIT_MODE_OFF = 0, ///< sem limite, usa max do hardware + LIMIT_MODE_MANUAL, ///< corrente fixa definida pelo utilizador + LIMIT_MODE_LOADBALANCER, ///< limita pelo contador da instalação + LIMIT_MODE_SOLAR ///< só com excedente FV + } limit_mode_t; -/** - * @brief Força tentativa de ligação ao broker (normalmente não é necessário chamar). - * - * Útil apenas se quiseres forçar manualmente depois de mudares config. - */ -esp_err_t mqtt_start(void); + /** + * @brief Inicializa o módulo MQTT. + * + * - Carrega configuração da NVS + * - Regista handlers de eventos (AUTH, SCHED, LOADBALANCER, METER, EVSE, NETWORK) + * - Cria a task de telemetria periódica + * - Aplica o modo de limite persistido + * + * Não inicia a ligação ao broker imediatamente; isso acontece quando existir IP + * (NETWORK_EVENT_STA_GOT_IP) e `enabled == true`. + * + * IMPORTANTE: chamar DEPOIS de loadbalancer_init() para o restauro do modo + * de limite funcionar correctamente. + */ + esp_err_t mqtt_init(void); -/** - * @brief Pára o cliente MQTT e destrói o handle. - */ -void mqtt_stop(void); + /** + * @brief Força tentativa de ligação ao broker (normalmente não é necessário chamar). + */ + esp_err_t mqtt_start(void); -// ============================= -// Getters de configuração -// ============================= + /** + * @brief Pára o cliente MQTT e destrói o handle. + */ + void mqtt_stop(void); -/** - * @brief Indica se o MQTT está ativo (flag em config). - */ -bool mqtt_get_enabled(void); + // ============================= + // Getters de configuração + // ============================= -/** - * @brief Obtém o servidor MQTT (host ou URI). - * - * @param server buffer de saída, com pelo menos MQTT_SERVER_MAX_LEN bytes. - */ -void mqtt_get_server(char *server); + bool mqtt_get_enabled(void); + void mqtt_get_server(char *server); + void mqtt_get_base_topic(char *base_topic); + void mqtt_get_user(char *username); + void mqtt_get_password(char *password); + uint16_t mqtt_get_periodicity(void); -/** - * @brief Obtém o tópico base (ex.: "evse"). - * - * @param base_topic buffer de saída, com pelo menos MQTT_BASE_TOPIC_MAX_LEN bytes. - */ -void mqtt_get_base_topic(char *base_topic); + // ============================= + // Setter de configuração + // ============================= -/** - * @brief Obtém o username MQTT (se configurado). - * - * @param username buffer de saída, com pelo menos MQTT_USERNAME_MAX_LEN bytes. - */ -void mqtt_get_user(char *username); - -/** - * @brief Obtém a password MQTT (se configurada). - * - * @param password buffer de saída, com pelo menos MQTT_PASSWORD_MAX_LEN bytes. - */ -void mqtt_get_password(char *password); - -/** - * @brief Obtém a periodicidade da telemetria periódica (em segundos). - */ -uint16_t mqtt_get_periodicity(void); - -// ============================= -// Setter de configuração -// ============================= - -/** - * @brief Atualiza a configuração MQTT (tipicamente chamado pelo endpoint REST). - * - * - Grava em NVS - * - Se já existir cliente ligado, pára e recria com a nova config - * - Se `enabled == true` e houver IP, tenta ligar ao broker - * - * @param enabled true para ativar MQTT - * @param host hostname ou URI (ex: "broker.hivemq.com" ou "mqtt://x.y.z") - * @param topic tópico base (ex: "evse") - * @param username username MQTT (ou NULL para manter/limpar) - * @param password password MQTT (ou NULL para manter/limpar) - * @param periodicity período da task de telemetria em segundos (ex: 30) - * - * @return ESP_OK em caso de sucesso, ou erro de NVS / MQTT. - */ -esp_err_t mqtt_set_config(bool enabled, - const char *host, - const char *topic, - const char *username, - const char *password, - int periodicity); + /** + * @brief Atualiza a configuração MQTT (tipicamente chamado pelo endpoint REST). + * + * - Grava em NVS + * - Se já existir cliente ligado, pára e recria com a nova config + * - Se `enabled == true` e houver IP, tenta ligar ao broker + */ + esp_err_t mqtt_set_config(bool enabled, + const char *host, + const char *topic, + const char *username, + const char *password, + int periodicity); #ifdef __cplusplus } #endif -// === Fim de: components/mqtt/include/mqtt.h === +// === Fim de: components/mqtt/include/mqtt.h === \ No newline at end of file diff --git a/components/protocols/src/mqtt.c b/components/protocols/src/mqtt.c index 332c62d..b540142 100755 --- a/components/protocols/src/mqtt.c +++ b/components/protocols/src/mqtt.c @@ -1,5 +1,3 @@ -// === Início de: components/mqtt/src/mqtt.c === - #include "mqtt.h" #include @@ -12,6 +10,9 @@ #include "esp_system.h" #include "esp_event.h" #include "esp_timer.h" +#include "esp_wifi.h" +#include "esp_netif.h" +#include "esp_mac.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" @@ -19,10 +20,8 @@ #include "mqtt_client.h" #include "cJSON.h" -// --- storage service (NVS-backed, debounced, async) --- #include "storage_service.h" -// --- módulos da aplicação --- #include "network_events.h" #include "auth.h" @@ -44,14 +43,11 @@ #include "evse_limits.h" #include "evse_error.h" #include "evse_events.h" - -// -------------------------------------------------- -// Config / estado interno -// -------------------------------------------------- +#include "evse_session.h" +#include "evse_meter.h" static const char *TAG = "mqtt"; -// Storage namespace/keys (mantém os mesmos nomes) #define MQTT_NVS_NAMESPACE "mqttcfg" #define MQTT_KEY_ENABLED "enabled" #define MQTT_KEY_HOST "host" @@ -59,6 +55,7 @@ static const char *TAG = "mqtt"; #define MQTT_KEY_USER "user" #define MQTT_KEY_PASS "pass" #define MQTT_KEY_PERIOD "period" +#define MQTT_KEY_LIMITMODE "limitmode" #define MQTT_HOST_MAX_LEN 64 #define MQTT_TOPIC_MAX_LEN 64 @@ -66,27 +63,49 @@ static const char *TAG = "mqtt"; #define MQTT_PASS_MAX_LEN 64 #define MQTT_TOPIC_BUF_LEN 128 +#define MQTT_CLIENT_ID_MAX_LEN 32 +#define FW_VERSION "1.0" -// Config atual em RAM static bool s_mqtt_enabled = false; -static char s_mqtt_host[MQTT_HOST_MAX_LEN] = {0}; // host ou URI -static char s_mqtt_base_topic[MQTT_TOPIC_MAX_LEN] = "evse"; // default +static char s_mqtt_host[MQTT_HOST_MAX_LEN] = {0}; +static char s_mqtt_base_topic[MQTT_TOPIC_MAX_LEN] = "evse"; static char s_mqtt_user[MQTT_USER_MAX_LEN] = {0}; static char s_mqtt_pass[MQTT_PASS_MAX_LEN] = {0}; -static uint16_t s_mqtt_periodicity = 30; // segundos +static uint16_t s_mqtt_periodicity = 30; + +static limit_mode_t s_limit_mode = LIMIT_MODE_OFF; +static char s_client_id[MQTT_CLIENT_ID_MAX_LEN] = {0}; +static bool s_session_was_active = false; -// Cliente MQTT static esp_mqtt_client_handle_t s_client = NULL; static bool s_connected = false; - -// Task de telemetria periódica static TaskHandle_t s_telemetry_task = NULL; static inline TickType_t TO_TICKS_MS(uint32_t ms) { return pdMS_TO_TICKS(ms); } -// -------------------------------------------------- -// Helpers STORAGE (substitui NVS direto) -// -------------------------------------------------- +static void mqtt_auth_publish_state(void); +static void mqtt_scheduler_publish_state(void); +static void mqtt_loadbalancer_publish_state(void); +static void mqtt_meters_publish_config(void); +static void mqtt_meters_publish_live(const meter_event_data_t *m, int64_t ts_us); +static void mqtt_evse_publish_state(void); +static void mqtt_limit_publish_state(void); +static void mqtt_session_publish_state(void); +static void mqtt_system_publish_heartbeat(void); + +static void mqtt_apply_limit_mode(limit_mode_t mode); +static const char *limit_mode_to_str(limit_mode_t m); +static bool limit_mode_from_str(const char *s, limit_mode_t *out); + +static void mqtt_subscribe_all(void); +static void mqtt_dispatch_command(const char *topic, const char *data); + +static void mqtt_auth_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data); +static void mqtt_scheduler_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data); +static void mqtt_loadbalancer_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data); +static void mqtt_meter_event_handler(void *arg, esp_event_base_t base, int32_t id, void *ev_data); +static void mqtt_evse_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data); +static void mqtt_network_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data); static void mqtt_load_config_from_storage(void) { @@ -99,7 +118,6 @@ static void mqtt_load_config_from_storage(void) bool needs_flush = false; - // enabled { uint8_t u8 = 0; err = storage_get_u8_sync(MQTT_NVS_NAMESPACE, MQTT_KEY_ENABLED, &u8, TO_TICKS_MS(500)); @@ -113,7 +131,6 @@ static void mqtt_load_config_from_storage(void) } } - // host { char tmp[MQTT_HOST_MAX_LEN] = {0}; err = storage_get_str_sync(MQTT_NVS_NAMESPACE, MQTT_KEY_HOST, tmp, sizeof(tmp), TO_TICKS_MS(500)); @@ -125,13 +142,11 @@ static void mqtt_load_config_from_storage(void) else { s_mqtt_host[0] = '\0'; - // opcional: persistir vazio (para chave existir) (void)storage_set_str_async(MQTT_NVS_NAMESPACE, MQTT_KEY_HOST, ""); needs_flush = true; } } - // topic { char tmp[MQTT_TOPIC_MAX_LEN] = {0}; err = storage_get_str_sync(MQTT_NVS_NAMESPACE, MQTT_KEY_TOPIC, tmp, sizeof(tmp), TO_TICKS_MS(500)); @@ -149,7 +164,6 @@ static void mqtt_load_config_from_storage(void) } } - // user { char tmp[MQTT_USER_MAX_LEN] = {0}; err = storage_get_str_sync(MQTT_NVS_NAMESPACE, MQTT_KEY_USER, tmp, sizeof(tmp), TO_TICKS_MS(500)); @@ -166,7 +180,6 @@ static void mqtt_load_config_from_storage(void) } } - // pass { char tmp[MQTT_PASS_MAX_LEN] = {0}; err = storage_get_str_sync(MQTT_NVS_NAMESPACE, MQTT_KEY_PASS, tmp, sizeof(tmp), TO_TICKS_MS(500)); @@ -183,7 +196,6 @@ static void mqtt_load_config_from_storage(void) } } - // period { uint16_t u16 = 30; err = storage_get_u16_sync(MQTT_NVS_NAMESPACE, MQTT_KEY_PERIOD, &u16, TO_TICKS_MS(500)); @@ -197,14 +209,25 @@ static void mqtt_load_config_from_storage(void) } } - if (needs_flush) { - // não bloqueia chamador; commit imediato no storage_task - (void)storage_flush_async(); + uint8_t u8 = 0; + err = storage_get_u8_sync(MQTT_NVS_NAMESPACE, MQTT_KEY_LIMITMODE, &u8, TO_TICKS_MS(500)); + if (err == ESP_OK && u8 <= LIMIT_MODE_SOLAR) + s_limit_mode = (limit_mode_t)u8; + else + { + s_limit_mode = LIMIT_MODE_OFF; + (void)storage_set_u8_async(MQTT_NVS_NAMESPACE, MQTT_KEY_LIMITMODE, 0); + needs_flush = true; + } } - ESP_LOGI(TAG, "Config MQTT (storage): enabled=%d host='%s' topic='%s' period=%u user='%s'", - s_mqtt_enabled, s_mqtt_host, s_mqtt_base_topic, (unsigned)s_mqtt_periodicity, s_mqtt_user); + if (needs_flush) + (void)storage_flush_async(); + + ESP_LOGI(TAG, "Config MQTT (storage): enabled=%d host='%s' topic='%s' period=%u user='%s' limitmode=%s", + s_mqtt_enabled, s_mqtt_host, s_mqtt_base_topic, + (unsigned)s_mqtt_periodicity, s_mqtt_user, limit_mode_to_str(s_limit_mode)); } static esp_err_t mqtt_save_config_to_storage(void) @@ -239,7 +262,10 @@ static esp_err_t mqtt_save_config_to_storage(void) if (e != ESP_OK) err = e; - // commit imediato sem bloquear o caller + e = storage_set_u8_async(MQTT_NVS_NAMESPACE, MQTT_KEY_LIMITMODE, (uint8_t)s_limit_mode); + if (e != ESP_OK) + err = e; + (void)storage_flush_async(); if (err != ESP_OK) @@ -248,10 +274,6 @@ static esp_err_t mqtt_save_config_to_storage(void) return err; } -// -------------------------------------------------- -// Getters públicos (usados por REST /config/mqtt) -// -------------------------------------------------- - bool mqtt_get_enabled(void) { return s_mqtt_enabled; } void mqtt_get_server(char *server) @@ -288,9 +310,84 @@ void mqtt_get_password(char *password) uint16_t mqtt_get_periodicity(void) { return s_mqtt_periodicity; } -// -------------------------------------------------- -// Helpers de tópico / publish -// -------------------------------------------------- +static const char *limit_mode_to_str(limit_mode_t m) +{ + switch (m) + { + case LIMIT_MODE_OFF: + return "off"; + case LIMIT_MODE_MANUAL: + return "manual"; + case LIMIT_MODE_LOADBALANCER: + return "loadbalancer"; + case LIMIT_MODE_SOLAR: + return "solar"; + default: + return "off"; + } +} + +static bool limit_mode_from_str(const char *s, limit_mode_t *out) +{ + if (!s || !out) + return false; + if (strcmp(s, "off") == 0) + { + *out = LIMIT_MODE_OFF; + return true; + } + if (strcmp(s, "manual") == 0) + { + *out = LIMIT_MODE_MANUAL; + return true; + } + if (strcmp(s, "loadbalancer") == 0) + { + *out = LIMIT_MODE_LOADBALANCER; + return true; + } + if (strcmp(s, "solar") == 0) + { + *out = LIMIT_MODE_SOLAR; + return true; + } + return false; +} + +static void mqtt_apply_limit_mode(limit_mode_t mode) +{ + switch (mode) + { + case LIMIT_MODE_OFF: + loadbalancer_pv_set_enabled(false); + loadbalancer_grid_set_enabled(false); + loadbalancer_set_enabled(false); + evse_set_charging_current(evse_get_max_charging_current()); + break; + + case LIMIT_MODE_MANUAL: + loadbalancer_pv_set_enabled(false); + loadbalancer_grid_set_enabled(false); + loadbalancer_set_enabled(false); + break; + + case LIMIT_MODE_LOADBALANCER: + loadbalancer_pv_set_enabled(false); + loadbalancer_grid_set_enabled(true); + loadbalancer_set_enabled(true); + break; + + case LIMIT_MODE_SOLAR: + loadbalancer_grid_set_enabled(false); + loadbalancer_pv_set_enabled(true); + loadbalancer_set_enabled(true); + break; + } + + s_limit_mode = mode; + (void)storage_set_u8_async(MQTT_NVS_NAMESPACE, MQTT_KEY_LIMITMODE, (uint8_t)mode); + (void)storage_flush_async(); +} static void mqtt_build_topic(char *out, size_t out_len, const char *suffix) { @@ -309,15 +406,8 @@ static void mqtt_publish_raw(const char *subtopic, const char *payload, bool ret char topic[MQTT_TOPIC_BUF_LEN]; mqtt_build_topic(topic, sizeof(topic), subtopic); - int msg_id = esp_mqtt_client_publish( - s_client, - topic, - payload, - 0, - 1, - retain ? 1 : 0); - - ESP_LOGI(TAG, "MQTT publish [%s] (id=%d): %s", topic, msg_id, payload); + int msg_id = esp_mqtt_client_publish(s_client, topic, payload, 0, 1, retain ? 1 : 0); + ESP_LOGD(TAG, "MQTT publish [%s] (id=%d): %s", topic, msg_id, payload); } static void mqtt_publish_json(const char *subtopic, cJSON *obj, bool retain) @@ -338,10 +428,6 @@ static void mqtt_publish_json(const char *subtopic, cJSON *obj, bool retain) cJSON_Delete(obj); } -// -------------------------------------------------- -// Helpers scheduler HH:MM -// -------------------------------------------------- - static bool parse_hhmm(const char *s, uint16_t *out_min) { if (!s || !out_min) @@ -350,6 +436,13 @@ static bool parse_hhmm(const char *s, uint16_t *out_min) int h = 0, m = 0; if (sscanf(s, "%d:%d", &h, &m) != 2) return false; + + if (h == 24 && m == 0) + { + *out_min = 1440; + return true; + } + if (h < 0 || h > 23 || m < 0 || m > 59) return false; @@ -361,18 +454,17 @@ static void format_hhmm(uint16_t minutes, char *buf, size_t buf_sz) { if (!buf || buf_sz < 6) return; - minutes %= (24 * 60); - int h = minutes / 60; - int m = minutes % 60; - snprintf(buf, buf_sz, "%02d:%02d", h, m); + + if (minutes == 1440) + { + snprintf(buf, buf_sz, "24:00"); + return; + } + + minutes %= 1440; + snprintf(buf, buf_sz, "%02d:%02d", minutes / 60, minutes % 60); } -// -------------------------------------------------- -// Publicar estado de cada componente -// -------------------------------------------------- - -// ---- AUTH ---- - static void mqtt_auth_publish_state(void) { cJSON *root = cJSON_CreateObject(); @@ -395,12 +487,73 @@ static void mqtt_auth_publish_state(void) mqtt_publish_json("state/auth", root, true); } -// ---- Scheduler ---- +static cJSON *sched_window_to_json(const sched_window_t *w) +{ + if (!w) + return NULL; + + cJSON *o = cJSON_CreateObject(); + if (!o) + return NULL; + + char buf[8]; + format_hhmm(w->start_min, buf, sizeof(buf)); + cJSON_AddStringToObject(o, "startTime", buf); + format_hhmm(w->end_min, buf, sizeof(buf)); + cJSON_AddStringToObject(o, "endTime", buf); + cJSON_AddNumberToObject(o, "currentA", (int)w->current_a); + + return o; +} + +static bool sched_parse_windows_array(cJSON *arr, sched_config_t *cfg) +{ + if (!arr || !cfg || !cJSON_IsArray(arr)) + return false; + + int n = cJSON_GetArraySize(arr); + if (n <= 0) + return false; + if (n > SCHED_MAX_WINDOWS) + n = SCHED_MAX_WINDOWS; + + cfg->window_count = 0; + + for (int i = 0; i < n; i++) + { + cJSON *it = cJSON_GetArrayItem(arr, i); + if (!cJSON_IsObject(it)) + return false; + + cJSON *js = cJSON_GetObjectItem(it, "startTime"); + cJSON *je = cJSON_GetObjectItem(it, "endTime"); + cJSON *ja = cJSON_GetObjectItem(it, "currentA"); + + if (!cJSON_IsString(js) || !cJSON_IsString(je) || !cJSON_IsNumber(ja)) + return false; + + uint16_t sm = 0, em = 0; + if (!parse_hhmm(js->valuestring, &sm)) + return false; + if (!parse_hhmm(je->valuestring, &em)) + return false; + + int a = ja->valueint; + if (a < 6 || a > 80) + return false; + + cfg->windows[cfg->window_count].start_min = sm; + cfg->windows[cfg->window_count].end_min = em; + cfg->windows[cfg->window_count].current_a = (uint16_t)a; + cfg->window_count++; + } + + return true; +} static void mqtt_scheduler_publish_state(void) { sched_config_t cfg = scheduler_get_config(); - bool allowed_now = scheduler_is_allowed_now(); cJSON *root = cJSON_CreateObject(); if (!root) @@ -409,36 +562,36 @@ static void mqtt_scheduler_publish_state(void) cJSON_AddBoolToObject(root, "enabled", cfg.enabled); cJSON_AddStringToObject(root, "mode", sched_mode_to_str(cfg.mode)); - char buf[8]; - format_hhmm(cfg.start_min, buf, sizeof(buf)); - cJSON_AddStringToObject(root, "startTime", buf); - format_hhmm(cfg.end_min, buf, sizeof(buf)); - cJSON_AddStringToObject(root, "endTime", buf); + cJSON *arr = cJSON_CreateArray(); + if (arr) + { + for (int i = 0; i < cfg.window_count && i < SCHED_MAX_WINDOWS; i++) + { + cJSON *w = sched_window_to_json(&cfg.windows[i]); + if (w) + cJSON_AddItemToArray(arr, w); + } + cJSON_AddItemToObject(root, "windows", arr); + } - cJSON_AddBoolToObject(root, "allowedNow", allowed_now); + cJSON_AddBoolToObject(root, "allowedNow", scheduler_is_allowed_now()); + cJSON_AddNumberToObject(root, "currentLimitNowA", (int)scheduler_get_current_limit_now_a()); mqtt_publish_json("state/scheduler", root, true); } -// ---- Loadbalancer ---- - static void mqtt_loadbalancer_publish_state(void) { cJSON *root = cJSON_CreateObject(); if (!root) return; - bool enabled = loadbalancer_is_enabled(); - uint8_t limit = load_balancing_get_max_grid_current(); - - cJSON_AddBoolToObject(root, "loadBalancingEnabled", enabled); - cJSON_AddNumberToObject(root, "loadBalancingCurrentLimit", limit); + cJSON_AddBoolToObject(root, "enabled", loadbalancer_is_enabled()); + cJSON_AddNumberToObject(root, "currentLimitA", load_balancing_get_max_grid_current()); mqtt_publish_json("state/loadbalancing", root, true); } -// ---- Meters: config + live ---- - static void mqtt_meters_publish_config(void) { cJSON *root = cJSON_CreateObject(); @@ -466,7 +619,7 @@ static void mqtt_meters_publish_live(const meter_event_data_t *m, int64_t ts_us) cJSON_AddStringToObject(root, "source", m->source ? m->source : "GRID"); cJSON_AddNumberToObject(root, "frequency", m->frequency); cJSON_AddNumberToObject(root, "powerFactor", m->power_factor); - cJSON_AddNumberToObject(root, "totalEnergy", m->total_energy); + cJSON_AddNumberToObject(root, "meterTotalEnergyKWh", m->total_energy); cJSON_AddNumberToObject(root, "timestampUs", (double)ts_us); cJSON *vr = cJSON_CreateArray(); @@ -489,69 +642,139 @@ static void mqtt_meters_publish_live(const meter_event_data_t *m, int64_t ts_us) mqtt_publish_json(suffix, root, false); } -// ---- EVSE ---- - static void mqtt_evse_publish_state(void) { evse_state_t state = evse_get_state(); + evse_session_t s = {0}; cJSON *dash = cJSON_CreateObject(); if (!dash) return; cJSON_AddStringToObject(dash, "status", evse_state_to_str(state)); + cJSON_AddNumberToObject(dash, "powerW", evse_meter_get_instant_power()); + cJSON_AddNumberToObject(dash, "currentA", evse_get_runtime_charging_current()); + cJSON_AddNumberToObject(dash, "maxCurrentA", evse_get_charging_current()); - cJSON *chargers = cJSON_CreateArray(); - cJSON *ch = cJSON_CreateObject(); - cJSON_AddNumberToObject(ch, "id", 1); - cJSON_AddStringToObject(ch, "status", evse_state_to_str(state)); - cJSON_AddNumberToObject(ch, "current", evse_get_runtime_charging_current()); - cJSON_AddNumberToObject(ch, "maxCurrent", evse_get_charging_current()); - - int power = evse_get_runtime_charging_current() * 230; - cJSON_AddNumberToObject(ch, "power", power); - - cJSON_AddItemToArray(chargers, ch); - cJSON_AddItemToObject(dash, "chargers", chargers); - - cJSON_AddNumberToObject(dash, "energyConsumed", evse_get_consumption_limit()); - cJSON_AddNumberToObject(dash, "chargingTime", evse_get_charging_time_limit()); + if (evse_session_get(&s)) + { + cJSON_AddNumberToObject(dash, "sessionId", (double)s.session_id); + if (s.tag[0] != '\0') + cJSON_AddStringToObject(dash, "sessionTag", s.tag); + cJSON_AddNumberToObject(dash, "sessionEnergyWh", (double)s.energy_wh); + cJSON_AddNumberToObject(dash, "sessionDurationS", (double)s.duration_s); + } + else + { + cJSON_AddNumberToObject(dash, "sessionId", 0); + cJSON_AddNumberToObject(dash, "sessionEnergyWh", 0); + cJSON_AddNumberToObject(dash, "sessionDurationS", 0); + } cJSON *alerts = cJSON_CreateArray(); - if (evse_is_limit_reached()) - cJSON_AddItemToArray(alerts, cJSON_CreateString("Limite de consumo atingido.")); - if (!evse_config_is_available()) - cJSON_AddItemToArray(alerts, cJSON_CreateString("Estação indisponível.")); - if (!evse_config_is_enabled()) - cJSON_AddItemToArray(alerts, cJSON_CreateString("EVSE desativado.")); - cJSON_AddItemToObject(dash, "alerts", alerts); + if (alerts) + { + if (evse_is_limit_reached()) + cJSON_AddItemToArray(alerts, cJSON_CreateString("Limite de consumo atingido.")); + if (!evse_config_is_available()) + cJSON_AddItemToArray(alerts, cJSON_CreateString("Estação indisponível.")); + if (!evse_config_is_enabled()) + cJSON_AddItemToArray(alerts, cJSON_CreateString("EVSE desativado.")); + cJSON_AddItemToObject(dash, "alerts", alerts); + } uint32_t error_bits = evse_get_error(); cJSON *errors = cJSON_CreateArray(); - if (error_bits & EVSE_ERR_DIODE_SHORT_BIT) - cJSON_AddItemToArray(errors, cJSON_CreateString("Diodo curto-circuitado")); - if (error_bits & EVSE_ERR_LOCK_FAULT_BIT) - cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no travamento")); - if (error_bits & EVSE_ERR_UNLOCK_FAULT_BIT) - cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no destravamento")); - if (error_bits & EVSE_ERR_RCM_SELFTEST_FAULT_BIT) - cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no autoteste do RCM")); - if (error_bits & EVSE_ERR_RCM_TRIGGERED_BIT) - cJSON_AddItemToArray(errors, cJSON_CreateString("RCM disparado")); - if (error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT) - cJSON_AddItemToArray(errors, cJSON_CreateString("Temperatura elevada")); - if (error_bits & EVSE_ERR_PILOT_FAULT_BIT) - cJSON_AddItemToArray(errors, cJSON_CreateString("Erro no sinal piloto")); - if (error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT) - cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no sensor de temperatura")); - cJSON_AddItemToObject(dash, "errors", errors); + if (errors) + { + if (error_bits & EVSE_ERR_DIODE_SHORT_BIT) + cJSON_AddItemToArray(errors, cJSON_CreateString("Diodo curto-circuitado")); + if (error_bits & EVSE_ERR_LOCK_FAULT_BIT) + cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no travamento")); + if (error_bits & EVSE_ERR_UNLOCK_FAULT_BIT) + cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no destravamento")); + if (error_bits & EVSE_ERR_RCM_SELFTEST_FAULT_BIT) + cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no autoteste do RCM")); + if (error_bits & EVSE_ERR_RCM_TRIGGERED_BIT) + cJSON_AddItemToArray(errors, cJSON_CreateString("RCM disparado")); + if (error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT) + cJSON_AddItemToArray(errors, cJSON_CreateString("Temperatura elevada")); + if (error_bits & EVSE_ERR_PILOT_FAULT_BIT) + cJSON_AddItemToArray(errors, cJSON_CreateString("Erro no sinal piloto")); + if (error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT) + cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no sensor de temperatura")); + cJSON_AddItemToObject(dash, "errors", errors); + } mqtt_publish_json("state/evse", dash, true); } -// -------------------------------------------------- -// Handlers de eventos de módulo -> publish -// -------------------------------------------------- +static void mqtt_limit_publish_state(void) +{ + cJSON *root = cJSON_CreateObject(); + if (!root) + return; + + cJSON_AddStringToObject(root, "mode", limit_mode_to_str(s_limit_mode)); + cJSON_AddNumberToObject(root, "requestedCurrentA", (int)evse_get_charging_current()); + cJSON_AddNumberToObject(root, "effectiveCurrentA", (int)evse_get_runtime_charging_current()); + cJSON_AddNumberToObject(root, "hardwareMaxA", (int)evse_get_max_charging_current()); + cJSON_AddNumberToObject(root, "gridMaxImportA", (int)loadbalancer_grid_get_max_import_a()); + cJSON_AddNumberToObject(root, "maxImportW", (int)loadbalancer_pv_get_max_import_w()); + + mqtt_publish_json("state/limit", root, true); +} + +static void mqtt_session_publish_state(void) +{ + evse_session_t s = {0}; + if (!evse_session_get(&s)) + return; + + cJSON *root = cJSON_CreateObject(); + if (!root) + return; + + cJSON_AddNumberToObject(root, "sessionId", (double)s.session_id); + if (s.tag[0] != '\0') + cJSON_AddStringToObject(root, "tag", s.tag); + cJSON_AddBoolToObject(root, "isCurrent", s.is_current); + cJSON_AddNumberToObject(root, "durationS", (double)s.duration_s); + cJSON_AddNumberToObject(root, "energyWh", (double)s.energy_wh); + cJSON_AddNumberToObject(root, "avgPowerW", (double)s.avg_power_w); + + mqtt_publish_json("state/session", root, !s.is_current); +} + +static void mqtt_system_publish_heartbeat(void) +{ + cJSON *root = cJSON_CreateObject(); + if (!root) + return; + + int64_t uptime_us = esp_timer_get_time(); + cJSON_AddNumberToObject(root, "uptimeS", (double)(uptime_us / 1000000)); + cJSON_AddNumberToObject(root, "freeHeap", (double)esp_get_free_heap_size()); + cJSON_AddStringToObject(root, "fwVersion", FW_VERSION); + + esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); + if (netif) + { + esp_netif_ip_info_t ip_info; + if (esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) + { + char ip_str[16]; + snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip)); + cJSON_AddStringToObject(root, "ip", ip_str); + } + } + + wifi_ap_record_t ap; + if (esp_wifi_sta_get_ap_info(&ap) == ESP_OK) + cJSON_AddNumberToObject(root, "rssi", ap.rssi); + + mqtt_publish_json("state/system", root, true); +} static void mqtt_auth_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) { @@ -603,6 +826,7 @@ static void mqtt_loadbalancer_event_handler(void *arg, esp_event_base_t base, in case LOADBALANCER_EVENT_STATE_CHANGED: case LOADBALANCER_EVENT_GLOBAL_CURRENT_LIMIT: mqtt_loadbalancer_publish_state(); + mqtt_limit_publish_state(); break; default: break; @@ -648,16 +872,24 @@ static void mqtt_evse_event_handler(void *arg, esp_event_base_t base, int32_t id case EVSE_EVENT_AVAILABLE_UPDATED: case EVSE_EVENT_ERROR_CHANGED: mqtt_evse_publish_state(); + + { + evse_session_t s = {0}; + bool has = evse_session_get(&s); + bool active_now = has && s.is_current; + + if (active_now != s_session_was_active) + { + mqtt_session_publish_state(); + s_session_was_active = active_now; + } + } break; default: break; } } -// -------------------------------------------------- -// Comandos MQTT (JSON -> chamar APIs públicas) -// -------------------------------------------------- - static void handle_cmd_auth_mode_set(const char *payload) { cJSON *json = cJSON_Parse(payload); @@ -742,43 +974,30 @@ static void handle_cmd_scheduler_set(const char *payload) if (cJSON_IsBool(j_enabled)) cfg.enabled = cJSON_IsTrue(j_enabled); - cJSON *j_mode = cJSON_GetObjectItem(json, "mode"); - if (cJSON_IsString(j_mode) && j_mode->valuestring) + if (!cfg.enabled) { - sched_mode_t m; - if (!sched_mode_from_str(j_mode->valuestring, &m)) - { - ESP_LOGW(TAG, "Invalid scheduler mode: %s", j_mode->valuestring); - cJSON_Delete(json); - return; - } - cfg.mode = m; + cfg.mode = SCHED_MODE_DISABLED; + cfg.window_count = 0; + scheduler_set_config(&cfg); + cJSON_Delete(json); + return; } - cJSON *j_start = cJSON_GetObjectItem(json, "startTime"); - if (cJSON_IsString(j_start) && j_start->valuestring) + cfg.mode = SCHED_MODE_DAILY_MULTI; + + cJSON *j_windows = cJSON_GetObjectItem(json, "windows"); + if (!j_windows || !cJSON_IsArray(j_windows)) { - uint16_t minutes; - if (!parse_hhmm(j_start->valuestring, &minutes)) - { - ESP_LOGW(TAG, "Invalid startTime: %s", j_start->valuestring); - cJSON_Delete(json); - return; - } - cfg.start_min = minutes; + ESP_LOGW(TAG, "scheduler/set missing windows[]"); + cJSON_Delete(json); + return; } - cJSON *j_end = cJSON_GetObjectItem(json, "endTime"); - if (cJSON_IsString(j_end) && j_end->valuestring) + if (!sched_parse_windows_array(j_windows, &cfg)) { - uint16_t minutes; - if (!parse_hhmm(j_end->valuestring, &minutes)) - { - ESP_LOGW(TAG, "Invalid endTime: %s", j_end->valuestring); - cJSON_Delete(json); - return; - } - cfg.end_min = minutes; + ESP_LOGW(TAG, "scheduler/set invalid windows[]"); + cJSON_Delete(json); + return; } scheduler_set_config(&cfg); @@ -794,7 +1013,9 @@ static void handle_cmd_loadbalancing_set(const char *payload) return; } - cJSON *j_enabled = cJSON_GetObjectItem(json, "loadBalancingEnabled"); + cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled"); + if (!j_enabled) + j_enabled = cJSON_GetObjectItem(json, "loadBalancingEnabled"); if (j_enabled && cJSON_IsBool(j_enabled)) { bool enabled = cJSON_IsTrue(j_enabled); @@ -802,13 +1023,15 @@ static void handle_cmd_loadbalancing_set(const char *payload) loadbalancer_set_enabled(enabled); } - cJSON *j_limit = cJSON_GetObjectItem(json, "loadBalancingCurrentLimit"); + cJSON *j_limit = cJSON_GetObjectItem(json, "currentLimitA"); + if (!j_limit) + j_limit = cJSON_GetObjectItem(json, "loadBalancingCurrentLimit"); if (j_limit && cJSON_IsNumber(j_limit)) { int limit = j_limit->valueint; if (limit < 6 || limit > 100) { - ESP_LOGW(TAG, "Invalid loadBalancingCurrentLimit: %d", limit); + ESP_LOGW(TAG, "Invalid loadbalancer current limit: %d", limit); } else { @@ -877,11 +1100,151 @@ static void handle_cmd_evse_settings(const char *payload) cJSON_Delete(json); mqtt_evse_publish_state(); + mqtt_limit_publish_state(); } -// -------------------------------------------------- -// Handler de eventos MQTT -// -------------------------------------------------- +static void handle_cmd_evse_action(const char *payload) +{ + cJSON *json = cJSON_Parse(payload); + if (!json) + { + ESP_LOGW(TAG, "Invalid JSON in evse/action"); + return; + } + + cJSON *j_action = cJSON_GetObjectItem(json, "action"); + if (!cJSON_IsString(j_action) || !j_action->valuestring) + { + ESP_LOGW(TAG, "evse/action: missing 'action'"); + cJSON_Delete(json); + return; + } + + const char *act = j_action->valuestring; + + if (strcmp(act, "available") == 0) + { + ESP_LOGI(TAG, "MQTT: evse set available=true"); + evse_set_available(true); + } + else if (strcmp(act, "unavailable") == 0) + { + ESP_LOGI(TAG, "MQTT: evse set available=false"); + evse_set_available(false); + } + else + { + ESP_LOGW(TAG, "evse/action: unknown action '%s'", act); + } + + cJSON_Delete(json); + mqtt_evse_publish_state(); +} + +static void handle_cmd_evse_limit(const char *payload) +{ + cJSON *json = cJSON_Parse(payload); + if (!json) + { + ESP_LOGW(TAG, "Invalid JSON in evse/limit"); + return; + } + + cJSON *j_mode = cJSON_GetObjectItem(json, "mode"); + if (!cJSON_IsString(j_mode) || !j_mode->valuestring) + { + ESP_LOGW(TAG, "evse/limit: missing 'mode'"); + cJSON_Delete(json); + return; + } + + limit_mode_t mode; + if (!limit_mode_from_str(j_mode->valuestring, &mode)) + { + ESP_LOGW(TAG, "evse/limit: unknown mode '%s'", j_mode->valuestring); + cJSON_Delete(json); + return; + } + + switch (mode) + { + case LIMIT_MODE_MANUAL: + { + cJSON *j_curr = cJSON_GetObjectItem(json, "requestedCurrentA"); + if (!j_curr) + j_curr = cJSON_GetObjectItem(json, "currentA"); + + if (!cJSON_IsNumber(j_curr)) + { + ESP_LOGW(TAG, "evse/limit manual: missing 'requestedCurrentA/currentA'"); + cJSON_Delete(json); + return; + } + + int a = j_curr->valueint; + uint8_t hw_max = evse_get_max_charging_current(); + if (a < MIN_CHARGING_CURRENT_LIMIT || a > hw_max) + { + ESP_LOGW(TAG, "evse/limit manual: currentA %d out of range [%d,%u]", + a, MIN_CHARGING_CURRENT_LIMIT, (unsigned)hw_max); + cJSON_Delete(json); + return; + } + + evse_set_charging_current((uint16_t)a); + break; + } + + case LIMIT_MODE_LOADBALANCER: + { + cJSON *j = cJSON_GetObjectItem(json, "gridMaxImportA"); + if (cJSON_IsNumber(j)) + { + int a = j->valueint; + if (a < 6 || a > 100) + { + ESP_LOGW(TAG, "evse/limit loadbalancer: gridMaxImportA %d out of range", a); + cJSON_Delete(json); + return; + } + esp_err_t e = loadbalancer_grid_set_max_import_a((uint8_t)a); + if (e != ESP_OK) + ESP_LOGW(TAG, "loadbalancer_grid_set_max_import_a failed: %s", esp_err_to_name(e)); + } + break; + } + + case LIMIT_MODE_SOLAR: + { + cJSON *j = cJSON_GetObjectItem(json, "maxImportW"); + if (cJSON_IsNumber(j)) + { + int w = j->valueint; + if (w < 0) + { + ESP_LOGW(TAG, "evse/limit solar: maxImportW %d invalid", w); + cJSON_Delete(json); + return; + } + esp_err_t e = loadbalancer_pv_set_max_import_w(w); + if (e != ESP_OK) + ESP_LOGW(TAG, "loadbalancer_pv_set_max_import_w failed: %s", esp_err_to_name(e)); + } + break; + } + + case LIMIT_MODE_OFF: + break; + } + + ESP_LOGI(TAG, "MQTT: applying limit mode = %s", limit_mode_to_str(mode)); + mqtt_apply_limit_mode(mode); + + cJSON_Delete(json); + mqtt_loadbalancer_publish_state(); + mqtt_limit_publish_state(); + mqtt_evse_publish_state(); +} static void mqtt_subscribe_all(void) { @@ -910,6 +1273,12 @@ static void mqtt_subscribe_all(void) mqtt_build_topic(topic, sizeof(topic), "cmd/evse/settings"); esp_mqtt_client_subscribe(s_client, topic, 1); + + mqtt_build_topic(topic, sizeof(topic), "cmd/evse/action"); + esp_mqtt_client_subscribe(s_client, topic, 1); + + mqtt_build_topic(topic, sizeof(topic), "cmd/evse/limit"); + esp_mqtt_client_subscribe(s_client, topic, 1); } static void mqtt_dispatch_command(const char *topic, const char *data) @@ -941,6 +1310,10 @@ static void mqtt_dispatch_command(const char *topic, const char *data) handle_cmd_meters_set(data); else if (strcmp(sub, "cmd/evse/settings") == 0) handle_cmd_evse_settings(data); + else if (strcmp(sub, "cmd/evse/action") == 0) + handle_cmd_evse_action(data); + else if (strcmp(sub, "cmd/evse/limit") == 0) + handle_cmd_evse_limit(data); else ESP_LOGW(TAG, "MQTT command in unknown topic: %s", topic); } @@ -956,11 +1329,16 @@ static esp_err_t mqtt_event_handler_cb(esp_mqtt_event_handle_t event) s_connected = true; mqtt_subscribe_all(); + mqtt_publish_raw("state/availability", "online", true); + mqtt_auth_publish_state(); mqtt_scheduler_publish_state(); mqtt_loadbalancer_publish_state(); mqtt_meters_publish_config(); mqtt_evse_publish_state(); + mqtt_limit_publish_state(); + mqtt_session_publish_state(); + mqtt_system_publish_heartbeat(); break; case MQTT_EVENT_DISCONNECTED: @@ -1015,10 +1393,6 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_ mqtt_event_handler_cb((esp_mqtt_event_handle_t)event_data); } -// -------------------------------------------------- -// Ligação MQTT (start/stop) + handler de rede -// -------------------------------------------------- - static void mqtt_client_start_if_needed(void) { if (!s_mqtt_enabled) @@ -1052,8 +1426,28 @@ static void mqtt_client_start_if_needed(void) snprintf(uri, sizeof(uri), "mqtt://%s", s_mqtt_host); } + if (s_client_id[0] == '\0') + { + uint8_t mac[6] = {0}; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + snprintf(s_client_id, sizeof(s_client_id), + "evse-%02x%02x%02x%02x%02x%02x", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + } + + static char lwt_topic[MQTT_TOPIC_BUF_LEN]; + mqtt_build_topic(lwt_topic, sizeof(lwt_topic), "state/availability"); + esp_mqtt_client_config_t cfg = { .broker.address.uri = uri, + .credentials.client_id = s_client_id, + .session.last_will = { + .topic = lwt_topic, + .msg = "offline", + .msg_len = 0, + .qos = 1, + .retain = 1, + }, }; if (s_mqtt_user[0] != '\0') @@ -1068,11 +1462,7 @@ static void mqtt_client_start_if_needed(void) return; } - esp_mqtt_client_register_event( - s_client, - ESP_EVENT_ANY_ID, - mqtt_event_handler, - NULL); + esp_mqtt_client_register_event(s_client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL); esp_err_t err = esp_mqtt_client_start(s_client); if (err != ESP_OK) @@ -1112,14 +1502,11 @@ static void mqtt_network_event_handler(void *arg, esp_event_base_t base, int32_t } } -// -------------------------------------------------- -// Task de telemetria periódica (opcional) -// -------------------------------------------------- - static void mqtt_telemetry_task(void *arg) { (void)arg; TickType_t last_wake = xTaskGetTickCount(); + uint32_t heartbeat_counter = 0; while (1) { @@ -1130,47 +1517,33 @@ static void mqtt_telemetry_task(void *arg) if (s_mqtt_enabled && s_client && s_connected) { mqtt_evse_publish_state(); - mqtt_loadbalancer_publish_state(); - mqtt_scheduler_publish_state(); + mqtt_session_publish_state(); + + if ((heartbeat_counter++ % 5) == 0) + mqtt_system_publish_heartbeat(); } vTaskDelayUntil(&last_wake, pdMS_TO_TICKS(period * 1000)); } } -// -------------------------------------------------- -// API pública -// -------------------------------------------------- - esp_err_t mqtt_init(void) { mqtt_load_config_from_storage(); - ESP_ERROR_CHECK(esp_event_handler_register( - AUTH_EVENTS, ESP_EVENT_ANY_ID, mqtt_auth_event_handler, NULL)); - ESP_ERROR_CHECK(esp_event_handler_register( - SCHED_EVENTS, ESP_EVENT_ANY_ID, mqtt_scheduler_event_handler, NULL)); - ESP_ERROR_CHECK(esp_event_handler_register( - LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, mqtt_loadbalancer_event_handler, NULL)); - ESP_ERROR_CHECK(esp_event_handler_register( - METER_EVENT, ESP_EVENT_ANY_ID, mqtt_meter_event_handler, NULL)); - ESP_ERROR_CHECK(esp_event_handler_register( - EVSE_EVENTS, ESP_EVENT_ANY_ID, mqtt_evse_event_handler, NULL)); - - ESP_ERROR_CHECK(esp_event_handler_register( - NETWORK_EVENTS, ESP_EVENT_ANY_ID, mqtt_network_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, mqtt_auth_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(SCHED_EVENTS, ESP_EVENT_ANY_ID, mqtt_scheduler_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, mqtt_loadbalancer_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, ESP_EVENT_ANY_ID, mqtt_meter_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, ESP_EVENT_ANY_ID, mqtt_evse_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(NETWORK_EVENTS, ESP_EVENT_ANY_ID, mqtt_network_event_handler, NULL)); if (!s_telemetry_task) { - xTaskCreate( - mqtt_telemetry_task, - "mqtt_telemetry_task", - 4096, - NULL, - 3, - &s_telemetry_task); + xTaskCreate(mqtt_telemetry_task, "mqtt_telemetry_task", 4096, NULL, 3, &s_telemetry_task); } + mqtt_apply_limit_mode(s_limit_mode); return ESP_OK; } @@ -1225,6 +1598,4 @@ esp_err_t mqtt_set_config(bool enabled, mqtt_client_start_if_needed(); return ESP_OK; -} - -// === Fim de: components/mqtt/src/mqtt.c === +} \ No newline at end of file diff --git a/components/rest_api/CMakeLists.txt b/components/rest_api/CMakeLists.txt index d5735ab..a1f0345 100755 --- a/components/rest_api/CMakeLists.txt +++ b/components/rest_api/CMakeLists.txt @@ -10,6 +10,7 @@ set(srcs "src/dashboard_api.c" "src/scheduler_settings_api.c" "src/static_file_api.c" + "src/system_api.c" ) idf_component_register( diff --git a/components/rest_api/include/system_api.h b/components/rest_api/include/system_api.h new file mode 100644 index 0000000..46ce86c --- /dev/null +++ b/components/rest_api/include/system_api.h @@ -0,0 +1,16 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_http_server.h" + +/** + * @brief Regista os handlers REST de informação do sistema. + */ +void register_system_handlers(httpd_handle_t server, void *ctx); + +#ifdef __cplusplus +} +#endif diff --git a/components/rest_api/src/auth_api.c b/components/rest_api/src/auth_api.c index b33e6d4..c524a5e 100755 --- a/components/rest_api/src/auth_api.c +++ b/components/rest_api/src/auth_api.c @@ -57,6 +57,94 @@ static esp_err_t recv_body(httpd_req_t *req, char *buf, size_t buf_sz, int *out_ return ESP_OK; } + +static void trim_in_place(char *s) { + if (!s || s[0] == '\0') return; + + char *start = s; + while (*start == ' ' || *start == '\t' || *start == '\r' || *start == '\n') { + start++; + } + + if (start != s) { + memmove(s, start, strlen(start) + 1); + } + + size_t len = strlen(s); + while (len > 0) { + char c = s[len - 1]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { + s[len - 1] = '\0'; + len--; + } else { + break; + } + } +} + +static void strip_wrapping_quotes_in_place(char *s) { + size_t len; + if (!s) return; + + trim_in_place(s); + len = strlen(s); + if (len >= 2 && s[0] == '"' && s[len - 1] == '"') { + memmove(s, s + 1, len - 2); + s[len - 2] = '\0'; + trim_in_place(s); + } +} + +static bool extract_username_from_body(const char *buf, int len, char *out, size_t out_sz) { + bool ok = false; + cJSON *json = cJSON_ParseWithLength(buf, len); + out[0] = '\0'; + + if (json) { + if (cJSON_IsString(json) && json->valuestring) { + strlcpy(out, json->valuestring, out_sz); + ok = true; + } else if (cJSON_IsObject(json)) { + cJSON *username_js = cJSON_GetObjectItem(json, "username"); + if (cJSON_IsString(username_js) && username_js->valuestring) { + strlcpy(out, username_js->valuestring, out_sz); + ok = true; + } + } + cJSON_Delete(json); + } + + if (!ok) { + strlcpy(out, buf, out_sz); + } + + trim_in_place(out); + strip_wrapping_quotes_in_place(out); + return out[0] != '\0'; +} + +static bool usernames_equal_normalized(const char *a, const char *b) { + char na[128] = {0}; + char nb[128] = {0}; + + strlcpy(na, a ? a : "", sizeof(na)); + strlcpy(nb, b ? b : "", sizeof(nb)); + trim_in_place(na); + trim_in_place(nb); + strip_wrapping_quotes_in_place(na); + strip_wrapping_quotes_in_place(nb); + return strcmp(na, nb) == 0; +} + +static int find_user_index(const char *username) { + for (int i = 0; i < num_users; ++i) { + if (usernames_equal_normalized(users[i].username, username)) { + return i; + } + } + return -1; +} + // ================================= // Auth Mode (NEW API) // ================================= @@ -125,37 +213,48 @@ static esp_err_t users_get_handler(httpd_req_t *req) { static esp_err_t users_post_handler(httpd_req_t *req) { char buf[128]; + char username[128] = {0}; int len = 0; + if (recv_body(req, buf, sizeof(buf), &len) != ESP_OK || len <= 0) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Body vazio"); return ESP_FAIL; } - if (num_users < 10) { - strlcpy(users[num_users].username, buf, sizeof(users[num_users].username)); - num_users++; - httpd_resp_sendstr(req, "Usuário adicionado com sucesso"); - return ESP_OK; - } else { + if (!extract_username_from_body(buf, len, username, sizeof(username))) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Campo 'username' inválido ou ausente"); + return ESP_FAIL; + } + + if (find_user_index(username) >= 0) { + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Usuário já existe"); + return ESP_FAIL; + } + + if (num_users >= 10) { httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Limite de usuários atingido"); return ESP_FAIL; } + + strlcpy(users[num_users].username, username, sizeof(users[num_users].username)); + num_users++; + httpd_resp_sendstr(req, "Usuário adicionado com sucesso"); + return ESP_OK; } static esp_err_t users_delete_handler(httpd_req_t *req) { char query[128]; if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) { - char username[128]; + char username[128] = {0}; if (httpd_query_key_value(query, "username", username, sizeof(username)) == ESP_OK) { - for (int i = 0; i < num_users; i++) { - if (strcmp(users[i].username, username) == 0) { - for (int j = i; j < num_users - 1; j++) { - users[j] = users[j + 1]; - } - num_users--; - httpd_resp_sendstr(req, "Usuário removido com sucesso"); - return ESP_OK; + int idx = find_user_index(username); + if (idx >= 0) { + for (int j = idx; j < num_users - 1; j++) { + users[j] = users[j + 1]; } + num_users--; + httpd_resp_sendstr(req, "Usuário removido com sucesso"); + return ESP_OK; } } } diff --git a/components/rest_api/src/dashboard_api.c b/components/rest_api/src/dashboard_api.c index 2fdc5d4..4522d19 100644 --- a/components/rest_api/src/dashboard_api.c +++ b/components/rest_api/src/dashboard_api.c @@ -17,19 +17,34 @@ static esp_err_t dashboard_get_handler(httpd_req_t *req) { // Status do sistema evse_state_t state = evse_get_state(); + int runtime_current = evse_get_runtime_charging_current(); + int requested_current = evse_get_charging_current(); + int hardware_max = evse_get_max_charging_current(); + int power = runtime_current * 230; + cJSON_AddStringToObject(dashboard, "status", evse_state_to_str(state)); + // Aliases de topo para clientes novos (mantendo compatibilidade com chargers[]) + cJSON_AddNumberToObject(dashboard, "currentA", runtime_current); + cJSON_AddNumberToObject(dashboard, "currentLimitNowA", runtime_current); + cJSON_AddNumberToObject(dashboard, "maxCurrentA", requested_current); + cJSON_AddNumberToObject(dashboard, "hardwareMaxA", hardware_max); + cJSON_AddNumberToObject(dashboard, "power", power); + cJSON_AddNumberToObject(dashboard, "powerW", power); + // Carregador - informação do carregador 1 (adapte conforme necessário) cJSON *chargers = cJSON_CreateArray(); cJSON *charger1 = cJSON_CreateObject(); cJSON_AddNumberToObject(charger1, "id", 1); cJSON_AddStringToObject(charger1, "status", evse_state_to_str(state)); - cJSON_AddNumberToObject(charger1, "current", evse_get_runtime_charging_current()); - cJSON_AddNumberToObject(charger1, "maxCurrent", evse_get_charging_current()); + cJSON_AddNumberToObject(charger1, "current", runtime_current); + cJSON_AddNumberToObject(charger1, "currentA", runtime_current); + cJSON_AddNumberToObject(charger1, "maxCurrent", requested_current); + cJSON_AddNumberToObject(charger1, "maxCurrentA", requested_current); // Calcular a potência com base na corrente (considerando 230V) - int power = (evse_get_runtime_charging_current()) * 230; cJSON_AddNumberToObject(charger1, "power", power); + cJSON_AddNumberToObject(charger1, "powerW", power); cJSON_AddItemToArray(chargers, charger1); cJSON_AddItemToObject(dashboard, "chargers", chargers); diff --git a/components/rest_api/src/evse_settings_api.c b/components/rest_api/src/evse_settings_api.c index efa9137..c4e656d 100755 --- a/components/rest_api/src/evse_settings_api.c +++ b/components/rest_api/src/evse_settings_api.c @@ -4,6 +4,7 @@ #include "evse_settings_api.h" #include "evse_api.h" #include "evse_config.h" +#include "evse_limits.h" #include "esp_log.h" #include "cJSON.h" @@ -11,12 +12,23 @@ static const char *TAG = "evse_settings_api"; static esp_err_t config_settings_get_handler(httpd_req_t *req) { httpd_resp_set_type(req, "application/json"); + cJSON *config = cJSON_CreateObject(); + cJSON_AddNumberToObject(config, "maxCurrentLimit", evse_get_max_charging_current()); cJSON_AddNumberToObject(config, "currentLimit", evse_get_charging_current()); + cJSON_AddNumberToObject(config, "powerLimit", 0); + cJSON_AddNumberToObject(config, "energyLimit", evse_get_consumption_limit()); + cJSON_AddNumberToObject(config, "chargingTimeLimit", evse_get_charging_time_limit()); cJSON_AddNumberToObject(config, "temperatureLimit", evse_get_temp_threshold()); - const char *json_str = cJSON_Print(config); - httpd_resp_sendstr(req, json_str); - free((void *)json_str); + + cJSON *security = cJSON_CreateObject(); + cJSON_AddBoolToObject(security, "earthFault", false); + cJSON_AddBoolToObject(security, "rcm", false); + cJSON_AddItemToObject(config, "security", security); + + char *json_str = cJSON_PrintUnformatted(config); + httpd_resp_sendstr(req, json_str ? json_str : "{}"); + if (json_str) free(json_str); cJSON_Delete(config); return ESP_OK; } @@ -36,9 +48,25 @@ static esp_err_t config_settings_post_handler(httpd_req_t *req) { } cJSON *current = cJSON_GetObjectItem(json, "currentLimit"); - if (current) evse_set_charging_current(current->valueint); + if (cJSON_IsNumber(current)) { + int value = current->valueint; + int hw_max = evse_get_max_charging_current(); + if (value < 0) value = 0; + if (value > hw_max) value = hw_max; + evse_set_charging_current(value); + } + cJSON *temp = cJSON_GetObjectItem(json, "temperatureLimit"); - if (temp) evse_set_temp_threshold(temp->valueint); + if (cJSON_IsNumber(temp)) { + evse_set_temp_threshold(temp->valueint); + } + + // Campos aceites por compatibilidade de contrato v1. + // Nesta sprint são ignorados caso o firmware ainda não os suporte internamente. + (void)cJSON_GetObjectItem(json, "powerLimit"); + (void)cJSON_GetObjectItem(json, "energyLimit"); + (void)cJSON_GetObjectItem(json, "chargingTimeLimit"); + (void)cJSON_GetObjectItem(json, "security"); cJSON_Delete(json); httpd_resp_sendstr(req, "Configurações atualizadas com sucesso"); diff --git a/components/rest_api/src/network_api.c b/components/rest_api/src/network_api.c index f63891d..0530397 100755 --- a/components/rest_api/src/network_api.c +++ b/components/rest_api/src/network_api.c @@ -13,6 +13,23 @@ static const char *TAG = "network_api"; +static bool mqtt_host_is_invalid(const char *host) +{ + if (!host || host[0] == '\0') + return true; + + if (strcmp(host, "localhost") == 0) + return true; + + if (strcmp(host, "127.0.0.1") == 0) + return true; + + if (strcmp(host, "0.0.0.0") == 0) + return true; + + return false; +} + typedef struct { bool enabled; @@ -225,10 +242,14 @@ static esp_err_t config_mqtt_post_handler(httpd_req_t *req) if (cJSON_IsNumber(j_periodicity)) periodicity = j_periodicity->valueint; - // --- Regras: se vier NULL ou "" mantém o atual; se atual também estiver vazio, usa default - const char *host = - (host_in && host_in[0] != '\0') ? host_in : (current_host[0] != '\0') ? current_host - : "mqtt.plixin.com"; + const char *candidate_host = + (host_in && host_in[0] != '\0') ? host_in : + (current_host[0] != '\0') ? current_host : + "mqtt.plixin.com"; + + const char *host = mqtt_host_is_invalid(candidate_host) + ? "mqtt.plixin.com" + : candidate_host; const char *topic = (topic_in && topic_in[0] != '\0') ? topic_in : (current_topic[0] != '\0') ? current_topic @@ -298,4 +319,4 @@ void register_network_handlers(httpd_handle_t server, void *ctx) .handler = config_mqtt_post_handler, .user_ctx = ctx}; httpd_register_uri_handler(server, &config_mqtt_post_uri); -} +} \ No newline at end of file diff --git a/components/rest_api/src/rest_main.c b/components/rest_api/src/rest_main.c index 6a40c6d..9e74548 100755 --- a/components/rest_api/src/rest_main.c +++ b/components/rest_api/src/rest_main.c @@ -9,6 +9,7 @@ #include "dashboard_api.h" #include "scheduler_settings_api.h" #include "static_file_api.h" +#include "system_api.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" @@ -62,6 +63,7 @@ esp_err_t rest_server_init(const char *base_path) register_link_config_handlers(server, ctx); register_meters_data_handlers(server, ctx); register_scheduler_settings_handlers(server, ctx); + register_system_handlers(server, ctx); register_static_file_handlers(server, ctx); ESP_LOGI(TAG, "All REST API endpoint groups registered successfully"); diff --git a/components/rest_api/src/scheduler_settings_api.c b/components/rest_api/src/scheduler_settings_api.c index 34e09ef..9365792 100755 --- a/components/rest_api/src/scheduler_settings_api.c +++ b/components/rest_api/src/scheduler_settings_api.c @@ -1,3 +1,4 @@ +// components/rest_api/src/scheduler_settings_api.c #include "scheduler_settings_api.h" #include "scheduler.h" #include "scheduler_types.h" @@ -6,14 +7,14 @@ #include "esp_http_server.h" #include "cJSON.h" +#include #include -#include -#include // sscanf, snprintf static const char *TAG = "scheduler_api"; /* ========================= * Helpers HH:MM <-> minutos + * - aceita 24:00 => 1440 * ========================= */ static bool parse_hhmm(const char *s, uint16_t *out_min) @@ -21,16 +22,19 @@ static bool parse_hhmm(const char *s, uint16_t *out_min) if (!s || !out_min) return false; - // formato esperado: "HH:MM" int h = 0, m = 0; if (sscanf(s, "%d:%d", &h, &m) != 2) - { return false; + + if (h == 24 && m == 0) + { + *out_min = 1440; + return true; } + if (h < 0 || h > 23 || m < 0 || m > 59) - { return false; - } + *out_min = (uint16_t)(h * 60 + m); return true; } @@ -39,10 +43,78 @@ static void format_hhmm(uint16_t minutes, char *buf, size_t buf_sz) { if (!buf || buf_sz < 6) return; - minutes %= (24 * 60); - int h = minutes / 60; - int m = minutes % 60; - snprintf(buf, buf_sz, "%02d:%02d", h, m); + + if (minutes == 1440) + { + snprintf(buf, buf_sz, "24:00"); + return; + } + + minutes %= 1440; + snprintf(buf, buf_sz, "%02d:%02d", minutes / 60, minutes % 60); +} + +static cJSON *window_to_json(const sched_window_t *w) +{ + cJSON *o = cJSON_CreateObject(); + if (!o) + return NULL; + + char b[8]; + format_hhmm(w->start_min, b, sizeof(b)); + cJSON_AddStringToObject(o, "startTime", b); + + format_hhmm(w->end_min, b, sizeof(b)); + cJSON_AddStringToObject(o, "endTime", b); + + cJSON_AddNumberToObject(o, "currentA", (int)w->current_a); + + return o; +} + +static bool parse_windows_array(cJSON *arr, sched_config_t *cfg) +{ + if (!arr || !cfg || !cJSON_IsArray(arr)) + return false; + + int n = cJSON_GetArraySize(arr); + if (n <= 0) + return false; + if (n > SCHED_MAX_WINDOWS) + n = SCHED_MAX_WINDOWS; + + cfg->window_count = 0; + + for (int i = 0; i < n; i++) + { + cJSON *it = cJSON_GetArrayItem(arr, i); + if (!cJSON_IsObject(it)) + return false; + + cJSON *js = cJSON_GetObjectItem(it, "startTime"); + cJSON *je = cJSON_GetObjectItem(it, "endTime"); + cJSON *ja = cJSON_GetObjectItem(it, "currentA"); + + if (!cJSON_IsString(js) || !cJSON_IsString(je) || !cJSON_IsNumber(ja)) + return false; + + uint16_t sm = 0, em = 0; + if (!parse_hhmm(js->valuestring, &sm)) + return false; + if (!parse_hhmm(je->valuestring, &em)) + return false; + + int a = ja->valueint; + if (a < 6 || a > 80) + return false; + + cfg->windows[cfg->window_count].start_min = sm; + cfg->windows[cfg->window_count].end_min = em; + cfg->windows[cfg->window_count].current_a = (uint16_t)a; + cfg->window_count++; + } + + return true; } /* ========================= @@ -51,11 +123,9 @@ static void format_hhmm(uint16_t minutes, char *buf, size_t buf_sz) static esp_err_t scheduler_config_get_handler(httpd_req_t *req) { ESP_LOGD(TAG, "GET /api/v1/config/scheduler"); - httpd_resp_set_type(req, "application/json"); sched_config_t cfg = scheduler_get_config(); - bool allowed_now = scheduler_is_allowed_now(); cJSON *root = cJSON_CreateObject(); if (!root) @@ -67,13 +137,17 @@ static esp_err_t scheduler_config_get_handler(httpd_req_t *req) cJSON_AddBoolToObject(root, "enabled", cfg.enabled); cJSON_AddStringToObject(root, "mode", sched_mode_to_str(cfg.mode)); - char buf[8]; - format_hhmm(cfg.start_min, buf, sizeof(buf)); - cJSON_AddStringToObject(root, "startTime", buf); - format_hhmm(cfg.end_min, buf, sizeof(buf)); - cJSON_AddStringToObject(root, "endTime", buf); + cJSON *arr = cJSON_CreateArray(); + for (int i = 0; i < cfg.window_count && i < SCHED_MAX_WINDOWS; i++) + { + cJSON *w = window_to_json(&cfg.windows[i]); + if (w) + cJSON_AddItemToArray(arr, w); + } + cJSON_AddItemToObject(root, "windows", arr); - cJSON_AddBoolToObject(root, "allowedNow", allowed_now); + cJSON_AddBoolToObject(root, "allowedNow", scheduler_is_allowed_now()); + cJSON_AddNumberToObject(root, "currentLimitNowA", (int)scheduler_get_current_limit_now_a()); char *json_str = cJSON_PrintUnformatted(root); if (!json_str) @@ -84,7 +158,6 @@ static esp_err_t scheduler_config_get_handler(httpd_req_t *req) } httpd_resp_sendstr(req, json_str); - free(json_str); cJSON_Delete(root); return ESP_OK; @@ -97,113 +170,66 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req) { ESP_LOGD(TAG, "POST /api/v1/config/scheduler"); - // NOTA: para payloads pequenos 512 bytes chega; se quiseres robustez total, - // usa req->content_len e faz um loop com httpd_req_recv. char buf[512]; int len = httpd_req_recv(req, buf, sizeof(buf) - 1); if (len <= 0) { - ESP_LOGE(TAG, "Empty body / recv error"); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body"); return ESP_FAIL; } buf[len] = '\0'; - ESP_LOGD(TAG, "Body: %s", buf); cJSON *json = cJSON_Parse(buf); if (!json) { - ESP_LOGE(TAG, "Invalid JSON"); httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON"); return ESP_FAIL; } - // Começa a partir da config atual + // começa do estado atual sched_config_t cfg = scheduler_get_config(); // enabled cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled"); if (cJSON_IsBool(j_enabled)) - { cfg.enabled = cJSON_IsTrue(j_enabled); - ESP_LOGD(TAG, " enabled = %d", cfg.enabled); - } - // mode - cJSON *j_mode = cJSON_GetObjectItem(json, "mode"); - if (cJSON_IsString(j_mode) && j_mode->valuestring) + if (!cfg.enabled) { - sched_mode_t m; - if (!sched_mode_from_str(j_mode->valuestring, &m)) - { - ESP_LOGW(TAG, "Invalid mode: %s", j_mode->valuestring); - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, - "Invalid mode (use: disabled|simple|weekly)"); - return ESP_FAIL; - } - cfg.mode = m; - ESP_LOGD(TAG, " mode = %s", sched_mode_to_str(cfg.mode)); - } - - // startTime (string "HH:MM") - cJSON *j_start = cJSON_GetObjectItem(json, "startTime"); - if (cJSON_IsString(j_start) && j_start->valuestring) - { - uint16_t minutes = 0; - if (!parse_hhmm(j_start->valuestring, &minutes)) - { - ESP_LOGW(TAG, "Invalid startTime: %s", j_start->valuestring); - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, - "Invalid startTime (use HH:MM)"); - return ESP_FAIL; - } - cfg.start_min = minutes; - ESP_LOGD(TAG, " start_min = %u", (unsigned)cfg.start_min); - } - - // endTime (string "HH:MM") - cJSON *j_end = cJSON_GetObjectItem(json, "endTime"); - if (cJSON_IsString(j_end) && j_end->valuestring) - { - uint16_t minutes = 0; - if (!parse_hhmm(j_end->valuestring, &minutes)) - { - ESP_LOGW(TAG, "Invalid endTime: %s", j_end->valuestring); - cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, - "Invalid endTime (use HH:MM)"); - return ESP_FAIL; - } - cfg.end_min = minutes; - ESP_LOGD(TAG, " end_min = %u", (unsigned)cfg.end_min); - } - - // (Opcional) validações extra: - // exemplo: impedir janela vazia quando ativo - /* - if (cfg.enabled && cfg.mode != SCHED_MODE_DISABLED && - cfg.start_min == cfg.end_min) { + cfg.mode = SCHED_MODE_DISABLED; + cfg.window_count = 0; + scheduler_set_config(&cfg); cJSON_Delete(json); - httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, - "startTime and endTime cannot be equal"); + httpd_resp_sendstr(req, "OK"); + return ESP_OK; + } + + // enabled=true => exige windows[] + cJSON *j_windows = cJSON_GetObjectItem(json, "windows"); + if (!j_windows || !cJSON_IsArray(j_windows)) + { + cJSON_Delete(json); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing windows[]"); + return ESP_FAIL; + } + + cfg.mode = SCHED_MODE_DAILY_MULTI; + + if (!parse_windows_array(j_windows, &cfg)) + { + cJSON_Delete(json); + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, + "Invalid windows (use: [{startTime,endTime,currentA}], currentA 6-80)"); return ESP_FAIL; } - */ - // Aplica config no módulo scheduler (ele trata de NVS + eventos) scheduler_set_config(&cfg); - cJSON_Delete(json); - httpd_resp_sendstr(req, "Scheduler config atualizada com sucesso"); + httpd_resp_sendstr(req, "OK"); return ESP_OK; } -/* ========================= - * Registo dos handlers - * ========================= */ void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx) { httpd_uri_t get_uri = { @@ -221,4 +247,4 @@ void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx) httpd_register_uri_handler(server, &post_uri); ESP_LOGD(TAG, "Scheduler REST handlers registered"); -} +} \ No newline at end of file diff --git a/components/rest_api/src/system_api.c b/components/rest_api/src/system_api.c new file mode 100644 index 0000000..e34786c --- /dev/null +++ b/components/rest_api/src/system_api.c @@ -0,0 +1,108 @@ +#include "system_api.h" + +#include "cJSON.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_system.h" +#include "esp_timer.h" + +#include "auth.h" +#include "evse_api.h" +#include "evse_config.h" +#include "evse_limits.h" +#include "mqtt.h" +#include "network.h" + +#include + +static const char *TAG = "system_api"; + +#ifndef FW_VERSION +#define FW_VERSION "1.0" +#endif + +static esp_err_t system_info_get_handler(httpd_req_t *req) +{ + httpd_resp_set_type(req, "application/json"); + + cJSON *root = cJSON_CreateObject(); + if (!root) + { + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Falha ao criar resposta JSON"); + return ESP_FAIL; + } + + const bool wifi_enabled = wifi_get_enabled(); + const int64_t uptime_us = esp_timer_get_time(); + + cJSON_AddStringToObject(root, "apiVersion", "1.0"); + cJSON_AddStringToObject(root, "device", "PLX EV Charger"); + cJSON_AddStringToObject(root, "fwVersion", FW_VERSION); + cJSON_AddStringToObject(root, "serial", ""); + cJSON_AddStringToObject(root, "mode", wifi_enabled ? "sta" : "ap"); + cJSON_AddBoolToObject(root, "online", true); + cJSON_AddStringToObject(root, "localConfigMode", wifi_enabled ? "sta" : "ap_default"); + cJSON_AddStringToObject(root, "authScheme", "basic"); + cJSON_AddNumberToObject(root, "uptimeS", (double)(uptime_us / 1000000)); + cJSON_AddNumberToObject(root, "freeHeap", (double)esp_get_free_heap_size()); + cJSON_AddNumberToObject(root, "hardwareMaxA", evse_get_max_charging_current()); + cJSON_AddNumberToObject(root, "currentLimit", evse_get_charging_current()); + cJSON_AddBoolToObject(root, "wifiEnabled", wifi_enabled); + cJSON_AddStringToObject(root, "authMode", auth_mode_to_str(auth_get_mode())); + + esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); + if (netif) + { + esp_netif_ip_info_t ip_info; + if (esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) + { + char ip_str[16] = {0}; + snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip)); + cJSON_AddStringToObject(root, "ip", ip_str); + } + } + + cJSON *capabilities = cJSON_CreateObject(); + if (capabilities) + { + cJSON_AddBoolToObject(capabilities, "wifiScan", false); + cJSON_AddBoolToObject(capabilities, "logs", false); + cJSON_AddBoolToObject(capabilities, "systemReboot", false); + cJSON_AddBoolToObject(capabilities, "systemFactoryReset", false); + cJSON_AddBoolToObject(capabilities, "systemApExit", false); + cJSON_AddBoolToObject(capabilities, "users", true); + cJSON_AddBoolToObject(capabilities, "tags", true); + cJSON_AddBoolToObject(capabilities, "security", true); + cJSON_AddBoolToObject(capabilities, "scheduler", true); + cJSON_AddBoolToObject(capabilities, "loadBalancing", true); + cJSON_AddBoolToObject(capabilities, "ocpp", true); + cJSON_AddBoolToObject(capabilities, "link", true); + cJSON_AddBoolToObject(capabilities, "metersConfig", true); + cJSON_AddItemToObject(root, "capabilities", capabilities); + } + + char *json_str = cJSON_PrintUnformatted(root); + if (!json_str) + { + cJSON_Delete(root); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Falha ao serializar JSON"); + return ESP_FAIL; + } + + ESP_LOGD(TAG, "GET /api/v1/system/info -> %s", json_str); + httpd_resp_sendstr(req, json_str); + + free(json_str); + cJSON_Delete(root); + return ESP_OK; +} + +void register_system_handlers(httpd_handle_t server, void *ctx) +{ + httpd_register_uri_handler(server, &(httpd_uri_t){ + .uri = "/api/v1/system/info", + .method = HTTP_GET, + .handler = system_info_get_handler, + .user_ctx = ctx, + }); +} diff --git a/components/rest_api/webfolder/assets/index-0q0tbwk5.js b/components/rest_api/webfolder/assets/index-0q0tbwk5.js deleted file mode 100644 index 55f3ce6..0000000 --- a/components/rest_api/webfolder/assets/index-0q0tbwk5.js +++ /dev/null @@ -1,51 +0,0 @@ -(function(){const d=document.createElement("link").relList;if(d&&d.supports&&d.supports("modulepreload"))return;for(const m of document.querySelectorAll('link[rel="modulepreload"]'))s(m);new MutationObserver(m=>{for(const y of m)if(y.type==="childList")for(const v of y.addedNodes)v.tagName==="LINK"&&v.rel==="modulepreload"&&s(v)}).observe(document,{childList:!0,subtree:!0});function o(m){const y={};return m.integrity&&(y.integrity=m.integrity),m.referrerPolicy&&(y.referrerPolicy=m.referrerPolicy),m.crossOrigin==="use-credentials"?y.credentials="include":m.crossOrigin==="anonymous"?y.credentials="omit":y.credentials="same-origin",y}function s(m){if(m.ep)return;m.ep=!0;const y=o(m);fetch(m.href,y)}})();function sm(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var Or={exports:{}},Ln={};/** - * @license React - * react-jsx-runtime.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Gd;function ky(){if(Gd)return Ln;Gd=1;var i=Symbol.for("react.transitional.element"),d=Symbol.for("react.fragment");function o(s,m,y){var v=null;if(y!==void 0&&(v=""+y),m.key!==void 0&&(v=""+m.key),"key"in m){y={};for(var R in m)R!=="key"&&(y[R]=m[R])}else y=m;return m=y.ref,{$$typeof:i,type:s,key:v,ref:m!==void 0?m:null,props:y}}return Ln.Fragment=d,Ln.jsx=o,Ln.jsxs=o,Ln}var Xd;function $y(){return Xd||(Xd=1,Or.exports=ky()),Or.exports}var r=$y(),Dr={exports:{}},ne={};/** - * @license React - * react.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Qd;function Wy(){if(Qd)return ne;Qd=1;var i=Symbol.for("react.transitional.element"),d=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),s=Symbol.for("react.strict_mode"),m=Symbol.for("react.profiler"),y=Symbol.for("react.consumer"),v=Symbol.for("react.context"),R=Symbol.for("react.forward_ref"),x=Symbol.for("react.suspense"),h=Symbol.for("react.memo"),j=Symbol.for("react.lazy"),D=Symbol.iterator;function S(p){return p===null||typeof p!="object"?null:(p=D&&p[D]||p["@@iterator"],typeof p=="function"?p:null)}var L={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},U=Object.assign,G={};function Z(p,H,J){this.props=p,this.context=H,this.refs=G,this.updater=J||L}Z.prototype.isReactComponent={},Z.prototype.setState=function(p,H){if(typeof p!="object"&&typeof p!="function"&&p!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,p,H,"setState")},Z.prototype.forceUpdate=function(p){this.updater.enqueueForceUpdate(this,p,"forceUpdate")};function Y(){}Y.prototype=Z.prototype;function q(p,H,J){this.props=p,this.context=H,this.refs=G,this.updater=J||L}var B=q.prototype=new Y;B.constructor=q,U(B,Z.prototype),B.isPureReactComponent=!0;var k=Array.isArray,K={H:null,A:null,T:null,S:null,V:null},fe=Object.prototype.hasOwnProperty;function X(p,H,J,Q,F,de){return J=de.ref,{$$typeof:i,type:p,key:H,ref:J!==void 0?J:null,props:de}}function ie(p,H){return X(p.type,H,void 0,void 0,void 0,p.props)}function ee(p){return typeof p=="object"&&p!==null&&p.$$typeof===i}function Ce(p){var H={"=":"=0",":":"=2"};return"$"+p.replace(/[=:]/g,function(J){return H[J]})}var yt=/\/+/g;function Je(p,H){return typeof p=="object"&&p!==null&&p.key!=null?Ce(""+p.key):H.toString(36)}function Dl(){}function zl(p){switch(p.status){case"fulfilled":return p.value;case"rejected":throw p.reason;default:switch(typeof p.status=="string"?p.then(Dl,Dl):(p.status="pending",p.then(function(H){p.status==="pending"&&(p.status="fulfilled",p.value=H)},function(H){p.status==="pending"&&(p.status="rejected",p.reason=H)})),p.status){case"fulfilled":return p.value;case"rejected":throw p.reason}}throw p}function ke(p,H,J,Q,F){var de=typeof p;(de==="undefined"||de==="boolean")&&(p=null);var ae=!1;if(p===null)ae=!0;else switch(de){case"bigint":case"string":case"number":ae=!0;break;case"object":switch(p.$$typeof){case i:case d:ae=!0;break;case j:return ae=p._init,ke(ae(p._payload),H,J,Q,F)}}if(ae)return F=F(p),ae=Q===""?"."+Je(p,0):Q,k(F)?(J="",ae!=null&&(J=ae.replace(yt,"$&/")+"/"),ke(F,H,J,"",function(nl){return nl})):F!=null&&(ee(F)&&(F=ie(F,J+(F.key==null||p&&p.key===F.key?"":(""+F.key).replace(yt,"$&/")+"/")+ae)),H.push(F)),1;ae=0;var ut=Q===""?".":Q+":";if(k(p))for(var Te=0;Te>>1,p=C[xe];if(0>>1;xem(Q,te))Fm(de,Q)?(C[xe]=de,C[F]=te,xe=F):(C[xe]=Q,C[J]=te,xe=J);else if(Fm(de,te))C[xe]=de,C[F]=te,xe=F;else break e}}return V}function m(C,V){var te=C.sortIndex-V.sortIndex;return te!==0?te:C.id-V.id}if(i.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var y=performance;i.unstable_now=function(){return y.now()}}else{var v=Date,R=v.now();i.unstable_now=function(){return v.now()-R}}var x=[],h=[],j=1,D=null,S=3,L=!1,U=!1,G=!1,Z=!1,Y=typeof setTimeout=="function"?setTimeout:null,q=typeof clearTimeout=="function"?clearTimeout:null,B=typeof setImmediate<"u"?setImmediate:null;function k(C){for(var V=o(h);V!==null;){if(V.callback===null)s(h);else if(V.startTime<=C)s(h),V.sortIndex=V.expirationTime,d(x,V);else break;V=o(h)}}function K(C){if(G=!1,k(C),!U)if(o(x)!==null)U=!0,fe||(fe=!0,Je());else{var V=o(h);V!==null&&ke(K,V.startTime-C)}}var fe=!1,X=-1,ie=5,ee=-1;function Ce(){return Z?!0:!(i.unstable_now()-eeC&&Ce());){var xe=D.callback;if(typeof xe=="function"){D.callback=null,S=D.priorityLevel;var p=xe(D.expirationTime<=C);if(C=i.unstable_now(),typeof p=="function"){D.callback=p,k(C),V=!0;break t}D===o(x)&&s(x),k(C)}else s(x);D=o(x)}if(D!==null)V=!0;else{var H=o(h);H!==null&&ke(K,H.startTime-C),V=!1}}break e}finally{D=null,S=te,L=!1}V=void 0}}finally{V?Je():fe=!1}}}var Je;if(typeof B=="function")Je=function(){B(yt)};else if(typeof MessageChannel<"u"){var Dl=new MessageChannel,zl=Dl.port2;Dl.port1.onmessage=yt,Je=function(){zl.postMessage(null)}}else Je=function(){Y(yt,0)};function ke(C,V){X=Y(function(){C(i.unstable_now())},V)}i.unstable_IdlePriority=5,i.unstable_ImmediatePriority=1,i.unstable_LowPriority=4,i.unstable_NormalPriority=3,i.unstable_Profiling=null,i.unstable_UserBlockingPriority=2,i.unstable_cancelCallback=function(C){C.callback=null},i.unstable_forceFrameRate=function(C){0>C||125xe?(C.sortIndex=te,d(h,C),o(x)===null&&C===o(h)&&(G?(q(X),X=-1):G=!0,ke(K,te-xe))):(C.sortIndex=p,d(x,C),U||L||(U=!0,fe||(fe=!0,Je()))),C},i.unstable_shouldYield=Ce,i.unstable_wrapCallback=function(C){var V=S;return function(){var te=S;S=V;try{return C.apply(this,arguments)}finally{S=te}}}}(_r)),_r}var Kd;function Iy(){return Kd||(Kd=1,Cr.exports=Py()),Cr.exports}var Ur={exports:{}},Fe={};/** - * @license React - * react-dom.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Jd;function e0(){if(Jd)return Fe;Jd=1;var i=qr();function d(x){var h="https://react.dev/errors/"+x;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(d){console.error(d)}}return i(),Ur.exports=e0(),Ur.exports}/** - * @license React - * react-dom-client.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var $d;function l0(){if($d)return Hn;$d=1;var i=Iy(),d=qr(),o=t0();function s(e){var t="https://react.dev/errors/"+e;if(1p||(e.current=xe[p],xe[p]=null,p--)}function Q(e,t){p++,xe[p]=e.current,e.current=t}var F=H(null),de=H(null),ae=H(null),ut=H(null);function Te(e,t){switch(Q(ae,t),Q(de,e),Q(F,null),t.nodeType){case 9:case 11:e=(e=t.documentElement)&&(e=e.namespaceURI)?yd(e):0;break;default:if(e=t.tagName,t=t.namespaceURI)t=yd(t),e=gd(t,e);else switch(e){case"svg":e=1;break;case"math":e=2;break;default:e=0}}J(F),Q(F,e)}function nl(){J(F),J(de),J(ae)}function hi(e){e.memoizedState!==null&&Q(ut,e);var t=F.current,l=gd(t,e.type);t!==l&&(Q(de,e),Q(F,l))}function Zn(e){de.current===e&&(J(F),J(de)),ut.current===e&&(J(ut),zn._currentValue=te)}var yi=Object.prototype.hasOwnProperty,gi=i.unstable_scheduleCallback,vi=i.unstable_cancelCallback,Am=i.unstable_shouldYield,Rm=i.unstable_requestPaint,zt=i.unstable_now,Mm=i.unstable_getCurrentPriorityLevel,Jr=i.unstable_ImmediatePriority,kr=i.unstable_UserBlockingPriority,Vn=i.unstable_NormalPriority,Om=i.unstable_LowPriority,$r=i.unstable_IdlePriority,Dm=i.log,zm=i.unstable_setDisableYieldValue,Ba=null,it=null;function ul(e){if(typeof Dm=="function"&&zm(e),it&&typeof it.setStrictMode=="function")try{it.setStrictMode(Ba,e)}catch{}}var ct=Math.clz32?Math.clz32:Um,Cm=Math.log,_m=Math.LN2;function Um(e){return e>>>=0,e===0?32:31-(Cm(e)/_m|0)|0}var Kn=256,Jn=4194304;function Cl(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194048;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function kn(e,t,l){var a=e.pendingLanes;if(a===0)return 0;var n=0,u=e.suspendedLanes,c=e.pingedLanes;e=e.warmLanes;var f=a&134217727;return f!==0?(a=f&~u,a!==0?n=Cl(a):(c&=f,c!==0?n=Cl(c):l||(l=f&~e,l!==0&&(n=Cl(l))))):(f=a&~u,f!==0?n=Cl(f):c!==0?n=Cl(c):l||(l=a&~e,l!==0&&(n=Cl(l)))),n===0?0:t!==0&&t!==n&&(t&u)===0&&(u=n&-n,l=t&-t,u>=l||u===32&&(l&4194048)!==0)?t:n}function qa(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function wm(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Wr(){var e=Kn;return Kn<<=1,(Kn&4194048)===0&&(Kn=256),e}function Fr(){var e=Jn;return Jn<<=1,(Jn&62914560)===0&&(Jn=4194304),e}function pi(e){for(var t=[],l=0;31>l;l++)t.push(e);return t}function Ya(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function Lm(e,t,l,a,n,u){var c=e.pendingLanes;e.pendingLanes=l,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=l,e.entangledLanes&=l,e.errorRecoveryDisabledLanes&=l,e.shellSuspendCounter=0;var f=e.entanglements,g=e.expirationTimes,A=e.hiddenUpdates;for(l=c&~l;0)":-1n||g[a]!==A[n]){var z=` -`+g[a].replace(" at new "," at ");return e.displayName&&z.includes("")&&(z=z.replace("",e.displayName)),z}while(1<=a&&0<=n);break}}}finally{ji=!1,Error.prepareStackTrace=l}return(l=e?e.displayName||e.name:"")?ta(l):""}function Xm(e){switch(e.tag){case 26:case 27:case 5:return ta(e.type);case 16:return ta("Lazy");case 13:return ta("Suspense");case 19:return ta("SuspenseList");case 0:case 15:return Ni(e.type,!1);case 11:return Ni(e.type.render,!1);case 1:return Ni(e.type,!0);case 31:return ta("Activity");default:return""}}function cs(e){try{var t="";do t+=Xm(e),e=e.return;while(e);return t}catch(l){return` -Error generating stack: `+l.message+` -`+l.stack}}function gt(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function rs(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function Qm(e){var t=rs(e)?"checked":"value",l=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),a=""+e[t];if(!e.hasOwnProperty(t)&&typeof l<"u"&&typeof l.get=="function"&&typeof l.set=="function"){var n=l.get,u=l.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return n.call(this)},set:function(c){a=""+c,u.call(this,c)}}),Object.defineProperty(e,t,{enumerable:l.enumerable}),{getValue:function(){return a},setValue:function(c){a=""+c},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Fn(e){e._valueTracker||(e._valueTracker=Qm(e))}function ss(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var l=t.getValue(),a="";return e&&(a=rs(e)?e.checked?"true":"false":e.value),e=a,e!==l?(t.setValue(e),!0):!1}function Pn(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var Zm=/[\n"\\]/g;function vt(e){return e.replace(Zm,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Ai(e,t,l,a,n,u,c,f){e.name="",c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"?e.type=c:e.removeAttribute("type"),t!=null?c==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+gt(t)):e.value!==""+gt(t)&&(e.value=""+gt(t)):c!=="submit"&&c!=="reset"||e.removeAttribute("value"),t!=null?Ri(e,c,gt(t)):l!=null?Ri(e,c,gt(l)):a!=null&&e.removeAttribute("value"),n==null&&u!=null&&(e.defaultChecked=!!u),n!=null&&(e.checked=n&&typeof n!="function"&&typeof n!="symbol"),f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?e.name=""+gt(f):e.removeAttribute("name")}function fs(e,t,l,a,n,u,c,f){if(u!=null&&typeof u!="function"&&typeof u!="symbol"&&typeof u!="boolean"&&(e.type=u),t!=null||l!=null){if(!(u!=="submit"&&u!=="reset"||t!=null))return;l=l!=null?""+gt(l):"",t=t!=null?""+gt(t):l,f||t===e.value||(e.value=t),e.defaultValue=t}a=a??n,a=typeof a!="function"&&typeof a!="symbol"&&!!a,e.checked=f?e.checked:!!a,e.defaultChecked=!!a,c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"&&(e.name=c)}function Ri(e,t,l){t==="number"&&Pn(e.ownerDocument)===e||e.defaultValue===""+l||(e.defaultValue=""+l)}function la(e,t,l,a){if(e=e.options,t){t={};for(var n=0;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Ci=!1;if(Gt)try{var Za={};Object.defineProperty(Za,"passive",{get:function(){Ci=!0}}),window.addEventListener("test",Za,Za),window.removeEventListener("test",Za,Za)}catch{Ci=!1}var cl=null,_i=null,eu=null;function vs(){if(eu)return eu;var e,t=_i,l=t.length,a,n="value"in cl?cl.value:cl.textContent,u=n.length;for(e=0;e=Ja),Ts=" ",js=!1;function Ns(e,t){switch(e){case"keyup":return ph.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function As(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ia=!1;function xh(e,t){switch(e){case"compositionend":return As(t);case"keypress":return t.which!==32?null:(js=!0,Ts);case"textInput":return e=t.data,e===Ts&&js?null:e;default:return null}}function Sh(e,t){if(ia)return e==="compositionend"||!Bi&&Ns(e,t)?(e=vs(),eu=_i=cl=null,ia=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:l,offset:t-e};e=a}e:{for(;l;){if(l.nextSibling){l=l.nextSibling;break e}l=l.parentNode}l=void 0}l=Us(l)}}function Ls(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Ls(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function Hs(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=Pn(e.document);t instanceof e.HTMLIFrameElement;){try{var l=typeof t.contentWindow.location.href=="string"}catch{l=!1}if(l)e=t.contentWindow;else break;t=Pn(e.document)}return t}function Gi(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var Oh=Gt&&"documentMode"in document&&11>=document.documentMode,ca=null,Xi=null,Fa=null,Qi=!1;function Bs(e,t,l){var a=l.window===l?l.document:l.nodeType===9?l:l.ownerDocument;Qi||ca==null||ca!==Pn(a)||(a=ca,"selectionStart"in a&&Gi(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Fa&&Wa(Fa,a)||(Fa=a,a=Zu(Xi,"onSelect"),0>=c,n-=c,Qt=1<<32-ct(t)+n|l<u?u:8;var c=C.T,f={};C.T=f,Mc(e,!1,t,l);try{var g=n(),A=C.S;if(A!==null&&A(f,g),g!==null&&typeof g=="object"&&typeof g.then=="function"){var z=Bh(g,a);mn(e,t,z,mt(e))}else mn(e,t,a,mt(e))}catch(w){mn(e,t,{then:function(){},status:"rejected",reason:w},mt())}finally{V.p=u,C.T=c}}function Qh(){}function Ac(e,t,l,a){if(e.tag!==5)throw Error(s(476));var n=Yf(e).queue;qf(e,n,t,te,l===null?Qh:function(){return Gf(e),l(a)})}function Yf(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:te,baseState:te,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Jt,lastRenderedState:te},next:null};var l={};return t.next={memoizedState:l,baseState:l,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Jt,lastRenderedState:l},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function Gf(e){var t=Yf(e).next.queue;mn(e,t,{},mt())}function Rc(){return We(zn)}function Xf(){return Ue().memoizedState}function Qf(){return Ue().memoizedState}function Zh(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var l=mt();e=fl(l);var a=ol(t,e,l);a!==null&&(ht(a,t,l),cn(a,t,l)),t={cache:ac()},e.payload=t;return}t=t.return}}function Vh(e,t,l){var a=mt();l={lane:a,revertLane:0,action:l,hasEagerState:!1,eagerState:null,next:null},ju(e)?Vf(t,l):(l=Ji(e,t,l,a),l!==null&&(ht(l,e,a),Kf(l,t,a)))}function Zf(e,t,l){var a=mt();mn(e,t,l,a)}function mn(e,t,l,a){var n={lane:a,revertLane:0,action:l,hasEagerState:!1,eagerState:null,next:null};if(ju(e))Vf(t,n);else{var u=e.alternate;if(e.lanes===0&&(u===null||u.lanes===0)&&(u=t.lastRenderedReducer,u!==null))try{var c=t.lastRenderedState,f=u(c,l);if(n.hasEagerState=!0,n.eagerState=f,rt(f,c))return cu(e,t,n,0),Ee===null&&iu(),!1}catch{}finally{}if(l=Ji(e,t,n,a),l!==null)return ht(l,e,a),Kf(l,t,a),!0}return!1}function Mc(e,t,l,a){if(a={lane:2,revertLane:ir(),action:a,hasEagerState:!1,eagerState:null,next:null},ju(e)){if(t)throw Error(s(479))}else t=Ji(e,l,a,2),t!==null&&ht(t,e,2)}function ju(e){var t=e.alternate;return e===ue||t!==null&&t===ue}function Vf(e,t){va=pu=!0;var l=e.pending;l===null?t.next=t:(t.next=l.next,l.next=t),e.pending=t}function Kf(e,t,l){if((l&4194048)!==0){var a=t.lanes;a&=e.pendingLanes,l|=a,t.lanes=l,Ir(e,l)}}var Nu={readContext:We,use:xu,useCallback:De,useContext:De,useEffect:De,useImperativeHandle:De,useLayoutEffect:De,useInsertionEffect:De,useMemo:De,useReducer:De,useRef:De,useState:De,useDebugValue:De,useDeferredValue:De,useTransition:De,useSyncExternalStore:De,useId:De,useHostTransitionStatus:De,useFormState:De,useActionState:De,useOptimistic:De,useMemoCache:De,useCacheRefresh:De},Jf={readContext:We,use:xu,useCallback:function(e,t){return tt().memoizedState=[e,t===void 0?null:t],e},useContext:We,useEffect:Df,useImperativeHandle:function(e,t,l){l=l!=null?l.concat([e]):null,Tu(4194308,4,Uf.bind(null,t,e),l)},useLayoutEffect:function(e,t){return Tu(4194308,4,e,t)},useInsertionEffect:function(e,t){Tu(4,2,e,t)},useMemo:function(e,t){var l=tt();t=t===void 0?null:t;var a=e();if(Vl){ul(!0);try{e()}finally{ul(!1)}}return l.memoizedState=[a,t],a},useReducer:function(e,t,l){var a=tt();if(l!==void 0){var n=l(t);if(Vl){ul(!0);try{l(t)}finally{ul(!1)}}}else n=t;return a.memoizedState=a.baseState=n,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},a.queue=e,e=e.dispatch=Vh.bind(null,ue,e),[a.memoizedState,e]},useRef:function(e){var t=tt();return e={current:e},t.memoizedState=e},useState:function(e){e=Ec(e);var t=e.queue,l=Zf.bind(null,ue,t);return t.dispatch=l,[e.memoizedState,l]},useDebugValue:jc,useDeferredValue:function(e,t){var l=tt();return Nc(l,e,t)},useTransition:function(){var e=Ec(!1);return e=qf.bind(null,ue,e.queue,!0,!1),tt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,l){var a=ue,n=tt();if(he){if(l===void 0)throw Error(s(407));l=l()}else{if(l=t(),Ee===null)throw Error(s(349));(oe&124)!==0||hf(a,t,l)}n.memoizedState=l;var u={value:l,getSnapshot:t};return n.queue=u,Df(gf.bind(null,a,u,e),[e]),a.flags|=2048,ba(9,Eu(),yf.bind(null,a,u,l,t),null),l},useId:function(){var e=tt(),t=Ee.identifierPrefix;if(he){var l=Zt,a=Qt;l=(a&~(1<<32-ct(a)-1)).toString(32)+l,t="«"+t+"R"+l,l=bu++,0I?(Ge=W,W=null):Ge=W.sibling;var me=M(T,W,N[I],_);if(me===null){W===null&&(W=Ge);break}e&&W&&me.alternate===null&&t(T,W),b=u(me,b,I),ce===null?$=me:ce.sibling=me,ce=me,W=Ge}if(I===N.length)return l(T,W),he&&ql(T,I),$;if(W===null){for(;II?(Ge=W,W=null):Ge=W.sibling;var Ml=M(T,W,me.value,_);if(Ml===null){W===null&&(W=Ge);break}e&&W&&Ml.alternate===null&&t(T,W),b=u(Ml,b,I),ce===null?$=Ml:ce.sibling=Ml,ce=Ml,W=Ge}if(me.done)return l(T,W),he&&ql(T,I),$;if(W===null){for(;!me.done;I++,me=N.next())me=w(T,me.value,_),me!==null&&(b=u(me,b,I),ce===null?$=me:ce.sibling=me,ce=me);return he&&ql(T,I),$}for(W=a(W);!me.done;I++,me=N.next())me=O(W,T,I,me.value,_),me!==null&&(e&&me.alternate!==null&&W.delete(me.key===null?I:me.key),b=u(me,b,I),ce===null?$=me:ce.sibling=me,ce=me);return e&&W.forEach(function(Jy){return t(T,Jy)}),he&&ql(T,I),$}function be(T,b,N,_){if(typeof N=="object"&&N!==null&&N.type===U&&N.key===null&&(N=N.props.children),typeof N=="object"&&N!==null){switch(N.$$typeof){case S:e:{for(var $=N.key;b!==null;){if(b.key===$){if($=N.type,$===U){if(b.tag===7){l(T,b.sibling),_=n(b,N.props.children),_.return=T,T=_;break e}}else if(b.elementType===$||typeof $=="object"&&$!==null&&$.$$typeof===ie&&$f($)===b.type){l(T,b.sibling),_=n(b,N.props),yn(_,N),_.return=T,T=_;break e}l(T,b);break}else t(T,b);b=b.sibling}N.type===U?(_=Hl(N.props.children,T.mode,_,N.key),_.return=T,T=_):(_=su(N.type,N.key,N.props,null,T.mode,_),yn(_,N),_.return=T,T=_)}return c(T);case L:e:{for($=N.key;b!==null;){if(b.key===$)if(b.tag===4&&b.stateNode.containerInfo===N.containerInfo&&b.stateNode.implementation===N.implementation){l(T,b.sibling),_=n(b,N.children||[]),_.return=T,T=_;break e}else{l(T,b);break}else t(T,b);b=b.sibling}_=Wi(N,T.mode,_),_.return=T,T=_}return c(T);case ie:return $=N._init,N=$(N._payload),be(T,b,N,_)}if(ke(N))return le(T,b,N,_);if(Je(N)){if($=Je(N),typeof $!="function")throw Error(s(150));return N=$.call(N),P(T,b,N,_)}if(typeof N.then=="function")return be(T,b,Au(N),_);if(N.$$typeof===B)return be(T,b,mu(T,N),_);Ru(T,N)}return typeof N=="string"&&N!==""||typeof N=="number"||typeof N=="bigint"?(N=""+N,b!==null&&b.tag===6?(l(T,b.sibling),_=n(b,N),_.return=T,T=_):(l(T,b),_=$i(N,T.mode,_),_.return=T,T=_),c(T)):l(T,b)}return function(T,b,N,_){try{hn=0;var $=be(T,b,N,_);return xa=null,$}catch(W){if(W===nn||W===yu)throw W;var ce=st(29,W,null,T.mode);return ce.lanes=_,ce.return=T,ce}finally{}}}var Sa=Wf(!0),Ff=Wf(!1),Et=H(null),_t=null;function ml(e){var t=e.alternate;Q(Le,Le.current&1),Q(Et,e),_t===null&&(t===null||ga.current!==null||t.memoizedState!==null)&&(_t=e)}function Pf(e){if(e.tag===22){if(Q(Le,Le.current),Q(Et,e),_t===null){var t=e.alternate;t!==null&&t.memoizedState!==null&&(_t=e)}}else hl()}function hl(){Q(Le,Le.current),Q(Et,Et.current)}function kt(e){J(Et),_t===e&&(_t=null),J(Le)}var Le=H(0);function Mu(e){for(var t=e;t!==null;){if(t.tag===13){var l=t.memoizedState;if(l!==null&&(l=l.dehydrated,l===null||l.data==="$?"||pr(l)))return t}else if(t.tag===19&&t.memoizedProps.revealOrder!==void 0){if((t.flags&128)!==0)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function Oc(e,t,l,a){t=e.memoizedState,l=l(a,t),l=l==null?t:j({},t,l),e.memoizedState=l,e.lanes===0&&(e.updateQueue.baseState=l)}var Dc={enqueueSetState:function(e,t,l){e=e._reactInternals;var a=mt(),n=fl(a);n.payload=t,l!=null&&(n.callback=l),t=ol(e,n,a),t!==null&&(ht(t,e,a),cn(t,e,a))},enqueueReplaceState:function(e,t,l){e=e._reactInternals;var a=mt(),n=fl(a);n.tag=1,n.payload=t,l!=null&&(n.callback=l),t=ol(e,n,a),t!==null&&(ht(t,e,a),cn(t,e,a))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var l=mt(),a=fl(l);a.tag=2,t!=null&&(a.callback=t),t=ol(e,a,l),t!==null&&(ht(t,e,l),cn(t,e,l))}};function If(e,t,l,a,n,u,c){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(a,u,c):t.prototype&&t.prototype.isPureReactComponent?!Wa(l,a)||!Wa(n,u):!0}function eo(e,t,l,a){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(l,a),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(l,a),t.state!==e&&Dc.enqueueReplaceState(t,t.state,null)}function Kl(e,t){var l=t;if("ref"in t){l={};for(var a in t)a!=="ref"&&(l[a]=t[a])}if(e=e.defaultProps){l===t&&(l=j({},l));for(var n in e)l[n]===void 0&&(l[n]=e[n])}return l}var Ou=typeof reportError=="function"?reportError:function(e){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof e=="object"&&e!==null&&typeof e.message=="string"?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",e);return}console.error(e)};function to(e){Ou(e)}function lo(e){console.error(e)}function ao(e){Ou(e)}function Du(e,t){try{var l=e.onUncaughtError;l(t.value,{componentStack:t.stack})}catch(a){setTimeout(function(){throw a})}}function no(e,t,l){try{var a=e.onCaughtError;a(l.value,{componentStack:l.stack,errorBoundary:t.tag===1?t.stateNode:null})}catch(n){setTimeout(function(){throw n})}}function zc(e,t,l){return l=fl(l),l.tag=3,l.payload={element:null},l.callback=function(){Du(e,t)},l}function uo(e){return e=fl(e),e.tag=3,e}function io(e,t,l,a){var n=l.type.getDerivedStateFromError;if(typeof n=="function"){var u=a.value;e.payload=function(){return n(u)},e.callback=function(){no(t,l,a)}}var c=l.stateNode;c!==null&&typeof c.componentDidCatch=="function"&&(e.callback=function(){no(t,l,a),typeof n!="function"&&(xl===null?xl=new Set([this]):xl.add(this));var f=a.stack;this.componentDidCatch(a.value,{componentStack:f!==null?f:""})})}function Jh(e,t,l,a,n){if(l.flags|=32768,a!==null&&typeof a=="object"&&typeof a.then=="function"){if(t=l.alternate,t!==null&&tn(t,l,n,!0),l=Et.current,l!==null){switch(l.tag){case 13:return _t===null?tr():l.alternate===null&&Me===0&&(Me=3),l.flags&=-257,l.flags|=65536,l.lanes=n,a===ic?l.flags|=16384:(t=l.updateQueue,t===null?l.updateQueue=new Set([a]):t.add(a),ar(e,a,n)),!1;case 22:return l.flags|=65536,a===ic?l.flags|=16384:(t=l.updateQueue,t===null?(t={transitions:null,markerInstances:null,retryQueue:new Set([a])},l.updateQueue=t):(l=t.retryQueue,l===null?t.retryQueue=new Set([a]):l.add(a)),ar(e,a,n)),!1}throw Error(s(435,l.tag))}return ar(e,a,n),tr(),!1}if(he)return t=Et.current,t!==null?((t.flags&65536)===0&&(t.flags|=256),t.flags|=65536,t.lanes=n,a!==Ii&&(e=Error(s(422),{cause:a}),en(pt(e,l)))):(a!==Ii&&(t=Error(s(423),{cause:a}),en(pt(t,l))),e=e.current.alternate,e.flags|=65536,n&=-n,e.lanes|=n,a=pt(a,l),n=zc(e.stateNode,a,n),sc(e,n),Me!==4&&(Me=2)),!1;var u=Error(s(520),{cause:a});if(u=pt(u,l),En===null?En=[u]:En.push(u),Me!==4&&(Me=2),t===null)return!0;a=pt(a,l),l=t;do{switch(l.tag){case 3:return l.flags|=65536,e=n&-n,l.lanes|=e,e=zc(l.stateNode,a,e),sc(l,e),!1;case 1:if(t=l.type,u=l.stateNode,(l.flags&128)===0&&(typeof t.getDerivedStateFromError=="function"||u!==null&&typeof u.componentDidCatch=="function"&&(xl===null||!xl.has(u))))return l.flags|=65536,n&=-n,l.lanes|=n,n=uo(n),io(n,e,l,a),sc(l,n),!1}l=l.return}while(l!==null);return!1}var co=Error(s(461)),qe=!1;function Ze(e,t,l,a){t.child=e===null?Ff(t,null,l,a):Sa(t,e.child,l,a)}function ro(e,t,l,a,n){l=l.render;var u=t.ref;if("ref"in a){var c={};for(var f in a)f!=="ref"&&(c[f]=a[f])}else c=a;return Ql(t),a=hc(e,t,l,c,u,n),f=yc(),e!==null&&!qe?(gc(e,t,n),$t(e,t,n)):(he&&f&&Fi(t),t.flags|=1,Ze(e,t,a,n),t.child)}function so(e,t,l,a,n){if(e===null){var u=l.type;return typeof u=="function"&&!ki(u)&&u.defaultProps===void 0&&l.compare===null?(t.tag=15,t.type=u,fo(e,t,u,a,n)):(e=su(l.type,null,a,t,t.mode,n),e.ref=t.ref,e.return=t,t.child=e)}if(u=e.child,!qc(e,n)){var c=u.memoizedProps;if(l=l.compare,l=l!==null?l:Wa,l(c,a)&&e.ref===t.ref)return $t(e,t,n)}return t.flags|=1,e=Xt(u,a),e.ref=t.ref,e.return=t,t.child=e}function fo(e,t,l,a,n){if(e!==null){var u=e.memoizedProps;if(Wa(u,a)&&e.ref===t.ref)if(qe=!1,t.pendingProps=a=u,qc(e,n))(e.flags&131072)!==0&&(qe=!0);else return t.lanes=e.lanes,$t(e,t,n)}return Cc(e,t,l,a,n)}function oo(e,t,l){var a=t.pendingProps,n=a.children,u=e!==null?e.memoizedState:null;if(a.mode==="hidden"){if((t.flags&128)!==0){if(a=u!==null?u.baseLanes|l:l,e!==null){for(n=t.child=e.child,u=0;n!==null;)u=u|n.lanes|n.childLanes,n=n.sibling;t.childLanes=u&~a}else t.childLanes=0,t.child=null;return mo(e,t,a,l)}if((l&536870912)!==0)t.memoizedState={baseLanes:0,cachePool:null},e!==null&&hu(t,u!==null?u.cachePool:null),u!==null?ff(t,u):oc(),Pf(t);else return t.lanes=t.childLanes=536870912,mo(e,t,u!==null?u.baseLanes|l:l,l)}else u!==null?(hu(t,u.cachePool),ff(t,u),hl(),t.memoizedState=null):(e!==null&&hu(t,null),oc(),hl());return Ze(e,t,n,l),t.child}function mo(e,t,l,a){var n=uc();return n=n===null?null:{parent:we._currentValue,pool:n},t.memoizedState={baseLanes:l,cachePool:n},e!==null&&hu(t,null),oc(),Pf(t),e!==null&&tn(e,t,a,!0),null}function zu(e,t){var l=t.ref;if(l===null)e!==null&&e.ref!==null&&(t.flags|=4194816);else{if(typeof l!="function"&&typeof l!="object")throw Error(s(284));(e===null||e.ref!==l)&&(t.flags|=4194816)}}function Cc(e,t,l,a,n){return Ql(t),l=hc(e,t,l,a,void 0,n),a=yc(),e!==null&&!qe?(gc(e,t,n),$t(e,t,n)):(he&&a&&Fi(t),t.flags|=1,Ze(e,t,l,n),t.child)}function ho(e,t,l,a,n,u){return Ql(t),t.updateQueue=null,l=df(t,a,l,n),of(e),a=yc(),e!==null&&!qe?(gc(e,t,u),$t(e,t,u)):(he&&a&&Fi(t),t.flags|=1,Ze(e,t,l,u),t.child)}function yo(e,t,l,a,n){if(Ql(t),t.stateNode===null){var u=oa,c=l.contextType;typeof c=="object"&&c!==null&&(u=We(c)),u=new l(a,u),t.memoizedState=u.state!==null&&u.state!==void 0?u.state:null,u.updater=Dc,t.stateNode=u,u._reactInternals=t,u=t.stateNode,u.props=a,u.state=t.memoizedState,u.refs={},cc(t),c=l.contextType,u.context=typeof c=="object"&&c!==null?We(c):oa,u.state=t.memoizedState,c=l.getDerivedStateFromProps,typeof c=="function"&&(Oc(t,l,c,a),u.state=t.memoizedState),typeof l.getDerivedStateFromProps=="function"||typeof u.getSnapshotBeforeUpdate=="function"||typeof u.UNSAFE_componentWillMount!="function"&&typeof u.componentWillMount!="function"||(c=u.state,typeof u.componentWillMount=="function"&&u.componentWillMount(),typeof u.UNSAFE_componentWillMount=="function"&&u.UNSAFE_componentWillMount(),c!==u.state&&Dc.enqueueReplaceState(u,u.state,null),sn(t,a,u,n),rn(),u.state=t.memoizedState),typeof u.componentDidMount=="function"&&(t.flags|=4194308),a=!0}else if(e===null){u=t.stateNode;var f=t.memoizedProps,g=Kl(l,f);u.props=g;var A=u.context,z=l.contextType;c=oa,typeof z=="object"&&z!==null&&(c=We(z));var w=l.getDerivedStateFromProps;z=typeof w=="function"||typeof u.getSnapshotBeforeUpdate=="function",f=t.pendingProps!==f,z||typeof u.UNSAFE_componentWillReceiveProps!="function"&&typeof u.componentWillReceiveProps!="function"||(f||A!==c)&&eo(t,u,a,c),sl=!1;var M=t.memoizedState;u.state=M,sn(t,a,u,n),rn(),A=t.memoizedState,f||M!==A||sl?(typeof w=="function"&&(Oc(t,l,w,a),A=t.memoizedState),(g=sl||If(t,l,g,a,M,A,c))?(z||typeof u.UNSAFE_componentWillMount!="function"&&typeof u.componentWillMount!="function"||(typeof u.componentWillMount=="function"&&u.componentWillMount(),typeof u.UNSAFE_componentWillMount=="function"&&u.UNSAFE_componentWillMount()),typeof u.componentDidMount=="function"&&(t.flags|=4194308)):(typeof u.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=a,t.memoizedState=A),u.props=a,u.state=A,u.context=c,a=g):(typeof u.componentDidMount=="function"&&(t.flags|=4194308),a=!1)}else{u=t.stateNode,rc(e,t),c=t.memoizedProps,z=Kl(l,c),u.props=z,w=t.pendingProps,M=u.context,A=l.contextType,g=oa,typeof A=="object"&&A!==null&&(g=We(A)),f=l.getDerivedStateFromProps,(A=typeof f=="function"||typeof u.getSnapshotBeforeUpdate=="function")||typeof u.UNSAFE_componentWillReceiveProps!="function"&&typeof u.componentWillReceiveProps!="function"||(c!==w||M!==g)&&eo(t,u,a,g),sl=!1,M=t.memoizedState,u.state=M,sn(t,a,u,n),rn();var O=t.memoizedState;c!==w||M!==O||sl||e!==null&&e.dependencies!==null&&du(e.dependencies)?(typeof f=="function"&&(Oc(t,l,f,a),O=t.memoizedState),(z=sl||If(t,l,z,a,M,O,g)||e!==null&&e.dependencies!==null&&du(e.dependencies))?(A||typeof u.UNSAFE_componentWillUpdate!="function"&&typeof u.componentWillUpdate!="function"||(typeof u.componentWillUpdate=="function"&&u.componentWillUpdate(a,O,g),typeof u.UNSAFE_componentWillUpdate=="function"&&u.UNSAFE_componentWillUpdate(a,O,g)),typeof u.componentDidUpdate=="function"&&(t.flags|=4),typeof u.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof u.componentDidUpdate!="function"||c===e.memoizedProps&&M===e.memoizedState||(t.flags|=4),typeof u.getSnapshotBeforeUpdate!="function"||c===e.memoizedProps&&M===e.memoizedState||(t.flags|=1024),t.memoizedProps=a,t.memoizedState=O),u.props=a,u.state=O,u.context=g,a=z):(typeof u.componentDidUpdate!="function"||c===e.memoizedProps&&M===e.memoizedState||(t.flags|=4),typeof u.getSnapshotBeforeUpdate!="function"||c===e.memoizedProps&&M===e.memoizedState||(t.flags|=1024),a=!1)}return u=a,zu(e,t),a=(t.flags&128)!==0,u||a?(u=t.stateNode,l=a&&typeof l.getDerivedStateFromError!="function"?null:u.render(),t.flags|=1,e!==null&&a?(t.child=Sa(t,e.child,null,n),t.child=Sa(t,null,l,n)):Ze(e,t,l,n),t.memoizedState=u.state,e=t.child):e=$t(e,t,n),e}function go(e,t,l,a){return Ia(),t.flags|=256,Ze(e,t,l,a),t.child}var _c={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function Uc(e){return{baseLanes:e,cachePool:tf()}}function wc(e,t,l){return e=e!==null?e.childLanes&~l:0,t&&(e|=Tt),e}function vo(e,t,l){var a=t.pendingProps,n=!1,u=(t.flags&128)!==0,c;if((c=u)||(c=e!==null&&e.memoizedState===null?!1:(Le.current&2)!==0),c&&(n=!0,t.flags&=-129),c=(t.flags&32)!==0,t.flags&=-33,e===null){if(he){if(n?ml(t):hl(),he){var f=Re,g;if(g=f){e:{for(g=f,f=Ct;g.nodeType!==8;){if(!f){f=null;break e}if(g=Ot(g.nextSibling),g===null){f=null;break e}}f=g}f!==null?(t.memoizedState={dehydrated:f,treeContext:Bl!==null?{id:Qt,overflow:Zt}:null,retryLane:536870912,hydrationErrors:null},g=st(18,null,null,0),g.stateNode=f,g.return=t,t.child=g,Pe=t,Re=null,g=!0):g=!1}g||Gl(t)}if(f=t.memoizedState,f!==null&&(f=f.dehydrated,f!==null))return pr(f)?t.lanes=32:t.lanes=536870912,null;kt(t)}return f=a.children,a=a.fallback,n?(hl(),n=t.mode,f=Cu({mode:"hidden",children:f},n),a=Hl(a,n,l,null),f.return=t,a.return=t,f.sibling=a,t.child=f,n=t.child,n.memoizedState=Uc(l),n.childLanes=wc(e,c,l),t.memoizedState=_c,a):(ml(t),Lc(t,f))}if(g=e.memoizedState,g!==null&&(f=g.dehydrated,f!==null)){if(u)t.flags&256?(ml(t),t.flags&=-257,t=Hc(e,t,l)):t.memoizedState!==null?(hl(),t.child=e.child,t.flags|=128,t=null):(hl(),n=a.fallback,f=t.mode,a=Cu({mode:"visible",children:a.children},f),n=Hl(n,f,l,null),n.flags|=2,a.return=t,n.return=t,a.sibling=n,t.child=a,Sa(t,e.child,null,l),a=t.child,a.memoizedState=Uc(l),a.childLanes=wc(e,c,l),t.memoizedState=_c,t=n);else if(ml(t),pr(f)){if(c=f.nextSibling&&f.nextSibling.dataset,c)var A=c.dgst;c=A,a=Error(s(419)),a.stack="",a.digest=c,en({value:a,source:null,stack:null}),t=Hc(e,t,l)}else if(qe||tn(e,t,l,!1),c=(l&e.childLanes)!==0,qe||c){if(c=Ee,c!==null&&(a=l&-l,a=(a&42)!==0?1:bi(a),a=(a&(c.suspendedLanes|l))!==0?0:a,a!==0&&a!==g.retryLane))throw g.retryLane=a,fa(e,a),ht(c,e,a),co;f.data==="$?"||tr(),t=Hc(e,t,l)}else f.data==="$?"?(t.flags|=192,t.child=e.child,t=null):(e=g.treeContext,Re=Ot(f.nextSibling),Pe=t,he=!0,Yl=null,Ct=!1,e!==null&&(xt[St++]=Qt,xt[St++]=Zt,xt[St++]=Bl,Qt=e.id,Zt=e.overflow,Bl=t),t=Lc(t,a.children),t.flags|=4096);return t}return n?(hl(),n=a.fallback,f=t.mode,g=e.child,A=g.sibling,a=Xt(g,{mode:"hidden",children:a.children}),a.subtreeFlags=g.subtreeFlags&65011712,A!==null?n=Xt(A,n):(n=Hl(n,f,l,null),n.flags|=2),n.return=t,a.return=t,a.sibling=n,t.child=a,a=n,n=t.child,f=e.child.memoizedState,f===null?f=Uc(l):(g=f.cachePool,g!==null?(A=we._currentValue,g=g.parent!==A?{parent:A,pool:A}:g):g=tf(),f={baseLanes:f.baseLanes|l,cachePool:g}),n.memoizedState=f,n.childLanes=wc(e,c,l),t.memoizedState=_c,a):(ml(t),l=e.child,e=l.sibling,l=Xt(l,{mode:"visible",children:a.children}),l.return=t,l.sibling=null,e!==null&&(c=t.deletions,c===null?(t.deletions=[e],t.flags|=16):c.push(e)),t.child=l,t.memoizedState=null,l)}function Lc(e,t){return t=Cu({mode:"visible",children:t},e.mode),t.return=e,e.child=t}function Cu(e,t){return e=st(22,e,null,t),e.lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function Hc(e,t,l){return Sa(t,e.child,null,l),e=Lc(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function po(e,t,l){e.lanes|=t;var a=e.alternate;a!==null&&(a.lanes|=t),tc(e.return,t,l)}function Bc(e,t,l,a,n){var u=e.memoizedState;u===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:a,tail:l,tailMode:n}:(u.isBackwards=t,u.rendering=null,u.renderingStartTime=0,u.last=a,u.tail=l,u.tailMode=n)}function bo(e,t,l){var a=t.pendingProps,n=a.revealOrder,u=a.tail;if(Ze(e,t,a.children,l),a=Le.current,(a&2)!==0)a=a&1|2,t.flags|=128;else{if(e!==null&&(e.flags&128)!==0)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&po(e,l,t);else if(e.tag===19)po(e,l,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}a&=1}switch(Q(Le,a),n){case"forwards":for(l=t.child,n=null;l!==null;)e=l.alternate,e!==null&&Mu(e)===null&&(n=l),l=l.sibling;l=n,l===null?(n=t.child,t.child=null):(n=l.sibling,l.sibling=null),Bc(t,!1,n,l,u);break;case"backwards":for(l=null,n=t.child,t.child=null;n!==null;){if(e=n.alternate,e!==null&&Mu(e)===null){t.child=n;break}e=n.sibling,n.sibling=l,l=n,n=e}Bc(t,!0,l,null,u);break;case"together":Bc(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function $t(e,t,l){if(e!==null&&(t.dependencies=e.dependencies),bl|=t.lanes,(l&t.childLanes)===0)if(e!==null){if(tn(e,t,l,!1),(l&t.childLanes)===0)return null}else return null;if(e!==null&&t.child!==e.child)throw Error(s(153));if(t.child!==null){for(e=t.child,l=Xt(e,e.pendingProps),t.child=l,l.return=t;e.sibling!==null;)e=e.sibling,l=l.sibling=Xt(e,e.pendingProps),l.return=t;l.sibling=null}return t.child}function qc(e,t){return(e.lanes&t)!==0?!0:(e=e.dependencies,!!(e!==null&&du(e)))}function kh(e,t,l){switch(t.tag){case 3:Te(t,t.stateNode.containerInfo),rl(t,we,e.memoizedState.cache),Ia();break;case 27:case 5:hi(t);break;case 4:Te(t,t.stateNode.containerInfo);break;case 10:rl(t,t.type,t.memoizedProps.value);break;case 13:var a=t.memoizedState;if(a!==null)return a.dehydrated!==null?(ml(t),t.flags|=128,null):(l&t.child.childLanes)!==0?vo(e,t,l):(ml(t),e=$t(e,t,l),e!==null?e.sibling:null);ml(t);break;case 19:var n=(e.flags&128)!==0;if(a=(l&t.childLanes)!==0,a||(tn(e,t,l,!1),a=(l&t.childLanes)!==0),n){if(a)return bo(e,t,l);t.flags|=128}if(n=t.memoizedState,n!==null&&(n.rendering=null,n.tail=null,n.lastEffect=null),Q(Le,Le.current),a)break;return null;case 22:case 23:return t.lanes=0,oo(e,t,l);case 24:rl(t,we,e.memoizedState.cache)}return $t(e,t,l)}function xo(e,t,l){if(e!==null)if(e.memoizedProps!==t.pendingProps)qe=!0;else{if(!qc(e,l)&&(t.flags&128)===0)return qe=!1,kh(e,t,l);qe=(e.flags&131072)!==0}else qe=!1,he&&(t.flags&1048576)!==0&&ks(t,ou,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var a=t.elementType,n=a._init;if(a=n(a._payload),t.type=a,typeof a=="function")ki(a)?(e=Kl(a,e),t.tag=1,t=yo(null,t,a,e,l)):(t.tag=0,t=Cc(null,t,a,e,l));else{if(a!=null){if(n=a.$$typeof,n===k){t.tag=11,t=ro(null,t,a,e,l);break e}else if(n===X){t.tag=14,t=so(null,t,a,e,l);break e}}throw t=zl(a)||a,Error(s(306,t,""))}}return t;case 0:return Cc(e,t,t.type,t.pendingProps,l);case 1:return a=t.type,n=Kl(a,t.pendingProps),yo(e,t,a,n,l);case 3:e:{if(Te(t,t.stateNode.containerInfo),e===null)throw Error(s(387));a=t.pendingProps;var u=t.memoizedState;n=u.element,rc(e,t),sn(t,a,null,l);var c=t.memoizedState;if(a=c.cache,rl(t,we,a),a!==u.cache&&lc(t,[we],l,!0),rn(),a=c.element,u.isDehydrated)if(u={element:a,isDehydrated:!1,cache:c.cache},t.updateQueue.baseState=u,t.memoizedState=u,t.flags&256){t=go(e,t,a,l);break e}else if(a!==n){n=pt(Error(s(424)),t),en(n),t=go(e,t,a,l);break e}else{switch(e=t.stateNode.containerInfo,e.nodeType){case 9:e=e.body;break;default:e=e.nodeName==="HTML"?e.ownerDocument.body:e}for(Re=Ot(e.firstChild),Pe=t,he=!0,Yl=null,Ct=!0,l=Ff(t,null,a,l),t.child=l;l;)l.flags=l.flags&-3|4096,l=l.sibling}else{if(Ia(),a===n){t=$t(e,t,l);break e}Ze(e,t,a,l)}t=t.child}return t;case 26:return zu(e,t),e===null?(l=jd(t.type,null,t.pendingProps,null))?t.memoizedState=l:he||(l=t.type,e=t.pendingProps,a=Ku(ae.current).createElement(l),a[$e]=t,a[Ie]=e,Ke(a,l,e),Be(a),t.stateNode=a):t.memoizedState=jd(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return hi(t),e===null&&he&&(a=t.stateNode=Sd(t.type,t.pendingProps,ae.current),Pe=t,Ct=!0,n=Re,Tl(t.type)?(br=n,Re=Ot(a.firstChild)):Re=n),Ze(e,t,t.pendingProps.children,l),zu(e,t),e===null&&(t.flags|=4194304),t.child;case 5:return e===null&&he&&((n=a=Re)&&(a=Ey(a,t.type,t.pendingProps,Ct),a!==null?(t.stateNode=a,Pe=t,Re=Ot(a.firstChild),Ct=!1,n=!0):n=!1),n||Gl(t)),hi(t),n=t.type,u=t.pendingProps,c=e!==null?e.memoizedProps:null,a=u.children,yr(n,u)?a=null:c!==null&&yr(n,c)&&(t.flags|=32),t.memoizedState!==null&&(n=hc(e,t,Yh,null,null,l),zn._currentValue=n),zu(e,t),Ze(e,t,a,l),t.child;case 6:return e===null&&he&&((e=l=Re)&&(l=Ty(l,t.pendingProps,Ct),l!==null?(t.stateNode=l,Pe=t,Re=null,e=!0):e=!1),e||Gl(t)),null;case 13:return vo(e,t,l);case 4:return Te(t,t.stateNode.containerInfo),a=t.pendingProps,e===null?t.child=Sa(t,null,a,l):Ze(e,t,a,l),t.child;case 11:return ro(e,t,t.type,t.pendingProps,l);case 7:return Ze(e,t,t.pendingProps,l),t.child;case 8:return Ze(e,t,t.pendingProps.children,l),t.child;case 12:return Ze(e,t,t.pendingProps.children,l),t.child;case 10:return a=t.pendingProps,rl(t,t.type,a.value),Ze(e,t,a.children,l),t.child;case 9:return n=t.type._context,a=t.pendingProps.children,Ql(t),n=We(n),a=a(n),t.flags|=1,Ze(e,t,a,l),t.child;case 14:return so(e,t,t.type,t.pendingProps,l);case 15:return fo(e,t,t.type,t.pendingProps,l);case 19:return bo(e,t,l);case 31:return a=t.pendingProps,l=t.mode,a={mode:a.mode,children:a.children},e===null?(l=Cu(a,l),l.ref=t.ref,t.child=l,l.return=t,t=l):(l=Xt(e.child,a),l.ref=t.ref,t.child=l,l.return=t,t=l),t;case 22:return oo(e,t,l);case 24:return Ql(t),a=We(we),e===null?(n=uc(),n===null&&(n=Ee,u=ac(),n.pooledCache=u,u.refCount++,u!==null&&(n.pooledCacheLanes|=l),n=u),t.memoizedState={parent:a,cache:n},cc(t),rl(t,we,n)):((e.lanes&l)!==0&&(rc(e,t),sn(t,null,null,l),rn()),n=e.memoizedState,u=t.memoizedState,n.parent!==a?(n={parent:a,cache:a},t.memoizedState=n,t.lanes===0&&(t.memoizedState=t.updateQueue.baseState=n),rl(t,we,a)):(a=u.cache,rl(t,we,a),a!==n.cache&&lc(t,[we],l,!0))),Ze(e,t,t.pendingProps.children,l),t.child;case 29:throw t.pendingProps}throw Error(s(156,t.tag))}function Wt(e){e.flags|=4}function So(e,t){if(t.type!=="stylesheet"||(t.state.loading&4)!==0)e.flags&=-16777217;else if(e.flags|=16777216,!Od(t)){if(t=Et.current,t!==null&&((oe&4194048)===oe?_t!==null:(oe&62914560)!==oe&&(oe&536870912)===0||t!==_t))throw un=ic,lf;e.flags|=8192}}function _u(e,t){t!==null&&(e.flags|=4),e.flags&16384&&(t=e.tag!==22?Fr():536870912,e.lanes|=t,Na|=t)}function gn(e,t){if(!he)switch(e.tailMode){case"hidden":t=e.tail;for(var l=null;t!==null;)t.alternate!==null&&(l=t),t=t.sibling;l===null?e.tail=null:l.sibling=null;break;case"collapsed":l=e.tail;for(var a=null;l!==null;)l.alternate!==null&&(a=l),l=l.sibling;a===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:a.sibling=null}}function Ne(e){var t=e.alternate!==null&&e.alternate.child===e.child,l=0,a=0;if(t)for(var n=e.child;n!==null;)l|=n.lanes|n.childLanes,a|=n.subtreeFlags&65011712,a|=n.flags&65011712,n.return=e,n=n.sibling;else for(n=e.child;n!==null;)l|=n.lanes|n.childLanes,a|=n.subtreeFlags,a|=n.flags,n.return=e,n=n.sibling;return e.subtreeFlags|=a,e.childLanes=l,t}function $h(e,t,l){var a=t.pendingProps;switch(Pi(t),t.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Ne(t),null;case 1:return Ne(t),null;case 3:return l=t.stateNode,a=null,e!==null&&(a=e.memoizedState.cache),t.memoizedState.cache!==a&&(t.flags|=2048),Kt(we),nl(),l.pendingContext&&(l.context=l.pendingContext,l.pendingContext=null),(e===null||e.child===null)&&(Pa(t)?Wt(t):e===null||e.memoizedState.isDehydrated&&(t.flags&256)===0||(t.flags|=1024,Fs())),Ne(t),null;case 26:return l=t.memoizedState,e===null?(Wt(t),l!==null?(Ne(t),So(t,l)):(Ne(t),t.flags&=-16777217)):l?l!==e.memoizedState?(Wt(t),Ne(t),So(t,l)):(Ne(t),t.flags&=-16777217):(e.memoizedProps!==a&&Wt(t),Ne(t),t.flags&=-16777217),null;case 27:Zn(t),l=ae.current;var n=t.type;if(e!==null&&t.stateNode!=null)e.memoizedProps!==a&&Wt(t);else{if(!a){if(t.stateNode===null)throw Error(s(166));return Ne(t),null}e=F.current,Pa(t)?$s(t):(e=Sd(n,a,l),t.stateNode=e,Wt(t))}return Ne(t),null;case 5:if(Zn(t),l=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==a&&Wt(t);else{if(!a){if(t.stateNode===null)throw Error(s(166));return Ne(t),null}if(e=F.current,Pa(t))$s(t);else{switch(n=Ku(ae.current),e){case 1:e=n.createElementNS("http://www.w3.org/2000/svg",l);break;case 2:e=n.createElementNS("http://www.w3.org/1998/Math/MathML",l);break;default:switch(l){case"svg":e=n.createElementNS("http://www.w3.org/2000/svg",l);break;case"math":e=n.createElementNS("http://www.w3.org/1998/Math/MathML",l);break;case"script":e=n.createElement("div"),e.innerHTML=" - + +
diff --git a/components/scheduler/include/scheduler.h b/components/scheduler/include/scheduler.h index c75ffee..7c2998e 100755 --- a/components/scheduler/include/scheduler.h +++ b/components/scheduler/include/scheduler.h @@ -2,17 +2,21 @@ #pragma once #include +#include #include "scheduler_types.h" #ifdef __cplusplus -extern "C" { +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); + 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); + uint16_t scheduler_get_current_limit_now_a(void); #ifdef __cplusplus } -#endif +#endif \ No newline at end of file diff --git a/components/scheduler/include/scheduler_events.h b/components/scheduler/include/scheduler_events.h index 3882ca1..c6031ac 100644 --- a/components/scheduler/include/scheduler_events.h +++ b/components/scheduler/include/scheduler_events.h @@ -1,17 +1,19 @@ -// scheduler_events.h +// components/scheduler/include/scheduler_events.h #pragma once #include "esp_event.h" #include +#include ESP_EVENT_DECLARE_BASE(SCHED_EVENTS); typedef enum { - SCHED_EVENT_INIT = 0, // envia estado inicial - SCHED_EVENT_WINDOW_CHANGED, // allowed_now mudou + SCHED_EVENT_INIT = 0, + SCHED_EVENT_WINDOW_CHANGED, } sched_event_id_t; typedef struct { bool allowed_now; -} sched_event_state_t; + uint16_t current_limit_a; // 0 se fora de janela / sem override +} sched_event_state_t; \ No newline at end of file diff --git a/components/scheduler/include/scheduler_types.h b/components/scheduler/include/scheduler_types.h index 3e301de..be58ae7 100644 --- a/components/scheduler/include/scheduler_types.h +++ b/components/scheduler/include/scheduler_types.h @@ -4,28 +4,37 @@ #include #ifdef __cplusplus -extern "C" { +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 enum + { + SCHED_MODE_DISABLED = 0, + SCHED_MODE_DAILY_MULTI = 1, + } sched_mode_t; -typedef struct { - bool enabled; // gating ativo? - sched_mode_t mode; +#define SCHED_MAX_WINDOWS 4 - // 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; + typedef struct + { + uint16_t start_min; // 0..1440 (aceita 1440 = 24:00) + uint16_t end_min; // 0..1440 + uint16_t current_a; // 6..80 + } sched_window_t; -const char *sched_mode_to_str(sched_mode_t mode); -bool sched_mode_from_str(const char *s, sched_mode_t *out); + typedef struct + { + bool enabled; + sched_mode_t mode; + + uint8_t window_count; + sched_window_t windows[SCHED_MAX_WINDOWS]; + } 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 +#endif \ No newline at end of file diff --git a/components/scheduler/src/scheduler.c b/components/scheduler/src/scheduler.c index b550c3e..b0c1930 100755 --- a/components/scheduler/src/scheduler.c +++ b/components/scheduler/src/scheduler.c @@ -14,55 +14,90 @@ #include #include +#include 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; +} \ No newline at end of file diff --git a/components/scheduler/src/scheduler_types.c b/components/scheduler/src/scheduler_types.c index 12a8cab..ce74035 100755 --- a/components/scheduler/src/scheduler_types.c +++ b/components/scheduler/src/scheduler_types.c @@ -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; -} +} \ No newline at end of file diff --git a/components/time_service/CMakeLists.txt b/components/time_service/CMakeLists.txt new file mode 100644 index 0000000..a16e79d --- /dev/null +++ b/components/time_service/CMakeLists.txt @@ -0,0 +1,12 @@ +set(srcs + "src/time_service.c" + "src/time_service_events.c" +) + +idf_component_register( + SRCS ${srcs} + INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "src" + REQUIRES esp_event network + PRIV_REQUIRES esp_netif lwip freertos +) diff --git a/components/time_service/idf_component.yml b/components/time_service/idf_component.yml new file mode 100644 index 0000000..d356aa5 --- /dev/null +++ b/components/time_service/idf_component.yml @@ -0,0 +1,2 @@ +version: "1.0.0" +description: Time synchronization and timezone service diff --git a/components/time_service/include/time_service.h b/components/time_service/include/time_service.h new file mode 100644 index 0000000..1a6699e --- /dev/null +++ b/components/time_service/include/time_service.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Inicializa o serviço de hora. + * + * Responsabilidades do módulo: + * - definir a timezone local do equipamento + * - registar handlers dos eventos de rede + * - arrancar SNTP quando a STA obtém IP + * - expor o estado atual da sincronização + */ +void time_service_init(void); + +/** + * @brief Indica se já houve pelo menos uma sincronização SNTP com sucesso. + */ +bool time_service_is_synced(void); + +/** + * @brief Indica se existe uma tentativa de sincronização em curso. + */ +bool time_service_is_sync_in_progress(void); + +/** + * @brief Retorna true se a hora atual parece válida. + * + * Critério simples: ano >= 2024. + */ +bool time_service_has_valid_time(void); + +/** + * @brief Força uma nova espera pela sincronização atual. + * + * Não reinicializa o SNTP. Apenas lança uma task curta que aguarda pela sync. + */ +esp_err_t time_service_force_resync(void); + +/** + * @brief Nome IANA da timezone alvo do produto. + */ +const char *time_service_get_tz_name(void); + +/** + * @brief String POSIX usada por setenv("TZ", ...). + */ +const char *time_service_get_tz_posix(void); + +/** + * @brief Obtém o epoch atual. + */ +time_t time_service_get_epoch(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/time_service/include/time_service_events.h b/components/time_service/include/time_service_events.h new file mode 100644 index 0000000..a3eed57 --- /dev/null +++ b/components/time_service/include/time_service_events.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include "esp_event.h" + +#ifdef __cplusplus +extern "C" { +#endif + +ESP_EVENT_DECLARE_BASE(TIME_SERVICE_EVENTS); + +typedef enum { + TIME_SERVICE_EVENT_INIT = 0, + TIME_SERVICE_EVENT_SYNC_STARTED, + TIME_SERVICE_EVENT_SYNC_OK, + TIME_SERVICE_EVENT_SYNC_TIMEOUT, + TIME_SERVICE_EVENT_NETWORK_LOST, +} time_service_event_id_t; + +typedef struct { + bool timezone_configured; + bool sntp_initialized; + bool sync_in_progress; + bool synced; + bool valid_time; + time_t now; +} time_service_state_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/time_service/src/time_service.c b/components/time_service/src/time_service.c new file mode 100644 index 0000000..60be6f7 --- /dev/null +++ b/components/time_service/src/time_service.c @@ -0,0 +1,267 @@ +#include "time_service.h" +#include "time_service_events.h" +#include "network_events.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_err.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif_sntp.h" + +#include +#include +#include +#include + +static const char *TAG = "time_service"; + +/* + * Portugal continental / Madeira / Canárias partilham esta regra POSIX em ESP-IDF. + * Fonte da equivalência Europe/Lisbon -> POSIX: documentação Espressif RainMaker. + */ +#define TIME_SERVICE_TZ_NAME "Europe/Lisbon" +#define TIME_SERVICE_TZ_POSIX "WET0WEST,M3.5.0/1,M10.5.0" + +#define TIME_SERVICE_SYNC_WAIT_MS 15000 +#define TIME_SERVICE_MIN_VALID_YEAR 2024 + +typedef struct { + bool timezone_configured; + bool sntp_initialized; + bool sync_in_progress; + bool synced; + bool inited; +} time_service_runtime_t; + +static time_service_runtime_t s_rt = { + .timezone_configured = false, + .sntp_initialized = false, + .sync_in_progress = false, + .synced = false, + .inited = false, +}; + +static TaskHandle_t s_sync_wait_task = NULL; + +static void publish_state(time_service_event_id_t id) +{ + time_service_state_t st = { + .timezone_configured = s_rt.timezone_configured, + .sntp_initialized = s_rt.sntp_initialized, + .sync_in_progress = s_rt.sync_in_progress, + .synced = s_rt.synced, + .valid_time = time_service_has_valid_time(), + .now = time(NULL), + }; + + (void)esp_event_post(TIME_SERVICE_EVENTS, id, &st, sizeof(st), portMAX_DELAY); +} + +static void set_portugal_timezone(void) +{ + setenv("TZ", TIME_SERVICE_TZ_POSIX, 1); + tzset(); + s_rt.timezone_configured = true; + ESP_LOGI(TAG, "Timezone set: %s (%s)", TIME_SERVICE_TZ_NAME, TIME_SERVICE_TZ_POSIX); +} + +static void time_sync_notification_cb(struct timeval *tv) +{ + (void)tv; + s_rt.synced = true; + s_rt.sync_in_progress = false; + + time_t now = time(NULL); + struct tm tm_now = {0}; + localtime_r(&now, &tm_now); + + ESP_LOGI(TAG, "Time synchronized: %04d-%02d-%02d %02d:%02d:%02d", + tm_now.tm_year + 1900, + tm_now.tm_mon + 1, + tm_now.tm_mday, + tm_now.tm_hour, + tm_now.tm_min, + tm_now.tm_sec); + + publish_state(TIME_SERVICE_EVENT_SYNC_OK); +} + +static void ensure_sntp_initialized(void) +{ + if (s_rt.sntp_initialized) + return; + + esp_sntp_config_t cfg = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org"); + cfg.sync_cb = time_sync_notification_cb; + + esp_err_t err = esp_netif_sntp_init(&cfg); + if (err == ESP_OK) + { + s_rt.sntp_initialized = true; + ESP_LOGI(TAG, "SNTP initialized"); + return; + } + + if (err == ESP_ERR_INVALID_STATE) + { + s_rt.sntp_initialized = true; + ESP_LOGW(TAG, "SNTP already initialized elsewhere"); + return; + } + + ESP_LOGE(TAG, "esp_netif_sntp_init failed: %s", esp_err_to_name(err)); +} + +static void sync_wait_task(void *arg) +{ + (void)arg; + + esp_err_t err = esp_netif_sntp_sync_wait(pdMS_TO_TICKS(TIME_SERVICE_SYNC_WAIT_MS)); + if (err == ESP_OK) + { + s_rt.synced = true; + s_rt.sync_in_progress = false; + ESP_LOGI(TAG, "Initial SNTP sync complete"); + publish_state(TIME_SERVICE_EVENT_SYNC_OK); + } + else + { + s_rt.sync_in_progress = false; + ESP_LOGW(TAG, "SNTP sync wait timeout/error: %s", esp_err_to_name(err)); + publish_state(TIME_SERVICE_EVENT_SYNC_TIMEOUT); + } + + s_sync_wait_task = NULL; + vTaskDelete(NULL); +} + +static esp_err_t start_sync_wait(void) +{ + if (!s_rt.sntp_initialized) + return ESP_ERR_INVALID_STATE; + + if (s_rt.sync_in_progress) + return ESP_ERR_INVALID_STATE; + + s_rt.sync_in_progress = true; + publish_state(TIME_SERVICE_EVENT_SYNC_STARTED); + + BaseType_t ok = xTaskCreate( + sync_wait_task, + "time_sync_wait", + 4096, + NULL, + 4, + &s_sync_wait_task); + + if (ok != pdPASS) + { + s_rt.sync_in_progress = false; + s_sync_wait_task = NULL; + return ESP_FAIL; + } + + return ESP_OK; +} + +static void network_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) +{ + (void)arg; + (void)base; + (void)data; + + if (id == NETWORK_EVENT_STA_GOT_IP) + { + ESP_LOGI(TAG, "NETWORK_EVENT_STA_GOT_IP -> init/check SNTP"); + ensure_sntp_initialized(); + + if (!s_rt.synced && !s_rt.sync_in_progress) + { + esp_err_t err = start_sync_wait(); + if (err != ESP_OK) + { + ESP_LOGW(TAG, "start_sync_wait failed: %s", esp_err_to_name(err)); + } + } + } + else if (id == NETWORK_EVENT_STA_LOST_IP) + { + ESP_LOGW(TAG, "NETWORK_EVENT_STA_LOST_IP"); + publish_state(TIME_SERVICE_EVENT_NETWORK_LOST); + } +} + +void time_service_init(void) +{ + if (s_rt.inited) + { + ESP_LOGW(TAG, "time_service_init called twice"); + return; + } + + set_portugal_timezone(); + + ESP_ERROR_CHECK(esp_event_handler_register( + NETWORK_EVENTS, + NETWORK_EVENT_STA_GOT_IP, + network_event_handler, + NULL)); + + ESP_ERROR_CHECK(esp_event_handler_register( + NETWORK_EVENTS, + NETWORK_EVENT_STA_LOST_IP, + network_event_handler, + NULL)); + + s_rt.inited = true; + publish_state(TIME_SERVICE_EVENT_INIT); +} + +bool time_service_is_synced(void) +{ + return s_rt.synced; +} + +bool time_service_is_sync_in_progress(void) +{ + return s_rt.sync_in_progress; +} + +bool time_service_has_valid_time(void) +{ + time_t now = time(NULL); + if (now <= 0) + return false; + + struct tm tm_now = {0}; + localtime_r(&now, &tm_now); + return (tm_now.tm_year + 1900) >= TIME_SERVICE_MIN_VALID_YEAR; +} + +esp_err_t time_service_force_resync(void) +{ + if (!s_rt.sntp_initialized) + return ESP_ERR_INVALID_STATE; + + if (s_rt.sync_in_progress) + return ESP_ERR_INVALID_STATE; + + return start_sync_wait(); +} + +const char *time_service_get_tz_name(void) +{ + return TIME_SERVICE_TZ_NAME; +} + +const char *time_service_get_tz_posix(void) +{ + return TIME_SERVICE_TZ_POSIX; +} + +time_t time_service_get_epoch(void) +{ + return time(NULL); +} diff --git a/components/time_service/src/time_service_events.c b/components/time_service/src/time_service_events.c new file mode 100644 index 0000000..3a9752e --- /dev/null +++ b/components/time_service/src/time_service_events.c @@ -0,0 +1,3 @@ +#include "time_service_events.h" + +ESP_EVENT_DEFINE_BASE(TIME_SERVICE_EVENTS); diff --git a/components_zips/ArduinoJson.zip b/components_zips/ArduinoJson.zip new file mode 100644 index 0000000..0267e9c Binary files /dev/null and b/components_zips/ArduinoJson.zip differ diff --git a/components_zips/MicroOcpp.zip b/components_zips/MicroOcpp.zip new file mode 100644 index 0000000..fa5ee7b Binary files /dev/null and b/components_zips/MicroOcpp.zip differ diff --git a/components_zips/MicroOcppMongoose.zip b/components_zips/MicroOcppMongoose.zip new file mode 100644 index 0000000..8aeffa7 Binary files /dev/null and b/components_zips/MicroOcppMongoose.zip differ diff --git a/components_zips/auth.zip b/components_zips/auth.zip new file mode 100644 index 0000000..27ca1c1 Binary files /dev/null and b/components_zips/auth.zip differ diff --git a/components_zips/buzzer.zip b/components_zips/buzzer.zip new file mode 100644 index 0000000..e62b750 Binary files /dev/null and b/components_zips/buzzer.zip differ diff --git a/components_zips/config.zip b/components_zips/config.zip new file mode 100644 index 0000000..ce72128 Binary files /dev/null and b/components_zips/config.zip differ diff --git a/components_zips/evse.zip b/components_zips/evse.zip new file mode 100644 index 0000000..a62de95 Binary files /dev/null and b/components_zips/evse.zip differ diff --git a/components_zips/evse_link.zip b/components_zips/evse_link.zip new file mode 100644 index 0000000..bfae1ed Binary files /dev/null and b/components_zips/evse_link.zip differ diff --git a/components_zips/led.zip b/components_zips/led.zip new file mode 100644 index 0000000..3355538 Binary files /dev/null and b/components_zips/led.zip differ diff --git a/components_zips/loadbalancer.zip b/components_zips/loadbalancer.zip new file mode 100644 index 0000000..5ddd872 Binary files /dev/null and b/components_zips/loadbalancer.zip differ diff --git a/components_zips/meter_manager.zip b/components_zips/meter_manager.zip new file mode 100644 index 0000000..bdd7a13 Binary files /dev/null and b/components_zips/meter_manager.zip differ diff --git a/components_zips/network.zip b/components_zips/network.zip new file mode 100644 index 0000000..4fd3f75 Binary files /dev/null and b/components_zips/network.zip differ diff --git a/components_zips/ocpp.zip b/components_zips/ocpp.zip new file mode 100644 index 0000000..2948bd9 Binary files /dev/null and b/components_zips/ocpp.zip differ diff --git a/components_zips/peripherals.zip b/components_zips/peripherals.zip new file mode 100644 index 0000000..38a49ea Binary files /dev/null and b/components_zips/peripherals.zip differ diff --git a/components_zips/protocols.zip b/components_zips/protocols.zip new file mode 100644 index 0000000..e880586 Binary files /dev/null and b/components_zips/protocols.zip differ diff --git a/components_zips/rest_api.zip b/components_zips/rest_api.zip new file mode 100644 index 0000000..83be0d0 Binary files /dev/null and b/components_zips/rest_api.zip differ diff --git a/components_zips/scheduler.zip b/components_zips/scheduler.zip new file mode 100644 index 0000000..2aa63be Binary files /dev/null and b/components_zips/scheduler.zip differ diff --git a/components_zips/spi_bus_manager.zip b/components_zips/spi_bus_manager.zip new file mode 100644 index 0000000..8d968ab Binary files /dev/null and b/components_zips/spi_bus_manager.zip differ diff --git a/components_zips/storage_service.zip b/components_zips/storage_service.zip new file mode 100644 index 0000000..90051df Binary files /dev/null and b/components_zips/storage_service.zip differ diff --git a/components_zips/time_service.zip b/components_zips/time_service.zip new file mode 100644 index 0000000..f767563 Binary files /dev/null and b/components_zips/time_service.zip differ diff --git a/dependencies.lock b/dependencies.lock index f9c0a96..dbab26e 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -56,13 +56,13 @@ dependencies: idf: source: type: idf - version: 5.5.2 + version: 5.5.4 direct_dependencies: - espressif/cjson - espressif/esp-modbus - espressif/mdns - espressif/ntc_driver - idf -manifest_hash: a9e96056e96b13af371d5c8d87f85c85e64afca6e06d96c10ecf1e3537459911 +manifest_hash: 408cfc8c76c47e4e8b8d2a2e355fb208821e92714a543797e2dd0d8601ed373f target: esp32 version: 2.0.0 diff --git a/main/main.c b/main/main.c index 52d65f0..e5cbefd 100755 --- a/main/main.c +++ b/main/main.c @@ -33,10 +33,41 @@ #include "led.h" #include "scheduler.h" #include "storage_service.h" +#include "time_service.h" -#define AP_CONNECTION_TIMEOUT 120000 -#define RESET_HOLD_TIME 30000 // ms -#define DEBOUNCE_TIME_MS 50 +/* + * Modos de controlo do AP: + * + * APP_AP_CONTROL_BUTTON: + * - Usa botão físico. + * - Pressão curta: inicia AP. + * - Pressão longa: apaga NVS e reinicia. + * + * APP_AP_CONTROL_BOOT_WINDOW: + * - Não usa botão físico. + * - Inicia AP automaticamente no arranque. + * - Mantém AP ativo durante os primeiros 3 minutos. + * - Depois desliga AP e volta ao modo STA. + */ +#define APP_AP_CONTROL_BUTTON 0 +#define APP_AP_CONTROL_BOOT_WINDOW 1 + +/* + * Escolher aqui o modo pretendido: + * + * Para placa COM botão: + * #define APP_AP_CONTROL_MODE APP_AP_CONTROL_BUTTON + * + * Para placa SEM botão: + * #define APP_AP_CONTROL_MODE APP_AP_CONTROL_BOOT_WINDOW + */ +#define APP_AP_CONTROL_MODE APP_AP_CONTROL_BOOT_WINDOW + +#define AP_BOOT_WINDOW_MS 180000UL // 3 minutos +#define AP_CONNECTION_TIMEOUT AP_BOOT_WINDOW_MS + +#define RESET_HOLD_TIME 30000UL // ms +#define DEBOUNCE_TIME_MS 50UL #define PRESS_BIT BIT0 #define RELEASED_BIT BIT1 @@ -98,6 +129,7 @@ static void wifi_event_task_func(void *param) { (void)param; EventBits_t mode_bits; + for (;;) { mode_bits = xEventGroupWaitBits( @@ -130,7 +162,8 @@ static void wifi_event_task_func(void *param) if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT) { // Timeout sem cliente ligado - // wifi_ap_stop(); + // O desligar do AP no modo sem botão é feito pela boot_ap_task_func(). + // No modo com botão, mantemos o comportamento antigo. ESP_LOGW(TAG, "AP timeout sem conexões"); } } @@ -165,9 +198,15 @@ static void handle_button_press(void) ESP_LOGI(TAG, "Starting Wi-Fi AP mode (short press)"); wifi_ap_start(); } + else + { + ESP_LOGI(TAG, "Wi-Fi AP mode já está ativo"); + } } +// // Task para lidar com notificações de botão (PRESS / RELEASE) +// static void user_input_task_func(void *param) { (void)param; @@ -186,12 +225,13 @@ static void user_input_task_func(void *param) press_tick = xTaskGetTickCount(); pressed = true; ESP_LOGI(TAG, "Button Pressed"); - // Decisão (short/long) é feita na soltura + // Decisão short/long é feita na soltura } if ((notification & RELEASED_BIT) && pressed) { pressed = false; + TickType_t held_ticks = xTaskGetTickCount() - press_tick; uint32_t held_ms = pdTICKS_TO_MS(held_ticks); @@ -200,7 +240,13 @@ static void user_input_task_func(void *param) if (held_ms >= RESET_HOLD_TIME) { ESP_LOGW(TAG, "Long press: erasing NVS + reboot"); - nvs_flash_erase(); + + esp_err_t ret = nvs_flash_erase(); + if (ret != ESP_OK) + { + ESP_LOGE(TAG, "Erro ao apagar NVS: %s", esp_err_to_name(ret)); + } + esp_restart(); } else @@ -213,7 +259,9 @@ static void user_input_task_func(void *param) } } -// ISR para GPIO do botão (ativo em nível baixo) +// +// ISR para GPIO do botão ativo em nível baixo +// static void IRAM_ATTR button_isr_handler(void *arg) { (void)arg; @@ -221,11 +269,13 @@ static void IRAM_ATTR button_isr_handler(void *arg) TickType_t now = xTaskGetTickCountFromISR(); portENTER_CRITICAL_ISR(&button_spinlock); + if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS)) { portEXIT_CRITICAL_ISR(&button_spinlock); return; } + last_interrupt_tick = now; if (user_input_task == NULL) @@ -265,11 +315,45 @@ static void button_init(void) } // -// Inicialização dos módulos do sistema (SEM botão) +// AP automático no arranque para placas sem botão +// +static void boot_ap_task_func(void *param) +{ + (void)param; + + if (wifi_event_group == NULL) + { + ESP_LOGW(TAG, "Wi-Fi ainda não inicializado, não é possível iniciar AP"); + vTaskDelete(NULL); + return; + } + + ESP_LOGI(TAG, "Modo sem botão: iniciar AP automaticamente durante 3 minutos"); + + wifi_ap_start(); + + vTaskDelay(pdMS_TO_TICKS(AP_BOOT_WINDOW_MS)); + + EventBits_t bits = xEventGroupGetBits(wifi_event_group); + + if (bits & WIFI_AP_MODE_BIT) + { + ESP_LOGI(TAG, "Modo sem botão: janela de AP terminou, a desligar AP"); + wifi_ap_stop(); + } + else + { + ESP_LOGI(TAG, "Modo sem botão: AP já não estava ativo"); + } + + vTaskDelete(NULL); +} + +// +// Inicialização dos módulos do sistema // static void init_modules(void) { - ESP_ERROR_CHECK(storage_service_init()); peripherals_init(); @@ -284,6 +368,7 @@ static void init_modules(void) meter_manager_init(); meter_manager_start(); ocpp_start(); + time_service_init(); scheduler_init(); protocols_init(); loadbalancer_init(); @@ -295,7 +380,6 @@ static void init_modules(void) // void app_main(void) { - esp_reset_reason_t reason = esp_reset_reason(); ESP_LOGI(TAG, "Reset reason: %d", reason); @@ -309,25 +393,74 @@ void app_main(void) ESP_ERROR_CHECK(ret); fs_init(); + ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); - ESP_ERROR_CHECK(gpio_install_isr_service(0)); board_config_load(); - // 1) Inicia todos os módulos (inclui Wi-Fi, EVSE, etc.) + /* + * O serviço GPIO ISR tem de estar instalado antes de qualquer módulo + * chamar gpio_isr_handler_add(). + * + * O RFID/Wiegand usa interrupções GPIO durante auth_init(), + * que é chamado dentro de init_modules(). + */ + esp_err_t gpio_isr_ret = gpio_install_isr_service(0); + if (gpio_isr_ret != ESP_OK && gpio_isr_ret != ESP_ERR_INVALID_STATE) + { + ESP_ERROR_CHECK(gpio_isr_ret); + } + + + // 1) Inicia todos os módulos, incluindo Wi-Fi, EVSE, etc. init_modules(); - // 2) Cria a task que recebe notificações do botão BaseType_t rc; - rc = xTaskCreate(user_input_task_func, "user_input_task", 4 * 1024, NULL, 3, &user_input_task); + +#if APP_AP_CONTROL_MODE == APP_AP_CONTROL_BUTTON + + ESP_LOGI(TAG, "Modo AP por botão ativo"); + + // 2) Cria a task que recebe notificações do botão + rc = xTaskCreate( + user_input_task_func, + "user_input_task", + 4 * 1024, + NULL, + 3, + &user_input_task); configASSERT(rc == pdPASS); // 3) Agora é seguro registrar ISR do botão button_init(); +#elif APP_AP_CONTROL_MODE == APP_AP_CONTROL_BOOT_WINDOW + + ESP_LOGI(TAG, "Modo AP automático no arranque ativo"); + + // 2) Inicia AP automaticamente durante os primeiros 3 minutos + rc = xTaskCreate( + boot_ap_task_func, + "boot_ap_task", + 4 * 1024, + NULL, + 3, + NULL); + configASSERT(rc == pdPASS); + +#else +#error "APP_AP_CONTROL_MODE inválido" +#endif + // 4) Task auxiliar para eventos Wi-Fi - rc = xTaskCreate(wifi_event_task_func, "wifi_event_task", 8 * 1024, NULL, 3, NULL); + rc = xTaskCreate( + wifi_event_task_func, + "wifi_event_task", + 8 * 1024, + NULL, + 3, + NULL); configASSERT(rc == pdPASS); } diff --git a/partitions.csv b/partitions.csv index 630dbf8..c1b4377 100755 --- a/partitions.csv +++ b/partitions.csv @@ -3,4 +3,4 @@ nvs, data, nvs, 0x9000, 0xA000, phy_init, data, phy, 0x13000, 0x1000, factory, app, factory, 0x20000, 3500K, cfg, data, spiffs, 0x38B000, 0x4000, -data, data, spiffs, 0x38F000, 0x4C000, +data, data, spiffs, 0x38F000, 0x71000, diff --git a/projeto_parte1.c b/projeto_parte1.c deleted file mode 100644 index 9180035..0000000 --- a/projeto_parte1.c +++ /dev/null @@ -1,1344 +0,0 @@ -. - -// === Início de: main/main.c === -// === Início de: main/main.c === -#include -#include -#include - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/event_groups.h" - -#include "esp_log.h" -#include "esp_err.h" -#include "esp_event.h" -#include "esp_netif.h" -#include "esp_spiffs.h" -#include "esp_system.h" -#include "nvs_flash.h" -#include "driver/gpio.h" - -#include "network.h" -#include "board_config.h" -#include "rest_main.h" - -#include "peripherals.h" -#include "protocols.h" -#include "evse_manager.h" -#include "evse_core.h" -#include "auth.h" -#include "loadbalancer.h" -#include "meter_manager.h" -#include "buzzer.h" -#include "evse_link.h" -#include "ocpp.h" -#include "led.h" -#include "scheduler.h" -#include "storage_service.h" - -#define AP_CONNECTION_TIMEOUT 120000 -#define RESET_HOLD_TIME 30000 // ms -#define DEBOUNCE_TIME_MS 50 - -#define PRESS_BIT BIT0 -#define RELEASED_BIT BIT1 - -static const char *TAG = "app_main"; - -static TaskHandle_t user_input_task = NULL; -static TickType_t press_tick = 0; -static volatile TickType_t last_interrupt_tick = 0; -static bool pressed = false; - -// Spinlock para garantir debounce seguro na ISR -static portMUX_TYPE button_spinlock = portMUX_INITIALIZER_UNLOCKED; - -// -// File system (SPIFFS) init and info -// -static void fs_info(esp_vfs_spiffs_conf_t *conf) -{ - size_t total = 0, used = 0; - esp_err_t ret = esp_spiffs_info(conf->partition_label, &total, &used); - if (ret == ESP_OK) - { - ESP_LOGI(TAG, "Partition %s: total: %d, used: %d", - conf->partition_label, (int)total, (int)used); - } - else - { - ESP_LOGE(TAG, "Failed to get SPIFFS info (%s): %s", - conf->partition_label, esp_err_to_name(ret)); - } -} - -static void fs_init(void) -{ - esp_vfs_spiffs_conf_t cfg_conf = { - .base_path = "/cfg", - .partition_label = "cfg", - .max_files = 1, - .format_if_mount_failed = false}; - - esp_vfs_spiffs_conf_t data_conf = { - .base_path = "/data", - .partition_label = "data", - .max_files = 5, - .format_if_mount_failed = true}; - - ESP_ERROR_CHECK(esp_vfs_spiffs_register(&cfg_conf)); - ESP_ERROR_CHECK(esp_vfs_spiffs_register(&data_conf)); - - fs_info(&cfg_conf); - fs_info(&data_conf); -} - -// -// Wi-Fi event monitoring task -// -static void wifi_event_task_func(void *param) -{ - (void)param; - EventBits_t mode_bits; - for (;;) - { - mode_bits = xEventGroupWaitBits( - wifi_event_group, - WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT, - pdFALSE, - pdFALSE, - portMAX_DELAY); - - if (mode_bits & WIFI_AP_MODE_BIT) - { - if (xEventGroupWaitBits( - wifi_event_group, - WIFI_AP_CONNECTED_BIT, - pdFALSE, - pdFALSE, - pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)) & - WIFI_AP_CONNECTED_BIT) - { - // Espera sair do AP - xEventGroupWaitBits( - wifi_event_group, - WIFI_AP_DISCONNECTED_BIT, - pdFALSE, - pdFALSE, - portMAX_DELAY); - } - else - { - if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT) - { - // Timeout sem cliente ligado - // wifi_ap_stop(); - ESP_LOGW(TAG, "AP timeout sem conexões"); - } - } - } - else if (mode_bits & WIFI_STA_MODE_BIT) - { - // Apenas aguarda desconexão STA - xEventGroupWaitBits( - wifi_event_group, - WIFI_STA_DISCONNECTED_BIT, - pdFALSE, - pdFALSE, - portMAX_DELAY); - } - } -} - -// -// Button press handler (short press => AP) -// -static void handle_button_press(void) -{ - // Pode ser chamado cedo demais se a ordem de init mudar - if (wifi_event_group == NULL) - { - ESP_LOGW(TAG, "Wi-Fi ainda não inicializado, ignorando botão Wi-Fi"); - return; - } - - if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)) - { - ESP_LOGI(TAG, "Starting Wi-Fi AP mode (short press)"); - wifi_ap_start(); - } -} - -// Task para lidar com notificações de botão (PRESS / RELEASE) -static void user_input_task_func(void *param) -{ - (void)param; - uint32_t notification; - - for (;;) - { - if (xTaskNotifyWait( - 0, - UINT32_MAX, - ¬ification, - portMAX_DELAY)) - { - if (notification & PRESS_BIT) - { - press_tick = xTaskGetTickCount(); - pressed = true; - ESP_LOGI(TAG, "Button Pressed"); - // Decisão (short/long) é feita na soltura - } - - if ((notification & RELEASED_BIT) && pressed) - { - pressed = false; - TickType_t held_ticks = xTaskGetTickCount() - press_tick; - uint32_t held_ms = pdTICKS_TO_MS(held_ticks); - - ESP_LOGI(TAG, "Button Released (held %u ms)", (unsigned)held_ms); - - if (held_ms >= RESET_HOLD_TIME) - { - ESP_LOGW(TAG, "Long press: erasing NVS + reboot"); - nvs_flash_erase(); - esp_restart(); - } - else - { - // Short press: apenas habilita modo AP - handle_button_press(); - } - } - } - } -} - -// ISR para GPIO do botão (ativo em nível baixo) -static void IRAM_ATTR button_isr_handler(void *arg) -{ - (void)arg; - BaseType_t higher_task_woken = pdFALSE; - TickType_t now = xTaskGetTickCountFromISR(); - - portENTER_CRITICAL_ISR(&button_spinlock); - if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS)) - { - portEXIT_CRITICAL_ISR(&button_spinlock); - return; - } - last_interrupt_tick = now; - - if (user_input_task == NULL) - { - portEXIT_CRITICAL_ISR(&button_spinlock); - return; - } - - int level = gpio_get_level(board_config.button_wifi_gpio); - uint32_t bits = (level == 0) ? PRESS_BIT : RELEASED_BIT; - - xTaskNotifyFromISR( - user_input_task, - bits, - eSetBits, - &higher_task_woken); - - portEXIT_CRITICAL_ISR(&button_spinlock); - - if (higher_task_woken) - { - portYIELD_FROM_ISR(); - } -} - -static void button_init(void) -{ - gpio_config_t conf = { - .pin_bit_mask = BIT64(board_config.button_wifi_gpio), - .mode = GPIO_MODE_INPUT, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .pull_up_en = GPIO_PULLUP_ENABLE, - .intr_type = GPIO_INTR_ANYEDGE}; - - ESP_ERROR_CHECK(gpio_config(&conf)); - ESP_ERROR_CHECK(gpio_isr_handler_add(board_config.button_wifi_gpio, button_isr_handler, NULL)); -} - -// -// Inicialização dos módulos do sistema (SEM botão) -// -static void init_modules(void) -{ - - ESP_ERROR_CHECK(storage_service_init()); - - peripherals_init(); - wifi_ini(); // garante wifi_event_group inicializado - buzzer_init(); - led_init(); - ESP_ERROR_CHECK(rest_server_init("/data")); - - evse_manager_init(); - evse_init(); - auth_init(); - meter_manager_init(); - meter_manager_start(); - ocpp_start(); - scheduler_init(); - protocols_init(); - loadbalancer_init(); - evse_link_init(); -} - -// -// Função principal do firmware -// -void app_main(void) -{ - - esp_reset_reason_t reason = esp_reset_reason(); - ESP_LOGI(TAG, "Reset reason: %d", reason); - - esp_err_t ret = nvs_flash_init(); - if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) - { - ESP_LOGW(TAG, "Erasing NVS flash"); - ESP_ERROR_CHECK(nvs_flash_erase()); - ret = nvs_flash_init(); - } - ESP_ERROR_CHECK(ret); - - fs_init(); - ESP_ERROR_CHECK(esp_netif_init()); - ESP_ERROR_CHECK(esp_event_loop_create_default()); - ESP_ERROR_CHECK(gpio_install_isr_service(0)); - - board_config_load(); - - // 1) Inicia todos os módulos (inclui Wi-Fi, EVSE, etc.) - init_modules(); - - // 2) Cria a task que recebe notificações do botão - BaseType_t rc; - rc = xTaskCreate(user_input_task_func, "user_input_task", 4 * 1024, NULL, 3, &user_input_task); - configASSERT(rc == pdPASS); - - // 3) Agora é seguro registrar ISR do botão - button_init(); - - // 4) Task auxiliar para eventos Wi-Fi - rc = xTaskCreate(wifi_event_task_func, "wifi_event_task", 8 * 1024, NULL, 3, NULL); - configASSERT(rc == pdPASS); -} - -// === Fim de: main/main.c === - -// === Fim de: main/main.c === - - -// === Início de: components/ocpp/src/ocpp_events.c === -#include "ocpp_events.h" - -/* Define a base, como em components/auth/src/auth_events.c */ -ESP_EVENT_DEFINE_BASE(OCPP_EVENTS); - -// === Fim de: components/ocpp/src/ocpp_events.c === - - -// === Início de: components/ocpp/src/ocpp.c === -// components/ocpp/src/ocpp.c - -#include -#include -#include -#include -#include - -#include "esp_log.h" -#include "esp_err.h" -#include "esp_timer.h" -#include "esp_event.h" -#include "esp_wifi.h" - -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" - -#include "ocpp.h" -#include "ocpp_events.h" - -#include "evse_error.h" -#include "auth_events.h" -#include "evse_events.h" -#include "evse_state.h" -#include "meter_events.h" - -/* MicroOcpp includes */ -#include -#include // C-facade of MicroOcpp -#include // WebSocket integration for ESP-IDF - -// NEW storage layer -#include "storage_service.h" - -#define NVS_NAMESPACE "ocpp" -#define NVS_OCPP_ENABLED "enabled" -#define NVS_OCPP_SERVER "ocpp_server" -#define NVS_OCPP_CHARGE_ID "charge_id" - -static const char *TAG = "ocpp"; - -static bool enabled = false; - -static TaskHandle_t ocpp_task = NULL; - -static struct mg_mgr mgr; // Event manager -static OCPP_Connection *g_ocpp_conn = NULL; // Para shutdown limpo - -static esp_event_handler_instance_t s_auth_verify_inst = NULL; - -// Flags refletindo o estado do EVSE (atualizadas por eventos) -static volatile bool s_ev_plugged = false; -static volatile bool s_ev_ready = false; - -static esp_event_handler_instance_t s_evse_state_inst = NULL; - -// Flags de config (vindas de EVSE_EVENTS) -static volatile bool s_evse_enabled = true; -static volatile bool s_evse_available = true; - -static esp_event_handler_instance_t s_evse_enable_inst = NULL; -static esp_event_handler_instance_t s_evse_available_inst = NULL; - -// --- cache de medições vindas de METER_EVENT_DATA_READY --- -typedef struct -{ - float vrms[3]; - float irms[3]; - int32_t watt[3]; // ativo por fase (W) - float frequency; - float power_factor; - - float total_energy_Wh; - - int32_t sum_watt; - float avg_voltage; - float sum_current; - - bool have_data; -} ocpp_meter_cache_t; - -static ocpp_meter_cache_t s_meter = {0}; -static portMUX_TYPE s_meter_mux = portMUX_INITIALIZER_UNLOCKED; -static esp_event_handler_instance_t s_meter_inst = NULL; - -// valor de oferta (A por conector) -static float s_current_offered_A = 16.0f; - -static float getCurrentOffered(void) -{ - return s_current_offered_A; -} - -// ----------------------------------------------------------------------------- -// Storage helpers (robustos) -// ----------------------------------------------------------------------------- -#define STORE_TO pdMS_TO_TICKS(800) -#define STORE_FLUSH_TO pdMS_TO_TICKS(2000) - -static void storage_init_best_effort(void) -{ - esp_err_t e = storage_service_init(); - if (e != ESP_OK) - ESP_LOGW(TAG, "storage_service_init failed: %s", esp_err_to_name(e)); -} - -static esp_err_t store_flush_best_effort(void) -{ - esp_err_t e = storage_flush_sync(STORE_FLUSH_TO); - if (e != ESP_OK) - ESP_LOGW(TAG, "storage_flush_sync failed: %s", esp_err_to_name(e)); - return e; -} - -static esp_err_t store_set_u8_best_effort(const char *ns, const char *key, uint8_t v) -{ - for (int attempt = 0; attempt < 3; ++attempt) - { - esp_err_t e = storage_set_u8_async(ns, key, v); - if (e == ESP_OK) - return ESP_OK; - - if (e == ESP_ERR_TIMEOUT) - { - (void)store_flush_best_effort(); - vTaskDelay(pdMS_TO_TICKS(10)); - continue; - } - return e; - } - return ESP_ERR_TIMEOUT; -} - -static esp_err_t store_set_str_best_effort(const char *ns, const char *key, const char *s) -{ - for (int attempt = 0; attempt < 3; ++attempt) - { - esp_err_t e = storage_set_str_async(ns, key, s ? s : ""); - if (e == ESP_OK) - return ESP_OK; - - if (e == ESP_ERR_TIMEOUT) - { - (void)store_flush_best_effort(); - vTaskDelay(portMAX_DELAY); - continue; - } - return e; - } - return ESP_ERR_TIMEOUT; -} - -// Lê string de forma segura (buffer grande -> truncagem segura para out) -static esp_err_t store_get_str_safe(const char *ns, const char *key, char *out, size_t out_sz) -{ - if (!out || out_sz == 0) - return ESP_ERR_INVALID_ARG; - - out[0] = '\0'; - - char tmp[STORAGE_MAX_VALUE_BYTES + 1]; - memset(tmp, 0, sizeof(tmp)); - - esp_err_t e = storage_get_str_sync(ns, key, tmp, sizeof(tmp), STORE_TO); - if (e == ESP_ERR_NOT_FOUND) - return ESP_OK; - if (e != ESP_OK) - return e; - - size_t n = strnlen(tmp, out_sz - 1); - memcpy(out, tmp, n); - out[n] = '\0'; - - return ESP_OK; -} - -// ----------------------------------------------------------------------------- -// Task / Main Loop -// ----------------------------------------------------------------------------- -static void ocpp_task_func(void *param) -{ - (void)param; - - while (true) - { - if (enabled) - { - mg_mgr_poll(&mgr, 100); - ocpp_loop(); - - bool operative = ocpp_isOperative(); - if (operative != s_evse_enabled) - { - s_evse_enabled = operative; - - ocpp_operative_event_t ev = { - .operative = operative, - .timestamp_us = esp_timer_get_time()}; - esp_event_post(OCPP_EVENTS, - OCPP_EVENT_OPERATIVE_UPDATED, - &ev, sizeof(ev), - portMAX_DELAY); - - ESP_LOGI(TAG, "[OCPP] ChangeAvailability remoto → operative=%d", (int)operative); - } - } - else - { - vTaskDelay(pdMS_TO_TICKS(500)); - } - } -} - -// ----------------------------------------------------------------------------- -// Storage GETs -// ----------------------------------------------------------------------------- -bool ocpp_get_enabled(void) -{ - storage_init_best_effort(); - - uint8_t value = 0; - esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_OCPP_ENABLED, &value, STORE_TO); - - if (err == ESP_ERR_NOT_FOUND) - return false; - if (err != ESP_OK) - { - ESP_LOGW(TAG, "storage_get_u8_sync(enabled) failed: %s", esp_err_to_name(err)); - return false; - } - return value != 0; -} - -void ocpp_get_server(char *value /* out, size>=64 */) -{ - if (!value) - return; - value[0] = '\0'; - - storage_init_best_effort(); - - esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_SERVER, value, 64); - if (e != ESP_OK) - { - ESP_LOGW(TAG, "store_get_str_safe(server) failed: %s", esp_err_to_name(e)); - value[0] = '\0'; - } -} - -void ocpp_get_charge_id(char *value /* out, size>=64 */) -{ - if (!value) - return; - value[0] = '\0'; - - storage_init_best_effort(); - - esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value, 64); - if (e != ESP_OK) - { - ESP_LOGW(TAG, "store_get_str_safe(charge_id) failed: %s", esp_err_to_name(e)); - value[0] = '\0'; - } -} - -// ----------------------------------------------------------------------------- -// Storage SETs -// ----------------------------------------------------------------------------- -void ocpp_set_enabled(bool value) -{ - storage_init_best_effort(); - - ESP_LOGI(TAG, "set enabled %d", value); - - esp_err_t e = store_set_u8_best_effort(NVS_NAMESPACE, NVS_OCPP_ENABLED, value ? 1 : 0); - if (e != ESP_OK) - { - ESP_LOGE(TAG, "store_set_u8_best_effort(enabled) failed: %s", esp_err_to_name(e)); - return; - } - - (void)store_flush_best_effort(); - enabled = value; -} - -void ocpp_set_server(char *value) -{ - storage_init_best_effort(); - - ESP_LOGI(TAG, "set server %s", value ? value : "(null)"); - - esp_err_t e = store_set_str_best_effort(NVS_NAMESPACE, NVS_OCPP_SERVER, value ? value : ""); - if (e != ESP_OK) - { - ESP_LOGE(TAG, "store_set_str_best_effort(server) failed: %s", esp_err_to_name(e)); - return; - } - - (void)store_flush_best_effort(); -} - -void ocpp_set_charge_id(char *value) -{ - storage_init_best_effort(); - - ESP_LOGI(TAG, "set charge_id %s", value ? value : "(null)"); - - esp_err_t e = store_set_str_best_effort(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value ? value : ""); - if (e != ESP_OK) - { - ESP_LOGE(TAG, "store_set_str_best_effort(charge_id) failed: %s", esp_err_to_name(e)); - return; - } - - (void)store_flush_best_effort(); -} - -// ----------------------------------------------------------------------------- -// Event handlers (AUTH / EVSE / METER) -// ----------------------------------------------------------------------------- -static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, void *event_data) -{ - (void)arg; - (void)base; - (void)id; - - const auth_tag_verify_event_t *rq = (const auth_tag_verify_event_t *)event_data; - if (!rq) - return; - - char idtag[AUTH_TAG_MAX_LEN]; - if (rq->tag[0] == '\0') - { - strncpy(idtag, "IDTAG", sizeof(idtag)); - idtag[sizeof(idtag) - 1] = '\0'; - } - else - { - strncpy(idtag, rq->tag, sizeof(idtag) - 1); - idtag[sizeof(idtag) - 1] = '\0'; - } - - ESP_LOGI(TAG, "AUTH_EVENT_TAG_VERIFY: tag=%s req_id=%u", idtag, (unsigned)rq->req_id); - - if (!enabled || g_ocpp_conn == NULL) - { - ESP_LOGW(TAG, "OCPP not ready (enabled=%d, conn=%p) – ignoring verify", - enabled, (void *)g_ocpp_conn); - return; - } - - if (ocpp_isTransactionActive()) - { - ESP_LOGI(TAG, "Transaction active -> ocpp_end_transaction(\"%s\")", idtag); - ocpp_endTransaction(idtag, "Local"); - } - else - { - ESP_LOGI(TAG, "No active transaction -> ocpp_begin_transaction(\"%s\")", idtag); - ocpp_beginTransaction(idtag); - } -} - -static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) -{ - (void)arg; - if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL) - return; - - const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data; - ESP_LOGI(TAG, "EVSE event received: state = %d", (int)evt->state); - - switch (evt->state) - { - case EVSE_STATE_EVENT_IDLE: - s_ev_plugged = false; - s_ev_ready = false; - break; - - case EVSE_STATE_EVENT_WAITING: - s_ev_plugged = true; - s_ev_ready = false; - break; - - case EVSE_STATE_EVENT_CHARGING: - s_ev_plugged = true; - s_ev_ready = true; - break; - - case EVSE_STATE_EVENT_FAULT: - default: - s_ev_ready = false; - break; - } -} - -static void evse_enable_available_handler(void *arg, esp_event_base_t base, int32_t id, void *data) -{ - (void)arg; - if (base != EVSE_EVENTS || data == NULL) - return; - - if (id == EVSE_EVENT_ENABLE_UPDATED) - { - const evse_enable_event_data_t *e = (const evse_enable_event_data_t *)data; - s_evse_enabled = e->enabled; - ESP_LOGI(TAG, "[EVSE] ENABLE_UPDATED: enabled=%d (ts=%lld)", - (int)e->enabled, (long long)e->timestamp_us); - return; - } - - if (id == EVSE_EVENT_AVAILABLE_UPDATED) - { - const evse_available_event_data_t *e = (const evse_available_event_data_t *)data; - s_evse_available = e->available; - ESP_LOGI(TAG, "[EVSE] AVAILABLE_UPDATED: available=%d (ts=%lld)", - (int)e->available, (long long)e->timestamp_us); - return; - } -} - -static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *data) -{ - (void)arg; - - if (base != METER_EVENT || id != METER_EVENT_DATA_READY || !data) - return; - - const meter_event_data_t *evt = (const meter_event_data_t *)data; - - if (!evt->source || strcmp(evt->source, "EVSE") != 0) - return; - - int32_t sum_w = (int32_t)evt->watt[0] + (int32_t)evt->watt[1] + (int32_t)evt->watt[2]; - float avg_v = (evt->vrms[0] + evt->vrms[1] + evt->vrms[2]) / 3.0f; - float sum_i = evt->irms[0] + evt->irms[1] + evt->irms[2]; - - portENTER_CRITICAL(&s_meter_mux); - memcpy(s_meter.vrms, evt->vrms, sizeof(s_meter.vrms)); - memcpy(s_meter.irms, evt->irms, sizeof(s_meter.irms)); - s_meter.watt[0] = evt->watt[0]; - s_meter.watt[1] = evt->watt[1]; - s_meter.watt[2] = evt->watt[2]; - s_meter.frequency = evt->frequency; - s_meter.power_factor = evt->power_factor; - s_meter.total_energy_Wh = evt->total_energy; - s_meter.sum_watt = sum_w; - s_meter.avg_voltage = avg_v; - s_meter.sum_current = sum_i; - s_meter.have_data = true; - portEXIT_CRITICAL(&s_meter_mux); -} - -// ----------------------------------------------------------------------------- -// MicroOCPP Inputs/CBs -// ----------------------------------------------------------------------------- -bool setConnectorPluggedInput(void) -{ - return s_ev_plugged; -} - -bool setEvReadyInput(void) -{ - return s_ev_ready; -} - -bool setEvseReadyInput(void) -{ - return s_evse_enabled && s_evse_available; -} - -float setPowerMeterInput(void) -{ - int32_t w = 0; - bool have = false; - - portENTER_CRITICAL(&s_meter_mux); - have = s_meter.have_data; - if (have) - w = s_meter.sum_watt; - portEXIT_CRITICAL(&s_meter_mux); - - if (!have) - ESP_LOGW(TAG, "[METER] PowerMeterInput: no data (return 0)"); - else - ESP_LOGD(TAG, "[METER] PowerMeterInput: %" PRId32 " W", w); - - return (float)w; -} - -float setEnergyMeterInput(void) -{ - float wh = 0.0f; - bool have = false; - - portENTER_CRITICAL(&s_meter_mux); - have = s_meter.have_data; - if (have) - wh = s_meter.total_energy_Wh; - portEXIT_CRITICAL(&s_meter_mux); - - if (!have) - ESP_LOGW(TAG, "[METER] EnergyMeterInput: no data (return 0)"); - else - ESP_LOGD(TAG, "[METER] EnergyMeterInput: (%.1f Wh)", wh); - - return wh; -} - -int setEnergyInput(void) -{ - float wh = setEnergyMeterInput(); - int wh_i = (int)lrintf((double)wh); - ESP_LOGD(TAG, "[METER] EnergyInput: %.1f Wh", wh); - return wh_i; -} - -float setCurrentInput(void) -{ - float a = 0.0f; - bool have = false; - - portENTER_CRITICAL(&s_meter_mux); - have = s_meter.have_data; - if (have) - a = s_meter.sum_current; - portEXIT_CRITICAL(&s_meter_mux); - - if (!have) - ESP_LOGW(TAG, "[METER] CurrentInput: no data (return 0)"); - else - ESP_LOGD(TAG, "[METER] CurrentInput: %.2f A (total)", a); - - return a; -} - -float setVoltageInput(void) -{ - float v = 0.0f; - bool have = false; - - portENTER_CRITICAL(&s_meter_mux); - have = s_meter.have_data; - if (have) - v = s_meter.avg_voltage; - portEXIT_CRITICAL(&s_meter_mux); - - if (!have) - ESP_LOGW(TAG, "[METER] VoltageInput: no data (return 0)"); - else - ESP_LOGD(TAG, "[METER] VoltageInput: %.1f V (avg)", v); - - return v; -} - -float setPowerInput(void) -{ - float w = setPowerMeterInput(); - ESP_LOGD(TAG, "[METER] PowerInput: %.1f W", w); - return w; -} - -float setTemperatureInput(void) -{ - ESP_LOGD(TAG, "TemperatureInput"); - return 16.5f; -} - -void setSmartChargingCurrentOutput(float limit) -{ - ESP_LOGI(TAG, "SmartChargingCurrentOutput: %.0f", limit); -} - -void setSmartChargingPowerOutput(float limit) -{ - ESP_LOGI(TAG, "SmartChargingPowerOutput: %.0f", limit); -} - -void setSmartChargingOutput(float power, float current, int nphases) -{ - ESP_LOGI(TAG, "SmartChargingOutput: P=%.0f W, I=%.0f A, phases=%d", power, current, nphases); -} - -void setGetConfiguration(const char *payload, size_t len) -{ - ESP_LOGI(TAG, "GetConfiguration: %.*s (%u)", (int)len, payload, (unsigned)len); -} - -void setStartTransaction(const char *payload, size_t len) -{ - ESP_LOGI(TAG, "StartTransaction: %.*s (%u)", (int)len, payload, (unsigned)len); -} - -void setChangeConfiguration(const char *payload, size_t len) -{ - ESP_LOGI(TAG, "ChangeConfiguration: %.*s (%u)", (int)len, payload, (unsigned)len); -} - -void OnResetExecute(bool state) -{ - ESP_LOGI(TAG, "OnResetExecute (state=%d)", state); - esp_restart(); -} - -bool setOccupiedInput(void) -{ - ESP_LOGD(TAG, "setOccupiedInput"); - return false; -} - -bool setStartTxReadyInput(void) -{ - ESP_LOGD(TAG, "setStartTxReadyInput"); - return true; -} - -bool setStopTxReadyInput(void) -{ - ESP_LOGD(TAG, "setStopTxReadyInput"); - return true; -} - -bool setOnResetNotify(bool value) -{ - ESP_LOGI(TAG, "setOnResetNotify %d", value); - return true; -} - -void notificationOutput(OCPP_Transaction *transaction, enum OCPP_TxNotification txNotification) -{ - (void)transaction; - - ESP_LOGI(TAG, "TxNotification: %d", txNotification); - - switch (txNotification) - { - case Authorized: - ESP_LOGI(TAG, "Authorized"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, NULL, 0, portMAX_DELAY); - break; - - case AuthorizationRejected: - ESP_LOGI(TAG, "AuthorizationRejected"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, portMAX_DELAY); - break; - - case AuthorizationTimeout: - ESP_LOGI(TAG, "AuthorizationTimeout"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_TIMEOUT, NULL, 0, portMAX_DELAY); - break; - - case ReservationConflict: - ESP_LOGI(TAG, "ReservationConflict"); - break; - - case ConnectionTimeout: - ESP_LOGI(TAG, "ConnectionTimeout"); - break; - - case DeAuthorized: - ESP_LOGI(TAG, "DeAuthorized"); - break; - - case RemoteStart: - ESP_LOGI(TAG, "RemoteStart"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, NULL, 0, portMAX_DELAY); - break; - - case RemoteStop: - ESP_LOGI(TAG, "RemoteStop"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_STOP, NULL, 0, portMAX_DELAY); - break; - - case StartTx: - ESP_LOGI(TAG, "StartTx"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, NULL, 0, portMAX_DELAY); - break; - - case StopTx: - ESP_LOGI(TAG, "StopTx"); - esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, NULL, 0, portMAX_DELAY); - break; - } -} - -bool ocpp_is_connected(void) -{ - return g_ocpp_conn != NULL; -} - -const char *addErrorCodeInput(void) -{ - const char *ptr = NULL; - uint32_t error = evse_get_error(); - - if (error & EVSE_ERR_PILOT_FAULT_BIT) - ptr = "InternalError"; - else if (error & EVSE_ERR_DIODE_SHORT_BIT) - ptr = "InternalError"; - else if (error & EVSE_ERR_LOCK_FAULT_BIT) - ptr = "ConnectorLockFailure"; - else if (error & EVSE_ERR_UNLOCK_FAULT_BIT) - ptr = "ConnectorLockFailure"; - else if (error & EVSE_ERR_RCM_TRIGGERED_BIT) - ptr = "OtherError"; - else if (error & EVSE_ERR_RCM_SELFTEST_FAULT_BIT) - ptr = "OtherError"; - else if (error & EVSE_ERR_TEMPERATURE_HIGH_BIT) - ptr = "HighTemperature"; - else if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT) - ptr = "OtherError"; - - return ptr; -} - -// ----------------------------------------------------------------------------- -// Start / Stop OCPP -// ----------------------------------------------------------------------------- -void ocpp_start(void) -{ - ESP_LOGI(TAG, "Starting OCPP"); - - if (ocpp_task != NULL) - { - ESP_LOGW(TAG, "OCPP already running"); - return; - } - - storage_init_best_effort(); - - enabled = ocpp_get_enabled(); - if (!enabled) - { - ESP_LOGW(TAG, "OCPP disabled"); - return; - } - - char serverstr[64] = {0}; - ocpp_get_server(serverstr); - if (serverstr[0] == '\0') - { - ESP_LOGW(TAG, "No OCPP server configured. Skipping connection."); - return; - } - - char charge_id[64] = {0}; - ocpp_get_charge_id(charge_id); - if (charge_id[0] == '\0') - { - ESP_LOGW(TAG, "No OCPP charge_id configured. Skipping connection."); - return; - } - - mg_mgr_init(&mgr); - mg_log_set(MG_LL_ERROR); - - struct OCPP_FilesystemOpt fsopt = {.use = true, .mount = true, .formatFsOnFail = true}; - - g_ocpp_conn = ocpp_makeConnection(&mgr, - serverstr, - charge_id, - "", - "", - fsopt); - if (!g_ocpp_conn) - { - ESP_LOGE(TAG, "ocpp_makeConnection failed"); - mg_mgr_free(&mgr); - return; - } - - - //chargePointModel: "EPower M1" - //chargePointVendor: "Plixin" - //firmwareVersion: "FW-PLXV1.0" - //chargePointSerialNumber: "SN001" - - ocpp_initialize(g_ocpp_conn, "EPower M1", "Plixin", fsopt, false); - - ocpp_setEvReadyInput(&setEvReadyInput); - ocpp_setEvseReadyInput(&setEvseReadyInput); - ocpp_setConnectorPluggedInput(&setConnectorPluggedInput); - ocpp_setOnResetExecute(&OnResetExecute); - ocpp_setTxNotificationOutput(¬ificationOutput); - ocpp_setStopTxReadyInput(&setStopTxReadyInput); - ocpp_setOnResetNotify(&setOnResetNotify); - - ocpp_setEnergyMeterInput(&setEnergyInput); - - ocpp_addMeterValueInputFloat(&setCurrentInput, "Current.Import", "A", NULL, NULL); - ocpp_addMeterValueInputFloat(&getCurrentOffered, "Current.Offered", "A", NULL, NULL); - ocpp_addMeterValueInputFloat(&setVoltageInput, "Voltage", "V", NULL, NULL); - ocpp_addMeterValueInputFloat(&setTemperatureInput, "Temperature", "Celsius", NULL, NULL); - ocpp_addMeterValueInputFloat(&setPowerMeterInput, "Power.Active.Import", "W", NULL, NULL); - ocpp_addMeterValueInputFloat(&setEnergyMeterInput, "Energy.Active.Import.Register", "Wh", NULL, NULL); - - ocpp_addErrorCodeInput(&addErrorCodeInput); - - xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 3, &ocpp_task); - - if (!s_auth_verify_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_register( - AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY, - &ocpp_on_auth_verify, NULL, &s_auth_verify_inst)); - ESP_LOGI(TAG, "Registered AUTH_EVENT_TAG_VERIFY listener"); - } - - if (!s_evse_state_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_register( - EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, - &evse_event_handler, NULL, &s_evse_state_inst)); - } - - if (!s_evse_enable_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_register( - EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED, - &evse_enable_available_handler, NULL, &s_evse_enable_inst)); - } - if (!s_evse_available_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_register( - EVSE_EVENTS, EVSE_EVENT_AVAILABLE_UPDATED, - &evse_enable_available_handler, NULL, &s_evse_available_inst)); - } - - if (!s_meter_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_register( - METER_EVENT, METER_EVENT_DATA_READY, - &on_meter_event, NULL, &s_meter_inst)); - ESP_LOGI(TAG, "Registered METER_EVENT_DATA_READY listener (EVSE source)"); - } -} - -void ocpp_stop(void) -{ - ESP_LOGI(TAG, "Stopping OCPP"); - - if (ocpp_task) - { - vTaskDelete(ocpp_task); - ocpp_task = NULL; - } - - ocpp_deinitialize(); - - if (g_ocpp_conn) - { - ocpp_deinitConnection(g_ocpp_conn); - g_ocpp_conn = NULL; - } - - mg_mgr_free(&mgr); - - if (s_auth_verify_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_unregister( - AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY, s_auth_verify_inst)); - s_auth_verify_inst = NULL; - } - - if (s_evse_state_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_unregister( - EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, s_evse_state_inst)); - s_evse_state_inst = NULL; - } - - if (s_evse_enable_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_unregister( - EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED, s_evse_enable_inst)); - s_evse_enable_inst = NULL; - } - if (s_evse_available_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_unregister( - EVSE_EVENTS, EVSE_EVENT_AVAILABLE_UPDATED, s_evse_available_inst)); - s_evse_available_inst = NULL; - } - if (s_meter_inst) - { - ESP_ERROR_CHECK(esp_event_handler_instance_unregister( - METER_EVENT, METER_EVENT_DATA_READY, s_meter_inst)); - s_meter_inst = NULL; - } -} - -// === Fim de: components/ocpp/src/ocpp.c === - - -// === Início de: components/ocpp/include/ocpp_events.h === -#pragma once -#include "esp_event.h" -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* Base de eventos do OCPP (igual ao padrão usado em auth_events.h) */ -ESP_EVENT_DECLARE_BASE(OCPP_EVENTS); - -/* IDs de eventos do OCPP */ -typedef enum { - OCPP_EVENT_CONNECTED = 0, // payload: const char* (server URL) – opcional - OCPP_EVENT_DISCONNECTED, // payload: NULL - OCPP_EVENT_AUTHORIZED, // payload: ocpp_idtag_event_t (opcional) - OCPP_EVENT_AUTH_REJECTED, // payload: ocpp_idtag_event_t (opcional) - OCPP_EVENT_AUTH_TIMEOUT, // payload: NULL - OCPP_EVENT_REMOTE_START, // payload: ocpp_idtag_event_t (opcional) - OCPP_EVENT_REMOTE_STOP, // payload: NULL - OCPP_EVENT_START_TX, // payload: ocpp_tx_event_t (opcional) - OCPP_EVENT_STOP_TX, // payload: ocpp_reason_event_t (opcional) - OCPP_EVENT_RESET, // payload: NULL - OCPP_EVENT_OPERATIVE_UPDATED -} ocpp_event_id_t; - -/* Limites de strings simples (evita dependência de auth.h) */ -#define OCPP_IDTAG_MAX 32 -#define OCPP_REASON_MAX 32 - -/* Payloads opcionais */ -typedef struct { - char idTag[OCPP_IDTAG_MAX]; -} ocpp_idtag_event_t; - -typedef struct { - int tx_id; // se disponível -} ocpp_tx_event_t; - -typedef struct { - char reason[OCPP_REASON_MAX]; -} ocpp_reason_event_t; - -// Payload do novo evento -typedef struct { - bool operative; // true = Operative, false = Inoperative - int64_t timestamp_us; // esp_timer_get_time() -} ocpp_operative_event_t; - -#ifdef __cplusplus -} -#endif - -// === Fim de: components/ocpp/include/ocpp_events.h === - - -// === Início de: components/ocpp/include/ocpp.h === -#ifndef OCPP_H_ -#define OCPP_H_ - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Start OCPP - */ -void ocpp_start(void); - -/** - * @brief Stop OCPP - */ -void ocpp_stop(void); - -/* Config getters / setters */ -bool ocpp_get_enabled(void); -void ocpp_set_enabled(bool value); - -void ocpp_get_server(char *value); // buffer >= 64 -void ocpp_set_server(char *value); - -void ocpp_get_charge_id(char *value); // buffer >= 64 -void ocpp_set_charge_id(char *value); - -/* Estado de conexão */ -bool ocpp_is_connected(void); - -#ifdef __cplusplus -} -#endif - -#endif /* OCPP_H_ */ - -// === Fim de: components/ocpp/include/ocpp.h === diff --git a/readproject.py b/readproject.py deleted file mode 100644 index 2e626b5..0000000 --- a/readproject.py +++ /dev/null @@ -1,66 +0,0 @@ -import os - -TAMANHO_MAX = 230000 # Limite por arquivo - -def coletar_arquivos(diretorios, extensoes=(".c", ".h")): - arquivos = [] - for diretorio in diretorios: - for raiz, pastas, nomes_arquivos in os.walk(diretorio): - pastas[:] = [p for p in pastas if p != "build"] # Ignorar "build" - for nome in nomes_arquivos: - if nome.endswith(extensoes): - caminho_completo = os.path.join(raiz, nome) - arquivos.append(caminho_completo) - return arquivos - -def unir_em_partes(arquivos, prefixo="projeto_parte", limite=TAMANHO_MAX): - parte = 1 - conteudo_atual = "." - total_arquivos = 0 - - for arquivo in arquivos: - try: - with open(arquivo, "r", encoding="utf-8") as f_origem: - conteudo = f_origem.read() - except Exception as e: - print(f"⚠️ Erro ao ler {arquivo}: {e}") - continue - - bloco = f"\n\n// === Início de: {arquivo} ===\n{conteudo}\n// === Fim de: {arquivo} ===\n" - - # Se ultrapassar o limite, salva em um novo arquivo - if len(conteudo_atual) + len(bloco) > limite: - nome_saida = f"{prefixo}{parte}.c" - with open(nome_saida, "w", encoding="utf-8") as f_saida: - f_saida.write(conteudo_atual) - print(f"✅ Criado: {nome_saida}") - parte += 1 - conteudo_atual = "" # reinicia buffer - - conteudo_atual += bloco - total_arquivos += 1 - - # Salvar o que sobrou - if conteudo_atual: - nome_saida = f"{prefixo}{parte}.c" - with open(nome_saida, "w", encoding="utf-8") as f_saida: - f_saida.write(conteudo_atual) - print(f"✅ Criado: {nome_saida}") - - print(f"\n🔹 {total_arquivos} arquivos de código processados.") - print(f"🔹 Arquivos gerados: {parte}") - -def main(): - diretorio_main = "main" - componentes_escolhidos = [ - "ocpp" - ] - - diretorios_componentes = [os.path.join("components", nome) for nome in componentes_escolhidos] - diretorios_para_incluir = [diretorio_main] + diretorios_componentes - - arquivos = coletar_arquivos(diretorios_para_incluir) - unir_em_partes(arquivos) - -if __name__ == "__main__": - main()