ESPHome 2026.1.5
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 {
7namespace cse7766 {
8
9static const char *const TAG = "cse7766";
10static constexpr size_t CSE7766_RAW_DATA_SIZE = 24;
11
13 const uint32_t now = App.get_loop_component_start_time();
14 if (now - this->last_transmission_ >= 500) {
15 // last transmission too long ago. Reset RX index.
16 this->raw_data_index_ = 0;
17 }
18
19 if (this->available() == 0) {
20 return;
21 }
22
23 this->last_transmission_ = now;
24 while (this->available() != 0) {
25 this->read_byte(&this->raw_data_[this->raw_data_index_]);
26 if (!this->check_byte_()) {
27 this->raw_data_index_ = 0;
28 this->status_set_warning();
29 continue;
30 }
31
32 if (this->raw_data_index_ == 23) {
33 this->parse_data_();
35 }
36
37 this->raw_data_index_ = (this->raw_data_index_ + 1) % 24;
38 }
39}
41
43 uint8_t index = this->raw_data_index_;
44 uint8_t byte = this->raw_data_[index];
45 if (index == 0) {
46 return (byte == 0x55) || ((byte & 0xF0) == 0xF0) || (byte == 0xAA);
47 }
48
49 if (index == 1) {
50 if (byte != 0x5A) {
51 ESP_LOGV(TAG, "Invalid Header 2 Start: 0x%02X!", byte);
52 return false;
53 }
54 return true;
55 }
56
57 if (index == 23) {
58 uint8_t checksum = 0;
59 for (uint8_t i = 2; i < 23; i++) {
60 checksum += this->raw_data_[i];
61 }
62
63 if (checksum != this->raw_data_[23]) {
64 ESP_LOGW(TAG, "Invalid checksum from CSE7766: 0x%02X != 0x%02X", checksum, this->raw_data_[23]);
65 return false;
66 }
67 return true;
68 }
69
70 return true;
71}
73#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
74 {
75 char hex_buf[format_hex_pretty_size(CSE7766_RAW_DATA_SIZE)];
76 ESP_LOGVV(TAG, "Raw data: %s", format_hex_pretty_to(hex_buf, this->raw_data_, sizeof(this->raw_data_)));
77 }
78#endif
79
80 // Parse header
81 uint8_t header1 = this->raw_data_[0];
82
83 if (header1 == 0xAA) {
84 ESP_LOGE(TAG, "CSE7766 not calibrated!");
85 return;
86 }
87
88 bool power_cycle_exceeds_range = false;
89 if ((header1 & 0xF0) == 0xF0) {
90 if (header1 & 0xD) {
91 ESP_LOGE(TAG, "CSE7766 reports abnormal external circuit or chip damage: (0x%02X)", header1);
92 if (header1 & (1 << 3)) {
93 ESP_LOGE(TAG, " Voltage cycle exceeds range.");
94 }
95 if (header1 & (1 << 2)) {
96 ESP_LOGE(TAG, " Current cycle exceeds range.");
97 }
98 if (header1 & (1 << 0)) {
99 ESP_LOGE(TAG, " Coefficient storage area is abnormal.");
100 }
101
102 // Datasheet: voltage or current cycle exceeding range means invalid values
103 return;
104 }
105
106 power_cycle_exceeds_range = header1 & (1 << 1);
107 }
108
109 // Parse data frame
110 uint32_t voltage_coeff = this->get_24_bit_uint_(2);
111 uint32_t voltage_cycle = this->get_24_bit_uint_(5);
112 uint32_t current_coeff = this->get_24_bit_uint_(8);
113 uint32_t current_cycle = this->get_24_bit_uint_(11);
114 uint32_t power_coeff = this->get_24_bit_uint_(14);
115 uint32_t power_cycle = this->get_24_bit_uint_(17);
116 uint8_t adj = this->raw_data_[20];
117 uint16_t cf_pulses = (this->raw_data_[21] << 8) + this->raw_data_[22];
118
119 bool have_power = adj & 0x10;
120 bool have_current = adj & 0x20;
121 bool have_voltage = adj & 0x40;
122
123 float voltage = 0.0f;
124 if (have_voltage) {
125 voltage = voltage_coeff / float(voltage_cycle);
126 if (this->voltage_sensor_ != nullptr) {
127 this->voltage_sensor_->publish_state(voltage);
128 }
129 }
130
131 float energy = 0.0;
132 if (this->energy_sensor_ != nullptr) {
133 if (this->cf_pulses_last_ == 0 && !this->energy_sensor_->has_state()) {
134 this->cf_pulses_last_ = cf_pulses;
135 }
136 uint16_t cf_diff = cf_pulses - this->cf_pulses_last_;
137 this->cf_pulses_total_ += cf_diff;
138 this->cf_pulses_last_ = cf_pulses;
139 energy = this->cf_pulses_total_ * float(power_coeff) / 1000000.0f / 3600.0f;
140 this->energy_sensor_->publish_state(energy);
141 }
142
143 float power = 0.0f;
144 if (power_cycle_exceeds_range) {
145 // Datasheet: power cycle exceeding range means active power is 0
146 have_power = true;
147 if (this->power_sensor_ != nullptr) {
148 this->power_sensor_->publish_state(0.0f);
149 }
150 } else if (have_power) {
151 power = power_coeff / float(power_cycle);
152 if (this->power_sensor_ != nullptr) {
153 this->power_sensor_->publish_state(power);
154 }
155 } else if (this->power_sensor_ != nullptr) {
156 // No valid power measurement from chip - publish 0W to avoid stale readings
157 // This typically happens when current is below the measurable threshold (~50mA)
158 this->power_sensor_->publish_state(0.0f);
159 }
160
161 float current = 0.0f;
162 float calculated_current = 0.0f;
163 if (have_current) {
164 // Assumption: if we don't have power measurement, then current is likely below 50mA
165 if (have_power && voltage > 1.0f) {
166 calculated_current = power / voltage;
167 }
168 // Datasheet: minimum measured current is 50mA
169 if (calculated_current > 0.05f) {
170 current = current_coeff / float(current_cycle);
171 }
172 if (this->current_sensor_ != nullptr) {
173 this->current_sensor_->publish_state(current);
174 }
175 }
176
177 if (have_voltage && have_current) {
178 const float apparent_power = voltage * current;
179 if (this->apparent_power_sensor_ != nullptr) {
180 this->apparent_power_sensor_->publish_state(apparent_power);
181 }
182 if (have_power && this->reactive_power_sensor_ != nullptr) {
183 const float reactive_power = apparent_power - power;
184 if (reactive_power < 0.0f) {
185 ESP_LOGD(TAG, "Impossible reactive power: %.4f is negative", reactive_power);
187 } else {
188 this->reactive_power_sensor_->publish_state(reactive_power);
189 }
190 }
191 if (this->power_factor_sensor_ != nullptr && (have_power || power_cycle_exceeds_range)) {
192 float pf = NAN;
193 if (apparent_power > 0) {
194 pf = power / apparent_power;
195 if (pf < 0 || pf > 1) {
196 ESP_LOGD(TAG, "Impossible power factor: %.4f not in interval [0, 1]", pf);
197 pf = NAN;
198 }
199 } else if (apparent_power == 0 && power == 0) {
200 // No load, report ideal power factor
201 pf = 1.0f;
202 } else if (current == 0 && calculated_current <= 0.05f) {
203 // Datasheet: minimum measured current is 50mA
204 ESP_LOGV(TAG, "Can't calculate power factor (current below minimum for CSE7766)");
205 } else {
206 ESP_LOGW(TAG, "Can't calculate power factor from P = %.4f W, S = %.4f VA", power, apparent_power);
207 }
209 }
210 }
211
212#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
213 {
214 std::string buf = "Parsed:";
215 if (have_voltage) {
216 buf += str_sprintf(" V=%fV", voltage);
217 }
218 if (have_current) {
219 buf += str_sprintf(" I=%fmA (~%fmA)", current * 1000.0f, calculated_current * 1000.0f);
220 }
221 if (have_power) {
222 buf += str_sprintf(" P=%fW", power);
223 }
224 if (energy != 0.0f) {
225 buf += str_sprintf(" E=%fkWh (%u)", energy, cf_pulses);
226 }
227 ESP_LOGVV(TAG, "%s", buf.c_str());
228 }
229#endif
230}
231
233 ESP_LOGCONFIG(TAG, "CSE7766:");
234 LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
235 LOG_SENSOR(" ", "Current", this->current_sensor_);
236 LOG_SENSOR(" ", "Power", this->power_sensor_);
237 LOG_SENSOR(" ", "Energy", this->energy_sensor_);
238 LOG_SENSOR(" ", "Apparent Power", this->apparent_power_sensor_);
239 LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_);
240 LOG_SENSOR(" ", "Power Factor", this->power_factor_sensor_);
242}
243
244} // namespace cse7766
245} // namespace esphome
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
float get_setup_priority() const override
Definition cse7766.cpp:40
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
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:76
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:12
bool read_byte(uint8_t *data)
Definition uart.h:34
const float DATA
For components that import data from directly connected sensors like DHT.
Definition component.cpp:81
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
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:334
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:830
std::string str_sprintf(const char *fmt,...)
Definition helpers.cpp:223
Application App
Global storage of Application pointer - only one Application can exist.