new release

This commit is contained in:
2026-05-15 12:20:47 +01:00
parent 286028b6a8
commit d0d431daf2
440 changed files with 2776 additions and 2462 deletions

View File

@@ -57,6 +57,94 @@ static esp_err_t recv_body(httpd_req_t *req, char *buf, size_t buf_sz, int *out_
return ESP_OK;
}
static void trim_in_place(char *s) {
if (!s || s[0] == '\0') return;
char *start = s;
while (*start == ' ' || *start == '\t' || *start == '\r' || *start == '\n') {
start++;
}
if (start != s) {
memmove(s, start, strlen(start) + 1);
}
size_t len = strlen(s);
while (len > 0) {
char c = s[len - 1];
if (c == ' ' || c == '\t' || c == '\r' || c == '\n') {
s[len - 1] = '\0';
len--;
} else {
break;
}
}
}
static void strip_wrapping_quotes_in_place(char *s) {
size_t len;
if (!s) return;
trim_in_place(s);
len = strlen(s);
if (len >= 2 && s[0] == '"' && s[len - 1] == '"') {
memmove(s, s + 1, len - 2);
s[len - 2] = '\0';
trim_in_place(s);
}
}
static bool extract_username_from_body(const char *buf, int len, char *out, size_t out_sz) {
bool ok = false;
cJSON *json = cJSON_ParseWithLength(buf, len);
out[0] = '\0';
if (json) {
if (cJSON_IsString(json) && json->valuestring) {
strlcpy(out, json->valuestring, out_sz);
ok = true;
} else if (cJSON_IsObject(json)) {
cJSON *username_js = cJSON_GetObjectItem(json, "username");
if (cJSON_IsString(username_js) && username_js->valuestring) {
strlcpy(out, username_js->valuestring, out_sz);
ok = true;
}
}
cJSON_Delete(json);
}
if (!ok) {
strlcpy(out, buf, out_sz);
}
trim_in_place(out);
strip_wrapping_quotes_in_place(out);
return out[0] != '\0';
}
static bool usernames_equal_normalized(const char *a, const char *b) {
char na[128] = {0};
char nb[128] = {0};
strlcpy(na, a ? a : "", sizeof(na));
strlcpy(nb, b ? b : "", sizeof(nb));
trim_in_place(na);
trim_in_place(nb);
strip_wrapping_quotes_in_place(na);
strip_wrapping_quotes_in_place(nb);
return strcmp(na, nb) == 0;
}
static int find_user_index(const char *username) {
for (int i = 0; i < num_users; ++i) {
if (usernames_equal_normalized(users[i].username, username)) {
return i;
}
}
return -1;
}
// =================================
// Auth Mode (NEW API)
// =================================
@@ -125,37 +213,48 @@ static esp_err_t users_get_handler(httpd_req_t *req) {
static esp_err_t users_post_handler(httpd_req_t *req) {
char buf[128];
char username[128] = {0};
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 {
if (!extract_username_from_body(buf, len, username, sizeof(username))) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Campo 'username' inválido ou ausente");
return ESP_FAIL;
}
if (find_user_index(username) >= 0) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Usuário já existe");
return ESP_FAIL;
}
if (num_users >= 10) {
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Limite de usuários atingido");
return ESP_FAIL;
}
strlcpy(users[num_users].username, username, sizeof(users[num_users].username));
num_users++;
httpd_resp_sendstr(req, "Usuário adicionado com sucesso");
return ESP_OK;
}
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];
char username[128] = {0};
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;
int idx = find_user_index(username);
if (idx >= 0) {
for (int j = idx; j < num_users - 1; j++) {
users[j] = users[j + 1];
}
num_users--;
httpd_resp_sendstr(req, "Usuário removido com sucesso");
return ESP_OK;
}
}
}

View File

