From 04c3a43c17a1383597696be0483ffbebb8bedaad Mon Sep 17 00:00:00 2001 From: Sourabh Jaiswal Date: Wed, 14 Jul 2021 06:35:51 +0530 Subject: [PATCH] Added support for havells_solar sensor (#1988) Co-authored-by: Oxan van Leeuwen Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/havells_solar/__init__.py | 0 .../havells_solar/havells_solar.cpp | 165 ++++++++++ .../components/havells_solar/havells_solar.h | 115 +++++++ .../havells_solar/havells_solar_registers.h | 49 +++ esphome/components/havells_solar/sensor.py | 293 ++++++++++++++++++ 6 files changed, 623 insertions(+) create mode 100644 esphome/components/havells_solar/__init__.py create mode 100644 esphome/components/havells_solar/havells_solar.cpp create mode 100644 esphome/components/havells_solar/havells_solar.h create mode 100644 esphome/components/havells_solar/havells_solar_registers.h create mode 100644 esphome/components/havells_solar/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 2a57e5d81a..d6769800cd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -47,6 +47,7 @@ esphome/components/fingerprint_grow/* @OnFreund @loongyh esphome/components/globals/* @esphome/core esphome/components/gpio/* @esphome/core esphome/components/gps/* @coogle +esphome/components/havells_solar/* @sourabhjaiswal esphome/components/homeassistant/* @OttoWinter esphome/components/i2c/* @esphome/core esphome/components/improv/* @jesserockz diff --git a/esphome/components/havells_solar/__init__.py b/esphome/components/havells_solar/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/havells_solar/havells_solar.cpp b/esphome/components/havells_solar/havells_solar.cpp new file mode 100644 index 0000000000..f029df10ad --- /dev/null +++ b/esphome/components/havells_solar/havells_solar.cpp @@ -0,0 +1,165 @@ +#include "havells_solar.h" +#include "havells_solar_registers.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace havells_solar { + +static const char *const TAG = "havells_solar"; + +static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x03; +static const uint8_t MODBUS_REGISTER_COUNT = 48; // 48 x 16-bit registers + +void HavellsSolar::on_modbus_data(const std::vector &data) { + if (data.size() < MODBUS_REGISTER_COUNT * 2) { + ESP_LOGW(TAG, "Invalid size for HavellsSolar!"); + return; + } + + /* Usage: returns the float value of 1 register read by modbus + Arg1: Register address * number of bytes per register + Arg2: Multiplier for final register value + */ + auto havells_solar_get_2_registers = [&](size_t i, float unit) -> float { + uint32_t temp = encode_uint32(data[i], data[i + 1], data[i + 2], data[i + 3]); + return temp * unit; + }; + + /* Usage: returns the float value of 2 registers read by modbus + Arg1: Register address * number of bytes per register + Arg2: Multiplier for final register value + */ + auto havells_solar_get_1_register = [&](size_t i, float unit) -> float { + uint16_t temp = encode_uint16(data[i], data[i + 1]); + return temp * unit; + }; + + for (uint8_t i = 0; i < 3; i++) { + auto phase = this->phases_[i]; + if (!phase.setup) + continue; + + float voltage = havells_solar_get_1_register(HAVELLS_PHASE_1_VOLTAGE * 2 + (i * 4), ONE_DEC_UNIT); + float current = havells_solar_get_1_register(HAVELLS_PHASE_1_CURRENT * 2 + (i * 4), TWO_DEC_UNIT); + + if (phase.voltage_sensor_ != nullptr) + phase.voltage_sensor_->publish_state(voltage); + if (phase.current_sensor_ != nullptr) + phase.current_sensor_->publish_state(current); + } + + for (uint8_t i = 0; i < 2; i++) { + auto pv = this->pvs_[i]; + if (!pv.setup) + continue; + + float voltage = havells_solar_get_1_register(HAVELLS_PV_1_VOLTAGE * 2 + (i * 4), ONE_DEC_UNIT); + float current = havells_solar_get_1_register(HAVELLS_PV_1_CURRENT * 2 + (i * 4), TWO_DEC_UNIT); + float active_power = havells_solar_get_1_register(HAVELLS_PV_1_POWER * 2 + (i * 2), MULTIPLY_TEN_UNIT); + float voltage_sampled_by_secondary_cpu = + havells_solar_get_1_register(HAVELLS_PV1_VOLTAGE_SAMPLED_BY_SECONDARY_CPU * 2 + (i * 2), ONE_DEC_UNIT); + float insulation_of_p_to_ground = + havells_solar_get_1_register(HAVELLS_PV1_INSULATION_OF_P_TO_GROUND * 2 + (i * 2), NO_DEC_UNIT); + + if (pv.voltage_sensor_ != nullptr) + pv.voltage_sensor_->publish_state(voltage); + if (pv.current_sensor_ != nullptr) + pv.current_sensor_->publish_state(current); + if (pv.active_power_sensor_ != nullptr) + pv.active_power_sensor_->publish_state(active_power); + if (pv.voltage_sampled_by_secondary_cpu_sensor_ != nullptr) + pv.voltage_sampled_by_secondary_cpu_sensor_->publish_state(voltage_sampled_by_secondary_cpu); + if (pv.insulation_of_p_to_ground_sensor_ != nullptr) + pv.insulation_of_p_to_ground_sensor_->publish_state(insulation_of_p_to_ground); + } + + float frequency = havells_solar_get_1_register(HAVELLS_GRID_FREQUENCY * 2, TWO_DEC_UNIT); + float active_power = havells_solar_get_1_register(HAVELLS_SYSTEM_ACTIVE_POWER * 2, MULTIPLY_TEN_UNIT); + float reactive_power = havells_solar_get_1_register(HAVELLS_SYSTEM_REACTIVE_POWER * 2, TWO_DEC_UNIT); + float today_production = havells_solar_get_1_register(HAVELLS_TODAY_PRODUCTION * 2, TWO_DEC_UNIT); + float total_energy_production = havells_solar_get_2_registers(HAVELLS_TOTAL_ENERGY_PRODUCTION * 2, NO_DEC_UNIT); + float total_generation_time = havells_solar_get_2_registers(HAVELLS_TOTAL_GENERATION_TIME * 2, NO_DEC_UNIT); + float today_generation_time = havells_solar_get_1_register(HAVELLS_TODAY_GENERATION_TIME * 2, NO_DEC_UNIT); + float inverter_module_temp = havells_solar_get_1_register(HAVELLS_INVERTER_MODULE_TEMP * 2, NO_DEC_UNIT); + float inverter_inner_temp = havells_solar_get_1_register(HAVELLS_INVERTER_INNER_TEMP * 2, NO_DEC_UNIT); + float inverter_bus_voltage = havells_solar_get_1_register(HAVELLS_INVERTER_BUS_VOLTAGE * 2, NO_DEC_UNIT); + float insulation_pv_n_to_ground = havells_solar_get_1_register(HAVELLS_INSULATION_OF_PV_N_TO_GROUND * 2, NO_DEC_UNIT); + float gfci_value = havells_solar_get_1_register(HAVELLS_GFCI_VALUE * 2, NO_DEC_UNIT); + float dci_of_r = havells_solar_get_1_register(HAVELLS_DCI_OF_R * 2, NO_DEC_UNIT); + float dci_of_s = havells_solar_get_1_register(HAVELLS_DCI_OF_S * 2, NO_DEC_UNIT); + float dci_of_t = havells_solar_get_1_register(HAVELLS_DCI_OF_T * 2, NO_DEC_UNIT); + + if (this->frequency_sensor_ != nullptr) + this->frequency_sensor_->publish_state(frequency); + if (this->active_power_sensor_ != nullptr) + this->active_power_sensor_->publish_state(active_power); + if (this->reactive_power_sensor_ != nullptr) + this->reactive_power_sensor_->publish_state(reactive_power); + if (this->today_production_sensor_ != nullptr) + this->today_production_sensor_->publish_state(today_production); + if (this->total_energy_production_sensor_ != nullptr) + this->total_energy_production_sensor_->publish_state(total_energy_production); + if (this->total_generation_time_sensor_ != nullptr) + this->total_generation_time_sensor_->publish_state(total_generation_time); + if (this->today_generation_time_sensor_ != nullptr) + this->today_generation_time_sensor_->publish_state(today_generation_time); + if (this->inverter_module_temp_sensor_ != nullptr) + this->inverter_module_temp_sensor_->publish_state(inverter_module_temp); + if (this->inverter_inner_temp_sensor_ != nullptr) + this->inverter_inner_temp_sensor_->publish_state(inverter_inner_temp); + if (this->inverter_bus_voltage_sensor_ != nullptr) + this->inverter_bus_voltage_sensor_->publish_state(inverter_bus_voltage); + if (this->insulation_pv_n_to_ground_sensor_ != nullptr) + this->insulation_pv_n_to_ground_sensor_->publish_state(insulation_pv_n_to_ground); + if (this->gfci_value_sensor_ != nullptr) + this->gfci_value_sensor_->publish_state(gfci_value); + if (this->dci_of_r_sensor_ != nullptr) + this->dci_of_r_sensor_->publish_state(dci_of_r); + if (this->dci_of_s_sensor_ != nullptr) + this->dci_of_s_sensor_->publish_state(dci_of_s); + if (this->dci_of_t_sensor_ != nullptr) + this->dci_of_t_sensor_->publish_state(dci_of_t); +} + +void HavellsSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); } +void HavellsSolar::dump_config() { + ESP_LOGCONFIG(TAG, "HAVELLS Solar:"); + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); + for (uint8_t i = 0; i < 3; i++) { + auto phase = this->phases_[i]; + if (!phase.setup) + continue; + ESP_LOGCONFIG(TAG, " Phase %c", i + 'A'); + LOG_SENSOR(" ", "Voltage", phase.voltage_sensor_); + LOG_SENSOR(" ", "Current", phase.current_sensor_); + } + for (uint8_t i = 0; i < 2; i++) { + auto pv = this->pvs_[i]; + if (!pv.setup) + continue; + ESP_LOGCONFIG(TAG, " PV %d", i + 1); + LOG_SENSOR(" ", "Voltage", pv.voltage_sensor_); + LOG_SENSOR(" ", "Current", pv.current_sensor_); + LOG_SENSOR(" ", "Active Power", pv.active_power_sensor_); + LOG_SENSOR(" ", "Voltage Sampled By Secondary CPU", pv.voltage_sampled_by_secondary_cpu_sensor_); + LOG_SENSOR(" ", "Insulation Of PV+ To Ground", pv.insulation_of_p_to_ground_sensor_); + } + LOG_SENSOR(" ", "Frequency", this->frequency_sensor_); + LOG_SENSOR(" ", "Active Power", this->active_power_sensor_); + LOG_SENSOR(" ", "Reactive Power", this->reactive_power_sensor_); + LOG_SENSOR(" ", "Today Generation", this->today_production_sensor_); + LOG_SENSOR(" ", "Total Generation", this->total_energy_production_sensor_); + LOG_SENSOR(" ", "Total Generation Time", this->total_generation_time_sensor_); + LOG_SENSOR(" ", "Today Generation Time", this->today_generation_time_sensor_); + LOG_SENSOR(" ", "Inverter Module Temp", this->inverter_module_temp_sensor_); + LOG_SENSOR(" ", "Inverter Inner Temp", this->inverter_inner_temp_sensor_); + LOG_SENSOR(" ", "Inverter Bus Voltage", this->inverter_bus_voltage_sensor_); + LOG_SENSOR(" ", "Insulation Of PV- To Ground", this->insulation_pv_n_to_ground_sensor_); + LOG_SENSOR(" ", "GFCI Value", this->gfci_value_sensor_); + LOG_SENSOR(" ", "DCI Of R", this->dci_of_r_sensor_); + LOG_SENSOR(" ", "DCI Of S", this->dci_of_s_sensor_); + LOG_SENSOR(" ", "DCI Of T", this->dci_of_t_sensor_); +} + +} // namespace havells_solar +} // namespace esphome diff --git a/esphome/components/havells_solar/havells_solar.h b/esphome/components/havells_solar/havells_solar.h new file mode 100644 index 0000000000..2ccc8be3d4 --- /dev/null +++ b/esphome/components/havells_solar/havells_solar.h @@ -0,0 +1,115 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/modbus/modbus.h" + +namespace esphome { +namespace havells_solar { + +class HavellsSolar : public PollingComponent, public modbus::ModbusDevice { + public: + void set_voltage_sensor(uint8_t phase, sensor::Sensor *voltage_sensor) { + this->phases_[phase].setup = true; + this->phases_[phase].voltage_sensor_ = voltage_sensor; + } + void set_current_sensor(uint8_t phase, sensor::Sensor *current_sensor) { + this->phases_[phase].setup = true; + this->phases_[phase].current_sensor_ = current_sensor; + } + void set_voltage_sensor_pv(uint8_t pv, sensor::Sensor *voltage_sensor) { + this->pvs_[pv].setup = true; + this->pvs_[pv].voltage_sensor_ = voltage_sensor; + } + void set_current_sensor_pv(uint8_t pv, sensor::Sensor *current_sensor) { + this->pvs_[pv].setup = true; + this->pvs_[pv].current_sensor_ = current_sensor; + } + void set_active_power_sensor_pv(uint8_t pv, sensor::Sensor *active_power_sensor) { + this->pvs_[pv].setup = true; + this->pvs_[pv].active_power_sensor_ = active_power_sensor; + } + void set_voltage_sampled_by_secondary_cpu_sensor_pv(uint8_t pv, + sensor::Sensor *voltage_sampled_by_secondary_cpu_sensor) { + this->pvs_[pv].setup = true; + this->pvs_[pv].voltage_sampled_by_secondary_cpu_sensor_ = voltage_sampled_by_secondary_cpu_sensor; + } + void set_insulation_of_p_to_ground_sensor_pv(uint8_t pv, sensor::Sensor *insulation_of_p_to_ground_sensor) { + this->pvs_[pv].setup = true; + this->pvs_[pv].insulation_of_p_to_ground_sensor_ = insulation_of_p_to_ground_sensor; + } + void set_frequency_sensor(sensor::Sensor *frequency_sensor) { this->frequency_sensor_ = frequency_sensor; } + void set_active_power_sensor(sensor::Sensor *active_power_sensor) { + this->active_power_sensor_ = active_power_sensor; + } + void set_reactive_power_sensor(sensor::Sensor *reactive_power_sensor) { + this->reactive_power_sensor_ = reactive_power_sensor; + } + void set_today_production_sensor(sensor::Sensor *today_production_sensor) { + this->today_production_sensor_ = today_production_sensor; + } + void set_total_energy_production_sensor(sensor::Sensor *total_energy_production_sensor) { + this->total_energy_production_sensor_ = total_energy_production_sensor; + } + void set_total_generation_time_sensor(sensor::Sensor *total_generation_time_sensor) { + this->total_generation_time_sensor_ = total_generation_time_sensor; + } + void set_today_generation_time_sensor(sensor::Sensor *today_generation_time_sensor) { + this->today_generation_time_sensor_ = today_generation_time_sensor; + } + void set_inverter_module_temp_sensor(sensor::Sensor *inverter_module_temp_sensor) { + this->inverter_module_temp_sensor_ = inverter_module_temp_sensor; + } + void set_inverter_inner_temp_sensor(sensor::Sensor *inverter_inner_temp_sensor) { + this->inverter_inner_temp_sensor_ = inverter_inner_temp_sensor; + } + void set_inverter_bus_voltage_sensor(sensor::Sensor *inverter_bus_voltage_sensor) { + this->inverter_bus_voltage_sensor_ = inverter_bus_voltage_sensor; + } + void set_insulation_pv_n_to_ground_sensor(sensor::Sensor *insulation_pv_n_to_ground_sensor) { + this->insulation_pv_n_to_ground_sensor_ = insulation_pv_n_to_ground_sensor; + } + void set_gfci_value_sensor(sensor::Sensor *gfci_value_sensor) { this->gfci_value_sensor_ = gfci_value_sensor; } + void set_dci_of_r_sensor(sensor::Sensor *dci_of_r_sensor) { this->dci_of_r_sensor_ = dci_of_r_sensor; } + void set_dci_of_s_sensor(sensor::Sensor *dci_of_s_sensor) { this->dci_of_s_sensor_ = dci_of_s_sensor; } + void set_dci_of_t_sensor(sensor::Sensor *dci_of_t_sensor) { this->dci_of_t_sensor_ = dci_of_t_sensor; } + + void update() override; + + void on_modbus_data(const std::vector &data) override; + + void dump_config() override; + + protected: + struct HAVELLSPhase { + bool setup{false}; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + } phases_[3]; + struct HAVELLSPV { + bool setup{false}; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *active_power_sensor_{nullptr}; + sensor::Sensor *voltage_sampled_by_secondary_cpu_sensor_{nullptr}; + sensor::Sensor *insulation_of_p_to_ground_sensor_{nullptr}; + } pvs_[2]; + sensor::Sensor *frequency_sensor_{nullptr}; + sensor::Sensor *active_power_sensor_{nullptr}; + sensor::Sensor *reactive_power_sensor_{nullptr}; + sensor::Sensor *today_production_sensor_{nullptr}; + sensor::Sensor *total_energy_production_sensor_{nullptr}; + sensor::Sensor *total_generation_time_sensor_{nullptr}; + sensor::Sensor *today_generation_time_sensor_{nullptr}; + sensor::Sensor *inverter_module_temp_sensor_{nullptr}; + sensor::Sensor *inverter_inner_temp_sensor_{nullptr}; + sensor::Sensor *inverter_bus_voltage_sensor_{nullptr}; + sensor::Sensor *insulation_pv_n_to_ground_sensor_{nullptr}; + sensor::Sensor *gfci_value_sensor_{nullptr}; + sensor::Sensor *dci_of_r_sensor_{nullptr}; + sensor::Sensor *dci_of_s_sensor_{nullptr}; + sensor::Sensor *dci_of_t_sensor_{nullptr}; +}; + +} // namespace havells_solar +} // namespace esphome diff --git a/esphome/components/havells_solar/havells_solar_registers.h b/esphome/components/havells_solar/havells_solar_registers.h new file mode 100644 index 0000000000..8e1cb3ec7a --- /dev/null +++ b/esphome/components/havells_solar/havells_solar_registers.h @@ -0,0 +1,49 @@ +#pragma once +namespace esphome { +namespace havells_solar { + +static const float TWO_DEC_UNIT = 0.01; +static const float ONE_DEC_UNIT = 0.1; +static const float NO_DEC_UNIT = 1; +static const float MULTIPLY_TEN_UNIT = 10; + +/* PV Input Message */ +static const uint16_t HAVELLS_PV_1_VOLTAGE = 0x0006; +static const uint16_t HAVELLS_PV_1_CURRENT = 0x0007; +static const uint16_t HAVELLS_PV_2_VOLTAGE = 0x0008; +static const uint16_t HAVELLS_PV_2_CURRENT = 0x0009; +static const uint16_t HAVELLS_PV_1_POWER = 0x000A; +static const uint16_t HAVELLS_PV_2_POWER = 0x000B; + +/* Output Grid Message */ +static const uint16_t HAVELLS_SYSTEM_ACTIVE_POWER = 0x000C; +static const uint16_t HAVELLS_SYSTEM_REACTIVE_POWER = 0x000D; +static const uint16_t HAVELLS_GRID_FREQUENCY = 0x000E; +static const uint16_t HAVELLS_PHASE_1_VOLTAGE = 0x000F; +static const uint16_t HAVELLS_PHASE_1_CURRENT = 0x0010; +static const uint16_t HAVELLS_PHASE_2_VOLTAGE = 0x0011; +static const uint16_t HAVELLS_PHASE_2_CURRENT = 0x0012; +static const uint16_t HAVELLS_PHASE_3_VOLTAGE = 0x0013; +static const uint16_t HAVELLS_PHASE_3_CURRENT = 0x0014; + +/* Inverter Generation message */ +static const uint16_t HAVELLS_TOTAL_ENERGY_PRODUCTION = 0x0015; +static const uint16_t HAVELLS_TOTAL_GENERATION_TIME = 0x0017; +static const uint16_t HAVELLS_TODAY_PRODUCTION = 0x0019; +static const uint16_t HAVELLS_TODAY_GENERATION_TIME = 0x001A; + +/* Inverter inner message */ +static const uint16_t HAVELLS_INVERTER_MODULE_TEMP = 0x001B; +static const uint16_t HAVELLS_INVERTER_INNER_TEMP = 0x001C; +static const uint16_t HAVELLS_INVERTER_BUS_VOLTAGE = 0x001D; +static const uint16_t HAVELLS_PV1_VOLTAGE_SAMPLED_BY_SECONDARY_CPU = 0x001E; +static const uint16_t HAVELLS_PV2_VOLTAGE_SAMPLED_BY_SECONDARY_CPU = 0x001F; +static const uint16_t HAVELLS_PV1_INSULATION_OF_P_TO_GROUND = 0x0024; +static const uint16_t HAVELLS_PV2_INSULATION_OF_P_TO_GROUND = 0x0025; +static const uint16_t HAVELLS_INSULATION_OF_PV_N_TO_GROUND = 0x0026; +static const uint16_t HAVELLS_GFCI_VALUE = 0x002A; +static const uint16_t HAVELLS_DCI_OF_R = 0x002B; +static const uint16_t HAVELLS_DCI_OF_S = 0x002C; +static const uint16_t HAVELLS_DCI_OF_T = 0x002D; +} // namespace havells_solar +} // namespace esphome diff --git a/esphome/components/havells_solar/sensor.py b/esphome/components/havells_solar/sensor.py new file mode 100644 index 0000000000..7d1e2be581 --- /dev/null +++ b/esphome/components/havells_solar/sensor.py @@ -0,0 +1,293 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, modbus +from esphome.const import ( + CONF_ACTIVE_POWER, + CONF_CURRENT, + CONF_FREQUENCY, + CONF_ID, + CONF_REACTIVE_POWER, + CONF_VOLTAGE, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_VOLTAGE, + ICON_CURRENT_AC, + ICON_EMPTY, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_NONE, + UNIT_AMPERE, + UNIT_DEGREES, + UNIT_HERTZ, + UNIT_MINUTE, + UNIT_VOLT, + UNIT_VOLT_AMPS_REACTIVE, + UNIT_WATT, +) + +CONF_PHASE_A = "phase_a" +CONF_PHASE_B = "phase_b" +CONF_PHASE_C = "phase_c" +CONF_ENERGY_PRODUCTION_DAY = "energy_production_day" +CONF_TOTAL_ENERGY_PRODUCTION = "total_energy_production" +CONF_TOTAL_GENERATION_TIME = "total_generation_time" +CONF_TODAY_GENERATION_TIME = "today_generation_time" +CONF_PV1 = "pv1" +CONF_PV2 = "pv2" +UNIT_KILOWATT_HOURS = "kWh" +UNIT_HOURS = "h" +UNIT_KOHM = "kΩ" +UNIT_MILLIAMPERE = "mA" + + +CONF_INVERTER_MODULE_TEMP = "inverter_module_temp" +CONF_INVERTER_INNER_TEMP = "inverter_inner_temp" +CONF_INVERTER_BUS_VOLTAGE = "inverter_bus_voltage" +CONF_VOLTAGE_SAMPLED_BY_SECONDARY_CPU = "voltage_sampled_by_secondary_cpu" +CONF_INSULATION_OF_P_TO_GROUND = "insulation_of_p_to_ground" +CONF_INSULATION_OF_PV_N_TO_GROUND = "insulation_of_pv_n_to_ground" +CONF_GFCI_VALUE = "gfci_value" +CONF_DCI_OF_R = "dci_of_r" +CONF_DCI_OF_S = "dci_of_s" +CONF_DCI_OF_T = "dci_of_t" + + +AUTO_LOAD = ["modbus"] +CODEOWNERS = ["@sourabhjaiswal"] + +havells_solar_ns = cg.esphome_ns.namespace("havells_solar") +HavellsSolar = havells_solar_ns.class_( + "HavellsSolar", cg.PollingComponent, modbus.ModbusDevice +) + +PHASE_SENSORS = { + CONF_VOLTAGE: sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE), + CONF_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + ), +} +PV_SENSORS = { + CONF_VOLTAGE: sensor.sensor_schema(UNIT_VOLT, ICON_EMPTY, 2, DEVICE_CLASS_VOLTAGE), + CONF_CURRENT: sensor.sensor_schema( + UNIT_AMPERE, ICON_EMPTY, 2, DEVICE_CLASS_CURRENT, STATE_CLASS_MEASUREMENT + ), + CONF_ACTIVE_POWER: sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + CONF_VOLTAGE_SAMPLED_BY_SECONDARY_CPU: sensor.sensor_schema( + UNIT_VOLT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + CONF_INSULATION_OF_P_TO_GROUND: sensor.sensor_schema( + UNIT_KOHM, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), +} + +PHASE_SCHEMA = cv.Schema( + {cv.Optional(sensor): schema for sensor, schema in PHASE_SENSORS.items()} +) +PV_SCHEMA = cv.Schema( + {cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()} +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(HavellsSolar), + cv.Optional(CONF_PHASE_A): PHASE_SCHEMA, + cv.Optional(CONF_PHASE_B): PHASE_SCHEMA, + cv.Optional(CONF_PHASE_C): PHASE_SCHEMA, + cv.Optional(CONF_PV1): PV_SCHEMA, + cv.Optional(CONF_PV2): PV_SCHEMA, + cv.Optional(CONF_FREQUENCY): sensor.sensor_schema( + UNIT_HERTZ, + ICON_CURRENT_AC, + 2, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACTIVE_POWER): sensor.sensor_schema( + UNIT_WATT, ICON_EMPTY, 0, DEVICE_CLASS_POWER, STATE_CLASS_MEASUREMENT + ), + cv.Optional(CONF_REACTIVE_POWER): sensor.sensor_schema( + UNIT_VOLT_AMPS_REACTIVE, + ICON_EMPTY, + 2, + DEVICE_CLASS_POWER, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ENERGY_PRODUCTION_DAY): sensor.sensor_schema( + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 2, + DEVICE_CLASS_ENERGY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_TOTAL_ENERGY_PRODUCTION): sensor.sensor_schema( + UNIT_KILOWATT_HOURS, + ICON_EMPTY, + 0, + DEVICE_CLASS_ENERGY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_TOTAL_GENERATION_TIME): sensor.sensor_schema( + UNIT_HOURS, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_TODAY_GENERATION_TIME): sensor.sensor_schema( + UNIT_MINUTE, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_NONE, + ), + cv.Optional(CONF_INVERTER_MODULE_TEMP): sensor.sensor_schema( + UNIT_DEGREES, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_INVERTER_INNER_TEMP): sensor.sensor_schema( + UNIT_DEGREES, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_INVERTER_BUS_VOLTAGE): sensor.sensor_schema( + UNIT_VOLT, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_INSULATION_OF_PV_N_TO_GROUND): sensor.sensor_schema( + UNIT_KOHM, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_GFCI_VALUE): sensor.sensor_schema( + UNIT_MILLIAMPERE, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DCI_OF_R): sensor.sensor_schema( + UNIT_MILLIAMPERE, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DCI_OF_S): sensor.sensor_schema( + UNIT_MILLIAMPERE, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DCI_OF_T): sensor.sensor_schema( + UNIT_MILLIAMPERE, + ICON_EMPTY, + 0, + DEVICE_CLASS_EMPTY, + STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("10s")) + .extend(modbus.modbus_device_schema(0x01)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await modbus.register_modbus_device(var, config) + + if CONF_FREQUENCY in config: + sens = await sensor.new_sensor(config[CONF_FREQUENCY]) + cg.add(var.set_frequency_sensor(sens)) + + if CONF_ACTIVE_POWER in config: + sens = await sensor.new_sensor(config[CONF_ACTIVE_POWER]) + cg.add(var.set_active_power_sensor(sens)) + + if CONF_REACTIVE_POWER in config: + sens = await sensor.new_sensor(config[CONF_REACTIVE_POWER]) + cg.add(var.set_reactive_power_sensor(sens)) + + if CONF_ENERGY_PRODUCTION_DAY in config: + sens = await sensor.new_sensor(config[CONF_ENERGY_PRODUCTION_DAY]) + cg.add(var.set_today_production_sensor(sens)) + + if CONF_TOTAL_ENERGY_PRODUCTION in config: + sens = await sensor.new_sensor(config[CONF_TOTAL_ENERGY_PRODUCTION]) + cg.add(var.set_total_energy_production_sensor(sens)) + + if CONF_TOTAL_GENERATION_TIME in config: + sens = await sensor.new_sensor(config[CONF_TOTAL_GENERATION_TIME]) + cg.add(var.set_total_generation_time_sensor(sens)) + + if CONF_TODAY_GENERATION_TIME in config: + sens = await sensor.new_sensor(config[CONF_TODAY_GENERATION_TIME]) + cg.add(var.set_today_generation_time_sensor(sens)) + + if CONF_INVERTER_MODULE_TEMP in config: + sens = await sensor.new_sensor(config[CONF_INVERTER_MODULE_TEMP]) + cg.add(var.set_inverter_module_temp_sensor(sens)) + + if CONF_INVERTER_INNER_TEMP in config: + sens = await sensor.new_sensor(config[CONF_INVERTER_INNER_TEMP]) + cg.add(var.set_inverter_inner_temp_sensor(sens)) + + if CONF_INVERTER_BUS_VOLTAGE in config: + sens = await sensor.new_sensor(config[CONF_INVERTER_BUS_VOLTAGE]) + cg.add(var.set_inverter_bus_voltage_sensor(sens)) + + if CONF_INSULATION_OF_PV_N_TO_GROUND in config: + sens = await sensor.new_sensor(config[CONF_INSULATION_OF_PV_N_TO_GROUND]) + cg.add(var.set_insulation_pv_n_to_ground_sensor(sens)) + + if CONF_GFCI_VALUE in config: + sens = await sensor.new_sensor(config[CONF_GFCI_VALUE]) + cg.add(var.set_gfci_value_sensor(sens)) + + if CONF_DCI_OF_R in config: + sens = await sensor.new_sensor(config[CONF_DCI_OF_R]) + cg.add(var.set_dci_of_r_sensor(sens)) + + if CONF_DCI_OF_S in config: + sens = await sensor.new_sensor(config[CONF_DCI_OF_S]) + cg.add(var.set_dci_of_s_sensor(sens)) + + if CONF_DCI_OF_T in config: + sens = await sensor.new_sensor(config[CONF_DCI_OF_T]) + cg.add(var.set_dci_of_t_sensor(sens)) + + for i, phase in enumerate([CONF_PHASE_A, CONF_PHASE_B, CONF_PHASE_C]): + if phase not in config: + continue + + phase_config = config[phase] + for sensor_type in PHASE_SENSORS: + if sensor_type in phase_config: + sens = await sensor.new_sensor(phase_config[sensor_type]) + cg.add(getattr(var, f"set_{sensor_type}_sensor")(i, sens)) + + for i, pv in enumerate([CONF_PV1, CONF_PV2]): + if pv not in config: + continue + + pv_config = config[pv] + for sensor_type in pv_config: + if sensor_type in pv_config: + sens = await sensor.new_sensor(pv_config[sensor_type]) + cg.add(getattr(var, f"set_{sensor_type}_sensor_pv")(i, sens))