mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Add support for SDMXXX energy meters (#1260)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -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" | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user