// === 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" #define AP_CONNECTION_TIMEOUT 120000 #define RESET_HOLD_TIME 30000 // ms #define DEBOUNCE_TIME_MS 50 #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 // wifi_ap_stop(); 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(); } } // 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"); nvs_flash_erase(); 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)); } // // Inicialização dos módulos do sistema (SEM botão) // 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(); scheduler_init(); protocols_init(); loadbalancer_init(); evse_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()); ESP_ERROR_CHECK(gpio_install_isr_service(0)); board_config_load(); // 1) Inicia todos os módulos (inclui Wi-Fi, EVSE, etc.) init_modules(); // 2) Cria a task que recebe notificações do botão BaseType_t rc; 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(); // 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 ===