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