. // === 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 "logger.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" #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; for (;;) { // Wait indefinitely until either AP or STA mode is entered mode_bits = xEventGroupWaitBits( wifi_event_group, WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT, pdFALSE, // do not clear bits on exit pdFALSE, // wait for any bit portMAX_DELAY ); if (mode_bits & WIFI_AP_MODE_BIT) { // We're in AP mode: wait for a client to connect within the timeout if (xEventGroupWaitBits( wifi_event_group, WIFI_AP_CONNECTED_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT) ) & WIFI_AP_CONNECTED_BIT) { // Once connected, block until the client disconnects xEventGroupWaitBits( wifi_event_group, WIFI_AP_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY ); } else { // Timeout expired with no client—optionally stop the AP if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT) { // wifi_ap_stop(); } } } else if (mode_bits & WIFI_STA_MODE_BIT) { // We're in STA mode: block until disconnected from the AP xEventGroupWaitBits( wifi_event_group, WIFI_STA_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY ); } // Prevent this task from hogging the CPU when idle //vTaskDelay(pdMS_TO_TICKS(10)); } } // // Button press handler // static void handle_button_press(void) { // If not already in AP mode, start it if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)) { ESP_LOGI(TAG, "Starting Wi-Fi AP mode"); wifi_ap_start(); } } // Task to handle button press/release notifications static void user_input_task_func(void *param) { uint32_t notification; for (;;) { // Wait for notification bits from ISR if (xTaskNotifyWait( 0, // do not clear any bits on entry UINT32_MAX, // clear all bits on exit ¬ification, portMAX_DELAY)) { // Handle button press event if (notification & PRESS_BIT) { press_tick = xTaskGetTickCount(); pressed = true; ESP_LOGI(TAG, "Button Pressed"); handle_button_press(); } // Handle button release event (only if previously pressed) if ((notification & RELEASED_BIT) && pressed) { pressed = false; ESP_LOGI(TAG, "Button Released"); handle_button_press(); } } } } // ISR for button GPIO interrupt (active-low) static void IRAM_ATTR button_isr_handler(void *arg) { BaseType_t higher_task_woken = pdFALSE; TickType_t now = xTaskGetTickCountFromISR(); // Debounce: ignore interrupts occurring too close together if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS)) { return; } last_interrupt_tick = now; // Read GPIO level: 0 = button pressed, 1 = button released int level = gpio_get_level(board_config.button_wifi_gpio); if (level == 0) { // Notify task: button pressed xTaskNotifyFromISR( user_input_task, PRESS_BIT, eSetBits, &higher_task_woken); } else { // Notify task: button released xTaskNotifyFromISR( user_input_task, RELEASED_BIT, eSetBits, &higher_task_woken); } // Yield to higher priority task if unblocked 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(); buzzer_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_init(); meter_manager_start(); evse_link_init(); // wifi_ap_start(); // 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 "adc121s021_dma.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 // ADC121S021 setup #define ADC121_VREF_MV 3300 // AJUSTE conforme Vref do seu hardware! #define ADC121_MAX 4095 // 12 bits static const char *TAG = "evse_pilot"; // Memoização de estado para evitar comandos/logs desnecessários static int last_pilot_level = -1; static uint32_t last_pwm_duty = 0; // Função para converter leitura bruta do ADC para mV static int adc_raw_to_mv(uint16_t raw) { return (raw * ADC121_VREF_MV) / ADC121_MAX; } void pilot_init(void) { // PWM (LEDC) configuração 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)); // Inicializa ADC121S021 externo adc121s021_dma_init(); } void pilot_set_level(bool level) { if (last_pilot_level == level) return; // só muda se necessário last_pilot_level = level; ESP_LOGI(TAG, "Set level %d", level); ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, level ? 1 : 0); last_pwm_duty = 0; // PWM parado } void pilot_set_amps(uint16_t amps) { if (amps < 6 || amps > 80) { ESP_LOGE(TAG, "Invalid ampere value: %d A (valid: 6–80 A)", amps); return; } uint32_t duty_percent; if (amps <= 51) { duty_percent = (amps * 10) / 6; // Duty (%) = Amps / 0.6 } else { duty_percent = (amps * 10) / 25 + 64; // Duty (%) = (Amps / 2.5) + 64 } if (duty_percent > 100) duty_percent = 100; uint32_t duty = (PILOT_PWM_MAX_DUTY * duty_percent) / 100; if (last_pilot_level == 0 && last_pwm_duty == duty) return; last_pilot_level = 0; last_pwm_duty = duty; ESP_LOGI(TAG, "Pilot set: %d A → %d/%d (≈ %d%% duty)", amps, (int)duty, PILOT_PWM_MAX_DUTY, (int)duty_percent); 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; uint16_t adc_sample = 0; // Lê samples usando ADC121S021 externo while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) { adc_sample = 0; if (adc121s021_dma_get_sample(&adc_sample)) { samples[collected++] = adc_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); int high_mv = adc_raw_to_mv(high_raw); int low_mv = adc_raw_to_mv(low_raw); // Aplica thresholds definidos em board_config (em mV) 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_meter.c === #include "evse_meter.h" #include "meter_events.h" #include "esp_event.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include #include static const char *TAG = "evse_meter"; static SemaphoreHandle_t meter_mutex; typedef struct { uint32_t power_watts[EVSE_METER_PHASE_COUNT]; float voltage[EVSE_METER_PHASE_COUNT]; float current[EVSE_METER_PHASE_COUNT]; uint32_t energy_wh; } evse_meter_data_t; static evse_meter_data_t meter_data; static void on_meter_event_dispatcher(void* arg, esp_event_base_t base, int32_t id, void* data) { if (base == METER_EVENT && id == METER_EVENT_DATA_READY && data) { const meter_event_data_t *evt = (const meter_event_data_t *)data; if (strcmp(evt->source, "EVSE") == 0) { evse_meter_on_meter_event(arg, data); } } } void evse_meter_on_meter_event(void* arg, void* event_data) { const meter_event_data_t *evt = (const meter_event_data_t *)event_data; if (!evt) return; xSemaphoreTake(meter_mutex, portMAX_DELAY); for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i) { meter_data.power_watts[i] = evt->watt[i]; meter_data.voltage[i] = evt->vrms[i]; meter_data.current[i] = evt->irms[i]; } meter_data.energy_wh = (uint32_t)(evt->total_energy * 1000.0f); xSemaphoreGive(meter_mutex); ESP_LOGI(TAG, "Meter updated: power[W]={%" PRIu32 ",%" PRIu32 ",%" PRIu32 "}, " "voltage[V]={%.2f,%.2f,%.2f}, " "current[A]={%.2f,%.2f,%.2f}, " "total_energy=%" PRIu32 "Wh", meter_data.power_watts[0], meter_data.power_watts[1], meter_data.power_watts[2], meter_data.voltage[0], meter_data.voltage[1], meter_data.voltage[2], meter_data.current[0], meter_data.current[1], meter_data.current[2], meter_data.energy_wh ); } void evse_meter_init(void) { meter_mutex = xSemaphoreCreateMutex(); ESP_ERROR_CHECK(meter_mutex ? ESP_OK : ESP_FAIL); ESP_ERROR_CHECK(esp_event_handler_register( METER_EVENT, METER_EVENT_DATA_READY, on_meter_event_dispatcher, NULL)); memset(&meter_data, 0, sizeof(meter_data)); ESP_LOGI(TAG, "EVSE Meter listener registered."); } int evse_meter_get_instant_power(void) { xSemaphoreTake(meter_mutex, portMAX_DELAY); int sum = 0; for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i) { sum += meter_data.power_watts[i]; } xSemaphoreGive(meter_mutex); return sum; } int evse_meter_get_total_energy(void) { xSemaphoreTake(meter_mutex, portMAX_DELAY); int val = meter_data.energy_wh; xSemaphoreGive(meter_mutex); return val; } void evse_meter_get_power(int power[EVSE_METER_PHASE_COUNT]) { xSemaphoreTake(meter_mutex, portMAX_DELAY); for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i) { power[i] = meter_data.power_watts[i]; } xSemaphoreGive(meter_mutex); } void evse_meter_get_voltage(float voltage[EVSE_METER_PHASE_COUNT]) { xSemaphoreTake(meter_mutex, portMAX_DELAY); for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i) { voltage[i] = meter_data.voltage[i]; } xSemaphoreGive(meter_mutex); } void evse_meter_get_current(float current[EVSE_METER_PHASE_COUNT]) { xSemaphoreTake(meter_mutex, portMAX_DELAY); for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i) { current[i] = meter_data.current[i]; } xSemaphoreGive(meter_mutex); } // === Fim de: components/evse/evse_meter.c === // === Início de: components/evse/evse_session.c === /* * evse_session.c * Implementation of evse_session module using instantaneous power accumulation */ #include // for PRIu32 #include "evse_session.h" #include "evse_meter.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" static const char *TAG = "evse_session"; // Internal state static TickType_t session_start_tick = 0; static uint32_t watt_seconds = 0; // accumulator of W·s static evse_session_t last_session; static bool last_session_valid = false; void evse_session_init(void) { session_start_tick = 0; watt_seconds = 0; last_session_valid = false; } void evse_session_start(void) { session_start_tick = xTaskGetTickCount(); watt_seconds = 0; ESP_LOGI(TAG, "Session started at tick %u", (unsigned)session_start_tick); } void evse_session_end(void) { if (session_start_tick == 0) { ESP_LOGW(TAG, "evse_session_end called without active session"); return; } TickType_t now = xTaskGetTickCount(); uint32_t duration_s = (now - session_start_tick) / configTICK_RATE_HZ; uint32_t energy_wh = watt_seconds / 3600U; uint32_t avg_power = duration_s > 0 ? watt_seconds / duration_s : 0; last_session.start_tick = session_start_tick; last_session.duration_s = duration_s; last_session.energy_wh = energy_wh; last_session.avg_power_w = avg_power; last_session.is_current = false; last_session_valid = true; session_start_tick = 0; ESP_LOGI(TAG, "Session ended: duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg_power=%" PRIu32 " W", (uint32_t)duration_s, (uint32_t)energy_wh, (uint32_t)avg_power); } void evse_session_tick(void) { if (session_start_tick == 0) return; // Should be called every second (or known interval) uint32_t power_w = evse_meter_get_instant_power(); watt_seconds += power_w; } bool evse_session_get(evse_session_t *out) { if (out == NULL) return false; if (session_start_tick != 0) { TickType_t now = xTaskGetTickCount(); uint32_t duration_s = (now - session_start_tick) / configTICK_RATE_HZ; uint32_t energy_wh = watt_seconds / 3600U; uint32_t avg_power = duration_s > 0 ? watt_seconds / duration_s : 0; out->start_tick = session_start_tick; out->duration_s = duration_s; out->energy_wh = energy_wh; out->avg_power_w = avg_power; out->is_current = true; return true; } if (last_session_valid) { *out = last_session; return true; } return false; } // === Fim de: components/evse/evse_session.c === // === Início de: components/evse/evse_state.c === #include "evse_api.h" #include "evse_state.h" #include "evse_session.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 portMUX_TYPE state_mux = portMUX_INITIALIZER_UNLOCKED; static const char *TAG = "evse_state"; // ========================= // 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: case EVSE_STATE_B2: return EVSE_STATE_EVENT_WAITING; 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 new_state) { bool changed = false; evse_state_t prev_state; bool start_session = false; bool end_session = false; // 1) Detecta transição de estado dentro da região crítica portENTER_CRITICAL(&state_mux); prev_state = current_state; if (new_state != current_state) { // se entrou em charging pela primeira vez if (evse_state_is_charging(new_state) && !evse_state_is_charging(prev_state)) { start_session = true; } // se saiu de charging para qualquer outro else if (!evse_state_is_charging(new_state) && evse_state_is_charging(prev_state)) { end_session = true; } current_state = new_state; changed = true; } portEXIT_CRITICAL(&state_mux); // 2) Executa start/end de sessão FORA da região crítica, evitando logs/alloc dentro dela if (start_session) { evse_session_start(); } if (end_session) { evse_session_end(); } // 3) Se mudou o estado, faz log e dispara evento if (changed) { const char *prev_str = evse_state_to_str(prev_state); const char *curr_str = evse_state_to_str(new_state); ESP_LOGI(TAG, "State changed: %s → %s", prev_str, curr_str); evse_state_event_data_t evt = { .state = map_state_to_event(new_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; } 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; 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 === #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" #include "evse_error.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; } // ... includes e defines como já estão 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(); } // Segurança: relé sempre off e outputs seguros em caso de erro if (evse_get_error() != 0) { if (ac_relay_get_state()) { ac_relay_set_state(false); ESP_LOGW(TAG, "ERRO ativo: relé estava ligado, agora desligado por segurança!"); } ac_relay_set_state(false); // redundância tolerável pilot_set_level(true); // sinal pilot sempre 12V (A) if (board_config.socket_lock && socket_outlet) { socket_lock_set_locked(false); } return; } // Fluxo normal 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, cable_max_current)); 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, cable_max_current)); ac_relay_set_state(true); // Só chega aqui se não há erro! break; } } // FSM principal - centraliza a lógica de erro e de todos os estados void evse_fsm_process( pilot_voltage_t pilot_voltage, bool authorized, bool available, bool enabled ) { // Proteção total: erro força F sempre! if (evse_get_error() != 0) { if (evse_get_state() != EVSE_STATE_F) { ESP_LOGW(TAG, "Erro ativo detectado: forçando estado FAULT (F)"); evse_set_state(EVSE_STATE_F); } update_outputs(EVSE_STATE_F); return; } 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)); 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: // Estado elétrico grave: só reset manual break; case EVSE_STATE_F: // Fault: só sai se disponível e sem erro if (available && evse_get_error() == 0) { evse_set_state(EVSE_STATE_A); } break; } evse_state_t next = evse_get_state(); update_outputs(next); if (next != prev) { ESP_LOGI(TAG, "State changed: %s -> %s", evse_state_to_str(prev), evse_state_to_str(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 - Main EVSE control logic #include "evse_fsm.h" #include "evse_error.h" #include "evse_limits.h" #include "evse_config.h" #include "evse_api.h" #include "evse_session.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); // ================================ // Initialization // ================================ void evse_init(void) { ESP_LOGI(TAG, "EVSE Init"); mutex = xSemaphoreCreateMutex(); // Optional: use static version for deterministic memory evse_check_defaults(); evse_fsm_reset(); pilot_set_level(true); // Enable pilot output xTaskCreate(evse_core_task, "evse_core_task", 4096, NULL, 5, NULL); } // ================================ // Main Processing Logic // ================================ 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"); evse_error_check(pilot_voltage, is_n12v); // Só chama FSM, que decide tudo 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); } // ================================ // Background Task // ================================ static void evse_core_task(void *arg) { while (true) { evse_process(); vTaskDelay(pdMS_TO_TICKS(100)); // 10 Hz cycle } } // === Fim de: components/evse/evse_core.c === // === Início de: components/evse/evse_limits.c === #include // for PRIu32 #include "evse_state.h" #include "evse_api.h" #include "evse_limits.h" #include "evse_meter.h" #include "evse_session.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" // ======================== // External state references // ======================== //extern evse_state_t current_state; // Current EVSE FSM state //extern TickType_t session_start_tick; // Timestamp of charging session start // ======================== // Concurrency protection // ======================== static portMUX_TYPE evse_mux = portMUX_INITIALIZER_UNLOCKED; // ======================== // Runtime state (volatile) // ======================== static bool limit_reached = false; static uint32_t consumption_limit = 0; // Energy limit in Wh static uint32_t charging_time_limit = 0; // Time limit in seconds static uint16_t under_power_limit = 0; // Minimum acceptable power in W // ======================== // Default (persistent) limits // ======================== static uint32_t default_consumption_limit = 0; static uint32_t default_charging_time_limit = 0; static uint16_t default_under_power_limit = 0; // ======================== // Limit status flag // ======================== bool evse_get_limit_reached(void) { bool val; portENTER_CRITICAL(&evse_mux); val = limit_reached; portEXIT_CRITICAL(&evse_mux); return val; } void evse_set_limit_reached(bool v) { portENTER_CRITICAL(&evse_mux); limit_reached = v; portEXIT_CRITICAL(&evse_mux); } // ======================== // Runtime limit accessors // ======================== uint32_t evse_get_consumption_limit(void) { uint32_t val; portENTER_CRITICAL(&evse_mux); val = consumption_limit; portEXIT_CRITICAL(&evse_mux); return val; } void evse_set_consumption_limit(uint32_t value) { portENTER_CRITICAL(&evse_mux); consumption_limit = value; portEXIT_CRITICAL(&evse_mux); } uint32_t evse_get_charging_time_limit(void) { uint32_t val; portENTER_CRITICAL(&evse_mux); val = charging_time_limit; portEXIT_CRITICAL(&evse_mux); return val; } void evse_set_charging_time_limit(uint32_t value) { portENTER_CRITICAL(&evse_mux); charging_time_limit = value; portEXIT_CRITICAL(&evse_mux); } uint16_t evse_get_under_power_limit(void) { uint16_t val; portENTER_CRITICAL(&evse_mux); val = under_power_limit; portEXIT_CRITICAL(&evse_mux); return val; } void evse_set_under_power_limit(uint16_t value) { portENTER_CRITICAL(&evse_mux); under_power_limit = value; portEXIT_CRITICAL(&evse_mux); } // ======================== // Default (persistent) limit accessors // These values can be stored/restored via NVS // ======================== uint32_t evse_get_default_consumption_limit(void) { return default_consumption_limit; } void evse_set_default_consumption_limit(uint32_t value) { default_consumption_limit = value; } uint32_t evse_get_default_charging_time_limit(void) { return default_charging_time_limit; } void evse_set_default_charging_time_limit(uint32_t value) { default_charging_time_limit = value; } uint16_t evse_get_default_under_power_limit(void) { return default_under_power_limit; } void evse_set_default_under_power_limit(uint16_t value) { default_under_power_limit = value; } bool evse_is_limit_reached(void) { return evse_get_limit_reached(); } // ======================== // Limit checking logic // This function must be called periodically while charging. // It will flag the session as "limit reached" when thresholds are violated. // ======================== void evse_limits_check(void) { // Only check during an active charging session if (!evse_state_is_charging(evse_get_state())) { return; } evse_session_t sess; // Retrieve accumulated data for the current session if (!evse_session_get(&sess) || !sess.is_current) { // If there's no active session, abort return; } bool reached = false; // 1) Energy consumption limit (Wh) if (consumption_limit > 0 && sess.energy_wh >= consumption_limit) { ESP_LOGW("EVSE_LIMITS", "Energy limit reached: %" PRIu32 " Wh ≥ %" PRIu32 " Wh", sess.energy_wh, consumption_limit); reached = true; } // 2) Charging time limit (seconds) if (charging_time_limit > 0 && sess.duration_s >= charging_time_limit) { ESP_LOGW("EVSE_LIMITS", "Charging time limit reached: %" PRIu32 " s ≥ %" PRIu32 " s", sess.duration_s, charging_time_limit); reached = true; } // 3) Under-power limit (instantaneous power) uint32_t inst_power = evse_meter_get_instant_power(); if (under_power_limit > 0 && inst_power < under_power_limit) { ESP_LOGW("EVSE_LIMITS", "Under-power limit reached: %" PRIu32 " W < %" PRIu32 " W", (uint32_t)inst_power, (uint32_t)under_power_limit); reached = true; } if (reached) { evse_set_limit_reached(true); } } // === Fim de: components/evse/evse_limits.c === // === Início de: components/evse/evse_api.c === // evse_api.c - Main EVSE control logic #include "evse_fsm.h" #include "evse_error.h" #include "evse_limits.h" #include "evse_config.h" #include "evse_api.h" #include "evse_session.h" #include "evse_pilot.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_log.h" static const char *TAG = "evse_api"; // ================================ // Public Configuration Interface // ================================ 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); } bool evse_get_session(evse_session_t *out) { return evse_session_get(out); } // === Fim de: components/evse/evse_api.c === // === Início de: components/evse/evse_config.c === #include // For PRI macros #include "evse_config.h" #include "board_config.h" #include "evse_limits.h" #include "esp_log.h" #include "nvs.h" #include "esp_timer.h" static const char *TAG = "evse_config"; static nvs_handle_t nvs; // ======================== // Configurable parameters // ======================== static uint8_t max_charging_current = MAX_CHARGING_CURRENT_LIMIT; static uint16_t charging_current; // Persisted (NVS) static uint16_t charging_current_runtime = 0; // Runtime only static bool socket_outlet; static bool rcm; static uint8_t temp_threshold = 60; static bool require_auth; // ======================== // Initialization // ======================== esp_err_t evse_config_init(void) { ESP_LOGD(TAG, "Initializing NVS configuration..."); return nvs_open("evse", NVS_READWRITE, &nvs); } void evse_check_defaults(void) { esp_err_t err; uint8_t u8; uint16_t u16; uint32_t u32; bool needs_commit = false; ESP_LOGD(TAG, "Checking default parameters..."); // Max charging current err = nvs_get_u8(nvs, "max_chrg_curr", &u8); if (err != ESP_OK || u8 < MIN_CHARGING_CURRENT_LIMIT || u8 > MAX_CHARGING_CURRENT_LIMIT) { max_charging_current = MAX_CHARGING_CURRENT_LIMIT; nvs_set_u8(nvs, "max_chrg_curr", max_charging_current); needs_commit = true; ESP_LOGW(TAG, "Invalid or missing max_chrg_curr, resetting to %d", max_charging_current); } else { max_charging_current = u8; } // Charging current (default, persisted) err = nvs_get_u16(nvs, "def_chrg_curr", &u16); if (err != ESP_OK || u16 < (MIN_CHARGING_CURRENT_LIMIT) || u16 > (max_charging_current)) { charging_current = max_charging_current; nvs_set_u16(nvs, "def_chrg_curr", charging_current); needs_commit = true; ESP_LOGW(TAG, "Invalid or missing def_chrg_curr, resetting to %d", charging_current); } else { charging_current = u16; } // Runtime charging current initialized from persisted default charging_current_runtime = max_charging_current; ESP_LOGD(TAG, "Runtime charging current initialized to: %d", charging_current_runtime); // Auth required err = nvs_get_u8(nvs, "require_auth", &u8); require_auth = (err == ESP_OK && u8 <= 1) ? u8 : false; if (err != ESP_OK) { nvs_set_u8(nvs, "require_auth", require_auth); needs_commit = true; } // Socket outlet err = nvs_get_u8(nvs, "socket_outlet", &u8); socket_outlet = (err == ESP_OK && u8) && board_config.proximity; if (err != ESP_OK) { nvs_set_u8(nvs, "socket_outlet", socket_outlet); needs_commit = true; } // RCM err = nvs_get_u8(nvs, "rcm", &u8); rcm = (err == ESP_OK && u8) && board_config.rcm; if (err != ESP_OK) { nvs_set_u8(nvs, "rcm", rcm); needs_commit = true; } // Temp threshold err = nvs_get_u8(nvs, "temp_threshold", &u8); temp_threshold = (err == ESP_OK && u8 >= 40 && u8 <= 80) ? u8 : 60; if (err != ESP_OK) { nvs_set_u8(nvs, "temp_threshold", temp_threshold); needs_commit = true; } // Optional limits if (nvs_get_u32(nvs, "def_cons_lim", &u32) == ESP_OK) evse_set_consumption_limit(u32); if (nvs_get_u32(nvs, "def_ch_time_lim", &u32) == ESP_OK) evse_set_charging_time_limit(u32); if (nvs_get_u16(nvs, "def_un_pwr_lim", &u16) == ESP_OK) evse_set_under_power_limit(u16); // Save to NVS if needed if (needs_commit) { err = nvs_commit(nvs); if (err == ESP_OK) { ESP_LOGD(TAG, "Configuration committed to NVS."); } else { ESP_LOGE(TAG, "Failed to commit configuration to NVS: %s", esp_err_to_name(err)); } } } // ======================== // Charging current getters/setters // ======================== uint8_t evse_get_max_charging_current(void) { return max_charging_current; } esp_err_t evse_set_max_charging_current(uint8_t value) { if (value < MIN_CHARGING_CURRENT_LIMIT || value > MAX_CHARGING_CURRENT_LIMIT) return ESP_ERR_INVALID_ARG; max_charging_current = value; evse_set_runtime_charging_current(value); nvs_set_u8(nvs, "max_chrg_curr", value); return nvs_commit(nvs); } uint16_t evse_get_charging_current(void) { return charging_current; } esp_err_t evse_set_charging_current(uint16_t value) { if (value < (MIN_CHARGING_CURRENT_LIMIT) || value > (max_charging_current)) return ESP_ERR_INVALID_ARG; charging_current = value; nvs_set_u16(nvs, "def_chrg_curr", value); return nvs_commit(nvs); } uint16_t evse_get_default_charging_current(void) { uint16_t value; if (nvs_get_u16(nvs, "def_chrg_curr", &value) == ESP_OK) return value; return charging_current; } esp_err_t evse_set_default_charging_current(uint16_t value) { if (value < (MIN_CHARGING_CURRENT_LIMIT) || value > (max_charging_current)) return ESP_ERR_INVALID_ARG; nvs_set_u16(nvs, "def_chrg_curr", value); return nvs_commit(nvs); } // ======================== // Runtime current (not saved) // ======================== void evse_set_runtime_charging_current(uint16_t value) { if (value > max_charging_current) { value = max_charging_current; } else if (value < MIN_CHARGING_CURRENT_LIMIT) { value = MIN_CHARGING_CURRENT_LIMIT; } charging_current_runtime = value; // --- PUBLICA ALTERAÇÃO DE CONFIG DO EVSE --- evse_config_event_data_t evt = { .charging = evse_state_is_charging(evse_get_state()), .hw_max_current = (float)evse_get_max_charging_current(), .runtime_current = (float)charging_current_runtime, .timestamp_us = esp_timer_get_time() }; esp_event_post(EVSE_EVENTS, EVSE_EVENT_CONFIG_UPDATED, &evt, sizeof(evt), portMAX_DELAY); ESP_LOGI(TAG, "Runtime charging current updated: %d", charging_current_runtime); } uint16_t evse_get_runtime_charging_current(void) { return charging_current_runtime; } // ======================== // Socket outlet // ======================== bool evse_get_socket_outlet(void) { return socket_outlet; } esp_err_t evse_set_socket_outlet(bool value) { if (value && !board_config.proximity) return ESP_ERR_INVALID_ARG; socket_outlet = value; nvs_set_u8(nvs, "socket_outlet", value); return nvs_commit(nvs); } // ======================== // RCM // ======================== bool evse_is_rcm(void) { return rcm; } esp_err_t evse_set_rcm(bool value) { if (value && !board_config.rcm) return ESP_ERR_INVALID_ARG; rcm = value; nvs_set_u8(nvs, "rcm", value); return nvs_commit(nvs); } // ======================== // Temperature // ======================== uint8_t evse_get_temp_threshold(void) { return temp_threshold; } esp_err_t evse_set_temp_threshold(uint8_t value) { if (value < 40 || value > 80) return ESP_ERR_INVALID_ARG; temp_threshold = value; nvs_set_u8(nvs, "temp_threshold", value); return nvs_commit(nvs); } // ======================== // Availability // ======================== static bool is_available = true; bool evse_config_is_available(void) { return is_available; } void evse_config_set_available(bool available) { is_available = available; } // ======================== // Enable/Disable // ======================== static bool is_enabled = true; bool evse_config_is_enabled(void) { return is_enabled; } void evse_config_set_enabled(bool enabled) { is_enabled = enabled; } // === Fim de: components/evse/evse_config.c === // === Início de: components/evse/evse_manager.c === #include "evse_manager.h" #include "evse_state.h" #include "evse_error.h" #include "evse_hardware.h" #include "evse_config.h" #include "evse_api.h" #include "evse_meter.h" #include "evse_session.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" #include "freertos/queue.h" #include "esp_log.h" #include #include "auth_events.h" #include "loadbalancer_events.h" #include "esp_event.h" static const char *TAG = "EVSE_Manager"; static SemaphoreHandle_t evse_mutex; static bool auth_enabled = false; #define EVSE_MANAGER_TICK_PERIOD_MS 1000 // 1 segundo // ===== Task de ciclo principal ===== static void evse_manager_task(void *arg) { while (true) { evse_manager_tick(); vTaskDelay(pdMS_TO_TICKS(EVSE_MANAGER_TICK_PERIOD_MS)); } } // ===== Tratador de eventos de autenticação ===== static void on_auth_event(void* arg, esp_event_base_t base, int32_t id, void* data) { if (base != AUTH_EVENTS || data == NULL) return; switch (id) { case AUTH_EVENT_TAG_PROCESSED: { auth_tag_event_data_t *evt = (auth_tag_event_data_t*)data; ESP_LOGI("EVSE", "Tag: %s | Autorized: %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED"); evse_state_set_authorized(evt->authorized); break; } case AUTH_EVENT_ENABLED_CHANGED: case AUTH_EVENT_INIT: { auth_enabled_event_data_t *evt = (auth_enabled_event_data_t*)data; auth_enabled = evt->enabled; ESP_LOGI("EVSE", "Auth %s (%s)", id == AUTH_EVENT_ENABLED_CHANGED ? "ficou" : "init", evt->enabled ? "ATIVO" : "INATIVO"); if (!auth_enabled) { evse_state_set_authorized(true); ESP_LOGI("EVSE", "Autenticação desativada → autorização forçada."); } else { evse_state_set_authorized(false); ESP_LOGI("EVSE", "Autenticação ativada → aguardando autorização por tag."); } break; } } } // ===== Tratador de eventos de loadbalancer ===== static void on_loadbalancer_event(void* handler_arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (event_id == LOADBALANCER_EVENT_INIT || event_id == LOADBALANCER_EVENT_STATE_CHANGED) { const loadbalancer_state_event_t* evt = (const loadbalancer_state_event_t*) event_data; ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)", evt->enabled ? "ENABLED" : "DISABLED", evt->timestamp_us); // Ações adicionais podem ser adicionadas aqui conforme necessário } else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT) { const loadbalancer_master_limit_event_t* evt = (const loadbalancer_master_limit_event_t*) event_data; ESP_LOGD(TAG, "Novo limite de corrente (master): %u A (ts: %lld)", evt->max_current, evt->timestamp_us); evse_set_runtime_charging_current(evt->max_current); } } // ===== Inicialização ===== void evse_manager_init(void) { evse_mutex = xSemaphoreCreateMutex(); evse_config_init(); evse_error_init(); evse_hardware_init(); evse_state_init(); evse_meter_init(); evse_session_init(); ESP_ERROR_CHECK(esp_event_handler_register(AUTH_EVENTS, ESP_EVENT_ANY_ID, &on_auth_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS, ESP_EVENT_ANY_ID, &on_loadbalancer_event, NULL)); ESP_LOGI(TAG, "EVSE Manager inicializado."); xTaskCreate(evse_manager_task, "evse_manager_task", 4096, NULL, 5, NULL); } // ===== Main Tick ===== void evse_manager_tick(void) { xSemaphoreTake(evse_mutex, portMAX_DELAY); evse_hardware_tick(); evse_error_tick(); evse_state_tick(); evse_temperature_check(); evse_session_tick(); if (auth_enabled) { // If the car is disconnected, revoke authorization if (evse_state_get_authorized() && evse_get_state() == EVSE_STATE_A) { ESP_LOGI(TAG, "Vehicle disconnected → revoking authorization."); evse_state_set_authorized(false); } } else { // If authentication is disabled, ensure authorization is always granted if (!evse_state_get_authorized()) { evse_state_set_authorized(true); ESP_LOGI(TAG, "Authentication disabled → forced authorization."); } } xSemaphoreGive(evse_mutex); } // === Fim de: components/evse/evse_manager.c === // === Início de: components/evse/evse_events.c === #include "evse_events.h" ESP_EVENT_DEFINE_BASE(EVSE_EVENTS); // === Fim de: components/evse/evse_events.c === // === Início de: components/evse/include/evse_pilot.h === #ifndef PILOT_H_ #define PILOT_H_ #ifdef __cplusplus extern "C" { #endif #include #include /** * @brief Níveis categóricos de tensão no sinal CP (Control Pilot) */ typedef enum { PILOT_VOLTAGE_12, ///< Estado A: +12V PILOT_VOLTAGE_9, ///< Estado B: +9V PILOT_VOLTAGE_6, ///< Estado C: +6V PILOT_VOLTAGE_3, ///< Estado D: +3V PILOT_VOLTAGE_1 ///< Estado E/F: abaixo de 3V } pilot_voltage_t; /** * @brief Inicializa o driver do sinal Pilot */ void pilot_init(void); /** * @brief Define o nível do Pilot: +12V ou -12V * * @param level true = +12V, false = -12V */ void pilot_set_level(bool level); /** * @brief Ativa o PWM do Pilot com corrente limitada * * @param amps Corrente em décimos de ampère (ex: 160 = 16A) */ void pilot_set_amps(uint16_t amps); /** * @brief Mede o nível de tensão do Pilot e detecta -12V * * @param up_voltage Valor categórico da tensão positiva * @param down_voltage_n12 true se o nível negativo atingir -12V */ void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12); /** * @brief Retorna o estado lógico atual do Pilot (nível alto = +12V) * * @return true se nível atual for +12V, false se for -12V */ bool pilot_get_state(void); /** * @brief Cache interno opcional dos níveis de tensão reais do Pilot */ typedef struct { uint16_t high_mv; ///< Pico positivo medido (mV) uint16_t low_mv; ///< Pico negativo medido (mV) } pilot_voltage_cache_t; #ifdef __cplusplus } #endif #endif /* PILOT_H_ */ // === Fim de: components/evse/include/evse_pilot.h === // === Início de: components/evse/include/evse_manager.h === #ifndef EVSE_MANAGER_H #define EVSE_MANAGER_H #pragma once #ifdef __cplusplus extern "C" { #endif #include #include #include #include /** * @brief Inicializa os módulos internos do EVSE (hardware, estado, erros, etc.) * e inicia a tarefa de supervisão periódica (tick). */ void evse_manager_init(void); /** * @brief Executa uma iteração do ciclo de controle do EVSE. * * Esta função é chamada automaticamente pela task periódica, * mas pode ser chamada manualmente em testes. */ void evse_manager_tick(void); #ifdef __cplusplus } #endif #endif // EVSE_MANAGER_H // === Fim de: components/evse/include/evse_manager.h === // === Início de: components/evse/include/evse_fsm.h === #ifndef EVSE_FSM_H #define EVSE_FSM_H #include #include #include "evse_api.h" #include "evse_pilot.h" #include "freertos/FreeRTOS.h" #ifdef __cplusplus extern "C" { #endif /** * @brief Reinicia a máquina de estados do EVSE para o estado inicial (A). */ void evse_fsm_reset(void); /** * @brief Processa uma leitura do sinal de piloto e atualiza a máquina de estados do EVSE. * * Esta função deve ser chamada periodicamente pelo núcleo de controle para * avaliar mudanças no estado do conector, disponibilidade do carregador e * autorização do usuário. * * @param pilot_voltage Leitura atual da tensão do sinal piloto. * @param authorized Indica se o carregamento foi autorizado. * @param available Indica se o carregador está disponível (ex: sem falhas). * @param enabled Indica se o carregador está habilitado via software. */ void evse_fsm_process(pilot_voltage_t pilot_voltage, bool authorized, bool available, bool enabled); #ifdef __cplusplus } #endif #endif // EVSE_FSM_H // === Fim de: components/evse/include/evse_fsm.h === // === Início de: components/evse/include/evse_hardware.h === #ifndef EVSE_HARDWARE_H #define EVSE_HARDWARE_H #ifdef __cplusplus extern "C" { #endif #include #include /** * @brief Inicializa todos os periféricos de hardware do EVSE (pilot, relé, trava, etc.) */ void evse_hardware_init(void); /** * @brief Executa atualizações periódicas no hardware (tick) */ void evse_hardware_tick(void); /** * @brief Verifica se o sinal piloto está em nível alto (12V) */ bool evse_hardware_is_pilot_high(void); /** * @brief Verifica se o veículo está fisicamente conectado via Proximity */ bool evse_hardware_is_vehicle_connected(void); /** * @brief Verifica se há consumo de energia (corrente detectada) */ bool evse_hardware_is_energy_detected(void); /** * @brief Liga o relé de fornecimento de energia */ void evse_hardware_relay_on(void); /** * @brief Desliga o relé de fornecimento de energia */ void evse_hardware_relay_off(void); /** * @brief Consulta o estado atual do relé * @return true se ligado, false se desligado */ bool evse_hardware_relay_status(void); /** * @brief Aciona a trava física do conector */ void evse_hardware_lock(void); /** * @brief Libera a trava física do conector */ void evse_hardware_unlock(void); /** * @brief Verifica se o conector está travado */ bool evse_hardware_is_locked(void); #ifdef __cplusplus } #endif #endif // EVSE_HARDWARE_H // === Fim de: components/evse/include/evse_hardware.h === // === Início de: components/evse/include/evse_config.h === #ifndef EVSE_CONFIG_H #define EVSE_CONFIG_H #include #include #include "esp_err.h" #include "freertos/FreeRTOS.h" #include "evse_events.h" #ifdef __cplusplus extern "C" { #endif // ======================== // Limites Globais (Defines) // ======================== // Corrente máxima de carregamento (configurável pelo usuário) #define MIN_CHARGING_CURRENT_LIMIT 6 // A #define MAX_CHARGING_CURRENT_LIMIT 32 // A // Corrente via cabo (proximity) — se configurável #define MIN_CABLE_CURRENT_LIMIT 6 // A #define MAX_CABLE_CURRENT_LIMIT 63 // A // ======================== // Funções de Configuração // ======================== // Inicialização esp_err_t evse_config_init(void); void evse_check_defaults(void); // Corrente de carregamento uint8_t evse_get_max_charging_current(void); esp_err_t evse_set_max_charging_current(uint8_t value); uint16_t evse_get_charging_current(void); esp_err_t evse_set_charging_current(uint16_t value); uint16_t evse_get_default_charging_current(void); esp_err_t evse_set_default_charging_current(uint16_t value); // Configuração de socket outlet bool evse_get_socket_outlet(void); esp_err_t evse_set_socket_outlet(bool socket_outlet); void evse_set_runtime_charging_current(uint16_t value); uint16_t evse_get_runtime_charging_current(void); // RCM bool evse_is_rcm(void); esp_err_t evse_set_rcm(bool rcm); // Temperatura uint8_t evse_get_temp_threshold(void); esp_err_t evse_set_temp_threshold(uint8_t threshold); // Disponibilidade bool evse_config_is_available(void); void evse_config_set_available(bool available); // Ativação/desativação do EVSE bool evse_config_is_enabled(void); void evse_config_set_enabled(bool enabled); #ifdef __cplusplus } #endif #endif // EVSE_CONFIG_H // === Fim de: components/evse/include/evse_config.h === // === Início de: components/evse/include/evse_state.h === #ifndef EVSE_STATE_H #define EVSE_STATE_H #include #include "freertos/FreeRTOS.h" #include "evse_events.h" #ifdef __cplusplus extern "C" { #endif // ============================ // EVSE Pilot Signal States // ============================ typedef enum { EVSE_STATE_A, // EV Not Connected (12V) EVSE_STATE_B1, // EV Connected (9V, Not Authorized) EVSE_STATE_B2, // EV Connected (9V, Authorized and Ready) EVSE_STATE_C1, // Charging Requested (6V, Relay Off) EVSE_STATE_C2, // Charging Active (6V, Relay On) EVSE_STATE_D1, // Ventilation Required (3V, Relay Off) EVSE_STATE_D2, // Ventilation Active (3V, Relay On) EVSE_STATE_E, // Error: Pilot Short to Ground (0V) EVSE_STATE_F // Fault: No Pilot or EVSE Unavailable } evse_state_t; // ============================ // Initialization // ============================ /** * @brief Initializes the EVSE state machine and default state. */ void evse_state_init(void); /** * @brief Periodic tick for state handling (optional hook). */ void evse_state_tick(void); // ============================ // State Access & Control // ============================ /** * @brief Returns the current EVSE state. */ evse_state_t evse_get_state(void); /** * @brief Sets the current EVSE state and emits a change event if needed. */ void evse_set_state(evse_state_t state); /** * @brief Converts the state enum into a human-readable string. */ const char* evse_state_to_str(evse_state_t state); // ============================ // State Evaluation Helpers // ============================ /** * @brief True if EV is in an active session (B2, C1, C2). */ bool evse_state_is_session(evse_state_t state); /** * @brief True if EV is actively charging (C1, C2). */ bool evse_state_is_charging(evse_state_t state); /** * @brief True if EV is physically plugged in (B1 and beyond). */ bool evse_state_is_plugged(evse_state_t state); // ============================ // Authorization Control // ============================ /** * @brief Sets whether the EV is authorized to charge. */ void evse_state_set_authorized(bool authorized); /** * @brief Gets whether the EV is currently authorized. */ bool evse_state_get_authorized(void); #ifdef __cplusplus } #endif #endif // EVSE_STATE_H // === Fim de: components/evse/include/evse_state.h === // === Início de: components/evse/include/evse_error.h === #ifndef EVSE_ERROR_H #define EVSE_ERROR_H #include #include #include "evse_pilot.h" #define EVSE_ERR_AUTO_CLEAR_BITS ( \ EVSE_ERR_DIODE_SHORT_BIT | \ EVSE_ERR_TEMPERATURE_HIGH_BIT | \ EVSE_ERR_RCM_TRIGGERED_BIT ) // Error bits #define EVSE_ERR_DIODE_SHORT_BIT (1 << 0) #define EVSE_ERR_LOCK_FAULT_BIT (1 << 1) #define EVSE_ERR_UNLOCK_FAULT_BIT (1 << 2) #define EVSE_ERR_RCM_SELFTEST_FAULT_BIT (1 << 3) #define EVSE_ERR_RCM_TRIGGERED_BIT (1 << 4) #define EVSE_ERR_TEMPERATURE_HIGH_BIT (1 << 5) #define EVSE_ERR_PILOT_FAULT_BIT (1 << 6) #define EVSE_ERR_TEMPERATURE_FAULT_BIT (1 << 7) // Inicialização do módulo de erros void evse_error_init(void); // Verificações e monitoramento void evse_error_check(pilot_voltage_t pilot_voltage, bool is_n12v); void evse_temperature_check(void); void evse_error_tick(void); // Leitura e controle de erros uint32_t evse_get_error(void); bool evse_is_error_cleared(void); void evse_mark_error_cleared(void); void evse_error_set(uint32_t bitmask); void evse_error_clear(uint32_t bitmask); bool evse_error_is_active(void); uint32_t evse_error_get_bits(void); void evse_error_reset_flag(void); bool evse_error_cleared_flag(void); #endif // EVSE_ERROR_H // === Fim de: components/evse/include/evse_error.h === // === Início de: components/evse/include/evse_session.h === /* * evse_session.h * Module to track and retrieve charging session data (current or last completed), * accumulating energy via periodic tick of instantaneous power. */ #ifndef EVSE_SESSION_H #define EVSE_SESSION_H #include #include #include "freertos/FreeRTOS.h" /** * @brief Charging session statistics */ typedef struct { TickType_t start_tick; ///< tick when session began uint32_t duration_s; ///< total duration in seconds uint32_t energy_wh; ///< total energy consumed in Wh uint32_t avg_power_w; ///< average power in W bool is_current; ///< true if session still in progress } evse_session_t; /** * @brief Initialize the session module */ void evse_session_init(void); /** * @brief Mark the beginning of a charging session */ void evse_session_start(void); /** * @brief Mark the end of the charging session and store it as "last session" */ void evse_session_end(void); /** * @brief Periodic tick: must be called (e.g., each 1s) to accumulate energy from instant power */ void evse_session_tick(void); /** * @brief Retrieve statistics of either the current ongoing session (if any) or * the last completed session. * @param out pointer to evse_session_t to be filled * @return true if there is a current or last session available, false otherwise */ bool evse_session_get(evse_session_t *out); #endif // EVSE_SESSION_H // === Fim de: components/evse/include/evse_session.h === // === Início de: components/evse/include/evse_meter.h === #ifndef EVSE_METER_H #define EVSE_METER_H #include #ifdef __cplusplus extern "C" { #endif #define EVSE_METER_PHASE_COUNT 3 /// Inicializa o módulo EVSE Meter e registra os tratadores de eventos void evse_meter_init(void); /// Retorna a potência instantânea (soma das 3 fases, em watts) int evse_meter_get_instant_power(void); /// Retorna a energia total acumulada (em Wh) int evse_meter_get_total_energy(void); /// Retorna as potências instantâneas nas fases L1, L2 e L3 (em watts) void evse_meter_get_power(int power[EVSE_METER_PHASE_COUNT]); /// Retorna as tensões medidas nas fases L1, L2 e L3 (em volts) void evse_meter_get_voltage(float voltage[EVSE_METER_PHASE_COUNT]); /// Retorna as correntes medidas nas fases L1, L2 e L3 (em amperes) void evse_meter_get_current(float current[EVSE_METER_PHASE_COUNT]); /// Handler interno para eventos do medidor (não chamar externamente) void evse_meter_on_meter_event(void* arg, void* event_data); #ifdef __cplusplus } #endif #endif // EVSE_METER_H // === Fim de: components/evse/include/evse_meter.h === // === Início de: components/evse/include/evse_core.h === #ifndef EVSE_CORE_H #define EVSE_CORE_H #ifdef __cplusplus extern "C" { #endif /** * @brief Initializes the EVSE system and starts core task loop. */ void evse_init(void); #ifdef __cplusplus } #endif #endif // EVSE_CORE_H // === Fim de: components/evse/include/evse_core.h === // === Início de: components/evse/include/evse_limits.h === #ifndef EVSE_LIMITS_H #define EVSE_LIMITS_H #include #include #include "evse_state.h" #ifdef __cplusplus extern "C" { #endif // ============================ // Limit Status & Evaluation // ============================ /** * @brief Sets the internal 'limit reached' flag. * Called internally when a limit condition is triggered. */ void evse_set_limit_reached(bool value); /** * @brief Returns true if any runtime charging limit has been reached. */ bool evse_get_limit_reached(void); /** * @brief Checks if any session limit has been exceeded (energy, time or power). * Should be called periodically during charging. */ void evse_limits_check(void); // ============================ // Runtime Limit Configuration // ============================ /** * @brief Get/set energy consumption limit (in Wh). */ uint32_t evse_get_consumption_limit(void); void evse_set_consumption_limit(uint32_t value); /** * @brief Get/set maximum charging time (in seconds). */ uint32_t evse_get_charging_time_limit(void); void evse_set_charging_time_limit(uint32_t value); /** * @brief Get/set minimum acceptable power level (in Watts). * If the power remains below this for a long time, the session may be interrupted. */ uint16_t evse_get_under_power_limit(void); void evse_set_under_power_limit(uint16_t value); // ============================ // Default (Persistent) Limits // ============================ /** * @brief Default values used after system boot or reset. * These can be restored from NVS or fallback values. */ uint32_t evse_get_default_consumption_limit(void); void evse_set_default_consumption_limit(uint32_t value); uint32_t evse_get_default_charging_time_limit(void); void evse_set_default_charging_time_limit(uint32_t value); uint16_t evse_get_default_under_power_limit(void); void evse_set_default_under_power_limit(uint16_t value); #ifdef __cplusplus } #endif #endif // EVSE_LIMITS_H // === Fim de: components/evse/include/evse_limits.h === // === Início de: components/evse/include/evse_events.h === #ifndef EVSE_EVENTS_H #define EVSE_EVENTS_H #pragma once #include "esp_event.h" ESP_EVENT_DECLARE_BASE(EVSE_EVENTS); typedef enum { EVSE_EVENT_INIT, EVSE_EVENT_STATE_CHANGED, EVSE_EVENT_CONFIG_UPDATED, } evse_event_id_t; typedef enum { EVSE_STATE_EVENT_IDLE, EVSE_STATE_EVENT_WAITING, EVSE_STATE_EVENT_CHARGING, EVSE_STATE_EVENT_FAULT } evse_state_event_t; typedef struct { evse_state_event_t state; } evse_state_event_data_t; typedef struct { bool charging; // Estado de carregamento float hw_max_current; // Corrente máxima suportada pelo hardware float runtime_current; // Corrente de carregamento em uso int64_t timestamp_us; // Momento da atualização } evse_config_event_data_t; #endif // EVSE_EVENTS_H // === Fim de: components/evse/include/evse_events.h === // === Início de: components/evse/include/evse_api.h === #ifndef EVSE_API_H #define EVSE_API_H #include #include #include "evse_state.h" #include "freertos/FreeRTOS.h" #include "evse_session.h" #ifdef __cplusplus extern "C" { #endif // =============================== // Core EVSE State // =============================== /** * @brief Get current EVSE state (e.g., A, B1, C2). */ evse_state_t evse_get_state(void); /** * @brief Set the EVSE state (e.g., called by FSM or hardware layer). */ void evse_set_state(evse_state_t state); // =============================== // Charging Session Info // =============================== /** * @brief Retrieve statistics of either the current ongoing session (if any) * or the last completed session. * @param out pointer to evse_session_t to be filled * @return true if there is a current or last session available */ bool evse_get_session(evse_session_t *out); // =============================== // Charging Session Info // =============================== /** * @brief Returns true if the EV is charging (C1 or C2). */ bool evse_state_is_charging(evse_state_t state); /** * @brief Returns true if the EV is connected (plugged). */ bool evse_state_is_plugged(evse_state_t state); /** * @brief Returns true if a charging session is active (B2, C1, C2). */ bool evse_state_is_session(evse_state_t state); // =============================== // Authorization // =============================== /** * @brief Set whether the vehicle is authorized to charge. */ void evse_state_set_authorized(bool authorized); /** * @brief Get current authorization status. */ bool evse_state_get_authorized(void); // =============================== // Limit Status // =============================== /** * @brief Returns true if any runtime charging limit has been reached. */ bool evse_is_limit_reached(void); #ifdef __cplusplus } #endif #endif // EVSE_API_H // === Fim de: components/evse/include/evse_api.h === // === Início de: components/loadbalancer/src/loadbalancer_events.c === #include "loadbalancer_events.h" // Define a base de eventos para o loadbalancer ESP_EVENT_DEFINE_BASE(LOADBALANCER_EVENTS); // === Fim de: components/loadbalancer/src/loadbalancer_events.c === // === Início de: components/loadbalancer/src/loadbalancer.c === #include "loadbalancer.h" #include "loadbalancer_events.h" #include "esp_event.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "input_filter.h" #include "nvs_flash.h" #include "nvs.h" #include #include "meter_events.h" #include "evse_events.h" #include "math.h" #ifndef MIN #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif static const char *TAG = "loadbalancer"; // Limites configuráveis #define MIN_CHARGING_CURRENT_LIMIT 6 // A #define MAX_CHARGING_CURRENT_LIMIT 32 // A #define MIN_GRID_CURRENT_LIMIT 6 // A #define MAX_GRID_CURRENT_LIMIT 100 // A // Parâmetros static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT; static bool loadbalancer_enabled = false; static float grid_current = 0.0f; static float evse_current = 0.0f; static input_filter_t grid_filter; static input_filter_t evse_filter; #define MAX_SLAVES 255 #define CONNECTOR_COUNT (MAX_SLAVES + 1) // Estrutura unificada para master e slaves typedef struct { uint8_t id; // 0xFF = master, 0..MAX_SLAVES-1 = slave bool is_master; bool charging; float hw_max_current; float runtime_current; int64_t timestamp; // microssegundos bool online; float assigned; } evse_connector_t; static evse_connector_t connectors[CONNECTOR_COUNT]; const int64_t METRICS_TIMEOUT_US = 60 * 1000000; // 60 segundos // Helper: inicializa array de conectores static void init_connectors(void) { // master em índice 0 connectors[0] = (evse_connector_t){ .id = 0xFF, .is_master = true, .charging = false, .hw_max_current = MAX_CHARGING_CURRENT_LIMIT, .runtime_current = 0, .timestamp = 0, .online = false, .assigned = 0.0f }; // slaves em 1..CONNECTOR_COUNT-1 for (int i = 1; i < CONNECTOR_COUNT; i++) { connectors[i] = (evse_connector_t){ .id = (uint8_t)(i - 1), .is_master = false, .charging = false, .hw_max_current = 0.0f, .runtime_current = 0.0f, .timestamp = 0, .online = false, .assigned = 0.0f }; } } // --- Helpers --- static void input_filter_reset(input_filter_t *filter) { filter->value = 0.0f; } // Callback de status de slave static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id, void *data) { const loadbalancer_slave_status_event_t *status = (const loadbalancer_slave_status_event_t *)data; if (status->slave_id >= MAX_SLAVES) { ESP_LOGW(TAG, "Invalid slave_id %d", status->slave_id); return; } int idx = status->slave_id + 1; // slaves começam no índice 1 connectors[idx].charging = status->charging; connectors[idx].hw_max_current = status->hw_max_current; connectors[idx].runtime_current = status->runtime_current; connectors[idx].timestamp = esp_timer_get_time(); connectors[idx].online = true; ESP_LOGI(TAG, "Slave %d status: charging=%d hw_max_current=%.1fA runtime_current=%.2fA", status->slave_id, status->charging, status->hw_max_current, status->runtime_current); } static void on_evse_config_event(void* handler_arg, esp_event_base_t base, int32_t id, void* event_data) { const evse_config_event_data_t *evt = (const evse_config_event_data_t*) event_data; int idx = 0; // MASTER INDICE 0 connectors[idx].charging = evt->charging; connectors[idx].hw_max_current = evt->hw_max_current; connectors[idx].runtime_current = evt->runtime_current; connectors[idx].timestamp = esp_timer_get_time(); connectors[idx].online = true; ESP_LOGI(TAG, "EVSE config updated: charging=%d hw_max_current=%.1f runtime_current=%.1f", evt->charging, evt->hw_max_current, evt->runtime_current); } // --- Handlers de eventos externos --- static void loadbalancer_meter_event_handler(void *handler_arg, esp_event_base_t base, int32_t id, void *event_data) { if (id != METER_EVENT_DATA_READY || event_data == NULL) return; const meter_event_data_t *evt = (const meter_event_data_t *)event_data; float max_irms = evt->irms[0]; for (int i = 1; i < 3; ++i) if (evt->irms[i] > max_irms) max_irms = evt->irms[i]; if (evt->source && strcmp(evt->source, "GRID") == 0) { grid_current = input_filter_update(&grid_filter, max_irms); ESP_LOGI(TAG, "GRID IRMS (filtered): %.2f A", grid_current); } else if (evt->source && strcmp(evt->source, "EVSE") == 0) { evse_current = input_filter_update(&evse_filter, max_irms); ESP_LOGI(TAG, "EVSE IRMS (filtered): %.2f A", evse_current); } else { ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source); } } static void loadbalancer_evse_event_handler(void *handler_arg, esp_event_base_t base, int32_t id, void *event_data) { const evse_state_event_data_t *evt = (const evse_state_event_data_t *)event_data; switch (evt->state) { case EVSE_STATE_EVENT_IDLE: ESP_LOGI(TAG, "EVSE is IDLE - vehicle disconnected"); connectors[0].charging = false; connectors[0].online = false; break; case EVSE_STATE_EVENT_WAITING: ESP_LOGI(TAG, "EVSE is WAITING - connected but not charging"); connectors[0].charging = false; connectors[0].online = false; break; case EVSE_STATE_EVENT_CHARGING: ESP_LOGI(TAG, "EVSE is CHARGING - resetting filters"); grid_current = 0.0f; evse_current = 0.0f; input_filter_reset(&grid_filter); input_filter_reset(&evse_filter); connectors[0].charging = true; connectors[0].online = true; connectors[0].timestamp = esp_timer_get_time(); break; case EVSE_STATE_EVENT_FAULT: ESP_LOGW(TAG, "EVSE is in FAULT state - consider disabling load balancing"); break; default: ESP_LOGW(TAG, "Unknown EVSE state: %d", evt->state); break; } } // --- Config persistência --- static esp_err_t loadbalancer_load_config() { nvs_handle_t handle; esp_err_t err = nvs_open("loadbalancing", NVS_READWRITE, &handle); if (err != ESP_OK) return err; bool needs_commit = false; uint8_t temp_u8; err = nvs_get_u8(handle, "max_grid_curr", &temp_u8); if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT) max_grid_current = temp_u8; else { max_grid_current = MAX_GRID_CURRENT_LIMIT; nvs_set_u8(handle, "max_grid_curr", max_grid_current); needs_commit = true; } err = nvs_get_u8(handle, "enabled", &temp_u8); if (err == ESP_OK && temp_u8 <= 1) loadbalancer_enabled = (temp_u8 != 0); else { loadbalancer_enabled = false; nvs_set_u8(handle, "enabled", 0); needs_commit = true; } if (needs_commit) nvs_commit(handle); nvs_close(handle); return ESP_OK; } // --- API --- void loadbalancer_set_enabled(bool enabled) { nvs_handle_t handle; if (nvs_open("loadbalancing", NVS_READWRITE, &handle) == ESP_OK) { nvs_set_u8(handle, "enabled", enabled ? 1 : 0); nvs_commit(handle); nvs_close(handle); } loadbalancer_enabled = enabled; loadbalancer_state_event_t evt = { .enabled = enabled, .timestamp_us = esp_timer_get_time() }; esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_STATE_CHANGED, &evt, sizeof(evt), portMAX_DELAY); } esp_err_t load_balancing_set_max_grid_current(uint8_t value) { if (value < MIN_GRID_CURRENT_LIMIT || value > MAX_GRID_CURRENT_LIMIT) return ESP_ERR_INVALID_ARG; nvs_handle_t handle; if (nvs_open("loadbalancing", NVS_READWRITE, &handle) != ESP_OK) return ESP_FAIL; nvs_set_u8(handle, "max_grid_curr", value); nvs_commit(handle); nvs_close(handle); max_grid_current = value; return ESP_OK; } uint8_t load_balancing_get_max_grid_current(void) { return max_grid_current; } bool loadbalancer_is_enabled(void) { return loadbalancer_enabled; } // --- Task principal --- void loadbalancer_task(void *param) { while (true) { if (!loadbalancer_is_enabled()) { vTaskDelay(pdMS_TO_TICKS(5000)); continue; } int64_t now = esp_timer_get_time(); // --- Atualiza online e conta ativos --- int idxs[CONNECTOR_COUNT]; int active_cnt = 0; for (int i = 0; i < CONNECTOR_COUNT; i++) { bool valid = connectors[i].online && connectors[i].charging; if (!connectors[i].is_master) { valid &= (now - connectors[i].timestamp) < METRICS_TIMEOUT_US; } if (valid) { idxs[active_cnt++] = i; ESP_LOGI(TAG, "Connector[%d] ONLINE & CHARGING (is_master=%d, hw_max_current=%.1f, timestamp_diff=%lld us)", i, connectors[i].is_master, connectors[i].hw_max_current, (long long)(now - connectors[i].timestamp)); } else if (!connectors[i].is_master) { if (connectors[i].online) { ESP_LOGW(TAG, "Connector[%d] marked OFFLINE (charging=%d, timestamp_diff=%lld us)", i, connectors[i].charging, (long long)(now - connectors[i].timestamp)); } connectors[i].online = false; } } ESP_LOGI(TAG, "Active connectors: %d", active_cnt); // --- Corrente disponível --- float available = max_grid_current - grid_current; if (available < MIN_CHARGING_CURRENT_LIMIT) { available = MIN_CHARGING_CURRENT_LIMIT; } else if (available > max_grid_current) { available = max_grid_current; } ESP_LOGI(TAG, "LB Calc: available=%.1fA, active_connectors=%d", available, active_cnt); // --- Water-filling --- // Ordena índices por hw_max_current crescente for (int a = 0; a < active_cnt - 1; a++) { for (int b = 0; b < active_cnt - 1 - a; b++) { if (connectors[idxs[b]].hw_max_current > connectors[idxs[b+1]].hw_max_current) { int tmp = idxs[b]; idxs[b] = idxs[b+1]; idxs[b+1] = tmp; } } } // Distribuição de corrente float remaining = available; int remaining_cnt = active_cnt; for (int k = 0; k < active_cnt; k++) { int i = idxs[k]; float share = remaining / remaining_cnt; if (share >= connectors[i].hw_max_current) { connectors[i].assigned = connectors[i].hw_max_current; remaining -= connectors[i].assigned; remaining_cnt--; } else { for (int m = k; m < active_cnt; m++) { connectors[idxs[m]].assigned = share; } break; } } // Assegura o piso mínimo for (int k = 0; k < active_cnt; k++) { int i = idxs[k]; if (connectors[i].assigned < MIN_CHARGING_CURRENT_LIMIT) { connectors[i].assigned = MIN_CHARGING_CURRENT_LIMIT; } } // --- Publica limites --- for (int k = 0; k < active_cnt; k++) { int i = idxs[k]; uint16_t max_cur = (uint16_t)MIN(connectors[i].assigned, MAX_CHARGING_CURRENT_LIMIT); // só envia evento se a corrente aplicada for diferente do limite calculado uint16_t current_rounded = (uint16_t)roundf(connectors[i].runtime_current); if (current_rounded == max_cur) { continue; // sem alteração -> não faz nada } if (connectors[i].is_master) { loadbalancer_master_limit_event_t master_evt = { .slave_id = connectors[i].id, .max_current = max_cur, .timestamp_us = now }; esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT, &master_evt, sizeof(master_evt), portMAX_DELAY); ESP_LOGI(TAG, "Master limit changed -> %.1f A (runtime=%.1f A)", (float)max_cur, connectors[i].runtime_current); } else { loadbalancer_slave_limit_event_t slave_evt = { .slave_id = connectors[i].id, .max_current = max_cur, .timestamp_us = now }; esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT, &slave_evt, sizeof(slave_evt), portMAX_DELAY); ESP_LOGI(TAG, "Slave %d limit changed -> %.1f A (runtime=%.1f A)", connectors[i].id, (float)max_cur, connectors[i].runtime_current); } } vTaskDelay(pdMS_TO_TICKS(5000)); } } // --- Init --- void loadbalancer_init(void) { if (loadbalancer_load_config() != ESP_OK) ESP_LOGW(TAG, "Failed to load/init config. Using defaults."); init_connectors(); input_filter_init(&grid_filter, 0.3f); input_filter_init(&evse_filter, 0.3f); if (xTaskCreate(loadbalancer_task, "loadbalancer", 4096, NULL, 4, NULL) != pdPASS) ESP_LOGE(TAG, "Failed to create loadbalancer task"); loadbalancer_state_event_t evt = {.enabled = loadbalancer_enabled, .timestamp_us = esp_timer_get_time()}; esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY); ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, METER_EVENT_DATA_READY, &loadbalancer_meter_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &loadbalancer_evse_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS,EVSE_EVENT_CONFIG_UPDATED, &on_evse_config_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS,LOADBALANCER_EVENT_SLAVE_STATUS, &on_slave_status, NULL)); } // === Fim de: components/loadbalancer/src/loadbalancer.c === // === Início de: components/loadbalancer/src/input_filter.c === #include "input_filter.h" void input_filter_init(input_filter_t *filter, float alpha) { if (filter) { filter->alpha = alpha; filter->value = 0.0f; filter->initialized = 0; } } float input_filter_update(input_filter_t *filter, float input) { if (!filter) return input; if (!filter->initialized) { filter->value = input; filter->initialized = 1; } else { filter->value = filter->alpha * input + (1.0f - filter->alpha) * filter->value; } return filter->value; } // === Fim de: components/loadbalancer/src/input_filter.c === // === Início de: components/loadbalancer/include/loadbalancer_events.h === #pragma once #include "esp_event.h" #include #include #include "esp_timer.h" ESP_EVENT_DECLARE_BASE(LOADBALANCER_EVENTS); typedef enum { LOADBALANCER_EVENT_INIT, LOADBALANCER_EVENT_STATE_CHANGED, LOADBALANCER_EVENT_GLOBAL_CURRENT_LIMIT, LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT, LOADBALANCER_EVENT_SLAVE_STATUS } loadbalancer_event_id_t; typedef struct { bool enabled; int64_t timestamp_us; } loadbalancer_state_event_t; // (opcional) typedef struct { float limit; int64_t timestamp_us; } loadbalancer_global_limit_event_t; typedef struct { uint8_t slave_id; uint16_t max_current; int64_t timestamp_us; } loadbalancer_master_limit_event_t; typedef struct { uint8_t slave_id; uint16_t max_current; int64_t timestamp_us; } loadbalancer_slave_limit_event_t; typedef struct { uint8_t slave_id; // ID do slave que reportou bool charging; // Status de carregamento float hw_max_current; // Limite máximo de corrente do hardware informado float runtime_current; // Corrente atual de carregamento (A) int64_t timestamp_us; // Momento em que o status foi coletado } loadbalancer_slave_status_event_t; // === Fim de: components/loadbalancer/include/loadbalancer_events.h === // === Início de: components/loadbalancer/include/loadbalancer.h === #ifndef LOADBALANCER_H_ #define LOADBALANCER_H_ #ifdef __cplusplus extern "C" { #endif #include #include #include "esp_err.h" /** * @brief Inicializa o módulo de load balancer */ void loadbalancer_init(void); /** * @brief Task contínua do algoritmo de balanceamento */ void loadbalancer_task(void *param); /** * @brief Ativa ou desativa o load balancing */ void loadbalancer_set_enabled(bool value); /** * @brief Verifica se o load balancing está ativo */ bool loadbalancer_is_enabled(void); /** * @brief Define a corrente máxima do grid */ esp_err_t load_balancing_set_max_grid_current(uint8_t max_grid_current); /** * @brief Obtém a corrente máxima do grid */ uint8_t load_balancing_get_max_grid_current(void); #ifdef __cplusplus } #endif #endif /* LOADBALANCER_H_ */ // === Fim de: components/loadbalancer/include/loadbalancer.h === // === Início de: components/loadbalancer/include/input_filter.h === #pragma once #ifdef __cplusplus extern "C" { #endif typedef struct { float alpha; ///< Fator de suavização (0.0 a 1.0) float value; ///< Último valor filtrado int initialized; ///< Flag de inicialização } input_filter_t; /** * @brief Inicializa o filtro com o fator alpha desejado. * @param filter Ponteiro para a estrutura do filtro * @param alpha Valor entre 0.0 (mais lento) e 1.0 (sem filtro) */ void input_filter_init(input_filter_t *filter, float alpha); /** * @brief Atualiza o valor filtrado com uma nova entrada. * @param filter Ponteiro para o filtro * @param input Valor bruto * @return Valor suavizado */ float input_filter_update(input_filter_t *filter, float input); #ifdef __cplusplus } #endif // === Fim de: components/loadbalancer/include/input_filter.h ===