2273 lines
66 KiB
C
2273 lines
66 KiB
C
.
|
||
|
||
// === 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,
|
||
¬ification,
|
||
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 (0–254)");
|
||
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 ===
|