ESPHome 2026.11.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
7ESPHOME_LOG_TAG(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
21// Empirical Sonoff POW CT factor taken from Tasmota
22static constexpr uint32_t CURRENT_TRANSFORMER_COEFFICIENT_SCALE = 5;
23
24// PGA 16x on both current channels
25static constexpr uint16_t SYSCON_SHUNT = 0xFF04;
26// All PGAs 1x, ADC2ON
27static constexpr uint16_t SYSCON_CURRENT_TRANSFORMER = 0xFE00;
28
29// System clock (3.579545MHz) / 8, divided by Ufreq gives the line frequency in Hz
30static constexpr float CSE7761_UFREQ_CLOCK = 3579545.0f / 8.0f;
31
32static constexpr uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
33static constexpr uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
34static constexpr uint8_t CSE7761_REG_HFCONST = 0x02; // (2) Pulse frequency register (0x1000)
35static constexpr uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
36static constexpr uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
37
38static constexpr uint8_t CSE7761_REG_UFREQ = 0x23; // (2) Voltage frequency (0x0000)
39static constexpr uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
40static constexpr uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
41static constexpr uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
42static constexpr uint8_t CSE7761_REG_POWERFACTOR = 0x27; // (3) Power factor of the selected channel (0x7FFFFF)
43static constexpr uint8_t CSE7761_REG_ENERGY_PA = 0x28; // (3) Channel A active energy pulse count (0x000000)
44static constexpr uint8_t CSE7761_REG_ENERGY_PB = 0x29; // (3) Channel B active energy pulse count (0x000000)
45static constexpr uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
46static constexpr uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
47static constexpr uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
48
49static constexpr uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
50static constexpr uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
51
52// Datasheet: Wh = pulses * EnergyXC * HFConst * 1000 / 2^41 with K1 = K2 = 1
53static constexpr float CSE7761_ENERGY_WH_SCALE = 1000.0f / 2199023255552.0f;
54
55static constexpr uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
56static constexpr uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
57static constexpr uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
58static constexpr uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
59
61
63 this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
64 uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
65 if ((0x0A04 == syscon) && this->chip_init_()) {
66 this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE);
67 ESP_LOGD(TAG, "CSE7761 found");
68 this->data_.ready = true;
69 } else {
70 this->mark_failed(LOG_STR(ESP_LOG_MSG_COMM_FAIL));
71 }
72}
73
75 ESP_LOGCONFIG(TAG, "CSE7761:");
76 ESP_LOGCONFIG(TAG, " Current input: %s",
77 LOG_STR_ARG(this->is_current_transformer_() ? LOG_STR("current transformer") : LOG_STR("shunt")));
78 LOG_UPDATE_INTERVAL(this);
79 LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
80 LOG_SENSOR(" ", "Current 1", this->current_sensor_1_);
81 LOG_SENSOR(" ", "Current 2", this->current_sensor_2_);
82 LOG_SENSOR(" ", "Power 1", this->power_sensor_1_);
83 LOG_SENSOR(" ", "Power 2", this->power_sensor_2_);
84 LOG_SENSOR(" ", "Frequency", this->frequency_sensor_);
85 LOG_SENSOR(" ", "Power Factor 1", this->power_factor_sensor_1_);
86 LOG_SENSOR(" ", "Energy 1", this->energy_sensors_[0]);
87 LOG_SENSOR(" ", "Energy 2", this->energy_sensors_[1]);
88}
89
91 if (this->data_.ready) {
92 this->get_data_();
93 }
94}
95
96void CSE7761Component::write_(uint8_t reg, uint16_t data) {
97 uint8_t buffer[5];
98
99 buffer[0] = 0xA5;
100 buffer[1] = reg;
101 uint32_t len = 2;
102 if (data) {
103 if (data <= 0xFF) {
104 buffer[2] = data & 0xFF;
105 len = 3;
106 } else {
107 buffer[2] = (data >> 8) & 0xFF;
108 buffer[3] = data & 0xFF;
109 len = 4;
110 }
111 buffer[len] = ~sum_bytes(buffer, len);
112 len++;
113 }
114
115 this->write_array(buffer, len);
116}
117
118bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) {
119 this->discard_available();
120
121 this->write_(reg, 0);
122
123 uint8_t buffer[8] = {0};
124 uint32_t rcvd = 0;
125
126 for (uint32_t i = 0; i <= size; i++) {
127 int value = this->read();
128 if (value > -1 && rcvd < sizeof(buffer) - 1) {
129 buffer[rcvd++] = value;
130 }
131 }
132
133 if (!rcvd) {
134 ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg);
135 return false;
136 }
137
138 rcvd--;
139 if (static_cast<uint8_t>(~sum_bytes(buffer, rcvd, 0xA5 + reg)) != buffer[rcvd]) {
140 return false;
141 }
142 uint32_t result = 0;
143 for (uint32_t i = 0; i < rcvd; i++) {
144 result = (result << 8) | buffer[i];
145 }
146
147 *value = result;
148 return true;
149}
150
151bool CSE7761Component::read_(uint8_t reg, uint8_t size, uint32_t *value) {
152 for (uint8_t retry = 0; retry < 3; retry++) {
153 if (this->read_once_(reg, size, value))
154 return true;
155 }
156 ESP_LOGE(TAG, "Reading register %hhu failed!", reg);
157 return false;
158}
159
160uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
161 uint32_t value = 0;
162 return this->read_(reg, size, &value) ? value : 0;
163}
164
166 const uint32_t scale = this->is_current_transformer_() ? CURRENT_TRANSFORMER_COEFFICIENT_SCALE : 1;
167 uint32_t coeff = 0;
168 switch (unit) {
169 case RMS_UC:
170 coeff = this->data_.coefficient[RMS_UC];
171 return coeff ? 0x400000 * 100 / coeff : 0;
172 case RMS_IAC:
173 coeff = this->data_.coefficient[RMS_IAC] * scale;
174 return coeff ? (0x800000 * 100 / coeff) * 10 : 0; // Stay within 32 bits
175 case POWER_PAC:
176 coeff = this->data_.coefficient[POWER_PAC] * scale;
177 return coeff ? 0x80000000 / coeff : 0;
178 default:
179 break;
180 }
181 return 0;
182}
183
185 uint16_t calc_chksum = 0xFFFF;
186 for (uint32_t i = 0; i < 8; i++) {
187 this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2);
188 calc_chksum += this->data_.coefficient[i];
189 }
190 calc_chksum = ~calc_chksum;
191 const bool energy_configured = this->energy_sensors_[0] != nullptr || this->energy_sensors_[1] != nullptr;
192 uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2);
193 if ((calc_chksum != coeff_chksum) || (!calc_chksum)) {
194 ESP_LOGD(TAG, "Default calibration");
195 this->data_.coefficient[RMS_IAC] = CSE7761_IREF;
196 this->data_.coefficient[RMS_UC] = CSE7761_UREF;
197 this->data_.coefficient[POWER_PAC] = CSE7761_PREF;
198 if (energy_configured) {
199 ESP_LOGW(TAG, "No energy calibration, energy unavailable");
200 }
201 } else if (energy_configured) {
202 uint32_t hf_const = 0;
203 if (!this->read_(CSE7761_REG_HFCONST, 2, &hf_const)) {
204 ESP_LOGW(TAG, "HFConst read failed, energy unavailable");
205 }
206 // The pulse counters follow the raw power, so they need the same front end factor as power
207 const float input_scale = this->is_current_transformer_() ? CURRENT_TRANSFORMER_COEFFICIENT_SCALE : 1.0f;
208 for (uint8_t channel = 0; channel < 2; channel++) {
209 uint32_t scale = this->data_.coefficient[ENERGY_AC + channel] * hf_const;
210 this->energy_[channel].wh_per_pulse = static_cast<float>(scale) * CSE7761_ENERGY_WH_SCALE * input_scale;
211 }
212 }
213
214 this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
215
216 uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1);
217 if (sys_status & 0x10) { // Write enable to protected registers (WREN)
218 this->write_(CSE7761_REG_SYSCON | 0x80,
219 this->is_current_transformer_() ? SYSCON_CURRENT_TRANSFORMER : SYSCON_SHUNT);
220 this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183);
221 // WaveEN|ZxEN, needed for Ufreq
222 this->write_(CSE7761_REG_EMUCON2 | 0x80, this->frequency_sensor_ != nullptr ? 0x0FE5 : 0x0FC1);
223 this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290);
224 } else {
225 ESP_LOGD(TAG, "Write failed at chip_init");
226 return false;
227 }
228 return true;
229}
230
232 // The effective value of current and voltage Rms is a 24-bit signed number,
233 // the highest bit is 0 for valid data,
234 // and when the highest bit is 1, the reading will be processed as zero
235 // The active power parameter PowerA/B is in two’s complement format, 32-bit
236 // data, the highest bit is Sign bit.
237 uint32_t value = this->read_(CSE7761_REG_RMSU, 3);
238 this->data_.voltage_rms = (value >= 0x800000) ? 0 : value;
239
240 // No load threshold of 10mA with a shunt; the raw value stands for 5 times more with a current transformer
241 value = this->read_(CSE7761_REG_RMSIA, 3);
242 this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value;
243 value = this->read_(CSE7761_REG_POWERPA, 4);
244 // PowerPA is two's complement signed 32-bit per datasheet
245 this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : static_cast<int32_t>(value);
246
247 value = this->read_(CSE7761_REG_RMSIB, 3);
248 this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value;
249 value = this->read_(CSE7761_REG_POWERPB, 4);
250 // PowerPB is two's complement signed 32-bit per datasheet
251 this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : static_cast<int32_t>(value);
252
253 // convert values and publish to sensors
254
255 float voltage = static_cast<float>(this->data_.voltage_rms) / this->coefficient_by_unit_(RMS_UC);
256 if (this->voltage_sensor_ != nullptr) {
257 this->voltage_sensor_->publish_state(voltage);
258 }
259
260 uint32_t raw_reg = 0;
261 if (this->frequency_sensor_ != nullptr && this->read_(CSE7761_REG_UFREQ, 2, &raw_reg)) {
262 // Ufreq highest bit set means no signal
263 float frequency = (raw_reg == 0 || raw_reg >= 0x8000) ? 0.0f : CSE7761_UFREQ_CLOCK / raw_reg;
264 this->frequency_sensor_->publish_state(frequency);
265 }
266
267 if (this->power_factor_sensor_1_ != nullptr && this->read_(CSE7761_REG_POWERFACTOR, 3, &raw_reg)) {
268 // ADC2ON makes the chip report channel A
269 int24_t pf{static_cast<int32_t>(raw_reg)};
270 this->power_factor_sensor_1_->publish_state(static_cast<float>(static_cast<int32_t>(pf)) / 0x800000);
271 }
272
273 for (uint8_t channel = 0; channel < 2; channel++) {
274 // Active power = PowerPA * PowerPAC * 1000 / 0x80000000
275 float active_power =
276 static_cast<float>(this->data_.active_power[channel]) / this->coefficient_by_unit_(POWER_PAC); // W
277 float amps = static_cast<float>(this->data_.current_rms[channel]) / this->coefficient_by_unit_(RMS_IAC); // A
278 ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps);
279 sensor::Sensor *energy_sensor = this->energy_sensors_[channel];
280 CSE7761EnergyChannel &energy = this->energy_[channel];
281 uint8_t energy_reg = (channel == 0) ? CSE7761_REG_ENERGY_PA : CSE7761_REG_ENERGY_PB;
282 uint32_t raw = 0;
283 if (energy_sensor != nullptr && energy.wh_per_pulse > 0.0f && this->read_(energy_reg, 3, &raw)) {
284 // 24-bit counter, not cleared on read
285 energy.pulses += (raw - energy.last_pulses) & 0xFFFFFF;
286 energy.last_pulses = raw;
287 energy_sensor->publish_state(energy.pulses * energy.wh_per_pulse);
288 }
289 if (channel == 0) {
290 if (this->power_sensor_1_ != nullptr) {
291 this->power_sensor_1_->publish_state(active_power);
292 }
293 if (this->current_sensor_1_ != nullptr) {
294 this->current_sensor_1_->publish_state(amps);
295 }
296 } else if (channel == 1) {
297 if (this->power_sensor_2_ != nullptr) {
298 this->power_sensor_2_->publish_state(active_power);
299 }
300 if (this->current_sensor_2_ != nullptr) {
301 this->current_sensor_2_->publish_state(amps);
302 }
303 }
304 }
305}
306
307} // namespace esphome::cse7761
uint8_t raw[35]
Definition bl0939.h:0
uint16_le_t frequency
Definition bl0942.h:6
void mark_failed()
Mark this component as failed.
sensor::Sensor * current_sensor_1_
Definition cse7761.h:54
sensor::Sensor * power_sensor_2_
Definition cse7761.h:55
sensor::Sensor * voltage_sensor_
Definition cse7761.h:50
uint32_t coefficient_by_unit_(uint32_t unit)
Definition cse7761.cpp:165
sensor::Sensor * frequency_sensor_
Definition cse7761.h:51
sensor::Sensor * energy_sensors_[2]
Definition cse7761.h:57
sensor::Sensor * current_sensor_2_
Definition cse7761.h:56
sensor::Sensor * power_sensor_1_
Definition cse7761.h:53
sensor::Sensor * power_factor_sensor_1_
Definition cse7761.h:52
bool read_once_(uint8_t reg, uint8_t size, uint32_t *value)
Definition cse7761.cpp:118
bool read_(uint8_t reg, uint8_t size, uint32_t *value)
Definition cse7761.cpp:151
void write_(uint8_t reg, uint16_t data)
Definition cse7761.cpp:96
CSE7761EnergyChannel energy_[2]
Definition cse7761.h:59
Base-class for all sensors.
Definition sensor.h:47
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
void discard_available()
Read and drop every byte waiting in the receive buffer.
Definition uart.cpp:36
void write_array(const uint8_t *data, size_t len)
Definition uart.h:30
uint32_t buffer
Definition gif.cpp:50
ESPHOME_LOG_TAG(TAG, "cse7761")
constexpr uint8_t sum_bytes(const uint8_t *data, size_t len, uint8_t sum=0)
Add up the bytes of data with size len, truncated to 8 bits: the checksum many serial and IR protocol...
Definition helpers.h:894
const void size_t len
Definition hal.h:68
uint16_t size
Definition helpers.cpp:34
static void uint32_t
24-bit signed integer type, transparently converting to 32-bit.
Definition datatypes.h:38