new module
This commit is contained in:
@@ -1,16 +1,31 @@
|
||||
// === Início de: components/evse_link/include/evse_link_events.h ===
|
||||
#ifndef EVSE_LINK_EVENTS_H_
|
||||
#define EVSE_LINK_EVENTS_H_
|
||||
|
||||
#include "esp_event.h"
|
||||
|
||||
// Base de eventos do EVSE-Link
|
||||
ESP_EVENT_DECLARE_BASE(EVSE_LINK_EVENTS);
|
||||
|
||||
// Tamanho máximo de tag propagada via EVSE-Link (inclui NUL)
|
||||
#define EVSE_LINK_TAG_MAX_LEN 32
|
||||
|
||||
// IDs de eventos EVSE-Link
|
||||
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
|
||||
LINK_EVENT_SLAVE_CONFIG_UPDATED, // config atualizada pelo master
|
||||
LINK_EVENT_REMOTE_AUTH_GRANTED // autorização remota (master -> slave)
|
||||
} evse_link_event_t;
|
||||
|
||||
// Payload para LINK_EVENT_REMOTE_AUTH_GRANTED
|
||||
typedef struct {
|
||||
char tag[EVSE_LINK_TAG_MAX_LEN]; // idTag enviada pelo master
|
||||
} evse_link_auth_grant_event_t;
|
||||
|
||||
#endif // EVSE_LINK_EVENTS_H_
|
||||
|
||||
// === Fim de: components/evse_link/include/evse_link_events.h ===
|
||||
|
||||
@@ -5,14 +5,20 @@
|
||||
#include <stdbool.h>
|
||||
#include "driver/uart.h"
|
||||
|
||||
// UART configuration
|
||||
#define UART_PORT UART_NUM_2
|
||||
// UART instance and configuration
|
||||
#define UART_PORT UART_NUM_2 // Usa a UART2
|
||||
#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
|
||||
// GPIO pin assignments for UART (ajuste conforme o hardware)
|
||||
#define UART_TXD 17 // TX -> DI do MAX3485
|
||||
#define UART_RXD 16 // RX -> RO do MAX3485
|
||||
#define UART_RTS 2 // RTS -> DE+RE do MAX3485
|
||||
|
||||
// Conveniência: nomes usados no .c
|
||||
#define TX_PIN UART_TXD
|
||||
#define RX_PIN UART_RXD
|
||||
#define RTS_PIN UART_RTS
|
||||
|
||||
// Frame delimiters
|
||||
#define MAGIC_START 0x7E
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// components/evse_link_framing/src/evse_link_framing.c
|
||||
|
||||
#include "evse_link_framing.h"
|
||||
#include "driver/uart.h"
|
||||
#include "freertos/semphr.h"
|
||||
@@ -8,75 +6,124 @@
|
||||
|
||||
static const char *TAG = "evse_framing";
|
||||
|
||||
static SemaphoreHandle_t tx_mutex;
|
||||
static SemaphoreHandle_t tx_mutex = NULL;
|
||||
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) {
|
||||
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);
|
||||
if (crc & 0x80) {
|
||||
crc = (uint8_t)((crc << 1) ^ 0x07);
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void evse_link_framing_init(void) {
|
||||
// Create mutex for TX
|
||||
void evse_link_framing_init(void)
|
||||
{
|
||||
// Mutex para proteger TX (framings de várias tasks)
|
||||
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){
|
||||
|
||||
// Instala driver UART
|
||||
uart_driver_install(UART_PORT,
|
||||
UART_RX_BUF_SIZE * 2, // RX buffer
|
||||
0, // TX buffer (0 = usa buffer interno)
|
||||
0,
|
||||
NULL,
|
||||
0);
|
||||
|
||||
uart_config_t cfg = {
|
||||
.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);
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
uart_param_config(UART_PORT, &cfg);
|
||||
|
||||
// Define pinos: TX, RX e RTS (RTS ligado a DE+RE do transceiver RS485)
|
||||
uart_set_pin(UART_PORT,
|
||||
TX_PIN, // MB_UART_TXD (ex: GPIO17)
|
||||
RX_PIN, // MB_UART_RXD (ex: GPIO16)
|
||||
RTS_PIN, // MB_UART_RTS (ex: GPIO2, DE+RE)
|
||||
UART_PIN_NO_CHANGE);
|
||||
|
||||
// Modo RS485 half-duplex: driver controla RTS/DE/RE automaticamente
|
||||
uart_set_mode(UART_PORT, UART_MODE_RS485_HALF_DUPLEX);
|
||||
|
||||
ESP_LOGI(TAG, "Framing init: UART%d TX=%d RX=%d RTS(DE/RE)=%d baud=%d",
|
||||
UART_PORT, TX_PIN, RX_PIN, RTS_PIN, UART_BAUDRATE);
|
||||
}
|
||||
|
||||
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;
|
||||
const uint8_t *payload, uint8_t len)
|
||||
{
|
||||
if (len > EVSE_LINK_MAX_PAYLOAD) {
|
||||
ESP_LOGW(TAG, "Payload too large: %u (max=%u)",
|
||||
len, EVSE_LINK_MAX_PAYLOAD);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (xSemaphoreTake(tx_mutex, portMAX_DELAY) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "Failed to take TX mutex");
|
||||
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++] = (uint8_t)(len + 1); // LEN = SEQ + payload
|
||||
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];
|
||||
if (len > 0 && payload != NULL) {
|
||||
memcpy(&frame[idx], payload, len);
|
||||
idx += len;
|
||||
}
|
||||
|
||||
// CRC cobre: 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);
|
||||
uint8_t crc = crc8(crc_input, (uint8_t)(3 + 1 + len));
|
||||
frame[idx++] = crc;
|
||||
|
||||
frame[idx++] = MAGIC_END;
|
||||
|
||||
uart_write_bytes(UART_PORT, (const char *)frame, idx);
|
||||
uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(10));
|
||||
// Envia frame completo
|
||||
int written = uart_write_bytes(UART_PORT, (const char *)frame, idx);
|
||||
if (written != idx) {
|
||||
ESP_LOGW(TAG, "uart_write_bytes wrote %d of %d", written, idx);
|
||||
}
|
||||
|
||||
// Aguarda TX terminar (o driver controla DE/RE via RTS)
|
||||
uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(20));
|
||||
|
||||
xSemaphoreGive(tx_mutex);
|
||||
|
||||
ESP_LOGD(TAG, "Sent frame dest=0x%02X src=0x%02X len=%u seq=%u",
|
||||
ESP_LOGI(TAG, "Sent frame dest=0x%02X src=0x%02X len=%u seq=%u",
|
||||
dest, src, len, seq);
|
||||
|
||||
seq++; // increment sequence after sending
|
||||
seq++; // incrementa sequência após envio
|
||||
return true;
|
||||
}
|
||||
|
||||
void evse_link_framing_recv_byte(uint8_t b) {
|
||||
// State machine for frame parsing
|
||||
void evse_link_framing_recv_byte(uint8_t b)
|
||||
{
|
||||
// Máquina de estados para parsing do frame
|
||||
static enum {
|
||||
ST_WAIT_START,
|
||||
ST_WAIT_START = 0,
|
||||
ST_WAIT_DEST,
|
||||
ST_WAIT_SRC,
|
||||
ST_WAIT_LEN,
|
||||
@@ -88,7 +135,7 @@ void evse_link_framing_recv_byte(uint8_t b) {
|
||||
|
||||
static uint8_t rx_dest;
|
||||
static uint8_t rx_src;
|
||||
static uint8_t rx_len;
|
||||
static uint8_t rx_len; // inclui SEQ + payload
|
||||
static uint8_t rx_seq;
|
||||
static uint8_t rx_buf[EVSE_LINK_MAX_PAYLOAD];
|
||||
static uint8_t rx_pos;
|
||||
@@ -112,19 +159,25 @@ void evse_link_framing_recv_byte(uint8_t b) {
|
||||
break;
|
||||
|
||||
case ST_WAIT_LEN:
|
||||
rx_len = b; // includes SEQ + payload
|
||||
rx_len = b; // LEN = 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;
|
||||
if (rx_len > 1) {
|
||||
rx_state = ST_READING;
|
||||
} else {
|
||||
rx_state = ST_WAIT_CRC;
|
||||
}
|
||||
break;
|
||||
|
||||
case ST_READING:
|
||||
rx_buf[rx_pos++] = b;
|
||||
if (rx_pos >= (rx_len - 1)) { // all payload bytes read
|
||||
if (rx_pos < EVSE_LINK_MAX_PAYLOAD) {
|
||||
rx_buf[rx_pos++] = b;
|
||||
}
|
||||
if (rx_pos >= (uint8_t)(rx_len - 1)) { // payload completo
|
||||
rx_state = ST_WAIT_CRC;
|
||||
}
|
||||
break;
|
||||
@@ -136,33 +189,43 @@ void evse_link_framing_recv_byte(uint8_t b) {
|
||||
|
||||
case ST_WAIT_END:
|
||||
if (b == MAGIC_END) {
|
||||
// Build data for CRC calculation
|
||||
// Monta buffer para verificar CRC:
|
||||
// DEST + SRC + LEN + SEQ + PAYLOAD
|
||||
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;
|
||||
if (rx_len > 1) {
|
||||
memcpy(&temp[temp_len], rx_buf, rx_len - 1);
|
||||
temp_len += rx_len - 1;
|
||||
}
|
||||
|
||||
uint8_t expected = crc8(temp, temp_len);
|
||||
uint8_t expected = crc8(temp, (uint8_t)temp_len);
|
||||
if (expected == rx_crc) {
|
||||
uint8_t payload_len = (uint8_t)(rx_len - 1); // exclui SEQ
|
||||
if (rx_cb) {
|
||||
rx_cb(rx_src, rx_dest, rx_buf, rx_len - 1);
|
||||
rx_cb(rx_src, rx_dest, rx_buf, payload_len);
|
||||
}
|
||||
ESP_LOGD(TAG, "Frame OK src=0x%02X dest=0x%02X len=%u seq=%u",
|
||||
rx_src, rx_dest, rx_len - 1, rx_seq);
|
||||
rx_src, rx_dest, payload_len, rx_seq);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "CRC mismatch: expected=0x%02X got=0x%02X",
|
||||
expected, rx_crc);
|
||||
}
|
||||
}
|
||||
// Em qualquer caso, volta a esperar novo frame
|
||||
rx_state = ST_WAIT_START;
|
||||
break;
|
||||
|
||||
default:
|
||||
rx_state = ST_WAIT_START;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void evse_link_framing_register_cb(evse_link_frame_cb_t cb) {
|
||||
void evse_link_framing_register_cb(evse_link_frame_cb_t cb)
|
||||
{
|
||||
rx_cb = cb;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// === components/evse_link/src/evse_link_master.c ===
|
||||
|
||||
#include "evse_link.h"
|
||||
#include "evse_link_events.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/timers.h"
|
||||
@@ -8,94 +7,142 @@
|
||||
#include "esp_event.h"
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "loadbalancer_events.h"
|
||||
#include "auth_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_POLL 0x01
|
||||
#define CMD_HEARTBEAT 0x02
|
||||
#define CMD_HEARTBEAT_ACK 0x09
|
||||
#define CMD_CONFIG_BROADCAST 0x03
|
||||
#define CMD_SET_CURRENT 0x08
|
||||
#define CMD_SET_CURRENT 0x08
|
||||
#define CMD_AUTH_GRANTED 0x0A // novo: master concede autorização a slave
|
||||
|
||||
// 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
|
||||
#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 {
|
||||
typedef struct
|
||||
{
|
||||
TimerHandle_t timer;
|
||||
TickType_t interval;
|
||||
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) };
|
||||
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;
|
||||
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;
|
||||
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)
|
||||
};
|
||||
(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);
|
||||
}
|
||||
|
||||
// --- Bridge AUTH -> EVSE-Link: enviar AUTH_GRANTED para slaves ---
|
||||
static void on_auth_result(void *arg, esp_event_base_t base, int32_t id, void *data)
|
||||
{
|
||||
if (base != AUTH_EVENTS || id != AUTH_EVENT_TAG_PROCESSED || data == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auth_tag_event_data_t *ev = (const auth_tag_event_data_t *)data;
|
||||
|
||||
if (!ev->authorized) {
|
||||
ESP_LOGI(TAG, "Tag %s not authorized, not propagating to slaves", ev->tag);
|
||||
return;
|
||||
}
|
||||
|
||||
// Construir payload: [ CMD_AUTH_GRANTED, tag..., '\0' ]
|
||||
uint8_t buf[1 + EVSE_LINK_TAG_MAX_LEN];
|
||||
buf[0] = CMD_AUTH_GRANTED;
|
||||
|
||||
// Copiar tag e garantir NUL
|
||||
strncpy((char *)&buf[1], ev->tag, EVSE_LINK_TAG_MAX_LEN - 1);
|
||||
((char *)&buf[1])[EVSE_LINK_TAG_MAX_LEN - 1] = '\0';
|
||||
|
||||
uint8_t payload_len = 1 + (uint8_t)(strlen((char *)&buf[1]) + 1); // opcode + tag + '\0'
|
||||
|
||||
// Neste exemplo: broadcast para todos os slaves (0xFF)
|
||||
uint8_t dest = 0xFF;
|
||||
|
||||
if (!evse_link_send(dest, buf, payload_len)) {
|
||||
ESP_LOGW(TAG, "Failed to send CMD_AUTH_GRANTED to dest=0x%02X for tag=%s",
|
||||
dest, (char *)&buf[1]);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Sent CMD_AUTH_GRANTED to dest=0x%02X for tag=%s",
|
||||
dest, (char *)&buf[1]);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Polling broadcast callback ---
|
||||
static void poll_timer_cb(TimerHandle_t xTimer) {
|
||||
ESP_LOGD(TAG, "Broadcasting CMD_POLL to all slaves");;
|
||||
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) {
|
||||
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;
|
||||
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
|
||||
switch (cmd)
|
||||
{
|
||||
case CMD_HEARTBEAT:
|
||||
{
|
||||
if (len != LEN_HEARTBEAT)
|
||||
{ // 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);
|
||||
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,
|
||||
.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()
|
||||
};
|
||||
|
||||
.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 };
|
||||
uint8_t ack[] = {CMD_HEARTBEAT_ACK};
|
||||
evse_link_send(src, ack, sizeof(ack));
|
||||
ESP_LOGD(TAG, "Sent HEARTBEAT_ACK to 0x%02X", src);
|
||||
break;
|
||||
@@ -115,10 +162,11 @@ static void on_frame_master(uint8_t src, uint8_t dest,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Master initialization ---
|
||||
void evse_link_master_init(void) {
|
||||
if (evse_link_get_mode() != EVSE_LINK_MODE_MASTER || !evse_link_is_enabled()) {
|
||||
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());
|
||||
@@ -132,9 +180,15 @@ void evse_link_master_init(void) {
|
||||
LOADBALANCER_EVENTS,
|
||||
LOADBALANCER_EVENT_SLAVE_CURRENT_LIMIT,
|
||||
on_new_limit,
|
||||
NULL
|
||||
)
|
||||
);
|
||||
NULL));
|
||||
|
||||
// escutar resultado do AUTH para propagar autorização aos slaves
|
||||
ESP_ERROR_CHECK(
|
||||
esp_event_handler_register(
|
||||
AUTH_EVENTS,
|
||||
AUTH_EVENT_TAG_PROCESSED,
|
||||
on_auth_result,
|
||||
NULL));
|
||||
|
||||
// create and start poll timer
|
||||
poll_timer.timer = xTimerCreate("poll_tmr",
|
||||
@@ -150,3 +204,4 @@ void evse_link_master_init(void) {
|
||||
hb_timer_cb);
|
||||
xTimerStart(hb_timer.timer, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ static const char *TAG = "evse_link_slave";
|
||||
#define CMD_CONFIG_BROADCAST 0x03
|
||||
#define CMD_SET_CURRENT 0x08
|
||||
#define CMD_HEARTBEAT_ACK 0x09
|
||||
#define CMD_AUTH_GRANTED 0x0A // novo: master concede autorização
|
||||
|
||||
// payload lengths (exclui seq byte)
|
||||
#define LEN_POLL_REQ 1 // [ CMD_POLL ]
|
||||
@@ -82,6 +83,11 @@ static void on_frame_slave(uint8_t src, uint8_t dest,
|
||||
break;
|
||||
|
||||
case CMD_SET_CURRENT: {
|
||||
if (len < LEN_SET_CURRENT) {
|
||||
ESP_LOGW(TAG, "SET_CURRENT from 0x%02X with invalid length %u", src, len);
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -103,6 +109,33 @@ static void on_frame_slave(uint8_t src, uint8_t dest,
|
||||
}
|
||||
break;
|
||||
|
||||
case CMD_AUTH_GRANTED: {
|
||||
if (len < 2) {
|
||||
ESP_LOGW(TAG, "CMD_AUTH_GRANTED from 0x%02X with invalid length %u", src, len);
|
||||
break;
|
||||
}
|
||||
|
||||
const char *tag = (const char *)&payload[1];
|
||||
|
||||
evse_link_auth_grant_event_t ev = {0};
|
||||
strncpy(ev.tag, tag, EVSE_LINK_TAG_MAX_LEN - 1);
|
||||
ev.tag[EVSE_LINK_TAG_MAX_LEN - 1] = '\0';
|
||||
|
||||
ESP_LOGI(TAG, "Received CMD_AUTH_GRANTED from master 0x%02X, tag='%s'", src, ev.tag);
|
||||
|
||||
esp_err_t err = esp_event_post(
|
||||
EVSE_LINK_EVENTS,
|
||||
LINK_EVENT_REMOTE_AUTH_GRANTED,
|
||||
&ev,
|
||||
sizeof(ev),
|
||||
portMAX_DELAY);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to post LINK_EVENT_REMOTE_AUTH_GRANTED: %s", esp_err_to_name(err));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown command 0x%02X from master 0x%02X", cmd, src);
|
||||
}
|
||||
@@ -162,3 +195,5 @@ void evse_link_slave_init(void) {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// === Fim de: components/evse_link/src/evse_link_slave.c ===
|
||||
|
||||
Reference in New Issue
Block a user