From cc53eb42b273631b14ddf3d0a0308372a58043e7 Mon Sep 17 00:00:00 2001
From: Nick Kinnan <nkinnan@users.noreply.github.com>
Date: Mon, 23 Sep 2024 20:53:13 -0700
Subject: [PATCH] Add CSE7766 reactive power (#7301)

---
 esphome/components/cse7766/cse7766.cpp | 11 +++++++++++
 esphome/components/cse7766/cse7766.h   |  4 ++++
 esphome/components/cse7766/sensor.py   | 12 ++++++++++++
 3 files changed, 27 insertions(+)

diff --git a/esphome/components/cse7766/cse7766.cpp b/esphome/components/cse7766/cse7766.cpp
index f1420aa127..47058badce 100644
--- a/esphome/components/cse7766/cse7766.cpp
+++ b/esphome/components/cse7766/cse7766.cpp
@@ -147,6 +147,7 @@ void CSE7766Component::parse_data_() {
   float power = 0.0f;
   if (power_cycle_exceeds_range) {
     // Datasheet: power cycle exceeding range means active power is 0
+    have_power = true;
     if (this->power_sensor_ != nullptr) {
       this->power_sensor_->publish_state(0.0f);
     }
@@ -178,6 +179,15 @@ void CSE7766Component::parse_data_() {
     if (this->apparent_power_sensor_ != nullptr) {
       this->apparent_power_sensor_->publish_state(apparent_power);
     }
+    if (have_power && this->reactive_power_sensor_ != nullptr) {
+      const float reactive_power = apparent_power - power;
+      if (reactive_power < 0.0f) {
+        ESP_LOGD(TAG, "Impossible reactive power: %.4f is negative", reactive_power);
+        this->reactive_power_sensor_->publish_state(0.0f);
+      } else {
+        this->reactive_power_sensor_->publish_state(reactive_power);
+      }
+    }
     if (this->power_factor_sensor_ != nullptr && (have_power || power_cycle_exceeds_range)) {
       float pf = NAN;
       if (apparent_power > 0) {
@@ -232,6 +242,7 @@ void CSE7766Component::dump_config() {
   LOG_SENSOR("  ", "Power", this->power_sensor_);
   LOG_SENSOR("  ", "Energy", this->energy_sensor_);
   LOG_SENSOR("  ", "Apparent Power", this->apparent_power_sensor_);
+  LOG_SENSOR("  ", "Reactive Power", this->reactive_power_sensor_);
   LOG_SENSOR("  ", "Power Factor", this->power_factor_sensor_);
   this->check_uart_settings(4800);
 }
diff --git a/esphome/components/cse7766/cse7766.h b/esphome/components/cse7766/cse7766.h
index 0b724d6bbb..5d89b3b75b 100644
--- a/esphome/components/cse7766/cse7766.h
+++ b/esphome/components/cse7766/cse7766.h
@@ -16,6 +16,9 @@ class CSE7766Component : public Component, public uart::UARTDevice {
   void set_apparent_power_sensor(sensor::Sensor *apparent_power_sensor) {
     apparent_power_sensor_ = apparent_power_sensor;
   }
+  void set_reactive_power_sensor(sensor::Sensor *reactive_power_sensor) {
+    reactive_power_sensor_ = reactive_power_sensor;
+  }
   void set_power_factor_sensor(sensor::Sensor *power_factor_sensor) { power_factor_sensor_ = power_factor_sensor; }
 
   void loop() override;
@@ -35,6 +38,7 @@ class CSE7766Component : public Component, public uart::UARTDevice {
   sensor::Sensor *power_sensor_{nullptr};
   sensor::Sensor *energy_sensor_{nullptr};
   sensor::Sensor *apparent_power_sensor_{nullptr};
+  sensor::Sensor *reactive_power_sensor_{nullptr};
   sensor::Sensor *power_factor_sensor_{nullptr};
   uint32_t cf_pulses_total_{0};
   uint16_t cf_pulses_last_{0};
diff --git a/esphome/components/cse7766/sensor.py b/esphome/components/cse7766/sensor.py
index b64dcf7de3..ecb59c4b5f 100644
--- a/esphome/components/cse7766/sensor.py
+++ b/esphome/components/cse7766/sensor.py
@@ -8,18 +8,21 @@ from esphome.const import (
     CONF_ID,
     CONF_POWER,
     CONF_POWER_FACTOR,
+    CONF_REACTIVE_POWER,
     CONF_VOLTAGE,
     DEVICE_CLASS_APPARENT_POWER,
     DEVICE_CLASS_CURRENT,
     DEVICE_CLASS_ENERGY,
     DEVICE_CLASS_POWER,
     DEVICE_CLASS_POWER_FACTOR,
+    DEVICE_CLASS_REACTIVE_POWER,
     DEVICE_CLASS_VOLTAGE,
     STATE_CLASS_MEASUREMENT,
     STATE_CLASS_TOTAL_INCREASING,
     UNIT_AMPERE,
     UNIT_VOLT,
     UNIT_VOLT_AMPS,
+    UNIT_VOLT_AMPS_REACTIVE,
     UNIT_WATT,
     UNIT_WATT_HOURS,
 )
@@ -62,6 +65,12 @@ CONFIG_SCHEMA = cv.Schema(
             device_class=DEVICE_CLASS_APPARENT_POWER,
             state_class=STATE_CLASS_MEASUREMENT,
         ),
+        cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema(
+            unit_of_measurement=UNIT_VOLT_AMPS_REACTIVE,
+            accuracy_decimals=1,
+            device_class=DEVICE_CLASS_REACTIVE_POWER,
+            state_class=STATE_CLASS_MEASUREMENT,
+        ),
         cv.Optional(CONF_POWER_FACTOR): sensor.sensor_schema(
             accuracy_decimals=2,
             device_class=DEVICE_CLASS_POWER_FACTOR,
@@ -94,6 +103,9 @@ async def to_code(config):
     if apparent_power_config := config.get(CONF_APPARENT_POWER):
         sens = await sensor.new_sensor(apparent_power_config)
         cg.add(var.set_apparent_power_sensor(sens))
+    if reactive_power_config := config.get(CONF_REACTIVE_POWER):
+        sens = await sensor.new_sensor(reactive_power_config)
+        cg.add(var.set_reactive_power_sensor(sens))
     if power_factor_config := config.get(CONF_POWER_FACTOR):
         sens = await sensor.new_sensor(power_factor_config)
         cg.add(var.set_power_factor_sensor(sens))