Files
chargeflow/projeto_parte1.c
2025-08-05 16:55:11 +01:00

3367 lines
94 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.
// === Início de: main/main.c ===
#include <string.h>
#include <stdbool.h>
#include <inttypes.h>
#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
&notification,
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 <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#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: 680 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 <string.h>
#include <inttypes.h>
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 <inttypes.h> // 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 <inttypes.h> // 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 <inttypes.h> // 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 <string.h>
#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 <stdbool.h>
#include <stdint.h>
/**
* @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 <stdbool.h>
#include <stdint.h>
#include <freertos/FreeRTOS.h>
#include <freertos/queue.h>
/**
* @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 <stdint.h>
#include <stdbool.h>
#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 <stdbool.h>
#include <stdint.h>
/**
* @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 <stdbool.h>
#include <stdint.h>
#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 <stdbool.h>
#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 <stdint.h>
#include <stdbool.h>
#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 <stdint.h>
#include <stdbool.h>
#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 <stdint.h>
#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 <stdint.h>
#include <stdbool.h>
#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 <stdint.h>
#include <stdbool.h>
#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 <string.h>
#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 <stdint.h>
#include <stdbool.h>
#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 <stdbool.h>
#include <stdint.h>
#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 ===