new module

This commit is contained in:
2025-12-09 11:48:31 +00:00
parent 4820d9111e
commit e6e2622a95
98 changed files with 5349 additions and 8607 deletions

598
components/led/src/led.c Executable file
View File

@@ -0,0 +1,598 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_event.h"
#include <inttypes.h> // para PRIu32
#include "led.h"
#include "board_config.h"
#include "evse_events.h"
#include "evse_state.h"
#include "ledc_driver.h"
#define BLOCK_TIME pdMS_TO_TICKS(10)
static const char *TAG = "led";
typedef struct
{
bool present; ///< LED existe nesta placa
bool on : 1; ///< estado lógico atual (ligado/desligado)
uint16_t ontime; ///< ms ligado
uint16_t offtime; ///< ms desligado
TimerHandle_t timer; ///< timer de piscar (para padrões que usam on/off)
led_pattern_t pattern; ///< padrão atual
uint8_t blink_count; ///< reservado para padrões mais complexos
} led_t;
static led_t leds[LED_ID_MAX] = {0};
// ----------------------------
// Tabela de padrões (tempo on/off)
// ----------------------------
typedef struct
{
uint16_t on_ms;
uint16_t off_ms;
} led_timing_t;
// índice = led_pattern_t
static const led_timing_t led_pattern_table[] = {
[LED_PATTERN_OFF] = {0, 0},
[LED_PATTERN_ON] = {1, 0}, // 1ms só para cair na lógica "sempre ligado"
[LED_PATTERN_BLINK] = {500, 500},
[LED_PATTERN_BLINK_FAST] = {250, 250},
[LED_PATTERN_BLINK_SLOW] = {500, 1500},
[LED_PATTERN_CHARGING_EFFECT] = {2000, 1000},
// Para BREATHING, o temporizador FreeRTOS NÃO é usado; tratamos via task de efeitos.
// Mesmo assim, usamos (1,0) para marcar LED como "on" logicamente.
[LED_PATTERN_BREATHING] = {1, 0},
};
#define LED_PATTERN_COUNT (sizeof(led_pattern_table) / sizeof(led_pattern_table[0]))
// ----------------------------
// Estado base + efeitos de sessão
// ----------------------------
typedef enum
{
SESSION_EFFECT_NONE = 0,
SESSION_EFFECT_START,
SESSION_EFFECT_FINISH,
} led_session_effect_type_t;
static evse_state_event_t current_state_mode = EVSE_STATE_EVENT_IDLE;
static bool session_effect_active = false;
static TimerHandle_t session_effect_timer = NULL;
static led_session_effect_type_t session_effect_type = SESSION_EFFECT_NONE;
static uint8_t session_effect_phase = 0;
// Forwards
static void led_update_rgb_from_state(void);
static void led_effect_task(void *arg);
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data);
static void evse_session_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data);
static void session_effect_timer_cb(TimerHandle_t xTimer);
static void led_apply_state_mode(evse_state_event_t state);
// ----------------------------
// Timer de piscar (para padrões on/off)
// ----------------------------
static void led_timer_callback(TimerHandle_t xTimer)
{
led_t *led = (led_t *)pvTimerGetTimerID(xTimer);
led->on = !led->on;
uint32_t next_time = led->on ? led->ontime : led->offtime;
xTimerChangePeriod(led->timer, pdMS_TO_TICKS(next_time), BLOCK_TIME);
// Atualiza hardware (via LEDC).
led_update_rgb_from_state();
}
// ----------------------------
// Atualiza hardware a partir do estado lógico dos LEDs
// (para padrões "normais": ON/OFF/BLINK/...)
// ----------------------------
static void led_update_rgb_from_state(void)
{
uint32_t red = 0;
uint32_t green = 0;
uint32_t blue = 0;
if (LED_ID_RED < LED_ID_MAX && leds[LED_ID_RED].present && leds[LED_ID_RED].on)
red = 255;
if (LED_ID_GREEN < LED_ID_MAX && leds[LED_ID_GREEN].present && leds[LED_ID_GREEN].on)
green = 255;
if (LED_ID_BLUE < LED_ID_MAX && leds[LED_ID_BLUE].present && leds[LED_ID_BLUE].on)
blue = 255;
ledc_set_rgb(red, green, blue);
}
// ----------------------------
// Task de efeitos (BREATHING)
// ----------------------------
static void led_effect_task(void *arg)
{
// v = "intensidade" lógica 0100
uint32_t v = 0;
bool up = true;
const uint32_t v_min = 0;
const uint32_t v_max = 100;
const uint32_t step = 3;
const TickType_t delay_breathe = pdMS_TO_TICKS(50); // velocidade da respiração
const TickType_t delay_idle = pdMS_TO_TICKS(1000); // quando não há LED em BREATHING
for (;;)
{
// Verifica se algum LED está em BREATHING
bool has_breath = false;
led_id_t breath_id = LED_ID_MAX;
for (int i = 0; i < LED_ID_MAX; ++i)
{
if (leds[i].present && leds[i].pattern == LED_PATTERN_BREATHING)
{
has_breath = true;
breath_id = (led_id_t)i;
break;
}
}
if (has_breath)
{
// Usa o valor atual de v para calcular o brilho (0255)
uint32_t brightness = (v * 255U) / 100U;
uint32_t r = 0, g = 0, b = 0;
switch (breath_id)
{
case LED_ID_RED:
r = brightness;
break;
case LED_ID_GREEN:
g = brightness;
break;
case LED_ID_BLUE:
b = brightness;
break;
default:
// fallback: usa azul se algo estranho acontecer
b = brightness;
break;
}
// Aplica só um canal, sem misturar cores
ledc_set_rgb(r, g, b);
// Se estiver completamente apagado (v == 0),
// queremos que fique 500 ms off antes de voltar a subir
TickType_t delay = (v == v_min) ? pdMS_TO_TICKS(500) : delay_breathe;
// Atualiza v para a próxima iteração (triângulo 0→100→0)
if (up)
{
if (v + step >= v_max)
{
v = v_max;
up = false;
}
else
{
v += step;
}
}
else
{
if (v <= v_min + step)
{
v = v_min;
up = true;
}
else
{
v -= step;
}
}
vTaskDelay(delay);
}
else
{
// Ninguém em BREATHING: deixa padrões normais controlarem o LED
led_update_rgb_from_state();
vTaskDelay(delay_idle);
// Opcional: quando sair de BREATHING e voltar mais tarde,
// começa de novo a partir de apagado
v = v_min;
up = true;
}
}
}
// ----------------------------
// Aplica padrão em função do estado EVSE (base)
// GARANTINDO apenas 1 LED por estado
// ----------------------------
static void led_apply_state_mode(evse_state_event_t state)
{
// Desliga padrões anteriores para todos os canais
for (int i = 0; i < LED_ID_MAX; ++i)
{
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
}
switch (state)
{
case EVSE_STATE_EVENT_IDLE:
// IDLE → verde fixo (claro e visível)
led_apply_pattern(LED_ID_GREEN, LED_PATTERN_ON);
break;
case EVSE_STATE_EVENT_WAITING:
// WAITING → azul a piscar lento
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_SLOW);
break;
case EVSE_STATE_EVENT_CHARGING:
// CHARGING → azul "breathing"
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BREATHING);
break;
case EVSE_STATE_EVENT_FAULT:
// FAULT → vermelho a piscar rápido
led_apply_pattern(LED_ID_RED, LED_PATTERN_BLINK_FAST);
break;
default:
break;
}
led_update_rgb_from_state();
}
// ----------------------------
// Timer callback do efeito de sessão
// Efeitos usam sempre UM LED forte
// ----------------------------
static void session_effect_timer_cb(TimerHandle_t xTimer)
{
switch (session_effect_type)
{
case SESSION_EFFECT_START:
session_effect_phase++;
switch (session_effect_phase)
{
case 1:
// Fase 1: depois de flash sólido, passa a azul a piscar rápido
for (int i = 0; i < LED_ID_MAX; ++i)
{
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
}
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
led_update_rgb_from_state();
// Mantém piscar rápido mais um bocado
xTimerChangePeriod(session_effect_timer,
pdMS_TO_TICKS(5000),
BLOCK_TIME);
xTimerStart(session_effect_timer, BLOCK_TIME);
break;
case 2:
default:
// Fim do efeito de START → volta ao estado base (tipicamente CHARGING)
session_effect_active = false;
session_effect_type = SESSION_EFFECT_NONE;
session_effect_phase = 0;
led_apply_state_mode(current_state_mode);
break;
}
break;
case SESSION_EFFECT_FINISH:
session_effect_phase++;
switch (session_effect_phase)
{
case 1:
// Fase 1: depois de flash sólido, passa a azul a piscar rápido
for (int i = 0; i < LED_ID_MAX; ++i)
{
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
}
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_BLINK_FAST);
led_update_rgb_from_state();
// Mantém piscar rápido mais tempo (destaque de fim de sessão)
xTimerChangePeriod(session_effect_timer,
pdMS_TO_TICKS(5000),
BLOCK_TIME);
xTimerStart(session_effect_timer, BLOCK_TIME);
break;
case 2:
default:
// Fim do efeito de FINISH → volta ao estado base (IDLE/WAITING)
session_effect_active = false;
session_effect_type = SESSION_EFFECT_NONE;
session_effect_phase = 0;
led_apply_state_mode(current_state_mode);
break;
}
break;
case SESSION_EFFECT_NONE:
default:
session_effect_active = false;
session_effect_phase = 0;
led_apply_state_mode(current_state_mode);
break;
}
}
// ----------------------------
// Event Handler: EVSE State
// ----------------------------
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL)
{
return;
}
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
ESP_LOGI(TAG, "EVSE State Changed: state=%d", evt->state);
// Atualiza o estado base
current_state_mode = evt->state;
// Se estiver a decorrer um efeito de sessão, não mexe agora nos LEDs.
if (session_effect_active)
{
return;
}
led_apply_state_mode(current_state_mode);
}
// ----------------------------
// Event Handler: EVSE Session
// (efeitos de início/fim de sessão, 1 LED de cada vez)
// ----------------------------
static void evse_session_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != EVSE_EVENTS || id != EVSE_EVENT_SESSION || data == NULL)
{
return;
}
const evse_session_event_data_t *evt =
(const evse_session_event_data_t *)data;
ESP_LOGI(TAG,
"EVSE Session Event: type=%d, id=%" PRIu32
", duration=%" PRIu32 " s, energy=%" PRIu32 " Wh, avg=%" PRIu32 " W, current=%d",
(int)evt->type,
evt->session_id,
evt->duration_s,
evt->energy_wh,
evt->avg_power_w,
evt->is_current);
// Marca que um efeito de sessão está ativo
session_effect_active = true;
session_effect_phase = 0;
if (session_effect_timer)
{
xTimerStop(session_effect_timer, BLOCK_TIME);
}
// Apaga tudo antes de iniciar o efeito
for (int i = 0; i < LED_ID_MAX; ++i)
{
led_apply_pattern((led_id_t)i, LED_PATTERN_OFF);
}
switch (evt->type)
{
case EVSE_SESSION_EVENT_STARTED:
// Efeito de início:
// Fase 0: azul sólido curto
session_effect_type = SESSION_EFFECT_START;
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_ON);
led_update_rgb_from_state();
xTimerChangePeriod(session_effect_timer,
pdMS_TO_TICKS(300), // 0.3 s flash
BLOCK_TIME);
xTimerStart(session_effect_timer, BLOCK_TIME);
break;
case EVSE_SESSION_EVENT_FINISHED:
// Efeito de fim:
// Fase 0: azul sólido curto
session_effect_type = SESSION_EFFECT_FINISH;
led_apply_pattern(LED_ID_BLUE, LED_PATTERN_ON);
led_update_rgb_from_state();
xTimerChangePeriod(session_effect_timer,
pdMS_TO_TICKS(300), // 0.3 s flash
BLOCK_TIME);
xTimerStart(session_effect_timer, BLOCK_TIME);
break;
default:
// Se for tipo desconhecido, desiste do efeito e volta ao estado base
session_effect_active = false;
session_effect_type = SESSION_EFFECT_NONE;
session_effect_phase = 0;
led_apply_state_mode(current_state_mode);
break;
}
}
// ----------------------------
// Inicialização
// ----------------------------
void led_init(void)
{
// Marca quais LEDs existem de acordo com o board_config
leds[LED_ID_GREEN].present = board_config.led_green;
leds[LED_ID_BLUE].present = board_config.led_blue;
leds[LED_ID_RED].present = board_config.led_red;
// Inicializa LEDC com os GPIOs definidos na board
esp_err_t err = ledc_init(board_config.led_red_gpio,
board_config.led_green_gpio,
board_config.led_blue_gpio);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to init LEDC: %s", esp_err_to_name(err));
}
// Regista handler de evento EVSE - STATE
ESP_ERROR_CHECK(esp_event_handler_register(
EVSE_EVENTS,
EVSE_EVENT_STATE_CHANGED,
evse_led_event_handler,
NULL));
// Regista handler de evento EVSE - SESSION
ESP_ERROR_CHECK(esp_event_handler_register(
EVSE_EVENTS,
EVSE_EVENT_SESSION,
evse_session_led_event_handler,
NULL));
// Cria task de efeitos (breathing)
xTaskCreate(led_effect_task, "led_effect_task", 2048, NULL, 1, NULL);
// Cria timer one-shot para efeitos de sessão
session_effect_timer = xTimerCreate(
"session_eff",
pdMS_TO_TICKS(1000), // valor default; ajustado com xTimerChangePeriod
pdFALSE, // one-shot
NULL,
session_effect_timer_cb);
ESP_LOGI(TAG, "LED system initialized");
// Estado inicial: IDLE
evse_state_event_data_t evt = {
.state = EVSE_STATE_EVENT_IDLE};
evse_led_event_handler(NULL, EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &evt);
}
// ----------------------------
// API Pública
// ----------------------------
void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime)
{
if (led_id >= LED_ID_MAX)
{
return;
}
led_t *led = &leds[led_id];
if (!led->present)
{
return;
}
if (led->ontime == ontime && led->offtime == offtime)
{
return;
}
if (led->timer)
{
xTimerStop(led->timer, BLOCK_TIME);
}
led->ontime = ontime;
led->offtime = offtime;
if (ontime == 0)
{
// sempre desligado
led->on = false;
}
else if (offtime == 0)
{
// sempre ligado
led->on = true;
}
else
{
// pisca
led->on = true;
if (!led->timer)
{
// nome só para debug; opcional
led->timer = xTimerCreate("led_timer",
pdMS_TO_TICKS(ontime),
pdFALSE,
(void *)led,
led_timer_callback);
}
if (led->timer)
{
xTimerStart(led->timer, BLOCK_TIME);
}
}
// Atualiza hardware (para estados sem BREATHING)
led_update_rgb_from_state();
}
void led_apply_pattern(led_id_t id, led_pattern_t pattern)
{
if (id >= LED_ID_MAX)
{
return;
}
led_t *led = &leds[id];
if (!led->present)
{
return;
}
if ((unsigned)pattern >= LED_PATTERN_COUNT)
{
ESP_LOGW(TAG, "Invalid LED pattern %d", pattern);
return;
}
if (led->pattern == pattern)
{
return;
}
if (led->timer)
{
xTimerStop(led->timer, BLOCK_TIME);
}
led->pattern = pattern;
led->blink_count = 0;
const led_timing_t *cfg = &led_pattern_table[pattern];
led_set_state(id, cfg->on_ms, cfg->off_ms);
// led_set_state já chama led_update_rgb_from_state()
}