@@ -17,19 +17,34 @@ static esp_err_t dashboard_get_handler(httpd_req_t *req) {
// Status do sistema
evse_state_t state = evse_get_state();
int runtime_current = evse_get_runtime_charging_current();
int requested_current = evse_get_charging_current();
int hardware_max = evse_get_max_charging_current();
int power = runtime_current * 230;
cJSON_AddStringToObject(dashboard, "status", evse_state_to_str(state));
// Aliases de topo para clientes novos (mantendo compatibilidade com chargers[])
cJSON_AddNumberToObject(dashboard, "currentA", runtime_current);
cJSON_AddNumberToObject(dashboard, "currentLimitNowA", runtime_current);
cJSON_AddNumberToObject(dashboard, "maxCurrentA", requested_current);
cJSON_AddNumberToObject(dashboard, "hardwareMaxA", hardware_max);
cJSON_AddNumberToObject(dashboard, "power", power);
cJSON_AddNumberToObject(dashboard, "powerW", power);
// 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_charging_current());
cJSON_AddNumberToObject(charger1, "current", runtime_current);
cJSON_AddNumberToObject(charger1, "currentA", runtime_current);
cJSON_AddNumberToObject(charger1, "maxCurrent", requested_current);
cJSON_AddNumberToObject(charger1, "maxCurrentA", requested_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_AddNumberToObject(charger1, "powerW", power);
cJSON_AddItemToArray(chargers, charger1);
cJSON_AddItemToObject(dashboard, "chargers", chargers);

View File

@@ -4,6 +4,7 @@
#include "evse_settings_api.h"
#include "evse_api.h"
#include "evse_config.h"
#include "evse_limits.h"
#include "esp_log.h"
#include "cJSON.h"
@@ -11,12 +12,23 @@ 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, "maxCurrentLimit", evse_get_max_charging_current());
cJSON_AddNumberToObject(config, "currentLimit", evse_get_charging_current());
cJSON_AddNumberToObject(config, "powerLimit", 0);
cJSON_AddNumberToObject(config, "energyLimit", evse_get_consumption_limit());
cJSON_AddNumberToObject(config, "chargingTimeLimit", evse_get_charging_time_limit());
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 *security = cJSON_CreateObject();
cJSON_AddBoolToObject(security, "earthFault", false);
cJSON_AddBoolToObject(security, "rcm", false);
cJSON_AddItemToObject(config, "security", security);
char *json_str = cJSON_PrintUnformatted(config);
httpd_resp_sendstr(req, json_str ? json_str : "{}");
if (json_str) free(json_str);
cJSON_Delete(config);
return ESP_OK;
}
@@ -36,9 +48,25 @@ static esp_err_t config_settings_post_handler(httpd_req_t *req) {
}
cJSON *current = cJSON_GetObjectItem(json, "currentLimit");
if (current) evse_set_charging_current(current->valueint);
if (cJSON_IsNumber(current)) {
int value = current->valueint;
int hw_max = evse_get_max_charging_current();
if (value < 0) value = 0;
if (value > hw_max) value = hw_max;
evse_set_charging_current(value);
}
cJSON *temp = cJSON_GetObjectItem(json, "temperatureLimit");
if (temp) evse_set_temp_threshold(temp->valueint);
if (cJSON_IsNumber(temp)) {
evse_set_temp_threshold(temp->valueint);
}
// Campos aceites por compatibilidade de contrato v1.
// Nesta sprint são ignorados caso o firmware ainda não os suporte internamente.
(void)cJSON_GetObjectItem(json, "powerLimit");
(void)cJSON_GetObjectItem(json, "energyLimit");
(void)cJSON_GetObjectItem(json, "chargingTimeLimit");
(void)cJSON_GetObjectItem(json, "security");
cJSON_Delete(json);
httpd_resp_sendstr(req, "Configurações atualizadas com sucesso");

View File

