. // === 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 "logger.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" #define EVSE_MANAGER_TICK_PERIOD_MS 1000 #define AP_CONNECTION_TIMEOUT 120000 #define RESET_HOLD_TIME 30000 #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; static TickType_t press_tick = 0; static TickType_t last_interrupt_tick = 0; static bool pressed = false; // // 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, total, used); else ESP_LOGE(TAG, "Failed to get SPIFFS info: %s", 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) { EventBits_t mode_bits; for (;;) { // Wait indefinitely until either AP or STA mode is entered mode_bits = xEventGroupWaitBits( wifi_event_group, WIFI_AP_MODE_BIT | WIFI_STA_MODE_BIT, pdFALSE, // do not clear bits on exit pdFALSE, // wait for any bit portMAX_DELAY); if (mode_bits & WIFI_AP_MODE_BIT) { // We're in AP mode: wait for a client to connect within the timeout if (xEventGroupWaitBits( wifi_event_group, WIFI_AP_CONNECTED_BIT, pdFALSE, pdFALSE, pdMS_TO_TICKS(AP_CONNECTION_TIMEOUT)) & WIFI_AP_CONNECTED_BIT) { // Once connected, block until the client disconnects xEventGroupWaitBits( wifi_event_group, WIFI_AP_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY); } else { // Timeout expired with no client—optionally stop the AP if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT) { // wifi_ap_stop(); } } } else if (mode_bits & WIFI_STA_MODE_BIT) { // We're in STA mode: block until disconnected from the AP xEventGroupWaitBits( wifi_event_group, WIFI_STA_DISCONNECTED_BIT, pdFALSE, pdFALSE, portMAX_DELAY); } // Prevent this task from hogging the CPU when idle // vTaskDelay(pdMS_TO_TICKS(10)); } } // // Button press handler // static void handle_button_press(void) { // If not already in AP mode, start it if (!(xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)) { ESP_LOGI(TAG, "Starting Wi-Fi AP mode"); wifi_ap_start(); } } // Task to handle button press/release notifications static void user_input_task_func(void *param) { uint32_t notification; for (;;) { // Wait for notification bits from ISR if (xTaskNotifyWait( 0, // do not clear any bits on entry UINT32_MAX, // clear all bits on exit ¬ification, portMAX_DELAY)) { // Handle button press event if (notification & PRESS_BIT) { press_tick = xTaskGetTickCount(); pressed = true; ESP_LOGI(TAG, "Button Pressed"); handle_button_press(); // só aqui } if ((notification & RELEASED_BIT) && pressed) { pressed = false; TickType_t held = xTaskGetTickCount() - press_tick; ESP_LOGI(TAG, "Button Released (held %u ms)", (unsigned)pdTICKS_TO_MS(held)); if (held >= pdMS_TO_TICKS(RESET_HOLD_TIME)) { ESP_LOGW(TAG, "Long press: erasing NVS + reboot"); nvs_flash_erase(); esp_restart(); } } } } } // ISR for button GPIO interrupt (active-low) static void IRAM_ATTR button_isr_handler(void *arg) { BaseType_t higher_task_woken = pdFALSE; TickType_t now = xTaskGetTickCountFromISR(); // Debounce: ignore interrupts occurring too close together if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS)) { return; } last_interrupt_tick = now; // Read GPIO level: 0 = button pressed, 1 = button released int level = gpio_get_level(board_config.button_wifi_gpio); if (level == 0) { // Notify task: button pressed xTaskNotifyFromISR( user_input_task, PRESS_BIT, eSetBits, &higher_task_woken); } else { // Notify task: button released xTaskNotifyFromISR( user_input_task, RELEASED_BIT, eSetBits, &higher_task_woken); } // Yield to higher priority task if unblocked 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 // static void init_modules(void) { peripherals_init(); wifi_ini(); // api_init(); buzzer_init(); ESP_ERROR_CHECK(rest_server_init("/data")); protocols_init(); evse_manager_init(); evse_init(); // Cria a task para FSM button_init(); auth_init(); loadbalancer_init(); meter_manager_init(); meter_manager_start(); evse_link_init(); ocpp_start(); // wifi_ap_start(); // Outros módulos (descomente conforme necessário) // meter_init(); // ocpp_start(); // orno_modbus_start(); // currentshaper_start(); // initWiegand(); // meter_zigbee_start(); // master_sync_start(); // slave_sync_start(); } // // Função principal do firmware // void app_main(void) { logger_init(); esp_log_set_vprintf(logger_vprintf); 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(); init_modules(); xTaskCreate(wifi_event_task_func, "wifi_event_task", 8 * 1024, NULL, 3, NULL); xTaskCreate(user_input_task_func, "user_input_task", 4 * 1024, NULL, 3, &user_input_task); } // === Fim de: main/main.c === // === Início de: components/ocpp/src/ocpp_events.c === #include "ocpp_events.h" /* Define a base, como em components/auth/src/auth_events.c */ ESP_EVENT_DEFINE_BASE(OCPP_EVENTS); // === Fim de: components/ocpp/src/ocpp_events.c === // === Início de: components/ocpp/src/ocpp.c === // components/ocpp/src/ocpp.c #include #include #include #include #include "esp_log.h" #include "esp_err.h" #include "ocpp.h" #include "ocpp_events.h" #include "esp_wifi.h" #include "nvs.h" #include "nvs_flash.h" #include "evse_error.h" #include "auth_events.h" #include "evse_events.h" #include "evse_state.h" #include "meter_events.h" #include "esp_timer.h" #include /* MicroOcpp includes */ #include #include // C-facade of MicroOcpp #include // WebSocket integration for ESP-IDF #define NVS_NAMESPACE "ocpp" #define NVS_OCPP_ENABLED "enabled" #define NVS_OCPP_SERVER "ocpp_server" #define NVS_OCPP_CHARGE_ID "charge_id" static const char *TAG = "ocpp"; static bool enabled = false; static TaskHandle_t ocpp_task = NULL; static struct mg_mgr mgr; // Event manager static OCPP_Connection *g_ocpp_conn = NULL; // Para shutdown limpo static esp_event_handler_instance_t s_auth_verify_inst = NULL; // Flags refletindo o estado do EVSE (atualizadas por eventos) static volatile bool s_ev_plugged = false; static volatile bool s_ev_ready = false; static esp_event_handler_instance_t s_evse_state_inst = NULL; // Flags de config (vindas de EVSE_EVENTS) static volatile bool s_evse_enabled = true; static volatile bool s_evse_available = true; static esp_event_handler_instance_t s_evse_enable_inst = NULL; static esp_event_handler_instance_t s_evse_available_inst = NULL; // --- cache de medições vindas de METER_EVENT_DATA_READY --- typedef struct { // dados por fase float vrms[3]; float irms[3]; int32_t watt[3]; // ativo por fase (W) float frequency; float power_factor; // acumulados float total_energy_kWh; // derivados práticos int32_t sum_watt; // soma das 3 fases float avg_voltage; // média das 3 fases float sum_current; // soma das 3 fases // flag de validade bool have_data; } ocpp_meter_cache_t; static ocpp_meter_cache_t s_meter = {0}; static portMUX_TYPE s_meter_mux = portMUX_INITIALIZER_UNLOCKED; static esp_event_handler_instance_t s_meter_inst = NULL; /* ========================= * Task / Main Loop * ========================= * */ static void ocpp_task_func(void *param) { while (true) { if (enabled) { mg_mgr_poll(&mgr, 100); ocpp_loop(); bool operative = ocpp_isOperative(); if (operative != s_evse_enabled) { s_evse_enabled = operative; // >>> enviar OCPP_EVENT (remoto → local) ocpp_operative_event_t ev = { .operative = operative, .timestamp_us = esp_timer_get_time()}; esp_event_post(OCPP_EVENTS, OCPP_EVENT_OPERATIVE_UPDATED, &ev, sizeof(ev), portMAX_DELAY); ESP_LOGI(TAG, "[OCPP] ChangeAvailability remoto → operative=%d", (int)operative); } } else { vTaskDelay(pdMS_TO_TICKS(500)); } } } /* ========================= * NVS GETs * ========================= */ bool ocpp_get_enabled(void) { nvs_handle_t h; esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); if (err == ESP_ERR_NVS_NOT_FOUND) { // namespace ainda não existe -> default: disabled return false; } if (err != ESP_OK) { ESP_LOGW(TAG, "nvs_open(%s) RO failed: %s", NVS_NAMESPACE, esp_err_to_name(err)); return false; } uint8_t value = 0; err = nvs_get_u8(h, NVS_OCPP_ENABLED, &value); nvs_close(h); if (err == ESP_ERR_NVS_NOT_FOUND) return false; // default if (err != ESP_OK) { ESP_LOGW(TAG, "nvs_get_u8(enabled) failed: %s", esp_err_to_name(err)); return false; } return value != 0; } void ocpp_get_server(char *value /* out, size>=64 */) { if (!value) return; value[0] = '\0'; nvs_handle_t h; esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); if (err == ESP_ERR_NVS_NOT_FOUND) { // namespace ainda não existe -> default: "" return; } if (err != ESP_OK) { ESP_LOGW(TAG, "nvs_open(%s) RO failed: %s", NVS_NAMESPACE, esp_err_to_name(err)); return; } size_t len = 64; err = nvs_get_str(h, NVS_OCPP_SERVER, value, &len); nvs_close(h); if (err == ESP_ERR_NVS_NOT_FOUND) { value[0] = '\0'; return; } if (err != ESP_OK) { ESP_LOGW(TAG, "nvs_get_str(server) failed: %s", esp_err_to_name(err)); value[0] = '\0'; } } void ocpp_get_charge_id(char *value /* out, size>=64 */) { if (!value) return; value[0] = '\0'; nvs_handle_t h; esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &h); if (err == ESP_ERR_NVS_NOT_FOUND) { // namespace ainda não existe -> default: "" return; } if (err != ESP_OK) { ESP_LOGW(TAG, "nvs_open(%s) RO failed: %s", NVS_NAMESPACE, esp_err_to_name(err)); return; } size_t len = 64; err = nvs_get_str(h, NVS_OCPP_CHARGE_ID, value, &len); nvs_close(h); if (err == ESP_ERR_NVS_NOT_FOUND) { value[0] = '\0'; return; } if (err != ESP_OK) { ESP_LOGW(TAG, "nvs_get_str(charge_id) failed: %s", esp_err_to_name(err)); value[0] = '\0'; } } /* ========================= * NVS SETs * ========================= */ // --- SETTERS: RW (cria o namespace na 1ª escrita), commit e fecha --- void ocpp_set_enabled(bool value) { nvs_handle_t h; esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); if (err != ESP_OK) { ESP_LOGE(TAG, "nvs_open(%s) RW failed: %s", NVS_NAMESPACE, esp_err_to_name(err)); return; } ESP_LOGI(TAG, "set enabled %d", value); ESP_ERROR_CHECK(nvs_set_u8(h, NVS_OCPP_ENABLED, value ? 1 : 0)); ESP_ERROR_CHECK(nvs_commit(h)); nvs_close(h); enabled = value; } void ocpp_set_server(char *value) { nvs_handle_t h; esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); if (err != ESP_OK) { ESP_LOGE(TAG, "nvs_open(%s) RW failed: %s", NVS_NAMESPACE, esp_err_to_name(err)); return; } ESP_LOGI(TAG, "set server %s", value ? value : "(null)"); ESP_ERROR_CHECK(nvs_set_str(h, NVS_OCPP_SERVER, value ? value : "")); ESP_ERROR_CHECK(nvs_commit(h)); nvs_close(h); } void ocpp_set_charge_id(char *value) { nvs_handle_t h; esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &h); if (err != ESP_OK) { ESP_LOGE(TAG, "nvs_open(%s) RW failed: %s", NVS_NAMESPACE, esp_err_to_name(err)); return; } ESP_LOGI(TAG, "set charge_id %s", value ? value : "(null)"); ESP_ERROR_CHECK(nvs_set_str(h, NVS_OCPP_CHARGE_ID, value ? value : "")); ESP_ERROR_CHECK(nvs_commit(h)); nvs_close(h); } static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, void *event_data) { const auth_tag_verify_event_t *rq = (const auth_tag_verify_event_t *)event_data; if (!rq) return; // Sanitizar/copiar a idTag char idtag[AUTH_TAG_MAX_LEN]; if (rq->tag[0] == '\0') { strncpy(idtag, "IDTAG", sizeof(idtag)); idtag[sizeof(idtag) - 1] = '\0'; } else { strncpy(idtag, rq->tag, sizeof(idtag) - 1); idtag[sizeof(idtag) - 1] = '\0'; } ESP_LOGI(TAG, "AUTH_EVENT_TAG_VERIFY: tag=%s req_id=%u", idtag, (unsigned)rq->req_id); // Se não está pronto, apenas regista e sai (podes adaptar conforme política) if (!enabled || g_ocpp_conn == NULL) { ESP_LOGW(TAG, "OCPP not ready (enabled=%d, conn=%p) – ignoring verify", enabled, (void *)g_ocpp_conn); return; } // Regra pedida: // - se já existe transação/charge em andamento -> terminar // - senão -> iniciar com a IDTAG recebida if (ocpp_isTransactionActive()) { ESP_LOGI(TAG, "Transaction active -> ocpp_end_transaction(\"%s\")", idtag); ocpp_endTransaction(idtag, "Local"); } else { ESP_LOGI(TAG, "No active transaction -> ocpp_begin_transaction(\"%s\")", idtag); ocpp_beginTransaction(idtag); } } static void evse_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 event received: state = %d", (int)evt->state); switch (evt->state) { case EVSE_STATE_EVENT_IDLE: s_ev_plugged = false; s_ev_ready = false; break; case EVSE_STATE_EVENT_WAITING: s_ev_plugged = true; s_ev_ready = false; break; case EVSE_STATE_EVENT_CHARGING: s_ev_plugged = true; s_ev_ready = true; // EV está a pedir/receber energia break; case EVSE_STATE_EVENT_FAULT: default: // em falha, considera não pronto (mantém plugged se quiseres) s_ev_ready = false; break; } } static void evse_enable_available_handler(void *arg, esp_event_base_t base, int32_t id, void *data) { if (base != EVSE_EVENTS || data == NULL) return; if (id == EVSE_EVENT_ENABLE_UPDATED) { const evse_enable_event_data_t *e = (const evse_enable_event_data_t *)data; s_evse_enabled = e->enabled; ESP_LOGI(TAG, "[EVSE] ENABLE_UPDATED: enabled=%d (ts=%lld)", (int)e->enabled, (long long)e->timestamp_us); return; } if (id == EVSE_EVENT_AVAILABLE_UPDATED) { const evse_available_event_data_t *e = (const evse_available_event_data_t *)data; s_evse_available = e->available; ESP_LOGI(TAG, "[EVSE] AVAILABLE_UPDATED: available=%d (ts=%lld)", (int)e->available, (long long)e->timestamp_us); return; } } static void on_meter_event(void *arg, esp_event_base_t base, int32_t id, void *data) { if (base != METER_EVENT || id != METER_EVENT_DATA_READY || !data) return; const meter_event_data_t *evt = (const meter_event_data_t *)data; // Só queremos o medidor do EVSE (não o GRID) if (!evt->source || strcmp(evt->source, "EVSE") != 0) return; // Derivados simples int32_t sum_w = (int32_t)evt->watt[0] + (int32_t)evt->watt[1] + (int32_t)evt->watt[2]; float avg_v = (evt->vrms[0] + evt->vrms[1] + evt->vrms[2]) / 3.0f; float sum_i = evt->irms[0] + evt->irms[1] + evt->irms[2]; portENTER_CRITICAL(&s_meter_mux); memcpy(s_meter.vrms, evt->vrms, sizeof(s_meter.vrms)); memcpy(s_meter.irms, evt->irms, sizeof(s_meter.irms)); s_meter.watt[0] = evt->watt[0]; s_meter.watt[1] = evt->watt[1]; s_meter.watt[2] = evt->watt[2]; s_meter.frequency = evt->frequency; s_meter.power_factor = evt->power_factor; s_meter.total_energy_kWh = evt->total_energy; // já vem em kWh segundo o teu .h s_meter.sum_watt = sum_w; s_meter.avg_voltage = avg_v; s_meter.sum_current = sum_i; s_meter.have_data = true; portEXIT_CRITICAL(&s_meter_mux); } /* ========================= * MicroOCPP Inputs/CBs * ========================= */ bool setConnectorPluggedInput(void) { return s_ev_plugged; // EV fisicamente ligado } bool setEvReadyInput(void) { return s_ev_ready; // EV pede / pronto a carregar } bool setEvseReadyInput(void) { // EVSE autorizado / operacional return s_evse_enabled && s_evse_available; } float setPowerMeterInput(void) { int32_t w = 0; bool have = false; portENTER_CRITICAL(&s_meter_mux); have = s_meter.have_data; if (have) w = s_meter.sum_watt; portEXIT_CRITICAL(&s_meter_mux); if (!have) { ESP_LOGW(TAG, "[METER] PowerMeterInput: no data (return 0)"); } else { ESP_LOGD(TAG, "[METER] PowerMeterInput: %" PRId32 " W", w); } return (float)w; } float setEnergyMeterInput(void) { float kwh = 0.0f; bool have = false; portENTER_CRITICAL(&s_meter_mux); have = s_meter.have_data; if (have) kwh = s_meter.total_energy_kWh; portEXIT_CRITICAL(&s_meter_mux); float wh = kwh * 1000.0f; if (!have) { ESP_LOGW(TAG, "[METER] EnergyMeterInput: no data (return 0)"); } else { ESP_LOGD(TAG, "[METER] EnergyMeterInput: %.3f kWh (%.1f Wh)", kwh, wh); } return wh; // agora devolve Wh } int setEnergyInput(void) { float energy_kWh = setEnergyMeterInput(); // kWh int wh = (int)lrintf((double)energy_kWh * 1000.0); // Wh arredondado ESP_LOGD(TAG, "[METER] EnergyInput: %.3f kWh -> %d Wh", energy_kWh, wh); return wh; } float setCurrentInput(void) { float a = 0.0f; bool have = false; portENTER_CRITICAL(&s_meter_mux); have = s_meter.have_data; if (have) a = s_meter.sum_current; portEXIT_CRITICAL(&s_meter_mux); if (!have) { ESP_LOGW(TAG, "[METER] CurrentInput: no data (return 0)"); } else { ESP_LOGD(TAG, "[METER] CurrentInput: %.2f A (total)", a); } return a; } float setVoltageInput(void) { float v = 0.0f; bool have = false; portENTER_CRITICAL(&s_meter_mux); have = s_meter.have_data; if (have) v = s_meter.avg_voltage; portEXIT_CRITICAL(&s_meter_mux); if (!have) { ESP_LOGW(TAG, "[METER] VoltageInput: no data (return 0)"); } else { ESP_LOGD(TAG, "[METER] VoltageInput: %.1f V (avg)", v); } return v; } float setPowerInput(void) { float w = setPowerMeterInput(); // alias ESP_LOGD(TAG, "[METER] PowerInput: %.1f W", w); return w; } float setTemperatureInput(void) { ESP_LOGD(TAG, "TemperatureInput"); return 16.5f; } void setSmartChargingCurrentOutput(float limit) { ESP_LOGI(TAG, "SmartChargingCurrentOutput: %.0f", limit); } void setSmartChargingPowerOutput(float limit) { ESP_LOGI(TAG, "SmartChargingPowerOutput: %.0f", limit); } void setSmartChargingOutput(float power, float current, int nphases) { ESP_LOGI(TAG, "SmartChargingOutput: P=%.0f W, I=%.0f A, phases=%d", power, current, nphases); } void setGetConfiguration(const char *payload, size_t len) { ESP_LOGI(TAG, "GetConfiguration: %.*s (%u)", (int)len, payload, (unsigned)len); } void setStartTransaction(const char *payload, size_t len) { ESP_LOGI(TAG, "StartTransaction: %.*s (%u)", (int)len, payload, (unsigned)len); } void setChangeConfiguration(const char *payload, size_t len) { ESP_LOGI(TAG, "ChangeConfiguration: %.*s (%u)", (int)len, payload, (unsigned)len); } void OnResetExecute(bool state) { ESP_LOGI(TAG, "OnResetExecute (state=%d)", state); esp_restart(); } bool setOccupiedInput(void) { ESP_LOGD(TAG, "setOccupiedInput"); return false; } bool setStartTxReadyInput(void) { ESP_LOGD(TAG, "setStartTxReadyInput"); return true; } bool setStopTxReadyInput(void) { ESP_LOGD(TAG, "setStopTxReadyInput"); return true; } bool setOnResetNotify(bool value) { ESP_LOGI(TAG, "setOnResetNotify %d", value); return true; } void notificationOutput(OCPP_Transaction *transaction, enum OCPP_TxNotification txNotification) { ESP_LOGI(TAG, "TxNotification: %d", txNotification); switch (txNotification) { case Authorized: ESP_LOGI(TAG, "Authorized"); // TODO: send event ocpp Authorized // evse_authorize(); // Opcional: enviar idTag no payload (se tiveres como obter do transaction) // ocpp_idtag_event_t ev = {0}; // strlcpy(ev.idTag, ocpp_tx_get_idTag(transaction), sizeof(ev.idTag)); // esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, &ev, sizeof(ev), portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, NULL, 0, portMAX_DELAY); break; case AuthorizationRejected: ESP_LOGI(TAG, "AuthorizationRejected"); // TODO: send event ocpp AuthorizationRejected // ocpp_idtag_event_t ev = {0}; // strlcpy(ev.idTag, ocpp_tx_get_idTag(transaction), sizeof(ev.idTag)); // esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, &ev, sizeof(ev), portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, portMAX_DELAY); break; case AuthorizationTimeout: ESP_LOGI(TAG, "AuthorizationTimeout"); // TODO: send event ocpp AuthorizationTimeout esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_TIMEOUT, NULL, 0, portMAX_DELAY); break; case ReservationConflict: ESP_LOGI(TAG, "ReservationConflict"); // TODO: send event ocpp ReservationConflict // (Se quiseres, cria um ID específico no enum e publica aqui) break; case ConnectionTimeout: ESP_LOGI(TAG, "ConnectionTimeout"); // TODO: send event ocpp ConnectionTimeout // (Se quiseres, cria um ID específico no enum e publica aqui) break; case DeAuthorized: ESP_LOGI(TAG, "DeAuthorized"); // TODO: send event ocpp DeAuthorized // TODO: adapt to the new interface // evse_set_authorized(false); // evse_set_limit_reached(2); // Poderias mapear para AUTH_REJECTED ou STOP_TX por política da aplicação: // esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, portMAX_DELAY); break; case RemoteStart: ESP_LOGI(TAG, "RemoteStart"); // TODO: send event ocpp RemoteStart // ocpp_idtag_event_t ev = {0}; // strlcpy(ev.idTag, ocpp_tx_get_idTag(transaction), sizeof(ev.idTag)); // esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, &ev, sizeof(ev), portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, NULL, 0, portMAX_DELAY); break; case RemoteStop: ESP_LOGI(TAG, "RemoteStop"); // TODO: send event ocpp RemoteStop esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_STOP, NULL, 0, portMAX_DELAY); break; case StartTx: ESP_LOGI(TAG, "StartTx"); // TODO: send event ocpp StartTx // ocpp_tx_event_t tx = { .tx_id = ocpp_tx_get_id(transaction) }; // esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, &tx, sizeof(tx), portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, NULL, 0, portMAX_DELAY); break; case StopTx: ESP_LOGI(TAG, "StopTx"); // TODO: send event ocpp StopTx // TODO: adapt to the new interface // evse_set_authorized(false); // evse_set_limit_reached(2); // ocpp_reason_event_t rs = {0}; // strlcpy(rs.reason, "Local", sizeof(rs.reason)); // esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, &rs, sizeof(rs), portMAX_DELAY); esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, NULL, 0, portMAX_DELAY); break; } } // Estado de conexão simples do OCPP bool ocpp_is_connected(void) { // Se quiser algo mais preciso (WS aberto), substitui por uma chamada da MicroOcpp, // mas como fallback isto já evita o undefined symbol. return g_ocpp_conn != NULL; } const char *addErrorCodeInput(void) { const char *ptr = NULL; uint32_t error = evse_get_error(); if (error & EVSE_ERR_PILOT_FAULT_BIT) ptr = "InternalError"; else if (error & EVSE_ERR_DIODE_SHORT_BIT) ptr = "InternalError"; else if (error & EVSE_ERR_LOCK_FAULT_BIT) ptr = "ConnectorLockFailure"; else if (error & EVSE_ERR_UNLOCK_FAULT_BIT) ptr = "ConnectorLockFailure"; else if (error & EVSE_ERR_RCM_TRIGGERED_BIT) ptr = "OtherError"; else if (error & EVSE_ERR_RCM_SELFTEST_FAULT_BIT) ptr = "OtherError"; else if (error & EVSE_ERR_TEMPERATURE_HIGH_BIT) ptr = "HighTemperature"; else if (error & EVSE_ERR_TEMPERATURE_FAULT_BIT) ptr = "OtherError"; return ptr; // NULL => sem erro } /* ========================= * Start / Stop OCPP * ========================= */ void ocpp_start(void) { ESP_LOGI(TAG, "Starting OCPP"); if (ocpp_task != NULL) { ESP_LOGW(TAG, "OCPP already running"); return; } enabled = ocpp_get_enabled(); if (!enabled) { ESP_LOGW(TAG, "OCPP disabled"); return; } char serverstr[64] = {0}; ocpp_get_server(serverstr); if (serverstr[0] == '\0') { ESP_LOGW(TAG, "No OCPP server configured. Skipping connection."); return; } char charge_id[64] = {0}; ocpp_get_charge_id(charge_id); if (charge_id[0] == '\0') { ESP_LOGW(TAG, "No OCPP charge_id configured. Skipping connection."); return; } /* Inicializar Mongoose + MicroOcpp */ mg_mgr_init(&mgr); mg_log_set(MG_LL_ERROR); struct OCPP_FilesystemOpt fsopt = {.use = true, .mount = true, .formatFsOnFail = true}; g_ocpp_conn = ocpp_makeConnection(&mgr, serverstr, /* ex: ws://host:port/OCPP16/... */ charge_id, /* ChargeBoxId / identity */ "", "", fsopt); if (!g_ocpp_conn) { ESP_LOGE(TAG, "ocpp_makeConnection failed"); mg_mgr_free(&mgr); return; } ocpp_initialize(g_ocpp_conn, "EPower M1", "Plixin", fsopt, false); /* Inputs/outputs e callbacks */ ocpp_setEvReadyInput(&setEvReadyInput); ocpp_setEvseReadyInput(&setEvseReadyInput); ocpp_setConnectorPluggedInput(&setConnectorPluggedInput); ocpp_setOnResetExecute(&OnResetExecute); ocpp_setTxNotificationOutput(¬ificationOutput); // ocpp_setStartTxReadyInput(&setStartTxReadyInput); ocpp_setStopTxReadyInput(&setStopTxReadyInput); ocpp_setOnResetNotify(&setOnResetNotify); ocpp_setEnergyMeterInput(&setEnergyInput); // inteiro Wh /* Metering */ ocpp_addMeterValueInputFloat(&setCurrentInput, "Current.Import", "A", NULL, NULL); ocpp_addMeterValueInputFloat(&setCurrentInput, "Current.Offered", "A", NULL, NULL); ocpp_addMeterValueInputFloat(&setVoltageInput, "Voltage", "V", NULL, NULL); ocpp_addMeterValueInputFloat(&setTemperatureInput, "Temperature", "Celsius", NULL, NULL); ocpp_addMeterValueInputFloat(&setPowerMeterInput, "Power.Active.Import", "W", NULL, NULL); ocpp_addMeterValueInputFloat(&setEnergyMeterInput, "Energy.Active.Import.Register", "Wh", NULL, NULL); ocpp_addErrorCodeInput(&addErrorCodeInput); /* Task */ xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 5, &ocpp_task); if (!s_auth_verify_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_register( AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY, &ocpp_on_auth_verify, NULL, &s_auth_verify_inst)); ESP_LOGI(TAG, "Registered AUTH_EVENT_TAG_VERIFY listener"); } // ouvir mudanças de estado do EVSE if (!s_evse_state_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_register( EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &evse_event_handler, NULL, &s_evse_state_inst)); } // ouvir mudanças de ENABLE / AVAILABLE do EVSE (Local → OCPP) if (!s_evse_enable_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_register( EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED, &evse_enable_available_handler, NULL, &s_evse_enable_inst)); } if (!s_evse_available_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_register( EVSE_EVENTS, EVSE_EVENT_AVAILABLE_UPDATED, &evse_enable_available_handler, NULL, &s_evse_available_inst)); } if (!s_meter_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_register( METER_EVENT, METER_EVENT_DATA_READY, &on_meter_event, NULL, &s_meter_inst)); ESP_LOGI(TAG, "Registered METER_EVENT_DATA_READY listener (EVSE source)"); } } void ocpp_stop(void) { ESP_LOGI(TAG, "Stopping OCPP"); if (ocpp_task) { vTaskDelete(ocpp_task); ocpp_task = NULL; } ocpp_deinitialize(); if (g_ocpp_conn) { ocpp_deinitConnection(g_ocpp_conn); g_ocpp_conn = NULL; } mg_mgr_free(&mgr); if (s_auth_verify_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_unregister( AUTH_EVENTS, AUTH_EVENT_TAG_VERIFY, s_auth_verify_inst)); s_auth_verify_inst = NULL; } if (s_evse_state_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_unregister( EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, s_evse_state_inst)); s_evse_state_inst = NULL; } if (s_evse_enable_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_unregister( EVSE_EVENTS, EVSE_EVENT_ENABLE_UPDATED, s_evse_enable_inst)); s_evse_enable_inst = NULL; } if (s_evse_available_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_unregister( EVSE_EVENTS, EVSE_EVENT_AVAILABLE_UPDATED, s_evse_available_inst)); s_evse_available_inst = NULL; } if (s_meter_inst) { ESP_ERROR_CHECK(esp_event_handler_instance_unregister( METER_EVENT, METER_EVENT_DATA_READY, s_meter_inst)); s_meter_inst = NULL; } } // === Fim de: components/ocpp/src/ocpp.c === // === Início de: components/ocpp/include/ocpp_events.h === #pragma once #include "esp_event.h" #include #include #ifdef __cplusplus extern "C" { #endif /* Base de eventos do OCPP (igual ao padrão usado em auth_events.h) */ ESP_EVENT_DECLARE_BASE(OCPP_EVENTS); /* IDs de eventos do OCPP */ typedef enum { OCPP_EVENT_CONNECTED = 0, // payload: const char* (server URL) – opcional OCPP_EVENT_DISCONNECTED, // payload: NULL OCPP_EVENT_AUTHORIZED, // payload: ocpp_idtag_event_t (opcional) OCPP_EVENT_AUTH_REJECTED, // payload: ocpp_idtag_event_t (opcional) OCPP_EVENT_AUTH_TIMEOUT, // payload: NULL OCPP_EVENT_REMOTE_START, // payload: ocpp_idtag_event_t (opcional) OCPP_EVENT_REMOTE_STOP, // payload: NULL OCPP_EVENT_START_TX, // payload: ocpp_tx_event_t (opcional) OCPP_EVENT_STOP_TX, // payload: ocpp_reason_event_t (opcional) OCPP_EVENT_RESET, // payload: NULL OCPP_EVENT_OPERATIVE_UPDATED } ocpp_event_id_t; /* Limites de strings simples (evita dependência de auth.h) */ #define OCPP_IDTAG_MAX 32 #define OCPP_REASON_MAX 32 /* Payloads opcionais */ typedef struct { char idTag[OCPP_IDTAG_MAX]; } ocpp_idtag_event_t; typedef struct { int tx_id; // se disponível } ocpp_tx_event_t; typedef struct { char reason[OCPP_REASON_MAX]; } ocpp_reason_event_t; // Payload do novo evento typedef struct { bool operative; // true = Operative, false = Inoperative int64_t timestamp_us; // esp_timer_get_time() } ocpp_operative_event_t; #ifdef __cplusplus } #endif // === Fim de: components/ocpp/include/ocpp_events.h === // === Início de: components/ocpp/include/ocpp.h === #ifndef OCPP_H_ #define OCPP_H_ #include #include #ifdef __cplusplus extern "C" { #endif /** * @brief Start OCPP */ void ocpp_start(void); /** * @brief Stop OCPP */ void ocpp_stop(void); /* Config getters / setters */ bool ocpp_get_enabled(void); void ocpp_set_enabled(bool value); void ocpp_get_server(char *value); // buffer >= 64 void ocpp_set_server(char *value); void ocpp_get_charge_id(char *value); // buffer >= 64 void ocpp_set_charge_id(char *value); /* Estado de conexão */ bool ocpp_is_connected(void); #ifdef __cplusplus } #endif #endif /* OCPP_H_ */ // === Fim de: components/ocpp/include/ocpp.h ===