#include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/timers.h" #include "esp_log.h" #include "driver/gpio.h" #include "led.h" #include "board_config.h" #include "evse_error.h" #include "evse_api.h" #define LED_UPDATE_INTERVAL_MS 100 #define BLOCK_TIME pdMS_TO_TICKS(10) static const char *TAG = "led"; typedef struct { gpio_num_t gpio; bool on : 1; uint16_t ontime; uint16_t offtime; TimerHandle_t timer; led_pattern_t pattern; uint8_t blink_count; } led_t; static led_t leds[LED_ID_MAX] = {0}; static TimerHandle_t led_update_timer = NULL; static evse_state_t led_state = -1; // ---------------------------- // Funções Internas // ---------------------------- static void led_update_timer_callback(TimerHandle_t xTimer); static void led_update(void); static void led_apply_by_state(evse_state_t state); static inline void led_gpio_write(gpio_num_t gpio, bool level) { if (gpio != GPIO_NUM_NC) gpio_set_level(gpio, level); } static void led_timer_callback(TimerHandle_t xTimer) { led_t *led = (led_t *)pvTimerGetTimerID(xTimer); led->on = !led->on; led_gpio_write(led->gpio, led->on); uint32_t next_time = led->on ? led->ontime : led->offtime; xTimerChangePeriod(led->timer, pdMS_TO_TICKS(next_time), BLOCK_TIME); } // ---------------------------- // Inicialização // ---------------------------- void led_init(void) { gpio_config_t io_conf = { .mode = GPIO_MODE_OUTPUT, .intr_type = GPIO_INTR_DISABLE, .pull_up_en = GPIO_PULLUP_DISABLE, .pull_down_en = GPIO_PULLDOWN_ENABLE, .pin_bit_mask = 0 }; for (int i = 0; i < LED_ID_MAX; i++) { leds[i].gpio = GPIO_NUM_NC; } if (board_config.led_stop) { leds[LED_ID_STOP].gpio = board_config.led_stop_gpio; io_conf.pin_bit_mask |= BIT64(board_config.led_stop_gpio); } if (board_config.led_charging) { leds[LED_ID_CHARGING].gpio = board_config.led_charging_gpio; io_conf.pin_bit_mask |= BIT64(board_config.led_charging_gpio); } if (board_config.led_error) { leds[LED_ID_ERROR].gpio = board_config.led_error_gpio; io_conf.pin_bit_mask |= BIT64(board_config.led_error_gpio); } if (io_conf.pin_bit_mask != 0) { ESP_ERROR_CHECK(gpio_config(&io_conf)); } if (!led_update_timer) { led_update_timer = xTimerCreate("led_update_timer", pdMS_TO_TICKS(LED_UPDATE_INTERVAL_MS), pdTRUE, NULL, led_update_timer_callback); if (led_update_timer) { xTimerStart(led_update_timer, BLOCK_TIME); } else { ESP_LOGE(TAG, "Failed to create LED update timer"); } } } // ---------------------------- // 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->gpio == GPIO_NUM_NC) return; // Evita reconfiguração idêntica 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) { led->on = false; led_gpio_write(led->gpio, 0); } else if (offtime == 0) { led->on = true; led_gpio_write(led->gpio, 1); } else { led->on = true; led_gpio_write(led->gpio, 1); if (!led->timer) { led->timer = xTimerCreate("led_timer", pdMS_TO_TICKS(ontime), pdFALSE, (void *)led, led_timer_callback); } if (led->timer) { xTimerStart(led->timer, BLOCK_TIME); } } } 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->gpio == GPIO_NUM_NC) return; if (led->pattern == pattern) return; if (led->timer) { xTimerStop(led->timer, BLOCK_TIME); } led->pattern = pattern; led->blink_count = 0; switch (pattern) { case LED_PATTERN_OFF: led_set_state(id, 0, 0); break; case LED_PATTERN_ON: led_set_state(id, 1, 0); break; case LED_PATTERN_BLINK: led_set_state(id, 500, 500); break; case LED_PATTERN_BLINK_FAST: led_set_state(id, 200, 200); break; case LED_PATTERN_BLINK_SLOW: led_set_state(id, 300, 1700); break; case LED_PATTERN_CHARGING_EFFECT: led_set_state(id, 2000, 1000); break; } } // ---------------------------- // Controle por Estado // ---------------------------- static void led_apply_by_state(evse_state_t state) { // Reset todos led_apply_pattern(LED_ID_STOP, LED_PATTERN_OFF); led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_OFF); led_apply_pattern(LED_ID_ERROR, LED_PATTERN_OFF); switch (state) { case EVSE_STATE_A: led_apply_pattern(LED_ID_STOP, LED_PATTERN_ON); break; case EVSE_STATE_B1: case EVSE_STATE_B2: case EVSE_STATE_C1: led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_ON); break; case EVSE_STATE_C2: led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_CHARGING_EFFECT); break; case EVSE_STATE_D1: case EVSE_STATE_D2: led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_BLINK_FAST); break; case EVSE_STATE_E: case EVSE_STATE_F: led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST); break; default: break; } } // ---------------------------- // Timer Update // ---------------------------- static void led_update(void) { if (evse_error_is_active()) { led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST); led_apply_pattern(LED_ID_STOP, LED_PATTERN_OFF); led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_OFF); return; } evse_state_t current = evse_get_state(); if (current != led_state) { led_state = current; led_apply_by_state(current); } } static void led_update_timer_callback(TimerHandle_t xTimer) { (void)xTimer; led_update(); }