208 lines
6.6 KiB
C
208 lines
6.6 KiB
C
#include "evse_link.h"
|
|
#include "evse_link_events.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 <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_CONFIG_BROADCAST 0x03
|
|
#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
|
|
|
|
// 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);
|
|
}
|
|
|
|
// --- 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");
|
|
;
|
|
// 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 != 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);
|
|
|
|
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));
|
|
|
|
// 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",
|
|
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);
|
|
}
|
|
|