[sdm_meter] Use the typed modbus read callback with address-based extraction (#18849)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Bonne Eggleston
2026-08-28 15:34:23 -05:00
committed by GitHub
co-authored by J. Nick Koston
parent 346c250901
commit 7255315ce2
2 changed files with 31 additions and 66 deletions
+29 -65
View File
@@ -1,85 +1,49 @@
#include "sdm_meter.h"
#include "sdm_meter_registers.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::sdm_meter {
static const char *const TAG = "sdm_meter";
static const uint8_t MODBUS_REGISTER_COUNT = 80; // 74 x 16-bit registers
static const uint8_t MODBUS_REGISTER_COUNT = 80; // 80 x 16-bit registers (40 float values)
void SDMMeter::on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) {
auto data = modbus::helpers::server_pdu_payload(response_pdu);
if (data.size() < MODBUS_REGISTER_COUNT * 2) {
ESP_LOGW(TAG, "Invalid size for SDMMeter!");
return;
}
void SDMMeter::on_read_input_registers(uint16_t start_address, std::span<const uint16_t> registers,
modbus::ResponseStatus status) {
if (!modbus::succeeded(status))
return; // the hub already logs exception responses
auto sdm_meter_get_float = [&](size_t i) -> float {
uint32_t temp = encode_uint32(data[i], data[i + 1], data[i + 2], data[i + 3]);
float f;
memcpy(&f, &temp, sizeof(f));
return f;
// 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)
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));
};
for (uint8_t i = 0; i < 3; i++) {
auto phase = this->phases_[i];
auto &phase = this->phases_[i];
if (!phase.setup)
continue;
float voltage = sdm_meter_get_float(SDM_PHASE_1_VOLTAGE * 2 + (i * 4));
float current = sdm_meter_get_float(SDM_PHASE_1_CURRENT * 2 + (i * 4));
float active_power = sdm_meter_get_float(SDM_PHASE_1_ACTIVE_POWER * 2 + (i * 4));
float apparent_power = sdm_meter_get_float(SDM_PHASE_1_APPARENT_POWER * 2 + (i * 4));
float reactive_power = sdm_meter_get_float(SDM_PHASE_1_REACTIVE_POWER * 2 + (i * 4));
float power_factor = sdm_meter_get_float(SDM_PHASE_1_POWER_FACTOR * 2 + (i * 4));
float phase_angle = sdm_meter_get_float(SDM_PHASE_1_ANGLE * 2 + (i * 4));
ESP_LOGD(
TAG,
"SDMMeter Phase %c: V=%.3f V, I=%.3f A, Active P=%.3f W, Apparent P=%.3f VA, Reactive P=%.3f var, PF=%.3f, "
"PA=%.3f °",
i + 'A', voltage, current, active_power, apparent_power, reactive_power, power_factor, phase_angle);
if (phase.voltage_sensor_ != nullptr)
phase.voltage_sensor_->publish_state(voltage);
if (phase.current_sensor_ != nullptr)
phase.current_sensor_->publish_state(current);
if (phase.active_power_sensor_ != nullptr)
phase.active_power_sensor_->publish_state(active_power);
if (phase.apparent_power_sensor_ != nullptr)
phase.apparent_power_sensor_->publish_state(apparent_power);
if (phase.reactive_power_sensor_ != nullptr)
phase.reactive_power_sensor_->publish_state(reactive_power);
if (phase.power_factor_sensor_ != nullptr)
phase.power_factor_sensor_->publish_state(power_factor);
if (phase.phase_angle_sensor_ != nullptr)
phase.phase_angle_sensor_->publish_state(phase_angle);
publish(SDM_PHASE_1_VOLTAGE + i * 2, phase.voltage_sensor_);
publish(SDM_PHASE_1_CURRENT + i * 2, phase.current_sensor_);
publish(SDM_PHASE_1_ACTIVE_POWER + i * 2, phase.active_power_sensor_);
publish(SDM_PHASE_1_APPARENT_POWER + i * 2, phase.apparent_power_sensor_);
publish(SDM_PHASE_1_REACTIVE_POWER + i * 2, phase.reactive_power_sensor_);
publish(SDM_PHASE_1_POWER_FACTOR + i * 2, phase.power_factor_sensor_);
publish(SDM_PHASE_1_ANGLE + i * 2, phase.phase_angle_sensor_);
}
float total_power = sdm_meter_get_float(SDM_TOTAL_SYSTEM_POWER * 2);
float frequency = sdm_meter_get_float(SDM_FREQUENCY * 2);
float import_active_energy = sdm_meter_get_float(SDM_IMPORT_ACTIVE_ENERGY * 2);
float export_active_energy = sdm_meter_get_float(SDM_EXPORT_ACTIVE_ENERGY * 2);
float import_reactive_energy = sdm_meter_get_float(SDM_IMPORT_REACTIVE_ENERGY * 2);
float export_reactive_energy = sdm_meter_get_float(SDM_EXPORT_REACTIVE_ENERGY * 2);
ESP_LOGD(TAG, "SDMMeter: F=%.3f Hz, Im.A.E=%.3f Wh, Ex.A.E=%.3f Wh, Im.R.E=%.3f VARh, Ex.R.E=%.3f VARh, T.P=%.3f W",
frequency, import_active_energy, export_active_energy, import_reactive_energy, export_reactive_energy,
total_power);
if (this->total_power_sensor_ != nullptr)
this->total_power_sensor_->publish_state(total_power);
if (this->frequency_sensor_ != nullptr)
this->frequency_sensor_->publish_state(frequency);
if (this->import_active_energy_sensor_ != nullptr)
this->import_active_energy_sensor_->publish_state(import_active_energy);
if (this->export_active_energy_sensor_ != nullptr)
this->export_active_energy_sensor_->publish_state(export_active_energy);
if (this->import_reactive_energy_sensor_ != nullptr)
this->import_reactive_energy_sensor_->publish_state(import_reactive_energy);
if (this->export_reactive_energy_sensor_ != nullptr)
this->export_reactive_energy_sensor_->publish_state(export_reactive_energy);
publish(SDM_TOTAL_SYSTEM_POWER, this->total_power_sensor_);
publish(SDM_FREQUENCY, this->frequency_sensor_);
publish(SDM_IMPORT_ACTIVE_ENERGY, this->import_active_energy_sensor_);
publish(SDM_EXPORT_ACTIVE_ENERGY, this->export_active_energy_sensor_);
publish(SDM_IMPORT_REACTIVE_ENERGY, this->import_reactive_energy_sensor_);
publish(SDM_EXPORT_REACTIVE_ENERGY, this->export_reactive_energy_sensor_);
}
void SDMMeter::update() { this->read_input_registers(0, MODBUS_REGISTER_COUNT); }
+2 -1
View File
@@ -55,7 +55,8 @@ class SDMMeter final : public PollingComponent, public modbus::ModbusClientDevic
void update() override;
void on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) override;
void on_read_input_registers(uint16_t start_address, std::span<const uint16_t> registers,
modbus::ResponseStatus status) override;
void dump_config() override;