Files
chargeflow/projeto_parte1.c
2026-01-24 16:56:51 +00:00

1345 lines
35 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.
// === Início de: main/main.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"
#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,
&notification,
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 ===
// === 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 <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#include <math.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_timer.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "ocpp.h"
#include "ocpp_events.h"
#include "evse_error.h"
#include "auth_events.h"
#include "evse_events.h"
#include "evse_state.h"
#include "meter_events.h"
/* MicroOcpp includes */
#include <mongoose.h>
#include <MicroOcpp_c.h> // C-facade of MicroOcpp
#include <MicroOcppMongooseClient_c.h> // WebSocket integration for ESP-IDF
// NEW storage layer
#include "storage_service.h"
#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
{
float vrms[3];
float irms[3];
int32_t watt[3]; // ativo por fase (W)
float frequency;
float power_factor;
float total_energy_Wh;
int32_t sum_watt;
float avg_voltage;
float sum_current;
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;
// valor de oferta (A por conector)
static float s_current_offered_A = 16.0f;
static float getCurrentOffered(void)
{
return s_current_offered_A;
}
// -----------------------------------------------------------------------------
// Storage helpers (robustos)
// -----------------------------------------------------------------------------
#define STORE_TO pdMS_TO_TICKS(800)
#define STORE_FLUSH_TO pdMS_TO_TICKS(2000)
static void storage_init_best_effort(void)
{
esp_err_t e = storage_service_init();
if (e != ESP_OK)
ESP_LOGW(TAG, "storage_service_init failed: %s", esp_err_to_name(e));
}
static esp_err_t store_flush_best_effort(void)
{
esp_err_t e = storage_flush_sync(STORE_FLUSH_TO);
if (e != ESP_OK)
ESP_LOGW(TAG, "storage_flush_sync failed: %s", esp_err_to_name(e));
return e;
}
static esp_err_t store_set_u8_best_effort(const char *ns, const char *key, uint8_t v)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_u8_async(ns, key, v);
if (e == ESP_OK)
return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(pdMS_TO_TICKS(10));
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
static esp_err_t store_set_str_best_effort(const char *ns, const char *key, const char *s)
{
for (int attempt = 0; attempt < 3; ++attempt)
{
esp_err_t e = storage_set_str_async(ns, key, s ? s : "");
if (e == ESP_OK)
return ESP_OK;
if (e == ESP_ERR_TIMEOUT)
{
(void)store_flush_best_effort();
vTaskDelay(portMAX_DELAY);
continue;
}
return e;
}
return ESP_ERR_TIMEOUT;
}
// Lê string de forma segura (buffer grande -> truncagem segura para out)
static esp_err_t store_get_str_safe(const char *ns, const char *key, char *out, size_t out_sz)
{
if (!out || out_sz == 0)
return ESP_ERR_INVALID_ARG;
out[0] = '\0';
char tmp[STORAGE_MAX_VALUE_BYTES + 1];
memset(tmp, 0, sizeof(tmp));
esp_err_t e = storage_get_str_sync(ns, key, tmp, sizeof(tmp), STORE_TO);
if (e == ESP_ERR_NOT_FOUND)
return ESP_OK;
if (e != ESP_OK)
return e;
size_t n = strnlen(tmp, out_sz - 1);
memcpy(out, tmp, n);
out[n] = '\0';
return ESP_OK;
}
// -----------------------------------------------------------------------------
// Task / Main Loop
// -----------------------------------------------------------------------------
static void ocpp_task_func(void *param)
{
(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;
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));
}
}
}
// -----------------------------------------------------------------------------
// Storage GETs
// -----------------------------------------------------------------------------
bool ocpp_get_enabled(void)
{
storage_init_best_effort();
uint8_t value = 0;
esp_err_t err = storage_get_u8_sync(NVS_NAMESPACE, NVS_OCPP_ENABLED, &value, STORE_TO);
if (err == ESP_ERR_NOT_FOUND)
return false;
if (err != ESP_OK)
{
ESP_LOGW(TAG, "storage_get_u8_sync(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';
storage_init_best_effort();
esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_SERVER, value, 64);
if (e != ESP_OK)
{
ESP_LOGW(TAG, "store_get_str_safe(server) failed: %s", esp_err_to_name(e));
value[0] = '\0';
}
}
void ocpp_get_charge_id(char *value /* out, size>=64 */)
{
if (!value)
return;
value[0] = '\0';
storage_init_best_effort();
esp_err_t e = store_get_str_safe(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value, 64);
if (e != ESP_OK)
{
ESP_LOGW(TAG, "store_get_str_safe(charge_id) failed: %s", esp_err_to_name(e));
value[0] = '\0';
}
}
// -----------------------------------------------------------------------------
// Storage SETs
// -----------------------------------------------------------------------------
void ocpp_set_enabled(bool value)
{
storage_init_best_effort();
ESP_LOGI(TAG, "set enabled %d", value);
esp_err_t e = store_set_u8_best_effort(NVS_NAMESPACE, NVS_OCPP_ENABLED, value ? 1 : 0);
if (e != ESP_OK)
{
ESP_LOGE(TAG, "store_set_u8_best_effort(enabled) failed: %s", esp_err_to_name(e));
return;
}
(void)store_flush_best_effort();
enabled = value;
}
void ocpp_set_server(char *value)
{
storage_init_best_effort();
ESP_LOGI(TAG, "set server %s", value ? value : "(null)");
esp_err_t e = store_set_str_best_effort(NVS_NAMESPACE, NVS_OCPP_SERVER, value ? value : "");
if (e != ESP_OK)
{
ESP_LOGE(TAG, "store_set_str_best_effort(server) failed: %s", esp_err_to_name(e));
return;
}
(void)store_flush_best_effort();
}
void ocpp_set_charge_id(char *value)
{
storage_init_best_effort();
ESP_LOGI(TAG, "set charge_id %s", value ? value : "(null)");
esp_err_t e = store_set_str_best_effort(NVS_NAMESPACE, NVS_OCPP_CHARGE_ID, value ? value : "");
if (e != ESP_OK)
{
ESP_LOGE(TAG, "store_set_str_best_effort(charge_id) failed: %s", esp_err_to_name(e));
return;
}
(void)store_flush_best_effort();
}
// -----------------------------------------------------------------------------
// Event handlers (AUTH / EVSE / METER)
// -----------------------------------------------------------------------------
static void ocpp_on_auth_verify(void *arg, esp_event_base_t base, int32_t id, void *event_data)
{
(void)arg;
(void)base;
(void)id;
const auth_tag_verify_event_t *rq = (const auth_tag_verify_event_t *)event_data;
if (!rq)
return;
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);
if (!enabled || g_ocpp_conn == NULL)
{
ESP_LOGW(TAG, "OCPP not ready (enabled=%d, conn=%p) ignoring verify",
enabled, (void *)g_ocpp_conn);
return;
}
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)
{
(void)arg;
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;
break;
case EVSE_STATE_EVENT_FAULT:
default:
s_ev_ready = false;
break;
}
}
static void evse_enable_available_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
(void)arg;
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)
{
(void)arg;
if (base != METER_EVENT || id != METER_EVENT_DATA_READY || !data)
return;
const meter_event_data_t *evt = (const meter_event_data_t *)data;
if (!evt->source || strcmp(evt->source, "EVSE") != 0)
return;
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_Wh = evt->total_energy;
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;
}
bool setEvReadyInput(void)
{
return s_ev_ready;
}
bool setEvseReadyInput(void)
{
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 wh = 0.0f;
bool have = false;
portENTER_CRITICAL(&s_meter_mux);
have = s_meter.have_data;
if (have)
wh = s_meter.total_energy_Wh;
portEXIT_CRITICAL(&s_meter_mux);
if (!have)
ESP_LOGW(TAG, "[METER] EnergyMeterInput: no data (return 0)");
else
ESP_LOGD(TAG, "[METER] EnergyMeterInput: (%.1f Wh)", wh);
return wh;
}
int setEnergyInput(void)
{
float wh = setEnergyMeterInput();
int wh_i = (int)lrintf((double)wh);
ESP_LOGD(TAG, "[METER] EnergyInput: %.1f Wh", wh);
return wh_i;
}
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();
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)
{
(void)transaction;
ESP_LOGI(TAG, "TxNotification: %d", txNotification);
switch (txNotification)
{
case Authorized:
ESP_LOGI(TAG, "Authorized");
esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTHORIZED, NULL, 0, portMAX_DELAY);
break;
case AuthorizationRejected:
ESP_LOGI(TAG, "AuthorizationRejected");
esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_REJECTED, NULL, 0, portMAX_DELAY);
break;
case AuthorizationTimeout:
ESP_LOGI(TAG, "AuthorizationTimeout");
esp_event_post(OCPP_EVENTS, OCPP_EVENT_AUTH_TIMEOUT, NULL, 0, portMAX_DELAY);
break;
case ReservationConflict:
ESP_LOGI(TAG, "ReservationConflict");
break;
case ConnectionTimeout:
ESP_LOGI(TAG, "ConnectionTimeout");
break;
case DeAuthorized:
ESP_LOGI(TAG, "DeAuthorized");
break;
case RemoteStart:
ESP_LOGI(TAG, "RemoteStart");
esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_START, NULL, 0, portMAX_DELAY);
break;
case RemoteStop:
ESP_LOGI(TAG, "RemoteStop");
esp_event_post(OCPP_EVENTS, OCPP_EVENT_REMOTE_STOP, NULL, 0, portMAX_DELAY);
break;
case StartTx:
ESP_LOGI(TAG, "StartTx");
esp_event_post(OCPP_EVENTS, OCPP_EVENT_START_TX, NULL, 0, portMAX_DELAY);
break;
case StopTx:
ESP_LOGI(TAG, "StopTx");
esp_event_post(OCPP_EVENTS, OCPP_EVENT_STOP_TX, NULL, 0, portMAX_DELAY);
break;
}
}
bool ocpp_is_connected(void)
{
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;
}
// -----------------------------------------------------------------------------
// Start / Stop OCPP
// -----------------------------------------------------------------------------
void ocpp_start(void)
{
ESP_LOGI(TAG, "Starting OCPP");
if (ocpp_task != NULL)
{
ESP_LOGW(TAG, "OCPP already running");
return;
}
storage_init_best_effort();
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;
}
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,
charge_id,
"",
"",
fsopt);
if (!g_ocpp_conn)
{
ESP_LOGE(TAG, "ocpp_makeConnection failed");
mg_mgr_free(&mgr);
return;
}
//chargePointModel: "EPower M1"
//chargePointVendor: "Plixin"
//firmwareVersion: "FW-PLXV1.0"
//chargePointSerialNumber: "SN001"
ocpp_initialize(g_ocpp_conn, "EPower M1", "Plixin", fsopt, false);
ocpp_setEvReadyInput(&setEvReadyInput);
ocpp_setEvseReadyInput(&setEvseReadyInput);
ocpp_setConnectorPluggedInput(&setConnectorPluggedInput);
ocpp_setOnResetExecute(&OnResetExecute);
ocpp_setTxNotificationOutput(&notificationOutput);
ocpp_setStopTxReadyInput(&setStopTxReadyInput);
ocpp_setOnResetNotify(&setOnResetNotify);
ocpp_setEnergyMeterInput(&setEnergyInput);
ocpp_addMeterValueInputFloat(&setCurrentInput, "Current.Import", "A", NULL, NULL);
ocpp_addMeterValueInputFloat(&getCurrentOffered, "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);
xTaskCreate(ocpp_task_func, "ocpp_task", 5 * 1024, NULL, 3, &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");
}
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));
}
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 <stdbool.h>
#include <stdint.h>
#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 <stdint.h>
#include <stdbool.h>
#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 ===