// === Início de: main/main.c === #include #include #include #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 ===