From 796d7e31e9518de8ac7179692d4580697e50b77c Mon Sep 17 00:00:00 2001 From: DitroniXDev Date: Wed, 17 Dec 2025 14:23:02 +0000 Subject: [PATCH] inital upload --- components/ATM90E36/__init__.py | 8 + components/ATM90E36/atm90e36.cpp | 516 +++++++++++++++++++ components/ATM90E36/atm90e36.h | 239 +++++++++ components/ATM90E36/atm90e36_reg.h | 244 +++++++++ components/ATM90E36/number/__init__.py | 130 +++++ components/ATM90E36/number/atm90e36_number.h | 16 + components/ATM90E36/sensor.py | 265 ++++++++++ components/ATM90E36/text_sensor/__init__.py | 33 ++ 8 files changed, 1451 insertions(+) create mode 100644 components/ATM90E36/__init__.py create mode 100644 components/ATM90E36/atm90e36.cpp create mode 100644 components/ATM90E36/atm90e36.h create mode 100644 components/ATM90E36/atm90e36_reg.h create mode 100644 components/ATM90E36/number/__init__.py create mode 100644 components/ATM90E36/number/atm90e36_number.h create mode 100644 components/ATM90E36/sensor.py create mode 100644 components/ATM90E36/text_sensor/__init__.py diff --git a/components/ATM90E36/__init__.py b/components/ATM90E36/__init__.py new file mode 100644 index 0000000..d71e917 --- /dev/null +++ b/components/ATM90E36/__init__.py @@ -0,0 +1,8 @@ +import esphome.codegen as cg + +CODEOWNERS = ["@DitroniXDev"] + +atm90e36_ns = cg.esphome_ns.namespace("atm90e36") +ATM90E36Component = atm90e36_ns.class_("ATM90E36Component", cg.Component) + +CONF_ATM90E36_ID = "atm90e36_id" diff --git a/components/ATM90E36/atm90e36.cpp b/components/ATM90E36/atm90e36.cpp new file mode 100644 index 0000000..537c8e7 --- /dev/null +++ b/components/ATM90E36/atm90e36.cpp @@ -0,0 +1,516 @@ +#include "atm90e36.h" +#include +#include +#include +#include "esphome/core/log.h" + +namespace esphome { +namespace atm90e36 { + +static const char *const TAG = "atm90e36"; +//Loop called by normal ESP32 Loop() +void ATM90E36Component::loop() { + if (this->get_publish_interval_flag_()) { + this->set_publish_interval_flag_(false); + for (uint8_t phase = 0; phase < 3; phase++) { + if (this->phase_[phase].voltage_sensor_ != nullptr) + this->phase_[phase].voltage_ = this->get_phase_voltage_(phase); + + if (this->phase_[phase].current_sensor_ != nullptr) + this->phase_[phase].current_ = this->get_phase_current_(phase); + + if (this->phase_[phase].power_sensor_ != nullptr) + this->phase_[phase].active_power_ = this->get_phase_active_power_(phase); + + if (this->phase_[phase].power_factor_sensor_ != nullptr) + this->phase_[phase].power_factor_ = this->get_phase_power_factor_(phase); + + if (this->phase_[phase].reactive_power_sensor_ != nullptr) + this->phase_[phase].reactive_power_ = this->get_phase_reactive_power_(phase); + + if (this->phase_[phase].apparent_power_sensor_ != nullptr) + this->phase_[phase].apparent_power_ = this->get_phase_apparent_power_(phase); + + if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) + this->phase_[phase].forward_active_energy_ = this->get_phase_forward_active_energy_(phase); + + if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) + this->phase_[phase].reverse_active_energy_ = this->get_phase_reverse_active_energy_(phase); + + if (this->phase_[phase].phase_angle_sensor_ != nullptr) + this->phase_[phase].phase_angle_ = this->get_phase_angle_(phase); + + if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) + this->phase_[phase].harmonic_active_power_ = this->get_phase_harmonic_active_power_(phase); + + if (this->phase_[phase].peak_current_sensor_ != nullptr) + this->phase_[phase].peak_current_ = this->get_phase_peak_current_(phase); + + // After the local store is collected we can publish them trusting they are within +-1 hardware sampling + if (this->phase_[phase].voltage_sensor_ != nullptr) + this->phase_[phase].voltage_sensor_->publish_state(this->get_local_phase_voltage_(phase)); + + if (this->phase_[phase].current_sensor_ != nullptr) + this->phase_[phase].current_sensor_->publish_state(this->get_local_phase_current_(phase)); + + if (this->phase_[phase].power_sensor_ != nullptr) + this->phase_[phase].power_sensor_->publish_state(this->get_local_phase_active_power_(phase)); + + if (this->phase_[phase].power_factor_sensor_ != nullptr) + this->phase_[phase].power_factor_sensor_->publish_state(this->get_local_phase_power_factor_(phase)); + + if (this->phase_[phase].reactive_power_sensor_ != nullptr) + this->phase_[phase].reactive_power_sensor_->publish_state(this->get_local_phase_reactive_power_(phase)); + + if (this->phase_[phase].apparent_power_sensor_ != nullptr) + this->phase_[phase].apparent_power_sensor_->publish_state(this->get_local_phase_apparent_power_(phase)); + + if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) { + this->phase_[phase].forward_active_energy_sensor_->publish_state( + this->get_local_phase_forward_active_energy_(phase)); + } + + if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) { + this->phase_[phase].reverse_active_energy_sensor_->publish_state( + this->get_local_phase_reverse_active_energy_(phase)); + } + + if (this->phase_[phase].phase_angle_sensor_ != nullptr) + this->phase_[phase].phase_angle_sensor_->publish_state(this->get_local_phase_angle_(phase)); + + if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) { + this->phase_[phase].harmonic_active_power_sensor_->publish_state( + this->get_local_phase_harmonic_active_power_(phase)); + } + + if (this->phase_[phase].peak_current_sensor_ != nullptr) + this->phase_[phase].peak_current_sensor_->publish_state(this->get_local_phase_peak_current_(phase)); + } + if (this->freq_sensor_ != nullptr) + this->freq_sensor_->publish_state(this->get_frequency_()); + + if (this->chip_temperature_sensor_ != nullptr) + this->chip_temperature_sensor_->publish_state(this->get_chip_temperature_()); + } +} + +void ATM90E36Component::update() { + this->set_publish_interval_flag_(true); + this->status_clear_warning(); + +#ifdef USE_TEXT_SENSOR + this->check_phase_status(); + this->check_freq_status(); +#endif +} + +//Setup called by ESP32 setup() +void ATM90E36Component::setup() { + this->spi_setup(); + this->cs_summary_ = this->cs_->dump_summary(); + const char *cs = this->cs_summary_.c_str(); + + uint16_t mmode0 = 0x87; // 3P4W 50Hz + uint16_t high_thresh = 0; + uint16_t low_thresh = 0; + + + if (line_freq_ == 60) { + mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz + // for freq threshold registers + high_thresh = 6300; // 63.00 Hz + low_thresh = 5700; // 57.00 Hz + } else { + high_thresh = 5300; // 53.00 Hz + low_thresh = 4700; // 47.00 Hz + } + + if (current_phases_ == 2) { + mmode0 |= 1 << 8; // sets 8th bit to 1, 3P3W + mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S) + } + + //Calulate MMMode1 taking current_gain gain_dpga and gain_voltage + pga_cal = pga_current_; + pga_cal |= pga_voltage_ << 8; + pga_cal |= dpga_gain_ << 14; + + this->write16_(ATM90E36_REGISTER_SOFTRESET, 0x789A, false); // Perform soft reset + delay(6); // Wait for the minimum 5ms + 1ms + this->write16_(ATM90E36_REGISTER_FUNCEN0, 0x0000); // Voltage sag + this->write16_(ATM90E36_REGISTER_FUNCEN1, 0x0000); // Voltage sag + this->write16_(ATM90E36_REGISTER_SAGTH, calculate_voltage_threshold(50.0, this->phase_[0].voltage_gain_, 1)); // Voltage sag + this->write16_(ATM90E36_REGISTER_CONFIGSTART, 0x5678); // Start Config of chip + this->write16_(ATM90E36_REGISTER_PLCONSTH, 0x0861); // PL Constant MSB (default) = 140625000 + this->write16_(ATM90E36_REGISTER_PLCONSTL, 0xC468); // PL Constant LSB (default) + this->write16_(ATM90E36_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program) mmode0 + this->write16_(ATM90E36_REGISTER_MMODE1, pga_cal); // PGA Gain Configuration for Current Channels 0x5555 + this->write16_(ATM90E36_REGISTER_PSTARTTH, 0x0000); // All Active Startup Power Threshold - 0.02A/0.00032 = 7500 + this->write16_(ATM90E36_REGISTER_QSTARTTH, 0x0000); // All Reactive Startup Power Threshold - 50% + this->write16_(ATM90E36_REGISTER_SSTARTTH, 0x0000); // All Reactive Startup Power Threshold - 50% + this->write16_(ATM90E36_REGISTER_PPHASETH, 0x0000); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750 + this->write16_(ATM90E36_REGISTER_QPHASETH, 0x0000); // Each phase Reactive Phase Threshold - 10% + this->write16_(ATM90E36_REGISTER_SPHASETH, 0x0000); // Apparent Phase Threshold + this->write16_(ATM90E36_REGISTER_CSO, 0x4741); // Checksum 0 + + this->write16_(ATM90E36_REGISTER_CALSTART, 0x5678); // Metering calibration startup + this->write16_(ATM90E36_REGISTER_PQGAINA, 0x0000); // CT1 line active power offset + this->write16_(ATM90E36_REGISTER_PHIA, 0x0000); // CT1 line reactive power offset + this->write16_(ATM90E36_REGISTER_PQGAINB, 0x0000); // CT2 line active power offset + this->write16_(ATM90E36_REGISTER_PHIB, 0x0000); // CT2 line reactive power offset + this->write16_(ATM90E36_REGISTER_PQGAINC, 0x0000); // CT3 line active power offset + this->write16_(ATM90E36_REGISTER_PHIC, 0x0000); // CT3 line reactive power offset + this->write16_(ATM90E36_REGISTER_POFFSETA, 0x0000); // Line calibration gain + this->write16_(ATM90E36_REGISTER_QOFFSETA, 0x0000); // Line calibration angle + this->write16_(ATM90E36_REGISTER_POFFSETB, 0x0000); // Line calibration gain + this->write16_(ATM90E36_REGISTER_QOFFSETB, 0x0000); // Line calibration angle + this->write16_(ATM90E36_REGISTER_POFFSETC, 0x0000); // Line calibration gain + this->write16_(ATM90E36_REGISTER_QOFFSETC, 0x0000); // Line calibration angle + this->write16_(ATM90E36_REGISTER_CS1, 0x0000); // Checksum 1 + + // **************** FUNDAMENTAL/HARMONIC & ENERGY CALIBRATION REGISTERS **************** + this->write16_(ATM90E36_REGISTER_HARMSTART, 0x5678); // Metering calibration startup + this->write16_(ATM90E36_REGISTER_POFFSETAF, 0x0000); // CT1 Fund. active power offset + this->write16_(ATM90E36_REGISTER_POFFSETBF, 0x0000); // CT2 Fund. active power offset + this->write16_(ATM90E36_REGISTER_POFFSETCF, 0x0000); // CT3 Fund. active power offset + this->write16_(ATM90E36_REGISTER_PGAINAF, 0x0000); // CT1 Fund. active power gain + this->write16_(ATM90E36_REGISTER_PGAINBF, 0x0000); // CT2 Fund. active power gain + this->write16_(ATM90E36_REGISTER_PGAINCF, 0x0000); // CT3 Fund. active power gain + this->write16_(ATM90E36_REGISTER_CS2, 0x0000); // Checksum 2 + + // **************** MEASUREMENT CALIBRATION REGISTERS **************** + this->write16_(ATM90E36_REGISTER_ADJSTART, 0x5678); // Measurement calibration + + this->write16_(ATM90E36_REGISTER_UGAINA, this->phase_[0].voltage_gain_); // CT1 SVoltage RMS gain + this->write16_(ATM90E36_REGISTER_IGAINA, this->phase_[0].ct_gain_); // CT1 line current gain. + this->write16_(ATM90E36_REGISTER_UOFFSETA, 0x0000); // CT1 Voltage offset + this->write16_(ATM90E36_REGISTER_IOFFSETA, 0x0000); // CT1 line current offset + + this->write16_(ATM90E36_REGISTER_UGAINB, this->phase_[1].voltage_gain_); // CT2 SVoltage RMS gain + this->write16_(ATM90E36_REGISTER_IGAINB, this->phase_[1].ct_gain_); // CT2 line current gain. + this->write16_(ATM90E36_REGISTER_UOFFSETB, 0x0000); // CT2 Voltage offset + this->write16_(ATM90E36_REGISTER_IOFFSETB, 0x0000); // CT2 line current offset + + this->write16_(ATM90E36_REGISTER_UGAINC, this->phase_[2].voltage_gain_); // CT3 SVoltage RMS gain + this->write16_(ATM90E36_REGISTER_IGAINC, this->phase_[2].ct_gain_); // CT3 line current gain. + this->write16_(ATM90E36_REGISTER_UOFFSETA, 0x0000); // CT3 Voltage offset + this->write16_(ATM90E36_REGISTER_IOFFSETA, 0x0000); // CT3 line current offset + // N + this->write16_(ATM90E36_REGISTER_IGAINN, 0xFD7F); // D line current gain + + this->write16_(ATM90E36_REGISTER_CS3, 0x02F6); // Checksum 3 +} + +void ATM90E36Component::dump_config() { + ESP_LOGCONFIG("", "ATM90E36:"); + LOG_PIN(" CS Pin: ", this->cs_); + if (this->is_failed()) { + ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Voltage A", this->phase_[PHASEA].voltage_sensor_); + LOG_SENSOR(" ", "Current A", this->phase_[PHASEA].current_sensor_); + LOG_SENSOR(" ", "Power A", this->phase_[PHASEA].power_sensor_); + LOG_SENSOR(" ", "Reactive Power A", this->phase_[PHASEA].reactive_power_sensor_); + LOG_SENSOR(" ", "Apparent Power A", this->phase_[PHASEA].apparent_power_sensor_); + LOG_SENSOR(" ", "PF A", this->phase_[PHASEA].power_factor_sensor_); + LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[PHASEA].forward_active_energy_sensor_); + LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[PHASEA].reverse_active_energy_sensor_); + LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEA].harmonic_active_power_sensor_); + LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEA].phase_angle_sensor_); + LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEA].peak_current_sensor_); + LOG_SENSOR(" ", "Voltage B", this->phase_[PHASEB].voltage_sensor_); + LOG_SENSOR(" ", "Current B", this->phase_[PHASEB].current_sensor_); + LOG_SENSOR(" ", "Power B", this->phase_[PHASEB].power_sensor_); + LOG_SENSOR(" ", "Reactive Power B", this->phase_[PHASEB].reactive_power_sensor_); + LOG_SENSOR(" ", "Apparent Power B", this->phase_[PHASEB].apparent_power_sensor_); + LOG_SENSOR(" ", "PF B", this->phase_[PHASEB].power_factor_sensor_); + LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[PHASEB].forward_active_energy_sensor_); + LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[PHASEB].reverse_active_energy_sensor_); + LOG_SENSOR(" ", "Harmonic Power B", this->phase_[PHASEB].harmonic_active_power_sensor_); + LOG_SENSOR(" ", "Phase Angle B", this->phase_[PHASEB].phase_angle_sensor_); + LOG_SENSOR(" ", "Peak Current B", this->phase_[PHASEB].peak_current_sensor_); + LOG_SENSOR(" ", "Voltage C", this->phase_[PHASEC].voltage_sensor_); + LOG_SENSOR(" ", "Current C", this->phase_[PHASEC].current_sensor_); + LOG_SENSOR(" ", "Power C", this->phase_[PHASEC].power_sensor_); + LOG_SENSOR(" ", "Reactive Power C", this->phase_[PHASEC].reactive_power_sensor_); + LOG_SENSOR(" ", "Apparent Power C", this->phase_[PHASEC].apparent_power_sensor_); + LOG_SENSOR(" ", "PF C", this->phase_[PHASEC].power_factor_sensor_); + LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[PHASEC].forward_active_energy_sensor_); + LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[PHASEC].reverse_active_energy_sensor_); + LOG_SENSOR(" ", "Harmonic Power C", this->phase_[PHASEC].harmonic_active_power_sensor_); + LOG_SENSOR(" ", "Phase Angle C", this->phase_[PHASEC].phase_angle_sensor_); + LOG_SENSOR(" ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_); + LOG_SENSOR(" ", "Frequency", this->freq_sensor_); + LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_); +} + +float ATM90E36Component::get_setup_priority() const { return setup_priority::IO; } + +// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H) +// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period +// Default is 143FH (20ms, 63ms) +uint16_t ATM90E36Component::read16_(uint16_t a_register) { + this->enable(); + delay_microseconds_safe(10); // min delay between CS low and first SCK is 200ns - 1us is plenty + uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03); + uint8_t addrl = (a_register & 0xFF); + uint8_t data[4] = {addrh, addrl, 0x00, 0x00}; + this->transfer_array(data, 4); + uint16_t output = encode_uint16(data[2], data[3]); + ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output); + delay_microseconds_safe(10); // allow the last clock to propagate before releasing CS + this->disable(); + delay_microseconds_safe(1); // meet minimum CS high time before next transaction + return output; +} + +void ATM90E36Component::write16_(uint16_t a_register, uint16_t val, bool validate) { + ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val); + uint8_t addrh = ((a_register >> 8) & 0x03); + uint8_t addrl = (a_register & 0xFF); + uint8_t data[4] = {addrh, addrl, uint8_t((val >> 8) & 0xFF), uint8_t(val & 0xFF)}; + this->enable(); + delay_microseconds_safe(1); // ensure CS setup time + this->write_array(data, 4); + delay_microseconds_safe(1); // allow clock to settle before raising CS + this->disable(); + delay_microseconds_safe(1); // ensure minimum CS high time + if (validate) + this->validate_spi_read_(val, "write16()"); +} + +float ATM90E36Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; } + +float ATM90E36Component::get_local_phase_current_(uint8_t phase) { return this->phase_[phase].current_; } + +float ATM90E36Component::get_local_phase_active_power_(uint8_t phase) { return this->phase_[phase].active_power_; } + +float ATM90E36Component::get_local_phase_reactive_power_(uint8_t phase) { return this->phase_[phase].reactive_power_; } + +float ATM90E36Component::get_local_phase_apparent_power_(uint8_t phase) { return this->phase_[phase].apparent_power_; } + +float ATM90E36Component::get_local_phase_power_factor_(uint8_t phase) { return this->phase_[phase].power_factor_; } + +float ATM90E36Component::get_local_phase_forward_active_energy_(uint8_t phase) { + return this->phase_[phase].forward_active_energy_; +} + +float ATM90E36Component::get_local_phase_reverse_active_energy_(uint8_t phase) { + return this->phase_[phase].reverse_active_energy_; +} + +float ATM90E36Component::get_local_phase_angle_(uint8_t phase) { return this->phase_[phase].phase_angle_; } + +float ATM90E36Component::get_local_phase_harmonic_active_power_(uint8_t phase) { + return this->phase_[phase].harmonic_active_power_; +} + +float ATM90E36Component::get_local_phase_peak_current_(uint8_t phase) { return this->phase_[phase].peak_current_; } + +float ATM90E36Component::get_phase_voltage_(uint8_t phase) { + uint16_t voltage = this->read16_(ATM90E36_REGISTER_URMS + phase); + this->validate_spi_read_(voltage, "get_phase_voltage()"); + + if (voltage < 50) { + voltage = 0; + } + + return (float) voltage / 100; +} + +float ATM90E36Component::get_phase_voltage_avg_(uint8_t phase) { + const uint8_t reads = 10; + uint32_t accumulation = 0; + uint16_t voltage = 0; + for (uint8_t i = 0; i < reads; i++) { + voltage = this->read16_(ATM90E36_REGISTER_URMS + phase); + this->validate_spi_read_(voltage, "get_phase_voltage_avg_()"); + accumulation += voltage; + } + voltage = accumulation / reads; + this->phase_[phase].voltage_ = (float) voltage / 100; + return this->phase_[phase].voltage_; +} + +float ATM90E36Component::get_phase_current_avg_(uint8_t phase) { + const uint8_t reads = 10; + uint32_t accumulation = 0; + uint16_t current = 0; + for (uint8_t i = 0; i < reads; i++) { + current = this->read16_(ATM90E36_REGISTER_IRMS + phase); + this->validate_spi_read_(current, "get_phase_current_avg_()"); + accumulation += current; + } + current = accumulation / reads; + this->phase_[phase].current_ = (float) current / 1000; + return this->phase_[phase].current_; +} + +float ATM90E36Component::get_phase_current_(uint8_t phase) { + const uint16_t current = this->read16_(ATM90E36_REGISTER_IRMS + phase); + this->validate_spi_read_(current, "get_phase_current_()"); + return (float) current / 1000; +} + +float ATM90E36Component::get_phase_active_power_(uint8_t phase) { + float val = (float)this->read16_(ATM90E36_REGISTER_PMEAN + phase); + + if (val >= 0xFFFF) { + val = 0; + } + + return val; +} + +float ATM90E36Component::get_phase_reactive_power_(uint8_t phase) { + int16_t val = (int16_t)this->read16_(ATM90E36_REGISTER_QMEAN + phase); + return (float)val / 1000; +} + +float ATM90E36Component::get_phase_apparent_power_(uint8_t phase) { + const int val = this->read16_(ATM90E36_REGISTER_SMEANT + phase); + return val; +} + +float ATM90E36Component::get_phase_power_factor_(uint8_t phase) { + uint16_t powerfactor = this->read16_(ATM90E36_REGISTER_PFMEAN + phase); // unsigned to compare to lastspidata + this->validate_spi_read_(powerfactor, "get_phase_power_factor_()"); + return (float) ((int16_t) powerfactor) / 1000; // make it signed again +} + +float ATM90E36Component::get_phase_forward_active_energy_(uint8_t phase) { + const uint16_t val = this->read16_(ATM90E36_REGISTER_APENERGY + phase); + if ((UINT32_MAX - this->phase_[phase].cumulative_forward_active_energy_) > val) { + this->phase_[phase].cumulative_forward_active_energy_ += val; + } else { + this->phase_[phase].cumulative_forward_active_energy_ = val; + } + // 0.01CF resolution = 0.003125 Wh per count + return ((float) this->phase_[phase].cumulative_forward_active_energy_ * (10.0f / 3200.0f)); +} + +float ATM90E36Component::get_phase_reverse_active_energy_(uint8_t phase) { + const uint16_t val = this->read16_(ATM90E36_REGISTER_ANENERGY + phase); + if (UINT32_MAX - this->phase_[phase].cumulative_reverse_active_energy_ > val) { + this->phase_[phase].cumulative_reverse_active_energy_ += val; + } else { + this->phase_[phase].cumulative_reverse_active_energy_ = val; + } + // 0.01CF resolution = 0.003125 Wh per count + return ((float) this->phase_[phase].cumulative_reverse_active_energy_ * (10.0f / 3200.0f)); +} + +float ATM90E36Component::get_phase_harmonic_active_power_(uint8_t phase) { + int16_t val = (int16_t)this->read16_(ATM90E36_REGISTER_PMEANH + phase); + return (float)val; +} + +float ATM90E36Component::get_phase_angle_(uint8_t phase) { + int16_t val = (int16_t)this->read16_(ATM90E36_REGISTER_PANGLE + phase) / 10.0; + return (val > 180) ? (float) (val - 360.0f) : (float) val; +} + +float ATM90E36Component::get_phase_peak_current_(uint8_t phase) { + int16_t val = (float) this->read16_(ATM90E36_REGISTER_IPEAK + phase); + if (!this->peak_current_signed_) + val = std::abs(val); + // phase register * phase current gain value / 1000 * 2^13 + return (val * this->phase_[phase].ct_gain_ / 8192000.0); +} + +float ATM90E36Component::get_frequency_() { + const uint16_t freq = this->read16_(ATM90E36_REGISTER_FREQ); + return (float) freq / 100; +} + +float ATM90E36Component::get_chip_temperature_() { + const uint16_t ctemp = this->read16_(ATM90E36_REGISTER_TEMP); + return (float) ctemp; +} + + +#ifdef USE_TEXT_SENSOR +void ATM90E36Component::check_phase_status() { + SysStatus0 state0; + SysStatus1 state1; + + state0.data = this->read16_(ATM90E36_REGISTER_SYSSTATUS0); + state1.data = this->read16_(ATM90E36_REGISTER_SYSSTATUS1); + + std::string status; + + if (state0.bits.SagWarn) { + status += "Voltage Sag; "; + } + if (state1.bits.THDUOv) { + status += "Over Voltage; "; + } + + auto *sensor = this->phase_status_text_sensor_; + if (sensor == nullptr) + return; + + if (!status.empty()) { + status.pop_back(); // remove space + status.pop_back(); // remove semicolon + ESP_LOGW(TAG, "%s: %s", sensor->get_name().c_str(), status.c_str()); + sensor->publish_state(status); + } else { + sensor->publish_state("Okay"); + } +} + +void ATM90E36Component::check_freq_status() { + SysStatus0 state0; + + state0.data = this->read16_(ATM90E36_REGISTER_SYSSTATUS0); + + std::string freq_status; + + if (state0.bits.SagWarn) { + freq_status = "LOW"; + } else { + freq_status = "Normal"; + } + if (this->freq_status_text_sensor_ != nullptr) { + if (freq_status == "Normal") { + ESP_LOGD(TAG, "Frequency status: %s", freq_status.c_str()); + } else { + ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str()); + } + this->freq_status_text_sensor_->publish_state(freq_status); + } + +} +#endif + +uint16_t ATM90E36Component::calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier) { + // this assumes that 60Hz electrical systems use 120V mains, + // which is usually, but not always the case + float nominal_voltage = (line_freq == 60) ? 120.0f : 220.0f; + float target_voltage = nominal_voltage * multiplier; + + float peak_01v = target_voltage * 100.0f * std::numbers::sqrt2_v; // convert RMS → peak, scale to 0.01V + float divider = (2.0f * ugain) / 32768.0f; + + float threshold = peak_01v / divider; + + return static_cast(threshold); +} + +bool ATM90E36Component::validate_spi_read_(uint16_t expected, const char *context) { + uint16_t last = this->read16_(ATM90E36_REGISTER_LASTSPIDATA); + if (last != expected) { + if (context != nullptr) { + ESP_LOGW(TAG, "[%s] SPI read mismatch: expected 0x%04X, got 0x%04X", context, expected, last); + } else { + ESP_LOGW(TAG, "SPI read mismatch: expected 0x%04X, got 0x%04X", expected, last); + } + return false; + } + return true; +} + +} // namespace atm90e36 +} // namespace esphome diff --git a/components/ATM90E36/atm90e36.h b/components/ATM90E36/atm90e36.h new file mode 100644 index 0000000..c77f046 --- /dev/null +++ b/components/ATM90E36/atm90e36.h @@ -0,0 +1,239 @@ +#pragma once + +#include +#include "atm90e36_reg.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/spi/spi.h" +#include "esphome/core/application.h" +#include "esphome/core/component.h" +#include "esphome/core/helpers.h" +#include "esphome/core/preferences.h" + +namespace esphome { +namespace atm90e36 { + +class ATM90E36Component : public PollingComponent, + public spi::SPIDevice { + public: + static const uint8_t PHASEA = 0; + static const uint8_t PHASEB = 1; + static const uint8_t PHASEC = 2; + const char *phase_labels[3] = {"A", "B", "C"}; + void loop() override; + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + void set_voltage_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].voltage_sensor_ = obj; } + void set_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].current_sensor_ = obj; } + void set_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_sensor_ = obj; } + void set_reactive_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].reactive_power_sensor_ = obj; } + void set_apparent_power_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].apparent_power_sensor_ = obj; } + void set_forward_active_energy_sensor(int phase, sensor::Sensor *obj) { + this->phase_[phase].forward_active_energy_sensor_ = obj; + } + void set_reverse_active_energy_sensor(int phase, sensor::Sensor *obj) { + this->phase_[phase].reverse_active_energy_sensor_ = obj; + } + void set_power_factor_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].power_factor_sensor_ = obj; } + void set_phase_angle_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].phase_angle_sensor_ = obj; } + void set_harmonic_active_power_sensor(int phase, sensor::Sensor *obj) { + this->phase_[phase].harmonic_active_power_sensor_ = obj; + } + void set_peak_current_sensor(int phase, sensor::Sensor *obj) { this->phase_[phase].peak_current_sensor_ = obj; } + void set_volt_gain(int phase, uint16_t gain) { + this->phase_[phase].voltage_gain_ = gain; + } + void set_ct_gain(int phase, uint16_t gain) { + this->phase_[phase].ct_gain_ = gain; + } + void set_freq_sensor(sensor::Sensor *freq_sensor) { freq_sensor_ = freq_sensor; } + void set_peak_current_signed(bool flag) { peak_current_signed_ = flag; } + void set_chip_temperature_sensor(sensor::Sensor *chip_temperature_sensor) { + chip_temperature_sensor_ = chip_temperature_sensor; + } + void set_line_freq(int freq) { line_freq_ = freq; } + void set_current_phases(int phases) { current_phases_ = phases; } + void set_pga_current(uint16_t gain) { pga_current_ = gain; } + void set_pga_voltage(uint16_t gain) { pga_voltage_ = gain; } + void set_dpga_gain(uint16_t gain) { dpga_gain_ = gain; } +#ifdef USE_NUMBER + void set_reference_voltage(uint8_t phase, number::Number *ref_voltage) { ref_voltages_[phase] = ref_voltage; } + void set_reference_current(uint8_t phase, number::Number *ref_current) { ref_currents_[phase] = ref_current; } +#endif + float get_reference_voltage(uint8_t phase) { +#ifdef USE_NUMBER + return (phase >= 0 && phase < 3 && ref_voltages_[phase]) ? ref_voltages_[phase]->state : 120.0; // Default voltage +#else + return 120.0; // Default voltage +#endif + } + float get_reference_current(uint8_t phase) { +#ifdef USE_NUMBER + return (phase >= 0 && phase < 3 && ref_currents_[phase]) ? ref_currents_[phase]->state : 5.0f; // Default current +#else + return 5.0f; // Default current +#endif + } + bool using_saved_calibrations_ = false; // Track if stored calibrations are being used +#ifdef USE_TEXT_SENSOR + void check_phase_status(); + void check_freq_status(); + void check_over_current(); + void set_phase_status_text_sensor(text_sensor::TextSensor *sensor) { + this->phase_status_text_sensor_ = sensor; + } + void set_freq_status_text_sensor(text_sensor::TextSensor *sensor) { this->freq_status_text_sensor_ = sensor; } +#endif + uint16_t calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier); + int32_t last_periodic_millis = millis(); + + protected: +#ifdef USE_NUMBER + number::Number *ref_voltages_[3]{nullptr, nullptr, nullptr}; + number::Number *ref_currents_[3]{nullptr, nullptr, nullptr}; +#endif + uint16_t read16_(uint16_t a_register); + void write16_(uint16_t a_register, uint16_t val, bool validate = true); + float get_local_phase_voltage_(uint8_t phase); + float get_local_phase_current_(uint8_t phase); + float get_local_phase_active_power_(uint8_t phase); + float get_local_phase_reactive_power_(uint8_t phase); + float get_local_phase_apparent_power_(uint8_t phase); + float get_local_phase_power_factor_(uint8_t phase); + float get_local_phase_forward_active_energy_(uint8_t phase); + float get_local_phase_reverse_active_energy_(uint8_t phase); + float get_local_phase_angle_(uint8_t phase); + float get_local_phase_harmonic_active_power_(uint8_t phase); + float get_local_phase_peak_current_(uint8_t phase); + float get_phase_voltage_(uint8_t phase); + float get_phase_voltage_avg_(uint8_t phase); + float get_phase_current_(uint8_t phase); + float get_phase_current_avg_(uint8_t phase); + float get_phase_active_power_(uint8_t phase); + float get_phase_reactive_power_(uint8_t phase); + float get_phase_apparent_power_(uint8_t phase); + float get_phase_power_factor_(uint8_t phase); + float get_phase_forward_active_energy_(uint8_t phase); + float get_phase_reverse_active_energy_(uint8_t phase); + float get_phase_angle_(uint8_t phase); + float get_phase_harmonic_active_power_(uint8_t phase); + float get_phase_peak_current_(uint8_t phase); + float get_frequency_(); + float get_chip_temperature_(); + bool get_publish_interval_flag_() { return publish_interval_flag_; }; + void set_publish_interval_flag_(bool flag) { publish_interval_flag_ = flag; }; + void restore_offset_calibrations_(); + void restore_power_offset_calibrations_(); + void restore_gain_calibrations_(); + void save_offset_calibration_to_memory_(); + void save_gain_calibration_to_memory_(); + void save_power_offset_calibration_to_memory_(); + void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset); + void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset); + void write_gains_to_registers_(); + bool verify_gain_writes_(); + bool validate_spi_read_(uint16_t expected, const char *context = nullptr); + void log_calibration_status_(); + + struct ATM90E32Phase { + uint16_t voltage_gain_{0}; + uint16_t ct_gain_{0}; + int16_t voltage_offset_{0}; + int16_t current_offset_{0}; + int16_t active_power_offset_{0}; + int16_t reactive_power_offset_{0}; + float voltage_{0}; + float current_{0}; + float active_power_{0}; + float reactive_power_{0}; + float apparent_power_{0}; + float power_factor_{0}; + float forward_active_energy_{0}; + float reverse_active_energy_{0}; + float phase_angle_{0}; + float harmonic_active_power_{0}; + float peak_current_{0}; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; + sensor::Sensor *reactive_power_sensor_{nullptr}; + sensor::Sensor *apparent_power_sensor_{nullptr}; + sensor::Sensor *power_factor_sensor_{nullptr}; + sensor::Sensor *forward_active_energy_sensor_{nullptr}; + sensor::Sensor *reverse_active_energy_sensor_{nullptr}; + sensor::Sensor *phase_angle_sensor_{nullptr}; + sensor::Sensor *harmonic_active_power_sensor_{nullptr}; + sensor::Sensor *peak_current_sensor_{nullptr}; + uint32_t cumulative_forward_active_energy_{0}; + uint32_t cumulative_reverse_active_energy_{0}; + } phase_[3]; + + union SysStatus0 { + uint16_t data; + struct { + bool b0 : 1; + bool b1 : 1; + bool PhaseLoseWn : 1; + bool SagWarn : 1; + bool b4 : 1; + bool b5 : 1; + bool IRevWn : 1; + bool URevWn : 1; + bool CS3Err : 1; + bool b9 : 1; + bool CS2Err : 1; + bool b11 : 1; + bool CS1Err : 1; + bool b13 : 1; + bool CS0Err : 1; + bool b15 : 1; + } bits; + }; + + union SysStatus1 { + uint16_t data; + struct { + bool RevPchgC : 1; + bool RevPchgB : 1; + bool RevPchgA : 1; + bool RevPchgT : 1; + bool RevQchgC : 1; + bool RevQchgB : 1; + bool RevQchgA : 1; + bool RevQchgT : 1; + bool b8 : 1; + bool DFTDone : 1; + bool THDIOv : 1; + bool THDUOv : 1; + bool b12 : 1; + bool b13 : 1; + bool INOv0 : 1; + bool INOv1 : 1; + } bits; + }; + + ESPPreferenceObject offset_pref_; + ESPPreferenceObject power_offset_pref_; + ESPPreferenceObject gain_calibration_pref_; + std::string cs_summary_; + + sensor::Sensor *freq_sensor_{nullptr}; +#ifdef USE_TEXT_SENSOR + text_sensor::TextSensor *phase_status_text_sensor_{nullptr}; + text_sensor::TextSensor *freq_status_text_sensor_{nullptr}; +#endif + sensor::Sensor *chip_temperature_sensor_{nullptr}; + uint16_t pga_current_{0x15}; + uint16_t pga_voltage_{0x15}; + uint16_t dpga_gain_{0x2}; + int line_freq_{60}; + int current_phases_{3}; + bool publish_interval_flag_{false}; + bool peak_current_signed_{false}; + uint16_t pga_cal{0x0}; +}; + +} // namespace atm90e32 +} // namespace esphome diff --git a/components/ATM90E36/atm90e36_reg.h b/components/ATM90E36/atm90e36_reg.h new file mode 100644 index 0000000..7626074 --- /dev/null +++ b/components/ATM90E36/atm90e36_reg.h @@ -0,0 +1,244 @@ +#pragma once + +#include + +namespace esphome { +namespace atm90e36 { + +/* STATUS REGISTERS */ +static const uint16_t ATM90E36_REGISTER_SOFTRESET = 0x00; // Software Reset +static const uint16_t ATM90E36_REGISTER_SYSSTATUS0 = 0x01; // System Status 0 +static const uint16_t ATM90E36_REGISTER_SYSSTATUS1 = 0x02; // System Status 1 +static const uint16_t ATM90E36_REGISTER_FUNCEN0 = 0x03; // Function Enable 0 +static const uint16_t ATM90E36_REGISTER_FUNCEN1 = 0x04; // Function Enable 1 +static const uint16_t ATM90E36_REGISTER_ZXCONFIG = 0x07; // Zero-Crossing Config +static const uint16_t ATM90E36_REGISTER_SAGTH = 0x08; // Voltage Sag Th +static const uint16_t ATM90E36_REGISTER_PHASELOSSTH = 0x09; // Voltage Phase Losing Th +static const uint16_t ATM90E36_REGISTER_INWARNTH0 = 0x0A; // Threshold for sampled (from ADC) N line rms current +static const uint16_t ATM90E36_REGISTER_INWARNTH1 = 0x0B; // Threshold for sampled (from ADC) N line rms current +static const uint16_t ATM90E36_REGISTER_THDNUTH = 0x0C; // Voltage THD Warning Threshold +static const uint16_t ATM90E36_REGISTER_THDNITH = 0x0D; // Current THD Warning Threshold +static const uint16_t ATM90E36_REGISTER_DMACTRL = 0x0E; // DMA Mode Interface Control +static const uint16_t ATM90E36_REGISTER_LASTSPIDATA = 0x0F; // Last Read/ Write SPI Value + +/* LOW POWER MODE REGISTERS - NOT USED */ +static const uint16_t ATM90E36_REGISTER_DETECTCTRL = 0x10; +static const uint16_t ATM90E36_REGISTER_DETECTTH1 = 0x11; +static const uint16_t ATM90E36_REGISTER_DETECTTH2 = 0x12; +static const uint16_t ATM90E36_REGISTER_DETECTTH3 = 0x13; +static const uint16_t ATM90E36_REGISTER_PMOFFSETA = 0x14; +static const uint16_t ATM90E36_REGISTER_PMOFFSETB = 0x15; +static const uint16_t ATM90E36_REGISTER_PMOFFSETC = 0x16; +static const uint16_t ATM90E36_REGISTER_PMPGA = 0x17; +static const uint16_t ATM90E36_REGISTER_PMIRMSA = 0x18; +static const uint16_t ATM90E36_REGISTER_PMIRMSB = 0x19; +static const uint16_t ATM90E36_REGISTER_PMIRMSC = 0x1A; +static const uint16_t ATM90E36_REGISTER_PMCONFIG = 0x10B; +static const uint16_t ATM90E36_REGISTER_PMAVGSAMPLES = 0x1C; +static const uint16_t ATM90E36_REGISTER_PMIRMSLSB = 0x1D; + +/* CONFIGURATION REGISTERS */ +static const uint16_t ATM90E36_REGISTER_CONFIGSTART = 0x30; // Calibration Start Command +static const uint16_t ATM90E36_REGISTER_PLCONSTH = 0x31; // High Word of PL_Constant +static const uint16_t ATM90E36_REGISTER_PLCONSTL = 0x32; // Low Word of PL_Constant +static const uint16_t ATM90E36_REGISTER_MMODE0 = 0x33; // Metering Mode Config +static const uint16_t ATM90E36_REGISTER_MMODE1 = 0x34; // PGA Gain Configuration for Current Channels +static const uint16_t ATM90E36_REGISTER_PSTARTTH = 0x35; // Startup Power Th (P) +static const uint16_t ATM90E36_REGISTER_QSTARTTH = 0x36; // Startup Power Th (Q) +static const uint16_t ATM90E36_REGISTER_SSTARTTH = 0x37; // Startup Power Th (S) +static const uint16_t ATM90E36_REGISTER_PPHASETH = 0x38; // Startup Power Accum Th (P) +static const uint16_t ATM90E36_REGISTER_QPHASETH = 0x39; // Startup Power Accum Th (Q) +static const uint16_t ATM90E36_REGISTER_SPHASETH = 0x3A; // Startup Power Accum Th (S) +static const uint16_t ATM90E36_REGISTER_CSO = 0x3B; // Checksum 0 + +/* CALIBRATION REGISTERS */ +static const uint16_t ATM90E36_REGISTER_CALSTART = 0X40; // Calibration Start Command +static const uint16_t ATM90E36_REGISTER_POFFSETA = 0x41; // A Line Power Offset (P) +static const uint16_t ATM90E36_REGISTER_QOFFSETA = 0x42; // A Line Power Offset (Q) +static const uint16_t ATM90E36_REGISTER_POFFSETB = 0x43; // B Line Power Offset (P) +static const uint16_t ATM90E36_REGISTER_QOFFSETB = 0x44; // B Line Power Offset (Q) +static const uint16_t ATM90E36_REGISTER_POFFSETC = 0x45; // C Line Power Offset (P) +static const uint16_t ATM90E36_REGISTER_QOFFSETC = 0x46; // C Line Power Offset (Q) +static const uint16_t ATM90E36_REGISTER_PQGAINA = 0x47; // A Line Calibration Gain +static const uint16_t ATM90E36_REGISTER_PHIA = 0x48; // A Line Calibration Angle +static const uint16_t ATM90E36_REGISTER_PQGAINB = 0x49; // B Line Calibration Gain +static const uint16_t ATM90E36_REGISTER_PHIB = 0x4A; // B Line Calibration Angle +static const uint16_t ATM90E36_REGISTER_PQGAINC = 0x4B; // C Line Calibration Gain +static const uint16_t ATM90E36_REGISTER_PHIC = 0x4C; // C Line Calibration Angle +static const uint16_t ATM90E36_REGISTER_CS1 = 0X4D; // Checksum 1 + +/* FUNDAMENTAL/HARMONIC ENERGY CALIBRATION REGISTERS */ +static const uint16_t ATM90E36_REGISTER_HARMSTART = 0X50; // Harmonic Calibration Startup Command +static const uint16_t ATM90E36_REGISTER_POFFSETAF = 0x51; // A Fund Power Offset (P) +static const uint16_t ATM90E36_REGISTER_POFFSETBF = 0x52; // B Fund Power Offset (P) +static const uint16_t ATM90E36_REGISTER_POFFSETCF = 0x53; // C Fund Power Offset (P) +static const uint16_t ATM90E36_REGISTER_PGAINAF = 0x54; // A Fund Power Gain (P) +static const uint16_t ATM90E36_REGISTER_PGAINBF = 0x55; // B Fund Power Gain (P) +static const uint16_t ATM90E36_REGISTER_PGAINCF = 0x56; // C Fund Power Gain (P) +static const uint16_t ATM90E36_REGISTER_CS2 = 0X57; // Checksum 2 + +/* MEASUREMENT CALIBRATION REGISTERS */ +static const uint16_t ATM90E36_REGISTER_ADJSTART = 0X60; // Measurement Calibration Startup Command +static const uint16_t ATM90E36_REGISTER_UGAINA = 0x61; // A Voltage RMS Gain +static const uint16_t ATM90E36_REGISTER_IGAINA = 0x62; // A Current RMS Gain +static const uint16_t ATM90E36_REGISTER_UOFFSETA = 0x63; // A Voltage Offset +static const uint16_t ATM90E36_REGISTER_IOFFSETA = 0x64; // A Current Offset +static const uint16_t ATM90E36_REGISTER_UGAINB = 0x65; // B Voltage RMS Gain +static const uint16_t ATM90E36_REGISTER_IGAINB = 0x66; // B Current RMS Gain +static const uint16_t ATM90E36_REGISTER_UOFFSETB = 0x67; // B Voltage Offset +static const uint16_t ATM90E36_REGISTER_IOFFSETB = 0x68; // B Current Offset +static const uint16_t ATM90E36_REGISTER_UGAINC = 0x69; // C Voltage RMS Gain +static const uint16_t ATM90E36_REGISTER_IGAINC = 0x6A; // C Current RMS Gain +static const uint16_t ATM90E36_REGISTER_UOFFSETC = 0x6B; // C Voltage Offset +static const uint16_t ATM90E36_REGISTER_IOFFSETC = 0x6C; // C Current Offset +static const uint16_t ATM90E36_REGISTER_IGAINN = 0x6D; // N Current Gain +static const uint16_t ATM90E36_REGISTER_IOFFSETN = 0x6E; // N Current Offset +static const uint16_t ATM90E36_REGISTER_CS3 = 0X6F; // Checksum 3 + +/* ENERGY REGISTERS */ +static const uint16_t ATM90E36_REGISTER_APENERGYT = 0x80; // Total Forward Active +static const uint16_t ATM90E36_REGISTER_APENERGY = 0x81; // Forward Active Reg Base +static const uint16_t ATM90E36_REGISTER_APENERGYA = 0x81; // A Forward Active +static const uint16_t ATM90E36_REGISTER_APENERGYB = 0x82; // B Forward Active +static const uint16_t ATM90E36_REGISTER_APENERGYC = 0x83; // C Forward Active +static const uint16_t ATM90E36_REGISTER_ANENERGYT = 0x84; // Total Reverse Active +static const uint16_t ATM90E36_REGISTER_ANENERGY = 0x85; // Reverse Active Reg Base +static const uint16_t ATM90E36_REGISTER_ANENERGYA = 0x85; // A Reverse Active +static const uint16_t ATM90E36_REGISTER_ANENERGYB = 0x86; // B Reverse Active +static const uint16_t ATM90E36_REGISTER_ANENERGYC = 0x87; // C Reverse Active +static const uint16_t ATM90E36_REGISTER_RPENERGYT = 0x88; // Total Forward Reactive +static const uint16_t ATM90E36_REGISTER_RPENERGYA = 0x89; // A Forward Reactive +static const uint16_t ATM90E36_REGISTER_RPENERGYB = 0x8A; // B Forward Reactive +static const uint16_t ATM90E36_REGISTER_RPENERGYC = 0x8B; // C Forward Reactive +static const uint16_t ATM90E36_REGISTER_RNENERGYT = 0x8C; // Total Reverse Reactive +static const uint16_t ATM90E36_REGISTER_RNENERGYA = 0x8D; // A Reverse Reactive +static const uint16_t ATM90E36_REGISTER_RNENERGYB = 0x8E; // B Reverse Reactive +static const uint16_t ATM90E36_REGISTER_RNENERGYC = 0x8F; // C Reverse Reactive + +static const uint16_t ATM90E36_REGISTER_SAENERGYT = 0x90; // Total Apparent Energy +static const uint16_t ATM90E36_REGISTER_SENERGYA = 0x91; // A Apparent Energy +static const uint16_t ATM90E36_REGISTER_SENERGYB = 0x92; // B Apparent Energy +static const uint16_t ATM90E36_REGISTER_SENERGYC = 0x93; // C Apparent Energy +static const uint16_t ATM90E36_REGISTER_SVENERGYT = 0X94; // (Vector Sum) Total Apparent Energy + +static const uint16_t ATM90E36_REGISTER_ENSTATUS0 = 0X95; // Metering Status 0 +static const uint16_t ATM90E36_REGISTER_ENSTATUS1 = 0X96; // Metering Status 1 +///////////////// 0x97 // Reserved Register +static const uint16_t ATM90E36_REGISTER_SVMEANT = 0X98; // (Vector Sum) Total Apparent Power +static const uint16_t ATM90E36_REGISTER_SVMEANTLSB = 0X99; // lSB of (Vector Sum) Total Apparent Power + +/* FUNDAMENTAL / HARMONIC ENERGY REGISTERS */ +static const uint16_t ATM90E36_REGISTER_APENERGYTF = 0xA0; // Total Forward Fund. Energy +static const uint16_t ATM90E36_REGISTER_APENERGYAF = 0xA1; // A Forward Fund. Energy +static const uint16_t ATM90E36_REGISTER_APENERGYBF = 0xA2; // B Forward Fund. Energy +static const uint16_t ATM90E36_REGISTER_APENERGYCF = 0xA3; // C Forward Fund. Energy +static const uint16_t ATM90E36_REGISTER_ANENERGYTF = 0xA4; // Total Reverse Fund Energy +static const uint16_t ATM90E36_REGISTER_ANENERGYAF = 0xA5; // A Reverse Fund. Energy +static const uint16_t ATM90E36_REGISTER_ANENERGYBF = 0xA6; // B Reverse Fund. Energy +static const uint16_t ATM90E36_REGISTER_ANENERGYCF = 0xA7; // C Reverse Fund. Energy +static const uint16_t ATM90E36_REGISTER_APENERGYTH = 0xA8; // Total Forward Harm. Energy +static const uint16_t ATM90E36_REGISTER_APENERGYAH = 0xA9; // A Forward Harm. Energy +static const uint16_t ATM90E36_REGISTER_APENERGYBH = 0xAA; // B Forward Harm. Energy +static const uint16_t ATM90E36_REGISTER_APENERGYCH = 0xAB; // C Forward Harm. Energy +static const uint16_t ATM90E36_REGISTER_ANENERGYTH = 0xAC; // Total Reverse Harm. Energy +static const uint16_t ATM90E36_REGISTER_ANENERGYAH = 0xAD; // A Reverse Harm. Energy +static const uint16_t ATM90E36_REGISTER_ANENERGYBH = 0xAE; // B Reverse Harm. Energy +static const uint16_t ATM90E36_REGISTER_ANENERGYCH = 0xAF; // C Reverse Harm. Energy + +/* POWER & P.F. REGISTERS */ +static const uint16_t ATM90E36_REGISTER_PMEANT = 0xB0; // Phase Active Total (P) +static const uint16_t ATM90E36_REGISTER_PMEAN = 0xB1; // Phase A Active Power start for loop (P) +static const uint16_t ATM90E36_REGISTER_PMEANA = 0xB1; // Phase A Active Power (P) +static const uint16_t ATM90E36_REGISTER_PMEANB = 0xB2; // Phase B Active Power (P) +static const uint16_t ATM90E36_REGISTER_PMEANC = 0xB3; // Phase C Active Power (P) +static const uint16_t ATM90E36_REGISTER_QMEANT = 0xB4; // Potencia Media Total (Q) +static const uint16_t ATM90E36_REGISTER_QMEAN = 0xB5; // Reactive Power all Phases (Q) +static const uint16_t ATM90E36_REGISTER_QMEANA = 0xB5; // Reactive Power A (Q) +static const uint16_t ATM90E36_REGISTER_QMEANB = 0xB6; // Reactive Power B (Q) +static const uint16_t ATM90E36_REGISTER_QMEANC = 0xB7; // Reactive Power C (Q) +static const uint16_t ATM90E36_REGISTER_SMEANT = 0xB8; // Apparent Power All Phases (S) +static const uint16_t ATM90E36_REGISTER_SMEANA = 0xB9; // Apparent Power A (S) +static const uint16_t ATM90E36_REGISTER_SMEANB = 0xBA; // Apparent Power B (S) +static const uint16_t ATM90E36_REGISTER_SMEANC = 0xBB; // Apparent Power C (S) +static const uint16_t ATM90E36_REGISTER_PFMEANT = 0xBC; // Total Power Factor all Phases +static const uint16_t ATM90E36_REGISTER_PFMEAN = 0xBD; // Power Factor Start for loop +static const uint16_t ATM90E36_REGISTER_PFMEANA = 0xBD; // Power Factor A +static const uint16_t ATM90E36_REGISTER_PFMEANB = 0xBE; // Power Factor B +static const uint16_t ATM90E36_REGISTER_PFMEANC = 0xBF; // Power Factor C + +static const uint16_t ATM90E36_REGISTER_PMEANTLSB = 0xC0; // Lower Word (Tot. Act. Power) +static const uint16_t ATM90E36_REGISTER_PMEANLSB = 0xC1; // Lower Word Reg Base (Active Power) +static const uint16_t ATM90E36_REGISTER_PMEANALSB = 0xC1; // Lower Word (A Act. Power) +static const uint16_t ATM90E36_REGISTER_PMEANBLSB = 0xC2; // Lower Word (B Act. Power) +static const uint16_t ATM90E36_REGISTER_PMEANCLSB = 0xC3; // Lower Word (C Act. Power) +static const uint16_t ATM90E36_REGISTER_QMEANTLSB = 0xC4; // Lower Word (Tot. React. Power) +static const uint16_t ATM90E36_REGISTER_QMEANLSB = 0xC5; // Lower Word Reg Base (Reactive Power) +static const uint16_t ATM90E36_REGISTER_QMEANALSB = 0xC5; // Lower Word (A React. Power) +static const uint16_t ATM90E36_REGISTER_QMEANBLSB = 0xC6; // Lower Word (B React. Power) +static const uint16_t ATM90E36_REGISTER_QMEANCLSB = 0xC7; // Lower Word (C React. Power) +static const uint16_t ATM90E36_REGISTER_SAMEANTLSB = 0xC8; // Lower Word (Tot. App. Power) +static const uint16_t ATM90E36_REGISTER_SMEANALSB = 0xC9; // Lower Word (A App. Power) +static const uint16_t ATM90E36_REGISTER_SMEANBLSB = 0xCA; // Lower Word (B App. Power) +static const uint16_t ATM90E36_REGISTER_SMEANCLSB = 0xCB; // Lower Word (C App. Power) + +/* FUND/HARM POWER & V/I RMS REGISTERS */ +static const uint16_t ATM90E36_REGISTER_PMEANTF = 0xD0; // Total Active Fund. Power +static const uint16_t ATM90E36_REGISTER_PMEANAF = 0xD1; // A Active Fund. Power +static const uint16_t ATM90E36_REGISTER_PMEANBF = 0xD2; // B Active Fund. Power +static const uint16_t ATM90E36_REGISTER_PMEANCF = 0xD3; // C Active Fund. Power +static const uint16_t ATM90E36_REGISTER_PMEANTH = 0xD4; // Total Active Harm. Power +static const uint16_t ATM90E36_REGISTER_PMEANH = 0xD5; // Active Harm. Power Reg Base +static const uint16_t ATM90E36_REGISTER_PMEANAH = 0xD5; // A Active Harm. Power +static const uint16_t ATM90E36_REGISTER_PMEANBH = 0xD6; // B Active Harm. Power +static const uint16_t ATM90E36_REGISTER_PMEANCH = 0xD7; // C Active Harm. Power +static const uint16_t ATM90E36_REGISTER_URMS = 0xD9; // RMS Voltage Reg Base +static const uint16_t ATM90E36_REGISTER_URMSA = 0xD9; // A RMS Voltage +static const uint16_t ATM90E36_REGISTER_URMSB = 0xDA; // B RMS Voltage +static const uint16_t ATM90E36_REGISTER_URMSC = 0xDB; // C RMS Voltage +static const uint16_t ATM90E36_REGISTER_IRMS = 0xDD; // RMS Current Reg Base +static const uint16_t ATM90E36_REGISTER_IRMSA = 0xDD; // A RMS Current +static const uint16_t ATM90E36_REGISTER_IRMSB = 0xDE; // B RMS Current +static const uint16_t ATM90E36_REGISTER_IRMSC = 0xDF; // C RMS Current +static const uint16_t ATM90E36_REGISTER_IRMSN = 0xD8; // Calculated N RMS Current + +static const uint16_t ATM90E36_REGISTER_PMEANTFLSB = 0xE0; // Lower Word (Tot. Act. Fund. Power) +static const uint16_t ATM90E36_REGISTER_PMEANAFLSB = 0xE1; // Lower Word (A Act. Fund. Power) +static const uint16_t ATM90E36_REGISTER_PMEANBFLSB = 0xE2; // Lower Word (B Act. Fund. Power) +static const uint16_t ATM90E36_REGISTER_PMEANCFLSB = 0xE3; // Lower Word (C Act. Fund. Power) +static const uint16_t ATM90E36_REGISTER_PMEANTHLSB = 0xE4; // Lower Word (Tot. Act. Harm. Power) +static const uint16_t ATM90E36_REGISTER_PMEANHLSB = 0xE5; // Lower Word (A Act. Harm. Power) Reg Base +static const uint16_t ATM90E36_REGISTER_PMEANAHLSB = 0xE5; // Lower Word (A Act. Harm. Power) +static const uint16_t ATM90E36_REGISTER_PMEANBHLSB = 0xE6; // Lower Word (B Act. Harm. Power) +static const uint16_t ATM90E36_REGISTER_PMEANCHLSB = 0xE7; // Lower Word (C Act. Harm. Power) +///////////////// 0xE8 // Reserved Register +static const uint16_t ATM90E36_REGISTER_URMSLSB = 0xE9; // Lower Word RMS Voltage Reg Base +static const uint16_t ATM90E36_REGISTER_URMSALSB = 0xE9; // Lower Word (A RMS Voltage) +static const uint16_t ATM90E36_REGISTER_URMSBLSB = 0xEA; // Lower Word (B RMS Voltage) +static const uint16_t ATM90E36_REGISTER_URMSCLSB = 0xEB; // Lower Word (C RMS Voltage) +///////////////// 0xEC // Reserved Register +static const uint16_t ATM90E36_REGISTER_IRMSLSB = 0xED; // Lower Word RMS Current Reg Base +static const uint16_t ATM90E36_REGISTER_IRMSALSB = 0xED; // Lower Word (A RMS Current) +static const uint16_t ATM90E36_REGISTER_IRMSBLSB = 0xEE; // Lower Word (B RMS Current) +static const uint16_t ATM90E36_REGISTER_IRMSCLSB = 0xEF; // Lower Word (C RMS Current) + +/* THD, FREQUENCY, ANGLE & TEMPTEMP REGISTERS*/ +static const uint16_t ATM90E36_REGISTER_UPEAKA = 0xF1; // A Voltage Peak +static const uint16_t ATM90E36_REGISTER_UPEAKB = 0xF2; // B Voltage Peak +static const uint16_t ATM90E36_REGISTER_UPEAKC = 0xF3; // C Voltage Peak +//////////////// 0xF4 // Reserved Register +static const uint16_t ATM90E36_REGISTER_IPEAK = 0xF5; // Peak Current Reg Base +static const uint16_t ATM90E36_REGISTER_IPEAKA = 0xF5; // A Current Peak +static const uint16_t ATM90E36_REGISTER_IPEAKB = 0xF6; // B Current Peak +static const uint16_t ATM90E36_REGISTER_IPEAKC = 0xF7; // C Current Peak +static const uint16_t ATM90E36_REGISTER_FREQ = 0xF8; // Frequency +static const uint16_t ATM90E36_REGISTER_PANGLE = 0xF9; // Mean Phase Angle Reg Base +static const uint16_t ATM90E36_REGISTER_PANGLEA = 0xF9; // A Mean Phase Angle +static const uint16_t ATM90E36_REGISTER_PANGLEB = 0xFA; // B Mean Phase Angle +static const uint16_t ATM90E36_REGISTER_PANGLEC = 0xFB; // C Mean Phase Angle +static const uint16_t ATM90E36_REGISTER_TEMP = 0xFC; // Measured Temperature +static const uint16_t ATM90E36_REGISTER_UANGLEA = 0xFD; // A Voltage Phase Angle +static const uint16_t ATM90E36_REGISTER_UANGLEB = 0xFE; // B Voltage Phase Angle +static const uint16_t ATM90E36_REGISTER_UANGLEC = 0xFF; // C Voltage Phase Angle + +} // namespace atm90e32 +} // namespace esphome diff --git a/components/ATM90E36/number/__init__.py b/components/ATM90E36/number/__init__.py new file mode 100644 index 0000000..df7ba69 --- /dev/null +++ b/components/ATM90E36/number/__init__.py @@ -0,0 +1,130 @@ +import esphome.codegen as cg +from esphome.components import number +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_MAX_VALUE, + CONF_MIN_VALUE, + CONF_MODE, + CONF_PHASE_A, + CONF_PHASE_B, + CONF_PHASE_C, + CONF_REFERENCE_VOLTAGE, + CONF_STEP, + ENTITY_CATEGORY_CONFIG, + UNIT_AMPERE, + UNIT_VOLT, +) + +from .. import atm90e36_ns +from ..sensor import ATM90E36Component + +ATM90E36Number = atm90e36_ns.class_( + "ATM90E36Number", number.Number, cg.Parented.template(ATM90E36Component) +) + +CONF_REFERENCE_CURRENT = "reference_current" +PHASE_KEYS = [CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C] + + +REFERENCE_VOLTAGE_PHASE_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_MODE, default="box"): cv.string, + cv.Optional(CONF_MIN_VALUE, default=100.0): cv.float_, + cv.Optional(CONF_MAX_VALUE, default=260.0): cv.float_, + cv.Optional(CONF_STEP, default=0.1): cv.float_, + } + ).extend( + number.number_schema( + class_=ATM90E36Number, + unit_of_measurement=UNIT_VOLT, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:power-plug", + ) + ) +) + + +REFERENCE_CURRENT_PHASE_SCHEMA = cv.All( + cv.Schema( + { + cv.Optional(CONF_MODE, default="box"): cv.string, + cv.Optional(CONF_MIN_VALUE, default=1.0): cv.float_, + cv.Optional(CONF_MAX_VALUE, default=200.0): cv.float_, + cv.Optional(CONF_STEP, default=0.1): cv.float_, + } + ).extend( + number.number_schema( + class_=ATM90E36Number, + unit_of_measurement=UNIT_AMPERE, + entity_category=ENTITY_CATEGORY_CONFIG, + icon="mdi:home-lightning-bolt", + ) + ) +) + + +REFERENCE_VOLTAGE_SCHEMA = cv.Schema( + { + cv.Optional(CONF_PHASE_A): REFERENCE_VOLTAGE_PHASE_SCHEMA, + cv.Optional(CONF_PHASE_B): REFERENCE_VOLTAGE_PHASE_SCHEMA, + cv.Optional(CONF_PHASE_C): REFERENCE_VOLTAGE_PHASE_SCHEMA, + } +) + +REFERENCE_CURRENT_SCHEMA = cv.Schema( + { + cv.Optional(CONF_PHASE_A): REFERENCE_CURRENT_PHASE_SCHEMA, + cv.Optional(CONF_PHASE_B): REFERENCE_CURRENT_PHASE_SCHEMA, + cv.Optional(CONF_PHASE_C): REFERENCE_CURRENT_PHASE_SCHEMA, + } +) + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_ID): cv.use_id(ATM90E36Component), + cv.Optional(CONF_REFERENCE_VOLTAGE): REFERENCE_VOLTAGE_SCHEMA, + cv.Optional(CONF_REFERENCE_CURRENT): REFERENCE_CURRENT_SCHEMA, + } +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_ID]) + + if voltage_cfg := config.get(CONF_REFERENCE_VOLTAGE): + voltage_objs = [None, None, None] + + for i, key in enumerate(PHASE_KEYS): + if validated := voltage_cfg.get(key): + obj = await number.new_number( + validated, + min_value=validated["min_value"], + max_value=validated["max_value"], + step=validated["step"], + ) + await cg.register_parented(obj, parent) + voltage_objs[i] = obj + + # Inherit from A → B/C if only A defined + if voltage_objs[0] is not None: + for i in range(3): + if voltage_objs[i] is None: + voltage_objs[i] = voltage_objs[0] + + for i, obj in enumerate(voltage_objs): + if obj is not None: + cg.add(parent.set_reference_voltage(i, obj)) + + if current_cfg := config.get(CONF_REFERENCE_CURRENT): + for i, key in enumerate(PHASE_KEYS): + if validated := current_cfg.get(key): + obj = await number.new_number( + validated, + min_value=validated["min_value"], + max_value=validated["max_value"], + step=validated["step"], + ) + await cg.register_parented(obj, parent) + cg.add(parent.set_reference_current(i, obj)) diff --git a/components/ATM90E36/number/atm90e36_number.h b/components/ATM90E36/number/atm90e36_number.h new file mode 100644 index 0000000..9801729 --- /dev/null +++ b/components/ATM90E36/number/atm90e36_number.h @@ -0,0 +1,16 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/atm90e36/atm90e36.h" +#include "esphome/components/number/number.h" + +namespace esphome { +namespace atm90e36 { + +class ATM90E36Number : public number::Number, public Parented { + public: + void control(float value) override { this->publish_state(value); } +}; + +} // namespace atm90e32 +} // namespace esphome diff --git a/components/ATM90E36/sensor.py b/components/ATM90E36/sensor.py new file mode 100644 index 0000000..aab1ddb --- /dev/null +++ b/components/ATM90E36/sensor.py @@ -0,0 +1,265 @@ +import esphome.codegen as cg +from esphome.components import sensor, spi +import esphome.config_validation as cv +from esphome.const import ( + CONF_APPARENT_POWER, + CONF_CURRENT, + CONF_FORWARD_ACTIVE_ENERGY, + CONF_FREQUENCY, + CONF_ID, + CONF_LINE_FREQUENCY, + CONF_PHASE_A, + CONF_PHASE_ANGLE, + CONF_PHASE_B, + CONF_PHASE_C, + CONF_POWER, + CONF_POWER_FACTOR, + CONF_REACTIVE_POWER, + CONF_REVERSE_ACTIVE_ENERGY, + CONF_VOLTAGE, + DEVICE_CLASS_APPARENT_POWER, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_REACTIVE_POWER, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_DIAGNOSTIC, + ICON_CURRENT_AC, + ICON_LIGHTBULB, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_DEGREES, + UNIT_HERTZ, + UNIT_VOLT, + UNIT_VOLT_AMPS, + UNIT_VOLT_AMPS_REACTIVE, + UNIT_WATT, + UNIT_WATT_HOURS, +) + +from . import atm90e36_ns + +CONF_CHIP_TEMPERATURE = "chip_temperature" +CONF_GAIN_CURRNT = "gain_current" +CONF_GAIN_DPGA = "gain_dpga" +CONF_GAIN_CURRENT = "gain_current" +CONF_CURRENT_PHASES = "current_phases" +CONF_GAIN_VOLTAGE = "gain_voltage" +CONF_GAIN_CT = "gain_ct" +CONF_OFFSET_VOLTAGE = "offset_voltage" +CONF_OFFSET_CURRENT = "offset_current" +CONF_OFFSET_ACTIVE_POWER = "offset_active_power" +CONF_OFFSET_REACTIVE_POWER = "offset_reactive_power" +CONF_HARMONIC_POWER = "harmonic_power" +CONF_PEAK_CURRENT = "peak_current" +CONF_PEAK_CURRENT_SIGNED = "peak_current_signed" +CONF_ENABLE_OFFSET_CALIBRATION = "enable_offset_calibration" +CONF_ENABLE_GAIN_CALIBRATION = "enable_gain_calibration" +CONF_PHASE_STATUS = "phase_status" +CONF_FREQUENCY_STATUS = "frequency_status" +UNIT_DEG = "degrees" +LINE_FREQS = { + "50HZ": 50, + "60HZ": 60, +} +CURRENT_PHASES = { + "2": 2, + "3": 3, +} +CURRENT_GAINS = { + "1X": 0x0, + "2X": 0x15, + "4X": 0x2A, +} + +VOLTAGE_GAINS = { + "1X": 0x0, + "2X": 0x15, + "4X": 0x2A, +} + +DPGA_GAINS = { + "1X": 0x0, + "2X": 0x1, + "4X": 0x2, + "8X": 0x3 +} + +ATM90E36Component = atm90e36_ns.class_( + "ATM90E36Component", cg.PollingComponent, spi.SPIDevice +) + +ATM90E36_PHASE_SCHEMA = cv.Schema( + { + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE, + icon=ICON_LIGHTBULB, + accuracy_decimals=2, + device_class=DEVICE_CLASS_REACTIVE_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_APPARENT_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT_AMPS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_APPARENT_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema( + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_FORWARD_ACTIVE_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional(CONF_REVERSE_ACTIVE_ENERGY): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional(CONF_PHASE_ANGLE): sensor.sensor_schema( + unit_of_measurement=UNIT_DEGREES, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_HARMONIC_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PEAK_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_GAIN_VOLTAGE, default=7305): cv.uint16_t, + cv.Optional(CONF_GAIN_CT, default=27961): cv.uint16_t, + cv.Optional(CONF_OFFSET_VOLTAGE, default=0): cv.int_, + cv.Optional(CONF_OFFSET_CURRENT, default=0): cv.int_, + cv.Optional(CONF_OFFSET_ACTIVE_POWER, default=0): cv.int_, + cv.Optional(CONF_OFFSET_REACTIVE_POWER, default=0): cv.int_, + } +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(ATM90E36Component), + cv.Optional(CONF_PHASE_A): ATM90E36_PHASE_SCHEMA, + cv.Optional(CONF_PHASE_B): ATM90E36_PHASE_SCHEMA, + cv.Optional(CONF_PHASE_C): ATM90E36_PHASE_SCHEMA, + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + unit_of_measurement=UNIT_HERTZ, + icon=ICON_CURRENT_AC, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CHIP_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Required(CONF_LINE_FREQUENCY): cv.enum(LINE_FREQS, upper=True), + cv.Optional(CONF_CURRENT_PHASES, default="3"): cv.enum( + CURRENT_PHASES, upper=True + ), + cv.Optional(CONF_GAIN_CURRENT, default="1X"): cv.enum(CURRENT_GAINS, upper=True), + cv.Optional(CONF_GAIN_VOLTAGE, default="1X"): cv.enum(VOLTAGE_GAINS, upper=True), + cv.Optional(CONF_GAIN_DPGA, default="1X"): cv.enum(DPGA_GAINS, upper=True), + cv.Optional(CONF_PEAK_CURRENT_SIGNED, default=False): cv.boolean, + cv.Optional(CONF_ENABLE_OFFSET_CALIBRATION, default=False): cv.boolean, + cv.Optional(CONF_ENABLE_GAIN_CALIBRATION, default=False): cv.boolean, + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(spi.spi_device_schema()) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await spi.register_spi_device(var, config) + + for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]): + if phase not in config: + continue + conf = config[phase] + cg.add(var.set_volt_gain(i, conf[CONF_GAIN_VOLTAGE])) + cg.add(var.set_ct_gain(i, conf[CONF_GAIN_CT])) + if voltage_config := conf.get(CONF_VOLTAGE): + sens = await sensor.new_sensor(voltage_config) + cg.add(var.set_voltage_sensor(i, sens)) + if current_config := conf.get(CONF_CURRENT): + sens = await sensor.new_sensor(current_config) + cg.add(var.set_current_sensor(i, sens)) + if power_config := conf.get(CONF_POWER): + sens = await sensor.new_sensor(power_config) + cg.add(var.set_power_sensor(i, sens)) + if reactive_power_config := conf.get(CONF_REACTIVE_POWER): + sens = await sensor.new_sensor(reactive_power_config) + cg.add(var.set_reactive_power_sensor(i, sens)) + if apparent_power_config := conf.get(CONF_APPARENT_POWER): + sens = await sensor.new_sensor(apparent_power_config) + cg.add(var.set_apparent_power_sensor(i, sens)) + if power_factor_config := conf.get(CONF_POWER_FACTOR): + sens = await sensor.new_sensor(power_factor_config) + cg.add(var.set_power_factor_sensor(i, sens)) + if forward_active_energy_config := conf.get(CONF_FORWARD_ACTIVE_ENERGY): + sens = await sensor.new_sensor(forward_active_energy_config) + cg.add(var.set_forward_active_energy_sensor(i, sens)) + if reverse_active_energy_config := conf.get(CONF_REVERSE_ACTIVE_ENERGY): + sens = await sensor.new_sensor(reverse_active_energy_config) + cg.add(var.set_reverse_active_energy_sensor(i, sens)) + if phase_angle_config := conf.get(CONF_PHASE_ANGLE): + sens = await sensor.new_sensor(phase_angle_config) + cg.add(var.set_phase_angle_sensor(i, sens)) + if harmonic_active_power_config := conf.get(CONF_HARMONIC_POWER): + sens = await sensor.new_sensor(harmonic_active_power_config) + cg.add(var.set_harmonic_active_power_sensor(i, sens)) + if peak_current_config := conf.get(CONF_PEAK_CURRENT): + sens = await sensor.new_sensor(peak_current_config) + cg.add(var.set_peak_current_sensor(i, sens)) + if frequency_config := config.get(CONF_FREQUENCY): + sens = await sensor.new_sensor(frequency_config) + cg.add(var.set_freq_sensor(sens)) + if chip_temperature_config := config.get(CONF_CHIP_TEMPERATURE): + sens = await sensor.new_sensor(chip_temperature_config) + cg.add(var.set_chip_temperature_sensor(sens)) + cg.add(var.set_line_freq(config[CONF_LINE_FREQUENCY])) + cg.add(var.set_current_phases(config[CONF_CURRENT_PHASES])) + cg.add(var.set_pga_current(config[CONF_GAIN_CURRENT])) + cg.add(var.set_pga_voltage(config[CONF_GAIN_VOLTAGE])) + cg.add(var.set_dpga_gain(config[CONF_GAIN_DPGA])) + cg.add(var.set_peak_current_signed(config[CONF_PEAK_CURRENT_SIGNED])) diff --git a/components/ATM90E36/text_sensor/__init__.py b/components/ATM90E36/text_sensor/__init__.py new file mode 100644 index 0000000..c7afc05 --- /dev/null +++ b/components/ATM90E36/text_sensor/__init__.py @@ -0,0 +1,33 @@ +import esphome.codegen as cg +from esphome.components import text_sensor +import esphome.config_validation as cv +from esphome.const import CONF_ID, CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C + +from ..sensor import ATM90E36Component + +CONF_PHASE_STATUS = "phase_status" +CONF_FREQUENCY_STATUS = "frequency_status" + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(): cv.use_id(ATM90E36Component), + cv.Optional(CONF_PHASE_STATUS): text_sensor.text_sensor_schema( + icon="mdi:flash-alert" + ), + cv.Optional(CONF_FREQUENCY_STATUS): text_sensor.text_sensor_schema( + icon="mdi:lightbulb-alert" + ), + } +) + + +async def to_code(config): + parent = await cg.get_variable(config[CONF_ID]) + + if phase_cfg := config.get(CONF_PHASE_STATUS): + sens = await text_sensor.new_text_sensor(phase_cfg) + cg.add(parent.set_phase_status_text_sensor(sens)) + + if freq_status_config := config.get(CONF_FREQUENCY_STATUS): + sens = await text_sensor.new_text_sensor(freq_status_config) + cg.add(parent.set_freq_status_text_sensor(sens))