Files
chargeflow/projeto_parte1.c
2025-08-24 11:17:48 +01:00

1329 lines
36 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 ===
#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 "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
&notification,
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 <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <inttypes.h>
#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 <math.h>
/* MicroOcpp includes */
#include <mongoose.h>
#include <MicroOcpp_c.h> // C-facade of MicroOcpp
#include <MicroOcppMongooseClient_c.h> // 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(&notificationOutput);
// 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 <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 ===