chore: snapshot before shared RS485 bus integration

This commit is contained in:
2026-07-22 15:36:04 +01:00
parent ef02e5c5f5
commit 2a803fcef1
167 changed files with 5749 additions and 1128 deletions

View File

@@ -1,13 +1,27 @@
// meter_ea777.c — Driver Modbus RTU para EARU EA777 (ESP-IDF)
// Suporta 1 ou 2 EA777 no mesmo bus RS485:
// GRID -> slave ID 1
// EVSE -> slave ID 2
// Usa um único Modbus master e uma única task de leitura.
#include "meter_ea777.h"
#include "meter_events.h"
#include "modbus_params.h"
#include "mbcontroller.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "driver/uart.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include <stddef.h>
#include <string.h>
#include "meter_ea777.h"
#include <math.h>
#include <inttypes.h>
#define TAG "serial_mdb_ea777"
@@ -20,17 +34,30 @@
#define MB_UART_RXD 16
#define MB_UART_RTS 2 // pino DE/RE do transceiver RS-485
// ===== EA777 no mesmo bus =====
#define EA777_GRID_SLAVE_ID 1
#define EA777_EVSE_SLAVE_ID 2
// ===== Timings =====
#define UPDATE_INTERVAL (5000 / portTICK_PERIOD_MS)
#define POLL_INTERVAL (200 / portTICK_PERIOD_MS)
#define EA777_LOCK_TIMEOUT pdMS_TO_TICKS(10000)
// ===== Helpers =====
#define STR(fieldname) ((const char *)(fieldname))
#define OPTS(min_val, max_val, step_val) {.opt1 = min_val, .opt2 = max_val, .opt3 = step_val}
#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
// ===== Estado =====
static bool is_initialized = false;
static volatile bool task_should_run = false;
static TaskHandle_t meter_task = NULL;
static SemaphoreHandle_t ea777_lock = NULL;
static inline uint32_t ea777_swap_words_u32(uint32_t x)
{
return ((x & 0xFFFFu) << 16) | ((x >> 16) & 0xFFFFu);
}
// ============================================================================
// ============ MAPA DE REGISTROS EA777 (Holding 0x03) ========================
@@ -45,7 +72,6 @@ static TaskHandle_t meter_task = NULL;
#define EA777_L3CURRENT 0x0005
// Potência ativa total (W)
#define EA777_TOTAL_ACTIVE_P 0x0007
// (se quiser por fase, pode usar 0x0008/0x0009/0x000A)
// Fator de potência por fase (0.001)
#define EA777_PF_L1 0x0014
#define EA777_PF_L2 0x0015
@@ -56,7 +82,7 @@ static TaskHandle_t meter_task = NULL;
#define EA777_TOTAL_ACTIVE_E 0x001D
// ============================================================================
// ============ CIDs ============
// ============ CIDs locais por meter ============
enum
{
CID_EA777_L1_VOLTAGE = 0,
@@ -71,11 +97,42 @@ enum
CID_EA777_PF_L3,
CID_EA777_FREQUENCY,
CID_EA777_TOTAL_ACTIVE_E,
CID_EA777_COUNT,
};
// ======= Descritores (Holding registers) =======
typedef enum
{
EA777_SLOT_GRID = 0,
EA777_SLOT_EVSE,
EA777_MAX_INSTANCES
} ea777_slot_t;
typedef struct
{
bool registered;
const char *source; // "GRID" ou "EVSE"
uint8_t slave_id; // 1 ou 2
uint16_t cid_base; // atribuído dinamicamente em ea777_rebuild_descriptors_locked()
} ea777_instance_t;
static ea777_instance_t ea777_instances[EA777_MAX_INSTANCES] = {
[EA777_SLOT_GRID] = {
.registered = false,
.source = "GRID",
.slave_id = EA777_GRID_SLAVE_ID,
.cid_base = 0,
},
[EA777_SLOT_EVSE] = {
.registered = false,
.source = "EVSE",
.slave_id = EA777_EVSE_SLAVE_ID,
.cid_base = 0,
},
};
// ======= Template de descritores (Holding registers) =======
// Nota: param_offset = 0 -> não usamos holding_reg_params_t aqui.
const mb_parameter_descriptor_t device_parameters_ea777[] = {
static const mb_parameter_descriptor_t ea777_param_template[CID_EA777_COUNT] = {
// Tensões (0.1 V)
{CID_EA777_L1_VOLTAGE, STR("L1 Voltage"), STR("V"), 1,
MB_PARAM_HOLDING, EA777_L1VOLTAGE, 1,
@@ -131,18 +188,278 @@ const mb_parameter_descriptor_t device_parameters_ea777[] = {
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
};
const uint16_t num_device_parameters_ea777 =
sizeof(device_parameters_ea777) / sizeof(device_parameters_ea777[0]);
// O esp-modbus exige cid e param_key únicos no Data Dictionary.
// Como GRID e EVSE usam o mesmo mapa de registos, o param_key precisa de prefixo por instância.
static const char *const ea777_param_keys[EA777_MAX_INSTANCES][CID_EA777_COUNT] = {
[EA777_SLOT_GRID] = {
"GRID L1 Voltage",
"GRID L2 Voltage",
"GRID L3 Voltage",
"GRID L1 Current",
"GRID L2 Current",
"GRID L3 Current",
"GRID Total Active Power",
"GRID L1 PF",
"GRID L2 PF",
"GRID L3 PF",
"GRID Frequency",
"GRID Total Active Energy",
},
[EA777_SLOT_EVSE] = {
"EVSE L1 Voltage",
"EVSE L2 Voltage",
"EVSE L3 Voltage",
"EVSE L1 Current",
"EVSE L2 Current",
"EVSE L3 Current",
"EVSE Total Active Power",
"EVSE L1 PF",
"EVSE L2 PF",
"EVSE L3 PF",
"EVSE Frequency",
"EVSE Total Active Energy",
},
};
// ===== Post do evento de medição =====
static void meter_ea777_post_event(float *voltage, float *current, int *power_w,
float freq_hz, float pf_avg, float total_kwh)
static mb_parameter_descriptor_t device_parameters_ea777[EA777_MAX_INSTANCES * CID_EA777_COUNT];
static uint16_t num_device_parameters_ea777 = 0;
static bool ea777_descriptor_dirty = true;
static bool ea777_has_registered_instance_locked(void)
{
for (uint8_t i = 0; i < EA777_MAX_INSTANCES; ++i)
{
if (ea777_instances[i].registered)
return true;
}
return false;
}
static esp_err_t ea777_rebuild_descriptors_locked(void)
{
num_device_parameters_ea777 = 0;
ea777_descriptor_dirty = true;
for (uint8_t inst_idx = 0; inst_idx < EA777_MAX_INSTANCES; ++inst_idx)
{
ea777_instance_t *inst = &ea777_instances[inst_idx];
if (!inst->registered)
continue;
// Mantém os CIDs contíguos no descriptor ativo.
// Isto evita problemas em versões do esp-modbus que tratam CID como índice.
inst->cid_base = num_device_parameters_ea777;
for (uint16_t local_cid = 0; local_cid < CID_EA777_COUNT; ++local_cid)
{
mb_parameter_descriptor_t *dst = &device_parameters_ea777[num_device_parameters_ea777++];
*dst = ea777_param_template[local_cid];
dst->cid = inst->cid_base + local_cid;
dst->param_key = ea777_param_keys[inst_idx][local_cid];
dst->mb_slave_addr = inst->slave_id;
}
}
ea777_descriptor_dirty = true;
ESP_LOGI(TAG, "EA777 descriptor table prepared: %u parameters", num_device_parameters_ea777);
return ESP_OK;
}
static esp_err_t ea777_apply_descriptors_locked(void)
{
if (num_device_parameters_ea777 == 0)
return ESP_ERR_INVALID_STATE;
if (!ea777_descriptor_dirty)
return ESP_OK;
esp_err_t err = mbc_master_set_descriptor(device_parameters_ea777, num_device_parameters_ea777);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "mbc_master_set_descriptor failed: %s", esp_err_to_name(err));
return err;
}
ea777_descriptor_dirty = false;
ESP_LOGI(TAG, "EA777 descriptor table applied: %u parameters", num_device_parameters_ea777);
return ESP_OK;
}
static esp_err_t ea777_master_init_once(void)
{
if (is_initialized)
return ESP_OK;
if (!ea777_lock)
{
ea777_lock = xSemaphoreCreateMutex();
if (!ea777_lock)
return ESP_ERR_NO_MEM;
}
if (uart_is_driver_installed(MB_PORT_NUM))
{
uart_driver_delete(MB_PORT_NUM);
ESP_LOGI(TAG, "UART driver deleted before EA777 init");
}
(void)mbc_master_destroy();
ESP_LOGI(TAG, "meter_ea777 Modbus master init");
mb_communication_info_t comm = {
.port = MB_PORT_NUM,
.mode = MB_MODE_RTU,
.baudrate = MB_DEV_SPEED,
.parity = UART_PARITY_EVEN,
};
void *handler = NULL;
esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &handler);
if (err != ESP_OK)
return err;
err = mbc_master_setup(&comm);
if (err != ESP_OK)
{
(void)mbc_master_destroy();
return err;
}
// RS-485 half duplex
err = uart_set_pin(MB_PORT_NUM,
MB_UART_TXD, MB_UART_RXD,
MB_UART_RTS, UART_PIN_NO_CHANGE);
if (err != ESP_OK)
{
(void)mbc_master_destroy();
return err;
}
err = mbc_master_start();
if (err != ESP_OK)
{
(void)mbc_master_destroy();
return err;
}
err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX);
if (err != ESP_OK)
{
(void)mbc_master_destroy();
return err;
}
vTaskDelay(pdMS_TO_TICKS(50));
is_initialized = true;
ESP_LOGI(TAG, "EA777 Modbus master initialized (9600 8E1, Holding Reg 0x03)");
return ESP_OK;
}
static esp_err_t ea777_register_instance(ea777_slot_t slot)
{
if (slot >= EA777_MAX_INSTANCES)
return ESP_ERR_INVALID_ARG;
esp_err_t err = ea777_master_init_once();
if (err != ESP_OK)
return err;
if (xSemaphoreTake(ea777_lock, EA777_LOCK_TIMEOUT) != pdTRUE)
return ESP_ERR_TIMEOUT;
ea777_instances[slot].registered = true;
ESP_LOGI(TAG, "EA777 %s registered on Modbus slave ID %u",
ea777_instances[slot].source,
ea777_instances[slot].slave_id);
err = ea777_rebuild_descriptors_locked();
xSemaphoreGive(ea777_lock);
return err;
}
static void ea777_shutdown_if_idle(void)
{
bool any_registered = false;
if (ea777_lock && xSemaphoreTake(ea777_lock, EA777_LOCK_TIMEOUT) == pdTRUE)
{
any_registered = ea777_has_registered_instance_locked();
xSemaphoreGive(ea777_lock);
}
if (any_registered || !is_initialized)
return;
ESP_LOGI(TAG, "No EA777 instances registered; stopping shared Modbus master");
task_should_run = false;
for (int i = 0; i < 20 && meter_task != NULL; ++i)
{
vTaskDelay(pdMS_TO_TICKS(10));
}
if (meter_task != NULL)
{
ESP_LOGW(TAG, "EA777 task did not exit in time; deleting it");
vTaskDelete(meter_task);
meter_task = NULL;
}
esp_err_t err = mbc_master_destroy();
if (err != ESP_OK)
ESP_LOGW(TAG, "mbc_master_destroy() returned %s", esp_err_to_name(err));
if (uart_is_driver_installed(MB_PORT_NUM))
{
uart_driver_delete(MB_PORT_NUM);
ESP_LOGI(TAG, "UART driver deleted");
}
is_initialized = false;
num_device_parameters_ea777 = 0;
ea777_descriptor_dirty = true;
}
static void ea777_unregister_instance(ea777_slot_t slot)
{
if (slot >= EA777_MAX_INSTANCES)
return;
if (!is_initialized || !ea777_lock)
return;
if (xSemaphoreTake(ea777_lock, EA777_LOCK_TIMEOUT) == pdTRUE)
{
ESP_LOGI(TAG, "EA777 %s unregistered", ea777_instances[slot].source);
ea777_instances[slot].registered = false;
(void)ea777_rebuild_descriptors_locked();
xSemaphoreGive(ea777_lock);
}
ea777_shutdown_if_idle();
}
static void meter_ea777_post_event(const ea777_instance_t *inst,
const float *voltage,
const float *current,
const int32_t *power_w,
int32_t total_power_w,
float freq_hz,
float pf_avg,
float total_kwh)
{
meter_event_data_t evt = {
.source = "GRID",
.source = inst->source,
.frequency = freq_hz,
.power_factor = pf_avg,
.total_energy = total_kwh};
.total_energy = total_kwh,
.watt_total = total_power_w,
.timestamp_us = esp_timer_get_time(),
};
memcpy(evt.vrms, voltage, sizeof(evt.vrms));
memcpy(evt.irms, current, sizeof(evt.irms));
@@ -152,12 +469,11 @@ static void meter_ea777_post_event(float *voltage, float *current, int *power_w,
&evt, sizeof(evt), portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));
ESP_LOGW(TAG, "%s falha ao emitir evento: %s", inst->source, esp_err_to_name(err));
}
}
// ===== Task de polling =====
static void serial_mdb_ea777_task(void *param)
static void ea777_read_instance_locked(const ea777_instance_t *inst)
{
esp_err_t err;
const mb_parameter_descriptor_t *desc = NULL;
@@ -167,214 +483,279 @@ static void serial_mdb_ea777_task(void *param)
float pf[3] = {0};
float freq = 0.0f;
float total_kwh = 0.0f;
int32_t total_active_power_w = 0;
bool got_any_value = false;
// pequeno settle antes da 1ª leitura
vTaskDelay(pdMS_TO_TICKS(200));
while (1)
for (uint16_t local_cid = 0; local_cid < CID_EA777_COUNT; local_cid++)
{
for (uint16_t cid = 0; cid < num_device_parameters_ea777; cid++)
const uint16_t cid = inst->cid_base + local_cid;
err = mbc_master_get_cid_info(cid, &desc);
if (err != ESP_OK || !desc)
{
err = mbc_master_get_cid_info(cid, &desc);
if (err != ESP_OK || !desc)
{
continue;
}
ESP_LOGE(TAG, "%s get_cid_info(%u) failed: %s", inst->source, cid, esp_err_to_name(err));
continue;
}
uint8_t type = 0;
uint16_t raw16 = 0;
uint32_t raw32 = 0;
uint8_t type = 0;
uint16_t raw16 = 0;
uint32_t raw32 = 0;
void *value_ptr = (cid == CID_EA777_TOTAL_ACTIVE_E) ? (void *)&raw32 : (void *)&raw16;
void *value_ptr = (local_cid == CID_EA777_TOTAL_ACTIVE_E) ? (void *)&raw32 : (void *)&raw16;
// 1 retry simples em caso de timeout
// 1 retry simples em caso de timeout
err = mbc_master_get_parameter(cid,
(char *)desc->param_key,
(uint8_t *)value_ptr,
&type);
if (err == ESP_ERR_TIMEOUT)
{
vTaskDelay(pdMS_TO_TICKS(60));
err = mbc_master_get_parameter(cid,
(char *)desc->param_key,
(uint8_t *)value_ptr,
&type);
if (err == ESP_ERR_TIMEOUT)
{
vTaskDelay(pdMS_TO_TICKS(60));
err = mbc_master_get_parameter(cid,
(char *)desc->param_key,
(uint8_t *)value_ptr,
&type);
}
if (err == ESP_OK)
{
switch (cid)
{
case CID_EA777_L1_VOLTAGE:
v[0] = ((float)raw16) * 0.1f;
break;
case CID_EA777_L2_VOLTAGE:
v[1] = ((float)raw16) * 0.1f;
break;
case CID_EA777_L3_VOLTAGE:
v[2] = ((float)raw16) * 0.1f;
break;
case CID_EA777_L1_CURRENT:
i[0] = ((float)raw16) * 0.01f;
break;
case CID_EA777_L2_CURRENT:
i[1] = ((float)raw16) * 0.01f;
break;
case CID_EA777_L3_CURRENT:
i[2] = ((float)raw16) * 0.01f;
break;
case CID_EA777_TOTAL_ACTIVE_P:
// guarda se quiser usar em debug; para o evento usamos
// aproximação por fase abaixo
// (poderia ser passado direto em power_w[0..2] também)
break;
case CID_EA777_PF_L1:
pf[0] = ((float)raw16) * 0.001f;
break;
case CID_EA777_PF_L2:
pf[1] = ((float)raw16) * 0.001f;
break;
case CID_EA777_PF_L3:
pf[2] = ((float)raw16) * 0.001f;
break;
case CID_EA777_FREQUENCY:
freq = ((float)raw16) * 0.01f;
break;
case CID_EA777_TOTAL_ACTIVE_E:
total_kwh = ((float)raw32) * 0.01f;
break;
default:
break;
}
ESP_LOGD(TAG, "%s (cid=%u) -> raw16=%u raw32=%u",
desc->param_key, cid,
(unsigned int)raw16,
(unsigned int)raw32);
}
else
{
ESP_LOGE(TAG, "CID %u (%s) read failed: %s",
cid, desc->param_key, esp_err_to_name(err));
}
vTaskDelay(POLL_INTERVAL);
}
// Potência por fase aproximada: P = V * I * PF
int p_int[3] = {
(int)(v[0] * i[0] * pf[0]),
(int)(v[1] * i[1] * pf[1]),
(int)(v[2] * i[2] * pf[2]),
};
// PF médio simples (ignora zeros)
float pf_sum = 0.0f;
int pf_cnt = 0;
for (int k = 0; k < 3; ++k)
if (err == ESP_OK)
{
if (pf[k] != 0.0f)
{
pf_sum += pf[k];
pf_cnt++;
}
}
float pf_avg = (pf_cnt ? pf_sum / pf_cnt : 0.0f);
got_any_value = true;
meter_ea777_post_event(v, i, p_int, freq, pf_avg, total_kwh);
vTaskDelay(UPDATE_INTERVAL);
switch (local_cid)
{
case CID_EA777_L1_VOLTAGE:
v[0] = ((float)raw16) * 0.1f;
break;
case CID_EA777_L2_VOLTAGE:
v[1] = ((float)raw16) * 0.1f;
break;
case CID_EA777_L3_VOLTAGE:
v[2] = ((float)raw16) * 0.1f;
break;
case CID_EA777_L1_CURRENT:
i[0] = ((float)raw16) * 0.01f;
break;
case CID_EA777_L2_CURRENT:
i[1] = ((float)raw16) * 0.01f;
break;
case CID_EA777_L3_CURRENT:
i[2] = ((float)raw16) * 0.01f;
break;
case CID_EA777_TOTAL_ACTIVE_P:
total_active_power_w = (int32_t)raw16;
break;
case CID_EA777_PF_L1:
pf[0] = ((float)raw16) * 0.001f;
break;
case CID_EA777_PF_L2:
pf[1] = ((float)raw16) * 0.001f;
break;
case CID_EA777_PF_L3:
pf[2] = ((float)raw16) * 0.001f;
break;
case CID_EA777_FREQUENCY:
freq = ((float)raw16) * 0.01f;
break;
case CID_EA777_TOTAL_ACTIVE_E:
{
uint32_t raw_e = ea777_swap_words_u32(raw32);
total_kwh = ((float)raw_e) * 0.01f;
ESP_LOGI(TAG,
"%s EA777 energy raw=0x%08" PRIX32
" swapped=0x%08" PRIX32 " => %.3f kWh",
inst->source,
raw32,
raw_e,
(double)total_kwh);
break;
}
default:
break;
}
ESP_LOGD(TAG, "%s %s (cid=%u) -> raw16=%u raw32=%u",
inst->source, desc->param_key, cid,
(unsigned int)raw16,
(unsigned int)raw32);
}
else
{
ESP_LOGE(TAG, "%s CID %u (%s) read failed: %s",
inst->source, cid, desc->param_key, esp_err_to_name(err));
}
vTaskDelay(POLL_INTERVAL);
}
if (!got_any_value)
{
ESP_LOGW(TAG, "%s no valid EA777 values read in this cycle", inst->source);
return;
}
// Potência por fase aproximada: P = V * I * PF.
// O EA777 também fornece potência ativa total; esta é usada em watt_total quando existe.
int32_t p_int[3] = {
(int32_t)lrintf(v[0] * i[0] * pf[0]),
(int32_t)lrintf(v[1] * i[1] * pf[1]),
(int32_t)lrintf(v[2] * i[2] * pf[2]),
};
const int32_t p_sum = p_int[0] + p_int[1] + p_int[2];
const int32_t watt_total = (total_active_power_w != 0) ? total_active_power_w : p_sum;
// PF médio simples (ignora zeros)
float pf_sum = 0.0f;
int pf_cnt = 0;
for (int k = 0; k < 3; ++k)
{
if (pf[k] != 0.0f)
{
pf_sum += pf[k];
pf_cnt++;
}
}
float pf_avg = (pf_cnt ? pf_sum / pf_cnt : 0.0f);
meter_ea777_post_event(inst, v, i, p_int, watt_total, freq, pf_avg, total_kwh);
}
// ===== Task de polling partilhada =====
static void serial_mdb_ea777_task(void *param)
{
(void)param;
// pequeno settle antes da 1ª leitura
vTaskDelay(pdMS_TO_TICKS(200));
while (task_should_run)
{
if (!is_initialized || !ea777_lock)
{
vTaskDelay(UPDATE_INTERVAL);
continue;
}
bool had_instance = false;
for (uint8_t slot = 0; slot < EA777_MAX_INSTANCES && task_should_run; ++slot)
{
if (xSemaphoreTake(ea777_lock, EA777_LOCK_TIMEOUT) != pdTRUE)
{
ESP_LOGW(TAG, "EA777 task timeout waiting lock");
continue;
}
ea777_instance_t inst = ea777_instances[slot];
if (inst.registered)
{
had_instance = true;
ea777_read_instance_locked(&inst);
}
xSemaphoreGive(ea777_lock);
}
vTaskDelay(had_instance ? UPDATE_INTERVAL : pdMS_TO_TICKS(500));
}
ESP_LOGI(TAG, "EA777 task stopped");
meter_task = NULL;
vTaskDelete(NULL);
}
// ===== API pública =====
// Compatibilidade: chamada antiga inicializa EA777 como GRID/ID 1.
esp_err_t meter_ea777_init(void)
{
if (is_initialized)
{
ESP_LOGW(TAG, "Already initialized");
return ESP_ERR_INVALID_STATE;
}
return meter_ea777_grid_init();
}
if (uart_is_driver_installed(MB_PORT_NUM))
{
uart_driver_delete(MB_PORT_NUM);
ESP_LOGI(TAG, "UART driver deleted");
}
esp_err_t meter_ea777_grid_init(void)
{
return ea777_register_instance(EA777_SLOT_GRID);
}
(void)mbc_master_destroy();
mb_communication_info_t comm = {
.port = MB_PORT_NUM,
.mode = MB_MODE_RTU,
.baudrate = MB_DEV_SPEED,
.parity = UART_PARITY_EVEN};
void *handler = NULL;
ESP_ERROR_CHECK(mbc_master_init(MB_PORT_SERIAL_MASTER, &handler));
ESP_ERROR_CHECK(mbc_master_setup(&comm));
// >>> RS-485 half duplex
ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM,
MB_UART_TXD, MB_UART_RXD,
MB_UART_RTS, UART_PIN_NO_CHANGE));
ESP_ERROR_CHECK(mbc_master_start());
ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX));
vTaskDelay(pdMS_TO_TICKS(50));
ESP_ERROR_CHECK(mbc_master_set_descriptor(device_parameters_ea777,
num_device_parameters_ea777));
is_initialized = true;
ESP_LOGI(TAG, "EA777 Modbus master initialized (9600 8E1, Holding Reg 0x03)");
return ESP_OK;
esp_err_t meter_ea777_evse_init(void)
{
return ea777_register_instance(EA777_SLOT_EVSE);
}
esp_err_t meter_ea777_start(void)
{
if (!is_initialized)
{
ESP_LOGE(TAG, "Not initialized");
ESP_LOGE(TAG, "meter_ea777 not initialized");
return ESP_ERR_INVALID_STATE;
}
if (!ea777_lock)
return ESP_ERR_INVALID_STATE;
if (xSemaphoreTake(ea777_lock, EA777_LOCK_TIMEOUT) != pdTRUE)
return ESP_ERR_TIMEOUT;
const bool has_instance = ea777_has_registered_instance_locked();
esp_err_t desc_err = ESP_OK;
if (has_instance)
desc_err = ea777_apply_descriptors_locked();
xSemaphoreGive(ea777_lock);
if (!has_instance)
{
ESP_LOGW(TAG, "meter_ea777 start ignored: no instances registered");
return ESP_ERR_INVALID_STATE;
}
if (desc_err != ESP_OK)
return desc_err;
if (meter_task == NULL)
{
xTaskCreate(serial_mdb_ea777_task,
"meter_ea777_task",
4096, NULL, 3, &meter_task);
ESP_LOGI(TAG, "EA777 task started");
task_should_run = true;
BaseType_t ok = xTaskCreate(serial_mdb_ea777_task,
"meter_ea777_task",
4096, NULL, 3, &meter_task);
if (ok != pdPASS)
{
task_should_run = false;
meter_task = NULL;
return ESP_ERR_NO_MEM;
}
ESP_LOGI(TAG, "meter_ea777 shared task started");
}
return ESP_OK;
}
esp_err_t meter_ea777_grid_start(void)
{
return meter_ea777_start();
}
esp_err_t meter_ea777_evse_start(void)
{
return meter_ea777_start();
}
void meter_ea777_stop(void)
{
if (!is_initialized)
{
ESP_LOGW(TAG, "Not initialized, skipping stop");
return;
}
if (meter_task)
{
vTaskDelete(meter_task);
meter_task = NULL;
ESP_LOGI(TAG, "EA777 task stopped");
}
(void)mbc_master_destroy();
if (uart_is_driver_installed(MB_PORT_NUM))
{
uart_driver_delete(MB_PORT_NUM);
ESP_LOGI(TAG, "UART driver deleted");
}
is_initialized = false;
ESP_LOGI(TAG, "Meter EA777 cleaned up");
meter_ea777_grid_stop();
}
void meter_ea777_grid_stop(void)
{
ea777_unregister_instance(EA777_SLOT_GRID);
}
void meter_ea777_evse_stop(void)
{
ea777_unregister_instance(EA777_SLOT_EVSE);
}