#include "meter_orno526.h" #include "modbus_params.h" #include "mbcontroller.h" #include "meter_events.h" #include "esp_log.h" #include "driver/uart.h" #include #include #define TAG "serial_mdb_orno526" #define MB_PORT_NUM 2 #define MB_DEV_SPEED 9600 #define MB_UART_TXD 17 #define MB_UART_RXD 16 #define MB_UART_RTS 2 #define UPDATE_INTERVAL (3000 / portTICK_PERIOD_MS) #define POLL_INTERVAL (100 / portTICK_PERIOD_MS) #define HOLD_OFFSET(field) ((uint16_t)(offsetof(holding_reg_params_t, field) + 1)) #define STR(x) ((const char *)(x)) #define OPTS(min, max, step) {.opt1 = min, .opt2 = max, .opt3 = step} // State flag static bool is_initialized = false; static TaskHandle_t meter_task = NULL; // CID enums enum { CID_ACTIVE_ENERGY = 0, CID_REACTIVE_ENERGY, CID_ACTIVE_POWER, CID_APPARENT_POWER, CID_REACTIVE_POWER, CID_L1_CURRENT, CID_L1_VOLTAGE, CID_FREQUENCY }; // Register addresses #define TOTALFACTIVE 0x010E #define TOTALRACTIVE 0x0118 #define ACTIVEPOWER 0x0104 #define APPARENTPOWER 0x0106 #define REACTIVEPOWER 0x0108 #define L1CURRENT 0x0102 #define L1VOLTAGE 0x0100 #define FREQUENCY 0x010A const mb_parameter_descriptor_t device_parameters_orno526[] = { {CID_ACTIVE_ENERGY, "Active Energy", "kWh", 1, MB_PARAM_INPUT, TOTALFACTIVE, 2, HOLD_OFFSET(active_energy), PARAM_TYPE_I32_CDAB, 4, OPTS(0, 100000, 1), PAR_PERMS_READ}, {CID_REACTIVE_ENERGY, "Reactive Energy", "kWh", 1, MB_PARAM_INPUT, TOTALRACTIVE, 2, HOLD_OFFSET(reactive_energy), PARAM_TYPE_I32_CDAB, 4, OPTS(0, 100000, 1), PAR_PERMS_READ}, {CID_ACTIVE_POWER, "Active Power", "W", 1, MB_PARAM_INPUT, ACTIVEPOWER, 2, HOLD_OFFSET(active_power), PARAM_TYPE_I32_CDAB, 4, OPTS(-100000, 100000, 1), PAR_PERMS_READ}, {CID_APPARENT_POWER, "Apparent Power", "VA", 1, MB_PARAM_INPUT, APPARENTPOWER, 2, HOLD_OFFSET(apparent_power), PARAM_TYPE_I32_CDAB, 4, OPTS(0, 100000, 1), PAR_PERMS_READ}, {CID_REACTIVE_POWER, "Reactive Power", "VAR", 1, MB_PARAM_INPUT, REACTIVEPOWER, 2, HOLD_OFFSET(reactive_power), PARAM_TYPE_I32_CDAB, 4, OPTS(-100000, 100000, 1), PAR_PERMS_READ}, {CID_L1_CURRENT, "L1 Current", "A", 1, MB_PARAM_INPUT, L1CURRENT, 2, HOLD_OFFSET(l1_current), PARAM_TYPE_I32_CDAB, 4, OPTS(0, 100, 0.1), PAR_PERMS_READ}, {CID_L1_VOLTAGE, "L1 Voltage", "V", 1, MB_PARAM_INPUT, L1VOLTAGE, 2, HOLD_OFFSET(l1_voltage), PARAM_TYPE_I32_CDAB, 4, OPTS(0, 300, 0.1), PAR_PERMS_READ}, {CID_FREQUENCY, "Frequency", "Hz", 1, MB_PARAM_INPUT, FREQUENCY, 1, HOLD_OFFSET(frequency), PARAM_TYPE_I32_CDAB, 2, OPTS(0, 1000, 0.1), PAR_PERMS_READ} }; const uint16_t num_device_parameters_orno526 = sizeof(device_parameters_orno526) / sizeof(device_parameters_orno526[0]); static void *get_param_ptr(const mb_parameter_descriptor_t *param) { if (!param || param->param_offset == 0) return NULL; return ((uint8_t *)&holding_reg_params + param->param_offset - 1); } static inline float scale_for_cid(uint16_t cid) { switch (cid) { case CID_L1_VOLTAGE: case CID_L1_CURRENT: return 1000.0f; // V/A = raw / 1000 case CID_ACTIVE_POWER: case CID_APPARENT_POWER: case CID_REACTIVE_POWER: return 1.0f; // W/VA/var = raw case CID_ACTIVE_ENERGY: case CID_REACTIVE_ENERGY: return 100.0f; // kWh = raw / 100 case CID_FREQUENCY: return 10.0f; // Hz = raw / 10 default: return 1.0f; } } static void serial_mdb_task(void *param) { esp_err_t err; const mb_parameter_descriptor_t *desc = NULL; float voltage[3] = {0}; float current[3] = {0}; int watt[3] = {0}; float energy = 0.0f; float frequency_hz = 0.0f; // <- armazenar frequência lida (0x010A) while (1) { for (uint16_t cid = 0; cid < num_device_parameters_orno526; cid++) { err = mbc_master_get_cid_info(cid, &desc); if (err != ESP_OK || !desc) { ESP_LOGE(TAG, "mbc_master_get_cid_info(%u) failed: %s", cid, esp_err_to_name(err)); continue; } void *data_ptr = get_param_ptr(desc); if (!data_ptr) { ESP_LOGE(TAG, "CID %u (%s): null data_ptr", cid, desc->param_key); continue; } uint8_t type = 0; err = mbc_master_get_parameter(cid, (char *)desc->param_key, (uint8_t *)data_ptr, &type); if (err == ESP_OK) { float val = 0.0f; if (cid == CID_FREQUENCY) { // Frequência é U16 (1 registo), escala = /10.0 uint16_t raw16 = *(uint16_t *)data_ptr; val = raw16 / 10.0f; frequency_hz = val; } else { // Demais CIDs são I32_CDAB (2 registos) int32_t raw32 = *(int32_t *)data_ptr; float scale = scale_for_cid(cid); val = raw32 / scale; } ESP_LOGI(TAG, "%s: %.3f %s", desc->param_key, val, desc->param_units); switch (cid) { case CID_L1_VOLTAGE: voltage[0] = val; break; case CID_L1_CURRENT: current[0] = val; break; case CID_ACTIVE_POWER: watt[0] = (int)lrintf(val); watt[1] = watt[2] = watt[0]; break; case CID_ACTIVE_ENERGY: energy = val; // já em kWh (raw/100) break; // CID_FREQUENCY já atualiza 'frequency_hz' acima default: break; } } else { ESP_LOGE(TAG, "CID %u (%s) read failed: %s", cid, desc->param_key, esp_err_to_name(err)); } vTaskDelay(POLL_INTERVAL); } meter_event_data_t evt = { .frequency = frequency_hz, // agora preenchido .power_factor = 0.0f, // (adicione PF se quiser ler 0x010B) .total_energy = energy, .source = "GRID", }; memcpy(evt.vrms, voltage, sizeof(evt.vrms)); memcpy(evt.irms, current, sizeof(evt.irms)); memcpy(evt.watt, watt, sizeof(evt.watt)); esp_event_post(METER_EVENT, METER_EVENT_DATA_READY, &evt, sizeof(evt), portMAX_DELAY); vTaskDelay(UPDATE_INTERVAL); } } esp_err_t meter_orno526_init(void) { if (is_initialized) { ESP_LOGW(TAG, "meter_orno526 already initialized"); return ESP_ERR_INVALID_STATE; } ESP_LOGI(TAG, "meter_orno526_init"); // ORNO costuma vir 9600, 8E1. Se o teu estiver 8E2, troca os stop bits mais abaixo. mb_communication_info_t comm = { .port = MB_PORT_NUM, .mode = MB_MODE_RTU, .baudrate = MB_DEV_SPEED, // 9600 .parity = UART_PARITY_DISABLE, // 8E1 por padrão }; void *handler = NULL; esp_err_t err = mbc_master_init(MB_PORT_SERIAL_MASTER, &handler); if (err != ESP_OK) { ESP_LOGE(TAG, "mbc_master_init failed"); return err; } ESP_ERROR_CHECK(mbc_master_setup(&comm)); // Pinos RS-485 (TX, RX, RTS=DE/RE). CTS não usado. ESP_ERROR_CHECK(uart_set_pin(MB_PORT_NUM, MB_UART_TXD, MB_UART_RXD, MB_UART_RTS, UART_PIN_NO_CHANGE)); // Garanta 8 bits de dados e sem flow-control. ESP_ERROR_CHECK(uart_set_word_length(MB_PORT_NUM, UART_DATA_8_BITS)); ESP_ERROR_CHECK(uart_set_hw_flow_ctrl(MB_PORT_NUM, UART_HW_FLOWCTRL_DISABLE, 0)); // Stop bits: a maioria usa 1. Se continuar a dar INVALID_RESPONSE, teste 2. ESP_ERROR_CHECK(uart_set_stop_bits(MB_PORT_NUM, UART_STOP_BITS_1)); // Alternativa, se o medidor estiver configurado p/ 2 stop bits: // ESP_ERROR_CHECK(uart_set_stop_bits(MB_PORT_NUM, UART_STOP_BITS_2)); ESP_ERROR_CHECK(mbc_master_start()); ESP_ERROR_CHECK(uart_set_mode(MB_PORT_NUM, UART_MODE_RS485_HALF_DUPLEX)); // (Opcional) Logs detalhados para ver TX/RX/frames durante debug: esp_log_level_set("MB_CONTROLLER_MASTER", ESP_LOG_DEBUG); esp_log_level_set("MB_PORT_COMMON", ESP_LOG_DEBUG); esp_log_level_set("MB_SERIAL_MASTER", ESP_LOG_DEBUG); vTaskDelay(pdMS_TO_TICKS(5)); ESP_ERROR_CHECK(mbc_master_set_descriptor(device_parameters_orno526, num_device_parameters_orno526)); is_initialized = true; return ESP_OK; } esp_err_t meter_orno526_start(void) { ESP_LOGI(TAG, "meter_orno526_start"); if (!is_initialized) { ESP_LOGE(TAG, "meter_orno526 not initialized"); return ESP_ERR_INVALID_STATE; } if (meter_task == NULL) { xTaskCreate(serial_mdb_task, "meter_orno526_task", 4096, NULL, 3, &meter_task); ESP_LOGI(TAG, "meter_orno526 task started"); } return ESP_OK; } void meter_orno526_stop(void) { if (!is_initialized) { ESP_LOGW(TAG, "meter_orno526 not initialized"); return; } ESP_LOGI(TAG, "Stopping meter_orno526"); uart_driver_delete(MB_PORT_NUM); esp_err_t err = mbc_master_destroy(); if (err != ESP_OK) { ESP_LOGW(TAG, "mbc_master_destroy() returned %s", esp_err_to_name(err)); } is_initialized = false; }