14a7c2ef82
the register is now used for thd Signed-off-by: Matthew Thode <mthode@mthode.org>
555 lines
24 KiB
C++
555 lines
24 KiB
C++
#include "atm90e36.h"
|
|
#include <cinttypes>
|
|
#include <cmath>
|
|
#include <numbers>
|
|
#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));
|
|
}
|
|
|
|
}
|
|
// THD values
|
|
if (this->thd_voltage_a_sensor_ != nullptr) {
|
|
this->thd_voltage_a_sensor_->publish_state(this->get_thd_voltage_a());
|
|
}
|
|
if (this->thd_current_a_sensor_ != nullptr) {
|
|
this->thd_current_a_sensor_->publish_state(this->get_thd_current_a());
|
|
}
|
|
if (this->thd_voltage_b_sensor_ != nullptr) {
|
|
this->thd_voltage_b_sensor_->publish_state(this->get_thd_voltage_b());
|
|
}
|
|
if (this->thd_current_b_sensor_ != nullptr) {
|
|
this->thd_current_b_sensor_->publish_state(this->get_thd_current_b());
|
|
}
|
|
if (this->thd_voltage_c_sensor_ != nullptr) {
|
|
this->thd_voltage_c_sensor_->publish_state(this->get_thd_voltage_c());
|
|
}
|
|
if (this->thd_current_c_sensor_ != nullptr) {
|
|
this->thd_current_c_sensor_->publish_state(this->get_thd_current_c());
|
|
}
|
|
|
|
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->thd_voltage_a_sensor_);
|
|
LOG_SENSOR(" ", "THD Current A", this->thd_current_a_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->thd_voltage_b_sensor_);
|
|
LOG_SENSOR(" ", "THD Current B", this->thd_current_b_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->thd_voltage_c_sensor_);
|
|
LOG_SENSOR(" ", "THD Current C", this->thd_current_c_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_thd_voltage_a() {
|
|
uint16_t val = this->read16_(ATM90E36_REGISTER_THDNUA);
|
|
return (float)val / 100.0; // Adjust division scale based on your calibration constraints
|
|
}
|
|
|
|
float ATM90E36Component::get_thd_current_a() {
|
|
uint16_t val = this->read16_(ATM90E36_REGISTER_THDNIA);
|
|
return (float)val / 100.0;
|
|
}
|
|
|
|
float ATM90E36Component::get_thd_voltage_b() {
|
|
uint16_t val = this->read16_(ATM90E36_REGISTER_THDNUB);
|
|
return (float)val / 100.0; // Adjust division scale based on your calibration constraints
|
|
}
|
|
|
|
float ATM90E36Component::get_thd_current_b() {
|
|
uint16_t val = this->read16_(ATM90E36_REGISTER_THDNIB);
|
|
return (float)val / 100.0;
|
|
}
|
|
|
|
float ATM90E36Component::get_thd_voltage_c() {
|
|
uint16_t val = this->read16_(ATM90E36_REGISTER_THDNUC);
|
|
return (float)val / 100.0; // Adjust division scale based on your calibration constraints
|
|
}
|
|
|
|
float ATM90E36Component::get_thd_current_c() {
|
|
uint16_t val = this->read16_(ATM90E36_REGISTER_THDNIC);
|
|
return (float)val / 100.0;
|
|
}
|
|
|
|
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<float>; // convert RMS → peak, scale to 0.01V
|
|
float divider = (2.0f * ugain) / 32768.0f;
|
|
|
|
float threshold = peak_01v / divider;
|
|
|
|
return static_cast<uint16_t>(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
|