@@ -13,6 +13,23 @@
static const char *TAG = "network_api";
static bool mqtt_host_is_invalid(const char *host)
{
if (!host || host[0] == '\0')
return true;
if (strcmp(host, "localhost") == 0)
return true;
if (strcmp(host, "127.0.0.1") == 0)
return true;
if (strcmp(host, "0.0.0.0") == 0)
return true;
return false;
}
typedef struct
{
bool enabled;
@@ -225,10 +242,14 @@ static esp_err_t config_mqtt_post_handler(httpd_req_t *req)
if (cJSON_IsNumber(j_periodicity))
periodicity = j_periodicity->valueint;
// --- Regras: se vier NULL ou "" mantém o atual; se atual também estiver vazio, usa default
const char *host =
(host_in && host_in[0] != '\0') ? host_in : (current_host[0] != '\0') ? current_host
: "mqtt.plixin.com";
const char *candidate_host =
(host_in && host_in[0] != '\0') ? host_in :
(current_host[0] != '\0') ? current_host :
"mqtt.plixin.com";
const char *host = mqtt_host_is_invalid(candidate_host)
? "mqtt.plixin.com"
: candidate_host;
const char *topic =
(topic_in && topic_in[0] != '\0') ? topic_in : (current_topic[0] != '\0') ? current_topic
@@ -298,4 +319,4 @@ void register_network_handlers(httpd_handle_t server, void *ctx)
.handler = config_mqtt_post_handler,
.user_ctx = ctx};
httpd_register_uri_handler(server, &config_mqtt_post_uri);
}
}

View File

@@ -9,6 +9,7 @@
#include "dashboard_api.h"
#include "scheduler_settings_api.h"
#include "static_file_api.h"
#include "system_api.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
@@ -62,6 +63,7 @@ esp_err_t rest_server_init(const char *base_path)
register_link_config_handlers(server, ctx);
register_meters_data_handlers(server, ctx);
register_scheduler_settings_handlers(server, ctx);
register_system_handlers(server, ctx);
register_static_file_handlers(server, ctx);
ESP_LOGI(TAG, "All REST API endpoint groups registered successfully");

View File

