ESPHome 2026.6.0-dev
Loading...
Searching...
No Matches
cse7761.cpp
Go to the documentation of this file.
1#include "cse7761.h"
2
3#include "esphome/core/log.h"
4
5namespace esphome::cse7761 {
6
7static const char *const TAG = "cse7761";
8
9/*********************************************************************************************\
10 * CSE7761 - Energy (Sonoff Dual R3 Pow v1.x)
11 *
12 * Based on Tasmota source code
13 * See https://github.com/arendst/Tasmota/discussions/10793
14 * https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino
15\*********************************************************************************************/
16
17static constexpr int CSE7761_UREF = 42563; // RmsUc
18static constexpr int CSE7761_IREF = 52241; // RmsIAC
19static constexpr int CSE7761_PREF = 44513; // PowerPAC
20
21static constexpr uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
22static constexpr uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
23static constexpr uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
24static constexpr uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
25
26static constexpr uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
27static constexpr uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
28static constexpr uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
29static constexpr uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
30static constexpr uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
31static constexpr uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
32
33static constexpr uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
34static constexpr uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
35
36static constexpr uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
37static constexpr uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
38static constexpr uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
39static constexpr uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
40
42
44 this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
45 uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
46 if ((0x0A04 == syscon) && this->chip_init_()) {
47 this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE);
48 ESP_LOGD(TAG, "CSE7761 found");
49 this->data_.ready = true;
50 } else {
51 this->mark_failed();
52 }
53}
54
56 ESP_LOGCONFIG(TAG, "CSE7761:");
57 if (this->is_failed()) {
58 ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
59 }
60 LOG_UPDATE_INTERVAL(this);
62}
63
65 if (this->data_.ready) {
66 this->get_data_();
67 }
68}
69
70void CSE7761Component::write_(uint8_t reg, uint16_t data) {
71 uint8_t buffer[5];
72
73 buffer[0] = 0xA5;
74 buffer[1] = reg;
75 uint32_t len = 2;
76 if (data) {
77 if (data < 0xFF) {
78 buffer[2] = data & 0xFF;
79 len = 3;
80 } else {
81 buffer[2] = (data >> 8) & 0xFF;
82 buffer[3] = data & 0xFF;
83 len = 4;
84 }
85 uint8_t crc = 0;
86 for (uint32_t i = 0; i < len; i++) {
87 crc += buffer[i];
88 }
89 buffer[len] = ~crc;
90 len++;
91 }
92
93 this->write_array(buffer, len);
94}
95
96bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) {
97 while (this->available()) {
98 this->read();
99 }
100
101 this->write_(reg, 0);
102
103 uint8_t buffer[8] = {0};
104 uint32_t rcvd = 0;
105
106 for (uint32_t i = 0; i <= size; i++) {
107 int value = this->read();
108 if (value > -1 && rcvd < sizeof(buffer) - 1) {
109 buffer[rcvd++] = value;
110 }
111 }
112
113 if (!rcvd) {
114 ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg);
115 return false;
116 }
117
118 rcvd--;
119 uint32_t result = 0;
120 // CRC check
121 uint8_t crc = 0xA5 + reg;
122 for (uint32_t i = 0; i < rcvd; i++) {
123 result = (result << 8) | buffer[i];
124 crc += buffer[i];
125 }
126 crc = ~crc;
127 if (crc != buffer[rcvd]) {
128 return false;
129 }
130
131 *value = result;
132 return true;
133}
134
135uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
136 bool result = false; // Start loop
137 uint8_t retry = 3; // Retry up to three times
138 uint32_t value = 0; // Default no value
139 while (!result && retry > 0) {
140 retry--;
141 if (this->read_once_(reg, size, &value))
142 return value;
143 }
144 ESP_LOGE(TAG, "Reading register %hhu failed!", reg);
145 return value;
146}
147
149 uint32_t coeff = 0;
150 switch (unit) {
151 case RMS_UC:
152 coeff = this->data_.coefficient[RMS_UC];
153 return coeff ? 0x400000 * 100 / coeff : 0;
154 case RMS_IAC:
155 coeff = this->data_.coefficient[RMS_IAC];
156 return coeff ? (0x800000 * 100 / coeff) * 10 : 0; // Stay within 32 bits
157 case POWER_PAC:
158 coeff = this->data_.coefficient[POWER_PAC];
159 return coeff ? 0x80000000 / coeff : 0;
160 }
161 return 0;
162}
163
165 uint16_t calc_chksum = 0xFFFF;
166 for (uint32_t i = 0; i < 8; i++) {
167 this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2);
168 calc_chksum += this->data_.coefficient[i];
169 }
170 calc_chksum = ~calc_chksum;
171 uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2);
172 if ((calc_chksum != coeff_chksum) || (!calc_chksum)) {
173 ESP_LOGD(TAG, "Default calibration");
174 this->data_.coefficient[RMS_IAC] = CSE7761_IREF;
175 this->data_.coefficient[RMS_UC] = CSE7761_UREF;
176 this->data_.coefficient[POWER_PAC] = CSE7761_PREF;
177 }
178
179 this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
180
181 uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1);
182 if (sys_status & 0x10) { // Write enable to protected registers (WREN)
183 this->write_(CSE7761_REG_SYSCON | 0x80, 0xFF04);
184 this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183);
185 this->write_(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1);
186 this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290);
187 } else {
188 ESP_LOGD(TAG, "Write failed at chip_init");
189 return false;
190 }
191 return true;
192}
193
195 // The effective value of current and voltage Rms is a 24-bit signed number,
196 // the highest bit is 0 for valid data,
197 // and when the highest bit is 1, the reading will be processed as zero
198 // The active power parameter PowerA/B is in two’s complement format, 32-bit
199 // data, the highest bit is Sign bit.
200 uint32_t value = this->read_(CSE7761_REG_RMSU, 3);
201 this->data_.voltage_rms = (value >= 0x800000) ? 0 : value;
202
203 value = this->read_(CSE7761_REG_RMSIA, 3);
204 this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
205 value = this->read_(CSE7761_REG_POWERPA, 4);
206 // PowerPA is two's complement signed 32-bit per datasheet
207 this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : static_cast<int32_t>(value);
208
209 value = this->read_(CSE7761_REG_RMSIB, 3);
210 this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
211 value = this->read_(CSE7761_REG_POWERPB, 4);
212 // PowerPB is two's complement signed 32-bit per datasheet
213 this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : static_cast<int32_t>(value);
214
215 // convert values and publish to sensors
216
217 float voltage = static_cast<float>(this->data_.voltage_rms) / this->coefficient_by_unit_(RMS_UC);
218 if (this->voltage_sensor_ != nullptr) {
219 this->voltage_sensor_->publish_state(voltage);
220 }
221
222 for (uint8_t channel = 0; channel < 2; channel++) {
223 // Active power = PowerPA * PowerPAC * 1000 / 0x80000000
224 float active_power =
225 static_cast<float>(this->data_.active_power[channel]) / this->coefficient_by_unit_(POWER_PAC); // W
226 float amps = static_cast<float>(this->data_.current_rms[channel]) / this->coefficient_by_unit_(RMS_IAC); // A
227 ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps);
228 if (channel == 0) {
229 if (this->power_sensor_1_ != nullptr) {
230 this->power_sensor_1_->publish_state(active_power);
231 }
232 if (this->current_sensor_1_ != nullptr) {
233 this->current_sensor_1_->publish_state(amps);
234 }
235 } else if (channel == 1) {
236 if (this->power_sensor_2_ != nullptr) {
237 this->power_sensor_2_->publish_state(active_power);
238 }
239 if (this->current_sensor_2_ != nullptr) {
240 this->current_sensor_2_->publish_state(amps);
241 }
242 }
243 }
244}
245
246} // namespace esphome::cse7761
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:272
sensor::Sensor * current_sensor_1_
Definition cse7761.h:34
sensor::Sensor * power_sensor_2_
Definition cse7761.h:35
sensor::Sensor * voltage_sensor_
Definition cse7761.h:32
uint32_t coefficient_by_unit_(uint32_t unit)
Definition cse7761.cpp:148
uint32_t read_(uint8_t reg, uint8_t size)
Definition cse7761.cpp:135
sensor::Sensor * current_sensor_2_
Definition cse7761.h:36
sensor::Sensor * power_sensor_1_
Definition cse7761.h:33
bool read_once_(uint8_t reg, uint8_t size, uint32_t *value)
Definition cse7761.cpp:96
void write_(uint8_t reg, uint16_t data)
Definition cse7761.cpp:70
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:68
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
void write_array(const uint8_t *data, size_t len)
Definition uart.h:26
const void size_t len
Definition hal.h:64
uint16_t size
Definition helpers.cpp:25
static void uint32_t