mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
[cse7761] Add new sensors (#17531)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
This commit is contained in:
co-authored by
J. Nick Koston
parent
eb56ce2b76
commit
5cdefa910f
@@ -18,21 +18,32 @@ static constexpr int CSE7761_UREF = 42563; // RmsUc
|
||||
static constexpr int CSE7761_IREF = 52241; // RmsIAC
|
||||
static constexpr int CSE7761_PREF = 44513; // PowerPAC
|
||||
|
||||
// System clock (3.579545MHz) / 8, divided by Ufreq gives the line frequency in Hz
|
||||
static constexpr float CSE7761_UFREQ_CLOCK = 3579545.0f / 8.0f;
|
||||
|
||||
static constexpr uint8_t CSE7761_REG_SYSCON = 0x00; // (2) System Control Register (0x0A04)
|
||||
static constexpr uint8_t CSE7761_REG_EMUCON = 0x01; // (2) Metering control register (0x0000)
|
||||
static constexpr uint8_t CSE7761_REG_HFCONST = 0x02; // (2) Pulse frequency register (0x1000)
|
||||
static constexpr uint8_t CSE7761_REG_EMUCON2 = 0x13; // (2) Metering control register 2 (0x0001)
|
||||
static constexpr uint8_t CSE7761_REG_PULSE1SEL = 0x1D; // (2) Pin function output select register (0x3210)
|
||||
|
||||
static constexpr uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
|
||||
static constexpr uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
|
||||
static constexpr uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
|
||||
static constexpr uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
|
||||
static constexpr uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
|
||||
static constexpr uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
|
||||
static constexpr uint8_t CSE7761_REG_UFREQ = 0x23; // (2) Voltage frequency (0x0000)
|
||||
static constexpr uint8_t CSE7761_REG_RMSIA = 0x24; // (3) The effective value of channel A current (0x000000)
|
||||
static constexpr uint8_t CSE7761_REG_RMSIB = 0x25; // (3) The effective value of channel B current (0x000000)
|
||||
static constexpr uint8_t CSE7761_REG_RMSU = 0x26; // (3) Voltage RMS (0x000000)
|
||||
static constexpr uint8_t CSE7761_REG_POWERFACTOR = 0x27; // (3) Power factor of the selected channel (0x7FFFFF)
|
||||
static constexpr uint8_t CSE7761_REG_ENERGY_PA = 0x28; // (3) Channel A active energy pulse count (0x000000)
|
||||
static constexpr uint8_t CSE7761_REG_ENERGY_PB = 0x29; // (3) Channel B active energy pulse count (0x000000)
|
||||
static constexpr uint8_t CSE7761_REG_POWERPA = 0x2C; // (4) Channel A active power, update rate 27.2Hz (0x00000000)
|
||||
static constexpr uint8_t CSE7761_REG_POWERPB = 0x2D; // (4) Channel B active power, update rate 27.2Hz (0x00000000)
|
||||
static constexpr uint8_t CSE7761_REG_SYSSTATUS = 0x43; // (1) System status register
|
||||
|
||||
static constexpr uint8_t CSE7761_REG_COEFFCHKSUM = 0x6F; // (2) Coefficient checksum
|
||||
static constexpr uint8_t CSE7761_REG_RMSIAC = 0x70; // (2) Channel A effective current conversion coefficient
|
||||
|
||||
// Datasheet: Wh = pulses * EnergyXC * HFConst * 1000 / 2^41 with K1 = K2 = 1
|
||||
static constexpr float CSE7761_ENERGY_WH_SCALE = 1000.0f / 2199023255552.0f;
|
||||
|
||||
static constexpr uint8_t CSE7761_SPECIAL_COMMAND = 0xEA; // Start special command
|
||||
static constexpr uint8_t CSE7761_CMD_RESET = 0x96; // Reset command, after receiving the command, the chip resets
|
||||
static constexpr uint8_t CSE7761_CMD_CLOSE_WRITE = 0xDC; // Close write operation
|
||||
@@ -58,6 +69,15 @@ void CSE7761Component::dump_config() {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Voltage", this->voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current 1", this->current_sensor_1_);
|
||||
LOG_SENSOR(" ", "Current 2", this->current_sensor_2_);
|
||||
LOG_SENSOR(" ", "Power 1", this->power_sensor_1_);
|
||||
LOG_SENSOR(" ", "Power 2", this->power_sensor_2_);
|
||||
LOG_SENSOR(" ", "Frequency", this->frequency_sensor_);
|
||||
LOG_SENSOR(" ", "Power Factor 1", this->power_factor_sensor_1_);
|
||||
LOG_SENSOR(" ", "Energy 1", this->energy_sensors_[0]);
|
||||
LOG_SENSOR(" ", "Energy 2", this->energy_sensors_[1]);
|
||||
}
|
||||
|
||||
void CSE7761Component::update() {
|
||||
@@ -123,17 +143,18 @@ bool CSE7761Component::read_once_(uint8_t reg, uint8_t size, uint32_t *value) {
|
||||
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;
|
||||
bool CSE7761Component::read_(uint8_t reg, uint8_t size, uint32_t *value) {
|
||||
for (uint8_t retry = 0; retry < 3; retry++) {
|
||||
if (this->read_once_(reg, size, value))
|
||||
return true;
|
||||
}
|
||||
ESP_LOGE(TAG, "Reading register %hhu failed!", reg);
|
||||
return value;
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t CSE7761Component::read_(uint8_t reg, uint8_t size) {
|
||||
uint32_t value = 0;
|
||||
return this->read_(reg, size, &value) ? value : 0;
|
||||
}
|
||||
|
||||
uint32_t CSE7761Component::coefficient_by_unit_(uint32_t unit) {
|
||||
@@ -159,12 +180,25 @@ bool CSE7761Component::chip_init_() {
|
||||
calc_chksum += this->data_.coefficient[i];
|
||||
}
|
||||
calc_chksum = ~calc_chksum;
|
||||
const bool energy_configured = this->energy_sensors_[0] != nullptr || this->energy_sensors_[1] != nullptr;
|
||||
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;
|
||||
if (energy_configured) {
|
||||
ESP_LOGW(TAG, "No energy calibration, energy unavailable");
|
||||
}
|
||||
} else if (energy_configured) {
|
||||
uint32_t hf_const = 0;
|
||||
if (!this->read_(CSE7761_REG_HFCONST, 2, &hf_const)) {
|
||||
ESP_LOGW(TAG, "HFConst read failed, energy unavailable");
|
||||
}
|
||||
for (uint8_t channel = 0; channel < 2; channel++) {
|
||||
uint32_t scale = this->data_.coefficient[ENERGY_AC + channel] * hf_const;
|
||||
this->energy_[channel].wh_per_pulse = static_cast<float>(scale) * CSE7761_ENERGY_WH_SCALE;
|
||||
}
|
||||
}
|
||||
|
||||
this->write_(CSE7761_SPECIAL_COMMAND, CSE7761_CMD_ENABLE_WRITE);
|
||||
@@ -173,7 +207,8 @@ bool CSE7761Component::chip_init_() {
|
||||
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);
|
||||
// WaveEN|ZxEN, needed for Ufreq
|
||||
this->write_(CSE7761_REG_EMUCON2 | 0x80, this->frequency_sensor_ != nullptr ? 0x0FE5 : 0x0FC1);
|
||||
this->write_(CSE7761_REG_PULSE1SEL | 0x80, 0x3290);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Write failed at chip_init");
|
||||
@@ -210,12 +245,35 @@ void CSE7761Component::get_data_() {
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
}
|
||||
|
||||
uint32_t raw_reg = 0;
|
||||
if (this->frequency_sensor_ != nullptr && this->read_(CSE7761_REG_UFREQ, 2, &raw_reg)) {
|
||||
// Ufreq highest bit set means no signal
|
||||
float frequency = (raw_reg == 0 || raw_reg >= 0x8000) ? 0.0f : CSE7761_UFREQ_CLOCK / raw_reg;
|
||||
this->frequency_sensor_->publish_state(frequency);
|
||||
}
|
||||
|
||||
if (this->power_factor_sensor_1_ != nullptr && this->read_(CSE7761_REG_POWERFACTOR, 3, &raw_reg)) {
|
||||
// ADC2ON makes the chip report channel A
|
||||
int24_t pf{static_cast<int32_t>(raw_reg)};
|
||||
this->power_factor_sensor_1_->publish_state(static_cast<float>(static_cast<int32_t>(pf)) / 0x800000);
|
||||
}
|
||||
|
||||
for (uint8_t channel = 0; channel < 2; channel++) {
|
||||
// Active power = PowerPA * PowerPAC * 1000 / 0x80000000
|
||||
float active_power =
|
||||
static_cast<float>(this->data_.active_power[channel]) / this->coefficient_by_unit_(POWER_PAC); // W
|
||||
float amps = static_cast<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);
|
||||
sensor::Sensor *energy_sensor = this->energy_sensors_[channel];
|
||||
CSE7761EnergyChannel &energy = this->energy_[channel];
|
||||
uint8_t energy_reg = (channel == 0) ? CSE7761_REG_ENERGY_PA : CSE7761_REG_ENERGY_PB;
|
||||
uint32_t raw = 0;
|
||||
if (energy_sensor != nullptr && energy.wh_per_pulse > 0.0f && this->read_(energy_reg, 3, &raw)) {
|
||||
// 24-bit counter, not cleared on read
|
||||
energy.pulses += (raw - energy.last_pulses) & 0xFFFFFF;
|
||||
energy.last_pulses = raw;
|
||||
energy_sensor->publish_state(energy.pulses * energy.wh_per_pulse);
|
||||
}
|
||||
if (channel == 0) {
|
||||
if (this->power_sensor_1_ != nullptr) {
|
||||
this->power_sensor_1_->publish_state(active_power);
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/datatypes.h"
|
||||
|
||||
namespace esphome::cse7761 {
|
||||
|
||||
struct CSE7761DataStruct {
|
||||
uint32_t frequency = 0;
|
||||
uint32_t voltage_rms = 0;
|
||||
uint32_t current_rms[2] = {0};
|
||||
int32_t active_power[2] = {0};
|
||||
@@ -15,14 +15,26 @@ struct CSE7761DataStruct {
|
||||
bool ready = false;
|
||||
};
|
||||
|
||||
struct CSE7761EnergyChannel {
|
||||
uint32_t last_pulses{0};
|
||||
uint32_t pulses{0};
|
||||
float wh_per_pulse{0.0f};
|
||||
};
|
||||
|
||||
/// This class implements support for the CSE7761 UART power sensor.
|
||||
class CSE7761Component final : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_frequency_sensor(sensor::Sensor *frequency_sensor) { frequency_sensor_ = frequency_sensor; }
|
||||
void set_power_factor_1_sensor(sensor::Sensor *power_factor_sensor_1) {
|
||||
power_factor_sensor_1_ = power_factor_sensor_1;
|
||||
}
|
||||
void set_active_power_1_sensor(sensor::Sensor *power_sensor_1) { power_sensor_1_ = power_sensor_1; }
|
||||
void set_current_1_sensor(sensor::Sensor *current_sensor_1) { current_sensor_1_ = current_sensor_1; }
|
||||
void set_energy_1_sensor(sensor::Sensor *energy_sensor_1) { this->energy_sensors_[0] = energy_sensor_1; }
|
||||
void set_active_power_2_sensor(sensor::Sensor *power_sensor_2) { power_sensor_2_ = power_sensor_2; }
|
||||
void set_current_2_sensor(sensor::Sensor *current_sensor_2) { current_sensor_2_ = current_sensor_2; }
|
||||
void set_energy_2_sensor(sensor::Sensor *energy_sensor_2) { this->energy_sensors_[1] = energy_sensor_2; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
@@ -30,14 +42,19 @@ class CSE7761Component final : public PollingComponent, public uart::UARTDevice
|
||||
protected:
|
||||
// Sensors
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *frequency_sensor_{nullptr};
|
||||
sensor::Sensor *power_factor_sensor_1_{nullptr};
|
||||
sensor::Sensor *power_sensor_1_{nullptr};
|
||||
sensor::Sensor *current_sensor_1_{nullptr};
|
||||
sensor::Sensor *power_sensor_2_{nullptr};
|
||||
sensor::Sensor *current_sensor_2_{nullptr};
|
||||
sensor::Sensor *energy_sensors_[2]{nullptr, nullptr};
|
||||
CSE7761DataStruct data_;
|
||||
CSE7761EnergyChannel energy_[2];
|
||||
|
||||
void write_(uint8_t reg, uint16_t data);
|
||||
bool read_once_(uint8_t reg, uint8_t size, uint32_t *value);
|
||||
bool read_(uint8_t reg, uint8_t size, uint32_t *value);
|
||||
uint32_t read_(uint8_t reg, uint8_t size);
|
||||
uint32_t coefficient_by_unit_(uint32_t unit);
|
||||
bool chip_init_();
|
||||
|
||||
@@ -2,15 +2,22 @@ import esphome.codegen as cg
|
||||
from esphome.components import sensor, uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_FREQUENCY,
|
||||
CONF_ID,
|
||||
CONF_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_FREQUENCY,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_POWER_FACTOR,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_AMPERE,
|
||||
UNIT_HERTZ,
|
||||
UNIT_VOLT,
|
||||
UNIT_WATT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
from esphome.types import ConfigType
|
||||
|
||||
@@ -26,6 +33,9 @@ CONF_CURRENT_1 = "current_1"
|
||||
CONF_CURRENT_2 = "current_2"
|
||||
CONF_ACTIVE_POWER_1 = "active_power_1"
|
||||
CONF_ACTIVE_POWER_2 = "active_power_2"
|
||||
CONF_ENERGY_1 = "energy_1"
|
||||
CONF_ENERGY_2 = "energy_2"
|
||||
CONF_POWER_FACTOR_1 = "power_factor_1"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
@@ -37,6 +47,17 @@ CONFIG_SCHEMA = (
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_FREQUENCY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HERTZ,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_FREQUENCY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER_FACTOR_1): sensor.sensor_schema(
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER_FACTOR,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
@@ -61,6 +82,18 @@ CONFIG_SCHEMA = (
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ENERGY_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT_HOURS,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
cv.Optional(CONF_ENERGY_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT_HOURS,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_TOTAL_INCREASING,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
@@ -83,15 +116,13 @@ async def to_code(config: ConfigType) -> None:
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
for key in [
|
||||
CONF_VOLTAGE,
|
||||
CONF_CURRENT_1,
|
||||
CONF_CURRENT_2,
|
||||
CONF_ACTIVE_POWER_1,
|
||||
CONF_ACTIVE_POWER_2,
|
||||
]:
|
||||
if key not in config:
|
||||
continue
|
||||
conf = config[key]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(getattr(var, f"set_{key}_sensor")(sens))
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_VOLTAGE, var.set_voltage_sensor)
|
||||
await sensors(CONF_FREQUENCY, var.set_frequency_sensor)
|
||||
await sensors(CONF_POWER_FACTOR_1, var.set_power_factor_1_sensor)
|
||||
await sensors(CONF_CURRENT_1, var.set_current_1_sensor)
|
||||
await sensors(CONF_CURRENT_2, var.set_current_2_sensor)
|
||||
await sensors(CONF_ACTIVE_POWER_1, var.set_active_power_1_sensor)
|
||||
await sensors(CONF_ACTIVE_POWER_2, var.set_active_power_2_sensor)
|
||||
await sensors(CONF_ENERGY_1, var.set_energy_1_sensor)
|
||||
await sensors(CONF_ENERGY_2, var.set_energy_2_sensor)
|
||||
|
||||
@@ -2,6 +2,10 @@ sensor:
|
||||
- platform: cse7761
|
||||
voltage:
|
||||
name: CSE7761 Voltage
|
||||
frequency:
|
||||
name: CSE7761 Frequency
|
||||
power_factor_1:
|
||||
name: CSE7761 Power Factor 1
|
||||
current_1:
|
||||
name: CSE7761 Current 1
|
||||
current_2:
|
||||
@@ -10,3 +14,7 @@ sensor:
|
||||
name: CSE7761 Active Power 1
|
||||
active_power_2:
|
||||
name: CSE7761 Active Power 2
|
||||
energy_1:
|
||||
name: CSE7761 Energy 1
|
||||
energy_2:
|
||||
name: CSE7761 Energy 2
|
||||
|
||||
Reference in New Issue
Block a user