evse_link feature

This commit is contained in:
2025-08-05 16:55:11 +01:00
parent bd587a10c0
commit 0d0dc5b129
35 changed files with 4353 additions and 2257 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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:

View File

@@ -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" {

View File

@@ -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

View 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)

View 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 (0255)
// dest: device address of receiver (0255 or 0xFF broadcast)
// payload: pointer to received data buffer (command + data)
// len: length of payload (0255)
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_

View 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_

View 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_

View 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);
}

View 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);

View 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;
}

View 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);
}

View 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,
&amps, 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
)
);
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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));
}

View File

@@ -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
View File

View 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)

View 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

View 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 (0254)");
return ESP_FAIL;
}
evse_link_set_self_id((uint8_t)id);
ESP_LOGI(TAG, " set self_id = %d", id);
}
cJSON_Delete(json);
httpd_resp_sendstr(req, "Link settings updated");
return ESP_OK;
}
void register_link_config_handlers(httpd_handle_t server, void *ctx) {
httpd_uri_t get = {
.uri = "/api/v1/config/link",
.method = HTTP_GET,
.handler = link_config_get_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &get);
httpd_uri_t post = {
.uri = "/api/v1/config/link",
.method = HTTP_POST,
.handler = link_config_post_handler,
.user_ctx = ctx
};
httpd_register_uri_handler(server, &post);
}

View File

@@ -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;

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

File diff suppressed because one or more lines are too long

View File

@@ -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>

View File

@@ -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)

File diff suppressed because it is too large Load Diff

View File

@@ -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]