@@ -1,3 +1,4 @@
// components/rest_api/src/scheduler_settings_api.c
#include "scheduler_settings_api.h"
#include "scheduler.h"
#include "scheduler_types.h"
@@ -6,14 +7,14 @@
#include "esp_http_server.h"
#include "cJSON.h"
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h> // sscanf, snprintf
static const char *TAG = "scheduler_api";
/* =========================
* Helpers HH:MM <-> minutos
* - aceita 24:00 => 1440
* ========================= */
static bool parse_hhmm(const char *s, uint16_t *out_min)
@@ -21,16 +22,19 @@ 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 == 24 && m == 0)
{
*out_min = 1440;
return true;
}
if (h < 0 || h > 23 || m < 0 || m > 59)
{
return false;
}
*out_min = (uint16_t)(h * 60 + m);
return true;
}
@@ -39,10 +43,78 @@ 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);
if (minutes == 1440)
{
snprintf(buf, buf_sz, "24:00");
return;
}
minutes %= 1440;
snprintf(buf, buf_sz, "%02d:%02d", minutes / 60, minutes % 60);
}
static cJSON *window_to_json(const sched_window_t *w)
{
cJSON *o = cJSON_CreateObject();
if (!o)
return NULL;
char b[8];
format_hhmm(w->start_min, b, sizeof(b));
cJSON_AddStringToObject(o, "startTime", b);
format_hhmm(w->end_min, b, sizeof(b));
cJSON_AddStringToObject(o, "endTime", b);
cJSON_AddNumberToObject(o, "currentA", (int)w->current_a);
return o;
}
static bool parse_windows_array(cJSON *arr, sched_config_t *cfg)
{
if (!arr || !cfg || !cJSON_IsArray(arr))
return false;
int n = cJSON_GetArraySize(arr);
if (n <= 0)
return false;
if (n > SCHED_MAX_WINDOWS)
n = SCHED_MAX_WINDOWS;
cfg->window_count = 0;
for (int i = 0; i < n; i++)
{
cJSON *it = cJSON_GetArrayItem(arr, i);
if (!cJSON_IsObject(it))
return false;
cJSON *js = cJSON_GetObjectItem(it, "startTime");
cJSON *je = cJSON_GetObjectItem(it, "endTime");
cJSON *ja = cJSON_GetObjectItem(it, "currentA");
if (!cJSON_IsString(js) || !cJSON_IsString(je) || !cJSON_IsNumber(ja))
return false;
uint16_t sm = 0, em = 0;
if (!parse_hhmm(js->valuestring, &sm))
return false;
if (!parse_hhmm(je->valuestring, &em))
return false;
int a = ja->valueint;
if (a < 6 || a > 80)
return false;
cfg->windows[cfg->window_count].start_min = sm;
cfg->windows[cfg->window_count].end_min = em;
cfg->windows[cfg->window_count].current_a = (uint16_t)a;
cfg->window_count++;
}
return true;
}
/* =========================
@@ -51,11 +123,9 @@ static void format_hhmm(uint16_t minutes, char *buf, size_t buf_sz)
static esp_err_t scheduler_config_get_handler(httpd_req_t *req)
{
ESP_LOGD(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)
@@ -67,13 +137,17 @@ static esp_err_t scheduler_config_get_handler(httpd_req_t *req)
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 *arr = cJSON_CreateArray();
for (int i = 0; i < cfg.window_count && i < SCHED_MAX_WINDOWS; i++)
{
cJSON *w = window_to_json(&cfg.windows[i]);
if (w)
cJSON_AddItemToArray(arr, w);
}
cJSON_AddItemToObject(root, "windows", arr);
cJSON_AddBoolToObject(root, "allowedNow", allowed_now);
cJSON_AddBoolToObject(root, "allowedNow", scheduler_is_allowed_now());
cJSON_AddNumberToObject(root, "currentLimitNowA", (int)scheduler_get_current_limit_now_a());
char *json_str = cJSON_PrintUnformatted(root);
if (!json_str)
@@ -84,7 +158,6 @@ static esp_err_t scheduler_config_get_handler(httpd_req_t *req)
}
httpd_resp_sendstr(req, json_str);
free(json_str);
cJSON_Delete(root);
return ESP_OK;
@@ -97,113 +170,66 @@ static esp_err_t scheduler_config_post_handler(httpd_req_t *req)
{
ESP_LOGD(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_LOGD(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
// começa do estado 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_LOGD(TAG, " enabled = %d", cfg.enabled);
}
// mode
cJSON *j_mode = cJSON_GetObjectItem(json, "mode");
if (cJSON_IsString(j_mode) && j_mode->valuestring)
if (!cfg.enabled)
{
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_LOGD(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_LOGD(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_LOGD(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) {
cfg.mode = SCHED_MODE_DISABLED;
cfg.window_count = 0;
scheduler_set_config(&cfg);
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"startTime and endTime cannot be equal");
httpd_resp_sendstr(req, "OK");
return ESP_OK;
}
// enabled=true => exige windows[]
cJSON *j_windows = cJSON_GetObjectItem(json, "windows");
if (!j_windows || !cJSON_IsArray(j_windows))
{
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing windows[]");
return ESP_FAIL;
}
cfg.mode = SCHED_MODE_DAILY_MULTI;
if (!parse_windows_array(j_windows, &cfg))
{
cJSON_Delete(json);
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
"Invalid windows (use: [{startTime,endTime,currentA}], currentA 6-80)");
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");
httpd_resp_sendstr(req, "OK");
return ESP_OK;
}
/* =========================
* Registo dos handlers
* ========================= */
void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx)
{
httpd_uri_t get_uri = {
@@ -221,4 +247,4 @@ void register_scheduler_settings_handlers(httpd_handle_t server, void *ctx)
httpd_register_uri_handler(server, &post_uri);
ESP_LOGD(TAG, "Scheduler REST handlers registered");
}
}

View File

