new release
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
108
components/rest_api/src/system_api.c
Normal file
108
components/rest_api/src/system_api.c
Normal 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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user