mirror of
https://github.com/esphome/esphome.git
synced 2026-08-29 17:16:45 +00:00
[modbus] Decode by register address in the modbus sensor components (#18874)
This commit is contained in:
@@ -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<con
|
||||
|
||||
// Publish a sensor if its register(s) are in this response; skipping absent registers keeps this
|
||||
// correct for any read range, so the poll may be split into multiple requests.
|
||||
auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t reg, float unit) -> 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<helpers::SensorValueType::U_WORD>(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<value_type>(registers.data() + offset) * unit);
|
||||
if (auto value = helpers::value_at<helpers::SensorValueType::U_DWORD>(registers, start_address, reg))
|
||||
sensor->publish_state(*value * unit);
|
||||
};
|
||||
|
||||
switch (this->protocol_version_) {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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<c
|
||||
// Publish a sensor if its register(s) are in this response; skipping absent registers keeps this
|
||||
// correct for any read range, so the poll may be split into multiple requests.
|
||||
auto publish_1_register = [&](sensor::Sensor *sensor, uint16_t reg, float unit) -> 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<helpers::SensorValueType::U_WORD>(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<value_type>(registers.data() + offset) * unit);
|
||||
if (auto value = helpers::value_at<helpers::SensorValueType::U_DWORD>(registers, start_address, reg))
|
||||
sensor->publish_state(*value * unit);
|
||||
};
|
||||
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
|
||||
@@ -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<const uin
|
||||
// Publish a sensor if its register(s) are in this response; skipping absent registers keeps this
|
||||
// correct for any read range, so the poll may be split into multiple requests.
|
||||
auto publish_1_register = [&](sensor::Sensor *sensor, uint16_t reg, float divisor) -> 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<helpers::SensorValueType::U_WORD>(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<value_type>(registers.data() + offset) / divisor);
|
||||
if (auto value = helpers::value_at<helpers::SensorValueType::U_DWORD_R>(registers, start_address, reg))
|
||||
sensor->publish_state(*value / divisor);
|
||||
};
|
||||
|
||||
publish_1_register(this->voltage_sensor_, PZEM_REGISTER_VOLTAGE, 10.0f);
|
||||
|
||||
@@ -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<const uin
|
||||
// Publish a sensor if its register(s) are in this response; skipping absent registers keeps this
|
||||
// correct for any read range, so the poll may be split into multiple requests.
|
||||
auto publish_1_register = [&](sensor::Sensor *sensor, uint16_t reg, float divisor) -> 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<helpers::SensorValueType::U_WORD>(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<value_type>(registers.data() + offset) / divisor);
|
||||
if (auto value = helpers::value_at<helpers::SensorValueType::U_DWORD_R>(registers, start_address, reg))
|
||||
sensor->publish_state(*value / divisor);
|
||||
};
|
||||
|
||||
publish_1_register(this->voltage_sensor_, PZEM_REGISTER_VOLTAGE, 100.0f);
|
||||
|
||||
@@ -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<const u
|
||||
// Publish a sensor if both of its registers are in this response; skipping absent registers keeps
|
||||
// this correct for any read range, so the poll may be split into multiple requests.
|
||||
auto publish = [&](uint16_t reg, sensor::Sensor *sensor) {
|
||||
constexpr auto value_type = modbus::helpers::SensorValueType::FP32;
|
||||
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<value_type>(registers.data() + offset));
|
||||
if (auto value = helpers::value_at<helpers::SensorValueType::FP32>(registers, start_address, reg))
|
||||
sensor->publish_state(*value);
|
||||
};
|
||||
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
|
||||
@@ -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<const
|
||||
// this correct for any read range, so the poll may be split into multiple requests.
|
||||
// Values are 32-bit floats, low word first.
|
||||
auto publish = [&](sensor::Sensor *sensor, uint16_t reg, float unit) -> 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<value_type>(registers.data() + offset) * unit);
|
||||
if (auto value = helpers::value_at<helpers::SensorValueType::FP32_R>(registers, start_address, reg))
|
||||
sensor->publish_state(*value * unit);
|
||||
};
|
||||
|
||||
publish(this->total_active_energy_sensor_, SELEC_TOTAL_ACTIVE_ENERGY, NO_DEC_UNIT);
|
||||
|
||||
Reference in New Issue
Block a user