3367 lines
94 KiB
C
3367 lines
94 KiB
C
.
|
||
|
||
// === 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
|
||
¬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 <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: 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 <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 ===
|