ESPHome 2026.3.0-dev
Loading...
Searching...
No Matches
cse7766.cpp
Go to the documentation of this file.
1#include "cse7766.h"
4#include "esphome/core/log.h"
5
6namespace esphome::cse7766 {
7
8static const char *const TAG = "cse7766";
9
11 const uint32_t now = App.get_loop_component_start_time();
12 if (now - this->last_transmission_ >= 500) {
13 // last transmission too long ago. Reset RX index.
14 this->raw_data_index_ = 0;
15 }
16
17 // Early return prevents updating last_transmission_ when no data is available.
18 size_t avail = this->available();
19 if (avail == 0) {
20 return;
21 }
22
23 this->last_transmission_ = now;
24
25 // Read all available bytes in batches to reduce UART call overhead.
26 // At 4800 baud (~480 bytes/sec) with ~122 Hz loop rate, typically ~4 bytes per call.
27 uint8_t buf[CSE7766_RAW_DATA_SIZE];
28 while (avail > 0) {
29 size_t to_read = std::min(avail, sizeof(buf));
30 if (!this->read_array(buf, to_read)) {
31 break;
32 }
33 avail -= to_read;
34
35 for (size_t i = 0; i < to_read; i++) {
36 this->raw_data_[this->raw_data_index_] = buf[i];
37 if (!this->check_byte_()) {
38 this->raw_data_index_ = 0;
39 this->status_set_warning();
40 continue;
41 }
42
43 if (this->raw_data_index_ == CSE7766_RAW_DATA_SIZE - 1) {
44 this->parse_data_();
46 }
47
48 this->raw_data_index_ = (this->raw_data_index_ + 1) % CSE7766_RAW_DATA_SIZE;
49 }
50 }
51}
52
54 uint8_t index = this->raw_data_index_;
55 uint8_t byte = this->raw_data_[index];
56 if (index == 0) {
57 return (byte == 0x55) || ((byte & 0xF0) == 0xF0) || (byte == 0xAA);
58 }
59
60 if (index == 1) {
61 if (byte != 0x5A) {
62 ESP_LOGV(TAG, "Invalid Header 2 Start: 0x%02X!", byte);
63 return false;
64 }
65 return true;
66 }
67
68 if (index == CSE7766_RAW_DATA_SIZE - 1) {
69 uint8_t checksum = 0;
70 for (uint8_t i = 2; i < CSE7766_RAW_DATA_SIZE - 1; i++) {
71 checksum += this->raw_data_[i];
72 }
73
74 if (checksum != this->raw_data_[CSE7766_RAW_DATA_SIZE - 1]) {
75 ESP_LOGW(TAG, "Invalid checksum from CSE7766: 0x%02X != 0x%02X", checksum,
76 this->raw_data_[CSE7766_RAW_DATA_SIZE - 1]);
77 return false;
78 }
79 return true;
80 }
81
82 return true;
83}
85#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
86 {
87 char hex_buf[format_hex_pretty_size(CSE7766_RAW_DATA_SIZE)];
88 ESP_LOGVV(TAG, "Raw data: %s", format_hex_pretty_to(hex_buf, this->raw_data_, sizeof(this->raw_data_)));
89 }
90#endif
91
92 // Parse header
93 uint8_t header1 = this->raw_data_[0];
94
95 if (header1 == 0xAA) {
96 ESP_LOGE(TAG, "CSE7766 not calibrated!");
97 return;
98 }
99
100 bool power_cycle_exceeds_range = false;
101 if ((header1 & 0xF0) == 0xF0) {
102 if (header1 & 0xD) {
103 ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1);
104 if (header1 & (1 << 3)) {
105 ESP_LOGE(TAG, " Voltage cycle exceeds range.");
106 }
107 if (header1 & (1 << 2)) {
108 ESP_LOGE(TAG, " Current cycle exceeds range.");
109 }
110 if (header1 & (1 << 0)) {
111 ESP_LOGE(TAG, " Coefficient storage area is abnormal.");
112 }
113
114 // Datasheet: voltage or current cycle exceeding range means invalid values
115 return;
116 }
117
118 power_cycle_exceeds_range = header1 & (1 << 1);
119 }
120
121 // Parse data frame
122 uint32_t voltage_coeff = this->get_24_bit_uint_(2);
123 uint32_t voltage_cycle = this->get_24_bit_uint_(5);
124 uint32_t current_coeff = this->get_24_bit_uint_(8);
125 uint32_t current_cycle = this->get_24_bit_uint_(11);
126 uint32_t power_coeff = this->get_24_bit_uint_(14);
127 uint32_t power_cycle = this->get_24_bit_uint_(17);
128 uint8_t adj = this->raw_data_[20];
129 uint16_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
130
131 bool have_power = adj & 0x10;
132 bool have_current = adj & 0x20;
133 bool have_voltage = adj & 0x40;
134
135 float voltage = 0.0f;
136 if (have_voltage) {
137 voltage = voltage_coeff / float(voltage_cycle);
138 if (this->voltage_sensor_ != nullptr) {
139 this->voltage_sensor_->publish_state(voltage);
140 }
141 }
142
143 float energy = 0.0;
144 if (this->energy_sensor_ != nullptr) {
145 if (this->cf_pulses_last_ == 0 && !this->energy_sensor_->has_state()) {
146 this->cf_pulses_last_ = cf_pulses;
147 }
148 uint16_t cf_diff = cf_pulses - this->cf_pulses_last_;
149 this->cf_pulses_total_ += cf_diff;
150 this->cf_pulses_last_ = cf_pulses;
151 energy = this->cf_pulses_total_ * float(power_coeff) / 1000000.0f / 3600.0f;
152 this->energy_sensor_->publish_state(energy);
153 }
154
155 float power = 0.0f;
156 if (power_cycle_exceeds_range) {
157 // Datasheet: power cycle exceeding range means active power is 0
158 have_power = true;
159 if (this->power_sensor_ != nullptr) {
160 this->power_sensor_->publish_state(0.0f);
161 }
162 } else if (have_power) {
163 power = power_coeff / float(power_cycle);
164 if (this->power_sensor_ != nullptr) {
165 this->power_sensor_->publish_state(power);
166 }
167 } else if (this->power_sensor_ != nullptr) {
168 // No valid power measurement from chip - publish 0W to avoid stale readings
169 // This typically happens when current is below the measurable threshold (~50mA)
170 this->power_sensor_->publish_state(0.0f);
171 }
172
173 float current = 0.0f;
174 float calculated_current = 0.0f;
175 if (have_current) {
176 // Assumption: if we don't have power measurement, then current is likely below 50mA
177 if (have_power && voltage > 1.0f) {
178 calculated_current = power / voltage;
179 }
180 // Datasheet: minimum measured current is 50mA
181 if (calculated_current > 0.05f) {
182 current = current_coeff / float(current_cycle);
183 }
184 if (this->current_sensor_ != nullptr) {
185 this->current_sensor_->publish_state(current);
186 }
187 }
188
189 if (have_voltage && have_current) {
190 const float apparent_power = voltage * current;
191 if (this->apparent_power_sensor_ != nullptr) {
192 this->apparent_power_sensor_->publish_state(apparent_power);
193 }
194 if (have_power && this->reactive_power_sensor_ != nullptr) {
195 const float reactive_power = apparent_power - power;
196 if (reactive_power < 0.0f) {
197 ESP_LOGD(TAG, "Impossible reactive power: %.4f is negative", reactive_power);
199 } else {
200 this->reactive_power_sensor_->publish_state(reactive_power);
201 }
202 }
203 if (this->power_factor_sensor_ != nullptr && (have_power || power_cycle_exceeds_range)) {
204 float pf = NAN;
205 if (apparent_power > 0) {
206 pf = power / apparent_power;
207 if (pf < 0 || pf > 1) {
208 ESP_LOGD(TAG, "Impossible power factor: %.4f not in interval [0, 1]", pf);
209 pf = NAN;
210 }
211 } else if (apparent_power == 0 && power == 0) {
212 // No load, report ideal power factor
213 pf = 1.0f;
214 } else if (current == 0 && calculated_current <= 0.05f) {
215 // Datasheet: minimum measured current is 50mA
216 ESP_LOGV(TAG, "Can't calculate power factor (current below minimum for CSE7766)");
217 } else {
218 ESP_LOGW(TAG, "Can't calculate power factor from P = %.4f W, S = %.4f VA", power, apparent_power);
219 }
221 }
222 }
223
224#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
225 {
226 // Buffer: 7 + 15 + 33 + 15 + 25 = 95 chars max + null, rounded to 128 for safety margin.
227 // Float sizes with %.4f can be up to 11 chars for large values (e.g., 999999.9999).
228 char buf[128];
229 size_t pos = buf_append_printf(buf, sizeof(buf), 0, "Parsed:");
230 if (have_voltage) {
231 pos = buf_append_printf(buf, sizeof(buf), pos, " V=%.4fV", voltage);
232 }
233 if (have_current) {
234 pos = buf_append_printf(buf, sizeof(buf), pos, " I=%.4fmA (~%.4fmA)", current * 1000.0f,
235 calculated_current * 1000.0f);
236 }
237 if (have_power) {
238 pos = buf_append_printf(buf, sizeof(buf), pos, " P=%.4fW", power);
239 }
240 if (energy != 0.0f) {
241 buf_append_printf(buf, sizeof(buf), pos, " E=%.4fkWh (%u)", energy, cf_pulses);
242 }
243 ESP_LOGVV(TAG, "%s", buf);
244 }
245#endif
246}
247
249 ESP_LOGCONFIG(TAG, "CSE7766:");
250 LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
251 LOG_SENSOR(" ", "Current", this->current_sensor_);
252 LOG_SENSOR(" ", "Power", this->power_sensor_);
253 LOG_SENSOR(" ", "Energy", this->energy_sensor_);
254 LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_);
255 LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_);
256 LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_);
258}
259
260} // namespace esphome::cse7766
uint8_t checksum
Definition bl0906.h:3
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
void status_set_warning(const char *message=nullptr)
void status_clear_warning()
bool has_state() const
sensor::Sensor * current_sensor_
Definition cse7766.h:41
sensor::Sensor * voltage_sensor_
Definition cse7766.h:40
sensor::Sensor * reactive_power_sensor_
Definition cse7766.h:45
uint32_t get_24_bit_uint_(uint8_t start_index) const
Definition cse7766.h:32
sensor::Sensor * apparent_power_sensor_
Definition cse7766.h:44
sensor::Sensor * power_sensor_
Definition cse7766.h:42
uint8_t raw_data_[CSE7766_RAW_DATA_SIZE]
Definition cse7766.h:37
sensor::Sensor * power_factor_sensor_
Definition cse7766.h:46
sensor::Sensor * energy_sensor_
Definition cse7766.h:43
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
optional< std::array< uint8_t, N > > read_array()
Definition uart.h:38
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:16
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
Definition helpers.cpp:353
size_t size_t pos
Definition helpers.h:854
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
Definition helpers.h:1103
Application App
Global storage of Application pointer - only one Application can exist.