129
components/led/src/ledc_driver.c Executable file
View File

@@ -0,0 +1,129 @@
/*
* LEDC driver para 3 LEDs (R,G,B) controlados via ULN2003
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/ledc.h"
#include "esp_err.h"
#include "ledc_driver.h"
// Pode ser futuramente ligado a uma opção de Kconfig.
// Para o teu hardware (comum a 12 V via ULN2003), visto do ESP é ativo-alto.
#define IS_ACTIVE_HIGH 1
#define LEDC_LS_TIMER LEDC_TIMER_2
#define LEDC_LS_MODE LEDC_LOW_SPEED_MODE
// Canais usados: 2, 3, 4
#define LEDC_CH_RED LEDC_CHANNEL_2
#define LEDC_CH_GREEN LEDC_CHANNEL_3
#define LEDC_CH_BLUE LEDC_CHANNEL_4
#define LEDC_NUM_CHANNELS (3)
#define LEDC_DUTY_RES LEDC_TIMER_13_BIT
#define LEDC_DUTY_MAX (8192 - 1)
#define LEDC_FREQUENCY (5000) // 5 kHz
static ledc_channel_config_t ledc_channel[LEDC_NUM_CHANNELS] = {
{
.channel = LEDC_CH_RED,
.duty = 0,
.gpio_num = -1, // preenchido em runtime
.speed_mode = LEDC_LS_MODE,
.hpoint = 0,
.timer_sel = LEDC_LS_TIMER,
},
{
.channel = LEDC_CH_GREEN,
.duty = 0,
.gpio_num = -1,
.speed_mode = LEDC_LS_MODE,
.hpoint = 0,
.timer_sel = LEDC_LS_TIMER,
},
{
.channel = LEDC_CH_BLUE,
.duty = 0,
.gpio_num = -1,
.speed_mode = LEDC_LS_MODE,
.hpoint = 0,
.timer_sel = LEDC_LS_TIMER,
},
};
esp_err_t ledc_init(gpio_num_t gpio_red,
gpio_num_t gpio_green,
gpio_num_t gpio_blue)
{
// Configuração do timer
ledc_timer_config_t ledc_timer = {
.duty_resolution = LEDC_DUTY_RES,
.freq_hz = LEDC_FREQUENCY,
.speed_mode = LEDC_LS_MODE,
.timer_num = LEDC_LS_TIMER,
.clk_cfg = LEDC_AUTO_CLK,
};
esp_err_t err = ledc_timer_config(&ledc_timer);
if (err != ESP_OK)
{
return err;
}
// Atribuir GPIOs aos canais
ledc_channel[0].gpio_num = gpio_red;
ledc_channel[1].gpio_num = gpio_green;
ledc_channel[2].gpio_num = gpio_blue;
// Configurar canais
for (int ch = 0; ch < LEDC_NUM_CHANNELS; ch++)
{
err = ledc_channel_config(&ledc_channel[ch]);
if (err != ESP_OK)
{
return err;
}
}
return ESP_OK;
}
esp_err_t ledc_set_rgb(uint32_t red, uint32_t green, uint32_t blue)
{
if (red > 255)
red = 255;
if (green > 255)
green = 255;
if (blue > 255)
blue = 255;
red = red * LEDC_DUTY_MAX / 255;
green = green * LEDC_DUTY_MAX / 255;
blue = blue * LEDC_DUTY_MAX / 255;
if (!IS_ACTIVE_HIGH)
{
red = LEDC_DUTY_MAX - red;
green = LEDC_DUTY_MAX - green;
blue = LEDC_DUTY_MAX - blue;
}
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_RED, red);
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_RED);
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_GREEN, green);
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_GREEN);
ledc_set_duty(LEDC_LS_MODE, LEDC_CH_BLUE, blue);
ledc_update_duty(LEDC_LS_MODE, LEDC_CH_BLUE);
return ESP_OK;
}
esp_err_t ledc_clear(void)
{
return ledc_set_rgb(0, 0, 0);
}