new buzzer component

This commit is contained in:
2025-07-22 00:09:58 +01:00
parent 84f106eee5
commit bd587a10c0
58 changed files with 3215 additions and 6961 deletions

View File

@@ -10,6 +10,8 @@ set(srcs
evse_hardware.c
evse_pilot.c
evse_meter.c
evse_session.c
evse_api.c
)
idf_component_register(

37
components/evse/evse_api.c Executable file
View File

@@ -0,0 +1,37 @@
// 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);
}

View File

@@ -50,8 +50,8 @@ void evse_check_defaults(void) {
// Charging current (default, persisted)
err = nvs_get_u16(nvs, "def_chrg_curr", &u16);
if (err != ESP_OK || u16 < (MIN_CHARGING_CURRENT_LIMIT * 10) || u16 > (max_charging_current * 10)) {
charging_current = max_charging_current * 10;
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);
@@ -136,7 +136,7 @@ uint16_t evse_get_charging_current(void) {
}
esp_err_t evse_set_charging_current(uint16_t value) {
if (value < (MIN_CHARGING_CURRENT_LIMIT * 10) || value > (max_charging_current * 10))
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);
@@ -151,7 +151,7 @@ uint16_t evse_get_default_charging_current(void) {
}
esp_err_t evse_set_default_charging_current(uint16_t value) {
if (value < (MIN_CHARGING_CURRENT_LIMIT * 10) || value > (max_charging_current * 10))
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);
@@ -161,12 +161,17 @@ esp_err_t evse_set_default_charging_current(uint16_t value) {
// Runtime current (not saved)
// ========================
void evse_set_runtime_charging_current(uint16_t value) {
if (value < (MIN_CHARGING_CURRENT_LIMIT) || value > (max_charging_current)) {
ESP_LOGW(TAG, "Rejected runtime charging current (out of bounds): %d", value);
return;
if (value > (max_charging_current)) {
value= max_charging_current;
}
if (value < (MIN_CHARGING_CURRENT_LIMIT) ) {
value= MIN_CHARGING_CURRENT_LIMIT;
}
charging_current_runtime = value;
ESP_LOGD(TAG, "Runtime charging current updated: %d", charging_current_runtime);
ESP_LOGI(TAG, "Runtime charging current updated: %d", charging_current_runtime);
}
uint16_t evse_get_runtime_charging_current(void) {
@@ -219,18 +224,6 @@ esp_err_t evse_set_temp_threshold(uint8_t value) {
return nvs_commit(nvs);
}
// ========================
// Authentication
// ========================
bool evse_is_require_auth(void) {
return require_auth;
}
void evse_set_require_auth(bool value) {
require_auth = value;
nvs_set_u8(nvs, "require_auth", value);
nvs_commit(nvs);
}
// ========================
// Availability

View File

@@ -5,6 +5,7 @@
#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"
@@ -71,25 +72,6 @@ void evse_process(void) {
xSemaphoreGive(mutex);
}
// ================================
// 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);
}
// ================================
// Background Task
// ================================

View File

@@ -69,15 +69,16 @@ static void update_outputs(evse_state_t state) {
if (board_config.socket_lock && socket_outlet) {
socket_lock_set_locked(true);
}
if (rcm_test()) {
ESP_LOGI(TAG, "RCM self test passed");
//ESP_LOGI(TAG, "RCM self test passed");
} else {
ESP_LOGW(TAG, "RCM self test failed");
//ESP_LOGW(TAG, "RCM self test failed");
}
break;
case EVSE_STATE_B2:
pilot_set_amps(MIN(current * 10, cable_max_current * 10));
pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(false);
break;
@@ -90,7 +91,7 @@ static void update_outputs(evse_state_t state) {
case EVSE_STATE_C2:
case EVSE_STATE_D2:
pilot_set_amps(MIN(current * 10, cable_max_current * 10));
pilot_set_amps(MIN(current, cable_max_current));
ac_relay_set_state(true); // Só chega aqui se não há erro!
break;
}

View File

@@ -1,7 +1,9 @@
#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"
@@ -134,41 +136,53 @@ 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) {
evse_state_t state = evse_get_state();
if (!evse_state_is_charging(state)) return;
// 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;
uint32_t energy = evse_meter_get_total_energy();
uint32_t power = evse_meter_get_instant_power();
TickType_t now = xTaskGetTickCount();
TickType_t start = evse_get_session_start();
if (consumption_limit > 0 && energy >= consumption_limit) {
ESP_LOGW("EVSE", "Energy limit reached");
// 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;
}
if (charging_time_limit > 0 &&
(now - start) >= pdMS_TO_TICKS(charging_time_limit * 1000)) {
ESP_LOGW("EVSE", "Charging time limit reached");
// 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;
}
if (under_power_limit > 0 && power < under_power_limit) {
ESP_LOGW("EVSE", "Under power limit reached");
// 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);
}
}
}

View File

@@ -5,6 +5,7 @@
#include "evse_config.h"
#include "evse_api.h"
#include "evse_meter.h"
#include "evse_session.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -89,6 +90,7 @@ void evse_manager_init(void) {
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));
@@ -105,6 +107,7 @@ void evse_manager_tick(void) {
evse_error_tick();
evse_state_tick();
evse_temperature_check();
evse_session_tick();
if (auth_enabled) {
// If the car is disconnected, revoke authorization

View File

@@ -41,7 +41,7 @@ void evse_meter_on_meter_event(void* arg, void* event_data) {
meter_data.energy_wh = (uint32_t)(evt->total_energy * 1000.0f);
xSemaphoreGive(meter_mutex);
ESP_LOGD(TAG,
ESP_LOGI(TAG,
"Meter updated: power[W]={%" PRIu32 ",%" PRIu32 ",%" PRIu32 "}, "
"voltage[V]={%.2f,%.2f,%.2f}, "
"current[A]={%.2f,%.2f,%.2f}, "
@@ -63,9 +63,9 @@ void evse_meter_init(void) {
ESP_LOGI(TAG, "EVSE Meter listener registered.");
}
uint32_t evse_meter_get_instant_power(void) {
int evse_meter_get_instant_power(void) {
xSemaphoreTake(meter_mutex, portMAX_DELAY);
uint32_t sum = 0;
int sum = 0;
for (int i = 0; i < EVSE_METER_PHASE_COUNT; ++i) {
sum += meter_data.power_watts[i];
}
@@ -73,14 +73,14 @@ uint32_t evse_meter_get_instant_power(void) {
return sum;
}
uint32_t evse_meter_get_total_energy(void) {
int evse_meter_get_total_energy(void) {
xSemaphoreTake(meter_mutex, portMAX_DELAY);
uint32_t val = meter_data.energy_wh;
int val = meter_data.energy_wh;
xSemaphoreGive(meter_mutex);
return val;
}
void evse_meter_get_power(uint32_t power[EVSE_METER_PHASE_COUNT]) {
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];

View File

@@ -79,28 +79,36 @@ void pilot_set_level(bool level)
void pilot_set_amps(uint16_t amps)
{
if (amps < 60 || amps > 800) {
ESP_LOGE(TAG, "Invalid ampere value: %d A*10", amps);
if (amps < 6 || amps > 80) {
ESP_LOGE(TAG, "Invalid ampere value: %d A (valid: 680 A)", amps);
return;
}
uint32_t duty;
if (amps <= 510) {
duty = (PILOT_PWM_MAX_DUTY * amps) / 600;
uint32_t duty_percent;
if (amps <= 51) {
duty_percent = (amps * 10) / 6; // Duty (%) = Amps / 0.6
} else {
duty = ((PILOT_PWM_MAX_DUTY * amps) / 2500) + (64 * (PILOT_PWM_MAX_DUTY / 100));
duty_percent = (amps * 10) / 25 + 64; // Duty (%) = (Amps / 2.5) + 64
}
if (duty > PILOT_PWM_MAX_DUTY) duty = PILOT_PWM_MAX_DUTY;
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, "Set amp %dA*10 -> duty %lu/%d", amps, duty, PILOT_PWM_MAX_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);
}

View File

@@ -0,0 +1,84 @@
/*
* 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;
}

View File

@@ -1,5 +1,6 @@
#include "evse_api.h"
#include "evse_state.h"
#include "evse_session.h"
#include "evse_events.h"
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
@@ -11,10 +12,11 @@
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;
static const char *TAG = "evse_state";
// =========================
// Internal Mapping
// =========================
@@ -36,34 +38,45 @@ static evse_state_event_t map_state_to_event(evse_state_t s) {
// Public API
// =========================
void evse_set_state(evse_state_t state) {
void evse_set_state(evse_state_t new_state) {
bool changed = false;
evse_state_t previous_state;
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);
previous_state = current_state;
if (state != current_state) {
current_state = state;
changed = true;
// When entering a charging state, record the start tick
if (evse_state_is_charging(state) && !evse_state_is_charging(previous_state)) {
session_start_tick = xTaskGetTickCount();
}
// When exiting a charging state, reset the start tick
else if (!evse_state_is_charging(state) && evse_state_is_charging(previous_state)) {
session_start_tick = 0;
}
}
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) {
ESP_LOGI("EVSE_STATE", "State changed from %s to %s",
evse_state_to_str(previous_state),
evse_state_to_str(state));
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(state)
.state = map_state_to_event(new_state)
};
esp_event_post(EVSE_EVENTS,
EVSE_EVENT_STATE_CHANGED,
@@ -73,6 +86,8 @@ void evse_set_state(evse_state_t state) {
}
}
evse_state_t evse_get_state(void) {
portENTER_CRITICAL(&state_mux);
evse_state_t s = current_state;
@@ -80,13 +95,6 @@ evse_state_t evse_get_state(void) {
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)";
@@ -105,7 +113,6 @@ const char* evse_state_to_str(evse_state_t 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);

View File

@@ -3,8 +3,10 @@
#include <stdint.h>
#include <stdbool.h>
#include "evse_state.h" // Tipos e estados
#include "evse_state.h"
#include "freertos/FreeRTOS.h"
#include "evse_session.h"
#ifdef __cplusplus
extern "C" {
@@ -24,10 +26,17 @@ evse_state_t evse_get_state(void);
*/
void evse_set_state(evse_state_t state);
// ===============================
// Charging Session Info
// ===============================
/**
* @brief Get timestamp when the current session started (for timing limits).
* @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
*/
TickType_t evse_get_session_start(void);
bool evse_get_session(evse_session_t *out);
// ===============================
// Charging Session Info

View File

@@ -55,10 +55,6 @@ esp_err_t evse_set_rcm(bool rcm);
uint8_t evse_get_temp_threshold(void);
esp_err_t evse_set_temp_threshold(uint8_t threshold);
// Autenticação
bool evse_is_require_auth(void);
void evse_set_require_auth(bool require);
// Disponibilidade
bool evse_config_is_available(void);
void evse_config_set_available(bool available);

View File

@@ -13,13 +13,13 @@ extern "C" {
void evse_meter_init(void);
/// Retorna a potência instantânea (soma das 3 fases, em watts)
uint32_t evse_meter_get_instant_power(void);
int evse_meter_get_instant_power(void);
/// Retorna a energia total acumulada (em Wh)
uint32_t evse_meter_get_total_energy(void);
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(uint32_t power[EVSE_METER_PHASE_COUNT]);
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]);

View File

@@ -0,0 +1,53 @@
/*
* 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

View File

@@ -53,11 +53,6 @@ evse_state_t evse_get_state(void);
*/
void evse_set_state(evse_state_t state);
/**
* @brief Returns the tick count when the current charging session began.
*/
TickType_t evse_get_session_start(void);
/**
* @brief Converts the state enum into a human-readable string.
*/