#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); // 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].thd_voltage_sensor_ != nullptr) this->phase_[phase].thd_voltage_sensor_->publish_state(this->get_local_phase_thd_voltage_(phase)); if (this->phase_[phase].thd_current_sensor_ != nullptr) this->phase_[phase].thd_current_sensor_->publish_state(this->get_local_phase_thd_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(" ", "THD Voltage A", this->phase_[PHASEA].thd_voltage_sensor_); LOG_SENSOR(" ", "THD Current A", this->phase_[PHASEA].thd_current_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(" ", "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(" ", "THD Voltage B", this->phase_[PHASEB].thd_voltage_sensor_); LOG_SENSOR(" ", "THD Current B", this->phase_[PHASEB].thd_current_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(" ", "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(" ", "THD Voltage C", this->phase_[PHASEC].thd_voltage_sensor_); LOG_SENSOR(" ", "THD Current C", this->phase_[PHASEC].thd_current_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(" ", "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_thd_voltage_(uint8_t phase) { uint16_t val = this->read16_(ATM90E36_REGISTER_THDNU + phase); return (float) val / 100.0; // Adjust division scale based on your calibration constraints } float ATM90E36Component::get_local_phase_thd_current_(uint8_t phase) { uint16_t val = this->read16_(ATM90E36_REGISTER_THDNI + phase); return (float) val / 100.0; // Adjust division scale based on your calibration constraints } 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_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_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