ESPHome 2025.9.0-dev
Loading...
Searching...
No Matches
atm90e32.cpp
Go to the documentation of this file.
1#include "atm90e32.h"
2#include <cinttypes>
3#include <cmath>
4#include <numbers>
5#include "esphome/core/log.h"
6
7namespace esphome {
8namespace atm90e32 {
9
10static const char *const TAG = "atm90e32";
12 if (this->get_publish_interval_flag_()) {
13 this->set_publish_interval_flag_(false);
14 for (uint8_t phase = 0; phase < 3; phase++) {
15 if (this->phase_[phase].voltage_sensor_ != nullptr)
16 this->phase_[phase].voltage_ = this->get_phase_voltage_(phase);
17
18 if (this->phase_[phase].current_sensor_ != nullptr)
19 this->phase_[phase].current_ = this->get_phase_current_(phase);
20
21 if (this->phase_[phase].power_sensor_ != nullptr)
22 this->phase_[phase].active_power_ = this->get_phase_active_power_(phase);
23
24 if (this->phase_[phase].power_factor_sensor_ != nullptr)
25 this->phase_[phase].power_factor_ = this->get_phase_power_factor_(phase);
26
27 if (this->phase_[phase].reactive_power_sensor_ != nullptr)
28 this->phase_[phase].reactive_power_ = this->get_phase_reactive_power_(phase);
29
30 if (this->phase_[phase].apparent_power_sensor_ != nullptr)
31 this->phase_[phase].apparent_power_ = this->get_phase_apparent_power_(phase);
32
33 if (this->phase_[phase].forward_active_energy_sensor_ != nullptr)
35
36 if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr)
38
39 if (this->phase_[phase].phase_angle_sensor_ != nullptr)
40 this->phase_[phase].phase_angle_ = this->get_phase_angle_(phase);
41
42 if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr)
44
45 if (this->phase_[phase].peak_current_sensor_ != nullptr)
46 this->phase_[phase].peak_current_ = this->get_phase_peak_current_(phase);
47
48 // After the local store is collected we can publish them trusting they are within +-1 hardware sampling
49 if (this->phase_[phase].voltage_sensor_ != nullptr)
51
52 if (this->phase_[phase].current_sensor_ != nullptr)
54
55 if (this->phase_[phase].power_sensor_ != nullptr)
57
58 if (this->phase_[phase].power_factor_sensor_ != nullptr)
60
61 if (this->phase_[phase].reactive_power_sensor_ != nullptr)
63
64 if (this->phase_[phase].apparent_power_sensor_ != nullptr)
66
67 if (this->phase_[phase].forward_active_energy_sensor_ != nullptr) {
70 }
71
72 if (this->phase_[phase].reverse_active_energy_sensor_ != nullptr) {
75 }
76
77 if (this->phase_[phase].phase_angle_sensor_ != nullptr)
79
80 if (this->phase_[phase].harmonic_active_power_sensor_ != nullptr) {
83 }
84
85 if (this->phase_[phase].peak_current_sensor_ != nullptr)
87 }
88 if (this->freq_sensor_ != nullptr)
90
91 if (this->chip_temperature_sensor_ != nullptr)
93 }
94}
95
97 if (this->read16_(ATM90E32_REGISTER_METEREN) != 1) {
98 this->status_set_warning();
99 return;
100 }
101 this->set_publish_interval_flag_(true);
102 this->status_clear_warning();
103
104#ifdef USE_TEXT_SENSOR
105 this->check_phase_status();
106 this->check_over_current();
107 this->check_freq_status();
108#endif
109}
110
112 this->spi_setup();
113 this->cs_summary_ = this->cs_->dump_summary();
114 const char *cs = this->cs_summary_.c_str();
115
116 uint16_t mmode0 = 0x87; // 3P4W 50Hz
117 uint16_t high_thresh = 0;
118 uint16_t low_thresh = 0;
119
120 if (line_freq_ == 60) {
121 mmode0 |= 1 << 12; // sets 12th bit to 1, 60Hz
122 // for freq threshold registers
123 high_thresh = 6300; // 63.00 Hz
124 low_thresh = 5700; // 57.00 Hz
125 } else {
126 high_thresh = 5300; // 53.00 Hz
127 low_thresh = 4700; // 47.00 Hz
128 }
129
130 if (current_phases_ == 2) {
131 mmode0 |= 1 << 8; // sets 8th bit to 1, 3P3W
132 mmode0 |= 0 << 1; // sets 1st bit to 0, phase b is not counted into the all-phase sum energy/power (P/Q/S)
133 }
134
135 this->write16_(ATM90E32_REGISTER_SOFTRESET, 0x789A, false); // Perform soft reset
136 delay(6); // Wait for the minimum 5ms + 1ms
137 this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA); // enable register config access
138 if (!this->validate_spi_read_(0x55AA, "setup()")) {
139 ESP_LOGW(TAG, "Could not initialize ATM90E32 IC, check SPI settings");
140 this->mark_failed();
141 return;
142 }
143
144 this->write16_(ATM90E32_REGISTER_METEREN, 0x0001); // Enable Metering
145 this->write16_(ATM90E32_REGISTER_SAGPEAKDETCFG, 0xFF3F); // Peak Detector time (15:8) 255ms, Sag Period (7:0) 63ms
146 this->write16_(ATM90E32_REGISTER_PLCONSTH, 0x0861); // PL Constant MSB (default) = 140625000
147 this->write16_(ATM90E32_REGISTER_PLCONSTL, 0xC468); // PL Constant LSB (default)
148 this->write16_(ATM90E32_REGISTER_ZXCONFIG, 0xD654); // Zero crossing (ZX2, ZX1, ZX0) pin config
149 this->write16_(ATM90E32_REGISTER_MMODE0, mmode0); // Mode Config (frequency set in main program)
150 this->write16_(ATM90E32_REGISTER_MMODE1, pga_gain_); // PGA Gain Configuration for Current Channels
151 this->write16_(ATM90E32_REGISTER_FREQHITH, high_thresh); // Frequency high threshold
152 this->write16_(ATM90E32_REGISTER_FREQLOTH, low_thresh); // Frequency low threshold
153 this->write16_(ATM90E32_REGISTER_PSTARTTH, 0x1D4C); // All Active Startup Power Threshold - 0.02A/0.00032 = 7500
154 this->write16_(ATM90E32_REGISTER_QSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
155 this->write16_(ATM90E32_REGISTER_SSTARTTH, 0x1D4C); // All Reactive Startup Power Threshold - 50%
156 this->write16_(ATM90E32_REGISTER_PPHASETH, 0x02EE); // Each Phase Active Phase Threshold - 0.002A/0.00032 = 750
157 this->write16_(ATM90E32_REGISTER_QPHASETH, 0x02EE); // Each phase Reactive Phase Threshold - 10%
158
159 if (this->enable_offset_calibration_) {
160 // Initialize flash storage for offset calibrations
161 uint32_t o_hash = fnv1_hash(std::string("_offset_calibration_") + this->cs_summary_);
164
165 // Initialize flash storage for power offset calibrations
166 uint32_t po_hash = fnv1_hash(std::string("_power_offset_calibration_") + this->cs_summary_);
169 } else {
170 ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
171 cs);
172 for (uint8_t phase = 0; phase < 3; ++phase) {
173 this->write16_(this->voltage_offset_registers[phase],
174 static_cast<uint16_t>(this->offset_phase_[phase].voltage_offset_));
175 this->write16_(this->current_offset_registers[phase],
176 static_cast<uint16_t>(this->offset_phase_[phase].current_offset_));
177 this->write16_(this->power_offset_registers[phase],
178 static_cast<uint16_t>(this->power_offset_phase_[phase].active_power_offset));
179 this->write16_(this->reactive_power_offset_registers[phase],
180 static_cast<uint16_t>(this->power_offset_phase_[phase].reactive_power_offset));
181 }
182 }
183
184 if (this->enable_gain_calibration_) {
185 // Initialize flash storage for gain calibration
186 uint32_t g_hash = fnv1_hash(std::string("_gain_calibration_") + this->cs_summary_);
189
190 if (!this->using_saved_calibrations_) {
191 for (uint8_t phase = 0; phase < 3; ++phase) {
192 this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
193 this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
194 }
195 }
196 } else {
197 ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
198 for (uint8_t phase = 0; phase < 3; ++phase) {
199 this->write16_(voltage_gain_registers[phase], this->phase_[phase].voltage_gain_);
200 this->write16_(current_gain_registers[phase], this->phase_[phase].ct_gain_);
201 }
202 }
203
204 // Sag threshold (78%)
205 uint16_t sagth = calculate_voltage_threshold(line_freq_, this->phase_[0].voltage_gain_, 0.78f);
206 // Overvoltage threshold (122%)
207 uint16_t ovth = calculate_voltage_threshold(line_freq_, this->phase_[0].voltage_gain_, 1.22f);
208
209 // Write to registers
210 this->write16_(ATM90E32_REGISTER_SAGTH, sagth);
211 this->write16_(ATM90E32_REGISTER_OVTH, ovth);
212
213 this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000); // end configuration
214}
215
217 const char *cs = this->cs_summary_.c_str();
218
219 bool offset_mismatch = false;
220 bool power_mismatch = false;
221 bool gain_mismatch = false;
222
223 for (uint8_t phase = 0; phase < 3; ++phase) {
224 offset_mismatch |= this->offset_calibration_mismatch_[phase];
225 power_mismatch |= this->power_offset_calibration_mismatch_[phase];
226 gain_mismatch |= this->gain_calibration_mismatch_[phase];
227 }
228
229 if (offset_mismatch) {
230 ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
231 ESP_LOGW(TAG,
232 "[CALIBRATION][%s] ===================== Offset mismatch: using flash values =====================", cs);
233 ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
234 cs);
235 ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
236 ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
237 ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
238 cs);
239 for (uint8_t phase = 0; phase < 3; ++phase) {
240 ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
241 this->config_offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].voltage_offset_,
242 this->config_offset_phase_[phase].current_offset_, this->offset_phase_[phase].current_offset_);
243 }
244 ESP_LOGW(TAG,
245 "[CALIBRATION][%s] ===============================================================================", cs);
246 }
247 if (power_mismatch) {
248 ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
249 ESP_LOGW(TAG,
250 "[CALIBRATION][%s] ================= Power offset mismatch: using flash values =================", cs);
251 ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
252 cs);
253 ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | offset_active_power|offset_reactive_power|", cs);
254 ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
255 ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
256 cs);
257 for (uint8_t phase = 0; phase < 3; ++phase) {
258 ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6d | %6d | %6d | %6d |", cs, 'A' + phase,
259 this->config_power_offset_phase_[phase].active_power_offset,
260 this->power_offset_phase_[phase].active_power_offset,
261 this->config_power_offset_phase_[phase].reactive_power_offset,
262 this->power_offset_phase_[phase].reactive_power_offset);
263 }
264 ESP_LOGW(TAG,
265 "[CALIBRATION][%s] ===============================================================================", cs);
266 }
267 if (gain_mismatch) {
268 ESP_LOGW(TAG, "[CALIBRATION][%s] ", cs);
269 ESP_LOGW(TAG,
270 "[CALIBRATION][%s] ====================== Gain mismatch: using flash values =====================", cs);
271 ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
272 cs);
273 ESP_LOGW(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
274 ESP_LOGW(TAG, "[CALIBRATION][%s] | | config | flash | config | flash |", cs);
275 ESP_LOGW(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------------------",
276 cs);
277 for (uint8_t phase = 0; phase < 3; ++phase) {
278 ESP_LOGW(TAG, "[CALIBRATION][%s] | %c | %6u | %6u | %6u | %6u |", cs, 'A' + phase,
279 this->config_gain_phase_[phase].voltage_gain, this->gain_phase_[phase].voltage_gain,
280 this->config_gain_phase_[phase].current_gain, this->gain_phase_[phase].current_gain);
281 }
282 ESP_LOGW(TAG,
283 "[CALIBRATION][%s] ===============================================================================", cs);
284 }
285 if (!this->enable_offset_calibration_) {
286 ESP_LOGI(TAG, "[CALIBRATION][%s] Power & Voltage/Current offset calibration is disabled. Using config file values.",
287 cs);
288 } else if (this->restored_offset_calibration_ && !offset_mismatch) {
289 ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
290 ESP_LOGI(TAG, "[CALIBRATION][%s] ============== Restored offset calibration from memory ==============", cs);
291 ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
292 ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
293 ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
294 for (uint8_t phase = 0; phase < 3; phase++) {
295 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
296 this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
297 }
298 ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\\n", cs);
299 }
300
301 if (this->restored_power_offset_calibration_ && !power_mismatch) {
302 ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
303 ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restored power offset calibration from memory ============", cs);
304 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
305 ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
306 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
307 for (uint8_t phase = 0; phase < 3; phase++) {
308 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
309 this->power_offset_phase_[phase].active_power_offset,
310 this->power_offset_phase_[phase].reactive_power_offset);
311 }
312 ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
313 }
314 if (!this->enable_gain_calibration_) {
315 ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration is disabled. Using config file values.", cs);
316 } else if (this->restored_gain_calibration_ && !gain_mismatch) {
317 ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
318 ESP_LOGI(TAG, "[CALIBRATION][%s] ============ Restoring saved gain calibrations to registers ============", cs);
319 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
320 ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
321 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
322 for (uint8_t phase = 0; phase < 3; phase++) {
323 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
324 this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
325 }
326 ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\\n", cs);
327 ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration loaded and verified successfully.\n", cs);
328 }
329 this->calibration_message_printed_ = true;
330}
331
333 ESP_LOGCONFIG("", "ATM90E32:");
334 LOG_PIN(" CS Pin: ", this->cs_);
335 if (this->is_failed()) {
336 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
337 }
338 LOG_UPDATE_INTERVAL(this);
339 LOG_SENSOR(" ", "Voltage A", this->phase_[PHASEA].voltage_sensor_);
340 LOG_SENSOR(" ", "Current A", this->phase_[PHASEA].current_sensor_);
341 LOG_SENSOR(" ", "Power A", this->phase_[PHASEA].power_sensor_);
342 LOG_SENSOR(" ", "Reactive Power A", this->phase_[PHASEA].reactive_power_sensor_);
343 LOG_SENSOR(" ", "Apparent Power A", this->phase_[PHASEA].apparent_power_sensor_);
344 LOG_SENSOR(" ", "PF A", this->phase_[PHASEA].power_factor_sensor_);
345 LOG_SENSOR(" ", "Active Forward Energy A", this->phase_[PHASEA].forward_active_energy_sensor_);
346 LOG_SENSOR(" ", "Active Reverse Energy A", this->phase_[PHASEA].reverse_active_energy_sensor_);
347 LOG_SENSOR(" ", "Harmonic Power A", this->phase_[PHASEA].harmonic_active_power_sensor_);
348 LOG_SENSOR(" ", "Phase Angle A", this->phase_[PHASEA].phase_angle_sensor_);
349 LOG_SENSOR(" ", "Peak Current A", this->phase_[PHASEA].peak_current_sensor_);
350 LOG_SENSOR(" ", "Voltage B", this->phase_[PHASEB].voltage_sensor_);
351 LOG_SENSOR(" ", "Current B", this->phase_[PHASEB].current_sensor_);
352 LOG_SENSOR(" ", "Power B", this->phase_[PHASEB].power_sensor_);
353 LOG_SENSOR(" ", "Reactive Power B", this->phase_[PHASEB].reactive_power_sensor_);
354 LOG_SENSOR(" ", "Apparent Power B", this->phase_[PHASEB].apparent_power_sensor_);
355 LOG_SENSOR(" ", "PF B", this->phase_[PHASEB].power_factor_sensor_);
356 LOG_SENSOR(" ", "Active Forward Energy B", this->phase_[PHASEB].forward_active_energy_sensor_);
357 LOG_SENSOR(" ", "Active Reverse Energy B", this->phase_[PHASEB].reverse_active_energy_sensor_);
358 LOG_SENSOR(" ", "Harmonic Power B", this->phase_[PHASEB].harmonic_active_power_sensor_);
359 LOG_SENSOR(" ", "Phase Angle B", this->phase_[PHASEB].phase_angle_sensor_);
360 LOG_SENSOR(" ", "Peak Current B", this->phase_[PHASEB].peak_current_sensor_);
361 LOG_SENSOR(" ", "Voltage C", this->phase_[PHASEC].voltage_sensor_);
362 LOG_SENSOR(" ", "Current C", this->phase_[PHASEC].current_sensor_);
363 LOG_SENSOR(" ", "Power C", this->phase_[PHASEC].power_sensor_);
364 LOG_SENSOR(" ", "Reactive Power C", this->phase_[PHASEC].reactive_power_sensor_);
365 LOG_SENSOR(" ", "Apparent Power C", this->phase_[PHASEC].apparent_power_sensor_);
366 LOG_SENSOR(" ", "PF C", this->phase_[PHASEC].power_factor_sensor_);
367 LOG_SENSOR(" ", "Active Forward Energy C", this->phase_[PHASEC].forward_active_energy_sensor_);
368 LOG_SENSOR(" ", "Active Reverse Energy C", this->phase_[PHASEC].reverse_active_energy_sensor_);
369 LOG_SENSOR(" ", "Harmonic Power C", this->phase_[PHASEC].harmonic_active_power_sensor_);
370 LOG_SENSOR(" ", "Phase Angle C", this->phase_[PHASEC].phase_angle_sensor_);
371 LOG_SENSOR(" ", "Peak Current C", this->phase_[PHASEC].peak_current_sensor_);
372 LOG_SENSOR(" ", "Frequency", this->freq_sensor_);
373 LOG_SENSOR(" ", "Chip Temp", this->chip_temperature_sensor_);
377 }
378}
379
381
382// R/C registers can conly be cleared after the LastSPIData register is updated (register 78H)
383// Peakdetect period: 05H. Bit 15:8 are PeakDet_period in ms. 7:0 are Sag_period
384// Default is 143FH (20ms, 63ms)
385uint16_t ATM90E32Component::read16_transaction_(uint16_t a_register) {
386 uint8_t addrh = (1 << 7) | ((a_register >> 8) & 0x03);
387 uint8_t addrl = (a_register & 0xFF);
388 uint8_t data[4] = {addrh, addrl, 0x00, 0x00};
389 this->transfer_array(data, 4);
390 uint16_t output = encode_uint16(data[2], data[3]);
391 ESP_LOGVV(TAG, "read16_ 0x%04" PRIX16 " output 0x%04" PRIX16, a_register, output);
392 return output;
393}
394
395uint16_t ATM90E32Component::read16_(uint16_t a_register) {
396 this->enable();
397 delay_microseconds_safe(1); // min delay between CS low and first SCK is 200ns - 1us is plenty
398 uint16_t output = this->read16_transaction_(a_register);
399 delay_microseconds_safe(1); // allow the last clock to propagate before releasing CS
400 this->disable();
401 delay_microseconds_safe(1); // meet minimum CS high time before next transaction
402 return output;
403}
404
405int ATM90E32Component::read32_(uint16_t addr_h, uint16_t addr_l) {
406 this->enable();
408 const uint16_t val_h = this->read16_transaction_(addr_h);
410 const uint16_t val_l = this->read16_transaction_(addr_l);
412 this->disable();
414 const int32_t val = (val_h << 16) | val_l;
415
416 ESP_LOGVV(TAG,
417 "read32_ addr_h 0x%04" PRIX16 " val_h 0x%04" PRIX16 " addr_l 0x%04" PRIX16 " val_l 0x%04" PRIX16
418 " = %" PRId32,
419 addr_h, val_h, addr_l, val_l, val);
420
421 return val;
422}
423
424void ATM90E32Component::write16_(uint16_t a_register, uint16_t val, bool validate) {
425 ESP_LOGVV(TAG, "write16_ 0x%04" PRIX16 " val 0x%04" PRIX16, a_register, val);
426 uint8_t addrh = ((a_register >> 8) & 0x03);
427 uint8_t addrl = (a_register & 0xFF);
428 uint8_t data[4] = {addrh, addrl, uint8_t((val >> 8) & 0xFF), uint8_t(val & 0xFF)};
429 this->enable();
430 delay_microseconds_safe(1); // ensure CS setup time
431 this->write_array(data, 4);
432 delay_microseconds_safe(1); // allow clock to settle before raising CS
433 this->disable();
434 delay_microseconds_safe(1); // ensure minimum CS high time
435 if (validate)
436 this->validate_spi_read_(val, "write16()");
437}
438
439float ATM90E32Component::get_local_phase_voltage_(uint8_t phase) { return this->phase_[phase].voltage_; }
440
441float ATM90E32Component::get_local_phase_current_(uint8_t phase) { return this->phase_[phase].current_; }
442
443float ATM90E32Component::get_local_phase_active_power_(uint8_t phase) { return this->phase_[phase].active_power_; }
444
446
448
449float ATM90E32Component::get_local_phase_power_factor_(uint8_t phase) { return this->phase_[phase].power_factor_; }
450
454
458
459float ATM90E32Component::get_local_phase_angle_(uint8_t phase) { return this->phase_[phase].phase_angle_; }
460
464
465float ATM90E32Component::get_local_phase_peak_current_(uint8_t phase) { return this->phase_[phase].peak_current_; }
466
468 const uint16_t voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
469 this->validate_spi_read_(voltage, "get_phase_voltage()");
470 return (float) voltage / 100;
471}
472
474 const uint8_t reads = 10;
475 uint32_t accumulation = 0;
476 uint16_t voltage = 0;
477 for (uint8_t i = 0; i < reads; i++) {
478 voltage = this->read16_(ATM90E32_REGISTER_URMS + phase);
479 this->validate_spi_read_(voltage, "get_phase_voltage_avg_()");
480 accumulation += voltage;
481 }
482 voltage = accumulation / reads;
483 this->phase_[phase].voltage_ = (float) voltage / 100;
484 return this->phase_[phase].voltage_;
485}
486
488 const uint8_t reads = 10;
489 uint32_t accumulation = 0;
490 uint16_t current = 0;
491 for (uint8_t i = 0; i < reads; i++) {
492 current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
493 this->validate_spi_read_(current, "get_phase_current_avg_()");
494 accumulation += current;
495 }
496 current = accumulation / reads;
497 this->phase_[phase].current_ = (float) current / 1000;
498 return this->phase_[phase].current_;
499}
500
502 const uint16_t current = this->read16_(ATM90E32_REGISTER_IRMS + phase);
503 this->validate_spi_read_(current, "get_phase_current_()");
504 return (float) current / 1000;
505}
506
508 const int val = this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
509 return val * 0.00032f;
510}
511
513 const int val = this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase);
514 return val * 0.00032f;
515}
516
518 const int val = this->read32_(ATM90E32_REGISTER_SMEAN + phase, ATM90E32_REGISTER_SMEANLSB + phase);
519 return val * 0.00032f;
520}
521
523 uint16_t powerfactor = this->read16_(ATM90E32_REGISTER_PFMEAN + phase); // unsigned to compare to lastspidata
524 this->validate_spi_read_(powerfactor, "get_phase_power_factor_()");
525 return (float) ((int16_t) powerfactor) / 1000; // make it signed again
526}
527
529 const uint16_t val = this->read16_(ATM90E32_REGISTER_APENERGY + phase);
530 if ((UINT32_MAX - this->phase_[phase].cumulative_forward_active_energy_) > val) {
532 } else {
534 }
535 // 0.01CF resolution = 0.003125 Wh per count
536 return ((float) this->phase_[phase].cumulative_forward_active_energy_ * (10.0f / 3200.0f));
537}
538
540 const uint16_t val = this->read16_(ATM90E32_REGISTER_ANENERGY + phase);
541 if (UINT32_MAX - this->phase_[phase].cumulative_reverse_active_energy_ > val) {
543 } else {
545 }
546 // 0.01CF resolution = 0.003125 Wh per count
547 return ((float) this->phase_[phase].cumulative_reverse_active_energy_ * (10.0f / 3200.0f));
548}
549
551 int val = this->read32_(ATM90E32_REGISTER_PMEANH + phase, ATM90E32_REGISTER_PMEANHLSB + phase);
552 return val * 0.00032f;
553}
554
556 uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0;
557 return (val > 180) ? (float) (val - 360.0f) : (float) val;
558}
559
561 int16_t val = (float) this->read16_(ATM90E32_REGISTER_IPEAK + phase);
562 if (!this->peak_current_signed_)
563 val = std::abs(val);
564 // phase register * phase current gain value / 1000 * 2^13
565 return (val * this->phase_[phase].ct_gain_ / 8192000.0);
566}
567
569 const uint16_t freq = this->read16_(ATM90E32_REGISTER_FREQ);
570 return (float) freq / 100;
571}
572
574 const uint16_t ctemp = this->read16_(ATM90E32_REGISTER_TEMP);
575 return (float) ctemp;
576}
577
579 const char *cs = this->cs_summary_.c_str();
580 if (!this->enable_gain_calibration_) {
581 ESP_LOGW(TAG, "[CALIBRATION][%s] Gain calibration is disabled! Enable it first with enable_gain_calibration: true",
582 cs);
583 return;
584 }
585
586 float ref_voltages[3] = {
587 this->get_reference_voltage(0),
588 this->get_reference_voltage(1),
589 this->get_reference_voltage(2),
590 };
591 float ref_currents[3] = {this->get_reference_current(0), this->get_reference_current(1),
592 this->get_reference_current(2)};
593
594 ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
595 ESP_LOGI(TAG, "[CALIBRATION][%s] ========================= Gain Calibration =========================", cs);
596 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
597 ESP_LOGI(
598 TAG,
599 "[CALIBRATION][%s] | Phase | V_meas (V) | I_meas (A) | V_ref | I_ref | V_gain (old→new) | I_gain (old→new) |",
600 cs);
601 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
602
603 for (uint8_t phase = 0; phase < 3; phase++) {
604 float measured_voltage = this->get_phase_voltage_avg_(phase);
605 float measured_current = this->get_phase_current_avg_(phase);
606
607 float ref_voltage = ref_voltages[phase];
608 float ref_current = ref_currents[phase];
609
610 uint16_t current_voltage_gain = this->read16_(voltage_gain_registers[phase]);
611 uint16_t current_current_gain = this->read16_(current_gain_registers[phase]);
612
613 bool did_voltage = false;
614 bool did_current = false;
615
616 // Voltage calibration
617 if (ref_voltage <= 0.0f) {
618 ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: reference voltage is 0.", cs,
619 phase_labels[phase]);
620 } else if (measured_voltage == 0.0f) {
621 ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping voltage calibration: measured voltage is 0.", cs,
622 phase_labels[phase]);
623 } else {
624 uint32_t new_voltage_gain = static_cast<uint16_t>((ref_voltage / measured_voltage) * current_voltage_gain);
625 if (new_voltage_gain == 0) {
626 ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Voltage gain would be 0. Check reference and measured voltage.", cs,
627 phase_labels[phase]);
628 } else {
629 if (new_voltage_gain >= 65535) {
630 ESP_LOGW(TAG,
631 "[CALIBRATION][%s] Phase %s - Voltage gain exceeds 65535. You may need a higher output voltage "
632 "transformer.",
633 cs, phase_labels[phase]);
634 new_voltage_gain = 65535;
635 }
636 this->gain_phase_[phase].voltage_gain = static_cast<uint16_t>(new_voltage_gain);
637 did_voltage = true;
638 }
639 }
640
641 // Current calibration
642 if (ref_current == 0.0f) {
643 ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: reference current is 0.", cs,
644 phase_labels[phase]);
645 } else if (measured_current == 0.0f) {
646 ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Skipping current calibration: measured current is 0.", cs,
647 phase_labels[phase]);
648 } else {
649 uint32_t new_current_gain = static_cast<uint16_t>((ref_current / measured_current) * current_current_gain);
650 if (new_current_gain == 0) {
651 ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain would be 0. Check reference and measured current.", cs,
652 phase_labels[phase]);
653 } else {
654 if (new_current_gain >= 65535) {
655 ESP_LOGW(TAG, "[CALIBRATION][%s] Phase %s - Current gain exceeds 65535. You may need to turn up pga gain.",
656 cs, phase_labels[phase]);
657 new_current_gain = 65535;
658 }
659 this->gain_phase_[phase].current_gain = static_cast<uint16_t>(new_current_gain);
660 did_current = true;
661 }
662 }
663
664 // Final row output
665 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %9.2f | %9.4f | %5.2f | %6.4f | %5u → %-5u | %5u → %-5u |", cs,
666 'A' + phase, measured_voltage, measured_current, ref_voltage, ref_current, current_voltage_gain,
667 did_voltage ? this->gain_phase_[phase].voltage_gain : current_voltage_gain, current_current_gain,
668 did_current ? this->gain_phase_[phase].current_gain : current_current_gain);
669 }
670
671 ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
672
675 this->verify_gain_writes_();
676}
677
679 const char *cs = this->cs_summary_.c_str();
680 bool success = this->gain_calibration_pref_.save(&this->gain_phase_);
682 if (success) {
683 this->using_saved_calibrations_ = true;
684 ESP_LOGI(TAG, "[CALIBRATION][%s] Gain calibration saved to memory.", cs);
685 } else {
686 this->using_saved_calibrations_ = false;
687 ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save gain calibration to memory!", cs);
688 }
689}
690
692 const char *cs = this->cs_summary_.c_str();
693 bool success = this->offset_pref_.save(&this->offset_phase_);
695 if (success) {
696 this->using_saved_calibrations_ = true;
697 this->restored_offset_calibration_ = true;
698 for (bool &phase : this->offset_calibration_mismatch_)
699 phase = false;
700 ESP_LOGI(TAG, "[CALIBRATION][%s] Offset calibration saved to memory.", cs);
701 } else {
702 this->using_saved_calibrations_ = false;
703 ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save offset calibration to memory!", cs);
704 }
705}
706
708 const char *cs = this->cs_summary_.c_str();
709 bool success = this->power_offset_pref_.save(&this->power_offset_phase_);
711 if (success) {
712 this->using_saved_calibrations_ = true;
714 for (bool &phase : this->power_offset_calibration_mismatch_)
715 phase = false;
716 ESP_LOGI(TAG, "[CALIBRATION][%s] Power offset calibration saved to memory.", cs);
717 } else {
718 this->using_saved_calibrations_ = false;
719 ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to save power offset calibration to memory!", cs);
720 }
721}
722
724 const char *cs = this->cs_summary_.c_str();
725 if (!this->enable_offset_calibration_) {
726 ESP_LOGW(TAG,
727 "[CALIBRATION][%s] Offset calibration is disabled! Enable it first with enable_offset_calibration: true",
728 cs);
729 return;
730 }
731
732 ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
733 ESP_LOGI(TAG, "[CALIBRATION][%s] ======================== Offset Calibration ========================", cs);
734 ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
735 ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
736 ESP_LOGI(TAG, "[CALIBRATION][%s] ------------------------------------------------------------------", cs);
737
738 for (uint8_t phase = 0; phase < 3; phase++) {
739 int16_t voltage_offset = calibrate_offset(phase, true);
740 int16_t current_offset = calibrate_offset(phase, false);
741
742 this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
743
744 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
745 current_offset);
746 }
747
748 ESP_LOGI(TAG, "[CALIBRATION][%s] ==================================================================\n", cs);
749
751}
752
754 const char *cs = this->cs_summary_.c_str();
755 if (!this->enable_offset_calibration_) {
756 ESP_LOGW(
757 TAG,
758 "[CALIBRATION][%s] Offset power calibration is disabled! Enable it first with enable_offset_calibration: true",
759 cs);
760 return;
761 }
762
763 ESP_LOGI(TAG, "[CALIBRATION][%s] ", cs);
764 ESP_LOGI(TAG, "[CALIBRATION][%s] ===================== Power Offset Calibration =====================", cs);
765 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
766 ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
767 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
768
769 for (uint8_t phase = 0; phase < 3; ++phase) {
770 int16_t active_offset = calibrate_power_offset(phase, false);
771 int16_t reactive_offset = calibrate_power_offset(phase, true);
772
773 this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
774
775 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
776 reactive_offset);
777 }
778 ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
779
781}
782
784 this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
785
786 for (int phase = 0; phase < 3; phase++) {
787 this->write16_(voltage_gain_registers[phase], this->gain_phase_[phase].voltage_gain);
788 this->write16_(current_gain_registers[phase], this->gain_phase_[phase].current_gain);
789 }
790
791 this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
792}
793
794void ATM90E32Component::write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset) {
795 // Save to runtime
796 this->offset_phase_[phase].voltage_offset_ = voltage_offset;
797 this->phase_[phase].voltage_offset_ = voltage_offset;
798
799 // Save to flash-storable struct
800 this->offset_phase_[phase].current_offset_ = current_offset;
801 this->phase_[phase].current_offset_ = current_offset;
802
803 // Write to registers
804 this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
805 this->write16_(voltage_offset_registers[phase], static_cast<uint16_t>(voltage_offset));
806 this->write16_(current_offset_registers[phase], static_cast<uint16_t>(current_offset));
807 this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
808}
809
810void ATM90E32Component::write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset) {
811 // Save to runtime
812 this->phase_[phase].active_power_offset_ = p_offset;
813 this->phase_[phase].reactive_power_offset_ = q_offset;
814
815 // Save to flash-storable struct
816 this->power_offset_phase_[phase].active_power_offset = p_offset;
817 this->power_offset_phase_[phase].reactive_power_offset = q_offset;
818
819 // Write to registers
820 this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x55AA);
821 this->write16_(this->power_offset_registers[phase], static_cast<uint16_t>(p_offset));
822 this->write16_(this->reactive_power_offset_registers[phase], static_cast<uint16_t>(q_offset));
823 this->write16_(ATM90E32_REGISTER_CFGREGACCEN, 0x0000);
824}
825
827 const char *cs = this->cs_summary_.c_str();
828 for (uint8_t i = 0; i < 3; ++i) {
831 this->gain_phase_[i] = this->config_gain_phase_[i];
832 }
833
834 if (this->gain_calibration_pref_.load(&this->gain_phase_)) {
835 bool all_zero = true;
836 bool same_as_config = true;
837 for (uint8_t phase = 0; phase < 3; ++phase) {
838 const auto &cfg = this->config_gain_phase_[phase];
839 const auto &saved = this->gain_phase_[phase];
840 if (saved.voltage_gain != 0 || saved.current_gain != 0)
841 all_zero = false;
842 if (saved.voltage_gain != cfg.voltage_gain || saved.current_gain != cfg.current_gain)
843 same_as_config = false;
844 }
845
846 if (!all_zero && !same_as_config) {
847 for (uint8_t phase = 0; phase < 3; ++phase) {
848 bool mismatch = false;
849 if (this->has_config_voltage_gain_[phase] &&
850 this->gain_phase_[phase].voltage_gain != this->config_gain_phase_[phase].voltage_gain)
851 mismatch = true;
852 if (this->has_config_current_gain_[phase] &&
853 this->gain_phase_[phase].current_gain != this->config_gain_phase_[phase].current_gain)
854 mismatch = true;
855 if (mismatch)
856 this->gain_calibration_mismatch_[phase] = true;
857 }
858
860
861 if (this->verify_gain_writes_()) {
862 this->using_saved_calibrations_ = true;
863 this->restored_gain_calibration_ = true;
864 return;
865 }
866
867 this->using_saved_calibrations_ = false;
868 ESP_LOGE(TAG, "[CALIBRATION][%s] Gain verification failed! Calibration may not be applied correctly.", cs);
869 }
870 }
871
872 this->using_saved_calibrations_ = false;
873 for (uint8_t i = 0; i < 3; ++i)
874 this->gain_phase_[i] = this->config_gain_phase_[i];
876
877 ESP_LOGW(TAG, "[CALIBRATION][%s] No stored gain calibrations found. Using config file values.", cs);
878}
879
881 const char *cs = this->cs_summary_.c_str();
882 for (uint8_t i = 0; i < 3; ++i)
883 this->config_offset_phase_[i] = this->offset_phase_[i];
884
885 bool have_data = this->offset_pref_.load(&this->offset_phase_);
886 bool all_zero = true;
887 if (have_data) {
888 for (auto &phase : this->offset_phase_) {
889 if (phase.voltage_offset_ != 0 || phase.current_offset_ != 0) {
890 all_zero = false;
891 break;
892 }
893 }
894 }
895
896 if (have_data && !all_zero) {
897 this->restored_offset_calibration_ = true;
898 for (uint8_t phase = 0; phase < 3; phase++) {
899 auto &offset = this->offset_phase_[phase];
900 bool mismatch = false;
901 if (this->has_config_voltage_offset_[phase] &&
902 offset.voltage_offset_ != this->config_offset_phase_[phase].voltage_offset_)
903 mismatch = true;
904 if (this->has_config_current_offset_[phase] &&
905 offset.current_offset_ != this->config_offset_phase_[phase].current_offset_)
906 mismatch = true;
907 if (mismatch)
908 this->offset_calibration_mismatch_[phase] = true;
909 }
910 } else {
911 for (uint8_t phase = 0; phase < 3; phase++)
912 this->offset_phase_[phase] = this->config_offset_phase_[phase];
913 ESP_LOGW(TAG, "[CALIBRATION][%s] No stored offset calibrations found. Using default values.", cs);
914 }
915
916 for (uint8_t phase = 0; phase < 3; phase++) {
917 write_offsets_to_registers_(phase, this->offset_phase_[phase].voltage_offset_,
918 this->offset_phase_[phase].current_offset_);
919 }
920}
921
923 const char *cs = this->cs_summary_.c_str();
924 for (uint8_t i = 0; i < 3; ++i)
926
927 bool have_data = this->power_offset_pref_.load(&this->power_offset_phase_);
928 bool all_zero = true;
929 if (have_data) {
930 for (auto &phase : this->power_offset_phase_) {
931 if (phase.active_power_offset != 0 || phase.reactive_power_offset != 0) {
932 all_zero = false;
933 break;
934 }
935 }
936 }
937
938 if (have_data && !all_zero) {
940 for (uint8_t phase = 0; phase < 3; ++phase) {
941 auto &offset = this->power_offset_phase_[phase];
942 bool mismatch = false;
943 if (this->has_config_active_power_offset_[phase] &&
944 offset.active_power_offset != this->config_power_offset_phase_[phase].active_power_offset)
945 mismatch = true;
946 if (this->has_config_reactive_power_offset_[phase] &&
947 offset.reactive_power_offset != this->config_power_offset_phase_[phase].reactive_power_offset)
948 mismatch = true;
949 if (mismatch)
950 this->power_offset_calibration_mismatch_[phase] = true;
951 }
952 } else {
953 for (uint8_t phase = 0; phase < 3; ++phase)
954 this->power_offset_phase_[phase] = this->config_power_offset_phase_[phase];
955 ESP_LOGW(TAG, "[CALIBRATION][%s] No stored power offsets found. Using default values.", cs);
956 }
957
958 for (uint8_t phase = 0; phase < 3; ++phase) {
959 write_power_offsets_to_registers_(phase, this->power_offset_phase_[phase].active_power_offset,
960 this->power_offset_phase_[phase].reactive_power_offset);
961 }
962}
963
965 const char *cs = this->cs_summary_.c_str();
966 if (!this->using_saved_calibrations_) {
967 ESP_LOGI(TAG, "[CALIBRATION][%s] No stored gain calibrations to clear. Current values:", cs);
968 ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
969 ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
970 ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
971 for (int phase = 0; phase < 3; phase++) {
972 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase,
973 this->gain_phase_[phase].voltage_gain, this->gain_phase_[phase].current_gain);
974 }
975 ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
976 return;
977 }
978
979 ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored gain calibrations and restoring config-defined values", cs);
980 ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
981 ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | voltage_gain | current_gain |", cs);
982 ESP_LOGI(TAG, "[CALIBRATION][%s] ----------------------------------------------------------", cs);
983
984 for (int phase = 0; phase < 3; phase++) {
985 uint16_t voltage_gain = this->phase_[phase].voltage_gain_;
986 uint16_t current_gain = this->phase_[phase].ct_gain_;
987
988 this->config_gain_phase_[phase].voltage_gain = voltage_gain;
989 this->config_gain_phase_[phase].current_gain = current_gain;
990 this->gain_phase_[phase].voltage_gain = voltage_gain;
991 this->gain_phase_[phase].current_gain = current_gain;
992
993 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6u | %6u |", cs, 'A' + phase, voltage_gain, current_gain);
994 }
995 ESP_LOGI(TAG, "[CALIBRATION][%s] ==========================================================\n", cs);
996
997 GainCalibration zero_gains[3]{{0, 0}, {0, 0}, {0, 0}};
998 bool success = this->gain_calibration_pref_.save(&zero_gains);
1000
1001 this->using_saved_calibrations_ = false;
1002 this->restored_gain_calibration_ = false;
1003 for (bool &phase : this->gain_calibration_mismatch_)
1004 phase = false;
1005
1006 if (!success) {
1007 ESP_LOGE(TAG, "[CALIBRATION][%s] Failed to clear gain calibrations!", cs);
1008 }
1009
1010 this->write_gains_to_registers_(); // Apply them to the chip immediately
1011}
1012
1014 const char *cs = this->cs_summary_.c_str();
1015 if (!this->restored_offset_calibration_) {
1016 ESP_LOGI(TAG, "[CALIBRATION][%s] No stored offset calibrations to clear. Current values:", cs);
1017 ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
1018 ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
1019 ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
1020 for (uint8_t phase = 0; phase < 3; phase++) {
1021 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
1022 this->offset_phase_[phase].voltage_offset_, this->offset_phase_[phase].current_offset_);
1023 }
1024 ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
1025 return;
1026 }
1027
1028 ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored offset calibrations and restoring config-defined values", cs);
1029 ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
1030 ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_voltage | offset_current |", cs);
1031 ESP_LOGI(TAG, "[CALIBRATION][%s] --------------------------------------------------------------", cs);
1032
1033 for (uint8_t phase = 0; phase < 3; phase++) {
1034 int16_t voltage_offset =
1035 this->has_config_voltage_offset_[phase] ? this->config_offset_phase_[phase].voltage_offset_ : 0;
1036 int16_t current_offset =
1037 this->has_config_current_offset_[phase] ? this->config_offset_phase_[phase].current_offset_ : 0;
1038 this->write_offsets_to_registers_(phase, voltage_offset, current_offset);
1039 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, voltage_offset,
1040 current_offset);
1041 }
1042 ESP_LOGI(TAG, "[CALIBRATION][%s] ==============================================================\n", cs);
1043
1044 OffsetCalibration zero_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
1045 this->offset_pref_.save(&zero_offsets); // Clear stored values in flash
1047
1048 this->restored_offset_calibration_ = false;
1049 for (bool &phase : this->offset_calibration_mismatch_)
1050 phase = false;
1051
1052 ESP_LOGI(TAG, "[CALIBRATION][%s] Offsets cleared.", cs);
1053}
1054
1056 const char *cs = this->cs_summary_.c_str();
1058 ESP_LOGI(TAG, "[CALIBRATION][%s] No stored power offsets to clear. Current values:", cs);
1059 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
1060 ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
1061 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
1062 for (uint8_t phase = 0; phase < 3; phase++) {
1063 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase,
1064 this->power_offset_phase_[phase].active_power_offset,
1065 this->power_offset_phase_[phase].reactive_power_offset);
1066 }
1067 ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
1068 return;
1069 }
1070
1071 ESP_LOGI(TAG, "[CALIBRATION][%s] Clearing stored power offsets and restoring config-defined values", cs);
1072 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
1073 ESP_LOGI(TAG, "[CALIBRATION][%s] | Phase | offset_active_power | offset_reactive_power |", cs);
1074 ESP_LOGI(TAG, "[CALIBRATION][%s] ---------------------------------------------------------------------", cs);
1075
1076 for (uint8_t phase = 0; phase < 3; phase++) {
1077 int16_t active_offset =
1079 int16_t reactive_offset = this->has_config_reactive_power_offset_[phase]
1081 : 0;
1082 this->write_power_offsets_to_registers_(phase, active_offset, reactive_offset);
1083 ESP_LOGI(TAG, "[CALIBRATION][%s] | %c | %6d | %6d |", cs, 'A' + phase, active_offset,
1084 reactive_offset);
1085 }
1086 ESP_LOGI(TAG, "[CALIBRATION][%s] =====================================================================\n", cs);
1087
1088 PowerOffsetCalibration zero_power_offsets[3]{{0, 0}, {0, 0}, {0, 0}};
1089 this->power_offset_pref_.save(&zero_power_offsets);
1091
1093 for (bool &phase : this->power_offset_calibration_mismatch_)
1094 phase = false;
1095
1096 ESP_LOGI(TAG, "[CALIBRATION][%s] Power offsets cleared.", cs);
1097}
1098
1099int16_t ATM90E32Component::calibrate_offset(uint8_t phase, bool voltage) {
1100 const uint8_t num_reads = 5;
1101 uint64_t total_value = 0;
1102
1103 for (uint8_t i = 0; i < num_reads; ++i) {
1104 uint32_t reading = voltage ? this->read32_(ATM90E32_REGISTER_URMS + phase, ATM90E32_REGISTER_URMSLSB + phase)
1105 : this->read32_(ATM90E32_REGISTER_IRMS + phase, ATM90E32_REGISTER_IRMSLSB + phase);
1106 total_value += reading;
1107 }
1108
1109 const uint32_t average_value = total_value / num_reads;
1110 const uint32_t shifted = average_value >> 7;
1111 const uint32_t offset = ~shifted + 1;
1112 return static_cast<int16_t>(offset); // Takes lower 16 bits
1113}
1114
1115int16_t ATM90E32Component::calibrate_power_offset(uint8_t phase, bool reactive) {
1116 const uint8_t num_reads = 5;
1117 int64_t total_value = 0;
1118
1119 for (uint8_t i = 0; i < num_reads; ++i) {
1120 int32_t reading = reactive ? this->read32_(ATM90E32_REGISTER_QMEAN + phase, ATM90E32_REGISTER_QMEANLSB + phase)
1121 : this->read32_(ATM90E32_REGISTER_PMEAN + phase, ATM90E32_REGISTER_PMEANLSB + phase);
1122 total_value += reading;
1123 }
1124
1125 int32_t average_value = total_value / num_reads;
1126 int32_t power_offset = -average_value;
1127 return static_cast<int16_t>(power_offset); // Takes the lower 16 bits
1128}
1129
1131 const char *cs = this->cs_summary_.c_str();
1132 bool success = true;
1133 for (uint8_t phase = 0; phase < 3; phase++) {
1134 uint16_t read_voltage = this->read16_(voltage_gain_registers[phase]);
1135 uint16_t read_current = this->read16_(current_gain_registers[phase]);
1136
1137 if (read_voltage != this->gain_phase_[phase].voltage_gain ||
1138 read_current != this->gain_phase_[phase].current_gain) {
1139 ESP_LOGE(TAG, "[CALIBRATION][%s] Mismatch detected for Phase %s!", cs, phase_labels[phase]);
1140 success = false;
1141 }
1142 }
1143 return success; // Return true if all writes were successful, false otherwise
1144}
1145
1146#ifdef USE_TEXT_SENSOR
1148 uint16_t state0 = this->read16_(ATM90E32_REGISTER_EMMSTATE0);
1149 uint16_t state1 = this->read16_(ATM90E32_REGISTER_EMMSTATE1);
1150
1151 for (int phase = 0; phase < 3; phase++) {
1152 std::string status;
1153
1154 if (state0 & over_voltage_flags[phase])
1155 status += "Over Voltage; ";
1156 if (state1 & voltage_sag_flags[phase])
1157 status += "Voltage Sag; ";
1158 if (state1 & phase_loss_flags[phase])
1159 status += "Phase Loss; ";
1160
1161 auto *sensor = this->phase_status_text_sensor_[phase];
1162 if (sensor == nullptr)
1163 continue;
1164
1165 if (!status.empty()) {
1166 status.pop_back(); // remove space
1167 status.pop_back(); // remove semicolon
1168 ESP_LOGW(TAG, "%s: %s", sensor->get_name().c_str(), status.c_str());
1169 sensor->publish_state(status);
1170 } else {
1171 sensor->publish_state("Okay");
1172 }
1173 }
1174}
1175
1177 uint16_t state1 = this->read16_(ATM90E32_REGISTER_EMMSTATE1);
1178
1179 std::string freq_status;
1180
1181 if (state1 & ATM90E32_STATUS_S1_FREQHIST) {
1182 freq_status = "HIGH";
1183 } else if (state1 & ATM90E32_STATUS_S1_FREQLOST) {
1184 freq_status = "LOW";
1185 } else {
1186 freq_status = "Normal";
1187 }
1188 if (this->freq_status_text_sensor_ != nullptr) {
1189 if (freq_status == "Normal") {
1190 ESP_LOGD(TAG, "Frequency status: %s", freq_status.c_str());
1191 } else {
1192 ESP_LOGW(TAG, "Frequency status: %s", freq_status.c_str());
1193 }
1194 this->freq_status_text_sensor_->publish_state(freq_status);
1195 }
1196}
1197
1199 constexpr float max_current_threshold = 65.53f;
1200
1201 for (uint8_t phase = 0; phase < 3; phase++) {
1202 float current_val =
1203 this->phase_[phase].current_sensor_ != nullptr ? this->phase_[phase].current_sensor_->state : 0.0f;
1204
1205 if (current_val > max_current_threshold) {
1206 ESP_LOGW(TAG, "Over current detected on Phase %c: %.2f A", 'A' + phase, current_val);
1207 ESP_LOGW(TAG, "You may need to half your gain_ct: value & multiply the current and power values by 2");
1208 if (this->phase_status_text_sensor_[phase] != nullptr) {
1209 this->phase_status_text_sensor_[phase]->publish_state("Over Current; ");
1210 }
1211 }
1212 }
1213}
1214#endif
1215
1216uint16_t ATM90E32Component::calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier) {
1217 // this assumes that 60Hz electrical systems use 120V mains,
1218 // which is usually, but not always the case
1219 float nominal_voltage = (line_freq == 60) ? 120.0f : 220.0f;
1220 float target_voltage = nominal_voltage * multiplier;
1221
1222 float peak_01v = target_voltage * 100.0f * std::numbers::sqrt2_v<float>; // convert RMS → peak, scale to 0.01V
1223 float divider = (2.0f * ugain) / 32768.0f;
1224
1225 float threshold = peak_01v / divider;
1226
1227 return static_cast<uint16_t>(threshold);
1228}
1229
1230bool ATM90E32Component::validate_spi_read_(uint16_t expected, const char *context) {
1231 uint16_t last = this->read16_(ATM90E32_REGISTER_LASTSPIDATA);
1232 if (last != expected) {
1233 if (context != nullptr) {
1234 ESP_LOGW(TAG, "[%s] SPI read mismatch: expected 0x%04X, got 0x%04X", context, expected, last);
1235 } else {
1236 ESP_LOGW(TAG, "SPI read mismatch: expected 0x%04X, got 0x%04X", expected, last);
1237 }
1238 return false;
1239 }
1240 return true;
1241}
1242
1243} // namespace atm90e32
1244} // namespace esphome
uint8_t status
Definition bl0942.h:8
virtual void mark_failed()
Mark this component as failed.
bool is_failed() const
void status_set_warning(const char *message=nullptr)
void status_clear_warning()
bool save(const T *src)
Definition preferences.h:21
virtual bool sync()=0
Commit pending writes to flash.
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
virtual std::string dump_summary() const =0
float get_local_phase_reactive_power_(uint8_t phase)
Definition atm90e32.cpp:445
float get_phase_forward_active_energy_(uint8_t phase)
Definition atm90e32.cpp:528
float get_phase_current_avg_(uint8_t phase)
Definition atm90e32.cpp:487
float get_local_phase_apparent_power_(uint8_t phase)
Definition atm90e32.cpp:447
void write16_(uint16_t a_register, uint16_t val, bool validate=true)
Definition atm90e32.cpp:424
text_sensor::TextSensor * freq_status_text_sensor_
Definition atm90e32.h:256
ESPPreferenceObject power_offset_pref_
Definition atm90e32.h:249
const uint16_t voltage_gain_registers[3]
Definition atm90e32.h:24
float get_phase_voltage_avg_(uint8_t phase)
Definition atm90e32.cpp:473
void write_power_offsets_to_registers_(uint8_t phase, int16_t p_offset, int16_t q_offset)
Definition atm90e32.cpp:810
const uint16_t current_gain_registers[3]
Definition atm90e32.h:26
float get_reference_voltage(uint8_t phase)
Definition atm90e32.h:110
struct esphome::atm90e32::ATM90E32Component::GainCalibration gain_phase_[3]
const uint16_t current_offset_registers[3]
Definition atm90e32.h:30
static const uint8_t PHASEB
Definition atm90e32.h:20
float get_phase_reverse_active_energy_(uint8_t phase)
Definition atm90e32.cpp:539
float get_local_phase_harmonic_active_power_(uint8_t phase)
Definition atm90e32.cpp:461
float get_phase_angle_(uint8_t phase)
Definition atm90e32.cpp:555
float get_local_phase_current_(uint8_t phase)
Definition atm90e32.cpp:441
bool validate_spi_read_(uint16_t expected, const char *context=nullptr)
const uint16_t reactive_power_offset_registers[3]
Definition atm90e32.h:34
const uint16_t over_voltage_flags[3]
Definition atm90e32.h:36
float get_phase_voltage_(uint8_t phase)
Definition atm90e32.cpp:467
GainCalibration config_gain_phase_[3]
Definition atm90e32.h:239
int16_t calibrate_offset(uint8_t phase, bool voltage)
float get_local_phase_reverse_active_energy_(uint8_t phase)
Definition atm90e32.cpp:455
float get_local_phase_forward_active_energy_(uint8_t phase)
Definition atm90e32.cpp:451
OffsetCalibration config_offset_phase_[3]
Definition atm90e32.h:225
struct esphome::atm90e32::ATM90E32Component::OffsetCalibration offset_phase_[3]
uint16_t calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier)
float get_local_phase_power_factor_(uint8_t phase)
Definition atm90e32.cpp:449
float get_phase_reactive_power_(uint8_t phase)
Definition atm90e32.cpp:512
const uint16_t phase_loss_flags[3]
Definition atm90e32.h:40
float get_phase_apparent_power_(uint8_t phase)
Definition atm90e32.cpp:517
float get_local_phase_voltage_(uint8_t phase)
Definition atm90e32.cpp:439
uint16_t read16_transaction_(uint16_t a_register)
Definition atm90e32.cpp:385
ESPPreferenceObject gain_calibration_pref_
Definition atm90e32.h:250
void write_offsets_to_registers_(uint8_t phase, int16_t voltage_offset, int16_t current_offset)
Definition atm90e32.cpp:794
static const uint8_t PHASEA
Definition atm90e32.h:19
struct esphome::atm90e32::ATM90E32Component::PowerOffsetCalibration power_offset_phase_[3]
float get_reference_current(uint8_t phase)
Definition atm90e32.h:117
float get_phase_peak_current_(uint8_t phase)
Definition atm90e32.cpp:560
float get_phase_harmonic_active_power_(uint8_t phase)
Definition atm90e32.cpp:550
const uint16_t voltage_sag_flags[3]
Definition atm90e32.h:38
float get_phase_active_power_(uint8_t phase)
Definition atm90e32.cpp:507
PowerOffsetCalibration config_power_offset_phase_[3]
Definition atm90e32.h:232
static const uint8_t PHASEC
Definition atm90e32.h:21
const uint16_t power_offset_registers[3]
Definition atm90e32.h:32
float get_setup_priority() const override
Definition atm90e32.cpp:380
uint16_t read16_(uint16_t a_register)
Definition atm90e32.cpp:395
int read32_(uint16_t addr_h, uint16_t addr_l)
Definition atm90e32.cpp:405
sensor::Sensor * chip_temperature_sensor_
Definition atm90e32.h:258
float get_phase_power_factor_(uint8_t phase)
Definition atm90e32.cpp:522
float get_local_phase_angle_(uint8_t phase)
Definition atm90e32.cpp:459
const uint16_t voltage_offset_registers[3]
Definition atm90e32.h:28
text_sensor::TextSensor * phase_status_text_sensor_[3]
Definition atm90e32.h:255
float get_phase_current_(uint8_t phase)
Definition atm90e32.cpp:501
float get_local_phase_peak_current_(uint8_t phase)
Definition atm90e32.cpp:465
void set_publish_interval_flag_(bool flag)
Definition atm90e32.h:173
int16_t calibrate_power_offset(uint8_t phase, bool reactive)
struct esphome::atm90e32::ATM90E32Component::ATM90E32Phase phase_[3]
ESPPreferenceObject offset_pref_
Definition atm90e32.h:248
float get_local_phase_active_power_(uint8_t phase)
Definition atm90e32.cpp:443
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:45
float state
This member variable stores the last state that has passed through all filters.
Definition sensor.h:133
void publish_state(const std::string &state)
mopeka_std_values val[4]
const float IO
For components that represent GPIO pins like PCF8573.
Definition component.cpp:48
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
uint32_t fnv1_hash(const std::string &str)
Calculate a FNV-1 hash of str.
Definition helpers.cpp:134
ESPPreferences * global_preferences
void IRAM_ATTR HOT delay_microseconds_safe(uint32_t us)
Delay for the given amount of microseconds, possibly yielding to other processes during the wait.
Definition helpers.cpp:611
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:173
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29