ESPHome  2025.2.0
cse7766.cpp
Go to the documentation of this file.
1 #include "cse7766.h"
2 #include "esphome/core/log.h"
3 
4 namespace esphome {
5 namespace cse7766 {
6 
7 static const char *const TAG = "cse7766";
8 
10  const uint32_t now = millis();
11  if (now - this->last_transmission_ >= 500) {
12  // last transmission too long ago. Reset RX index.
13  this->raw_data_index_ = 0;
14  }
15 
16  if (this->available() == 0) {
17  return;
18  }
19 
20  this->last_transmission_ = now;
21  while (this->available() != 0) {
22  this->read_byte(&this->raw_data_[this->raw_data_index_]);
23  if (!this->check_byte_()) {
24  this->raw_data_index_ = 0;
25  this->status_set_warning();
26  continue;
27  }
28 
29  if (this->raw_data_index_ == 23) {
30  this->parse_data_();
31  this->status_clear_warning();
32  }
33 
34  this->raw_data_index_ = (this->raw_data_index_ + 1) % 24;
35  }
36 }
38 
40  uint8_t index = this->raw_data_index_;
41  uint8_t byte = this->raw_data_[index];
42  if (index == 0) {
43  return (byte == 0x55) || ((byte & 0xF0) == 0xF0) || (byte == 0xAA);
44  }
45 
46  if (index == 1) {
47  if (byte != 0x5A) {
48  ESP_LOGV(TAG, "Invalid Header 2 Start: 0x%02X!", byte);
49  return false;
50  }
51  return true;
52  }
53 
54  if (index == 23) {
55  uint8_t checksum = 0;
56  for (uint8_t i = 2; i < 23; i++) {
57  checksum += this->raw_data_[i];
58  }
59 
60  if (checksum != this->raw_data_[23]) {
61  ESP_LOGW(TAG, "Invalid checksum from CSE7766: 0x%02X != 0x%02X", checksum, this->raw_data_[23]);
62  return false;
63  }
64  return true;
65  }
66 
67  return true;
68 }
70 #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
71  {
72  std::string s = format_hex_pretty(this->raw_data_, sizeof(this->raw_data_));
73  ESP_LOGVV(TAG, "Raw data: %s", s.c_str());
74  }
75 #endif
76 
77  // Parse header
78  uint8_t header1 = this->raw_data_[0];
79 
80  if (header1 == 0xAA) {
81  ESP_LOGE(TAG, "CSE7766 not calibrated!");
82  return;
83  }
84 
85  bool power_cycle_exceeds_range = false;
86  if ((header1 & 0xF0) == 0xF0) {
87  if (header1 & 0xD) {
88  ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1);
89  if (header1 & (1 << 3)) {
90  ESP_LOGE(TAG, " Voltage cycle exceeds range.");
91  }
92  if (header1 & (1 << 2)) {
93  ESP_LOGE(TAG, " Current cycle exceeds range.");
94  }
95  if (header1 & (1 << 0)) {
96  ESP_LOGE(TAG, " Coefficient storage area is abnormal.");
97  }
98 
99  // Datasheet: voltage or current cycle exceeding range means invalid values
100  return;
101  }
102 
103  power_cycle_exceeds_range = header1 & (1 << 1);
104  }
105 
106  // Parse data frame
107  uint32_t voltage_coeff = this->get_24_bit_uint_(2);
108  uint32_t voltage_cycle = this->get_24_bit_uint_(5);
109  uint32_t current_coeff = this->get_24_bit_uint_(8);
110  uint32_t current_cycle = this->get_24_bit_uint_(11);
111  uint32_t power_coeff = this->get_24_bit_uint_(14);
112  uint32_t power_cycle = this->get_24_bit_uint_(17);
113  uint8_t adj = this->raw_data_[20];
114  uint16_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
115 
116  bool have_power = adj & 0x10;
117  bool have_current = adj & 0x20;
118  bool have_voltage = adj & 0x40;
119 
120  float voltage = 0.0f;
121  if (have_voltage) {
122  voltage = voltage_coeff / float(voltage_cycle);
123  if (this->voltage_sensor_ != nullptr) {
124  this->voltage_sensor_->publish_state(voltage);
125  }
126  }
127 
128  float energy = 0.0;
129  if (this->energy_sensor_ != nullptr) {
130  if (this->cf_pulses_last_ == 0 && !this->energy_sensor_->has_state()) {
131  this->cf_pulses_last_ = cf_pulses;
132  }
133  uint16_t cf_diff = cf_pulses - this->cf_pulses_last_;
134  this->cf_pulses_total_ += cf_diff;
135  this->cf_pulses_last_ = cf_pulses;
136  energy = this->cf_pulses_total_ * float(power_coeff) / 1000000.0f / 3600.0f;
137  this->energy_sensor_->publish_state(energy);
138  }
139 
140  float power = 0.0f;
141  if (power_cycle_exceeds_range) {
142  // Datasheet: power cycle exceeding range means active power is 0
143  have_power = true;
144  if (this->power_sensor_ != nullptr) {
145  this->power_sensor_->publish_state(0.0f);
146  }
147  } else if (have_power) {
148  power = power_coeff / float(power_cycle);
149  if (this->power_sensor_ != nullptr) {
150  this->power_sensor_->publish_state(power);
151  }
152  }
153 
154  float current = 0.0f;
155  float calculated_current = 0.0f;
156  if (have_current) {
157  // Assumption: if we don't have power measurement, then current is likely below 50mA
158  if (have_power && voltage > 1.0f) {
159  calculated_current = power / voltage;
160  }
161  // Datasheet: minimum measured current is 50mA
162  if (calculated_current > 0.05f) {
163  current = current_coeff / float(current_cycle);
164  }
165  if (this->current_sensor_ != nullptr) {
166  this->current_sensor_->publish_state(current);
167  }
168  }
169 
170  if (have_voltage && have_current) {
171  const float apparent_power = voltage * current;
172  if (this->apparent_power_sensor_ != nullptr) {
173  this->apparent_power_sensor_->publish_state(apparent_power);
174  }
175  if (have_power && this->reactive_power_sensor_ != nullptr) {
176  const float reactive_power = apparent_power - power;
177  if (reactive_power < 0.0f) {
178  ESP_LOGD(TAG, "Impossible reactive power: %.4f is negative", reactive_power);
180  } else {
181  this->reactive_power_sensor_->publish_state(reactive_power);
182  }
183  }
184  if (this->power_factor_sensor_ != nullptr && (have_power || power_cycle_exceeds_range)) {
185  float pf = NAN;
186  if (apparent_power > 0) {
187  pf = power / apparent_power;
188  if (pf < 0 || pf > 1) {
189  ESP_LOGD(TAG, "Impossible power factor: %.4f not in interval [0, 1]", pf);
190  pf = NAN;
191  }
192  } else if (apparent_power == 0 && power == 0) {
193  // No load, report ideal power factor
194  pf = 1.0f;
195  } else if (current == 0 && calculated_current <= 0.05f) {
196  // Datasheet: minimum measured current is 50mA
197  ESP_LOGV(TAG, "Can't calculate power factor (current below minimum for CSE7766)");
198  } else {
199  ESP_LOGW(TAG, "Can't calculate power factor from P = %.4f W, S = %.4f VA", power, apparent_power);
200  }
202  }
203  }
204 
205 #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
206  {
207  std::string buf = "Parsed:";
208  if (have_voltage) {
209  buf += str_sprintf(" V=%fV", voltage);
210  }
211  if (have_current) {
212  buf += str_sprintf(" I=%fmA (~%fmA)", current * 1000.0f, calculated_current * 1000.0f);
213  }
214  if (have_power) {
215  buf += str_sprintf(" P=%fW", power);
216  }
217  if (energy != 0.0f) {
218  buf += str_sprintf(" E=%fkWh (%u)", energy, cf_pulses);
219  }
220  ESP_LOGVV(TAG, "%s", buf.c_str());
221  }
222 #endif
223 }
224 
225 uint32_t CSE7766Component::get_24_bit_uint_(uint8_t start_index) {
226  return (uint32_t(this->raw_data_[start_index]) << 16) | (uint32_t(this->raw_data_[start_index + 1]) << 8) |
227  uint32_t(this->raw_data_[start_index + 2]);
228 }
229 
231  ESP_LOGCONFIG(TAG, "CSE7766:");
232  LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
233  LOG_SENSOR(" ", "Current", this->current_sensor_);
234  LOG_SENSOR(" ", "Power", this->power_sensor_);
235  LOG_SENSOR(" ", "Energy", this->energy_sensor_);
236  LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_);
237  LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_);
238  LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_);
240 }
241 
242 } // namespace cse7766
243 } // namespace esphome
sensor::Sensor * power_sensor_
Definition: cse7766.h:38
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
std::string format_hex_pretty(const uint8_t *data, size_t length)
Format the byte array data of length len in pretty-printed, human-readable hex.
Definition: helpers.cpp:373
void status_set_warning(const char *message="unspecified")
Definition: component.cpp:151
sensor::Sensor * power_factor_sensor_
Definition: cse7766.h:42
uint8_t checksum
Definition: bl0906.h:210
uint32_t get_24_bit_uint_(uint8_t start_index)
Definition: cse7766.cpp:225
sensor::Sensor * energy_sensor_
Definition: cse7766.h:39
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits=1, UARTParityOptions parity=UART_CONFIG_PARITY_NONE, uint8_t data_bits=8)
Check that the configuration of the UART bus matches the provided values and otherwise print a warnin...
Definition: uart.cpp:13
bool read_byte(uint8_t *data)
Definition: uart.h:29
std::string str_sprintf(const char *fmt,...)
Definition: helpers.cpp:324
void status_clear_warning()
Definition: component.cpp:166
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
float get_setup_priority() const override
Definition: cse7766.cpp:37
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
bool has_state() const
Return whether this sensor has gotten a full state (that passed through all filters) yet...
Definition: sensor.cpp:97
sensor::Sensor * current_sensor_
Definition: cse7766.h:37
sensor::Sensor * apparent_power_sensor_
Definition: cse7766.h:40
sensor::Sensor * voltage_sensor_
Definition: cse7766.h:36
sensor::Sensor * reactive_power_sensor_
Definition: cse7766.h:41