471 lines
12 KiB
C
471 lines
12 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 "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"
|
|
#include "ocpp.h"
|
|
#include "led.h"
|
|
#include "scheduler.h"
|
|
#include "storage_service.h"
|
|
#include "time_service.h"
|
|
#include "hmi_link.h"
|
|
|
|
|
|
/*
|
|
* Modos de controlo do AP:
|
|
*
|
|
* APP_AP_CONTROL_BUTTON:
|
|
* - Usa botão físico.
|
|
* - Pressão curta: inicia AP.
|
|
* - Pressão longa: apaga NVS e reinicia.
|
|
*
|
|
* APP_AP_CONTROL_BOOT_WINDOW:
|
|
* - Não usa botão físico.
|
|
* - Inicia AP automaticamente no arranque.
|
|
* - Mantém AP ativo durante os primeiros 3 minutos.
|
|
* - Depois desliga AP e volta ao modo STA.
|
|
*/
|
|
#define APP_AP_CONTROL_BUTTON 0
|
|
#define APP_AP_CONTROL_BOOT_WINDOW 1
|
|
|
|
/*
|
|
* Escolher aqui o modo pretendido:
|
|
*
|
|
* Para placa COM botão:
|
|
* #define APP_AP_CONTROL_MODE APP_AP_CONTROL_BUTTON
|
|
*
|
|
* Para placa SEM botão:
|
|
* #define APP_AP_CONTROL_MODE APP_AP_CONTROL_BOOT_WINDOW
|
|
*/
|
|
#define APP_AP_CONTROL_MODE APP_AP_CONTROL_BUTTON
|
|
|
|
#define AP_BOOT_WINDOW_MS 180000UL // 3 minutos
|
|
#define AP_CONNECTION_TIMEOUT AP_BOOT_WINDOW_MS
|
|
|
|
#define RESET_HOLD_TIME 30000UL // ms
|
|
#define DEBOUNCE_TIME_MS 50UL
|
|
|
|
#define PRESS_BIT BIT0
|
|
#define RELEASED_BIT BIT1
|
|
|
|
static const char *TAG = "app_main";
|
|
|
|
static TaskHandle_t user_input_task = NULL;
|
|
static TickType_t press_tick = 0;
|
|
static volatile TickType_t last_interrupt_tick = 0;
|
|
static bool pressed = false;
|
|
|
|
// Spinlock para garantir debounce seguro na ISR
|
|
static portMUX_TYPE button_spinlock = portMUX_INITIALIZER_UNLOCKED;
|
|
|
|
//
|
|
// 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, (int)total, (int)used);
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGE(TAG, "Failed to get SPIFFS info (%s): %s",
|
|
conf->partition_label, 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)
|
|
{
|
|
(void)param;
|
|
EventBits_t mode_bits;
|
|
|
|
for (;;)
|
|
{
|
|
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)
|
|
{
|
|
// Espera sair do AP
|
|
xEventGroupWaitBits(
|
|
wifi_event_group,
|
|
WIFI_AP_DISCONNECTED_BIT,
|
|
pdFALSE,
|
|
pdFALSE,
|
|
portMAX_DELAY);
|
|
}
|
|
else
|
|
{
|
|
if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)
|
|
{
|
|
// Timeout sem cliente ligado
|
|
// O desligar do AP no modo sem botão é feito pela boot_ap_task_func().
|
|
// No modo com botão, mantemos o comportamento antigo.
|
|
ESP_LOGW(TAG, "AP timeout sem conexões");
|
|
}
|
|
}
|
|
}
|
|
else if (mode_bits & WIFI_STA_MODE_BIT)
|
|
{
|
|
// Apenas aguarda desconexão STA
|
|
xEventGroupWaitBits(
|
|
wifi_event_group,
|
|
WIFI_STA_DISCONNECTED_BIT,
|
|
pdFALSE,
|
|
pdFALSE,
|
|
portMAX_DELAY);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Button press handler (short press => AP)
|
|
//
|
|
static void handle_button_press(void)
|
|
{
|
|
// Pode ser chamado cedo demais se a ordem de init mudar
|
|
if (wifi_event_group == NULL)
|
|
{
|
|
ESP_LOGW(TAG, "Wi-Fi ainda não inicializado, ignorando botão Wi-Fi");
|
|
return;
|
|
}
|
|
|
|
if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT))
|
|
{
|
|
ESP_LOGI(TAG, "Starting Wi-Fi AP mode (short press)");
|
|
wifi_ap_start();
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGI(TAG, "Wi-Fi AP mode já está ativo");
|
|
}
|
|
}
|
|
|
|
//
|
|
// Task para lidar com notificações de botão (PRESS / RELEASE)
|
|
//
|
|
static void user_input_task_func(void *param)
|
|
{
|
|
(void)param;
|
|
uint32_t notification;
|
|
|
|
for (;;)
|
|
{
|
|
if (xTaskNotifyWait(
|
|
0,
|
|
UINT32_MAX,
|
|
¬ification,
|
|
portMAX_DELAY))
|
|
{
|
|
if (notification & PRESS_BIT)
|
|
{
|
|
press_tick = xTaskGetTickCount();
|
|
pressed = true;
|
|
ESP_LOGI(TAG, "Button Pressed");
|
|
// Decisão short/long é feita na soltura
|
|
}
|
|
|
|
if ((notification & RELEASED_BIT) && pressed)
|
|
{
|
|
pressed = false;
|
|
|
|
TickType_t held_ticks = xTaskGetTickCount() - press_tick;
|
|
uint32_t held_ms = pdTICKS_TO_MS(held_ticks);
|
|
|
|
ESP_LOGI(TAG, "Button Released (held %u ms)", (unsigned)held_ms);
|
|
|
|
if (held_ms >= RESET_HOLD_TIME)
|
|
{
|
|
ESP_LOGW(TAG, "Long press: erasing NVS + reboot");
|
|
|
|
esp_err_t ret = nvs_flash_erase();
|
|
if (ret != ESP_OK)
|
|
{
|
|
ESP_LOGE(TAG, "Erro ao apagar NVS: %s", esp_err_to_name(ret));
|
|
}
|
|
|
|
esp_restart();
|
|
}
|
|
else
|
|
{
|
|
// Short press: apenas habilita modo AP
|
|
handle_button_press();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// ISR para GPIO do botão ativo em nível baixo
|
|
//
|
|
static void IRAM_ATTR button_isr_handler(void *arg)
|
|
{
|
|
(void)arg;
|
|
BaseType_t higher_task_woken = pdFALSE;
|
|
TickType_t now = xTaskGetTickCountFromISR();
|
|
|
|
portENTER_CRITICAL_ISR(&button_spinlock);
|
|
|
|
if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS))
|
|
{
|
|
portEXIT_CRITICAL_ISR(&button_spinlock);
|
|
return;
|
|
}
|
|
|
|
last_interrupt_tick = now;
|
|
|
|
if (user_input_task == NULL)
|
|
{
|
|
portEXIT_CRITICAL_ISR(&button_spinlock);
|
|
return;
|
|
}
|
|
|
|
int level = gpio_get_level(board_config.button_wifi_gpio);
|
|
uint32_t bits = (level == 0) ? PRESS_BIT : RELEASED_BIT;
|
|
|
|
xTaskNotifyFromISR(
|
|
user_input_task,
|
|
bits,
|
|
eSetBits,
|
|
&higher_task_woken);
|
|
|
|
portEXIT_CRITICAL_ISR(&button_spinlock);
|
|
|
|
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));
|
|
}
|
|
|
|
//
|
|
// AP automático no arranque para placas sem botão
|
|
//
|
|
static void boot_ap_task_func(void *param)
|
|
{
|
|
(void)param;
|
|
|
|
if (wifi_event_group == NULL)
|
|
{
|
|
ESP_LOGW(TAG, "Wi-Fi ainda não inicializado, não é possível iniciar AP");
|
|
vTaskDelete(NULL);
|
|
return;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "Modo sem botão: iniciar AP automaticamente durante 3 minutos");
|
|
|
|
wifi_ap_start();
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(AP_BOOT_WINDOW_MS));
|
|
|
|
EventBits_t bits = xEventGroupGetBits(wifi_event_group);
|
|
|
|
if (bits & WIFI_AP_MODE_BIT)
|
|
{
|
|
ESP_LOGI(TAG, "Modo sem botão: janela de AP terminou, a desligar AP");
|
|
wifi_ap_stop();
|
|
}
|
|
else
|
|
{
|
|
ESP_LOGI(TAG, "Modo sem botão: AP já não estava ativo");
|
|
}
|
|
|
|
vTaskDelete(NULL);
|
|
}
|
|
|
|
//
|
|
// Inicialização dos módulos do sistema
|
|
//
|
|
static void init_modules(void)
|
|
{
|
|
ESP_ERROR_CHECK(storage_service_init());
|
|
|
|
peripherals_init();
|
|
wifi_ini(); // garante wifi_event_group inicializado
|
|
buzzer_init();
|
|
led_init();
|
|
ESP_ERROR_CHECK(rest_server_init("/data"));
|
|
|
|
evse_manager_init();
|
|
evse_init();
|
|
auth_init();
|
|
meter_manager_init();
|
|
meter_manager_start();
|
|
ocpp_start();
|
|
time_service_init();
|
|
scheduler_init();
|
|
protocols_init();
|
|
loadbalancer_init();
|
|
evse_link_init();
|
|
//ESP_ERROR_CHECK(hmi_link_init());
|
|
}
|
|
|
|
//
|
|
// Função principal do firmware
|
|
//
|
|
void app_main(void)
|
|
{
|
|
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());
|
|
|
|
board_config_load();
|
|
|
|
/*
|
|
* O serviço GPIO ISR tem de estar instalado antes de qualquer módulo
|
|
* chamar gpio_isr_handler_add().
|
|
*
|
|
* O RFID/Wiegand usa interrupções GPIO durante auth_init(),
|
|
* que é chamado dentro de init_modules().
|
|
*/
|
|
esp_err_t gpio_isr_ret = gpio_install_isr_service(0);
|
|
if (gpio_isr_ret != ESP_OK && gpio_isr_ret != ESP_ERR_INVALID_STATE)
|
|
{
|
|
ESP_ERROR_CHECK(gpio_isr_ret);
|
|
}
|
|
|
|
|
|
// 1) Inicia todos os módulos, incluindo Wi-Fi, EVSE, etc.
|
|
init_modules();
|
|
|
|
BaseType_t rc;
|
|
|
|
#if APP_AP_CONTROL_MODE == APP_AP_CONTROL_BUTTON
|
|
|
|
ESP_LOGI(TAG, "Modo AP por botão ativo");
|
|
|
|
// 2) Cria a task que recebe notificações do botão
|
|
rc = xTaskCreate(
|
|
user_input_task_func,
|
|
"user_input_task",
|
|
4 * 1024,
|
|
NULL,
|
|
3,
|
|
&user_input_task);
|
|
configASSERT(rc == pdPASS);
|
|
|
|
// 3) Agora é seguro registrar ISR do botão
|
|
button_init();
|
|
|
|
#elif APP_AP_CONTROL_MODE == APP_AP_CONTROL_BOOT_WINDOW
|
|
|
|
ESP_LOGI(TAG, "Modo AP automático no arranque ativo");
|
|
|
|
// 2) Inicia AP automaticamente durante os primeiros 3 minutos
|
|
rc = xTaskCreate(
|
|
boot_ap_task_func,
|
|
"boot_ap_task",
|
|
4 * 1024,
|
|
NULL,
|
|
3,
|
|
NULL);
|
|
configASSERT(rc == pdPASS);
|
|
|
|
#else
|
|
#error "APP_AP_CONTROL_MODE inválido"
|
|
#endif
|
|
|
|
// 4) Task auxiliar para eventos Wi-Fi
|
|
rc = xTaskCreate(
|
|
wifi_event_task_func,
|
|
"wifi_event_task",
|
|
8 * 1024,
|
|
NULL,
|
|
3,
|
|
NULL);
|
|
configASSERT(rc == pdPASS);
|
|
}
|
|
|
|
// === Fim de: main/main.c ===
|