@@ -0,0 +1,108 @@
#include "system_api.h"
#include "cJSON.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_system.h"
#include "esp_timer.h"
#include "auth.h"
#include "evse_api.h"
#include "evse_config.h"
#include "evse_limits.h"
#include "mqtt.h"
#include "network.h"
#include <string.h>
static const char *TAG = "system_api";
#ifndef FW_VERSION
#define FW_VERSION "1.0"
#endif
static esp_err_t system_info_get_handler(httpd_req_t *req)
{
httpd_resp_set_type(req, "application/json");
cJSON *root = cJSON_CreateObject();
if (!root)
{
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Falha ao criar resposta JSON");
return ESP_FAIL;
}
const bool wifi_enabled = wifi_get_enabled();
const int64_t uptime_us = esp_timer_get_time();
cJSON_AddStringToObject(root, "apiVersion", "1.0");
cJSON_AddStringToObject(root, "device", "PLX EV Charger");
cJSON_AddStringToObject(root, "fwVersion", FW_VERSION);
cJSON_AddStringToObject(root, "serial", "");
cJSON_AddStringToObject(root, "mode", wifi_enabled ? "sta" : "ap");
cJSON_AddBoolToObject(root, "online", true);
cJSON_AddStringToObject(root, "localConfigMode", wifi_enabled ? "sta" : "ap_default");
cJSON_AddStringToObject(root, "authScheme", "basic");
cJSON_AddNumberToObject(root, "uptimeS", (double)(uptime_us / 1000000));
cJSON_AddNumberToObject(root, "freeHeap", (double)esp_get_free_heap_size());
cJSON_AddNumberToObject(root, "hardwareMaxA", evse_get_max_charging_current());
cJSON_AddNumberToObject(root, "currentLimit", evse_get_charging_current());
cJSON_AddBoolToObject(root, "wifiEnabled", wifi_enabled);
cJSON_AddStringToObject(root, "authMode", auth_mode_to_str(auth_get_mode()));
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (netif)
{
esp_netif_ip_info_t ip_info;
if (esp_netif_get_ip_info(netif, &ip_info) == ESP_OK)
{
char ip_str[16] = {0};
snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip));
cJSON_AddStringToObject(root, "ip", ip_str);
}
}
cJSON *capabilities = cJSON_CreateObject();
if (capabilities)
{
cJSON_AddBoolToObject(capabilities, "wifiScan", false);
cJSON_AddBoolToObject(capabilities, "logs", false);
cJSON_AddBoolToObject(capabilities, "systemReboot", false);
cJSON_AddBoolToObject(capabilities, "systemFactoryReset", false);
cJSON_AddBoolToObject(capabilities, "systemApExit", false);
cJSON_AddBoolToObject(capabilities, "users", true);
cJSON_AddBoolToObject(capabilities, "tags", true);
cJSON_AddBoolToObject(capabilities, "security", true);
cJSON_AddBoolToObject(capabilities, "scheduler", true);
cJSON_AddBoolToObject(capabilities, "loadBalancing", true);
cJSON_AddBoolToObject(capabilities, "ocpp", true);
cJSON_AddBoolToObject(capabilities, "link", true);
cJSON_AddBoolToObject(capabilities, "metersConfig", true);
cJSON_AddItemToObject(root, "capabilities", capabilities);
}
char *json_str = cJSON_PrintUnformatted(root);
if (!json_str)
{
cJSON_Delete(root);
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Falha ao serializar JSON");
return ESP_FAIL;
}
ESP_LOGD(TAG, "GET /api/v1/system/info -> %s", json_str);
httpd_resp_sendstr(req, json_str);
free(json_str);
cJSON_Delete(root);
return ESP_OK;
}
void register_system_handlers(httpd_handle_t server, void *ctx)
{
httpd_register_uri_handler(server, &(httpd_uri_t){
.uri = "/api/v1/system/info",
.method = HTTP_GET,
.handler = system_info_get_handler,
.user_ctx = ctx,
});
}