#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 #include #include #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); }