// === 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 "wifi.h" #include "board_config.h" #include "logger.h" #include "rest_main.h" #include "peripherals.h" #include "protocols.h" #include "evse_manager.h" #include "evse_api.h" #include "auth.h" #include "loadbalancer.h" #include "meter_manager.h" #define EVSE_MANAGER_TICK_PERIOD_MS 1000 #define AP_CONNECTION_TIMEOUT 120000 #define RESET_HOLD_TIME 10000 #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; static TickType_t press_tick = 0; static TickType_t last_interrupt_tick = 0; static bool pressed = false; // // 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, total, used); else ESP_LOGE(TAG, "Failed to get SPIFFS info: %s", 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) { EventBits_t mode_bits; while (1) { 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) { xEventGroupWaitBits(wifi_event_group, WIFI_AP_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY); } else { if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT) { //wifi_ap_stop(); } } } else if (mode_bits & WIFI_STA_MODE_BIT) { xEventGroupWaitBits(wifi_event_group, WIFI_STA_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY); } } } // // Botão e tratamento // static void handle_button_press(void) { ESP_LOGI(TAG, "Ativando modo AP"); if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)) { wifi_ap_start(); } } static void user_input_task_func(void *param) { uint32_t notification; while (1) { if (xTaskNotifyWait(0x00, 0xFF, ¬ification, portMAX_DELAY)) { if (notification & PRESS_BIT) { press_tick = xTaskGetTickCount(); pressed = true; ESP_LOGI(TAG, "Botão pressionado"); } if (notification & RELEASED_BIT && pressed) { pressed = false; ESP_LOGI(TAG, "Botão liberado"); handle_button_press(); } } } } static void IRAM_ATTR button_isr_handler(void *arg) { BaseType_t higher_task_woken = pdFALSE; TickType_t now = xTaskGetTickCountFromISR(); if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS)) return; last_interrupt_tick = now; if (!gpio_get_level(board_config.button_wifi_gpio)) { xTaskNotifyFromISR(user_input_task, RELEASED_BIT, eSetBits, &higher_task_woken); } else { xTaskNotifyFromISR(user_input_task, PRESS_BIT, eSetBits, &higher_task_woken); } 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 // static void init_modules(void) { peripherals_init(); //api_init(); ESP_ERROR_CHECK(rest_server_init("/data")); protocols_init(); evse_manager_init(); evse_init(); // Cria a task para FSM button_init(); auth_init(); loadbalancer_init(); meter_manager_grid_init(); meter_manager_grid_start(); //meter_manager_evse_init(); // Outros módulos (descomente conforme necessário) // meter_init(); // ocpp_start(); // orno_modbus_start(); // currentshaper_start(); // initWiegand(); // meter_zigbee_start(); // master_sync_start(); // slave_sync_start(); } // // Função principal do firmware // void app_main(void) { logger_init(); esp_log_set_vprintf(logger_vprintf); 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(); wifi_ini(); //wifi_ap_start(); init_modules(); xTaskCreate(wifi_event_task_func, "wifi_event_task", 8 * 1024, NULL, 3, NULL); xTaskCreate(user_input_task_func, "user_input_task", 4 * 1024, NULL, 3, &user_input_task); } // === Fim de: main/main.c === // === Início de: components/evse/evse_pilot.c === #include #include #include #include #include #include "driver/ledc.h" #include "esp_err.h" #include "esp_log.h" #include "esp_rom_sys.h" #include "evse_pilot.h" #include "adc.h" #include "board_config.h" #define PILOT_PWM_TIMER LEDC_TIMER_0 #define PILOT_PWM_CHANNEL LEDC_CHANNEL_0 #define PILOT_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE #define PILOT_PWM_DUTY_RES LEDC_TIMER_10_BIT #define PILOT_PWM_MAX_DUTY 1023 #define NUM_PILOT_SAMPLES 100 #define MAX_SAMPLE_ATTEMPTS 1000 #define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior static const char *TAG = "evse_pilot"; void pilot_init(void) { ledc_timer_config_t ledc_timer = { .speed_mode = PILOT_PWM_SPEED_MODE, .timer_num = PILOT_PWM_TIMER, .duty_resolution = PILOT_PWM_DUTY_RES, .freq_hz = 1000, .clk_cfg = LEDC_AUTO_CLK }; ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); ledc_channel_config_t ledc_channel = { .speed_mode = PILOT_PWM_SPEED_MODE, .channel = PILOT_PWM_CHANNEL, .timer_sel = PILOT_PWM_TIMER, .intr_type = LEDC_INTR_DISABLE, .gpio_num = board_config.pilot_pwm_gpio, .duty = 0, .hpoint = 0 }; ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0)); ESP_ERROR_CHECK(ledc_fade_func_install(0)); adc_oneshot_chan_cfg_t config = { .bitwidth = ADC_BITWIDTH_DEFAULT, .atten = ADC_ATTEN_DB_12, }; ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, board_config.pilot_adc_channel, &config)); } void pilot_set_level(bool level) { ESP_LOGI(TAG, "Set level %d", level); ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, level ? 1 : 0); } void pilot_set_amps(uint16_t amps) { ESP_LOGI(TAG, "Set amps %d", amps); if (amps < 60 || amps > 800) { ESP_LOGE(TAG, "Invalid ampere value: %d A*10", amps); return; } uint32_t duty; if (amps <= 510) { duty = (PILOT_PWM_MAX_DUTY * amps) / 600; } else { duty = ((PILOT_PWM_MAX_DUTY * amps) / 2500) + (64 * (PILOT_PWM_MAX_DUTY / 100)); } if (duty > PILOT_PWM_MAX_DUTY) duty = PILOT_PWM_MAX_DUTY; ESP_LOGI(TAG, "Set amp %dA*10 -> duty %lu/%d", amps, duty, PILOT_PWM_MAX_DUTY); ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty); ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL); } static int compare_int(const void *a, const void *b) { return (*(int *)a - *(int *)b); } static int select_low_median_qsort(int *src, int n, int percent) { int k = (n * percent) / 100; if (k == 0) k = 1; int *copy = alloca(n * sizeof(int)); memcpy(copy, src, n * sizeof(int)); qsort(copy, n, sizeof(int), compare_int); return copy[k / 2]; } static int select_high_median_qsort(int *src, int n, int percent) { int k = (n * percent) / 100; if (k == 0) k = 1; int *copy = alloca(n * sizeof(int)); memcpy(copy, src, n * sizeof(int)); qsort(copy, n, sizeof(int), compare_int); return copy[n - k + (k / 2)]; } void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12) { ESP_LOGD(TAG, "pilot_measure"); int samples[NUM_PILOT_SAMPLES]; int collected = 0, attempts = 0; int sample; while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) { if (adc_oneshot_read(adc_handle, board_config.pilot_adc_channel, &sample) == ESP_OK) { samples[collected++] = sample; esp_rom_delay_us(10); } else { esp_rom_delay_us(100); attempts++; } } if (collected < NUM_PILOT_SAMPLES) { ESP_LOGW(TAG, "Timeout on sample read (%d/%d)", collected, NUM_PILOT_SAMPLES); *up_voltage = PILOT_VOLTAGE_1; *down_voltage_n12 = false; return; } int high_raw = select_high_median_qsort(samples, collected, PILOT_EXTREME_PERCENT); int low_raw = select_low_median_qsort(samples, collected, PILOT_EXTREME_PERCENT); ESP_LOGD(TAG, "Final: high_raw=%d, low_raw=%d", high_raw, low_raw); int high_mv = 0; int low_mv = 0; if (adc_cali_raw_to_voltage(adc_cali_handle, high_raw, &high_mv) != ESP_OK || adc_cali_raw_to_voltage(adc_cali_handle, low_raw, &low_mv) != ESP_OK) { ESP_LOGW(TAG, "ADC calibration failed"); *up_voltage = PILOT_VOLTAGE_1; *down_voltage_n12 = false; return; } if (high_mv >= board_config.pilot_down_threshold_12) *up_voltage = PILOT_VOLTAGE_12; else if (high_mv >= board_config.pilot_down_threshold_9) *up_voltage = PILOT_VOLTAGE_9; else if (high_mv >= board_config.pilot_down_threshold_6) *up_voltage = PILOT_VOLTAGE_6; else if (high_mv >= board_config.pilot_down_threshold_3) *up_voltage = PILOT_VOLTAGE_3; else *up_voltage = PILOT_VOLTAGE_1; *down_voltage_n12 = (low_mv <= board_config.pilot_down_threshold_n12); ESP_LOGD(TAG, "Final: up_voltage=%d, down_voltage_n12=%d", *up_voltage, *down_voltage_n12); } // === Fim de: components/evse/evse_pilot.c === // === Início de: components/evse/evse_hardware.c === #include "evse_hardware.h" #include "evse_pilot.h" #include "ac_relay.h" #include "socket_lock.h" #include "proximity.h" static const char *TAG = "evse_hardware"; void evse_hardware_init(void) { pilot_init(); pilot_set_level(true); // Sinal piloto em 12V (inicial) ac_relay_set_state(false); // Relé desligado //socket_lock_set_locked(false); // Destrava o conector } void evse_hardware_tick(void) { // Tick para atualizações de hardware com polling, se necessário } bool evse_hardware_is_pilot_high(void) { return pilot_get_state(); // true se sinal piloto estiver em nível alto } bool evse_hardware_is_vehicle_connected(void) { // Verifica se o veículo está conectado pelo resistor do pino PP return proximity_get_max_current() > 0; } bool evse_hardware_is_energy_detected(void) { return false; } void evse_hardware_relay_on(void) { ac_relay_set_state(true); } void evse_hardware_relay_off(void) { ac_relay_set_state(false); } bool evse_hardware_relay_status(void) { return ac_relay_get_state(); } void evse_hardware_lock(void) { socket_lock_set_locked(true); } void evse_hardware_unlock(void) { socket_lock_set_locked(false); } bool evse_hardware_is_locked(void) { return socket_lock_is_locked_state(); } // === Fim de: components/evse/evse_hardware.c === // === Início de: components/evse/evse_state.c === #include "evse_api.h" #include "evse_state.h" #include "evse_events.h" #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "esp_log.h" // ========================= // Internal State Variables // ========================= static evse_state_t current_state = EVSE_STATE_A; static bool is_authorized = false; static TickType_t session_start_tick = 0; static portMUX_TYPE state_mux = portMUX_INITIALIZER_UNLOCKED; // ========================= // Internal Mapping // ========================= static evse_state_event_t map_state_to_event(evse_state_t s) { switch (s) { case EVSE_STATE_A: return EVSE_STATE_EVENT_IDLE; case EVSE_STATE_B1: return EVSE_STATE_EVENT_WAITING; case EVSE_STATE_B2: case EVSE_STATE_C1: case EVSE_STATE_C2: return EVSE_STATE_EVENT_CHARGING; case EVSE_STATE_E: case EVSE_STATE_F: return EVSE_STATE_EVENT_FAULT; default: return EVSE_STATE_EVENT_IDLE; } } // ========================= // Public API // ========================= void evse_set_state(evse_state_t state) { bool changed = false; evse_state_t previous_state; portENTER_CRITICAL(&state_mux); previous_state = current_state; if (state != current_state) { current_state = state; changed = true; if (evse_state_is_charging(state) && !evse_state_is_charging(previous_state)) { session_start_tick = xTaskGetTickCount(); } } portEXIT_CRITICAL(&state_mux); if (changed) { ESP_LOGI("EVSE_STATE", "State changed from %s to %s", evse_state_to_str(previous_state), evse_state_to_str(state)); evse_state_event_data_t evt = { .state = map_state_to_event(state) }; esp_event_post(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &evt, sizeof(evt), portMAX_DELAY); } } evse_state_t evse_get_state(void) { portENTER_CRITICAL(&state_mux); evse_state_t s = current_state; portEXIT_CRITICAL(&state_mux); return s; } TickType_t evse_get_session_start(void) { portENTER_CRITICAL(&state_mux); TickType_t t = session_start_tick; portEXIT_CRITICAL(&state_mux); return t; } const char* evse_state_to_str(evse_state_t state) { switch (state) { case EVSE_STATE_A: return "A - EV Not Connected (12V)"; case EVSE_STATE_B1: return "B1 - EV Connected (9V, Not Authorized)"; case EVSE_STATE_B2: return "B2 - EV Connected (9V, Authorized and Ready)"; case EVSE_STATE_C1: return "C1 - Charging Requested (6V, Relay Off)"; case EVSE_STATE_C2: return "C2 - Charging Active (6V, Relay On)"; case EVSE_STATE_D1: return "D1 - Ventilation Required (3V, Relay Off)"; case EVSE_STATE_D2: return "D2 - Ventilation Active (3V, Relay On)"; case EVSE_STATE_E: return "E - Error: Control Pilot Shorted to Ground (0V)"; case EVSE_STATE_F: return "F - Fault: EVSE Unavailable or No Pilot Signal"; default: return "Unknown State"; } } void evse_state_init(void) { portENTER_CRITICAL(&state_mux); current_state = EVSE_STATE_A; session_start_tick = xTaskGetTickCount(); is_authorized = true; portEXIT_CRITICAL(&state_mux); ESP_LOGI("EVSE_STATE", "Initialized in state: %s", evse_state_to_str(current_state)); evse_state_event_data_t evt = { .state = map_state_to_event(current_state) }; esp_event_post(EVSE_EVENTS, EVSE_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY); } void evse_state_tick(void) { // Placeholder for future state logic } bool evse_state_is_charging(evse_state_t state) { return state == EVSE_STATE_C1 || state == EVSE_STATE_C2; } bool evse_state_is_plugged(evse_state_t state) { return state == EVSE_STATE_B1 || state == EVSE_STATE_B2 || state == EVSE_STATE_C1 || state == EVSE_STATE_C2 || state == EVSE_STATE_D1 || state == EVSE_STATE_D2; } bool evse_state_is_session(evse_state_t state) { return state == EVSE_STATE_B2 || state == EVSE_STATE_C1 || state == EVSE_STATE_C2; } void evse_state_set_authorized(bool authorized) { portENTER_CRITICAL(&state_mux); is_authorized = authorized; portEXIT_CRITICAL(&state_mux); } bool evse_state_get_authorized(void) { portENTER_CRITICAL(&state_mux); bool result = is_authorized; portEXIT_CRITICAL(&state_mux); return result; } // === Fim de: components/evse/evse_state.c === // === Início de: components/evse/evse_fsm.c === // evse_fsm.c - Máquina de Estados EVSE com controle centralizado #include "evse_fsm.h" #include "evse_api.h" #include "evse_pilot.h" #include "evse_config.h" #include "esp_log.h" #include "ac_relay.h" #include "board_config.h" #include "socket_lock.h" #include "proximity.h" #include "rcm.h" #include "evse_state.h" static const char *TAG = "evse_fsm"; #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif static bool c1_d1_waiting = false; static TickType_t c1_d1_relay_to = 0; void evse_fsm_reset(void) { evse_set_state(EVSE_STATE_A); c1_d1_waiting = false; c1_d1_relay_to = 0; } static void update_outputs(evse_state_t state) { const uint16_t current = evse_get_runtime_charging_current(); uint8_t cable_max_current = evse_get_max_charging_current(); const bool socket_outlet = evse_get_socket_outlet(); if (socket_outlet) { cable_max_current = proximity_get_max_current(); } switch (state) { case EVSE_STATE_A: case EVSE_STATE_E: case EVSE_STATE_F: ac_relay_set_state(false); pilot_set_level(state == EVSE_STATE_A); if (board_config.socket_lock && socket_outlet) { socket_lock_set_locked(false); } break; case EVSE_STATE_B1: pilot_set_level(true); ac_relay_set_state(false); if (board_config.socket_lock && socket_outlet) { socket_lock_set_locked(true); } if (rcm_test()) { ESP_LOGI(TAG, "RCM self test passed"); } else { ESP_LOGW(TAG, "RCM self test failed"); } break; case EVSE_STATE_B2: pilot_set_amps(MIN(current * 10, cable_max_current * 10)); ac_relay_set_state(false); break; case EVSE_STATE_C1: case EVSE_STATE_D1: pilot_set_level(true); c1_d1_waiting = true; c1_d1_relay_to = xTaskGetTickCount() + pdMS_TO_TICKS(6000); break; case EVSE_STATE_C2: case EVSE_STATE_D2: pilot_set_amps(MIN(current * 10, cable_max_current * 10)); ac_relay_set_state(true); break; } } void evse_fsm_process(pilot_voltage_t pilot_voltage, bool authorized, bool available, bool enabled) { TickType_t now = xTaskGetTickCount(); evse_state_t prev = evse_get_state(); evse_state_t curr = prev; switch (curr) { case EVSE_STATE_A: if (!available) { evse_set_state(EVSE_STATE_F); } else if (pilot_voltage == PILOT_VOLTAGE_9) { evse_set_state(EVSE_STATE_B1); } break; case EVSE_STATE_B1: case EVSE_STATE_B2: if (!available) { evse_set_state(EVSE_STATE_F); break; } switch (pilot_voltage) { case PILOT_VOLTAGE_12: evse_set_state(EVSE_STATE_A); break; case PILOT_VOLTAGE_9: evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1); break; case PILOT_VOLTAGE_6: evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1); break; default: break; } break; case EVSE_STATE_C1: case EVSE_STATE_D1: if (c1_d1_waiting && now >= c1_d1_relay_to) { ac_relay_set_state(false); c1_d1_waiting = false; if (!available) { evse_set_state(EVSE_STATE_F); break; } } __attribute__((fallthrough)); // Evita warning de fallthrough implícito case EVSE_STATE_C2: case EVSE_STATE_D2: if (!enabled || !available) { evse_set_state((curr == EVSE_STATE_D2 || curr == EVSE_STATE_D1) ? EVSE_STATE_D1 : EVSE_STATE_C1); break; } switch (pilot_voltage) { case PILOT_VOLTAGE_6: evse_set_state((authorized && enabled) ? EVSE_STATE_C2 : EVSE_STATE_C1); break; case PILOT_VOLTAGE_3: evse_set_state((authorized && enabled) ? EVSE_STATE_D2 : EVSE_STATE_D1); break; case PILOT_VOLTAGE_9: evse_set_state((authorized && enabled) ? EVSE_STATE_B2 : EVSE_STATE_B1); break; case PILOT_VOLTAGE_12: evse_set_state(EVSE_STATE_A); break; default: break; } break; case EVSE_STATE_E: break; // Sem transições a partir de E case EVSE_STATE_F: if (available) { evse_set_state(EVSE_STATE_A); } break; } evse_state_t next = evse_get_state(); if (next != prev) { ESP_LOGI(TAG, "State changed: %s -> %s", evse_state_to_str(prev), evse_state_to_str(next)); update_outputs(next); } } // === Fim de: components/evse/evse_fsm.c === // === Início de: components/evse/evse_error.c === #include "evse_error.h" #include "evse_config.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "ntc_sensor.h" static const char *TAG = "evse_error"; static uint32_t error_bits = 0; static TickType_t auto_clear_timeout = 0; static bool error_cleared = false; void evse_error_init(void) { // Inicialização do sistema de erros } 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"); // Falha elétrica geral no pilot if (pilot_voltage == PILOT_VOLTAGE_1) { if (!(error_bits & EVSE_ERR_PILOT_FAULT_BIT)) { // Verifica se o erro já foi registrado evse_error_set(EVSE_ERR_PILOT_FAULT_BIT); ESP_LOGW(TAG, "Erro: pilot abaixo de 2V (falha)"); } } // Falta de -12V durante PWM (C ou D) if ((pilot_voltage == PILOT_VOLTAGE_6 || pilot_voltage == PILOT_VOLTAGE_3) && !is_n12v) { if (!(error_bits & EVSE_ERR_DIODE_SHORT_BIT)) { // Verifica se o erro já foi registrado evse_error_set(EVSE_ERR_DIODE_SHORT_BIT); ESP_LOGW(TAG, "Erro: ausência de -12V no PWM (sem diodo)"); ESP_LOGW(TAG, "Verificando erro: pilot_voltage = %d, is_n12v = %s", pilot_voltage, is_n12v ? "true" : "false"); } } } void evse_temperature_check(void) { float temp_c = ntc_temp_sensor(); // leitura atual (última medida válida) uint8_t threshold = evse_get_temp_threshold(); // padrão 60°C, configurável // Log informativo com os valores ESP_LOGD(TAG, "Verificando temperatura: atual = %.2f °C, limite = %d °C", temp_c, threshold); // Se a temperatura parecer inválida, aplica erro de sensor if (temp_c < -40.0f || temp_c > 150.0f) { if (!(error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT)) { // Verifica se o erro já foi registrado evse_error_set(EVSE_ERR_TEMPERATURE_FAULT_BIT); ESP_LOGW(TAG, "Sensor NTC falhou ou está desconectado"); } return; } evse_error_clear(EVSE_ERR_TEMPERATURE_FAULT_BIT); // leitura válida if (temp_c >= threshold) { if (!(error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT)) { // Verifica se o erro já foi registrado evse_error_set(EVSE_ERR_TEMPERATURE_HIGH_BIT); ESP_LOGW(TAG, "Temperatura acima do limite: %.2f °C ≥ %d °C", temp_c, threshold); } } else { evse_error_clear(EVSE_ERR_TEMPERATURE_HIGH_BIT); } } uint32_t evse_get_error(void) { return error_bits; } bool evse_is_error_cleared(void) { return error_cleared; } void evse_mark_error_cleared(void) { error_cleared = false; } // Já existentes void evse_error_set(uint32_t bitmask) { error_bits |= bitmask; if (bitmask & EVSE_ERR_AUTO_CLEAR_BITS) { auto_clear_timeout = xTaskGetTickCount() + pdMS_TO_TICKS(60000); // 60s } } void evse_error_clear(uint32_t bitmask) { bool had_error = error_bits != 0; error_bits &= ~bitmask; if (had_error && error_bits == 0) { error_cleared = true; } } void evse_error_tick(void) { if ((error_bits & EVSE_ERR_AUTO_CLEAR_BITS) && xTaskGetTickCount() >= auto_clear_timeout) { evse_error_clear(EVSE_ERR_AUTO_CLEAR_BITS); auto_clear_timeout = 0; } } bool evse_error_is_active(void) { return error_bits != 0; } uint32_t evse_error_get_bits(void) { return error_bits; } void evse_error_reset_flag(void) { error_cleared = false; } bool evse_error_cleared_flag(void) { return error_cleared; } // === Fim de: components/evse/evse_error.c === // === Início de: components/evse/evse_core.c === // evse_core.c - Função principal de controle do EVSE #include "evse_fsm.h" #include "evse_error.h" #include "evse_limits.h" #include "evse_config.h" #include "evse_api.h" #include "evse_pilot.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_log.h" static const char *TAG = "evse_core"; static SemaphoreHandle_t mutex; static evse_state_t last_state = EVSE_STATE_A; static void evse_core_task(void *arg); void evse_init(void) { ESP_LOGI(TAG, "EVSE Init"); mutex = xSemaphoreCreateMutex(); evse_check_defaults(); evse_fsm_reset(); pilot_set_level(true); // Estado inicial do piloto xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 5, NULL); } void evse_process(void) { xSemaphoreTake(mutex, portMAX_DELAY); pilot_voltage_t pilot_voltage; bool is_n12v = false; pilot_measure(&pilot_voltage, &is_n12v); ESP_LOGD(TAG, "Pilot: %d, -12V: %s", pilot_voltage, is_n12v ? "yes" : "no"); if (evse_get_error() == 0 && !evse_is_error_cleared()) { evse_error_check(pilot_voltage, is_n12v); evse_fsm_process( pilot_voltage, evse_state_get_authorized(), evse_config_is_available(), evse_config_is_enabled() ); evse_limits_check(); evse_state_t current = evse_get_state(); if (current != last_state) { ESP_LOGI(TAG, "State changed: %s → %s", evse_state_to_str(last_state), evse_state_to_str(current)); last_state = current; } evse_mark_error_cleared(); } xSemaphoreGive(mutex); } // ================================ // Interface pública // ================================ bool evse_is_enabled(void) { return evse_config_is_enabled(); } void evse_set_enabled(bool value) { ESP_LOGI(TAG, "Set enabled %d", value); evse_config_set_enabled(value); } bool evse_is_available(void) { return evse_config_is_available(); } void evse_set_available(bool value) { ESP_LOGI(TAG, "Set available %d", value); evse_config_set_available(value); } // ================================ // Tarefa principal // ================================ static void evse_core_task(void *arg) { while (true) { evse_process(); vTaskDelay(pdMS_TO_TICKS(100)); } } uint32_t evse_get_total_energy(void) { return 0; // Stub de 1 kWh } uint32_t evse_get_instant_power(void) { return 0; // Stub de 2 kW } // === Fim de: components/evse/evse_core.c ===