mirror of
https://github.com/esphome/esphome.git
synced 2024-11-24 16:08:10 +01:00
Add support for SDMXXX energy meters (#1260)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
9a2cf05c5f
commit
bb759d52c8
12 changed files with 525 additions and 2 deletions
|
@ -81,6 +81,7 @@ esphome/components/restart/* @esphome/core
|
|||
esphome/components/rf_bridge/* @jesserockz
|
||||
esphome/components/rtttl/* @glmnet
|
||||
esphome/components/script/* @esphome/core
|
||||
esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/sgp40/* @SenexCrenshaw
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
|
|
|
@ -3,6 +3,7 @@ import esphome.config_validation as cv
|
|||
from esphome.components import sensor, spi
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_REACTIVE_POWER,
|
||||
CONF_VOLTAGE,
|
||||
CONF_CURRENT,
|
||||
CONF_POWER,
|
||||
|
@ -34,7 +35,6 @@ CONF_PHASE_A = "phase_a"
|
|||
CONF_PHASE_B = "phase_b"
|
||||
CONF_PHASE_C = "phase_c"
|
||||
|
||||
CONF_REACTIVE_POWER = "reactive_power"
|
||||
CONF_LINE_FREQUENCY = "line_frequency"
|
||||
CONF_CHIP_TEMPERATURE = "chip_temperature"
|
||||
CONF_GAIN_PGA = "gain_pga"
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.cpp_helpers import gpio_pin_expression
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID, CONF_ADDRESS
|
||||
from esphome.const import CONF_FLOW_CONTROL_PIN, CONF_ID, CONF_ADDRESS
|
||||
from esphome import pins
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
|
@ -15,6 +17,7 @@ CONFIG_SCHEMA = (
|
|||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(Modbus),
|
||||
cv.Optional(CONF_FLOW_CONTROL_PIN): pins.gpio_output_pin_schema,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
|
@ -29,6 +32,10 @@ async def to_code(config):
|
|||
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
if CONF_FLOW_CONTROL_PIN in config:
|
||||
pin = await gpio_pin_expression(config[CONF_FLOW_CONTROL_PIN])
|
||||
cg.add(var.set_flow_control_pin(pin))
|
||||
|
||||
|
||||
def modbus_device_schema(default_address):
|
||||
schema = {
|
||||
|
|
|
@ -6,6 +6,11 @@ namespace modbus {
|
|||
|
||||
static const char *TAG = "modbus";
|
||||
|
||||
void Modbus::setup() {
|
||||
if (this->flow_control_pin_ != nullptr) {
|
||||
this->flow_control_pin_->setup();
|
||||
}
|
||||
}
|
||||
void Modbus::loop() {
|
||||
const uint32_t now = millis();
|
||||
if (now - this->last_modbus_byte_ > 50) {
|
||||
|
@ -94,6 +99,7 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
|
|||
|
||||
void Modbus::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Modbus:");
|
||||
LOG_PIN(" Flow Control Pin: ", this->flow_control_pin_);
|
||||
this->check_uart_settings(9600, 2);
|
||||
}
|
||||
float Modbus::get_setup_priority() const {
|
||||
|
@ -112,7 +118,14 @@ void Modbus::send(uint8_t address, uint8_t function, uint16_t start_address, uin
|
|||
frame[6] = crc >> 0;
|
||||
frame[7] = crc >> 8;
|
||||
|
||||
if (this->flow_control_pin_ != nullptr)
|
||||
this->flow_control_pin_->digital_write(true);
|
||||
|
||||
this->write_array(frame, 8);
|
||||
this->flush();
|
||||
|
||||
if (this->flow_control_pin_ != nullptr)
|
||||
this->flow_control_pin_->digital_write(false);
|
||||
}
|
||||
|
||||
} // namespace modbus
|
||||
|
|
|
@ -12,6 +12,8 @@ class Modbus : public uart::UARTDevice, public Component {
|
|||
public:
|
||||
Modbus() = default;
|
||||
|
||||
void setup() override;
|
||||
|
||||
void loop() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
@ -22,7 +24,11 @@ class Modbus : public uart::UARTDevice, public Component {
|
|||
|
||||
void send(uint8_t address, uint8_t function, uint16_t start_address, uint16_t register_count);
|
||||
|
||||
void set_flow_control_pin(GPIOPin *flow_control_pin) { this->flow_control_pin_ = flow_control_pin; }
|
||||
|
||||
protected:
|
||||
GPIOPin *flow_control_pin_{nullptr};
|
||||
|
||||
bool parse_modbus_byte_(uint8_t byte);
|
||||
|
||||
std::vector<uint8_t> rx_buffer_;
|
||||
|
|
0
esphome/components/sdm_meter/__init__.py
Normal file
0
esphome/components/sdm_meter/__init__.py
Normal file
106
esphome/components/sdm_meter/sdm_meter.cpp
Normal file
106
esphome/components/sdm_meter/sdm_meter.cpp
Normal file
|
@ -0,0 +1,106 @@
|
|||
#include "sdm_meter.h"
|
||||
#include "sdm_meter_registers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sdm_meter {
|
||||
|
||||
static const char *TAG = "sdm_meter";
|
||||
|
||||
static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04;
|
||||
static const uint8_t MODBUS_REGISTER_COUNT = 80; // 74 x 16-bit registers
|
||||
|
||||
void SDMMeter::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||
if (data.size() < MODBUS_REGISTER_COUNT * 2) {
|
||||
ESP_LOGW(TAG, "Invalid size for SDMMeter!");
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
for (uint8_t i = 0; i < 3; 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);
|
||||
}
|
||||
|
||||
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", frequency,
|
||||
import_active_energy, export_active_energy, import_reactive_energy, export_reactive_energy);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void SDMMeter::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); }
|
||||
void SDMMeter::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SDM Meter:");
|
||||
ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_);
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
auto phase = this->phases_[i];
|
||||
if (!phase.setup)
|
||||
continue;
|
||||
ESP_LOGCONFIG(TAG, " Phase %c", i + 'A');
|
||||
LOG_SENSOR(" ", "Voltage", phase.voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current", phase.current_sensor_);
|
||||
LOG_SENSOR(" ", "Active Power", phase.active_power_sensor_);
|
||||
LOG_SENSOR(" ", "Apparent Power", phase.apparent_power_sensor_);
|
||||
LOG_SENSOR(" ", "Reactive Power", phase.reactive_power_sensor_);
|
||||
LOG_SENSOR(" ", "Power Factor", phase.power_factor_sensor_);
|
||||
LOG_SENSOR(" ", "Phase Angle", phase.phase_angle_sensor_);
|
||||
}
|
||||
LOG_SENSOR(" ", "Frequency", this->frequency_sensor_);
|
||||
LOG_SENSOR(" ", "Import Active Energy", this->import_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Export Active Energy", this->export_active_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Import Reactive Energy", this->import_reactive_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Export Reactive Energy", this->export_reactive_energy_sensor_);
|
||||
}
|
||||
|
||||
} // namespace sdm_meter
|
||||
} // namespace esphome
|
79
esphome/components/sdm_meter/sdm_meter.h
Normal file
79
esphome/components/sdm_meter/sdm_meter.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/modbus/modbus.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sdm_meter {
|
||||
|
||||
class SDMMeter : public PollingComponent, public modbus::ModbusDevice {
|
||||
public:
|
||||
void set_voltage_sensor(uint8_t phase, sensor::Sensor *voltage_sensor) {
|
||||
this->phases_[phase].setup = true;
|
||||
this->phases_[phase].voltage_sensor_ = voltage_sensor;
|
||||
}
|
||||
void set_current_sensor(uint8_t phase, sensor::Sensor *current_sensor) {
|
||||
this->phases_[phase].setup = true;
|
||||
this->phases_[phase].current_sensor_ = current_sensor;
|
||||
}
|
||||
void set_active_power_sensor(uint8_t phase, sensor::Sensor *active_power_sensor) {
|
||||
this->phases_[phase].setup = true;
|
||||
this->phases_[phase].active_power_sensor_ = active_power_sensor;
|
||||
}
|
||||
void set_apparent_power_sensor(uint8_t phase, sensor::Sensor *apparent_power_sensor) {
|
||||
this->phases_[phase].setup = true;
|
||||
this->phases_[phase].apparent_power_sensor_ = apparent_power_sensor;
|
||||
}
|
||||
void set_reactive_power_sensor(uint8_t phase, sensor::Sensor *reactive_power_sensor) {
|
||||
this->phases_[phase].setup = true;
|
||||
this->phases_[phase].reactive_power_sensor_ = reactive_power_sensor;
|
||||
}
|
||||
void set_power_factor_sensor(uint8_t phase, sensor::Sensor *power_factor_sensor) {
|
||||
this->phases_[phase].setup = true;
|
||||
this->phases_[phase].power_factor_sensor_ = power_factor_sensor;
|
||||
}
|
||||
void set_phase_angle_sensor(uint8_t phase, sensor::Sensor *phase_angle_sensor) {
|
||||
this->phases_[phase].setup = true;
|
||||
this->phases_[phase].phase_angle_sensor_ = phase_angle_sensor;
|
||||
}
|
||||
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { this->frequency_sensor_ = frequency_sensor; }
|
||||
void set_import_active_energy_sensor(sensor::Sensor *import_active_energy_sensor) {
|
||||
this->import_active_energy_sensor_ = import_active_energy_sensor;
|
||||
}
|
||||
void set_export_active_energy_sensor(sensor::Sensor *export_active_energy_sensor) {
|
||||
this->export_active_energy_sensor_ = export_active_energy_sensor;
|
||||
}
|
||||
void set_import_reactive_energy_sensor(sensor::Sensor *import_reactive_energy_sensor) {
|
||||
this->import_reactive_energy_sensor_ = import_reactive_energy_sensor;
|
||||
}
|
||||
void set_export_reactive_energy_sensor(sensor::Sensor *export_reactive_energy_sensor) {
|
||||
this->export_reactive_energy_sensor_ = export_reactive_energy_sensor;
|
||||
}
|
||||
|
||||
void update() override;
|
||||
|
||||
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
struct SDMPhase {
|
||||
bool setup{false};
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *active_power_sensor_{nullptr};
|
||||
sensor::Sensor *apparent_power_sensor_{nullptr};
|
||||
sensor::Sensor *reactive_power_sensor_{nullptr};
|
||||
sensor::Sensor *power_factor_sensor_{nullptr};
|
||||
sensor::Sensor *phase_angle_sensor_{nullptr};
|
||||
} phases_[3];
|
||||
sensor::Sensor *frequency_sensor_{nullptr};
|
||||
sensor::Sensor *import_active_energy_sensor_{nullptr};
|
||||
sensor::Sensor *export_active_energy_sensor_{nullptr};
|
||||
sensor::Sensor *import_reactive_energy_sensor_{nullptr};
|
||||
sensor::Sensor *export_reactive_energy_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace sdm_meter
|
||||
} // namespace esphome
|
114
esphome/components/sdm_meter/sdm_meter_registers.h
Normal file
114
esphome/components/sdm_meter/sdm_meter_registers.h
Normal file
|
@ -0,0 +1,114 @@
|
|||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
namespace sdm_meter {
|
||||
|
||||
/* PHASE STATUS REGISTERS */
|
||||
static const uint16_t SDM_PHASE_1_VOLTAGE = 0x0000;
|
||||
static const uint16_t SDM_PHASE_2_VOLTAGE = 0x0002;
|
||||
static const uint16_t SDM_PHASE_3_VOLTAGE = 0x0004;
|
||||
static const uint16_t SDM_PHASE_1_CURRENT = 0x0006;
|
||||
static const uint16_t SDM_PHASE_2_CURRENT = 0x0008;
|
||||
static const uint16_t SDM_PHASE_3_CURRENT = 0x000A;
|
||||
static const uint16_t SDM_PHASE_1_ACTIVE_POWER = 0x000C;
|
||||
static const uint16_t SDM_PHASE_2_ACTIVE_POWER = 0x000E;
|
||||
static const uint16_t SDM_PHASE_3_ACTIVE_POWER = 0x0010;
|
||||
static const uint16_t SDM_PHASE_1_APPARENT_POWER = 0x0012;
|
||||
static const uint16_t SDM_PHASE_2_APPARENT_POWER = 0x0014;
|
||||
static const uint16_t SDM_PHASE_3_APPARENT_POWER = 0x0016;
|
||||
static const uint16_t SDM_PHASE_1_REACTIVE_POWER = 0x0018;
|
||||
static const uint16_t SDM_PHASE_2_REACTIVE_POWER = 0x001A;
|
||||
static const uint16_t SDM_PHASE_3_REACTIVE_POWER = 0x001C;
|
||||
static const uint16_t SDM_PHASE_1_POWER_FACTOR = 0x001E;
|
||||
static const uint16_t SDM_PHASE_2_POWER_FACTOR = 0x0020;
|
||||
static const uint16_t SDM_PHASE_3_POWER_FACTOR = 0x0022;
|
||||
static const uint16_t SDM_PHASE_1_ANGLE = 0x0024;
|
||||
static const uint16_t SDM_PHASE_2_ANGLE = 0x0026;
|
||||
static const uint16_t SDM_PHASE_3_ANGLE = 0x0028;
|
||||
|
||||
static const uint16_t SDM_AVERAGE_L_TO_N_VOLTS = 0x002A;
|
||||
static const uint16_t SDM_AVERAGE_LINE_CURRENT = 0x002E;
|
||||
static const uint16_t SDM_SUM_LINE_CURRENT = 0x0030;
|
||||
static const uint16_t SDM_TOTAL_SYSTEM_POWER = 0x0034;
|
||||
static const uint16_t SDM_TOTAL_SYSTEM_APPARENT_POWER = 0x0038;
|
||||
static const uint16_t SDM_TOTAL_SYSTEM_REACTIVE_POWER = 0x003C;
|
||||
static const uint16_t SDM_TOTAL_SYSTEM_POWER_FACTOR = 0x003E;
|
||||
static const uint16_t SDM_TOTAL_SYSTEM_PHASE_ANGLE = 0x0042;
|
||||
|
||||
static const uint16_t SDM_FREQUENCY = 0x0046;
|
||||
|
||||
static const uint16_t SDM_IMPORT_ACTIVE_ENERGY = 0x0048;
|
||||
static const uint16_t SDM_EXPORT_ACTIVE_ENERGY = 0x004A;
|
||||
static const uint16_t SDM_IMPORT_REACTIVE_ENERGY = 0x004C;
|
||||
static const uint16_t SDM_EXPORT_REACTIVE_ENERGY = 0x004E;
|
||||
|
||||
static const uint16_t SDM_VAH_SINCE_LAST_RESET = 0x0050;
|
||||
static const uint16_t SDM_AH_SINCE_LAST_RESET = 0x0052;
|
||||
static const uint16_t SDM_TOTAL_SYSTEM_POWER_DEMAND = 0x0054;
|
||||
static const uint16_t SDM_MAXIMUM_TOTAL_SYSTEM_POWER_DEMAND = 0x0056;
|
||||
static const uint16_t SDM_CURRENT_SYSTEM_POSITIVE_POWER_DEMAND = 0x0058;
|
||||
static const uint16_t SDM_MAXIMUM_SYSTEM_POSITIVE_POWER_DEMAND = 0x005A;
|
||||
static const uint16_t SDM_CURRENT_SYSTEM_REVERSE_POWER_DEMAND = 0x005C;
|
||||
static const uint16_t SDM_MAXIMUM_SYSTEM_REVERSE_POWER_DEMAND = 0x005E;
|
||||
static const uint16_t SDM_TOTAL_SYSTEM_VA_DEMAND = 0x0064;
|
||||
static const uint16_t SDM_MAXIMUM_TOTAL_SYSTEM_VA_DEMAND = 0x0066;
|
||||
static const uint16_t SDM_NEUTRAL_CURRENT_DEMAND = 0x0068;
|
||||
static const uint16_t SDM_MAXIMUM_NEUTRAL_CURRENT = 0x006A;
|
||||
static const uint16_t SDM_LINE_1_TO_LINE_2_VOLTS = 0x00C8;
|
||||
static const uint16_t SDM_LINE_2_TO_LINE_3_VOLTS = 0x00CA;
|
||||
static const uint16_t SDM_LINE_3_TO_LINE_1_VOLTS = 0x00CC;
|
||||
static const uint16_t SDM_AVERAGE_LINE_TO_LINE_VOLTS = 0x00CE;
|
||||
static const uint16_t SDM_NEUTRAL_CURRENT = 0x00E0;
|
||||
|
||||
static const uint16_t SDM_PHASE_1_LN_VOLTS_THD = 0x00EA;
|
||||
static const uint16_t SDM_PHASE_2_LN_VOLTS_THD = 0x00EC;
|
||||
static const uint16_t SDM_PHASE_3_LN_VOLTS_THD = 0x00EE;
|
||||
static const uint16_t SDM_PHASE_1_CURRENT_THD = 0x00F0;
|
||||
static const uint16_t SDM_PHASE_2_CURRENT_THD = 0x00F2;
|
||||
static const uint16_t SDM_PHASE_3_CURRENT_THD = 0x00F4;
|
||||
|
||||
static const uint16_t SDM_AVERAGE_LINE_TO_NEUTRAL_VOLTS_THD = 0x00F8;
|
||||
static const uint16_t SDM_AVERAGE_LINE_CURRENT_THD = 0x00FA;
|
||||
static const uint16_t SDM_TOTAL_SYSTEM_POWER_FACTOR_INV = 0x00FE;
|
||||
static const uint16_t SDM_PHASE_1_CURRENT_DEMAND = 0x0102;
|
||||
static const uint16_t SDM_PHASE_2_CURRENT_DEMAND = 0x0104;
|
||||
static const uint16_t SDM_PHASE_3_CURRENT_DEMAND = 0x0106;
|
||||
static const uint16_t SDM_MAXIMUM_PHASE_1_CURRENT_DEMAND = 0x0108;
|
||||
static const uint16_t SDM_MAXIMUM_PHASE_2_CURRENT_DEMAND = 0x010A;
|
||||
static const uint16_t SDM_MAXIMUM_PHASE_3_CURRENT_DEMAND = 0x010C;
|
||||
static const uint16_t SDM_LINE_1_TO_LINE_2_VOLTS_THD = 0x014E;
|
||||
static const uint16_t SDM_LINE_2_TO_LINE_3_VOLTS_THD = 0x0150;
|
||||
static const uint16_t SDM_LINE_3_TO_LINE_1_VOLTS_THD = 0x0152;
|
||||
static const uint16_t SDM_AVERAGE_LINE_TO_LINE_VOLTS_THD = 0x0154;
|
||||
|
||||
static const uint16_t SDM_TOTAL_ACTIVE_ENERGY = 0x0156;
|
||||
static const uint16_t SDM_TOTAL_REACTIVE_ENERGY = 0x0158;
|
||||
|
||||
static const uint16_t SDM_L1_IMPORT_ACTIVE_ENERGY = 0x015A;
|
||||
static const uint16_t SDM_L2_IMPORT_ACTIVE_ENERGY = 0x015C;
|
||||
static const uint16_t SDM_L3_IMPORT_ACTIVE_ENERGY = 0x015E;
|
||||
static const uint16_t SDM_L1_EXPORT_ACTIVE_ENERGY = 0x0160;
|
||||
static const uint16_t SDM_L2_EXPORT_ACTIVE_ENERGY = 0x0162;
|
||||
static const uint16_t SDM_L3_EXPORT_ACTIVE_ENERGY = 0x0164;
|
||||
static const uint16_t SDM_L1_TOTAL_ACTIVE_ENERGY = 0x0166;
|
||||
static const uint16_t SDM_L2_TOTAL_ACTIVE_ENERGY = 0x0168;
|
||||
static const uint16_t SDM_L3_TOTAL_ACTIVE_ENERGY = 0x016a;
|
||||
static const uint16_t SDM_L1_IMPORT_REACTIVE_ENERGY = 0x016C;
|
||||
static const uint16_t SDM_L2_IMPORT_REACTIVE_ENERGY = 0x016E;
|
||||
static const uint16_t SDM_L3_IMPORT_REACTIVE_ENERGY = 0x0170;
|
||||
static const uint16_t SDM_L1_EXPORT_REACTIVE_ENERGY = 0x0172;
|
||||
static const uint16_t SDM_L2_EXPORT_REACTIVE_ENERGY = 0x0174;
|
||||
static const uint16_t SDM_L3_EXPORT_REACTIVE_ENERGY = 0x0176;
|
||||
static const uint16_t SDM_L1_TOTAL_REACTIVE_ENERGY = 0x0178;
|
||||
static const uint16_t SDM_L2_TOTAL_REACTIVE_ENERGY = 0x017A;
|
||||
static const uint16_t SDM_L3_TOTAL_REACTIVE_ENERGY = 0x017C;
|
||||
|
||||
static const uint16_t SDM_CURRENT_RESETTABLE_TOTAL_ACTIVE_ENERGY = 0x0180;
|
||||
static const uint16_t SDM_CURRENT_RESETTABLE_TOTAL_REACTIVE_ENERGY = 0x0182;
|
||||
static const uint16_t SDM_CURRENT_RESETTABLE_IMPORT_ENERGY = 0x0184;
|
||||
static const uint16_t SDM_CURRENT_RESETTABLE_EXPORT_ENERGY = 0x0186;
|
||||
static const uint16_t SDM_IMPORT_POWER = 0x0500;
|
||||
static const uint16_t SDM_EXPORT_POWER = 0x0502;
|
||||
|
||||
} // namespace sdm_meter
|
||||
} // namespace esphome
|
131
esphome/components/sdm_meter/sensor.py
Normal file
131
esphome/components/sdm_meter/sensor.py
Normal file
|
@ -0,0 +1,131 @@
|
|||
from esphome.components.atm90e32.sensor import CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, modbus
|
||||
from esphome.const import (
|
||||
CONF_ACTIVE_POWER,
|
||||
CONF_APPARENT_POWER,
|
||||
CONF_CURRENT,
|
||||
CONF_EXPORT_ACTIVE_ENERGY,
|
||||
CONF_EXPORT_REACTIVE_ENERGY,
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
CONF_IMPORT_ACTIVE_ENERGY,
|
||||
CONF_IMPORT_REACTIVE_ENERGY,
|
||||
CONF_PHASE_ANGLE,
|
||||
CONF_POWER_FACTOR,
|
||||
CONF_REACTIVE_POWER,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ICON_CURRENT_AC,
|
||||
ICON_EMPTY,
|
||||
ICON_FLASH,
|
||||
UNIT_AMPERE,
|
||||
UNIT_DEGREES,
|
||||
UNIT_EMPTY,
|
||||
UNIT_HERTZ,
|
||||
UNIT_VOLT,
|
||||
UNIT_VOLT_AMPS,
|
||||
UNIT_VOLT_AMPS_REACTIVE,
|
||||
UNIT_VOLT_AMPS_REACTIVE_HOURS,
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["modbus"]
|
||||
CODEOWNERS = ["@polyfaces", "@jesserockz"]
|
||||
|
||||
sdm_meter_ns = cg.esphome_ns.namespace("sdm_meter")
|
||||
SDMMeter = sdm_meter_ns.class_("SDMMeter", cg.PollingComponent, modbus.ModbusDevice)
|
||||
|
||||
PHASE_SENSORS = {
|
||||
CONF_VOLTAGE: sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE),
|
||||
CONF_CURRENT: sensor.sensor_schema(
|
||||
UNIT_AMPERE, ICON_EMPTY, 3, DEVICE_CLASS_CURRENT
|
||||
),
|
||||
CONF_ACTIVE_POWER: sensor.sensor_schema(
|
||||
UNIT_WATT, ICON_EMPTY, 2, DEVICE_CLASS_POWER
|
||||
),
|
||||
CONF_APPARENT_POWER: sensor.sensor_schema(
|
||||
UNIT_VOLT_AMPS, ICON_EMPTY, 2, DEVICE_CLASS_POWER
|
||||
),
|
||||
CONF_REACTIVE_POWER: sensor.sensor_schema(
|
||||
UNIT_VOLT_AMPS_REACTIVE, ICON_EMPTY, 2, DEVICE_CLASS_POWER
|
||||
),
|
||||
CONF_POWER_FACTOR: sensor.sensor_schema(
|
||||
UNIT_EMPTY, ICON_EMPTY, 3, DEVICE_CLASS_POWER_FACTOR
|
||||
),
|
||||
CONF_PHASE_ANGLE: sensor.sensor_schema(UNIT_DEGREES, ICON_FLASH, 3),
|
||||
}
|
||||
|
||||
PHASE_SCHEMA = cv.Schema(
|
||||
{cv.Optional(sensor): schema for sensor, schema in PHASE_SENSORS.items()}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SDMMeter),
|
||||
cv.Optional(CONF_PHASE_A): PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_B): PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_C): PHASE_SCHEMA,
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||
UNIT_HERTZ, ICON_CURRENT_AC, 3
|
||||
),
|
||||
cv.Optional(CONF_IMPORT_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY
|
||||
),
|
||||
cv.Optional(CONF_EXPORT_ACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_WATT_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY
|
||||
),
|
||||
cv.Optional(CONF_IMPORT_REACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_VOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY
|
||||
),
|
||||
cv.Optional(CONF_EXPORT_REACTIVE_ENERGY): sensor.sensor_schema(
|
||||
UNIT_VOLT_AMPS_REACTIVE_HOURS, ICON_EMPTY, 2, DEVICE_CLASS_ENERGY
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("10s"))
|
||||
.extend(modbus.modbus_device_schema(0x01))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await modbus.register_modbus_device(var, config)
|
||||
|
||||
if CONF_FREQUENCY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FREQUENCY])
|
||||
cg.add(var.set_frequency_sensor(sens))
|
||||
|
||||
if CONF_IMPORT_ACTIVE_ENERGY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_IMPORT_ACTIVE_ENERGY])
|
||||
cg.add(var.set_import_active_energy_sensor(sens))
|
||||
|
||||
if CONF_EXPORT_ACTIVE_ENERGY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_EXPORT_ACTIVE_ENERGY])
|
||||
cg.add(var.set_export_active_energy_sensor(sens))
|
||||
|
||||
if CONF_IMPORT_REACTIVE_ENERGY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_IMPORT_REACTIVE_ENERGY])
|
||||
cg.add(var.set_import_reactive_energy_sensor(sens))
|
||||
|
||||
if CONF_EXPORT_REACTIVE_ENERGY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_EXPORT_REACTIVE_ENERGY])
|
||||
cg.add(var.set_export_reactive_energy_sensor(sens))
|
||||
|
||||
for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]):
|
||||
if phase not in config:
|
||||
continue
|
||||
|
||||
phase_config = config[phase]
|
||||
for sensor_type in PHASE_SENSORS:
|
||||
if sensor_type in phase_config:
|
||||
sens = await sensor.new_sensor(phase_config[sensor_type])
|
||||
cg.add(getattr(var, f"set_{sensor_type}_sensor")(i, sens))
|
|
@ -55,12 +55,14 @@ CONF_ACCELERATION_Z = "acceleration_z"
|
|||
CONF_ACCURACY = "accuracy"
|
||||
CONF_ACCURACY_DECIMALS = "accuracy_decimals"
|
||||
CONF_ACTION_ID = "action_id"
|
||||
CONF_ACTIVE_POWER = "active_power"
|
||||
CONF_ADDRESS = "address"
|
||||
CONF_ADDRESSABLE_LIGHT_ID = "addressable_light_id"
|
||||
CONF_ALPHA = "alpha"
|
||||
CONF_ALTITUDE = "altitude"
|
||||
CONF_AND = "and"
|
||||
CONF_AP = "ap"
|
||||
CONF_APPARENT_POWER = "apparent_power"
|
||||
CONF_ARDUINO_VERSION = "arduino_version"
|
||||
CONF_ARGS = "args"
|
||||
CONF_ASSUMED_STATE = "assumed_state"
|
||||
|
@ -194,6 +196,8 @@ CONF_ESPHOME = "esphome"
|
|||
CONF_ETHERNET = "ethernet"
|
||||
CONF_EVENT = "event"
|
||||
CONF_EXPIRE_AFTER = "expire_after"
|
||||
CONF_EXPORT_ACTIVE_ENERGY = "export_active_energy"
|
||||
CONF_EXPORT_REACTIVE_ENERGY = "export_reactive_energy"
|
||||
CONF_EXTERNAL_COMPONENTS = "external_components"
|
||||
CONF_EXTERNAL_VCC = "external_vcc"
|
||||
CONF_FALLING_EDGE = "falling_edge"
|
||||
|
@ -218,6 +222,7 @@ CONF_FILTERS = "filters"
|
|||
CONF_FINGER_ID = "finger_id"
|
||||
CONF_FINGERPRINT_COUNT = "fingerprint_count"
|
||||
CONF_FLASH_LENGTH = "flash_length"
|
||||
CONF_FLOW_CONTROL_PIN = "flow_control_pin"
|
||||
CONF_FOR = "for"
|
||||
CONF_FORCE_UPDATE = "force_update"
|
||||
CONF_FORMALDEHYDE = "formaldehyde"
|
||||
|
@ -261,6 +266,8 @@ CONF_IF = "if"
|
|||
CONF_IIR_FILTER = "iir_filter"
|
||||
CONF_ILLUMINANCE = "illuminance"
|
||||
CONF_IMPEDANCE = "impedance"
|
||||
CONF_IMPORT_ACTIVE_ENERGY = "import_active_energy"
|
||||
CONF_IMPORT_REACTIVE_ENERGY = "import_reactive_energy"
|
||||
CONF_INCLUDES = "includes"
|
||||
CONF_INDEX = "index"
|
||||
CONF_INDOOR = "indoor"
|
||||
|
@ -413,6 +420,7 @@ CONF_PAYLOAD = "payload"
|
|||
CONF_PAYLOAD_AVAILABLE = "payload_available"
|
||||
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
|
||||
CONF_PERIOD = "period"
|
||||
CONF_PHASE_ANGLE = "phase_angle"
|
||||
CONF_PHASE_BALANCER = "phase_balancer"
|
||||
CONF_PIN = "pin"
|
||||
CONF_PIN_A = "pin_a"
|
||||
|
@ -455,6 +463,7 @@ CONF_RATE = "rate"
|
|||
CONF_RAW = "raw"
|
||||
CONF_RC_CODE_1 = "rc_code_1"
|
||||
CONF_RC_CODE_2 = "rc_code_2"
|
||||
CONF_REACTIVE_POWER = "reactive_power"
|
||||
CONF_REBOOT_TIMEOUT = "reboot_timeout"
|
||||
CONF_RECEIVE_TIMEOUT = "receive_timeout"
|
||||
CONF_RED = "red"
|
||||
|
@ -715,6 +724,7 @@ UNIT_STEPS = "steps"
|
|||
UNIT_VOLT = "V"
|
||||
UNIT_VOLT_AMPS = "VA"
|
||||
UNIT_VOLT_AMPS_REACTIVE = "VAR"
|
||||
UNIT_VOLT_AMPS_REACTIVE_HOURS = "VARh"
|
||||
UNIT_WATT = "W"
|
||||
UNIT_WATT_HOURS = "Wh"
|
||||
|
||||
|
|
|
@ -469,6 +469,62 @@ sensor:
|
|||
name: "Fingerprint Last Finger ID"
|
||||
last_confidence:
|
||||
name: "Fingerprint Last Confidence"
|
||||
- platform: sdm_meter
|
||||
phase_a:
|
||||
current:
|
||||
name: 'Phase A Current'
|
||||
voltage:
|
||||
name: 'Phase A Voltage'
|
||||
active_power:
|
||||
name: 'Phase A Power'
|
||||
power_factor:
|
||||
name: 'Phase A Power Factor'
|
||||
apparent_power:
|
||||
name: 'Phase A Apparent Power'
|
||||
reactive_power:
|
||||
name: 'Phase A Reactive Power'
|
||||
phase_angle:
|
||||
name: 'Phase A Phase Angle'
|
||||
phase_b:
|
||||
current:
|
||||
name: 'Phase B Current'
|
||||
voltage:
|
||||
name: 'Phase B Voltage'
|
||||
active_power:
|
||||
name: 'Phase B Power'
|
||||
power_factor:
|
||||
name: 'Phase B Power Factor'
|
||||
apparent_power:
|
||||
name: 'Phase B Apparent Power'
|
||||
reactive_power:
|
||||
name: 'Phase B Reactive Power'
|
||||
phase_angle:
|
||||
name: 'Phase B Phase Angle'
|
||||
phase_c:
|
||||
current:
|
||||
name: 'Phase C Current'
|
||||
voltage:
|
||||
name: 'Phase C Voltage'
|
||||
active_power:
|
||||
name: 'Phase C Power'
|
||||
power_factor:
|
||||
name: 'Phase C Power Factor'
|
||||
apparent_power:
|
||||
name: 'Phase C Apparent Power'
|
||||
reactive_power:
|
||||
name: 'Phase C Reactive Power'
|
||||
phase_angle:
|
||||
name: 'Phase C Phase Angle'
|
||||
frequency:
|
||||
name: 'Frequency'
|
||||
import_active_energy:
|
||||
name: 'Import Active Energy'
|
||||
export_active_energy:
|
||||
name: 'Export Active Energy'
|
||||
import_reactive_energy:
|
||||
name: 'Import Reactive Energy'
|
||||
export_reactive_energy:
|
||||
name: 'Export Reactive Energy'
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
|
|
Loading…
Reference in a new issue