Files
chargeflow/projeto_parte1.c
2025-12-09 11:48:31 +00:00

2273 lines
66 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 "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"
#include "led.h"
#include "scheduler.h"
#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 = NULL;
static TickType_t press_tick = 0;
static volatile 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 (;;)
{
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)
{
xEventGroupWaitBits(
wifi_event_group,
WIFI_AP_DISCONNECTED_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
}
else
{
if (xEventGroupGetBits(wifi_event_group) & WIFI_AP_MODE_BIT)
{
// wifi_ap_stop();
}
}
}
else if (mode_bits & WIFI_STA_MODE_BIT)
{
xEventGroupWaitBits(
wifi_event_group,
WIFI_STA_DISCONNECTED_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
}
}
}
//
// Button press handler
//
static void handle_button_press(void)
{
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 (;;)
{
if (xTaskNotifyWait(
0,
UINT32_MAX,
&notification,
portMAX_DELAY))
{
if (notification & PRESS_BIT)
{
press_tick = xTaskGetTickCount();
pressed = true;
ESP_LOGI(TAG, "Button Pressed");
handle_button_press();
}
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();
if (now - last_interrupt_tick < pdMS_TO_TICKS(DEBOUNCE_TIME_MS))
{
return;
}
last_interrupt_tick = now;
if (user_input_task == NULL)
{
return;
}
int level = gpio_get_level(board_config.button_wifi_gpio);
if (level == 0)
{
xTaskNotifyFromISR(
user_input_task,
PRESS_BIT,
eSetBits,
&higher_task_woken);
}
else
{
xTaskNotifyFromISR(
user_input_task,
RELEASED_BIT,
eSetBits,
&higher_task_woken);
}
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)
{
peripherals_init();
led_init();
wifi_ini();
buzzer_init();
ESP_ERROR_CHECK(rest_server_init("/data"));
protocols_init();
evse_manager_init();
evse_init();
auth_init();
loadbalancer_init();
meter_manager_init();
meter_manager_start();
evse_link_init();
ocpp_start();
scheduler_init();
}
//
// 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();
// 1) cria a task que recebe notificações do botão
xTaskCreate(user_input_task_func, "user_input_task", 4 * 1024, NULL, 3, &user_input_task);
// 2) agora é seguro registrar ISR do botão
button_init();
// 3) inicia o resto do sistema
init_modules();
// 4) tasks auxiliares
xTaskCreate(wifi_event_task_func, "wifi_event_task", 8 * 1024, NULL, 3, NULL);
}
// === Fim de: main/main.c ===
// === Fim de: main/main.c ===
// === Início de: components/rest_api/src/ocpp_api.c ===
// =========================
// ocpp_api.c
// =========================
#include "ocpp.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_http_server.h"
#include "cJSON.h"
#include <string.h>
static const char *TAG = "ocpp_api";
static esp_err_t ocpp_get_status_handler(httpd_req_t *req) {
ESP_LOGD(TAG, "GET /api/v1/ocpp");
httpd_resp_set_type(req, "application/json");
char server[64] = {0};
char charge_id[64] = {0};
ocpp_get_server(server);
ocpp_get_charge_id(charge_id);
cJSON *status = cJSON_CreateObject();
cJSON_AddBoolToObject(status, "connected", ocpp_is_connected());
cJSON_AddStringToObject(status, "server", server);
cJSON_AddStringToObject(status, "charge_id", charge_id);
char *str = cJSON_PrintUnformatted(status);
httpd_resp_sendstr(req, str);
free(str);
cJSON_Delete(status);
return ESP_OK;
}
static esp_err_t ocpp_get_config_handler(httpd_req_t *req) {
ESP_LOGD(TAG, "GET /api/v1/config/ocpp");
httpd_resp_set_type(req, "application/json");
char server[64] = {0};
char charge_id[64] = {0};
bool enabled = ocpp_get_enabled();
ocpp_get_server(server);
ocpp_get_charge_id(charge_id);
cJSON *json = cJSON_CreateObject();
cJSON_AddBoolToObject(json, "enabled", enabled);
cJSON_AddStringToObject(json, "url", server);
cJSON_AddStringToObject(json, "chargeBoxId", charge_id);
char *str = cJSON_PrintUnformatted(json);
httpd_resp_sendstr(req, str);
free(str);
cJSON_Delete(json);
return ESP_OK;
}
static esp_err_t ocpp_post_config_handler(httpd_req_t *req) {
ESP_LOGD(TAG, "POST /api/v1/config/ocpp");
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body");
return ESP_FAIL;
}
buf[len] = '\0';
cJSON *json = cJSON_Parse(buf);
if (!json) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
cJSON *enabled = cJSON_GetObjectItem(json, "enabled");
if (cJSON_IsBool(enabled)) {
ocpp_set_enabled(cJSON_IsTrue(enabled));
}
cJSON *url = cJSON_GetObjectItem(json, "url");
if (cJSON_IsString(url)) {
ocpp_set_server(url->valuestring);
}
cJSON *id = cJSON_GetObjectItem(json, "chargeBoxId");
if (cJSON_IsString(id)) {
ocpp_set_charge_id(id->valuestring);
}
cJSON_Delete(json);
httpd_resp_sendstr(req, "OCPP config atualizada com sucesso");
return ESP_OK;
}
void register_ocpp_handlers(httpd_handle_t server, void *ctx) {
httpd_uri_t status_uri = {
.uri = "/api/v1/ocpp",
.method = HTTP_GET,
.handler = ocpp_get_status_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &status_uri);
httpd_uri_t get_uri = {
.uri = "/api/v1/config/ocpp",
.method = HTTP_GET,
.handler = ocpp_get_config_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &get_uri);
httpd_uri_t post_uri = {
.uri = "/api/v1/config/ocpp",
.method = HTTP_POST,
.handler = ocpp_post_config_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &post_uri);
}
// === Fim de: components/rest_api/src/ocpp_api.c ===
// === Início de: components/rest_api/src/static_file_api.c ===
#include "static_file_api.h"
#include "esp_log.h"
#include <fcntl.h>
#include <string.h>
#include "esp_vfs.h"
static const char *TAG = "static_file_api";
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128)
#define SCRATCH_BUFSIZE (10240)
typedef struct rest_server_context {
char base_path[ESP_VFS_PATH_MAX + 1];
char scratch[SCRATCH_BUFSIZE];
} rest_server_context_t;
#define CHECK_FILE_EXTENSION(filename, ext) \
(strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0)
static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filepath) {
const char *type = "text/plain";
if (CHECK_FILE_EXTENSION(filepath, ".html")) type = "text/html";
else if (CHECK_FILE_EXTENSION(filepath, ".js")) type = "application/javascript";
else if (CHECK_FILE_EXTENSION(filepath, ".css")) type = "text/css";
else if (CHECK_FILE_EXTENSION(filepath, ".png")) type = "image/png";
else if (CHECK_FILE_EXTENSION(filepath, ".ico")) type = "image/x-icon";
else if (CHECK_FILE_EXTENSION(filepath, ".svg")) type = "image/svg+xml";
return httpd_resp_set_type(req, type);
}
static esp_err_t static_get_handler(httpd_req_t *req) {
char filepath[FILE_PATH_MAX];
rest_server_context_t *ctx = (rest_server_context_t *) req->user_ctx;
strlcpy(filepath, ctx->base_path, sizeof(filepath));
if (req->uri[strlen(req->uri) - 1] == '/') {
strlcat(filepath, "/index.html", sizeof(filepath));
} else {
strlcat(filepath, req->uri, sizeof(filepath));
}
int fd = open(filepath, O_RDONLY, 0);
if (fd == -1) {
// fallback para /index.html (SPA)
ESP_LOGW(TAG, "Arquivo não encontrado: %s. Tentando index.html", filepath);
strlcpy(filepath, ctx->base_path, sizeof(filepath));
strlcat(filepath, "/index.html", sizeof(filepath));
fd = open(filepath, O_RDONLY, 0);
if (fd == -1) {
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Arquivo não encontrado");
return ESP_FAIL;
}
}
set_content_type_from_file(req, filepath);
char *chunk = ctx->scratch;
ssize_t read_bytes;
do {
read_bytes = read(fd, chunk, SCRATCH_BUFSIZE);
if (read_bytes == -1) {
ESP_LOGE(TAG, "Erro lendo arquivo: %s", filepath);
close(fd);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao ler arquivo");
return ESP_FAIL;
} else if (read_bytes > 0) {
if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) {
close(fd);
httpd_resp_sendstr_chunk(req, NULL);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao enviar arquivo");
return ESP_FAIL;
}
}
} while (read_bytes > 0);
close(fd);
httpd_resp_send_chunk(req, NULL, 0);
return ESP_OK;
}
void register_static_file_handlers(httpd_handle_t server, void *ctx) {
httpd_uri_t uri = {
.uri = "/*",
.method = HTTP_GET,
.handler = static_get_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &uri);
}
// === Fim de: components/rest_api/src/static_file_api.c ===
// === Início de: components/rest_api/src/evse_link_config_api.c ===
#include "evse_link.h"
#include "evse_link_config_api.h" // new header for these handlers
#include "esp_log.h"
#include "cJSON.h"
static const char *TAG = "link_config_api";
// GET /api/v1/config/link
static esp_err_t link_config_get_handler(httpd_req_t *req) {
bool enabled = evse_link_is_enabled();
uint8_t mode = evse_link_get_mode(); // 0=MASTER,1=SLAVE
uint8_t self_id = evse_link_get_self_id();
ESP_LOGI(TAG, "GET link config: enabled=%d mode=%u id=%u",
enabled, mode, self_id);
httpd_resp_set_type(req, "application/json");
cJSON *root = cJSON_CreateObject();
cJSON_AddBoolToObject (root, "linkEnabled", enabled);
cJSON_AddStringToObject(root, "linkMode",
mode == EVSE_LINK_MODE_MASTER ? "MASTER" : "SLAVE");
cJSON_AddNumberToObject(root, "linkSelfId", self_id);
char *s = cJSON_Print(root);
httpd_resp_sendstr(req, s);
ESP_LOGI(TAG, " payload: %s", s);
free(s);
cJSON_Delete(root);
return ESP_OK;
}
// POST /api/v1/config/link
static esp_err_t link_config_post_handler(httpd_req_t *req) {
char buf[256];
int len = httpd_req_recv(req, buf, sizeof(buf)-1);
if (len <= 0) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body");
return ESP_FAIL;
}
buf[len] = '\0';
ESP_LOGI(TAG, "POST link config: %s", buf);
cJSON *json = cJSON_Parse(buf);
if (!json) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
// linkEnabled
cJSON *j_en = cJSON_GetObjectItem(json, "linkEnabled");
if (j_en && cJSON_IsBool(j_en)) {
evse_link_set_enabled(cJSON_IsTrue(j_en));
ESP_LOGI(TAG, " set enabled = %d", cJSON_IsTrue(j_en));
}
// linkMode
cJSON *j_md = cJSON_GetObjectItem(json, "linkMode");
if (j_md && cJSON_IsString(j_md)) {
const char *m = j_md->valuestring;
if (strcmp(m, "MASTER") == 0) {
evse_link_set_mode(EVSE_LINK_MODE_MASTER);
} else if (strcmp(m, "SLAVE") == 0) {
evse_link_set_mode(EVSE_LINK_MODE_SLAVE);
} else {
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Invalid linkMode (must be MASTER or SLAVE)");
return ESP_FAIL;
}
ESP_LOGI(TAG, " set mode = %s", m);
}
// linkSelfId
cJSON *j_id = cJSON_GetObjectItem(json, "linkSelfId");
if (j_id && cJSON_IsNumber(j_id)) {
int id = j_id->valueint;
if (id < 0 || id > 254) {
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Invalid linkSelfId (0254)");
return ESP_FAIL;
}
evse_link_set_self_id((uint8_t)id);
ESP_LOGI(TAG, " set self_id = %d", id);
}
cJSON_Delete(json);
httpd_resp_sendstr(req, "Link settings updated");
return ESP_OK;
}
void register_link_config_handlers(httpd_handle_t server, void *ctx) {
httpd_uri_t get = {
.uri = "/api/v1/config/link",
.method = HTTP_GET,
.handler = link_config_get_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &get);
httpd_uri_t post = {
.uri = "/api/v1/config/link",
.method = HTTP_POST,
.handler = link_config_post_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &post);
}
// === Fim de: components/rest_api/src/evse_link_config_api.c ===
// === Início de: components/rest_api/src/scheduler_settings_api.c ===
#include "scheduler_settings_api.h"
#include "scheduler.h"
#include "scheduler_types.h"
#include "esp_log.h"
#include "esp_http_server.h"
#include "cJSON.h"
#include <string.h>
#include <ctype.h>
#include <stdio.h> // sscanf, snprintf
static const char *TAG = "scheduler_api";
/* =========================
* Helpers HH:MM <-> minutos
* ========================= */
static bool parse_hhmm(const char *s, uint16_t *out_min)
{
if (!s || !out_min)
return false;
// formato esperado: "HH:MM"
int h = 0, m = 0;
if (sscanf(s, "%d:%d", &h, &m) != 2)
{
return false;
}
if (h < 0 || h > 23 || m < 0 || m > 59)
{
return false;
}
*out_min = (uint16_t)(h * 60 + m);
return true;
}
static void format_hhmm(uint16_t minutes, char *buf, size_t buf_sz)
{
if (!buf || buf_sz < 6)
return;
minutes %= (24 * 60);
int h = minutes / 60;
int m = minutes % 60;
snprintf(buf, buf_sz, "%02d:%02d", h, m);
}
/* =========================
* GET /api/v1/config/scheduler
* ========================= */
static esp_err_t scheduler_config_get_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "GET /api/v1/config/scheduler");
httpd_resp_set_type(req, "application/json");
sched_config_t cfg = scheduler_get_config();
bool allowed_now = scheduler_is_allowed_now();
cJSON *root = cJSON_CreateObject();
if (!root)
{
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "cJSON alloc failed");
return ESP_FAIL;
}
cJSON_AddBoolToObject(root, "enabled", cfg.enabled);
cJSON_AddStringToObject(root, "mode", sched_mode_to_str(cfg.mode));
char buf[8];
format_hhmm(cfg.start_min, buf, sizeof(buf));
cJSON_AddStringToObject(root, "startTime", buf);
format_hhmm(cfg.end_min, buf, sizeof(buf));
cJSON_AddStringToObject(root, "endTime", buf);
cJSON_AddBoolToObject(root, "allowedNow", allowed_now);
char *json_str = cJSON_PrintUnformatted(root);
if (!json_str)
{
cJSON_Delete(root);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "cJSON print failed");
return ESP_FAIL;
}
httpd_resp_sendstr(req, json_str);
free(json_str);
cJSON_Delete(root);
return ESP_OK;
}
/* =========================
* POST /api/v1/config/scheduler
* ========================= */
static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "POST /api/v1/config/scheduler");
// NOTA: para payloads pequenos 512 bytes chega; se quiseres robustez total,
// usa req->content_len e faz um loop com httpd_req_recv.
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0)
{
ESP_LOGE(TAG, "Empty body / recv error");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body");
return ESP_FAIL;
}
buf[len] = '\0';
ESP_LOGI(TAG, "Body: %s", buf);
cJSON *json = cJSON_Parse(buf);
if (!json)
{
ESP_LOGE(TAG, "Invalid JSON");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
// Começa a partir da config atual
sched_config_t cfg = scheduler_get_config();
// enabled
cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled");
if (cJSON_IsBool(j_enabled))
{
cfg.enabled = cJSON_IsTrue(j_enabled);
ESP_LOGI(TAG, " enabled = %d", cfg.enabled);
}
// mode
cJSON *j_mode = cJSON_GetObjectItem(json, "mode");
if (cJSON_IsString(j_mode) && j_mode->valuestring)
{
sched_mode_t m;
if (!sched_mode_from_str(j_mode->valuestring, &m))
{
ESP_LOGW(TAG, "Invalid mode: %s", j_mode->valuestring);
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Invalid mode (use: disabled|simple|weekly)");
return ESP_FAIL;
}
cfg.mode = m;
ESP_LOGI(TAG, " mode = %s", sched_mode_to_str(cfg.mode));
}
// startTime (string "HH:MM")
cJSON *j_start = cJSON_GetObjectItem(json, "startTime");
if (cJSON_IsString(j_start) && j_start->valuestring)
{
uint16_t minutes = 0;
if (!parse_hhmm(j_start->valuestring, &minutes))
{
ESP_LOGW(TAG, "Invalid startTime: %s", j_start->valuestring);
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Invalid startTime (use HH:MM)");
return ESP_FAIL;
}
cfg.start_min = minutes;
ESP_LOGI(TAG, " start_min = %u", (unsigned)cfg.start_min);
}
// endTime (string "HH:MM")
cJSON *j_end = cJSON_GetObjectItem(json, "endTime");
if (cJSON_IsString(j_end) && j_end->valuestring)
{
uint16_t minutes = 0;
if (!parse_hhmm(j_end->valuestring, &minutes))
{
ESP_LOGW(TAG, "Invalid endTime: %s", j_end->valuestring);
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Invalid endTime (use HH:MM)");
return ESP_FAIL;
}
cfg.end_min = minutes;
ESP_LOGI(TAG, " end_min = %u", (unsigned)cfg.end_min);
}
// (Opcional) validações extra:
// exemplo: impedir janela vazia quando ativo
/*
if (cfg.enabled && cfg.mode != SCHED_MODE_DISABLED &&
cfg.start_min == cfg.end_min) {
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"startTime and endTime cannot be equal");
return ESP_FAIL;
}
*/
// Aplica config no módulo scheduler (ele trata de NVS + eventos)
scheduler_set_config(&cfg);
cJSON_Delete(json);
httpd_resp_sendstr(req, "Scheduler config atualizada com sucesso");
return ESP_OK;
}
/* =========================
* Registo dos handlers
* ========================= */
void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx)
{
httpd_uri_t get_uri = {
.uri = "/api/v1/config/scheduler",
.method = HTTP_GET,
.handler = scheduler_config_get_handler,
.user_ctx = ctx};
httpd_register_uri_handler(server, &get_uri);
httpd_uri_t post_uri = {
.uri = "/api/v1/config/scheduler",
.method = HTTP_POST,
.handler = scheduler_config_post_handler,
.user_ctx = ctx};
httpd_register_uri_handler(server, &post_uri);
ESP_LOGI(TAG, "Scheduler REST handlers registered");
}
// === Fim de: components/rest_api/src/scheduler_settings_api.c ===
// === Início de: components/rest_api/src/meters_settings_api.c ===
#include "meters_settings_api.h"
#include "meter_manager.h" // Atualizado para usar o novo manager
#include "meter_events.h"
#include "esp_event.h"
#include "esp_log.h"
#include "cJSON.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include <string.h>
static const char *TAG = "meters_settings_api";
typedef struct
{
meter_event_data_t last_grid;
meter_event_data_t last_evse;
bool has_grid;
bool has_evse;
int64_t ts_grid_us;
int64_t ts_evse_us;
SemaphoreHandle_t mtx;
} meters_cache_t;
static meters_cache_t g_cache = {0};
static void cache_init_once(void)
{
if (!g_cache.mtx)
{
g_cache.mtx = xSemaphoreCreateMutex();
}
}
static void copy_event_locked(const meter_event_data_t *src)
{
if (!src)
return;
if (strcmp(src->source ? src->source : "", "EVSE") == 0)
{
g_cache.last_evse = *src;
g_cache.has_evse = true;
g_cache.ts_evse_us = esp_timer_get_time();
}
else
{
// Default trate como GRID
g_cache.last_grid = *src;
g_cache.has_grid = true;
g_cache.ts_grid_us = esp_timer_get_time();
}
}
// ---------- Event handler (atualiza cache) ----------
static void meters_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data)
{
if (base != METER_EVENT || id != METER_EVENT_DATA_READY || data == NULL)
return;
cache_init_once();
if (xSemaphoreTake(g_cache.mtx, pdMS_TO_TICKS(10)) == pdTRUE)
{
copy_event_locked((const meter_event_data_t *)data);
xSemaphoreGive(g_cache.mtx);
}
}
// ---------- Helpers JSON ----------
static cJSON *meter_event_to_json(const meter_event_data_t *m, int64_t ts_us)
{
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "source", m->source ? m->source : "GRID");
cJSON_AddNumberToObject(root, "frequency", m->frequency);
cJSON_AddNumberToObject(root, "powerFactor", m->power_factor);
cJSON_AddNumberToObject(root, "totalEnergy", m->total_energy);
// timestamp relativo (segundos desde boot) e em micros
cJSON_AddNumberToObject(root, "timestampUs", (double)ts_us);
// arrays
cJSON *vr = cJSON_CreateArray();
for (int i = 0; i < 3; i++)
cJSON_AddItemToArray(vr, cJSON_CreateNumber(m->vrms[i]));
cJSON *ir = cJSON_CreateArray();
for (int i = 0; i < 3; i++)
cJSON_AddItemToArray(ir, cJSON_CreateNumber(m->irms[i]));
cJSON *pw = cJSON_CreateArray();
for (int i = 0; i < 3; i++)
cJSON_AddItemToArray(pw, cJSON_CreateNumber(m->watt[i]));
cJSON_AddItemToObject(root, "vrms", vr);
cJSON_AddItemToObject(root, "irms", ir);
cJSON_AddItemToObject(root, "watt", pw);
return root;
}
// ---------- HTTP GET /api/v1/meters/live ----------
static esp_err_t meters_live_get_handler(httpd_req_t *req)
{
cache_init_once();
char query[64] = {0};
char source[16] = {0};
bool want_grid = true, want_evse = true;
if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK)
{
if (httpd_query_key_value(query, "source", source, sizeof(source)) == ESP_OK)
{
if (strcasecmp(source, "GRID") == 0)
{
want_grid = true;
want_evse = false;
}
else if (strcasecmp(source, "EVSE") == 0)
{
want_grid = false;
want_evse = true;
}
}
}
cJSON *out = cJSON_CreateObject();
cJSON *arr = cJSON_CreateArray();
if (xSemaphoreTake(g_cache.mtx, pdMS_TO_TICKS(50)) == pdTRUE)
{
if (want_grid && g_cache.has_grid)
{
cJSON_AddItemToArray(arr, meter_event_to_json(&g_cache.last_grid, g_cache.ts_grid_us));
}
if (want_evse && g_cache.has_evse)
{
cJSON_AddItemToArray(arr, meter_event_to_json(&g_cache.last_evse, g_cache.ts_evse_us));
}
xSemaphoreGive(g_cache.mtx);
}
cJSON_AddItemToObject(out, "meters", arr);
httpd_resp_set_type(req, "application/json");
char *s = cJSON_PrintUnformatted(out);
httpd_resp_sendstr(req, s ? s : "{\"meters\":[]}");
if (s)
free(s);
cJSON_Delete(out);
return ESP_OK;
}
// ---------- Registro ----------
void register_meters_data_handlers(httpd_handle_t server, void *ctx)
{
// registra event handler para receber amostras
ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, METER_EVENT_DATA_READY, meters_event_handler, NULL));
// endpoint GET
httpd_uri_t live = {
.uri = "/api/v1/meters/live",
.method = HTTP_GET,
.handler = meters_live_get_handler,
.user_ctx = ctx};
httpd_register_uri_handler(server, &live);
ESP_LOGI(TAG, "REST /api/v1/meters/live registrado");
}
// Função para recuperar as configurações dos contadores
static esp_err_t meters_config_get_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "Received GET request for /api/v1/config/meters");
httpd_resp_set_type(req, "application/json");
cJSON *config = cJSON_CreateObject();
// Recuperando as configurações dos contadores
meter_type_t gridmeterType = meter_manager_grid_get_model();
meter_type_t evsemeterType = meter_manager_evse_get_model();
ESP_LOGI(TAG, "Grid meter type: %s", meter_type_to_str(gridmeterType));
ESP_LOGI(TAG, "EVSE meter type: %s", meter_type_to_str(evsemeterType));
// Adicionando os tipos de contadores ao objeto JSON
cJSON_AddStringToObject(config, "gridmeter", meter_type_to_str(gridmeterType));
cJSON_AddStringToObject(config, "evsemeter", meter_type_to_str(evsemeterType));
// Convertendo o objeto JSON para uma string
const char *json_str = cJSON_Print(config);
ESP_LOGI(TAG, "Returning meters config: %s", json_str);
httpd_resp_sendstr(req, json_str);
// Liberação da memória
free((void *)json_str);
cJSON_Delete(config);
return ESP_OK;
}
// Função para atualizar as configurações dos contadores
static esp_err_t meters_config_post_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "Received POST request for /api/v1/config/meters");
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0)
{
ESP_LOGE(TAG, "Received empty body in POST request");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body");
return ESP_FAIL;
}
buf[len] = '\0'; // Garantir que a string está terminada
ESP_LOGI(TAG, "Received POST data: %s", buf);
cJSON *json = cJSON_Parse(buf);
if (!json)
{
ESP_LOGE(TAG, "Failed to parse JSON data");
// Resposta detalhada de erro
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON format");
return ESP_FAIL;
}
// Atualizando os contadores
cJSON *gridmeter = cJSON_GetObjectItem(json, "gridmeter");
if (gridmeter)
{
meter_type_t gridType = string_to_meter_type(gridmeter->valuestring); // Usando a função string_to_meter_type
ESP_LOGI(TAG, "Updating grid meter type to: %s", gridmeter->valuestring);
meter_manager_grid_set_model(gridType);
}
cJSON *evsemeter = cJSON_GetObjectItem(json, "evsemeter");
if (evsemeter)
{
meter_type_t evseType = string_to_meter_type(evsemeter->valuestring); // Usando a função string_to_meter_type
ESP_LOGI(TAG, "Updating EVSE meter type to: %s", evsemeter->valuestring);
meter_manager_evse_set_model(evseType);
}
cJSON_Delete(json);
httpd_resp_sendstr(req, "Meters updated successfully");
ESP_LOGI(TAG, "Meters configuration updated successfully");
return ESP_OK;
}
// Registrando os manipuladores de URI para os contadores
void register_meters_settings_handlers(httpd_handle_t server, void *ctx)
{
ESP_LOGD(TAG, "Registering URI handlers for meters settings");
// URI para o método GET
httpd_uri_t meters_get_uri = {
.uri = "/api/v1/config/meters",
.method = HTTP_GET,
.handler = meters_config_get_handler,
.user_ctx = ctx};
ESP_LOGD(TAG, "Registering GET handler for /api/v1/config/meters");
httpd_register_uri_handler(server, &meters_get_uri);
// URI para o método POST
httpd_uri_t meters_post_uri = {
.uri = "/api/v1/config/meters",
.method = HTTP_POST,
.handler = meters_config_post_handler,
.user_ctx = ctx};
ESP_LOGD(TAG, "Registering POST handler for /api/v1/config/meters");
httpd_register_uri_handler(server, &meters_post_uri);
}
// === Fim de: components/rest_api/src/meters_settings_api.c ===
// === Início de: components/rest_api/src/rest_main.c ===
#include "rest_main.h"
#include "evse_settings_api.h"
#include "meters_settings_api.h"
#include "loadbalancing_settings_api.h"
#include "evse_link_config_api.h"
#include "network_api.h"
#include "ocpp_api.h"
#include "auth_api.h"
#include "dashboard_api.h"
#include "scheduler_settings_api.h"
#include "static_file_api.h"
#include "esp_log.h"
static const char *TAG = "rest_main";
esp_err_t rest_server_init(const char *base_path)
{
ESP_LOGI(TAG, "Initializing REST API with base path: %s", base_path);
rest_server_context_t *ctx = calloc(1, sizeof(rest_server_context_t));
if (!ctx)
{
ESP_LOGE(TAG, "Failed to allocate memory for REST context");
return ESP_ERR_NO_MEM;
}
strlcpy(ctx->base_path, base_path, sizeof(ctx->base_path));
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.uri_match_fn = httpd_uri_match_wildcard;
config.max_uri_handlers = 32;
httpd_handle_t server = NULL;
esp_err_t err = httpd_start(&server, &config);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Failed to start HTTP server: %s", esp_err_to_name(err));
free(ctx);
return err;
}
ESP_LOGI(TAG, "HTTP server started successfully");
// Register endpoint groups
register_evse_settings_handlers(server, ctx); // Apenas chamando a função sem comparação
register_network_handlers(server, ctx); // Apenas chamando a função sem comparação
register_ocpp_handlers(server, ctx); // Apenas chamando a função sem comparação
register_auth_handlers(server, ctx); // Apenas chamando a função sem comparação
register_dashboard_handlers(server, ctx); // Apenas chamando a função sem comparação
register_meters_settings_handlers(server, ctx); // Apenas chamando a função sem comparação
register_loadbalancing_settings_handlers(server, ctx); // Apenas chamando a função sem comparação
register_link_config_handlers(server, ctx);
register_meters_data_handlers(server, ctx);
register_scheduler_settings_handlers(server, ctx);
register_static_file_handlers(server, ctx); // Apenas chamando a função sem comparação
ESP_LOGI(TAG, "All REST API endpoint groups registered successfully");
return ESP_OK;
}
// === Fim de: components/rest_api/src/rest_main.c ===
// === Início de: components/rest_api/src/network_api.c ===
// =========================
// network_api.c
// =========================
#include "network_api.h"
#include "esp_log.h"
#include "cJSON.h"
#include "network.h"
#include "mqtt.h"
static const char *TAG = "network_api";
typedef struct {
bool enabled;
char ssid[33];
char password[65];
} wifi_task_data_t;
static void wifi_apply_config_task(void *param) {
wifi_task_data_t *data = (wifi_task_data_t *)param;
ESP_LOGI("wifi_task", "Applying Wi-Fi config in background task");
wifi_set_config(data->enabled, data->ssid, data->password);
free(data);
vTaskDelete(NULL);
}
static esp_err_t wifi_get_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Handling GET /api/v1/config/wifi");
httpd_resp_set_type(req, "application/json");
// Obter dados da NVS via wifi.c
bool enabled = wifi_get_enabled();
char ssid[33] = {0};
char password[65] = {0};
wifi_get_ssid(ssid);
wifi_get_password(password);
// Criar JSON
cJSON *json = cJSON_CreateObject();
cJSON_AddBoolToObject(json, "enabled", enabled);
cJSON_AddStringToObject(json, "ssid", ssid);
cJSON_AddStringToObject(json, "password", password);
// Enviar resposta
char *response = cJSON_Print(json);
httpd_resp_sendstr(req, response);
// Limpeza
free(response);
cJSON_Delete(json);
return ESP_OK;
}
static esp_err_t wifi_post_handler(httpd_req_t *req) {
ESP_LOGI(TAG, "Handling POST /api/v1/config/wifi");
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0) return ESP_FAIL;
buf[len] = '\0';
cJSON *json = cJSON_Parse(buf);
if (!json) return ESP_FAIL;
// Valores padrão
bool enabled = false;
const char *ssid = NULL;
const char *password = NULL;
cJSON *j_enabled = cJSON_GetObjectItem(json, "enabled");
if (cJSON_IsBool(j_enabled)) enabled = j_enabled->valueint;
cJSON *j_ssid = cJSON_GetObjectItem(json, "ssid");
if (cJSON_IsString(j_ssid)) ssid = j_ssid->valuestring;
cJSON *j_password = cJSON_GetObjectItem(json, "password");
if (cJSON_IsString(j_password)) password = j_password->valuestring;
// Enviar resposta antes de alterar Wi-Fi
httpd_resp_sendstr(req, "Wi-Fi config atualizada com sucesso");
// Alocar struct para passar para a task
wifi_task_data_t *task_data = malloc(sizeof(wifi_task_data_t));
if (!task_data) {
cJSON_Delete(json);
ESP_LOGE(TAG, "Memory allocation failed for Wi-Fi task");
return ESP_ERR_NO_MEM;
}
task_data->enabled = enabled;
strncpy(task_data->ssid, ssid ? ssid : "", sizeof(task_data->ssid));
strncpy(task_data->password, password ? password : "", sizeof(task_data->password));
// Criar task normal com função C
xTaskCreate(
wifi_apply_config_task,
"wifi_config_task",
4096,
task_data,
3,
NULL
);
cJSON_Delete(json);
return ESP_OK;
}
static esp_err_t config_mqtt_get_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "Handling GET /api/v1/config/mqtt");
httpd_resp_set_type(req, "application/json");
bool enabled = mqtt_get_enabled();
char server[64] = {0};
char base_topic[32] = {0};
char username[32] = {0};
char password[64] = {0};
uint16_t periodicity = mqtt_get_periodicity();
mqtt_get_server(server);
mqtt_get_base_topic(base_topic);
mqtt_get_user(username);
mqtt_get_password(password);
ESP_LOGI(TAG, "MQTT Config:");
ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false");
ESP_LOGI(TAG, " Server: %s", server);
ESP_LOGI(TAG, " Topic: %s", base_topic);
ESP_LOGI(TAG, " Username: %s", username);
ESP_LOGI(TAG, " Password: %s", password);
ESP_LOGI(TAG, " Periodicity: %d", periodicity);
cJSON *config = cJSON_CreateObject();
cJSON_AddBoolToObject(config, "enabled", enabled);
cJSON_AddStringToObject(config, "host", server);
cJSON_AddNumberToObject(config, "port", 1883);
cJSON_AddStringToObject(config, "username", username);
cJSON_AddStringToObject(config, "password", password);
cJSON_AddStringToObject(config, "topic", base_topic);
cJSON_AddNumberToObject(config, "periodicity", periodicity);
const char *config_str = cJSON_Print(config);
httpd_resp_sendstr(req, config_str);
free((void *)config_str);
cJSON_Delete(config);
return ESP_OK;
}
static esp_err_t config_mqtt_post_handler(httpd_req_t *req)
{
ESP_LOGI(TAG, "Handling POST /api/v1/config/mqtt");
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0) {
ESP_LOGE(TAG, "Failed to read request body");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid request body");
return ESP_FAIL;
}
buf[len] = '\0';
ESP_LOGI(TAG, "Received JSON: %s", buf);
cJSON *json = cJSON_Parse(buf);
if (!json) {
ESP_LOGE(TAG, "Invalid JSON format");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
bool enabled = false;
const char *host = NULL, *topic = NULL, *username = NULL, *password = NULL;
int periodicity = 30;
if (cJSON_IsBool(cJSON_GetObjectItem(json, "enabled")))
enabled = cJSON_GetObjectItem(json, "enabled")->valueint;
cJSON *j_host = cJSON_GetObjectItem(json, "host");
if (cJSON_IsString(j_host)) host = j_host->valuestring;
cJSON *j_topic = cJSON_GetObjectItem(json, "topic");
if (cJSON_IsString(j_topic)) topic = j_topic->valuestring;
cJSON *j_user = cJSON_GetObjectItem(json, "username");
if (cJSON_IsString(j_user)) username = j_user->valuestring;
cJSON *j_pass = cJSON_GetObjectItem(json, "password");
if (cJSON_IsString(j_pass)) password = j_pass->valuestring;
cJSON *j_periodicity = cJSON_GetObjectItem(json, "periodicity");
if (cJSON_IsNumber(j_periodicity)) periodicity = j_periodicity->valueint;
ESP_LOGI(TAG, "Applying MQTT config:");
ESP_LOGI(TAG, " Enabled: %s", enabled ? "true" : "false");
ESP_LOGI(TAG, " Host: %s", host);
ESP_LOGI(TAG, " Topic: %s", topic);
ESP_LOGI(TAG, " Username: %s", username);
ESP_LOGI(TAG, " Password: %s", password);
ESP_LOGI(TAG, " Periodicity: %d", periodicity);
esp_err_t err = mqtt_set_config(enabled, host, topic, username, password, periodicity);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to apply MQTT config (code %d)", err);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to apply config");
cJSON_Delete(json);
return ESP_FAIL;
}
httpd_resp_sendstr(req, "Configuração MQTT atualizada com sucesso");
cJSON_Delete(json);
return ESP_OK;
}
void register_network_handlers(httpd_handle_t server, void *ctx) {
httpd_uri_t wifi_get = {
.uri = "/api/v1/config/wifi",
.method = HTTP_GET,
.handler = wifi_get_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &wifi_get);
httpd_uri_t wifi_post = {
.uri = "/api/v1/config/wifi",
.method = HTTP_POST,
.handler = wifi_post_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &wifi_post);
// URI handler for getting MQTT config
httpd_uri_t config_mqtt_get_uri = {
.uri = "/api/v1/config/mqtt",
.method = HTTP_GET,
.handler = config_mqtt_get_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &config_mqtt_get_uri);
// URI handler for posting MQTT config
httpd_uri_t config_mqtt_post_uri = {
.uri = "/api/v1/config/mqtt",
.method = HTTP_POST,
.handler = config_mqtt_post_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &config_mqtt_post_uri);
}
// === Fim de: components/rest_api/src/network_api.c ===
// === Início de: components/rest_api/src/dashboard_api.c ===
#include "dashboard_api.h"
#include "esp_log.h"
#include "cJSON.h"
#include "evse_api.h"
#include "evse_error.h"
#include "evse_config.h"
#include "evse_limits.h"
static const char *TAG = "dashboard_api";
static esp_err_t dashboard_get_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
// Cria o objeto JSON principal do dashboard
cJSON *dashboard = cJSON_CreateObject();
// Status do sistema
evse_state_t state = evse_get_state();
cJSON_AddStringToObject(dashboard, "status", evse_state_to_str(state));
// Carregador - informação do carregador 1 (adapte conforme necessário)
cJSON *chargers = cJSON_CreateArray();
cJSON *charger1 = cJSON_CreateObject();
cJSON_AddNumberToObject(charger1, "id", 1);
cJSON_AddStringToObject(charger1, "status", evse_state_to_str(state));
cJSON_AddNumberToObject(charger1, "current", evse_get_runtime_charging_current());
cJSON_AddNumberToObject(charger1, "maxCurrent", evse_get_max_charging_current());
// Calcular a potência com base na corrente (considerando 230V)
int power = (evse_get_runtime_charging_current()) * 230;
cJSON_AddNumberToObject(charger1, "power", power);
cJSON_AddItemToArray(chargers, charger1);
cJSON_AddItemToObject(dashboard, "chargers", chargers);
// Consumo e tempo de carregamento
cJSON_AddNumberToObject(dashboard, "energyConsumed", evse_get_consumption_limit());
cJSON_AddNumberToObject(dashboard, "chargingTime", evse_get_charging_time_limit());
// Alertas
cJSON *alerts = cJSON_CreateArray();
if (evse_is_limit_reached()) {
cJSON_AddItemToArray(alerts, cJSON_CreateString("Limite de consumo atingido."));
}
if (!evse_config_is_available()) {
cJSON_AddItemToArray(alerts, cJSON_CreateString("Estação indisponível."));
}
if (!evse_config_is_enabled()) {
cJSON_AddItemToArray(alerts, cJSON_CreateString("EVSE desativado."));
}
cJSON_AddItemToObject(dashboard, "alerts", alerts);
// Erros
uint32_t error_bits = evse_get_error();
cJSON *errors = cJSON_CreateArray();
if (error_bits & EVSE_ERR_DIODE_SHORT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Diodo curto-circuitado"));
if (error_bits & EVSE_ERR_LOCK_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no travamento"));
if (error_bits & EVSE_ERR_UNLOCK_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no destravamento"));
if (error_bits & EVSE_ERR_RCM_SELFTEST_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no autoteste do RCM"));
if (error_bits & EVSE_ERR_RCM_TRIGGERED_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("RCM disparado"));
if (error_bits & EVSE_ERR_TEMPERATURE_HIGH_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Temperatura elevada"));
if (error_bits & EVSE_ERR_PILOT_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Erro no sinal piloto"));
if (error_bits & EVSE_ERR_TEMPERATURE_FAULT_BIT) cJSON_AddItemToArray(errors, cJSON_CreateString("Falha no sensor de temperatura"));
cJSON_AddItemToObject(dashboard, "errors", errors);
// Enviar resposta JSON
const char *json_str = cJSON_Print(dashboard);
httpd_resp_sendstr(req, json_str);
// Liberar memória
free((void *)json_str);
cJSON_Delete(dashboard);
return ESP_OK;
}
void register_dashboard_handlers(httpd_handle_t server, void *ctx) {
httpd_uri_t uri = {
.uri = "/api/v1/dashboard",
.method = HTTP_GET,
.handler = dashboard_get_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &uri);
}
// === Fim de: components/rest_api/src/dashboard_api.c ===
// === Início de: components/rest_api/src/auth_api.c ===
// =========================
// auth_api.c (nova interface por modo)
// =========================
#include "auth_api.h"
#include "auth.h"
#include "esp_log.h"
#include "cJSON.h"
#include <string.h>
static const char *TAG = "auth_api";
// =================================
// Dummy user storage (static list)
// =================================
static struct {
char username[128];
} users[10] = { {"admin"}, {"user1"} };
static int num_users = 2;
// =================================
// Helpers
// =================================
static void send_json(httpd_req_t *req, cJSON *json) {
char *str = cJSON_PrintUnformatted(json);
httpd_resp_set_type(req, "application/json");
httpd_resp_sendstr(req, str ? str : "{}");
if (str) free(str);
cJSON_Delete(json);
}
static esp_err_t recv_body(httpd_req_t *req, char *buf, size_t buf_sz, int *out_len) {
int remaining = req->content_len;
int received = 0;
if (remaining <= 0) {
*out_len = 0;
return ESP_OK;
}
while (remaining > 0 && received < (int)(buf_sz - 1)) {
int chunk = remaining;
int space = (int)(buf_sz - 1 - received);
if (chunk > space) chunk = space;
int ret = httpd_req_recv(req, buf + received, chunk);
if (ret <= 0) return ESP_FAIL;
received += ret;
remaining -= ret;
}
buf[received] = '\0';
*out_len = received;
return ESP_OK;
}
// =================================
// Auth Mode (NEW API)
// =================================
static esp_err_t auth_mode_get_handler(httpd_req_t *req) {
auth_mode_t mode = auth_get_mode();
cJSON *json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "mode", auth_mode_to_str(mode));
send_json(req, json);
return ESP_OK;
}
static esp_err_t auth_mode_post_handler(httpd_req_t *req) {
char buf[256];
int len = 0;
if (recv_body(req, buf, sizeof(buf), &len) != ESP_OK) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Erro ao receber dados");
return ESP_FAIL;
}
cJSON *json = cJSON_ParseWithLength(buf, len);
if (!json) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "JSON inválido");
return ESP_FAIL;
}
cJSON *mode_js = cJSON_GetObjectItem(json, "mode");
if (!cJSON_IsString(mode_js) || mode_js->valuestring == NULL) {
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Campo 'mode' inválido ou ausente");
return ESP_FAIL;
}
auth_mode_t mode;
if (!auth_mode_from_str(mode_js->valuestring, &mode)) {
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Valor de 'mode' inválido (use: open|local|ocpp)");
return ESP_FAIL;
}
auth_set_mode(mode);
cJSON_Delete(json);
cJSON *resp = cJSON_CreateObject();
cJSON_AddStringToObject(resp, "mode", auth_mode_to_str(mode));
send_json(req, resp);
return ESP_OK;
}
// =================================
/* Users (mock) */
// =================================
static esp_err_t users_get_handler(httpd_req_t *req) {
cJSON *root = cJSON_CreateObject();
cJSON *list = cJSON_CreateArray();
for (int i = 0; i < num_users; ++i) {
cJSON *u = cJSON_CreateObject();
cJSON_AddStringToObject(u, "username", users[i].username);
cJSON_AddItemToArray(list, u);
}
cJSON_AddItemToObject(root, "users", list);
send_json(req, root);
return ESP_OK;
}
static esp_err_t users_post_handler(httpd_req_t *req) {
char buf[128];
int len = 0;
if (recv_body(req, buf, sizeof(buf), &len) != ESP_OK || len <= 0) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Body vazio");
return ESP_FAIL;
}
if (num_users < 10) {
strlcpy(users[num_users].username, buf, sizeof(users[num_users].username));
num_users++;
httpd_resp_sendstr(req, "Usuário adicionado com sucesso");
return ESP_OK;
} else {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Limite de usuários atingido");
return ESP_FAIL;
}
}
static esp_err_t users_delete_handler(httpd_req_t *req) {
char query[128];
if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) {
char username[128];
if (httpd_query_key_value(query, "username", username, sizeof(username)) == ESP_OK) {
for (int i = 0; i < num_users; i++) {
if (strcmp(users[i].username, username) == 0) {
for (int j = i; j < num_users - 1; j++) {
users[j] = users[j + 1];
}
num_users--;
httpd_resp_sendstr(req, "Usuário removido com sucesso");
return ESP_OK;
}
}
}
}
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Usuário não encontrado");
return ESP_FAIL;
}
// =================================
// Tags (apenas úteis no modo LOCAL)
// =================================
static esp_err_t tags_get_handler(httpd_req_t *req) {
cJSON *root = cJSON_CreateObject();
cJSON *list = cJSON_CreateArray();
int count = auth_get_tag_count();
for (int i = 0; i < count; i++) {
const char *tag = auth_get_tag_by_index(i);
if (tag) cJSON_AddItemToArray(list, cJSON_CreateString(tag));
}
cJSON_AddItemToObject(root, "tags", list);
send_json(req, root);
return ESP_OK;
}
static esp_err_t tags_delete_handler(httpd_req_t *req) {
char query[128];
if (httpd_req_get_url_query_str(req, query, sizeof(query)) == ESP_OK) {
char tag[AUTH_TAG_MAX_LEN];
if (httpd_query_key_value(query, "tag", tag, sizeof(tag)) == ESP_OK) {
if (auth_remove_tag(tag)) {
httpd_resp_sendstr(req, "Tag removida com sucesso");
return ESP_OK;
}
}
}
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Tag não encontrada ou inválida");
return ESP_FAIL;
}
static esp_err_t tags_register_handler(httpd_req_t *req) {
if (auth_get_mode() != AUTH_MODE_LOCAL_RFID) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Registo de tags disponível apenas no modo LOCAL");
return ESP_FAIL;
}
auth_wait_for_tag_registration();
httpd_resp_sendstr(req, "Modo de registro de tag ativado");
return ESP_OK;
}
// =================================
// Register All REST Endpoints
// =================================
void register_auth_handlers(httpd_handle_t server, void *ctx) {
// Auth mode
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/auth-mode",
.method = HTTP_GET,
.handler = auth_mode_get_handler,
.user_ctx = ctx
});
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/auth-mode",
.method = HTTP_POST,
.handler = auth_mode_post_handler,
.user_ctx = ctx
});
// Users (mock)
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/users",
.method = HTTP_GET,
.handler = users_get_handler,
.user_ctx = ctx
});
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/users",
.method = HTTP_POST,
.handler = users_post_handler,
.user_ctx = ctx
});
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/users",
.method = HTTP_DELETE,
.handler = users_delete_handler,
.user_ctx = ctx
});
// Tags
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/tags",
.method = HTTP_GET,
.handler = tags_get_handler,
.user_ctx = ctx
});
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/tags",
.method = HTTP_DELETE,
.handler = tags_delete_handler,
.user_ctx = ctx
});
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/config/tags/register",
.method = HTTP_POST,
.handler = tags_register_handler,
.user_ctx = ctx
});
}
// === Fim de: components/rest_api/src/auth_api.c ===
// === Início de: components/rest_api/src/loadbalancing_settings_api.c ===
#include "loadbalancing_settings_api.h"
#include "loadbalancer.h"
#include "esp_log.h"
#include "cJSON.h"
static const char *TAG = "loadbalancing_settings_api";
// GET Handler: Retorna configurações atuais de load balancing
static esp_err_t loadbalancing_config_get_handler(httpd_req_t *req) {
bool enabled = loadbalancer_is_enabled();
uint8_t currentLimit = load_balancing_get_max_grid_current();
ESP_LOGI(TAG, "Fetching load balancing settings: enabled = %d, currentLimit = %u", enabled, currentLimit);
httpd_resp_set_type(req, "application/json");
cJSON *config = cJSON_CreateObject();
cJSON_AddBoolToObject(config, "loadBalancingEnabled", enabled);
cJSON_AddNumberToObject(config, "loadBalancingCurrentLimit", currentLimit);
const char *json_str = cJSON_Print(config);
httpd_resp_sendstr(req, json_str);
ESP_LOGI(TAG, "Returned config: %s", json_str);
free((void *)json_str);
cJSON_Delete(config);
return ESP_OK;
}
// POST Handler: Atualiza configurações de load balancing
static esp_err_t loadbalancing_config_post_handler(httpd_req_t *req) {
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0) {
ESP_LOGE(TAG, "Received empty POST body");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body");
return ESP_FAIL;
}
buf[len] = '\0';
ESP_LOGI(TAG, "Received POST data: %s", buf);
cJSON *json = cJSON_Parse(buf);
if (!json) {
ESP_LOGE(TAG, "Invalid JSON");
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
// Atualizar estado habilitado
cJSON *enabled_item = cJSON_GetObjectItem(json, "loadBalancingEnabled");
if (enabled_item && cJSON_IsBool(enabled_item)) {
bool isEnabled = cJSON_IsTrue(enabled_item);
loadbalancer_set_enabled(isEnabled);
ESP_LOGI(TAG, "Updated loadBalancingEnabled to: %d", isEnabled);
}
// Atualizar limite de corrente
cJSON *limit_item = cJSON_GetObjectItem(json, "loadBalancingCurrentLimit");
if (limit_item && cJSON_IsNumber(limit_item)) {
uint8_t currentLimit = (uint8_t)limit_item->valuedouble;
// Validar intervalo
if (currentLimit < 6 || currentLimit > 100) {
ESP_LOGW(TAG, "Rejected invalid currentLimit: %d", currentLimit);
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid currentLimit (must be 6-100)");
return ESP_FAIL;
}
esp_err_t err = load_balancing_set_max_grid_current(currentLimit);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to save currentLimit: %s", esp_err_to_name(err));
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save setting");
return ESP_FAIL;
}
ESP_LOGI(TAG, "Updated loadBalancingCurrentLimit to: %d", currentLimit);
}
cJSON_Delete(json);
httpd_resp_sendstr(req, "Load balancing settings updated successfully");
return ESP_OK;
}
// Registro dos handlers na API HTTP
void register_loadbalancing_settings_handlers(httpd_handle_t server, void *ctx) {
// GET
httpd_uri_t get_uri = {
.uri = "/api/v1/config/loadbalancing",
.method = HTTP_GET,
.handler = loadbalancing_config_get_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &get_uri);
// POST
httpd_uri_t post_uri = {
.uri = "/api/v1/config/loadbalancing",
.method = HTTP_POST,
.handler = loadbalancing_config_post_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &post_uri);
}
// === Fim de: components/rest_api/src/loadbalancing_settings_api.c ===
// === Início de: components/rest_api/src/evse_settings_api.c ===
// =========================
// evse_settings_api.c
// =========================
#include "evse_settings_api.h"
#include "evse_api.h"
#include "evse_config.h"
#include "esp_log.h"
#include "cJSON.h"
static const char *TAG = "evse_settings_api";
static esp_err_t config_settings_get_handler(httpd_req_t *req) {
httpd_resp_set_type(req, "application/json");
cJSON *config = cJSON_CreateObject();
cJSON_AddNumberToObject(config, "currentLimit", evse_get_max_charging_current());
cJSON_AddNumberToObject(config, "temperatureLimit", evse_get_temp_threshold());
const char *json_str = cJSON_Print(config);
httpd_resp_sendstr(req, json_str);
free((void *)json_str);
cJSON_Delete(config);
return ESP_OK;
}
static esp_err_t config_settings_post_handler(httpd_req_t *req) {
char buf[512];
int len = httpd_req_recv(req, buf, sizeof(buf) - 1);
if (len <= 0) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Empty body");
return ESP_FAIL;
}
buf[len] = '\0';
cJSON *json = cJSON_Parse(buf);
if (!json) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
return ESP_FAIL;
}
cJSON *current = cJSON_GetObjectItem(json, "currentLimit");
if (current) evse_set_max_charging_current(current->valueint);
cJSON *temp = cJSON_GetObjectItem(json, "temperatureLimit");
if (temp) evse_set_temp_threshold(temp->valueint);
cJSON_Delete(json);
httpd_resp_sendstr(req, "Configurações atualizadas com sucesso");
return ESP_OK;
}
void register_evse_settings_handlers(httpd_handle_t server, void *ctx) {
httpd_uri_t get_uri = {
.uri = "/api/v1/config/settings",
.method = HTTP_GET,
.handler = config_settings_get_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &get_uri);
httpd_uri_t post_uri = {
.uri = "/api/v1/config/settings",
.method = HTTP_POST,
.handler = config_settings_post_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &post_uri);
}
// === Fim de: components/rest_api/src/evse_settings_api.c ===
// === Início de: components/rest_api/include/dashboard_api.h ===
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_http_server.h"
/**
* @brief Registra o handler da dashboard (status geral do sistema)
*/
void register_dashboard_handlers(httpd_handle_t server, void *ctx);
#ifdef __cplusplus
}
#endif
// === Fim de: components/rest_api/include/dashboard_api.h ===
// === Início de: components/rest_api/include/scheduler_settings_api.h ===
// =========================
// scheduler_settings_api.h
// =========================
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_http_server.h"
/**
* @brief Registra os handlers de configuração do scheduler
*
* Endpoints:
* GET /api/v1/config/scheduler
* POST /api/v1/config/scheduler
*/
void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx);
#ifdef __cplusplus
}
#endif
// === Fim de: components/rest_api/include/scheduler_settings_api.h ===
// === Início de: components/rest_api/include/static_file_api.h ===
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_http_server.h"
/**
* @brief Registra o handler para servir arquivos estáticos da web (SPA)
*/
void register_static_file_handlers(httpd_handle_t server, void *ctx);
#ifdef __cplusplus
}
#endif
// === Fim de: components/rest_api/include/static_file_api.h ===
// === Início de: components/rest_api/include/network_api.h ===
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_http_server.h"
/**
* @brief Registra os handlers de configuração Wi-Fi e MQTT
*/
void register_network_handlers(httpd_handle_t server, void *ctx);
#ifdef __cplusplus
}
#endif
// === Fim de: components/rest_api/include/network_api.h ===
// === Início de: components/rest_api/include/auth_api.h ===
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_http_server.h"
/**
* @brief Registra os handlers de autenticação e gerenciamento de usuários
*/
void register_auth_handlers(httpd_handle_t server, void *ctx);
#ifdef __cplusplus
}
#endif
// === Fim de: components/rest_api/include/auth_api.h ===
// === Início de: components/rest_api/include/loadbalancing_settings_api.h ===
// =========================
// loadbalancing_settings_api.h
// =========================
#ifndef LOADBALANCING_SETTINGS_API_H
#define LOADBALANCING_SETTINGS_API_H
#include "esp_err.h"
#include "esp_http_server.h"
// Função para registrar os manipuladores de URI para as configurações de load balancing e solar
void register_loadbalancing_settings_handlers(httpd_handle_t server, void *ctx);
#endif // LOADBALANCING_SETTINGS_API_H
// === Fim de: components/rest_api/include/loadbalancing_settings_api.h ===
// === Início de: components/rest_api/include/rest_main.h ===
#pragma once
#include <esp_err.h>
#include <esp_vfs.h>
#define SCRATCH_BUFSIZE (10240)
typedef struct rest_server_context {
char base_path[ESP_VFS_PATH_MAX + 1];
char scratch[SCRATCH_BUFSIZE];
} rest_server_context_t;
esp_err_t rest_server_init(const char *base_path);
// === Fim de: components/rest_api/include/rest_main.h ===
// === Início de: components/rest_api/include/meters_settings_api.h ===
// =========================
// meters_settings_api.h
// =========================
#ifndef METERS_SETTINGS_API_H
#define METERS_SETTINGS_API_H
#include "esp_err.h"
#include "esp_http_server.h"
// Função para registrar os manipuladores de URI para as configurações dos contadores
void register_meters_settings_handlers(httpd_handle_t server, void *ctx);
/**
* Exponha dados do meter via REST.
* Endpoints:
* GET /api/v1/meters/live -> dados mais recentes (GRID e/ou EVSE)
* GET /api/v1/meters/live?source=GRID|EVSE
*/
void register_meters_data_handlers(httpd_handle_t server, void *ctx);
#endif // METERS_SETTINGS_API_H
// === Fim de: components/rest_api/include/meters_settings_api.h ===
// === Início de: components/rest_api/include/ocpp_api.h ===
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_http_server.h"
/**
* @brief Registra os handlers da configuração e status do OCPP
*/
void register_ocpp_handlers(httpd_handle_t server, void *ctx);
#ifdef __cplusplus
}
#endif
// === Fim de: components/rest_api/include/ocpp_api.h ===
// === Início de: components/rest_api/include/evse_link_config_api.h ===
// =========================
// evse_link_config_api.h
// =========================
#ifndef EVSE_LINK_CONFIG_API_H
#define EVSE_LINK_CONFIG_API_H
#include "esp_err.h"
#include "esp_http_server.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Registra os manipuladores HTTP para configuração do EVSE-Link.
*
* Isso adiciona endpoints GET e POST em /api/v1/config/link
* para inspecionar e atualizar as configurações de link
* (habilitado, modo, self ID).
*
* @param server Handle do servidor HTTP
* @param ctx Contexto do usuário passado aos handlers
*/
void register_link_config_handlers(httpd_handle_t server, void *ctx);
#ifdef __cplusplus
}
#endif
#endif // EVSE_LINK_CONFIG_API_H
// === Fim de: components/rest_api/include/evse_link_config_api.h ===
// === Início de: components/rest_api/include/evse_settings_api.h ===
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "esp_http_server.h"
/**
* @brief Registra os handlers de configuração elétrica e limites de carregamento
*/
void register_evse_settings_handlers(httpd_handle_t server, void *ctx);
#ifdef __cplusplus
}
#endif
// === Fim de: components/rest_api/include/evse_settings_api.h ===