mirror of
https://github.com/esphome/esphome.git
synced 2024-12-02 11:44:13 +01:00
245 lines
8.5 KiB
C++
245 lines
8.5 KiB
C++
|
#include "cse7761.h"
|
|||
|
|
|||
|
#include "esphome/core/log.h"
|
|||
|
|
|||
|
namespace esphome {
|
|||
|
namespace cse7761 {
|
|||
|
|
|||
|
static const char *const TAG = "cse7761";
|
|||
|
|
|||
|
/*********************************************************************************************\
|
|||
|
* CSE7761 - Energy (Sonoff Dual R3 Pow v1.x)
|
|||
|
*
|
|||
|
* Based on Tasmota source code
|
|||
|
* See https://github.com/arendst/Tasmota/discussions/10793
|
|||
|
* https://github.com/arendst/Tasmota/blob/development/tasmota/xnrg_19_cse7761.ino
|
|||
|
\*********************************************************************************************/
|
|||
|
|
|||
|
static const int CSE7761_UREF = 42563; // RmsUc
|
|||
|
static const int CSE7761_IREF = 52241; // RmsIAC
|
|||
|
static const int CSE7761_PREF = 44513; // PowerPAC
|
|||
|
|
|||
|
static const uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
|
|||
|
static const uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
|
|||
|
static const uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
|
|||
|
static const uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
|
|||
|
|
|||
|
static const uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
|
|||
|
static const uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
|
|||
|
static const uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
|
|||
|
static const uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
|
|||
|
static const uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
|
|||
|
static const uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
|
|||
|
|
|||
|
static const uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
|
|||
|
static const uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
|
|||
|
|
|||
|
static const uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
|
|||
|
static const uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
|
|||
|
static const uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
|
|||
|
static const uint8_t CSE7761_CMD_ENABLE_WRITE = 0xE5; // Enable write operation
|
|||
|
|
|||
|
enum CSE7761 { RMS_IAC, RMS_IBC, RMS_UC, POWER_PAC, POWER_PBC, POWER_SC, ENERGY_AC, ENERGY_BC };
|
|||
|
|
|||
|
void CSE7761Component::setup() {
|
|||
|
ESP_LOGCONFIG(TAG, "Setting up CSE7761...");
|
|||
|
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_RESET);
|
|||
|
uint16_t syscon = this->read_(0x00, 2); // Default 0x0A04
|
|||
|
if ((0x0A04 == syscon) && this->chip_init_()) {
|
|||
|
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_CLOSE_WRITE);
|
|||
|
ESP_LOGD(TAG, "CSE7761 found");
|
|||
|
this->data_.ready = true;
|
|||
|
} else {
|
|||
|
this->mark_failed();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void CSE7761Component::dump_config() {
|
|||
|
ESP_LOGCONFIG(TAG, "CSE7761:");
|
|||
|
if (this->is_failed()) {
|
|||
|
ESP_LOGE(TAG, "Communication with CSE7761 failed!");
|
|||
|
}
|
|||
|
LOG_UPDATE_INTERVAL(this);
|
|||
|
this->check_uart_settings(38400, 1, uart::UART_CONFIG_PARITY_EVEN, 8);
|
|||
|
}
|
|||
|
|
|||
|
float CSE7761Component::get_setup_priority() const { return setup_priority::DATA; }
|
|||
|
|
|||
|
void CSE7761Component::update() {
|
|||
|
if (this->data_.ready) {
|
|||
|
this->get_data_();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void CSE7761Component::write_(uint8_t reg, uint16_t data) {
|
|||
|
uint8_t buffer[5];
|
|||
|
|
|||
|
buffer[0] = 0xA5;
|
|||
|
buffer[1] = reg;
|
|||
|
uint32_t len = 2;
|
|||
|
if (data) {
|
|||
|
if (data < 0xFF) {
|
|||
|
buffer[2] = data & 0xFF;
|
|||
|
len = 3;
|
|||
|
} else {
|
|||
|
buffer[2] = (data >> 8) & 0xFF;
|
|||
|
buffer[3] = data & 0xFF;
|
|||
|
len = 4;
|
|||
|
}
|
|||
|
uint8_t crc = 0;
|
|||
|
for (uint32_t i = 0; i < len; i++) {
|
|||
|
crc += buffer[i];
|
|||
|
}
|
|||
|
buffer[len] = ~crc;
|
|||
|
len++;
|
|||
|
}
|
|||
|
|
|||
|
this->write_array(buffer, len);
|
|||
|
}
|
|||
|
|
|||
|
bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) {
|
|||
|
while (this->available()) {
|
|||
|
this->read();
|
|||
|
}
|
|||
|
|
|||
|
this->write_(reg, 0);
|
|||
|
|
|||
|
uint8_t buffer[8] = {0};
|
|||
|
uint32_t rcvd = 0;
|
|||
|
|
|||
|
for (uint32_t i = 0; i <= size; i++) {
|
|||
|
int value = this->read();
|
|||
|
if (value > -1 && rcvd < sizeof(buffer) - 1) {
|
|||
|
buffer[rcvd++] = value;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!rcvd) {
|
|||
|
ESP_LOGD(TAG, "Received 0 bytes for register %hhu", reg);
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
rcvd--;
|
|||
|
uint32_t result = 0;
|
|||
|
// CRC check
|
|||
|
uint8_t crc = 0xA5 + reg;
|
|||
|
for (uint32_t i = 0; i < rcvd; i++) {
|
|||
|
result = (result << 8) | buffer[i];
|
|||
|
crc += buffer[i];
|
|||
|
}
|
|||
|
crc = ~crc;
|
|||
|
if (crc != buffer[rcvd]) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
*value = result;
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
|
|||
|
bool result = false; // Start loop
|
|||
|
uint8_t retry = 3; // Retry up to three times
|
|||
|
uint32_t value = 0; // Default no value
|
|||
|
while (!result && retry > 0) {
|
|||
|
retry--;
|
|||
|
if (this->read_once_(reg, size, &value))
|
|||
|
return value;
|
|||
|
}
|
|||
|
ESP_LOGE(TAG, "Reading register %hhu failed!", reg);
|
|||
|
return value;
|
|||
|
}
|
|||
|
|
|||
|
uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) {
|
|||
|
switch (unit) {
|
|||
|
case RMS_UC:
|
|||
|
return 0x400000 * 100 / this->data_.coefficient[RMS_UC];
|
|||
|
case RMS_IAC:
|
|||
|
return (0x800000 * 100 / this->data_.coefficient[RMS_IAC]) * 10; // Stay within 32 bits
|
|||
|
case POWER_PAC:
|
|||
|
return 0x80000000 / this->data_.coefficient[POWER_PAC];
|
|||
|
}
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
bool CSE7761Component::chip_init_() {
|
|||
|
uint16_t calc_chksum = 0xFFFF;
|
|||
|
for (uint32_t i = 0; i < 8; i++) {
|
|||
|
this->data_.coefficient[i] = this->read_(CSE7761_REG_RMSIAC + i, 2);
|
|||
|
calc_chksum += this->data_.coefficient[i];
|
|||
|
}
|
|||
|
calc_chksum = ~calc_chksum;
|
|||
|
uint16_t coeff_chksum = this->read_(CSE7761_REG_COEFFCHKSUM, 2);
|
|||
|
if ((calc_chksum != coeff_chksum) || (!calc_chksum)) {
|
|||
|
ESP_LOGD(TAG, "Default calibration");
|
|||
|
this->data_.coefficient[RMS_IAC] = CSE7761_IREF;
|
|||
|
this->data_.coefficient[RMS_UC] = CSE7761_UREF;
|
|||
|
this->data_.coefficient[POWER_PAC] = CSE7761_PREF;
|
|||
|
}
|
|||
|
|
|||
|
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
|
|||
|
|
|||
|
uint8_t sys_status = this->read_(CSE7761_REG_SYSSTATUS, 1);
|
|||
|
if (sys_status & 0x10) { // Write enable to protected registers (WREN)
|
|||
|
this->write_(CSE7761_REG_SYSCON | 0x80, 0xFF04);
|
|||
|
this->write_(CSE7761_REG_EMUCON | 0x80, 0x1183);
|
|||
|
this->write_(CSE7761_REG_EMUCON2 | 0x80, 0x0FC1);
|
|||
|
this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290);
|
|||
|
} else {
|
|||
|
ESP_LOGD(TAG, "Write failed at chip_init");
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
void CSE7761Component::get_data_() {
|
|||
|
// The effective value of current and voltage Rms is a 24-bit signed number,
|
|||
|
// the highest bit is 0 for valid data,
|
|||
|
// and when the highest bit is 1, the reading will be processed as zero
|
|||
|
// The active power parameter PowerA/B is in two’s complement format, 32-bit
|
|||
|
// data, the highest bit is Sign bit.
|
|||
|
uint32_t value = this->read_(CSE7761_REG_RMSU, 3);
|
|||
|
this->data_.voltage_rms = (value >= 0x800000) ? 0 : value;
|
|||
|
|
|||
|
value = this->read_(CSE7761_REG_RMSIA, 3);
|
|||
|
this->data_.current_rms[0] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
|
|||
|
value = this->read_(CSE7761_REG_POWERPA, 4);
|
|||
|
this->data_.active_power[0] = (0 == this->data_.current_rms[0]) ? 0 : ((uint32_t) abs((int) value));
|
|||
|
|
|||
|
value = this->read_(CSE7761_REG_RMSIB, 3);
|
|||
|
this->data_.current_rms[1] = ((value >= 0x800000) || (value < 1600)) ? 0 : value; // No load threshold of 10mA
|
|||
|
value = this->read_(CSE7761_REG_POWERPB, 4);
|
|||
|
this->data_.active_power[1] = (0 == this->data_.current_rms[1]) ? 0 : ((uint32_t) abs((int) value));
|
|||
|
|
|||
|
// convert values and publish to sensors
|
|||
|
|
|||
|
float voltage = (float) this->data_.voltage_rms / this->coefficient_by_unit_(RMS_UC);
|
|||
|
if (this->voltage_sensor_ != nullptr) {
|
|||
|
this->voltage_sensor_->publish_state(voltage);
|
|||
|
}
|
|||
|
|
|||
|
for (uint32_t channel = 0; channel < 2; channel++) {
|
|||
|
// Active power = PowerPA * PowerPAC * 1000 / 0x80000000
|
|||
|
float active_power = (float) this->data_.active_power[channel] / this->coefficient_by_unit_(POWER_PAC); // W
|
|||
|
float amps = (float) this->data_.current_rms[channel] / this->coefficient_by_unit_(RMS_IAC); // A
|
|||
|
ESP_LOGD(TAG, "Channel %d power %f W, current %f A", channel + 1, active_power, amps);
|
|||
|
if (channel == 0) {
|
|||
|
if (this->power_sensor_1_ != nullptr) {
|
|||
|
this->power_sensor_1_->publish_state(active_power);
|
|||
|
}
|
|||
|
if (this->current_sensor_1_ != nullptr) {
|
|||
|
this->current_sensor_1_->publish_state(amps);
|
|||
|
}
|
|||
|
} else if (channel == 1) {
|
|||
|
if (this->power_sensor_2_ != nullptr) {
|
|||
|
this->power_sensor_2_->publish_state(active_power);
|
|||
|
}
|
|||
|
if (this->current_sensor_2_ != nullptr) {
|
|||
|
this->current_sensor_2_->publish_state(amps);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
} // namespace cse7761
|
|||
|
} // namespace esphome
|