#include "evse_error.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/portmacro.h" #include "esp_log.h" #include "ntc_sensor.h" #include "esp_event.h" #include "esp_timer.h" #include "evse_events.h" #include "evse_config.h" static const char *TAG = "evse_error"; // ---------------------------------------------------- // Estado interno // ---------------------------------------------------- // raw_bits = erros “instantâneos” conforme checks (set/clear) // visible_bits = erros expostos ao resto do sistema (com holdoff) // clear_deadline = quando pode finalmente limpar visible_bits para 0 static uint32_t raw_bits = 0; static uint32_t visible_bits = 0; static TickType_t clear_deadline = 0; // Sticky flag: "todos erros visíveis foram limpos" static bool error_cleared = false; // Proteção contra concorrência static portMUX_TYPE error_mux = portMUX_INITIALIZER_UNLOCKED; // ---------------------------------------------------- // Helper: publicar evento de alteração de erro (visible_bits) // ---------------------------------------------------- static void evse_error_post_event(uint32_t new_bits, uint32_t changed_mask) { evse_error_event_data_t ev = { .error_bits = new_bits, .changed_mask = changed_mask, .timestamp_us = esp_timer_get_time(), }; esp_err_t err = esp_event_post( EVSE_EVENTS, EVSE_EVENT_ERROR_CHANGED, &ev, sizeof(ev), portMAX_DELAY); if (err != ESP_OK) { ESP_LOGW(TAG, "Falha ao publicar EVSE_EVENT_ERROR_CHANGED: %s", esp_err_to_name(err)); } } // ---------------------------------------------------- // Helpers internos // ---------------------------------------------------- static bool raw_has_bit(uint32_t bit) { bool v; portENTER_CRITICAL(&error_mux); v = ((raw_bits & bit) != 0); portEXIT_CRITICAL(&error_mux); return v; } static void reconcile_visible_locked(TickType_t now) { // Se existem erros reais, o visível segue imediatamente if (raw_bits != 0) { visible_bits = raw_bits; clear_deadline = 0; error_cleared = false; return; } // raw_bits == 0 if (visible_bits == 0) { clear_deadline = 0; return; } // Ainda há erro visível (holdoff). Arma deadline 1x. if (clear_deadline == 0) { clear_deadline = now + pdMS_TO_TICKS(EVSE_ERROR_COOLDOWN_MS); return; } // Expirou -> limpar finalmente if ((int32_t)(now - clear_deadline) >= 0) { visible_bits = 0; clear_deadline = 0; error_cleared = true; } } // ---------------------------------------------------- // API pública // ---------------------------------------------------- void evse_error_init(void) { uint32_t old_vis, new_vis, changed; bool post = false; portENTER_CRITICAL(&error_mux); old_vis = visible_bits; raw_bits = 0; visible_bits = 0; clear_deadline = 0; error_cleared = false; new_vis = visible_bits; changed = old_vis ^ new_vis; post = (changed != 0); portEXIT_CRITICAL(&error_mux); if (post) { evse_error_post_event(new_vis, changed); } } uint32_t evse_get_error(void) { portENTER_CRITICAL(&error_mux); uint32_t val = visible_bits; portEXIT_CRITICAL(&error_mux); return val; } bool evse_error_is_active(void) { return evse_get_error() != 0; } uint32_t evse_error_get_bits(void) { return evse_get_error(); } bool evse_error_cleared_flag(void) { portENTER_CRITICAL(&error_mux); bool v = error_cleared; portEXIT_CRITICAL(&error_mux); return v; } void evse_error_reset_flag(void) { portENTER_CRITICAL(&error_mux); error_cleared = false; portEXIT_CRITICAL(&error_mux); } void evse_error_set(uint32_t bitmask) { uint32_t old_vis, new_vis, changed; TickType_t now = xTaskGetTickCount(); portENTER_CRITICAL(&error_mux); old_vis = visible_bits; raw_bits |= bitmask; // se aparece qualquer erro, o "cleared" deixa de ser verdade error_cleared = false; reconcile_visible_locked(now); new_vis = visible_bits; changed = old_vis ^ new_vis; portEXIT_CRITICAL(&error_mux); if (changed != 0) { evse_error_post_event(new_vis, changed); } } void evse_error_clear(uint32_t bitmask) { uint32_t old_vis, new_vis, changed; TickType_t now = xTaskGetTickCount(); portENTER_CRITICAL(&error_mux); old_vis = visible_bits; raw_bits &= ~bitmask; // ✅ Aqui é onde o “60s depois do erro desaparecer” é armado: // quando raw_bits chega a 0, reconcile arma clear_deadline (uma vez) reconcile_visible_locked(now); new_vis = visible_bits; changed = old_vis ^ new_vis; portEXIT_CRITICAL(&error_mux); if (changed != 0) { evse_error_post_event(new_vis, changed); } } void evse_error_tick(void) { uint32_t old_vis, new_vis, changed; TickType_t now = xTaskGetTickCount(); portENTER_CRITICAL(&error_mux); old_vis = visible_bits; reconcile_visible_locked(now); new_vis = visible_bits; changed = old_vis ^ new_vis; portEXIT_CRITICAL(&error_mux); if (changed != 0) { evse_error_post_event(new_vis, changed); } } // ---------------------------------------------------- // Checks (raw -> set/clear) // ---------------------------------------------------- void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v) { ESP_LOGD(TAG, "Verificando erro: pilot_voltage=%d, is_n12v=%s", pilot_voltage, is_n12v ? "true" : "false"); // 1) Falha elétrica geral no pilot if (pilot_voltage == PILOT_VOLTAGE_1) { if (!raw_has_bit(EVSE_ERR_PILOT_FAULT_BIT)) { ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)"); } evse_error_set(EVSE_ERR_PILOT_FAULT_BIT); } else { evse_error_clear(EVSE_ERR_PILOT_FAULT_BIT); } // 2) Falta de -12V durante PWM (C ou D) if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v) { if (!raw_has_bit(EVSE_ERR_DIODE_SHORT_BIT)) { ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)"); } evse_error_set(EVSE_ERR_DIODE_SHORT_BIT); } else { evse_error_clear(EVSE_ERR_DIODE_SHORT_BIT); } } void evse_temperature_check(void) { float temp_c = ntc_temp_sensor(); uint8_t threshold = evse_get_temp_threshold(); ESP_LOGD(TAG, "Verificando temperatura: atual=%.2f °C, limite=%d °C", temp_c, threshold); // Temperatura inválida -> erro de sensor if (temp_c < -40.0f || temp_c > 150.0f) { if (!raw_has_bit(EVSE_ERR_TEMPERATURE_FAULT_BIT)) { ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado"); } evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT); return; } // Leitura válida -> limpa erro de sensor evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT); // Temperatura máxima if (temp_c >= threshold) { if (!raw_has_bit(EVSE_ERR_TEMPERATURE_HIGH_BIT)) { ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C", temp_c, threshold); } evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT); } else { evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT); } }