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