From bcec1d6cb8124a97203c8899d2d85577581819f0 Mon Sep 17 00:00:00 2001 From: Bonne Eggleston Date: Fri, 28 Aug 2026 16:44:37 -0700 Subject: [PATCH] [modbus] Decode by register address in the modbus sensor components (#18874) --- .../growatt_solar/growatt_solar.cpp | 23 +++-- .../components/growatt_solar/growatt_solar.h | 88 +++++++++---------- .../havells_solar/havells_solar.cpp | 19 ++-- esphome/components/pzemac/pzemac.cpp | 19 ++-- esphome/components/pzemdc/pzemdc.cpp | 19 ++-- esphome/components/sdm_meter/sdm_meter.cpp | 11 ++- .../components/selec_meter/selec_meter.cpp | 11 ++- 7 files changed, 88 insertions(+), 102 deletions(-) diff --git a/esphome/components/growatt_solar/growatt_solar.cpp b/esphome/components/growatt_solar/growatt_solar.cpp index bc3c3d52db..08c3966ed9 100644 --- a/esphome/components/growatt_solar/growatt_solar.cpp +++ b/esphome/components/growatt_solar/growatt_solar.cpp @@ -3,6 +3,8 @@ namespace esphome::growatt_solar { +namespace helpers = modbus::helpers; + static const char *const TAG = "growatt_solar"; static const uint8_t MODBUS_REGISTER_COUNT[] = {33, 95}; // indexed with enum GrowattProtocolVersion @@ -16,23 +18,18 @@ void GrowattSolar::on_read_input_registers(uint16_t start_address, std::span void { - if (sensor == nullptr || reg < start_address) + auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, uint16_t reg, float unit) -> void { + if (sensor == nullptr) return; - size_t offset = reg - start_address; - if (offset >= registers.size()) - return; - sensor->publish_state(registers[offset] * unit); + if (auto value = helpers::value_at(registers, start_address, reg)) + sensor->publish_state(*value * unit); }; - auto publish_2_reg_sensor_state = [&](sensor::Sensor *sensor, size_t reg, float unit) -> void { - constexpr auto value_type = modbus::helpers::SensorValueType::U_DWORD; - if (sensor == nullptr || reg < start_address) + auto publish_2_reg_sensor_state = [&](sensor::Sensor *sensor, uint16_t reg, float unit) -> void { + if (sensor == nullptr) return; - size_t offset = reg - start_address; - if (offset + modbus::helpers::register_width_for(value_type) > registers.size()) - return; - sensor->publish_state(modbus::helpers::registers_to_value(registers.data() + offset) * unit); + if (auto value = helpers::value_at(registers, start_address, reg)) + sensor->publish_state(*value * unit); }; switch (this->protocol_version_) { diff --git a/esphome/components/growatt_solar/growatt_solar.h b/esphome/components/growatt_solar/growatt_solar.h index 60706930c7..5b96521476 100644 --- a/esphome/components/growatt_solar/growatt_solar.h +++ b/esphome/components/growatt_solar/growatt_solar.h @@ -17,53 +17,53 @@ enum GrowattProtocolVersion { }; // Register addresses for the RTU protocol. -constexpr size_t RTU_INVERTER_STATUS = 0; // length = 1 -constexpr size_t RTU_PV_ACTIVE_POWER = 1; // length = 2 -constexpr size_t RTU_PV1_VOLTAGE = 3; // length = 1 -constexpr size_t RTU_PV1_CURRENT = 4; // length = 1 -constexpr size_t RTU_PV1_ACTIVE_POWER = 5; // length = 2 -constexpr size_t RTU_PV2_VOLTAGE = 7; // length = 1 -constexpr size_t RTU_PV2_CURRENT = 8; // length = 1 -constexpr size_t RTU_PV2_ACTIVE_POWER = 9; // length = 2 -constexpr size_t RTU_GRID_ACTIVE_POWER = 11; // length = 2 -constexpr size_t RTU_GRID_FREQUENCY = 13; // length = 1 -constexpr size_t RTU_PHASE1_VOLTAGE = 14; // length = 1 -constexpr size_t RTU_PHASE1_CURRENT = 15; // length = 1 -constexpr size_t RTU_PHASE1_ACTIVE_POWER = 16; // length = 2 -constexpr size_t RTU_PHASE2_VOLTAGE = 18; // length = 1 -constexpr size_t RTU_PHASE2_CURRENT = 19; // length = 1 -constexpr size_t RTU_PHASE2_ACTIVE_POWER = 20; // length = 2 -constexpr size_t RTU_PHASE3_VOLTAGE = 22; // length = 1 -constexpr size_t RTU_PHASE3_CURRENT = 23; // length = 1 -constexpr size_t RTU_PHASE3_ACTIVE_POWER = 24; // length = 2 -constexpr size_t RTU_TODAY_PRODUCTION = 26; // length = 2 -constexpr size_t RTU_TOTAL_ENERGY_PRODUCTION = 28; // length = 2 -constexpr size_t RTU_INVERTER_MODULE_TEMP = 32; // length = 1 +constexpr uint16_t RTU_INVERTER_STATUS = 0; // length = 1 +constexpr uint16_t RTU_PV_ACTIVE_POWER = 1; // length = 2 +constexpr uint16_t RTU_PV1_VOLTAGE = 3; // length = 1 +constexpr uint16_t RTU_PV1_CURRENT = 4; // length = 1 +constexpr uint16_t RTU_PV1_ACTIVE_POWER = 5; // length = 2 +constexpr uint16_t RTU_PV2_VOLTAGE = 7; // length = 1 +constexpr uint16_t RTU_PV2_CURRENT = 8; // length = 1 +constexpr uint16_t RTU_PV2_ACTIVE_POWER = 9; // length = 2 +constexpr uint16_t RTU_GRID_ACTIVE_POWER = 11; // length = 2 +constexpr uint16_t RTU_GRID_FREQUENCY = 13; // length = 1 +constexpr uint16_t RTU_PHASE1_VOLTAGE = 14; // length = 1 +constexpr uint16_t RTU_PHASE1_CURRENT = 15; // length = 1 +constexpr uint16_t RTU_PHASE1_ACTIVE_POWER = 16; // length = 2 +constexpr uint16_t RTU_PHASE2_VOLTAGE = 18; // length = 1 +constexpr uint16_t RTU_PHASE2_CURRENT = 19; // length = 1 +constexpr uint16_t RTU_PHASE2_ACTIVE_POWER = 20; // length = 2 +constexpr uint16_t RTU_PHASE3_VOLTAGE = 22; // length = 1 +constexpr uint16_t RTU_PHASE3_CURRENT = 23; // length = 1 +constexpr uint16_t RTU_PHASE3_ACTIVE_POWER = 24; // length = 2 +constexpr uint16_t RTU_TODAY_PRODUCTION = 26; // length = 2 +constexpr uint16_t RTU_TOTAL_ENERGY_PRODUCTION = 28; // length = 2 +constexpr uint16_t RTU_INVERTER_MODULE_TEMP = 32; // length = 1 // Input register addresses for the RTU2 protocol as described // in the "GROWATT INVERTER MODBUS PROTOCOL_II V1.39" document. -constexpr size_t RTU2_INVERTER_STATUS = 0; // length = 1 -constexpr size_t RTU2_PV_ACTIVE_POWER = 1; // length = 2 -constexpr size_t RTU2_PV1_VOLTAGE = 3; // length = 1 -constexpr size_t RTU2_PV1_CURRENT = 4; // length = 1 -constexpr size_t RTU2_PV1_ACTIVE_POWER = 5; // length = 2 -constexpr size_t RTU2_PV2_VOLTAGE = 7; // length = 1 -constexpr size_t RTU2_PV2_CURRENT = 8; // length = 1 -constexpr size_t RTU2_PV2_ACTIVE_POWER = 9; // length = 2 -constexpr size_t RTU2_GRID_ACTIVE_POWER = 35; // length = 2 -constexpr size_t RTU2_GRID_FREQUENCY = 37; // length = 1 -constexpr size_t RTU2_PHASE1_VOLTAGE = 38; // length = 1 -constexpr size_t RTU2_PHASE1_CURRENT = 39; // length = 1 -constexpr size_t RTU2_PHASE1_ACTIVE_POWER = 40; // length = 2 -constexpr size_t RTU2_PHASE2_VOLTAGE = 42; // length = 1 -constexpr size_t RTU2_PHASE2_CURRENT = 43; // length = 1 -constexpr size_t RTU2_PHASE2_ACTIVE_POWER = 44; // length = 2 -constexpr size_t RTU2_PHASE3_VOLTAGE = 46; // length = 1 -constexpr size_t RTU2_PHASE3_CURRENT = 47; // length = 1 -constexpr size_t RTU2_PHASE3_ACTIVE_POWER = 48; // length = 2 -constexpr size_t RTU2_TODAY_PRODUCTION = 53; // length = 2 -constexpr size_t RTU2_TOTAL_ENERGY_PRODUCTION = 55; // length = 2 -constexpr size_t RTU2_INVERTER_MODULE_TEMP = 93; // length = 1 +constexpr uint16_t RTU2_INVERTER_STATUS = 0; // length = 1 +constexpr uint16_t RTU2_PV_ACTIVE_POWER = 1; // length = 2 +constexpr uint16_t RTU2_PV1_VOLTAGE = 3; // length = 1 +constexpr uint16_t RTU2_PV1_CURRENT = 4; // length = 1 +constexpr uint16_t RTU2_PV1_ACTIVE_POWER = 5; // length = 2 +constexpr uint16_t RTU2_PV2_VOLTAGE = 7; // length = 1 +constexpr uint16_t RTU2_PV2_CURRENT = 8; // length = 1 +constexpr uint16_t RTU2_PV2_ACTIVE_POWER = 9; // length = 2 +constexpr uint16_t RTU2_GRID_ACTIVE_POWER = 35; // length = 2 +constexpr uint16_t RTU2_GRID_FREQUENCY = 37; // length = 1 +constexpr uint16_t RTU2_PHASE1_VOLTAGE = 38; // length = 1 +constexpr uint16_t RTU2_PHASE1_CURRENT = 39; // length = 1 +constexpr uint16_t RTU2_PHASE1_ACTIVE_POWER = 40; // length = 2 +constexpr uint16_t RTU2_PHASE2_VOLTAGE = 42; // length = 1 +constexpr uint16_t RTU2_PHASE2_CURRENT = 43; // length = 1 +constexpr uint16_t RTU2_PHASE2_ACTIVE_POWER = 44; // length = 2 +constexpr uint16_t RTU2_PHASE3_VOLTAGE = 46; // length = 1 +constexpr uint16_t RTU2_PHASE3_CURRENT = 47; // length = 1 +constexpr uint16_t RTU2_PHASE3_ACTIVE_POWER = 48; // length = 2 +constexpr uint16_t RTU2_TODAY_PRODUCTION = 53; // length = 2 +constexpr uint16_t RTU2_TOTAL_ENERGY_PRODUCTION = 55; // length = 2 +constexpr uint16_t RTU2_INVERTER_MODULE_TEMP = 93; // length = 1 class GrowattSolar final : public PollingComponent, public modbus::ModbusClientDevice { public: diff --git a/esphome/components/havells_solar/havells_solar.cpp b/esphome/components/havells_solar/havells_solar.cpp index c98dc0de2f..d43dfbb89a 100644 --- a/esphome/components/havells_solar/havells_solar.cpp +++ b/esphome/components/havells_solar/havells_solar.cpp @@ -4,6 +4,8 @@ namespace esphome::havells_solar { +namespace helpers = modbus::helpers; + static const char *const TAG = "havells_solar"; static const uint8_t MODBUS_REGISTER_COUNT = 48; // 48 x 16-bit registers @@ -16,22 +18,17 @@ void HavellsSolar::on_read_holding_registers(uint16_t start_address, std::span void { - if (sensor == nullptr || reg < start_address) + if (sensor == nullptr) return; - size_t offset = reg - start_address; - if (offset >= registers.size()) - return; - sensor->publish_state(registers[offset] * unit); + if (auto value = helpers::value_at(registers, start_address, reg)) + sensor->publish_state(*value * unit); }; auto publish_2_registers = [&](sensor::Sensor *sensor, uint16_t reg, float unit) -> void { - constexpr auto value_type = modbus::helpers::SensorValueType::U_DWORD; - if (sensor == nullptr || reg < start_address) + if (sensor == nullptr) return; - size_t offset = reg - start_address; - if (offset + modbus::helpers::register_width_for(value_type) > registers.size()) - return; - sensor->publish_state(modbus::helpers::registers_to_value(registers.data() + offset) * unit); + if (auto value = helpers::value_at(registers, start_address, reg)) + sensor->publish_state(*value * unit); }; for (uint8_t i = 0; i < 3; i++) { diff --git a/esphome/components/pzemac/pzemac.cpp b/esphome/components/pzemac/pzemac.cpp index 50c626ec7f..409de91124 100644 --- a/esphome/components/pzemac/pzemac.cpp +++ b/esphome/components/pzemac/pzemac.cpp @@ -3,6 +3,8 @@ namespace esphome::pzemac { +namespace helpers = modbus::helpers; + static const char *const TAG = "pzemac"; static const uint8_t PZEM_CMD_RESET_ENERGY = 0x42; @@ -25,22 +27,17 @@ void PZEMAC::on_read_input_registers(uint16_t start_address, std::span void { - if (sensor == nullptr || reg < start_address) + if (sensor == nullptr) return; - size_t offset = reg - start_address; - if (offset >= registers.size()) - return; - sensor->publish_state(registers[offset] / divisor); + if (auto value = helpers::value_at(registers, start_address, reg)) + sensor->publish_state(*value / divisor); }; auto publish_2_registers = [&](sensor::Sensor *sensor, uint16_t reg, float divisor) -> void { - constexpr auto value_type = modbus::helpers::SensorValueType::U_DWORD_R; - if (sensor == nullptr || reg < start_address) + if (sensor == nullptr) return; - size_t offset = reg - start_address; - if (offset + modbus::helpers::register_width_for(value_type) > registers.size()) - return; - sensor->publish_state(modbus::helpers::registers_to_value(registers.data() + offset) / divisor); + if (auto value = helpers::value_at(registers, start_address, reg)) + sensor->publish_state(*value / divisor); }; publish_1_register(this->voltage_sensor_, PZEM_REGISTER_VOLTAGE, 10.0f); diff --git a/esphome/components/pzemdc/pzemdc.cpp b/esphome/components/pzemdc/pzemdc.cpp index 546e4225de..eb9a355806 100644 --- a/esphome/components/pzemdc/pzemdc.cpp +++ b/esphome/components/pzemdc/pzemdc.cpp @@ -3,6 +3,8 @@ namespace esphome::pzemdc { +namespace helpers = modbus::helpers; + static const char *const TAG = "pzemdc"; static const uint8_t PZEM_CMD_RESET_ENERGY = 0x42; @@ -23,22 +25,17 @@ void PZEMDC::on_read_input_registers(uint16_t start_address, std::span void { - if (sensor == nullptr || reg < start_address) + if (sensor == nullptr) return; - size_t offset = reg - start_address; - if (offset >= registers.size()) - return; - sensor->publish_state(registers[offset] / divisor); + if (auto value = helpers::value_at(registers, start_address, reg)) + sensor->publish_state(*value / divisor); }; auto publish_2_registers = [&](sensor::Sensor *sensor, uint16_t reg, float divisor) -> void { - constexpr auto value_type = modbus::helpers::SensorValueType::U_DWORD_R; - if (sensor == nullptr || reg < start_address) + if (sensor == nullptr) return; - size_t offset = reg - start_address; - if (offset + modbus::helpers::register_width_for(value_type) > registers.size()) - return; - sensor->publish_state(modbus::helpers::registers_to_value(registers.data() + offset) / divisor); + if (auto value = helpers::value_at(registers, start_address, reg)) + sensor->publish_state(*value / divisor); }; publish_1_register(this->voltage_sensor_, PZEM_REGISTER_VOLTAGE, 100.0f); diff --git a/esphome/components/sdm_meter/sdm_meter.cpp b/esphome/components/sdm_meter/sdm_meter.cpp index f242b6b36d..c1b359cc97 100644 --- a/esphome/components/sdm_meter/sdm_meter.cpp +++ b/esphome/components/sdm_meter/sdm_meter.cpp @@ -4,6 +4,8 @@ namespace esphome::sdm_meter { +namespace helpers = modbus::helpers; + static const char *const TAG = "sdm_meter"; static const uint8_t MODBUS_REGISTER_COUNT = 80; // 80 x 16-bit registers (40 float values) @@ -16,13 +18,10 @@ void SDMMeter::on_read_input_registers(uint16_t start_address, std::span registers.size()) - return; - sensor->publish_state(modbus::helpers::registers_to_value(registers.data() + offset)); + if (auto value = helpers::value_at(registers, start_address, reg)) + sensor->publish_state(*value); }; for (uint8_t i = 0; i < 3; i++) { diff --git a/esphome/components/selec_meter/selec_meter.cpp b/esphome/components/selec_meter/selec_meter.cpp index 97831e8354..3ad1f8b87c 100644 --- a/esphome/components/selec_meter/selec_meter.cpp +++ b/esphome/components/selec_meter/selec_meter.cpp @@ -4,6 +4,8 @@ namespace esphome::selec_meter { +namespace helpers = modbus::helpers; + static const char *const TAG = "selec_meter"; static const uint8_t MODBUS_REGISTER_COUNT = 34; // 34 x 16-bit registers @@ -17,13 +19,10 @@ void SelecMeter::on_read_input_registers(uint16_t start_address, std::span void { - constexpr auto value_type = modbus::helpers::SensorValueType::FP32_R; - if (sensor == nullptr || reg < start_address) + if (sensor == nullptr) return; - size_t offset = reg - start_address; - if (offset + modbus::helpers::register_width_for(value_type) > registers.size()) - return; - sensor->publish_state(modbus::helpers::registers_to_value(registers.data() + offset) * unit); + if (auto value = helpers::value_at(registers, start_address, reg)) + sensor->publish_state(*value * unit); }; publish(this->total_active_energy_sensor_, SELEC_TOTAL_ACTIVE_ENERGY, NO_DEC_UNIT);