Files
chargeflow/components/meter_manager/driver/meter_modbus/meter_dts024m.c
2026-01-24 16:56:51 +00:00

543 lines
18 KiB
C
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// meter_dts024m.c — Driver Modbus RTU para DTS024M (ESP-IDF / esp-modbus)
// Versão PRODUÇÃO (SEM AUTO-PROBE): parâmetros fixos (baud/parity/id/FC/base).
// Ajusta os #defines DTS024M_PROD_* conforme o teu medidor.
#include "meter_events.h"
#include "modbus_params.h"
#include "mbcontroller.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/uart.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <stddef.h>
#include <string.h>
#include "meter_dts024m.h"
#define TAG "serial_mdb_dts024m"
// ===== UART / RS-485 =====
#define MB_PORT_NUM 2
// Ajuste os pinos conforme seu hardware
#define MB_UART_TXD 17
#define MB_UART_RXD 16
#define MB_UART_RTS 2 // pino DE/RE do transceiver RS-485
// ===== Timings =====
#define UPDATE_INTERVAL (5000 / portTICK_PERIOD_MS)
#define POLL_INTERVAL (200 / portTICK_PERIOD_MS)
// ===== 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]))
// ===== Config PRODUÇÃO (sem AUTO-PROBE) =====
// Ajusta estes valores:
#define DTS024M_PROD_BAUD 2400
#define DTS024M_PROD_PARITY UART_PARITY_DISABLE // 0 = none; UART_PARITY_EVEN se 8E1
#define DTS024M_PROD_SLAVE_ID 1 // endereço Modbus (1..247)
#define DTS024M_PROD_AREA MB_PARAM_INPUT // MB_PARAM_INPUT (FC04) ou MB_PARAM_HOLDING (FC03)
#define DTS024M_PROD_BASE_OFFSET 0 // 0 ou 1 (depende se o mapa é 0-based ou 1-based)
// ===== Estado =====
static bool is_initialized = false;
static bool mb_started = false;
static TaskHandle_t meter_task = NULL;
// ============================================================================
// MAPA DE REGISTROS (template) — pode variar conforme firmware.
// Estes endereços são um “perfil” comum.
// ============================================================================
#define DTS024M_L1_VOLTAGE 0x0000 // U32, 0.01 V (2 regs)
#define DTS024M_L2_VOLTAGE 0x0002
#define DTS024M_L3_VOLTAGE 0x0004
#define DTS024M_L1_CURRENT 0x0006 // U32, 0.001 A (2 regs)
#define DTS024M_L2_CURRENT 0x0008
#define DTS024M_L3_CURRENT 0x000A
#define DTS024M_L1_ACTIVE_P 0x000C // I32 (twos complement), (depende do modelo/escala)
#define DTS024M_L2_ACTIVE_P 0x000E
#define DTS024M_L3_ACTIVE_P 0x0010
#define DTS024M_PF_L1 0x001E // I16 (twos complement), 0.001
#define DTS024M_PF_L2 0x001F
#define DTS024M_PF_L3 0x0020
#define DTS024M_FREQUENCY 0x002A // U16, 0.01 Hz
#define DTS024M_TOTAL_ACTIVE_E 0x0404 // U32, 0.01 kWh (2 regs)
// ============================================================================
// Conversões signed (twos complement) — porque o projeto não tem PARAM_TYPE_I*
// ============================================================================
static inline int32_t s32_from_u32(uint32_t x)
{
return (x & 0x80000000u) ? (int32_t)(x - 0x100000000ULL) : (int32_t)x;
}
static inline int16_t s16_from_u16(uint16_t x)
{
return (x & 0x8000u) ? (int16_t)(x - 0x10000u) : (int16_t)x;
}
// ============================================================================
// CIDs
// ============================================================================
enum
{
CID_DTS024M_L1_VOLTAGE = 0,
CID_DTS024M_L2_VOLTAGE,
CID_DTS024M_L3_VOLTAGE,
CID_DTS024M_L1_CURRENT,
CID_DTS024M_L2_CURRENT,
CID_DTS024M_L3_CURRENT,
CID_DTS024M_L1_ACTIVE_P,
CID_DTS024M_L2_ACTIVE_P,
CID_DTS024M_L3_ACTIVE_P,
CID_DTS024M_PF_L1,
CID_DTS024M_PF_L2,
CID_DTS024M_PF_L3,
CID_DTS024M_FREQUENCY,
CID_DTS024M_TOTAL_ACTIVE_E,
};
// ============================================================================
// DESCRIPTORS (TEMPLATE) — copiamos para RAM e ajustamos:
// - slave_id
// - base offset (0/1)
// - mb_param_type (HOLDING/INPUT)
// ============================================================================
static const mb_parameter_descriptor_t device_parameters_dts024m_tmpl[] = {
// Tensões (U32 / 2 regs) — 0.01 V
{CID_DTS024M_L1_VOLTAGE, STR("L1 Voltage"), STR("V"), 1,
MB_PARAM_HOLDING, DTS024M_L1_VOLTAGE, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L2_VOLTAGE, STR("L2 Voltage"), STR("V"), 1,
MB_PARAM_HOLDING, DTS024M_L2_VOLTAGE, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L3_VOLTAGE, STR("L3 Voltage"), STR("V"), 1,
MB_PARAM_HOLDING, DTS024M_L3_VOLTAGE, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
// Correntes (U32 / 2 regs) — 0.001 A
{CID_DTS024M_L1_CURRENT, STR("L1 Current"), STR("A"), 1,
MB_PARAM_HOLDING, DTS024M_L1_CURRENT, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L2_CURRENT, STR("L2 Current"), STR("A"), 1,
MB_PARAM_HOLDING, DTS024M_L2_CURRENT, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L3_CURRENT, STR("L3 Current"), STR("A"), 1,
MB_PARAM_HOLDING, DTS024M_L3_CURRENT, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
// Potência ativa por fase (U32 / 2 regs no descriptor; interpretamos como signed I32)
{CID_DTS024M_L1_ACTIVE_P, STR("L1 Active Power"), STR("W"), 1,
MB_PARAM_HOLDING, DTS024M_L1_ACTIVE_P, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L2_ACTIVE_P, STR("L2 Active Power"), STR("W"), 1,
MB_PARAM_HOLDING, DTS024M_L2_ACTIVE_P, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
{CID_DTS024M_L3_ACTIVE_P, STR("L3 Active Power"), STR("W"), 1,
MB_PARAM_HOLDING, DTS024M_L3_ACTIVE_P, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
// PF (U16 / 1 reg; interpretamos como signed I16) — 0.001
{CID_DTS024M_PF_L1, STR("L1 PF"), STR(""), 1,
MB_PARAM_HOLDING, DTS024M_PF_L1, 1,
0, PARAM_TYPE_U16, 2, OPTS(0, 65535, 1), PAR_PERMS_READ},
{CID_DTS024M_PF_L2, STR("L2 PF"), STR(""), 1,
MB_PARAM_HOLDING, DTS024M_PF_L2, 1,
0, PARAM_TYPE_U16, 2, OPTS(0, 65535, 1), PAR_PERMS_READ},
{CID_DTS024M_PF_L3, STR("L3 PF"), STR(""), 1,
MB_PARAM_HOLDING, DTS024M_PF_L3, 1,
0, PARAM_TYPE_U16, 2, OPTS(0, 65535, 1), PAR_PERMS_READ},
// Frequência (U16 / 1 reg) — 0.01 Hz
{CID_DTS024M_FREQUENCY, STR("Frequency"), STR("Hz"), 1,
MB_PARAM_HOLDING, DTS024M_FREQUENCY, 1,
0, PARAM_TYPE_U16, 2, OPTS(0, 10000, 1), PAR_PERMS_READ},
// Energia ativa total (U32 / 2 regs) — 0.01 kWh
{CID_DTS024M_TOTAL_ACTIVE_E, STR("Total Active Energy"), STR("kWh"), 1,
MB_PARAM_HOLDING, DTS024M_TOTAL_ACTIVE_E, 2,
0, PARAM_TYPE_U32, 4, OPTS(0, 0xFFFFFFFF, 1), PAR_PERMS_READ},
};
static mb_parameter_descriptor_t device_parameters_dts024m[ARRAY_SIZE(device_parameters_dts024m_tmpl)];
static const uint16_t num_device_parameters_dts024m = ARRAY_SIZE(device_parameters_dts024m);
static void dts024m_build_descriptors(uint8_t slave_id, uint16_t base_offset, mb_param_type_t area)
{
memcpy(device_parameters_dts024m,
device_parameters_dts024m_tmpl,
sizeof(device_parameters_dts024m));
for (uint16_t i = 0; i < num_device_parameters_dts024m; ++i)
{
device_parameters_dts024m[i].mb_slave_addr = slave_id;
device_parameters_dts024m[i].mb_reg_start =
(uint16_t)(device_parameters_dts024m[i].mb_reg_start + base_offset);
device_parameters_dts024m[i].mb_param_type = area; // HOLDING (FC03) ou INPUT (FC04)
}
}
// ============================================================================
// Modbus master init (fixo) — garante ordem correta (start -> uart_set_mode)
// ============================================================================
static esp_err_t dts024m_master_reinit(uint32_t baud, uart_parity_t parity)
{
if (mb_started)
{
(void)mbc_master_destroy();
mb_started = false;
}
if (uart_is_driver_installed(MB_PORT_NUM))
{
uart_driver_delete(MB_PORT_NUM);
}
mb_communication_info_t comm = {
.port = MB_PORT_NUM,
.mode = MB_MODE_RTU,
.baudrate = baud,
.parity = parity};
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;
}
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;
}
// IMPORTANTE: start antes de uart_set_mode (driver UART costuma ser instalado no start)
err = mbc_master_start();
if (err != ESP_OK)
{
(void)mbc_master_destroy();
return err;
}
mb_started = true;
err = uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX);
if (err != ESP_OK)
{
(void)mbc_master_destroy();
mb_started = false;
return err;
}
vTaskDelay(pdMS_TO_TICKS(40));
return ESP_OK;
}
// ============================================================================
// Post do evento de medição
// ============================================================================
static void meter_dts024m_post_event(float *voltage, float *current, int *power_w,
float freq_hz, float pf_avg, float total_kwh)
{
meter_event_data_t evt = {
.source = "GRID",
.frequency = freq_hz,
.power_factor = pf_avg,
.total_energy = total_kwh};
memcpy(evt.vrms, voltage, sizeof(evt.vrms));
memcpy(evt.irms, current, sizeof(evt.irms));
memcpy(evt.watt, power_w, sizeof(evt.watt));
esp_err_t err = esp_event_post(METER_EVENT, METER_EVENT_DATA_READY,
&evt, sizeof(evt), portMAX_DELAY);
if (err != ESP_OK)
{
ESP_LOGW(TAG, "Falha ao emitir evento: %s", esp_err_to_name(err));
}
}
// ============================================================================
// Task de polling
// ============================================================================
static void serial_mdb_dts024m_task(void *param)
{
(void)param;
esp_err_t err;
const mb_parameter_descriptor_t *desc = NULL;
float v[3] = {0};
float i[3] = {0};
float pf[3] = {0};
float freq = 0.0f;
float total_kwh = 0.0f;
int p_w[3] = {0};
vTaskDelay(pdMS_TO_TICKS(200)); // settle
while (1)
{
for (uint16_t cid = 0; cid < num_device_parameters_dts024m; cid++)
{
err = mbc_master_get_cid_info(cid, &desc);
if (err != ESP_OK || !desc)
{
continue;
}
uint8_t type = 0;
uint16_t raw_u16 = 0;
uint32_t raw_u32 = 0;
void *value_ptr = &raw_u16;
// U32
switch (cid)
{
case CID_DTS024M_L1_VOLTAGE:
case CID_DTS024M_L2_VOLTAGE:
case CID_DTS024M_L3_VOLTAGE:
case CID_DTS024M_L1_CURRENT:
case CID_DTS024M_L2_CURRENT:
case CID_DTS024M_L3_CURRENT:
case CID_DTS024M_L1_ACTIVE_P:
case CID_DTS024M_L2_ACTIVE_P:
case CID_DTS024M_L3_ACTIVE_P:
case CID_DTS024M_TOTAL_ACTIVE_E:
value_ptr = &raw_u32;
break;
default:
value_ptr = &raw_u16;
break;
}
// 1 retry simples em caso de timeout (podes remover se quiseres menos carga)
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)
{
// V (0.01V)
case CID_DTS024M_L1_VOLTAGE:
v[0] = ((float)raw_u32) * 0.01f;
break;
case CID_DTS024M_L2_VOLTAGE:
v[1] = ((float)raw_u32) * 0.01f;
break;
case CID_DTS024M_L3_VOLTAGE:
v[2] = ((float)raw_u32) * 0.01f;
break;
// I (0.001A)
case CID_DTS024M_L1_CURRENT:
i[0] = ((float)raw_u32) * 0.001f;
break;
case CID_DTS024M_L2_CURRENT:
i[1] = ((float)raw_u32) * 0.001f;
break;
case CID_DTS024M_L3_CURRENT:
i[2] = ((float)raw_u32) * 0.001f;
break;
// P ativa (twos complement I32) — atenção: escala depende do modelo
case CID_DTS024M_L1_ACTIVE_P:
p_w[0] = (int)s32_from_u32(raw_u32);
break;
case CID_DTS024M_L2_ACTIVE_P:
p_w[1] = (int)s32_from_u32(raw_u32);
break;
case CID_DTS024M_L3_ACTIVE_P:
p_w[2] = (int)s32_from_u32(raw_u32);
break;
// PF (twos complement I16; 0.001)
case CID_DTS024M_PF_L1:
pf[0] = ((float)s16_from_u16(raw_u16)) * 0.001f;
break;
case CID_DTS024M_PF_L2:
pf[1] = ((float)s16_from_u16(raw_u16)) * 0.001f;
break;
case CID_DTS024M_PF_L3:
pf[2] = ((float)s16_from_u16(raw_u16)) * 0.001f;
break;
// Freq (0.01Hz)
case CID_DTS024M_FREQUENCY:
freq = ((float)raw_u16) * 0.01f;
break;
// Energia (0.01kWh)
case CID_DTS024M_TOTAL_ACTIVE_E:
total_kwh = ((float)raw_u32) * 0.01f;
break;
default:
break;
}
ESP_LOGD(TAG, "%s (cid=%u) ok (u16=%u u32=%u)",
desc->param_key, cid, (unsigned)raw_u16, (unsigned)raw_u32);
}
else
{
ESP_LOGE(TAG, "CID %u (%s) read failed: %s",
cid, desc->param_key, esp_err_to_name(err));
}
vTaskDelay(POLL_INTERVAL);
}
// 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_dts024m_post_event(v, i, p_w, freq, pf_avg, total_kwh);
vTaskDelay(UPDATE_INTERVAL);
}
}
// ============================================================================
// Init / Start / Stop
// ============================================================================
esp_err_t meter_dts024m_init(void)
{
if (is_initialized)
{
ESP_LOGW(TAG, "Already initialized");
return ESP_ERR_INVALID_STATE;
}
// init fixo (produção)
esp_err_t err = dts024m_master_reinit(DTS024M_PROD_BAUD, DTS024M_PROD_PARITY);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "master_reinit failed: %s", esp_err_to_name(err));
return err;
}
// monta descriptors reais com ID/offset/area fixos
dts024m_build_descriptors(DTS024M_PROD_SLAVE_ID, DTS024M_PROD_BASE_OFFSET, DTS024M_PROD_AREA);
// aplica descriptors reais
esp_err_t derr = mbc_master_set_descriptor(device_parameters_dts024m,
num_device_parameters_dts024m);
if (derr != ESP_OK)
{
ESP_LOGE(TAG, "set_descriptor failed: %s", esp_err_to_name(derr));
return derr;
}
is_initialized = true;
ESP_LOGI(TAG, "DTS024M initialized (PROD) baud=%d parity=%d id=%d area=%s base=%d",
DTS024M_PROD_BAUD,
(int)DTS024M_PROD_PARITY,
DTS024M_PROD_SLAVE_ID,
(DTS024M_PROD_AREA == MB_PARAM_HOLDING ? "FC03" : "FC04"),
DTS024M_PROD_BASE_OFFSET);
return ESP_OK;
}
esp_err_t meter_dts024m_start(void)
{
if (!is_initialized)
{
ESP_LOGE(TAG, "Not initialized");
return ESP_ERR_INVALID_STATE;
}
if (meter_task == NULL)
{
xTaskCreate(serial_mdb_dts024m_task,
"meter_dts024m_task",
4096, NULL, 3, &meter_task);
ESP_LOGI(TAG, "DTS024M task started");
}
return ESP_OK;
}
void meter_dts024m_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, "DTS024M task stopped");
}
if (mb_started)
{
(void)mbc_master_destroy();
mb_started = false;
}
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 DTS024M cleaned up");
}