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