evse_link feature
This commit is contained in:
@@ -4,7 +4,7 @@ DEVICE_NAME=Plixin Evse
|
||||
LED_CHARGING=y
|
||||
LED_CHARGING_GPIO=14
|
||||
LED_ERROR=y
|
||||
LED_ERROR_GPIO=13
|
||||
LED_ERROR_GPIO=26
|
||||
LED_STOP=y
|
||||
LED_STOP_GPIO=12
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ static void wiegand_task(void *arg) {
|
||||
snprintf(tag, sizeof(tag), "%03d%03d%03d%03d", p.data[0], p.data[1], p.data[2], p.data[3]);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unsupported bit length: %d", (int)p.bits);
|
||||
ESP_LOG_BUFFER_HEX(TAG, p.data, sizeof(p.data)); // loga o buffer bruto
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ typedef struct {
|
||||
} buzzer_step_t;
|
||||
|
||||
// Padrões de buzzer
|
||||
static const buzzer_step_t pattern_plugged[] = {{100, 100}, {200, 0}};
|
||||
static const buzzer_step_t pattern_plugged[] = {{200, 100}};
|
||||
static const buzzer_step_t pattern_unplugged[] = {{150, 150}, {150, 150}, {150, 0}};
|
||||
static const buzzer_step_t pattern_charging[] = {{80, 150}, {100, 120}, {120, 100}, {140, 0}};
|
||||
static const buzzer_step_t pattern_ap_start[] = {{300, 150}, {300, 0}};
|
||||
@@ -76,7 +76,7 @@ static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
|
||||
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL) return;
|
||||
|
||||
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
|
||||
ESP_LOGD(TAG, "EVSE event received: state = %d", evt->state);
|
||||
ESP_LOGI(TAG, "EVSE event received: state = %d", evt->state);
|
||||
|
||||
buzzer_event_data_t buzzer_evt = {0};
|
||||
|
||||
@@ -115,7 +115,7 @@ static void auth_event_handler(void *arg, esp_event_base_t base, int32_t id, voi
|
||||
|
||||
if (id == AUTH_EVENT_TAG_PROCESSED) {
|
||||
const auth_tag_event_data_t *evt = (const auth_tag_event_data_t *)event_data;
|
||||
ESP_LOGD(TAG, "AUTH processed: tag=%s authorized=%d", evt->tag, evt->authorized);
|
||||
ESP_LOGI(TAG, "AUTH processed: tag=%s authorized=%d", evt->tag, evt->authorized);
|
||||
buzzer_evt.pattern = evt->authorized ? BUZZER_PATTERN_CARD_READ : BUZZER_PATTERN_CARD_DENIED;
|
||||
|
||||
} else if (id == AUTH_EVENT_TAG_SAVED) {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "evse_limits.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
static const char *TAG = "evse_config";
|
||||
|
||||
@@ -60,7 +61,7 @@ void evse_check_defaults(void) {
|
||||
}
|
||||
|
||||
// Runtime charging current initialized from persisted default
|
||||
charging_current_runtime = charging_current;
|
||||
charging_current_runtime = max_charging_current;
|
||||
ESP_LOGD(TAG, "Runtime charging current initialized to: %d", charging_current_runtime);
|
||||
|
||||
// Auth required
|
||||
@@ -127,6 +128,7 @@ esp_err_t evse_set_max_charging_current(uint8_t value) {
|
||||
if (value < MIN_CHARGING_CURRENT_LIMIT || value > MAX_CHARGING_CURRENT_LIMIT)
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
max_charging_current = value;
|
||||
evse_set_runtime_charging_current(value);
|
||||
nvs_set_u8(nvs, "max_chrg_curr", value);
|
||||
return nvs_commit(nvs);
|
||||
}
|
||||
@@ -162,18 +164,34 @@ esp_err_t evse_set_default_charging_current(uint16_t value) {
|
||||
// ========================
|
||||
void evse_set_runtime_charging_current(uint16_t value) {
|
||||
|
||||
if (value > (max_charging_current)) {
|
||||
value= max_charging_current;
|
||||
}
|
||||
ESP_LOGI(TAG, "Runtime charging current updated: %d", charging_current_runtime);
|
||||
|
||||
if (value < (MIN_CHARGING_CURRENT_LIMIT) ) {
|
||||
if (value > max_charging_current) {
|
||||
value = max_charging_current;
|
||||
} else if (value < MIN_CHARGING_CURRENT_LIMIT) {
|
||||
value = MIN_CHARGING_CURRENT_LIMIT;
|
||||
}
|
||||
|
||||
charging_current_runtime = value;
|
||||
ESP_LOGI(TAG, "Runtime charging current updated: %d", charging_current_runtime);
|
||||
|
||||
// --- PUBLICA ALTERAÇÃO DE CONFIG DO EVSE ---
|
||||
evse_config_event_data_t evt = {
|
||||
.charging = evse_state_is_charging(evse_get_state()),
|
||||
.hw_max_current = (float)evse_get_max_charging_current(),
|
||||
.runtime_current = (float)charging_current_runtime,
|
||||
.timestamp_us = esp_timer_get_time()
|
||||
};
|
||||
|
||||
esp_event_post(EVSE_EVENTS,
|
||||
EVSE_EVENT_CONFIG_UPDATED,
|
||||
&evt,
|
||||
sizeof(evt),
|
||||
portMAX_DELAY);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
uint16_t evse_get_runtime_charging_current(void) {
|
||||
return charging_current_runtime;
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ static void on_auth_event(void* arg, esp_event_base_t base, int32_t id, void* da
|
||||
switch (id) {
|
||||
case AUTH_EVENT_TAG_PROCESSED: {
|
||||
auth_tag_event_data_t *evt = (auth_tag_event_data_t*)data;
|
||||
ESP_LOGI("EVSE", "Tag: %s | Autorizada: %s", evt->tag, evt->authorized ? "SIM" : "NÃO");
|
||||
ESP_LOGI("EVSE", "Tag: %s | Autorized: %s", evt->tag, evt->authorized ? "AUTHORIZED" : "DENIED");
|
||||
evse_state_set_authorized(evt->authorized);
|
||||
break;
|
||||
}
|
||||
@@ -74,10 +74,10 @@ static void on_loadbalancer_event(void* handler_arg, esp_event_base_t event_base
|
||||
ESP_LOGI(TAG, "Loadbalancer %s (ts: %lld)",
|
||||
evt->enabled ? "ENABLED" : "DISABLED", evt->timestamp_us);
|
||||
// Ações adicionais podem ser adicionadas aqui conforme necessário
|
||||
} else if (event_id == LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED) {
|
||||
const loadbalancer_charging_limit_event_t* evt = (const loadbalancer_charging_limit_event_t*) event_data;
|
||||
ESP_LOGD(TAG, "Novo limite de corrente: %.1f A (ts: %lld)", evt->limit, evt->timestamp_us);
|
||||
evse_set_runtime_charging_current((uint16_t)(evt->limit));
|
||||
} else if (event_id == LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT) {
|
||||
const loadbalancer_master_limit_event_t* evt = (const loadbalancer_master_limit_event_t*) event_data;
|
||||
ESP_LOGI(TAG, "Novo limite de corrente (master): %u A (ts: %lld)", evt->max_current, evt->timestamp_us);
|
||||
evse_set_runtime_charging_current(evt->max_current);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ static const char *TAG = "evse_state";
|
||||
static evse_state_event_t map_state_to_event(evse_state_t s) {
|
||||
switch (s) {
|
||||
case EVSE_STATE_A: return EVSE_STATE_EVENT_IDLE;
|
||||
case EVSE_STATE_B1: return EVSE_STATE_EVENT_WAITING;
|
||||
case EVSE_STATE_B2:
|
||||
case EVSE_STATE_B1:
|
||||
case EVSE_STATE_B2: return EVSE_STATE_EVENT_WAITING;
|
||||
case EVSE_STATE_C1:
|
||||
case EVSE_STATE_C2: return EVSE_STATE_EVENT_CHARGING;
|
||||
case EVSE_STATE_E:
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_err.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "evse_events.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
@@ -9,7 +9,7 @@ ESP_EVENT_DECLARE_BASE(EVSE_EVENTS);
|
||||
typedef enum {
|
||||
EVSE_EVENT_INIT,
|
||||
EVSE_EVENT_STATE_CHANGED,
|
||||
// Outros eventos possíveis futuramente
|
||||
EVSE_EVENT_CONFIG_UPDATED,
|
||||
} evse_event_id_t;
|
||||
|
||||
typedef enum {
|
||||
@@ -23,5 +23,11 @@ typedef struct {
|
||||
evse_state_event_t state;
|
||||
} evse_state_event_data_t;
|
||||
|
||||
typedef struct {
|
||||
bool charging; // Estado de carregamento
|
||||
float hw_max_current; // Corrente máxima suportada pelo hardware
|
||||
float runtime_current; // Corrente de carregamento em uso
|
||||
int64_t timestamp_us; // Momento da atualização
|
||||
} evse_config_event_data_t;
|
||||
|
||||
#endif // EVSE_EVENTS_H
|
||||
|
||||
16
components/evse_link/CMakeLists.txt
Executable file
16
components/evse_link/CMakeLists.txt
Executable file
@@ -0,0 +1,16 @@
|
||||
set(srcs
|
||||
"src/evse_link_master.c"
|
||||
"src/evse_link_slave.c"
|
||||
"src/evse_link_events.c"
|
||||
"src/evse_link_framing.c"
|
||||
"src/evse_link.c"
|
||||
)
|
||||
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_REQUIRES driver esp_timer nvs_flash
|
||||
REQUIRES config evse loadbalancer)
|
||||
|
||||
|
||||
|
||||
45
components/evse_link/include/evse_link.h
Executable file
45
components/evse_link/include/evse_link.h
Executable file
@@ -0,0 +1,45 @@
|
||||
#ifndef EVSE_LINK_H_
|
||||
#define EVSE_LINK_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// Operation mode: slave or master
|
||||
typedef enum {
|
||||
EVSE_LINK_MODE_SLAVE = 0,
|
||||
EVSE_LINK_MODE_MASTER = 1
|
||||
} evse_link_mode_t;
|
||||
|
||||
// Callback invoked when a complete frame is received:
|
||||
// src: device address of sender (0–255)
|
||||
// dest: device address of receiver (0–255 or 0xFF broadcast)
|
||||
// payload: pointer to received data buffer (command + data)
|
||||
// len: length of payload (0–255)
|
||||
typedef void (*evse_link_rx_cb_t)(uint8_t src, uint8_t dest,
|
||||
const uint8_t *payload, uint8_t len);
|
||||
|
||||
// Initializes the EVSE-Link component
|
||||
void evse_link_init(void);
|
||||
|
||||
// Sends a framed payload to `dest` with length `len`.
|
||||
// The source address is automatically set from configuration.
|
||||
// Returns true on successful enqueue/transmit.
|
||||
bool evse_link_send(uint8_t dest, const uint8_t *payload, uint8_t len);
|
||||
|
||||
// Feeds a received byte into the framing parser.
|
||||
void evse_link_recv_byte(uint8_t byte);
|
||||
|
||||
// Registers a callback to receive complete frames.
|
||||
void evse_link_register_rx_cb(evse_link_rx_cb_t cb);
|
||||
|
||||
// Runtime configuration getters/setters
|
||||
void evse_link_set_mode(evse_link_mode_t mode);
|
||||
evse_link_mode_t evse_link_get_mode(void);
|
||||
|
||||
void evse_link_set_self_id(uint8_t id);
|
||||
uint8_t evse_link_get_self_id(void);
|
||||
|
||||
void evse_link_set_enabled(bool enabled);
|
||||
bool evse_link_is_enabled(void);
|
||||
|
||||
#endif // EVSE_LINK_H_
|
||||
16
components/evse_link/include/evse_link_events.h
Normal file
16
components/evse_link/include/evse_link_events.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef EVSE_LINK_EVENTS_H_
|
||||
#define EVSE_LINK_EVENTS_H_
|
||||
|
||||
#include "esp_event.h"
|
||||
|
||||
ESP_EVENT_DECLARE_BASE(EVSE_LINK_EVENTS);
|
||||
|
||||
typedef enum {
|
||||
LINK_EVENT_FRAME_RECEIVED, // qualquer frame válido
|
||||
LINK_EVENT_SLAVE_ONLINE, // heartbeat recebido primeira vez
|
||||
LINK_EVENT_SLAVE_OFFLINE, // sem heartbeat no timeout
|
||||
LINK_EVENT_MASTER_POLL_SENT, // opcional: poll enviado pelo master
|
||||
LINK_EVENT_CURRENT_LIMIT_APPLIED,
|
||||
LINK_EVENT_SLAVE_CONFIG_UPDATED // <- NOVO evento
|
||||
} evse_link_event_t;
|
||||
#endif // EVSE_LINK_EVENTS_H_
|
||||
42
components/evse_link/include/evse_link_framing.h
Normal file
42
components/evse_link/include/evse_link_framing.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef EVSE_LINK_FRAMING_H_
|
||||
#define EVSE_LINK_FRAMING_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "driver/uart.h"
|
||||
|
||||
// UART configuration
|
||||
#define UART_PORT UART_NUM_2
|
||||
#define UART_BAUDRATE 115200
|
||||
#define UART_RX_BUF_SIZE 256
|
||||
|
||||
// GPIO pin assignments for UART
|
||||
#define TX_PIN 21 // GPIO21 -> RX on other board
|
||||
#define RX_PIN 22 // GPIO22 -> TX on other board
|
||||
|
||||
// Frame delimiters
|
||||
#define MAGIC_START 0x7E
|
||||
#define MAGIC_END 0x7F
|
||||
|
||||
// Maximum payload (excluding sequence byte)
|
||||
#define EVSE_LINK_MAX_PAYLOAD 254
|
||||
|
||||
// Callback type for when a full frame is received
|
||||
typedef void (*evse_link_frame_cb_t)(uint8_t src, uint8_t dest,
|
||||
const uint8_t *payload, uint8_t len);
|
||||
|
||||
// Initialize framing module (mutex, UART driver, etc.)
|
||||
void evse_link_framing_init(void);
|
||||
|
||||
// Send a framed payload to `dest` with length `len`
|
||||
// Includes source address in the header
|
||||
bool evse_link_framing_send(uint8_t dest, uint8_t src,
|
||||
const uint8_t *payload, uint8_t len);
|
||||
|
||||
// Feed a received byte into the framing parser
|
||||
void evse_link_framing_recv_byte(uint8_t byte);
|
||||
|
||||
// Register a callback for complete frames
|
||||
void evse_link_framing_register_cb(evse_link_frame_cb_t cb);
|
||||
|
||||
#endif // EVSE_LINK_FRAMING_H_
|
||||
142
components/evse_link/src/evse_link.c
Executable file
142
components/evse_link/src/evse_link.c
Executable file
@@ -0,0 +1,142 @@
|
||||
// components/evse_link/src/evse_link.c
|
||||
|
||||
#include "evse_link.h"
|
||||
#include "evse_link_framing.h"
|
||||
#include "driver/uart.h"
|
||||
#include "nvs.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
static const char *TAG = "evse_link";
|
||||
|
||||
// NVS keys
|
||||
#define _NVS_NAMESPACE "evse_link"
|
||||
#define _NVS_MODE_KEY "mode"
|
||||
#define _NVS_ID_KEY "self_id"
|
||||
#define _NVS_ENABLED_KEY "enabled"
|
||||
|
||||
// UART parameters
|
||||
#define UART_PORT UART_NUM_2
|
||||
#define UART_RX_BUF_SIZE 256
|
||||
|
||||
// Runtime config
|
||||
static evse_link_mode_t _mode = EVSE_LINK_MODE_MASTER;
|
||||
static uint8_t _self_id = 0x01;
|
||||
static bool _enabled = false;
|
||||
|
||||
// Registered Rx callback
|
||||
static evse_link_rx_cb_t _rx_cb = NULL;
|
||||
|
||||
// Forward declarations
|
||||
extern void evse_link_master_init(void);
|
||||
extern void evse_link_slave_init(void);
|
||||
|
||||
static void framing_rx_cb(uint8_t src, uint8_t dest,
|
||||
const uint8_t *payload, uint8_t len) {
|
||||
ESP_LOGD(TAG, "framing_rx_cb: src=0x%02X dest=0x%02X len=%u", src, dest, len);
|
||||
if (_rx_cb) {
|
||||
_rx_cb(src, dest, payload, len);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Register protocol-level Rx callback
|
||||
void evse_link_register_rx_cb(evse_link_rx_cb_t cb) {
|
||||
_rx_cb = cb;
|
||||
}
|
||||
|
||||
// Load config from NVS
|
||||
enum { EV_OK = ESP_OK };
|
||||
static void load_link_config(void) {
|
||||
nvs_handle_t handle;
|
||||
if (nvs_open(_NVS_NAMESPACE, NVS_READONLY, &handle) != EV_OK) {
|
||||
ESP_LOGW(TAG, "NVS open failed, using defaults");
|
||||
return;
|
||||
}
|
||||
uint8_t mode, id, en;
|
||||
if (nvs_get_u8(handle, _NVS_MODE_KEY, &mode) == EV_OK &&
|
||||
(mode == EVSE_LINK_MODE_MASTER || mode == EVSE_LINK_MODE_SLAVE)) {
|
||||
_mode = (evse_link_mode_t)mode;
|
||||
}
|
||||
if (nvs_get_u8(handle, _NVS_ID_KEY, &id) == EV_OK) {
|
||||
_self_id = id;
|
||||
}
|
||||
if (nvs_get_u8(handle, _NVS_ENABLED_KEY, &en) == EV_OK) {
|
||||
_enabled = (en != 0);
|
||||
}
|
||||
nvs_close(handle);
|
||||
}
|
||||
|
||||
// Save config to NVS
|
||||
static void save_link_config(void) {
|
||||
nvs_handle_t handle;
|
||||
if (nvs_open(_NVS_NAMESPACE, NVS_READWRITE, &handle) == EV_OK) {
|
||||
nvs_set_u8(handle, _NVS_MODE_KEY, (uint8_t)_mode);
|
||||
nvs_set_u8(handle, _NVS_ID_KEY, _self_id);
|
||||
nvs_set_u8(handle, _NVS_ENABLED_KEY, _enabled ? 1 : 0);
|
||||
nvs_commit(handle);
|
||||
nvs_close(handle);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to save NVS");
|
||||
}
|
||||
}
|
||||
|
||||
// Getters/setters
|
||||
void evse_link_set_mode(evse_link_mode_t m) { _mode = m; save_link_config(); }
|
||||
evse_link_mode_t evse_link_get_mode(void) { return _mode; }
|
||||
void evse_link_set_self_id(uint8_t id) { _self_id = id; save_link_config(); }
|
||||
uint8_t evse_link_get_self_id(void) { return _self_id; }
|
||||
void evse_link_set_enabled(bool en) { _enabled = en; save_link_config(); }
|
||||
bool evse_link_is_enabled(void) { return _enabled; }
|
||||
|
||||
// RX task: reads bytes from UART and feeds framing
|
||||
static void evse_link_rx_task(void *arg) {
|
||||
uint8_t buf[UART_RX_BUF_SIZE];
|
||||
while (true) {
|
||||
int len = uart_read_bytes(UART_PORT, buf, sizeof(buf), pdMS_TO_TICKS(1000));
|
||||
if (len > 0) {
|
||||
for (int i = 0; i < len; ++i) {
|
||||
evse_link_recv_byte(buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize EVSE-Link component
|
||||
void evse_link_init(void) {
|
||||
load_link_config();
|
||||
|
||||
ESP_LOGI(TAG, "Link init: mode=%c id=0x%02X enabled=%d",
|
||||
_mode == EVSE_LINK_MODE_MASTER ? 'M' : 'S',
|
||||
_self_id, _enabled);
|
||||
if (!_enabled) return;
|
||||
|
||||
// 1) framing layer init (sets up mutex, UART driver, etc.)
|
||||
evse_link_framing_init();
|
||||
evse_link_framing_register_cb(framing_rx_cb);
|
||||
|
||||
// 2) start RX task
|
||||
xTaskCreate(evse_link_rx_task, "evse_link_rx", 4096, NULL, 4, NULL);
|
||||
|
||||
// 3) delegate to master or slave
|
||||
if (_mode == EVSE_LINK_MODE_MASTER) {
|
||||
evse_link_master_init();
|
||||
} else {
|
||||
evse_link_slave_init();
|
||||
}
|
||||
}
|
||||
|
||||
// Send a frame (delegates to framing module)
|
||||
bool evse_link_send(uint8_t dest, const uint8_t *payload, uint8_t len) {
|
||||
if (!evse_link_is_enabled()) return false;
|
||||
uint8_t src = evse_link_get_self_id();
|
||||
return evse_link_framing_send(dest, src, payload, len);
|
||||
}
|
||||
|
||||
|
||||
// Receive byte (delegates to framing module)
|
||||
void evse_link_recv_byte(uint8_t byte) {
|
||||
evse_link_framing_recv_byte(byte);
|
||||
}
|
||||
4
components/evse_link/src/evse_link_events.c
Normal file
4
components/evse_link/src/evse_link_events.c
Normal file
@@ -0,0 +1,4 @@
|
||||
#include "evse_link_events.h"
|
||||
|
||||
// Esta única linha insere o símbolo EVSE_LINK_EVENTS no binário
|
||||
ESP_EVENT_DEFINE_BASE(EVSE_LINK_EVENTS);
|
||||
168
components/evse_link/src/evse_link_framing.c
Normal file
168
components/evse_link/src/evse_link_framing.c
Normal file
@@ -0,0 +1,168 @@
|
||||
// components/evse_link_framing/src/evse_link_framing.c
|
||||
|
||||
#include "evse_link_framing.h"
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_log.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "evse_framing";
|
||||
|
||||
static SemaphoreHandle_t tx_mutex;
|
||||
static uint8_t seq = 0;
|
||||
static evse_link_frame_cb_t rx_cb = NULL;
|
||||
|
||||
// CRC-8 (polynomial 0x07)
|
||||
static uint8_t crc8(const uint8_t *data, uint8_t len) {
|
||||
uint8_t crc = 0;
|
||||
for (uint8_t i = 0; i < len; ++i) {
|
||||
crc ^= data[i];
|
||||
for (uint8_t b = 0; b < 8; ++b) {
|
||||
crc = (crc & 0x80) ? (crc << 1) ^ 0x07 : (crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void evse_link_framing_init(void) {
|
||||
// Create mutex for TX
|
||||
tx_mutex = xSemaphoreCreateMutex();
|
||||
// Install UART driver
|
||||
uart_driver_install(UART_PORT, UART_RX_BUF_SIZE * 2, 0, 0, NULL, 0);
|
||||
uart_param_config(UART_PORT, &(uart_config_t){
|
||||
.baud_rate = UART_BAUDRATE,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE
|
||||
});
|
||||
uart_set_pin(UART_PORT, TX_PIN, RX_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
}
|
||||
|
||||
bool evse_link_framing_send(uint8_t dest, uint8_t src,
|
||||
const uint8_t *payload, uint8_t len) {
|
||||
if (len > EVSE_LINK_MAX_PAYLOAD) return false;
|
||||
if (xSemaphoreTake(tx_mutex, portMAX_DELAY) != pdTRUE) return false;
|
||||
|
||||
// Frame: START | DEST | SRC | LEN | SEQ | PAYLOAD | CRC | END
|
||||
uint8_t frame[EVSE_LINK_MAX_PAYLOAD + 7];
|
||||
int idx = 0;
|
||||
frame[idx++] = MAGIC_START;
|
||||
frame[idx++] = dest;
|
||||
frame[idx++] = src;
|
||||
frame[idx++] = len + 1; // +1 for SEQ
|
||||
frame[idx++] = seq;
|
||||
memcpy(&frame[idx], payload, len);
|
||||
idx += len;
|
||||
|
||||
// CRC covers DEST + SRC + LEN + SEQ + PAYLOAD
|
||||
uint8_t crc_input[3 + 1 + EVSE_LINK_MAX_PAYLOAD];
|
||||
memcpy(crc_input, &frame[1], 3 + 1 + len);
|
||||
frame[idx++] = crc8(crc_input, 3 + 1 + len);
|
||||
|
||||
frame[idx++] = MAGIC_END;
|
||||
|
||||
uart_write_bytes(UART_PORT, (const char *)frame, idx);
|
||||
uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(10));
|
||||
xSemaphoreGive(tx_mutex);
|
||||
|
||||
ESP_LOGD(TAG, "Sent frame dest=0x%02X src=0x%02X len=%u seq=%u",
|
||||
dest, src, len, seq);
|
||||
|
||||
seq++; // increment sequence after sending
|
||||
return true;
|
||||
}
|
||||
|
||||
void evse_link_framing_recv_byte(uint8_t b) {
|
||||
// State machine for frame parsing
|
||||
static enum {
|
||||
ST_WAIT_START,
|
||||
ST_WAIT_DEST,
|
||||
ST_WAIT_SRC,
|
||||
ST_WAIT_LEN,
|
||||
ST_WAIT_SEQ,
|
||||
ST_READING,
|
||||
ST_WAIT_CRC,
|
||||
ST_WAIT_END
|
||||
} rx_state = ST_WAIT_START;
|
||||
|
||||
static uint8_t rx_dest;
|
||||
static uint8_t rx_src;
|
||||
static uint8_t rx_len;
|
||||
static uint8_t rx_seq;
|
||||
static uint8_t rx_buf[EVSE_LINK_MAX_PAYLOAD];
|
||||
static uint8_t rx_pos;
|
||||
static uint8_t rx_crc;
|
||||
|
||||
switch (rx_state) {
|
||||
case ST_WAIT_START:
|
||||
if (b == MAGIC_START) {
|
||||
rx_state = ST_WAIT_DEST;
|
||||
}
|
||||
break;
|
||||
|
||||
case ST_WAIT_DEST:
|
||||
rx_dest = b;
|
||||
rx_state = ST_WAIT_SRC;
|
||||
break;
|
||||
|
||||
case ST_WAIT_SRC:
|
||||
rx_src = b;
|
||||
rx_state = ST_WAIT_LEN;
|
||||
break;
|
||||
|
||||
case ST_WAIT_LEN:
|
||||
rx_len = b; // includes SEQ + payload
|
||||
rx_pos = 0;
|
||||
rx_state = ST_WAIT_SEQ;
|
||||
break;
|
||||
|
||||
case ST_WAIT_SEQ:
|
||||
rx_seq = b;
|
||||
rx_state = (rx_len > 1) ? ST_READING : ST_WAIT_CRC;
|
||||
break;
|
||||
|
||||
case ST_READING:
|
||||
rx_buf[rx_pos++] = b;
|
||||
if (rx_pos >= (rx_len - 1)) { // all payload bytes read
|
||||
rx_state = ST_WAIT_CRC;
|
||||
}
|
||||
break;
|
||||
|
||||
case ST_WAIT_CRC:
|
||||
rx_crc = b;
|
||||
rx_state = ST_WAIT_END;
|
||||
break;
|
||||
|
||||
case ST_WAIT_END:
|
||||
if (b == MAGIC_END) {
|
||||
// Build data for CRC calculation
|
||||
uint8_t temp[3 + 1 + EVSE_LINK_MAX_PAYLOAD];
|
||||
int temp_len = 0;
|
||||
temp[temp_len++] = rx_dest;
|
||||
temp[temp_len++] = rx_src;
|
||||
temp[temp_len++] = rx_len;
|
||||
temp[temp_len++] = rx_seq;
|
||||
memcpy(&temp[temp_len], rx_buf, rx_len - 1);
|
||||
temp_len += rx_len - 1;
|
||||
|
||||
uint8_t expected = crc8(temp, temp_len);
|
||||
if (expected == rx_crc) {
|
||||
if (rx_cb) {
|
||||
rx_cb(rx_src, rx_dest, rx_buf, rx_len - 1);
|
||||
}
|
||||
ESP_LOGD(TAG, "Frame OK src=0x%02X dest=0x%02X len=%u seq=%u",
|
||||
rx_src, rx_dest, rx_len - 1, rx_seq);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "CRC mismatch: expected=0x%02X got=0x%02X",
|
||||
expected, rx_crc);
|
||||
}
|
||||
}
|
||||
rx_state = ST_WAIT_START;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void evse_link_framing_register_cb(evse_link_frame_cb_t cb) {
|
||||
rx_cb = cb;
|
||||
}
|
||||
152
components/evse_link/src/evse_link_master.c
Normal file
152
components/evse_link/src/evse_link_master.c
Normal file
@@ -0,0 +1,152 @@
|
||||
// === components/evse_link/src/evse_link_master.c ===
|
||||
|
||||
#include "evse_link.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_event.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "loadbalancer_events.h"
|
||||
|
||||
static const char *TAG = "evse_link_master";
|
||||
|
||||
// Link commands
|
||||
#define CMD_POLL 0x01
|
||||
#define CMD_HEARTBEAT 0x02
|
||||
#define CMD_HEARTBEAT_ACK 0x09
|
||||
#define CMD_CONFIG_BROADCAST 0x03
|
||||
#define CMD_SET_CURRENT 0x08
|
||||
|
||||
// payload lengths (exclui byte de opcode)
|
||||
#define LEN_POLL_REQ 1 // [ CMD_POLL ]
|
||||
#define LEN_POLL_RESP 9 // [ CMD_POLL, float V(4), float I(4) ]
|
||||
#define LEN_HEARTBEAT 6 // [ CMD_HEARTBEAT, charging, hw_max_lo, hw_max_hi, run_lo, run_hi ]
|
||||
#define LEN_CONFIG_BROADCAST 2 // [ CMD_CONFIG_BROADCAST, new_max_current ]
|
||||
#define LEN_SET_CURRENT 3 // [ CMD_SET_CURRENT, limit_lo, limit_hi ]
|
||||
#define LEN_HEARTBEAT_ACK 1
|
||||
|
||||
// polling / heartbeat timers interval
|
||||
typedef struct {
|
||||
TimerHandle_t timer;
|
||||
TickType_t interval;
|
||||
} timer_def_t;
|
||||
static timer_def_t poll_timer = { .timer = NULL, .interval = pdMS_TO_TICKS(30000) };
|
||||
static timer_def_t hb_timer = { .timer = NULL, .interval = pdMS_TO_TICKS(30000) };
|
||||
|
||||
// --- Send new limit to slave ---
|
||||
static void on_new_limit(void* arg, esp_event_base_t base, int32_t id, void* data) {
|
||||
if (id != LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT) return;
|
||||
const loadbalancer_slave_limit_event_t *evt = data;
|
||||
uint8_t slave_id = evt->slave_id;
|
||||
uint16_t max_current = evt->max_current;
|
||||
|
||||
uint8_t buf[LEN_SET_CURRENT] = {
|
||||
CMD_SET_CURRENT,
|
||||
(uint8_t)(max_current & 0xFF),
|
||||
(uint8_t)(max_current >> 8)
|
||||
};
|
||||
evse_link_send(slave_id, buf, sizeof(buf));
|
||||
ESP_LOGI(TAG, "Sent SET_CURRENT to 0x%02X: %uA", slave_id, max_current);
|
||||
}
|
||||
|
||||
// --- Polling broadcast callback ---
|
||||
static void poll_timer_cb(TimerHandle_t xTimer) {
|
||||
ESP_LOGD(TAG, "Broadcasting CMD_POLL to all slaves");;
|
||||
// Optionally post event LINK_EVENT_MASTER_POLL_SENT
|
||||
}
|
||||
|
||||
// --- Heartbeat timeout callback ---
|
||||
static void hb_timer_cb(TimerHandle_t xTimer) {
|
||||
ESP_LOGW(TAG, "Heartbeat timeout: possible slave offline");
|
||||
// post event LINK_EVENT_SLAVE_OFFLINE ???
|
||||
}
|
||||
|
||||
static void on_frame_master(uint8_t src, uint8_t dest,
|
||||
const uint8_t *payload, uint8_t len) {
|
||||
if (len < 1) return;
|
||||
uint8_t cmd = payload[0];
|
||||
|
||||
switch (cmd) {
|
||||
case CMD_HEARTBEAT: {
|
||||
if (len != 6) { // CMD + charging + hw_max_lo + hw_max_hi + runtime_lo + runtime_hi
|
||||
ESP_LOGW(TAG, "HEARTBEAT len invalid from 0x%02X: %u bytes", src, len);
|
||||
return;
|
||||
}
|
||||
bool charging = payload[1] != 0;
|
||||
uint16_t hw_max = payload[2] | (payload[3] << 8);
|
||||
uint16_t runtime = payload[4] | (payload[5] << 8);
|
||||
|
||||
ESP_LOGI(TAG, "Heartbeat from 0x%02X: charging=%d hw_max=%uA runtime=%uA",
|
||||
src, charging, hw_max, runtime);
|
||||
|
||||
loadbalancer_slave_status_event_t status = {
|
||||
.slave_id = src,
|
||||
.charging = charging,
|
||||
.hw_max_current = (float)hw_max,
|
||||
.runtime_current = (float)runtime, // corrente real medida no slave
|
||||
.timestamp_us = esp_timer_get_time()
|
||||
};
|
||||
|
||||
esp_event_post(LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_SLAVE_STATUS,
|
||||
&status, sizeof(status), portMAX_DELAY);
|
||||
|
||||
// Enviar ACK de volta
|
||||
uint8_t ack[] = { CMD_HEARTBEAT_ACK };
|
||||
evse_link_send(src, ack, sizeof(ack));
|
||||
ESP_LOGD(TAG, "Sent HEARTBEAT_ACK to 0x%02X", src);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_POLL:
|
||||
ESP_LOGD(TAG, "Received POLL_RESP from 0x%02X", src);
|
||||
break;
|
||||
|
||||
case CMD_CONFIG_BROADCAST:
|
||||
ESP_LOGI(TAG, "Slave 0x%02X acked CONFIG_BROADCAST: new_max=%uA",
|
||||
src, payload[1]);
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown cmd 0x%02X from 0x%02X", cmd, src);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Master initialization ---
|
||||
void evse_link_master_init(void) {
|
||||
if (evse_link_get_mode() != EVSE_LINK_MODE_MASTER || !evse_link_is_enabled()) {
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "Initializing MASTER (ID=0x%02X)", evse_link_get_self_id());
|
||||
|
||||
// register frame callback
|
||||
evse_link_register_rx_cb(on_frame_master);
|
||||
|
||||
// register loadbalancer event
|
||||
ESP_ERROR_CHECK(
|
||||
esp_event_handler_register(
|
||||
LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
|
||||
on_new_limit,
|
||||
NULL
|
||||
)
|
||||
);
|
||||
|
||||
// create and start poll timer
|
||||
poll_timer.timer = xTimerCreate("poll_tmr",
|
||||
poll_timer.interval,
|
||||
pdTRUE, NULL,
|
||||
poll_timer_cb);
|
||||
xTimerStart(poll_timer.timer, 0);
|
||||
|
||||
// create and start heartbeat monitor timer
|
||||
hb_timer.timer = xTimerCreate("hb_tmr",
|
||||
hb_timer.interval,
|
||||
pdFALSE, NULL,
|
||||
hb_timer_cb);
|
||||
xTimerStart(hb_timer.timer, 0);
|
||||
}
|
||||
164
components/evse_link/src/evse_link_slave.c
Normal file
164
components/evse_link/src/evse_link_slave.c
Normal file
@@ -0,0 +1,164 @@
|
||||
// === components/evse_link/src/evse_link_slave.c ===
|
||||
|
||||
#include "evse_link.h"
|
||||
#include "evse_link_events.h"
|
||||
#include "loadbalancer_events.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_event.h"
|
||||
#include "evse_events.h"
|
||||
#include "evse_state.h"
|
||||
#include "evse_config.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = "evse_link_slave";
|
||||
|
||||
// Link commands
|
||||
#define CMD_POLL 0x01
|
||||
#define CMD_HEARTBEAT 0x02 // not used by slave
|
||||
#define CMD_CONFIG_BROADCAST 0x03
|
||||
#define CMD_SET_CURRENT 0x08
|
||||
#define CMD_HEARTBEAT_ACK 0x09
|
||||
|
||||
// payload lengths (exclui seq byte)
|
||||
#define LEN_POLL_REQ 1 // [ CMD_POLL ]
|
||||
#define LEN_CONFIG_BROADCAST 2 // [ CMD_CONFIG_BROADCAST, new_max_current ]
|
||||
#define LEN_SET_CURRENT 3 // [ CMD_SET_CURRENT, limit_lo, limit_hi ]
|
||||
#define LEN_HEARTBEAT_ACK 1 // [ CMD_HEARTBEAT_ACK ]
|
||||
#define LEN_HEARTBEAT 6 // CMD_HEARTBEAT + charging + hw_max_lo + hw_max_hi + runtime_lo + runtime_hi
|
||||
|
||||
// Timing
|
||||
#define FALLBACK_TIMEOUT_MS 120000
|
||||
|
||||
static TimerHandle_t fallback_timer = NULL;
|
||||
static bool safe_mode = false;
|
||||
|
||||
// --- Helper to send a heartbeat frame ---
|
||||
static void send_heartbeat_frame(void) {
|
||||
bool charging = evse_state_is_charging(evse_get_state());
|
||||
uint16_t hw_max = evse_get_max_charging_current();
|
||||
uint16_t runtime = evse_get_runtime_charging_current();
|
||||
|
||||
ESP_LOGI(TAG, "Sending HEARTBEAT: charging=%d hw_max=%uA runtime=%uA",
|
||||
charging, hw_max, runtime);
|
||||
|
||||
uint8_t hb[] = {
|
||||
CMD_HEARTBEAT,
|
||||
charging ? 1 : 0,
|
||||
(uint8_t)(hw_max & 0xFF), (uint8_t)(hw_max >> 8),
|
||||
(uint8_t)(runtime & 0xFF), (uint8_t)(runtime >> 8)
|
||||
};
|
||||
// Broadcast to master (0xFF)
|
||||
evse_link_send(0xFF, hb, sizeof(hb));
|
||||
}
|
||||
|
||||
|
||||
// --- EVSE state change handler ---
|
||||
static void evse_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) {
|
||||
if (base!=EVSE_EVENTS || id!=EVSE_EVENT_STATE_CHANGED || data==NULL) return;
|
||||
const evse_state_event_data_t *evt = data;
|
||||
if (evt->state==EVSE_STATE_EVENT_IDLE || evt->state==EVSE_STATE_EVENT_CHARGING) {
|
||||
send_heartbeat_frame();
|
||||
}
|
||||
}
|
||||
|
||||
static void on_frame_slave(uint8_t src, uint8_t dest,
|
||||
const uint8_t *payload, uint8_t len) {
|
||||
if (dest != evse_link_get_self_id() && dest != 0xFF) return;
|
||||
if (len < 1) return;
|
||||
|
||||
uint8_t cmd = payload[0];
|
||||
switch (cmd) {
|
||||
case CMD_POLL:
|
||||
ESP_LOGD(TAG, "Received CMD_POLL from master 0x%02X", src);
|
||||
break;
|
||||
|
||||
case CMD_CONFIG_BROADCAST:
|
||||
ESP_LOGD(TAG, "Received CMD_CONFIG_BROADCAST from master 0x%02X", src);
|
||||
break;
|
||||
|
||||
case CMD_SET_CURRENT: {
|
||||
uint16_t amps = payload[1] | (payload[2] << 8);
|
||||
evse_set_runtime_charging_current(amps);
|
||||
ESP_LOGI(TAG, "Applied runtime limit: %uA from master 0x%02X", amps, src);
|
||||
esp_event_post(EVSE_LINK_EVENTS, LINK_EVENT_CURRENT_LIMIT_APPLIED,
|
||||
&s, sizeof(amps), portMAX_DELAY);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_HEARTBEAT_ACK:
|
||||
ESP_LOGI(TAG, "Received HEARTBEAT_ACK from master 0x%02X", src);
|
||||
if (fallback_timer) {
|
||||
xTimerReset(fallback_timer, 0);
|
||||
if (safe_mode) {
|
||||
safe_mode = false;
|
||||
uint16_t current = evse_get_runtime_charging_current();
|
||||
evse_set_runtime_charging_current(current);
|
||||
ESP_LOGI(TAG, "Exiting safe mode, restoring %uA", current);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown command 0x%02X from master 0x%02X", cmd, src);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Periodic heartbeat task ---
|
||||
static void slave_heartbeat_task(void *arg) {
|
||||
const TickType_t interval = pdMS_TO_TICKS(10000);
|
||||
for (;;) {
|
||||
send_heartbeat_frame();
|
||||
vTaskDelay(interval);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fallback safe mode callback ---
|
||||
static void fallback_timer_cb(TimerHandle_t xTimer) {
|
||||
if (!safe_mode) {
|
||||
safe_mode = true;
|
||||
ESP_LOGW(TAG, "Fallback timeout: entering safe mode");
|
||||
evse_set_runtime_charging_current(MIN_CHARGING_CURRENT_LIMIT);
|
||||
esp_event_post(EVSE_LINK_EVENTS,
|
||||
LINK_EVENT_SLAVE_OFFLINE,
|
||||
NULL, 0, portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Slave initialization ---
|
||||
void evse_link_slave_init(void) {
|
||||
if (evse_link_get_mode()!=EVSE_LINK_MODE_SLAVE || !evse_link_is_enabled()) return;
|
||||
|
||||
ESP_LOGI(TAG, "Initializing SLAVE mode (ID=0x%02X)", evse_link_get_self_id());
|
||||
|
||||
// register frame callback
|
||||
evse_link_register_rx_cb(on_frame_slave);
|
||||
|
||||
// start periodic heartbeat
|
||||
xTaskCreate(slave_heartbeat_task, "slave_hb", 4096, NULL, 5, NULL);
|
||||
|
||||
// fallback timer
|
||||
fallback_timer = xTimerCreate("fallback_tmr",
|
||||
pdMS_TO_TICKS(FALLBACK_TIMEOUT_MS),
|
||||
pdFALSE, NULL,
|
||||
fallback_timer_cb);
|
||||
if (fallback_timer) {
|
||||
xTimerStart(fallback_timer, 0);
|
||||
}
|
||||
|
||||
// react to EVSE state changes
|
||||
ESP_ERROR_CHECK(
|
||||
esp_event_handler_register(
|
||||
EVSE_EVENTS,
|
||||
EVSE_EVENT_STATE_CHANGED,
|
||||
evse_event_handler,
|
||||
NULL
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -11,67 +11,32 @@ extern "C" {
|
||||
|
||||
|
||||
/**
|
||||
* @brief Initializes the load balancer.
|
||||
*
|
||||
* This function configures the load balancer and its resources, including
|
||||
* any necessary persistence configurations, such as storage in NVS (Non-Volatile Storage).
|
||||
* This function prepares the system to perform load balancing efficiently.
|
||||
* @brief Inicializa o módulo de load balancer
|
||||
*/
|
||||
void loadbalancer_init(void);
|
||||
|
||||
/**
|
||||
* @brief Continuous task for the load balancer.
|
||||
*
|
||||
* This function executes the load balancing logic continuously, typically in a FreeRTOS task.
|
||||
* It performs balance calculations, checks the grid current and energy conditions, and adjusts
|
||||
* the outputs as necessary to ensure efficient energy consumption.
|
||||
*
|
||||
* @param param Input parameter, usually used to pass additional information or relevant context
|
||||
* for the task execution.
|
||||
* @brief Task contínua do algoritmo de balanceamento
|
||||
*/
|
||||
void loadbalancer_task(void *param);
|
||||
|
||||
/**
|
||||
* @brief Enables or disables the load balancing system.
|
||||
*
|
||||
* This function allows enabling or disabling the load balancing system. When enabled, the load
|
||||
* balancer starts managing the grid current based on the configured limits. If disabled, the system
|
||||
* operates without balancing.
|
||||
*
|
||||
* The configuration is persisted in NVS, ensuring that the choice is maintained across system restarts.
|
||||
*
|
||||
* @param value If true, enables load balancing. If false, disables it.
|
||||
* @brief Ativa ou desativa o load balancing
|
||||
*/
|
||||
void loadbalancer_set_enabled(bool value);
|
||||
|
||||
/**
|
||||
* @brief Checks if load balancing is enabled.
|
||||
*
|
||||
* This function returns the current status of the load balancing system.
|
||||
*
|
||||
* @return Returns true if load balancing is enabled, otherwise returns false.
|
||||
* @brief Verifica se o load balancing está ativo
|
||||
*/
|
||||
bool loadbalancer_is_enabled(void);
|
||||
|
||||
/**
|
||||
* @brief Sets the maximum grid current.
|
||||
*
|
||||
* This function configures the maximum grid current that can be supplied to the load balancing system.
|
||||
* The value set ensures that the system does not overload the electrical infrastructure and respects
|
||||
* the safety limits.
|
||||
*
|
||||
* @param max_grid_current The maximum allowed current (in amperes) for the load balancing system.
|
||||
* This value should be appropriate for the grid capacity and the installation.
|
||||
* @brief Define a corrente máxima do grid
|
||||
*/
|
||||
esp_err_t load_balancing_set_max_grid_current(uint8_t max_grid_current);
|
||||
|
||||
|
||||
/**
|
||||
* @brief Gets the maximum grid current.
|
||||
*
|
||||
* This function retrieves the current maximum grid current limit.
|
||||
*
|
||||
* @return The maximum grid current (in amperes).
|
||||
* @brief Obtém a corrente máxima do grid
|
||||
*/
|
||||
uint8_t load_balancing_get_max_grid_current(void);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
#include "esp_event.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include "esp_timer.h"
|
||||
|
||||
ESP_EVENT_DECLARE_BASE(LOADBALANCER_EVENTS);
|
||||
@@ -9,16 +9,39 @@ ESP_EVENT_DECLARE_BASE(LOADBALANCER_EVENTS);
|
||||
typedef enum {
|
||||
LOADBALANCER_EVENT_INIT,
|
||||
LOADBALANCER_EVENT_STATE_CHANGED,
|
||||
LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED
|
||||
LOADBALANCER_EVENT_GLOBAL_CURRENT_LIMIT,
|
||||
LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT,
|
||||
LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
|
||||
LOADBALANCER_EVENT_SLAVE_STATUS
|
||||
} loadbalancer_event_id_t;
|
||||
|
||||
typedef struct {
|
||||
float limit;
|
||||
int64_t timestamp_us;
|
||||
} loadbalancer_charging_limit_event_t;
|
||||
|
||||
typedef struct {
|
||||
bool enabled;
|
||||
int64_t timestamp_us;
|
||||
} loadbalancer_state_event_t;
|
||||
|
||||
// (opcional)
|
||||
typedef struct {
|
||||
float limit;
|
||||
int64_t timestamp_us;
|
||||
} loadbalancer_global_limit_event_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t slave_id;
|
||||
uint16_t max_current;
|
||||
int64_t timestamp_us;
|
||||
} loadbalancer_master_limit_event_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t slave_id;
|
||||
uint16_t max_current;
|
||||
int64_t timestamp_us;
|
||||
} loadbalancer_slave_limit_event_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t slave_id; // ID do slave que reportou
|
||||
bool charging; // Status de carregamento
|
||||
float hw_max_current; // Limite máximo de corrente do hardware informado
|
||||
float runtime_current; // Corrente atual de carregamento (A)
|
||||
int64_t timestamp_us; // Momento em que o status foi coletado
|
||||
} loadbalancer_slave_status_event_t;
|
||||
@@ -10,17 +10,22 @@
|
||||
#include <string.h>
|
||||
#include "meter_events.h"
|
||||
#include "evse_events.h"
|
||||
#include <math.h>
|
||||
#include "math.h"
|
||||
#include <inttypes.h> // Necessário para PRIu64
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
static const char *TAG = "loadbalancer";
|
||||
|
||||
// Configurable limits
|
||||
// Limites configuráveis
|
||||
#define MIN_CHARGING_CURRENT_LIMIT 6 // A
|
||||
#define MAX_CHARGING_CURRENT_LIMIT 32 // A
|
||||
#define MIN_GRID_CURRENT_LIMIT 6 // A
|
||||
#define MAX_GRID_CURRENT_LIMIT 100 // A
|
||||
|
||||
// Parameters
|
||||
// Parâmetros
|
||||
static uint8_t max_grid_current = MAX_GRID_CURRENT_LIMIT;
|
||||
static bool loadbalancer_enabled = false;
|
||||
|
||||
@@ -29,16 +34,109 @@ static float evse_current = 0.0f;
|
||||
static input_filter_t grid_filter;
|
||||
static input_filter_t evse_filter;
|
||||
|
||||
#define NVS_NAMESPACE "loadbalancing"
|
||||
#define NVS_MAX_GRID_CURRENT "max_grid_curr"
|
||||
#define NVS_LOADBALANCER_ENABLED "enabled"
|
||||
#define MAX_SLAVES 255
|
||||
#define CONNECTOR_COUNT (MAX_SLAVES + 1)
|
||||
|
||||
// Reset filter helper
|
||||
// Estrutura unificada para master e slaves
|
||||
typedef struct {
|
||||
uint8_t id; // 0xFF = master, 0..MAX_SLAVES-1 = slave
|
||||
bool is_master;
|
||||
bool charging;
|
||||
float hw_max_current;
|
||||
float runtime_current;
|
||||
int64_t timestamp; // microssegundos
|
||||
bool online;
|
||||
float assigned;
|
||||
} evse_connector_t;
|
||||
|
||||
static evse_connector_t connectors[CONNECTOR_COUNT];
|
||||
|
||||
const int64_t METRICS_TIMEOUT_US = 60 * 1000000; // 60 segundos
|
||||
|
||||
// Helper: inicializa array de conectores
|
||||
static void init_connectors(void)
|
||||
{
|
||||
// master em índice 0
|
||||
connectors[0] = (evse_connector_t){
|
||||
.id = 0xFF,
|
||||
.is_master = true,
|
||||
.charging = false,
|
||||
.hw_max_current = MAX_CHARGING_CURRENT_LIMIT,
|
||||
.runtime_current = 0,
|
||||
.timestamp = 0,
|
||||
.online = false,
|
||||
.assigned = 0.0f
|
||||
};
|
||||
// slaves em 1..CONNECTOR_COUNT-1
|
||||
for (int i = 1; i < CONNECTOR_COUNT; i++) {
|
||||
connectors[i] = (evse_connector_t){
|
||||
.id = (uint8_t)(i - 1),
|
||||
.is_master = false,
|
||||
.charging = false,
|
||||
.hw_max_current = 0.0f,
|
||||
.runtime_current = 0.0f,
|
||||
.timestamp = 0,
|
||||
.online = false,
|
||||
.assigned = 0.0f
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
static void input_filter_reset(input_filter_t *filter)
|
||||
{
|
||||
filter->value = 0.0f;
|
||||
}
|
||||
|
||||
// Callback de status de slave
|
||||
static void on_slave_status(void *handler_arg, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
const loadbalancer_slave_status_event_t *status = (const loadbalancer_slave_status_event_t *)data;
|
||||
|
||||
if (status->slave_id >= MAX_SLAVES) {
|
||||
ESP_LOGW(TAG, "Invalid slave_id %d", status->slave_id);
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = status->slave_id + 1; // slaves começam no índice 1
|
||||
connectors[idx].charging = status->charging;
|
||||
connectors[idx].hw_max_current = status->hw_max_current;
|
||||
connectors[idx].runtime_current = status->runtime_current;
|
||||
connectors[idx].timestamp = esp_timer_get_time();
|
||||
connectors[idx].online = true;
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"Slave %d status: charging=%d hw_max_current=%.1fA runtime_current=%.2fA",
|
||||
status->slave_id, status->charging,
|
||||
status->hw_max_current, status->runtime_current);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
static void on_evse_config_event(void* handler_arg,
|
||||
esp_event_base_t base,
|
||||
int32_t id,
|
||||
void* event_data)
|
||||
{
|
||||
const evse_config_event_data_t *evt = (const evse_config_event_data_t*) event_data;
|
||||
|
||||
int idx = 0; // MASTER INDICE 0
|
||||
connectors[idx].charging = evt->charging;
|
||||
connectors[idx].hw_max_current = evt->hw_max_current;
|
||||
connectors[idx].runtime_current = evt->runtime_current;
|
||||
connectors[idx].timestamp = esp_timer_get_time();
|
||||
connectors[idx].online = true;
|
||||
|
||||
|
||||
ESP_LOGI(TAG, "EVSE config updated: charging=%d hw_max_current=%.1f runtime_current=%.1f",
|
||||
evt->charging, evt->hw_max_current, evt->runtime_current);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Handlers de eventos externos ---
|
||||
static void loadbalancer_meter_event_handler(void *handler_arg,
|
||||
esp_event_base_t base,
|
||||
int32_t id,
|
||||
@@ -46,39 +144,18 @@ static void loadbalancer_meter_event_handler(void *handler_arg,
|
||||
{
|
||||
if (id != METER_EVENT_DATA_READY || event_data == NULL)
|
||||
return;
|
||||
|
||||
const meter_event_data_t *evt = (const meter_event_data_t *)event_data;
|
||||
|
||||
ESP_LOGI(TAG, "Received meter event from source: %s", evt->source);
|
||||
ESP_LOGI(TAG, "IRMS: [%.2f, %.2f, %.2f] A", evt->irms[0], evt->irms[1], evt->irms[2]);
|
||||
ESP_LOGI(TAG, "VRMS: [%.1f, %.1f, %.1f] V", evt->vrms[0], evt->vrms[1], evt->vrms[2]);
|
||||
ESP_LOGI(TAG, "Power: [W1=%d, W2=%d, W3=%d]", evt->watt[0], evt->watt[1], evt->watt[2]);
|
||||
ESP_LOGI(TAG, "Freq: %.2f Hz | PF: %.2f | Energy: %.3f kWh",
|
||||
evt->frequency, evt->power_factor, evt->total_energy);
|
||||
|
||||
float max_irms = evt->irms[0];
|
||||
for (int i = 1; i < 3; ++i)
|
||||
{
|
||||
if (evt->irms[i] > max_irms)
|
||||
{
|
||||
max_irms = evt->irms[i];
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Max IRMS detected: %.2f A", max_irms);
|
||||
|
||||
if (evt->source && strcmp(evt->source, "GRID") == 0)
|
||||
{
|
||||
if (evt->source && strcmp(evt->source, "GRID") == 0) {
|
||||
grid_current = input_filter_update(&grid_filter, max_irms);
|
||||
ESP_LOGI(TAG, "GRID IRMS (filtered): %.2f A", grid_current);
|
||||
}
|
||||
else if (evt->source && strcmp(evt->source, "EVSE") == 0)
|
||||
{
|
||||
} else if (evt->source && strcmp(evt->source, "EVSE") == 0) {
|
||||
evse_current = input_filter_update(&evse_filter, max_irms);
|
||||
ESP_LOGI(TAG, "EVSE IRMS (filtered): %.2f A", evse_current);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unknown meter event source: %s", evt->source);
|
||||
}
|
||||
}
|
||||
@@ -90,28 +167,32 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
|
||||
{
|
||||
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)event_data;
|
||||
|
||||
ESP_LOGI(TAG, "EVSE state changed: %d", evt->state);
|
||||
|
||||
switch (evt->state)
|
||||
{
|
||||
case EVSE_STATE_EVENT_IDLE:
|
||||
ESP_LOGI(TAG, "EVSE is IDLE - vehicle disconnected");
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_WAITING:
|
||||
ESP_LOGI(TAG, "EVSE is WAITING - connected but not charging");
|
||||
ESP_LOGI(TAG, "Local EVSE is %s - vehicle %sconnected / not charging",
|
||||
evt->state == EVSE_STATE_EVENT_IDLE ? "IDLE" : "WAITING",
|
||||
evt->state == EVSE_STATE_EVENT_IDLE ? "dis" : "");
|
||||
connectors[0].charging = false;
|
||||
connectors[0].online = true; // master está sempre online
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_CHARGING:
|
||||
ESP_LOGI(TAG, "EVSE is CHARGING - resetting filters");
|
||||
ESP_LOGI(TAG, "Local EVSE is CHARGING - resetting filters");
|
||||
grid_current = 0.0f;
|
||||
evse_current = 0.0f;
|
||||
input_filter_reset(&grid_filter);
|
||||
input_filter_reset(&evse_filter);
|
||||
connectors[0].charging = true;
|
||||
connectors[0].online = true;
|
||||
connectors[0].timestamp = esp_timer_get_time();
|
||||
break;
|
||||
|
||||
case EVSE_STATE_EVENT_FAULT:
|
||||
ESP_LOGW(TAG, "EVSE is in FAULT state - consider disabling load balancing");
|
||||
ESP_LOGW(TAG, "Local EVSE is in FAULT state - disabling load balancing temporarily");
|
||||
connectors[0].charging = false;
|
||||
connectors[0].online = true; // EVSE está online mas com falha
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -120,120 +201,66 @@ static void loadbalancer_evse_event_handler(void *handler_arg,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --- Config persistência ---
|
||||
static esp_err_t loadbalancer_load_config()
|
||||
{
|
||||
nvs_handle_t handle;
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
esp_err_t err = nvs_open("loadbalancing", NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
bool needs_commit = false;
|
||||
uint8_t temp_u8;
|
||||
|
||||
err = nvs_get_u8(handle, NVS_MAX_GRID_CURRENT, &temp_u8);
|
||||
err = nvs_get_u8(handle, "max_grid_curr", &temp_u8);
|
||||
if (err == ESP_OK && temp_u8 >= MIN_GRID_CURRENT_LIMIT && temp_u8 <= MAX_GRID_CURRENT_LIMIT)
|
||||
{
|
||||
max_grid_current = temp_u8;
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
max_grid_current = MAX_GRID_CURRENT_LIMIT;
|
||||
nvs_set_u8(handle, NVS_MAX_GRID_CURRENT, max_grid_current);
|
||||
ESP_LOGW(TAG, "max_grid_current invalid or missing, set to default: %d", max_grid_current);
|
||||
nvs_set_u8(handle, "max_grid_curr", max_grid_current);
|
||||
needs_commit = true;
|
||||
}
|
||||
|
||||
err = nvs_get_u8(handle, NVS_LOADBALANCER_ENABLED, &temp_u8);
|
||||
err = nvs_get_u8(handle, "enabled", &temp_u8);
|
||||
if (err == ESP_OK && temp_u8 <= 1)
|
||||
{
|
||||
loadbalancer_enabled = (temp_u8 != 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
loadbalancer_enabled = false;
|
||||
nvs_set_u8(handle, NVS_LOADBALANCER_ENABLED, 0);
|
||||
ESP_LOGW(TAG, "loadbalancer_enabled invalid or missing, set to false");
|
||||
nvs_set_u8(handle, "enabled", 0);
|
||||
needs_commit = true;
|
||||
}
|
||||
|
||||
if (needs_commit)
|
||||
{
|
||||
nvs_commit(handle);
|
||||
}
|
||||
|
||||
nvs_close(handle);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// --- API ---
|
||||
void loadbalancer_set_enabled(bool enabled)
|
||||
{
|
||||
ESP_LOGI(TAG, "Setting load balancing to %d", enabled);
|
||||
nvs_handle_t handle;
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
err = nvs_set_u8(handle, NVS_LOADBALANCER_ENABLED, enabled ? 1 : 0);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
if (nvs_open("loadbalancing", NVS_READWRITE, &handle) == ESP_OK) {
|
||||
nvs_set_u8(handle, "enabled", enabled ? 1 : 0);
|
||||
nvs_commit(handle);
|
||||
loadbalancer_enabled = enabled;
|
||||
ESP_LOGI(TAG, "Load balancing state saved");
|
||||
|
||||
loadbalancer_state_event_t evt = {
|
||||
.enabled = enabled,
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
|
||||
esp_event_post(LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_STATE_CHANGED,
|
||||
&evt,
|
||||
sizeof(evt),
|
||||
portMAX_DELAY);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to save loadbalancer_enabled");
|
||||
}
|
||||
|
||||
nvs_close(handle);
|
||||
}
|
||||
loadbalancer_enabled = enabled;
|
||||
loadbalancer_state_event_t evt = { .enabled = enabled, .timestamp_us = esp_timer_get_time() };
|
||||
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_STATE_CHANGED,
|
||||
&evt, sizeof(evt), portMAX_DELAY);
|
||||
}
|
||||
|
||||
esp_err_t load_balancing_set_max_grid_current(uint8_t value)
|
||||
{
|
||||
if (value < MIN_GRID_CURRENT_LIMIT || value > MAX_GRID_CURRENT_LIMIT)
|
||||
{
|
||||
ESP_LOGE(TAG, "Invalid grid current limit: %d", value);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
nvs_handle_t handle;
|
||||
esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &handle);
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to open NVS: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
err = nvs_set_u8(handle, NVS_MAX_GRID_CURRENT, value);
|
||||
if (err == ESP_OK)
|
||||
{
|
||||
if (nvs_open("loadbalancing", NVS_READWRITE, &handle) != ESP_OK)
|
||||
return ESP_FAIL;
|
||||
nvs_set_u8(handle, "max_grid_curr", value);
|
||||
nvs_commit(handle);
|
||||
max_grid_current = value;
|
||||
ESP_LOGI(TAG, "max_grid_current set to: %d", value);
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to save max_grid_current to NVS");
|
||||
}
|
||||
|
||||
nvs_close(handle);
|
||||
return err;
|
||||
max_grid_current = value;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint8_t load_balancing_get_max_grid_current(void)
|
||||
@@ -246,79 +273,168 @@ bool loadbalancer_is_enabled(void)
|
||||
return loadbalancer_enabled;
|
||||
}
|
||||
|
||||
// --- Task principal ---
|
||||
void loadbalancer_task(void *param)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (!loadbalancer_enabled)
|
||||
{
|
||||
while (true) {
|
||||
if (!loadbalancer_is_enabled()) {
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
continue;
|
||||
}
|
||||
|
||||
float available = max_grid_current - grid_current;
|
||||
int idxs[CONNECTOR_COUNT];
|
||||
int active_cnt = 0;
|
||||
int64_t now = esp_timer_get_time();
|
||||
|
||||
if (available < 0.0f)
|
||||
{
|
||||
available = 0.0f;
|
||||
// --- Atualiza estado online e conta ativos ---
|
||||
for (int i = 0; i < CONNECTOR_COUNT; i++) {
|
||||
|
||||
// --- Master nunca pode ficar offline ---
|
||||
if (connectors[i].is_master) {
|
||||
connectors[i].online = true;
|
||||
|
||||
ESP_LOGI(TAG, "Connector[%d] ONLINE (MASTER, charging=%d, hw_max_current=%.1f)",
|
||||
i, connectors[i].charging, connectors[i].hw_max_current);
|
||||
|
||||
if (connectors[i].charging) {
|
||||
idxs[active_cnt++] = i;
|
||||
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
|
||||
}
|
||||
else if (available < MIN_CHARGING_CURRENT_LIMIT)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- Ignora conectores já marcados como offline ---
|
||||
if (!connectors[i].online) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- Timeout de heartbeat para escravos ---
|
||||
if ((now - connectors[i].timestamp) >= METRICS_TIMEOUT_US) {
|
||||
connectors[i].online = false;
|
||||
ESP_LOGW(TAG, "Connector[%d] marked OFFLINE (charging=%d, timestamp_diff=%lld us)",
|
||||
i, connectors[i].charging, (long long)(now - connectors[i].timestamp));
|
||||
continue;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Connector[%d] ONLINE (charging=%d, hw_max_current=%.1f, timestamp_diff=%lld us)",
|
||||
i, connectors[i].charging, connectors[i].hw_max_current,
|
||||
(long long)(now - connectors[i].timestamp));
|
||||
|
||||
if (connectors[i].charging) {
|
||||
idxs[active_cnt++] = i;
|
||||
ESP_LOGI(TAG, "Connector[%d] is ACTIVE (charging)", i);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Active connectors: %d", active_cnt);
|
||||
|
||||
// --- Calcula corrente disponível ---
|
||||
float available = max_grid_current - grid_current;
|
||||
if (available < MIN_CHARGING_CURRENT_LIMIT) {
|
||||
available = MIN_CHARGING_CURRENT_LIMIT;
|
||||
}
|
||||
else if (available > max_grid_current)
|
||||
{
|
||||
} else if (available > max_grid_current) {
|
||||
available = max_grid_current;
|
||||
}
|
||||
ESP_LOGI(TAG, "LB Calc: available=%.1fA, active_connectors=%d", available, active_cnt);
|
||||
|
||||
ESP_LOGD(TAG, "Calculated available EVSE current: %.1f A", available);
|
||||
// --- Ordena conectores por hw_max_current (bubble sort simples) ---
|
||||
for (int a = 0; a < active_cnt - 1; a++) {
|
||||
for (int b = 0; b < active_cnt - 1 - a; b++) {
|
||||
if (connectors[idxs[b]].hw_max_current > connectors[idxs[b + 1]].hw_max_current) {
|
||||
int tmp = idxs[b];
|
||||
idxs[b] = idxs[b + 1];
|
||||
idxs[b + 1] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadbalancer_charging_limit_event_t evt = {
|
||||
.limit = available,
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
// --- Distribui corrente (water-filling) ---
|
||||
float remaining = available;
|
||||
int remaining_cnt = active_cnt;
|
||||
for (int k = 0; k < active_cnt; k++) {
|
||||
int i = idxs[k];
|
||||
float share = remaining / remaining_cnt;
|
||||
if (share >= connectors[i].hw_max_current) {
|
||||
connectors[i].assigned = connectors[i].hw_max_current;
|
||||
remaining -= connectors[i].assigned;
|
||||
remaining_cnt--;
|
||||
} else {
|
||||
for (int m = k; m < active_cnt; m++) {
|
||||
connectors[idxs[m]].assigned = share;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
esp_event_post(LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_CHARGING_LIMIT_CHANGED,
|
||||
&evt,
|
||||
sizeof(evt),
|
||||
portMAX_DELAY);
|
||||
// --- Aplica piso mínimo ---
|
||||
for (int k = 0; k < active_cnt; k++) {
|
||||
int i = idxs[k];
|
||||
if (connectors[i].assigned < MIN_CHARGING_CURRENT_LIMIT) {
|
||||
connectors[i].assigned = MIN_CHARGING_CURRENT_LIMIT;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Publica limites de corrente ---
|
||||
for (int k = 0; k < active_cnt; k++) {
|
||||
int i = idxs[k];
|
||||
uint16_t max_cur = (uint16_t)MIN(connectors[i].assigned, MAX_CHARGING_CURRENT_LIMIT);
|
||||
uint16_t current_rounded = (uint16_t)roundf(connectors[i].runtime_current);
|
||||
|
||||
if (current_rounded == max_cur) {
|
||||
continue; // sem alteração
|
||||
}
|
||||
|
||||
if (connectors[i].is_master) {
|
||||
loadbalancer_master_limit_event_t master_evt = {
|
||||
.slave_id = connectors[i].id,
|
||||
.max_current = max_cur,
|
||||
.timestamp_us = now
|
||||
};
|
||||
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_MASTER_CURRENT_LIMIT,
|
||||
&master_evt, sizeof(master_evt), portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Master limit changed -> %.1f A (runtime=%.1f A)",
|
||||
(float)max_cur, connectors[i].runtime_current);
|
||||
} else {
|
||||
loadbalancer_slave_limit_event_t slave_evt = {
|
||||
.slave_id = connectors[i].id,
|
||||
.max_current = max_cur,
|
||||
.timestamp_us = now
|
||||
};
|
||||
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
|
||||
&slave_evt, sizeof(slave_evt), portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "Slave %d limit changed -> %.1f A (runtime=%.1f A)",
|
||||
connectors[i].id, (float)max_cur, connectors[i].runtime_current);
|
||||
}
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
}
|
||||
}
|
||||
|
||||
// --- Init ---
|
||||
void loadbalancer_init(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Initializing load balancer");
|
||||
|
||||
if (loadbalancer_load_config() != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "Failed to load/init config. Using in-memory defaults.");
|
||||
}
|
||||
ESP_LOGW(TAG, "Failed to load/init config. Using defaults.");
|
||||
|
||||
init_connectors();
|
||||
input_filter_init(&grid_filter, 0.3f);
|
||||
input_filter_init(&evse_filter, 0.3f);
|
||||
|
||||
if (xTaskCreate(loadbalancer_task, "loadbalancer", 4096, NULL, 4, NULL) != pdPASS)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to create loadbalancer task");
|
||||
}
|
||||
|
||||
loadbalancer_state_event_t evt = {
|
||||
.enabled = loadbalancer_enabled,
|
||||
.timestamp_us = esp_timer_get_time()};
|
||||
|
||||
esp_event_post(LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_INIT,
|
||||
&evt,
|
||||
sizeof(evt),
|
||||
portMAX_DELAY);
|
||||
loadbalancer_state_event_t evt = {.enabled = loadbalancer_enabled, .timestamp_us = esp_timer_get_time()};
|
||||
esp_event_post(LOADBALANCER_EVENTS, LOADBALANCER_EVENT_INIT, &evt, sizeof(evt), portMAX_DELAY);
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(METER_EVENT, METER_EVENT_DATA_READY,
|
||||
&loadbalancer_meter_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED,
|
||||
&loadbalancer_evse_event_handler, NULL));
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS,
|
||||
EVSE_EVENT_STATE_CHANGED,
|
||||
&loadbalancer_evse_event_handler,
|
||||
NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(EVSE_EVENTS,EVSE_EVENT_CONFIG_UPDATED,
|
||||
&on_evse_config_event, NULL));
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(LOADBALANCER_EVENTS,LOADBALANCER_EVENT_SLAVE_STATUS,
|
||||
&on_slave_status, NULL));
|
||||
}
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
#include "driver/gpio.h"
|
||||
#include "led.h"
|
||||
#include "board_config.h"
|
||||
#include "evse_error.h"
|
||||
#include "evse_api.h"
|
||||
#include "evse_events.h"
|
||||
#include "evse_state.h"
|
||||
|
||||
#define LED_UPDATE_INTERVAL_MS 100
|
||||
#define BLOCK_TIME pdMS_TO_TICKS(10)
|
||||
|
||||
static const char *TAG = "led";
|
||||
@@ -24,32 +23,60 @@ typedef struct {
|
||||
} led_t;
|
||||
|
||||
static led_t leds[LED_ID_MAX] = {0};
|
||||
static TimerHandle_t led_update_timer = NULL;
|
||||
static evse_state_t led_state = -1;
|
||||
|
||||
// ----------------------------
|
||||
// Funções Internas
|
||||
// ----------------------------
|
||||
|
||||
static void led_update_timer_callback(TimerHandle_t xTimer);
|
||||
static void led_update(void);
|
||||
static void led_apply_by_state(evse_state_t state);
|
||||
|
||||
static inline void led_gpio_write(gpio_num_t gpio, bool level) {
|
||||
if (gpio != GPIO_NUM_NC)
|
||||
gpio_set_level(gpio, level);
|
||||
}
|
||||
|
||||
static void led_timer_callback(TimerHandle_t xTimer)
|
||||
{
|
||||
led_t *led = (led_t *)pvTimerGetTimerID(xTimer);
|
||||
led->on = !led->on;
|
||||
led_gpio_write(led->gpio, led->on);
|
||||
gpio_set_level(led->gpio, led->on);
|
||||
uint32_t next_time = led->on ? led->ontime : led->offtime;
|
||||
|
||||
xTimerChangePeriod(led->timer, pdMS_TO_TICKS(next_time), BLOCK_TIME);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Event Handler: EVSE State
|
||||
// ----------------------------
|
||||
static void evse_led_event_handler(void *arg, esp_event_base_t base, int32_t id, void *data) {
|
||||
if (base != EVSE_EVENTS || id != EVSE_EVENT_STATE_CHANGED || data == NULL) return;
|
||||
|
||||
const evse_state_event_data_t *evt = (const evse_state_event_data_t *)data;
|
||||
|
||||
// Log do evento recebido
|
||||
ESP_LOGI(TAG, "EVSE State Changed: state=%d", evt->state);
|
||||
|
||||
led_apply_pattern(LED_ID_STOP, LED_PATTERN_OFF);
|
||||
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_OFF);
|
||||
led_apply_pattern(LED_ID_ERROR, LED_PATTERN_OFF);
|
||||
|
||||
switch (evt->state) {
|
||||
case EVSE_STATE_EVENT_IDLE:
|
||||
ESP_LOGI(TAG, "EVSE_STATE_EVENT_IDLE");
|
||||
led_apply_pattern(LED_ID_STOP, LED_PATTERN_ON);
|
||||
break;
|
||||
case EVSE_STATE_EVENT_WAITING:
|
||||
ESP_LOGI(TAG, "EVSE_STATE_EVENT_WAITING");
|
||||
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_ON);
|
||||
break;
|
||||
case EVSE_STATE_EVENT_CHARGING:
|
||||
ESP_LOGI(TAG, "EVSE_STATE_EVENT_CHARGING");
|
||||
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_CHARGING_EFFECT);
|
||||
break;
|
||||
case EVSE_STATE_EVENT_FAULT:
|
||||
ESP_LOGI(TAG, "EVSE_STATE_EVENT_FAULT");
|
||||
led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown state: %d", evt->state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// Inicialização
|
||||
// ----------------------------
|
||||
@@ -87,17 +114,20 @@ void led_init(void)
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf));
|
||||
}
|
||||
|
||||
if (!led_update_timer) {
|
||||
led_update_timer = xTimerCreate("led_update_timer",
|
||||
pdMS_TO_TICKS(LED_UPDATE_INTERVAL_MS),
|
||||
pdTRUE, NULL,
|
||||
led_update_timer_callback);
|
||||
if (led_update_timer) {
|
||||
xTimerStart(led_update_timer, BLOCK_TIME);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to create LED update timer");
|
||||
}
|
||||
}
|
||||
// Registra handler de evento EVSE
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(
|
||||
EVSE_EVENTS,
|
||||
EVSE_EVENT_STATE_CHANGED,
|
||||
evse_led_event_handler,
|
||||
NULL));
|
||||
|
||||
ESP_LOGI(TAG, "LED system initialized");
|
||||
|
||||
// Aplica o estado atual do EVSE aos LEDs
|
||||
evse_state_event_data_t evt = {
|
||||
.state = EVSE_STATE_EVENT_IDLE
|
||||
};
|
||||
evse_led_event_handler(NULL, EVSE_EVENTS, EVSE_EVENT_STATE_CHANGED, &evt);
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
@@ -111,7 +141,6 @@ void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime)
|
||||
led_t *led = &leds[led_id];
|
||||
if (led->gpio == GPIO_NUM_NC) return;
|
||||
|
||||
// Evita reconfiguração idêntica
|
||||
if (led->ontime == ontime && led->offtime == offtime)
|
||||
return;
|
||||
|
||||
@@ -124,13 +153,13 @@ void led_set_state(led_id_t led_id, uint16_t ontime, uint16_t offtime)
|
||||
|
||||
if (ontime == 0) {
|
||||
led->on = false;
|
||||
led_gpio_write(led->gpio, 0);
|
||||
gpio_set_level(led->gpio, 0);
|
||||
} else if (offtime == 0) {
|
||||
led->on = true;
|
||||
led_gpio_write(led->gpio, 1);
|
||||
gpio_set_level(led->gpio, 1);
|
||||
} else {
|
||||
led->on = true;
|
||||
led_gpio_write(led->gpio, 1);
|
||||
gpio_set_level(led->gpio, 1);
|
||||
|
||||
if (!led->timer) {
|
||||
led->timer = xTimerCreate("led_timer", pdMS_TO_TICKS(ontime),
|
||||
@@ -180,66 +209,3 @@ void led_apply_pattern(led_id_t id, led_pattern_t pattern)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Controle por Estado
|
||||
// ----------------------------
|
||||
|
||||
static void led_apply_by_state(evse_state_t state)
|
||||
{
|
||||
// Reset todos
|
||||
led_apply_pattern(LED_ID_STOP, LED_PATTERN_OFF);
|
||||
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_OFF);
|
||||
led_apply_pattern(LED_ID_ERROR, LED_PATTERN_OFF);
|
||||
|
||||
switch (state) {
|
||||
case EVSE_STATE_A:
|
||||
led_apply_pattern(LED_ID_STOP, LED_PATTERN_ON);
|
||||
break;
|
||||
case EVSE_STATE_B1:
|
||||
case EVSE_STATE_B2:
|
||||
case EVSE_STATE_C1:
|
||||
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_ON);
|
||||
break;
|
||||
case EVSE_STATE_C2:
|
||||
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_CHARGING_EFFECT);
|
||||
break;
|
||||
case EVSE_STATE_D1:
|
||||
case EVSE_STATE_D2:
|
||||
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_BLINK_FAST);
|
||||
break;
|
||||
case EVSE_STATE_E:
|
||||
case EVSE_STATE_F:
|
||||
led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------
|
||||
// Timer Update
|
||||
// ----------------------------
|
||||
|
||||
static void led_update(void)
|
||||
{
|
||||
if (evse_error_is_active()) {
|
||||
led_apply_pattern(LED_ID_ERROR, LED_PATTERN_BLINK_FAST);
|
||||
led_apply_pattern(LED_ID_STOP, LED_PATTERN_OFF);
|
||||
led_apply_pattern(LED_ID_CHARGING, LED_PATTERN_OFF);
|
||||
return;
|
||||
}
|
||||
|
||||
evse_state_t current = evse_get_state();
|
||||
|
||||
if (current != led_state) {
|
||||
led_state = current;
|
||||
led_apply_by_state(current);
|
||||
}
|
||||
}
|
||||
|
||||
static void led_update_timer_callback(TimerHandle_t xTimer)
|
||||
{
|
||||
(void)xTimer;
|
||||
led_update();
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
0
components/protocols/webfolder/index.html
Executable file → Normal file
0
components/protocols/webfolder/index.html
Executable file → Normal file
@@ -6,6 +6,7 @@ set(srcs
|
||||
"src/network_api.c"
|
||||
"src/meters_settings_api.c"
|
||||
"src/loadbalancing_settings_api.c"
|
||||
"src/evse_link_config_api.c"
|
||||
"src/dashboard_api.c"
|
||||
"src/static_file_api.c"
|
||||
)
|
||||
@@ -14,7 +15,7 @@ idf_component_register(
|
||||
SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS "src"
|
||||
PRIV_REQUIRES nvs_flash esp_http_server esp_netif vfs spiffs json evse meter_manager loadbalancer
|
||||
PRIV_REQUIRES nvs_flash esp_http_server esp_netif vfs spiffs json evse meter_manager loadbalancer evse_link
|
||||
)
|
||||
|
||||
# SPIFFS image (opcional)
|
||||
|
||||
31
components/rest_api/include/evse_link_config_api.h
Normal file
31
components/rest_api/include/evse_link_config_api.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// =========================
|
||||
// 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
|
||||
108
components/rest_api/src/evse_link_config_api.c
Normal file
108
components/rest_api/src/evse_link_config_api.c
Normal file
@@ -0,0 +1,108 @@
|
||||
#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);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#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"
|
||||
@@ -45,8 +46,10 @@ esp_err_t rest_server_init(const char *base_path) {
|
||||
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_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;
|
||||
|
||||
2
components/protocols/webfolder/assets/index-Brq1-keN.css → components/rest_api/webfolder/assets/index-AuNGQ-2m.css
Executable file → Normal file
2
components/protocols/webfolder/assets/index-Brq1-keN.css → components/rest_api/webfolder/assets/index-AuNGQ-2m.css
Executable file → Normal file
File diff suppressed because one or more lines are too long
51
components/rest_api/webfolder/assets/index-ClgQvp_F.js
Normal file
51
components/rest_api/webfolder/assets/index-ClgQvp_F.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -13,8 +13,8 @@
|
||||
}
|
||||
</style>
|
||||
<title>Vite + React</title>
|
||||
<script type="module" crossorigin src="/assets/index-zZ02wEhQ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DvcKJk-E.css">
|
||||
<script type="module" crossorigin src="/assets/index-ClgQvp_F.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-AuNGQ-2m.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "loadbalancer.h"
|
||||
#include "meter_manager.h"
|
||||
#include "buzzer.h"
|
||||
#include "evse_link.h"
|
||||
|
||||
|
||||
#define EVSE_MANAGER_TICK_PERIOD_MS 1000
|
||||
@@ -235,8 +236,7 @@ static void init_modules(void) {
|
||||
loadbalancer_init();
|
||||
meter_manager_init();
|
||||
meter_manager_start();
|
||||
|
||||
|
||||
evse_link_init();
|
||||
|
||||
// wifi_ap_start();
|
||||
// Outros módulos (descomente conforme necessário)
|
||||
|
||||
4749
projeto_parte1.c
4749
projeto_parte1.c
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
import os
|
||||
|
||||
TAMANHO_MAX = 100000 # Limite por arquivo
|
||||
TAMANHO_MAX = 200000 # Limite por arquivo
|
||||
|
||||
def coletar_arquivos(diretorios, extensoes=(".c", ".h")):
|
||||
arquivos = []
|
||||
@@ -53,7 +53,7 @@ def unir_em_partes(arquivos, prefixo="projeto_parte", limite=TAMANHO_MAX):
|
||||
def main():
|
||||
diretorio_main = "main"
|
||||
componentes_escolhidos = [
|
||||
"rest_api" , "auth"
|
||||
"evse", "loadbalancer"
|
||||
]
|
||||
|
||||
diretorios_componentes = [os.path.join("components", nome) for nome in componentes_escolhidos]
|
||||
|
||||
Reference in New Issue
Block a user