ESPHome  2024.4.0
cse7761.cpp
Go to the documentation of this file.
1 #include "cse7761.h"
2 
3 #include "esphome/core/log.h"
4 
5 namespace esphome {
6 namespace cse7761 {
7 
8 static const char *const TAG = "cse7761";
9 
10 /*********************************************************************************************\
11  * CSE7761 - Energy (Sonoff Dual R3 Pow v1.x)
12  *
13  * Based on Tasmota source code
14  * See https://github.com/arendst/Tasmota/discussions/10793
15  * https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino
16 \*********************************************************************************************/
17 
18 static const int CSE7761_UREF = 42563; // RmsUc
19 static const int CSE7761_IREF = 52241; // RmsIAC
20 static const int CSE7761_PREF = 44513; // PowerPAC
21 
22 static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
23 static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
24 static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
25 static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
26 
27 static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
28 static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
29 static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
30 static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
31 static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
32 static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
33 
34 static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
35 static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
36 
37 static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
38 static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
39 static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
40 static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
41 
43 
45  ESP_LOGCONFIG(TAG, "Setting up CSE7761...");
46  this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
47  uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
48  if ((0x0A04 == syscon) && this->chip_init_()) {
49  this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE);
50  ESP_LOGD(TAG, "CSE7761 found");
51  this->data_.ready = true;
52  } else {
53  this->mark_failed();
54  }
55 }
56 
58  ESP_LOGCONFIG(TAG, "CSE7761:");
59  if (this->is_failed()) {
60  ESP_LOGE(TAG, "Communication with CSE7761 failed!");
61  }
62  LOG_UPDATE_INTERVAL(this);
64 }
65 
67 
69  if (this->data_.ready) {
70  this->get_data_();
71  }
72 }
73 
74 void CSE7761Component::write_(uint8_t reg, uint16_t data) {
75  uint8_t buffer[5];
76 
77  buffer[0] = 0xA5;
78  buffer[1] = reg;
79  uint32_t len = 2;
80  if (data) {
81  if (data < 0xFF) {
82  buffer[2] = data & 0xFF;
83  len = 3;
84  } else {
85  buffer[2] = (data >> 8) & 0xFF;
86  buffer[3] = data & 0xFF;
87  len = 4;
88  }
89  uint8_t crc = 0;
90  for (uint32_t i = 0; i < len; i++) {
91  crc += buffer[i];
92  }
93  buffer[len] = ~crc;
94  len++;
95  }
96 
97  this->write_array(buffer, len);
98 }
99 
100 bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) {
101  while (this->available()) {
102  this->read();
103  }
104 
105  this->write_(reg, 0);
106 
107  uint8_t buffer[8] = {0};
108  uint32_t rcvd = 0;
109 
110  for (uint32_t i = 0; i <= size; i++) {
111  int value = this->read();
112  if (value > -1 && rcvd < sizeof(buffer) - 1) {
113  buffer[rcvd++] = value;
114  }
115  }
116 
117  if (!rcvd) {
118  ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg);
119  return false;
120  }
121 
122  rcvd--;
123  uint32_t result = 0;
124  // CRC check
125  uint8_t crc = 0xA5 + reg;
126  for (uint32_t i = 0; i < rcvd; i++) {
127  result = (result << 8) | buffer[i];
128  crc += buffer[i];
129  }
130  crc = ~crc;
131  if (crc != buffer[rcvd]) {
132  return false;
133  }
134 
135  *value = result;
136  return true;
137 }
138 
139 uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
140  bool result = false; // Start loop
141  uint8_t retry = 3; // Retry up to three times
142  uint32_t value = 0; // Default no value
143  while (!result && retry > 0) {
144  retry--;
145  if (this->read_once_(reg, size, &value))
146  return value;
147  }
148  ESP_LOGE(TAG, "Reading register %hhu failed!", reg);
149  return value;
150 }
151 
152 uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) {
153  switch (unit) {
154  case RMS_UC:
155  return 0x400000 * 100 / this->data_.coefficient[RMS_UC];
156  case RMS_IAC:
157  return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10; // Stay within 32 bits
158  case POWER_PAC:
159  return 0x80000000 / this->data_.coefficient[POWER_PAC];
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  this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value));
207 
208  value = this->read_(CSE7761_REG_RMSIB, 3);
209  this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
210  value = this->read_(CSE7761_REG_POWERPB, 4);
211  this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value));
212 
213  // convert values and publish to sensors
214 
215  float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC);
216  if (this->voltage_sensor_ != nullptr) {
217  this->voltage_sensor_->publish_state(voltage);
218  }
219 
220  for (uint8_t channel = 0; channel < 2; channel++) {
221  // Active power = PowerPA * PowerPAC * 1000 / 0x80000000
222  float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W
223  float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A
224  ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps);
225  if (channel == 0) {
226  if (this->power_sensor_1_ != nullptr) {
227  this->power_sensor_1_->publish_state(active_power);
228  }
229  if (this->current_sensor_1_ != nullptr) {
230  this->current_sensor_1_->publish_state(amps);
231  }
232  } else if (channel == 1) {
233  if (this->power_sensor_2_ != nullptr) {
234  this->power_sensor_2_->publish_state(active_power);
235  }
236  if (this->current_sensor_2_ != nullptr) {
237  this->current_sensor_2_->publish_state(amps);
238  }
239  }
240  }
241 }
242 
243 } // namespace cse7761
244 } // namespace esphome
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
sensor::Sensor * power_sensor_2_
Definition: cse7761.h:39
float get_setup_priority() const override
Definition: cse7761.cpp:66
sensor::Sensor * power_sensor_1_
Definition: cse7761.h:37
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
uint32_t read_(uint8_t reg, uint8_t size)
Definition: cse7761.cpp:139
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
uint32_t coefficient_by_unit_(uint32_t unit)
Definition: cse7761.cpp:152
sensor::Sensor * current_sensor_1_
Definition: cse7761.h:38
std::string size_t len
Definition: helpers.h:292
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
sensor::Sensor * current_sensor_2_
Definition: cse7761.h:40
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
void write_(uint8_t reg, uint16_t data)
Definition: cse7761.cpp:74
sensor::Sensor * voltage_sensor_
Definition: cse7761.h:36
bool read_once_(uint8_t reg, uint8_t size, uint32_t *value)
Definition: cse7761.cpp:100