From 5e1535298a7fa26e1014fce92f5cbe82537081a8 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 10 Sep 2024 09:28:10 -0400 Subject: [PATCH 1/3] chore: minor mpq4242 component cleanup --- esphome/components/mpq4242/binary_sensor/__init__.py | 5 ++--- esphome/components/mpq4242/mpq4242.h | 5 ----- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/esphome/components/mpq4242/binary_sensor/__init__.py b/esphome/components/mpq4242/binary_sensor/__init__.py index 29d1068..adcc8d4 100644 --- a/esphome/components/mpq4242/binary_sensor/__init__.py +++ b/esphome/components/mpq4242/binary_sensor/__init__.py @@ -1,13 +1,12 @@ import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import binary_sensor -from esphome.const import CONF_ID, ENTITY_CATEGORY_DIAGNOSTIC +from esphome.const import CONF_ID, ENTITY_CATEGORY_DIAGNOSTIC, ENTITY_CATEGORY_NONE from .. import ( CONF_MPQ4242_ID, ICON_THERMOMETER_ALERT, ICON_THERMOMETER_HIGH, MPQ4242_COMPONENT_SCHEMA, - MPQ4242Component, mpq4242_ns, ) @@ -55,7 +54,7 @@ icon=ICON_POWER_SETTINGS, ), cv.Optional(CONF_SINK_ATTACHED): binary_sensor.binary_sensor_schema( - entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + entity_category=ENTITY_CATEGORY_NONE, icon=ICON_USB_C_PORT, ), } diff --git a/esphome/components/mpq4242/mpq4242.h b/esphome/components/mpq4242/mpq4242.h index b306ffb..640e4ff 100644 --- a/esphome/components/mpq4242/mpq4242.h +++ b/esphome/components/mpq4242/mpq4242.h @@ -163,11 +163,6 @@ class MPQ4242Component : public i2c::I2CDevice, public Component { */ void set_gpio2_function(MPQ4242Gpio2Function function) { this->gpio2_function_ = function; } - /** Sets the current limit for all PDOs. - * @param current The current limit in A - */ - void set_pdo_current(float current) { this->pdo_current_ = current; } - /** Sets the button for sending a hard reset message to the sink. */ void set_hard_reset_button(button::Button *button) { this->hard_reset_button_ = button; } From c950c26998d940f15a81472c9fc7f3dba7578e57 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 10 Sep 2024 09:46:05 -0400 Subject: [PATCH 2/3] chore: add components to release-please --- .release-please-manifest.json | 2 ++ release-please-config.json | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 256c2a1..961712a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,4 +1,6 @@ { + "esphome/components/lad": "0.0.1", + "esphome/components/pwrman_charger": "0.0.1", "firmware/charger-module": "0.5.0", "hardware/backplane": "0.7.6", "hardware/charger-module": "0.12.1", diff --git a/release-please-config.json b/release-please-config.json index aa8cdec..71567ec 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -3,6 +3,14 @@ "release-type": "simple", "separate-pull-requests": true, "packages": { + "esphome/components/lad": { + "component": "esphome-lad", + "include-component-in-tag": true + }, + "esphome/components/pwrman_charger": { + "component": "esphome-pwrman-charger", + "include-component-in-tag": true + }, "firmware/charger-module": { "component": "charger-module-firmware", "include-component-in-tag": true From 383ba0fbd769e751d7463b959c8f7fc7d27f8dbc Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Tue, 10 Sep 2024 09:46:40 -0400 Subject: [PATCH 3/3] feat: add initial meanwell lad esphome component --- esphome/components/lad/__init__.py | 30 +++ .../components/lad/binary_sensor/__init__.py | 177 ++++++++++++++++++ .../lad/binary_sensor/lad_binary_sensor.cpp | 106 +++++++++++ .../lad/binary_sensor/lad_binary_sensor.h | 137 ++++++++++++++ esphome/components/lad/lad_component.cpp | 50 +++++ esphome/components/lad/lad_component.h | 23 +++ esphome/components/lad/sensor/__init__.py | 116 ++++++++++++ esphome/components/lad/sensor/lad_sensor.cpp | 69 +++++++ esphome/components/lad/sensor/lad_sensor.h | 66 +++++++ 9 files changed, 774 insertions(+) create mode 100644 esphome/components/lad/__init__.py create mode 100644 esphome/components/lad/binary_sensor/__init__.py create mode 100644 esphome/components/lad/binary_sensor/lad_binary_sensor.cpp create mode 100644 esphome/components/lad/binary_sensor/lad_binary_sensor.h create mode 100644 esphome/components/lad/lad_component.cpp create mode 100644 esphome/components/lad/lad_component.h create mode 100644 esphome/components/lad/sensor/__init__.py create mode 100644 esphome/components/lad/sensor/lad_sensor.cpp create mode 100644 esphome/components/lad/sensor/lad_sensor.h diff --git a/esphome/components/lad/__init__.py b/esphome/components/lad/__init__.py new file mode 100644 index 0000000..a4f7d66 --- /dev/null +++ b/esphome/components/lad/__init__.py @@ -0,0 +1,30 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID, CONF_MAX_CURRENT + +CODEOWNERS = ["@mikesmitty"] +DEPENDENCIES = ["uart"] + +CONF_LAD_ID = "lad_id" + +lad_component_ns = cg.esphome_ns.namespace("lad_component") +LadComponent = lad_component_ns.class_("LadComponent", cg.Component, uart.UARTDevice) + +LAD_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_LAD_ID): cv.use_id(LadComponent), + } +) + +CONFIG_SCHEMA = ( + cv.Schema({cv.GenerateID(): cv.declare_id(LadComponent)}) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) diff --git a/esphome/components/lad/binary_sensor/__init__.py b/esphome/components/lad/binary_sensor/__init__.py new file mode 100644 index 0000000..dc6916f --- /dev/null +++ b/esphome/components/lad/binary_sensor/__init__.py @@ -0,0 +1,177 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import binary_sensor, uart +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_BATTERY_CHARGING, + ENTITY_CATEGORY_DIAGNOSTIC, +) +from .. import ( + CONF_LAD_ID, + LAD_SCHEMA, + lad_component_ns, +) + +CODEOWNERS = ["@mikesmitty"] +DEPENDENCIES = ["lad"] + +CONF_AC_POWER_FAULT = "ac_power_fault" +CONF_BATTERY_CHARGED = "battery_charged" +CONF_BATTERY_CHARGING = "battery_charging" +CONF_BATTERY_IMBALANCE = "battery_imbalance" +CONF_BATTERY_POWER_FAULT = "battery_power_fault" +CONF_BATTERY_REVERSED = "battery_reversed" +CONF_BATTERY_SWITCH = "battery_switch" +CONF_BATTERY1_FAULT = "battery1_fault" +CONF_BATTERY2_FAULT = "battery2_fault" +CONF_BATTERY3_FAULT = "battery3_fault" +CONF_BATTERY4_FAULT = "battery4_fault" +CONF_FORCE_START = "force_start" +CONF_LINK_CONTROL_STATUS = "link_control_status" +CONF_LOW_BATTERY = "low_battery" +CONF_OUTPUT_OVERLOAD = "output_overload" +CONF_OVP_ACTIVE = "ovp_active" +CONF_STANDBY_POWER_ACTIVE = "standby_power_active" + +ICON_BATTERY_ARROW_DOWN_OUTLINE = "mdi:battery-arrow-down-outline" +ICON_NUMERIC_5_BOX_MULTIPLE = "mdi:numeric-5-box-multiple" +ICON_POWER_SETTINGS = "mdi:power-settings" +ICON_USB_C_PORT = "mdi:usb-c-port" + +LadBinarySensor = lad_component_ns.class_( + "LadBinarySensor", uart.UARTDevice, cg.PollingComponent +) + +CONFIG_SCHEMA = LAD_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(LadBinarySensor), + cv.Optional(CONF_AC_POWER_FAULT): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY_CHARGED): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_BATTERY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY_CHARGING): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_BATTERY_CHARGING, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY_IMBALANCE): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY_POWER_FAULT): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY_REVERSED): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY_SWITCH): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY1_FAULT): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY2_FAULT): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY3_FAULT): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_BATTERY4_FAULT): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_FORCE_START): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_LINK_CONTROL_STATUS): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_LOW_BATTERY): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_OUTPUT_OVERLOAD): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_OVP_ACTIVE): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + cv.Optional(CONF_STANDBY_POWER_ACTIVE): binary_sensor.binary_sensor_schema( + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +).extend(cv.polling_component_schema("60s")) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_LAD_ID]) + var = cg.new_Pvariable(config[CONF_ID], paren) + await cg.register_component(var, config) + + if ac_power_fault_config := config.get(CONF_AC_POWER_FAULT): + sens = await binary_sensor.new_binary_sensor(ac_power_fault_config) + cg.add(var.set_ac_power_fault_binary_sensor(sens)) + + if battery_charged_config := config.get(CONF_BATTERY_CHARGED): + sens = await binary_sensor.new_binary_sensor(battery_charged_config) + cg.add(var.set_battery_charged_binary_sensor(sens)) + + if battery_charging_config := config.get(CONF_BATTERY_CHARGING): + sens = await binary_sensor.new_binary_sensor(battery_charging_config) + cg.add(var.set_battery_charging_binary_sensor(sens)) + + if battery_imbalance_config := config.get(CONF_BATTERY_IMBALANCE): + sens = await binary_sensor.new_binary_sensor(battery_imbalance_config) + cg.add(var.set_battery_imbalance_binary_sensor(sens)) + + if battery_power_fault_config := config.get(CONF_BATTERY_POWER_FAULT): + sens = await binary_sensor.new_binary_sensor(battery_power_fault_config) + cg.add(var.set_battery_power_fault_binary_sensor(sens)) + + if battery_reversed_config := config.get(CONF_BATTERY_REVERSED): + sens = await binary_sensor.new_binary_sensor(battery_reversed_config) + cg.add(var.set_battery_reversed_binary_sensor(sens)) + + if battery_switch_config := config.get(CONF_BATTERY_SWITCH): + sens = await binary_sensor.new_binary_sensor(battery_switch_config) + cg.add(var.set_battery_switch_binary_sensor(sens)) + + if battery1_fault_config := config.get(CONF_BATTERY1_FAULT): + sens = await binary_sensor.new_binary_sensor(battery1_fault_config) + cg.add(var.set_battery1_fault_binary_sensor(sens)) + + if battery2_fault_config := config.get(CONF_BATTERY2_FAULT): + sens = await binary_sensor.new_binary_sensor(battery2_fault_config) + cg.add(var.set_battery2_fault_binary_sensor(sens)) + + if battery3_fault_config := config.get(CONF_BATTERY3_FAULT): + sens = await binary_sensor.new_binary_sensor(battery3_fault_config) + cg.add(var.set_battery3_fault_binary_sensor(sens)) + + if battery4_fault_config := config.get(CONF_BATTERY4_FAULT): + sens = await binary_sensor.new_binary_sensor(battery4_fault_config) + cg.add(var.set_battery4_fault_binary_sensor(sens)) + + if force_start_config := config.get(CONF_FORCE_START): + sens = await binary_sensor.new_binary_sensor(force_start_config) + cg.add(var.set_force_start_binary_sensor(sens)) + + if link_control_status_config := config.get(CONF_LINK_CONTROL_STATUS): + sens = await binary_sensor.new_binary_sensor(link_control_status_config) + cg.add(var.set_link_control_status_binary_sensor(sens)) + + if low_battery_config := config.get(CONF_LOW_BATTERY): + sens = await binary_sensor.new_binary_sensor(low_battery_config) + cg.add(var.set_low_battery_binary_sensor(sens)) + + if output_overload_config := config.get(CONF_OUTPUT_OVERLOAD): + sens = await binary_sensor.new_binary_sensor(output_overload_config) + cg.add(var.set_output_overload_binary_sensor(sens)) + + if ovp_active_config := config.get(CONF_OVP_ACTIVE): + sens = await binary_sensor.new_binary_sensor(ovp_active_config) + cg.add(var.set_ovp_active_binary_sensor(sens)) + + if standby_power_active_config := config.get(CONF_STANDBY_POWER_ACTIVE): + sens = await binary_sensor.new_binary_sensor(standby_power_active_config) + cg.add(var.set_standby_power_active_binary_sensor(sens)) diff --git a/esphome/components/lad/binary_sensor/lad_binary_sensor.cpp b/esphome/components/lad/binary_sensor/lad_binary_sensor.cpp new file mode 100644 index 0000000..bbec2b5 --- /dev/null +++ b/esphome/components/lad/binary_sensor/lad_binary_sensor.cpp @@ -0,0 +1,106 @@ +#include "lad_binary_sensor.h" + +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/core/component.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace lad_component { + +static const char *TAG = "lad.binary_sensor"; + +float LadBinarySensor::get_setup_priority() const { return setup_priority::DATA; } + +void LadBinarySensor::setup() { ESP_LOGCONFIG(TAG, "Setting up LadBinarySensor..."); } + +void LadBinarySensor::update() { + // Response is 5 bytes plus the length of the data + // R/W | LEN | CMD 2 bytes | DATA | CRC + uint8_t data[9] = {0}; + + if (!this->parent_->read_register_(this->lad_status_, data, 9)) { + ESP_LOGE(TAG, "Failed to read LAD status register"); + } + + uint8_t battery_switch_status = data[5]; + uint8_t status_high = data[6]; + uint8_t status_low = data[7]; + + if (this->ac_power_fault_binary_sensor_ != nullptr) { + // AC power fault status is 0 when AC power is faulting + this->ac_power_fault_binary_sensor_->publish_state(CHECK_BIT(status_low, 0) == 0); + } + if (this->battery_charged_binary_sensor_ != nullptr) { + this->battery_charged_binary_sensor_->publish_state(CHECK_BIT(status_high, 4)); + } + if (this->battery_charging_binary_sensor_ != nullptr) { + this->battery_charging_binary_sensor_->publish_state(CHECK_BIT(status_high, 5)); + } + if (this->battery_imbalance_binary_sensor_ != nullptr) { + this->battery_imbalance_binary_sensor_->publish_state(CHECK_BIT(status_low, 7)); + } + if (this->battery_power_fault_binary_sensor_ != nullptr) { + this->battery_power_fault_binary_sensor_->publish_state(CHECK_BIT(status_low, 1)); + } + if (this->battery_reversed_binary_sensor_ != nullptr) { + this->battery_reversed_binary_sensor_->publish_state(CHECK_BIT(status_high, 6)); + } + if (this->battery_switch_binary_sensor_ != nullptr) { + // Battery switch status is 0 when the battery is online + this->battery_switch_binary_sensor_->publish_state(battery_switch_status == 0); + } + if (this->battery1_fault_binary_sensor_ != nullptr) { + this->battery1_fault_binary_sensor_->publish_state(CHECK_BIT(status_high, 0)); + } + if (this->battery2_fault_binary_sensor_ != nullptr) { + this->battery2_fault_binary_sensor_->publish_state(CHECK_BIT(status_high, 1)); + } + if (this->battery3_fault_binary_sensor_ != nullptr) { + this->battery3_fault_binary_sensor_->publish_state(CHECK_BIT(status_high, 2)); + } + if (this->battery4_fault_binary_sensor_ != nullptr) { + this->battery4_fault_binary_sensor_->publish_state(CHECK_BIT(status_high, 3)); + } + if (this->force_start_binary_sensor_ != nullptr) { + this->force_start_binary_sensor_->publish_state(CHECK_BIT(status_high, 7)); + } + if (this->link_control_status_binary_sensor_ != nullptr) { + this->link_control_status_binary_sensor_->publish_state(CHECK_BIT(status_low, 5)); + } + if (this->low_battery_binary_sensor_ != nullptr) { + this->low_battery_binary_sensor_->publish_state(CHECK_BIT(status_low, 3)); + } + if (this->output_overload_binary_sensor_ != nullptr) { + this->output_overload_binary_sensor_->publish_state(CHECK_BIT(status_low, 2)); + } + if (this->ovp_active_binary_sensor_ != nullptr) { + this->ovp_active_binary_sensor_->publish_state(CHECK_BIT(status_low, 6)); + } + if (this->standby_power_active_binary_sensor_ != nullptr) { + this->standby_power_active_binary_sensor_->publish_state(CHECK_BIT(status_low, 4)); + } +} + +void LadBinarySensor::dump_config() { + ESP_LOGCONFIG(TAG, "LAD Sensor:"); + LOG_BINARY_SENSOR(" ", "ACPowerFaultBinarySensor", this->ac_power_fault_binary_sensor_); + LOG_BINARY_SENSOR(" ", "BatteryChargedBinarySensor", this->battery_charged_binary_sensor_); + LOG_BINARY_SENSOR(" ", "BatteryChargingBinarySensor", this->battery_charging_binary_sensor_); + LOG_BINARY_SENSOR(" ", "BatteryImbalanceBinarySensor", this->battery_imbalance_binary_sensor_); + LOG_BINARY_SENSOR(" ", "BatteryPowerFaultBinarySensor", this->battery_power_fault_binary_sensor_); + LOG_BINARY_SENSOR(" ", "BatteryReversedBinarySensor", this->battery_reversed_binary_sensor_); + LOG_BINARY_SENSOR(" ", "BatterySwitchBinarySensor", this->battery_switch_binary_sensor_); + LOG_BINARY_SENSOR(" ", "Battery1FaultBinarySensor", this->battery1_fault_binary_sensor_); + LOG_BINARY_SENSOR(" ", "Battery2FaultBinarySensor", this->battery2_fault_binary_sensor_); + LOG_BINARY_SENSOR(" ", "Battery3FaultBinarySensor", this->battery3_fault_binary_sensor_); + LOG_BINARY_SENSOR(" ", "Battery4FaultBinarySensor", this->battery4_fault_binary_sensor_); + LOG_BINARY_SENSOR(" ", "ForceStartBinarySensor", this->force_start_binary_sensor_); + LOG_BINARY_SENSOR(" ", "LinkControlStatusBinarySensor", this->link_control_status_binary_sensor_); + LOG_BINARY_SENSOR(" ", "LowBatteryBinarySensor", this->low_battery_binary_sensor_); + LOG_BINARY_SENSOR(" ", "OutputOverloadBinarySensor", this->output_overload_binary_sensor_); + LOG_BINARY_SENSOR(" ", "OvpActiveBinarySensor", this->ovp_active_binary_sensor_); + LOG_BINARY_SENSOR(" ", "StandbyPowerActiveBinarySensor", this->standby_power_active_binary_sensor_); +} + +} // namespace lad_component +} // namespace esphome diff --git a/esphome/components/lad/binary_sensor/lad_binary_sensor.h b/esphome/components/lad/binary_sensor/lad_binary_sensor.h new file mode 100644 index 0000000..6873ef5 --- /dev/null +++ b/esphome/components/lad/binary_sensor/lad_binary_sensor.h @@ -0,0 +1,137 @@ +#pragma once + +#include "../lad_component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/core/defines.h" +#include "esphome/core/component.h" + +namespace esphome { +namespace lad_component { + +#define CHECK_BIT(var, pos) (((var) >> (pos)) & 1) + +/// This class includes UART support for the Meanwell LAD series of battery-backed power supplies. +class LadBinarySensor : public PollingComponent { + public: + LadBinarySensor(LadComponent *parent) : parent_(parent) {} + + /** Sets the binary sensor indicating if A/C power has a fault. */ + void set_ac_power_fault_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->ac_power_fault_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if the battery is charged. */ + void set_battery_charged_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->battery_charged_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if the battery is charging. */ + void set_battery_charging_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->battery_charging_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if the battery has an imbalanced cell. */ + void set_battery_imbalance_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->battery_imbalance_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if battery power has a fault. */ + void set_battery_power_fault_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->battery_power_fault_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if the battery is reversed. */ + void set_battery_reversed_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->battery_reversed_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if the battery switch is active. */ + void set_battery_switch_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->battery_switch_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if battery 1 has a fault. */ + void set_battery1_fault_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->battery1_fault_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if battery 2 has a fault. */ + void set_battery2_fault_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->battery2_fault_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if battery 3 has a fault. */ + void set_battery3_fault_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->battery3_fault_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if battery 4 has a fault. */ + void set_battery4_fault_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->battery4_fault_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if the force start mode is active. */ + void set_force_start_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->force_start_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if the link control status is active. */ + void set_link_control_status_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->link_control_status_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if the low battery protection is active. */ + void set_low_battery_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->low_battery_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if the output overload protection is enabled. */ + void set_output_overload_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->output_overload_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if OVP is active. */ + void set_ovp_active_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->ovp_active_binary_sensor_ = binary_sensor; + } + + /** Sets the binary sensor indicating if standby power is active. */ + void set_standby_power_active_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { + this->standby_power_active_binary_sensor_ = binary_sensor; + } + + /** Used by ESPHome framework. */ + void dump_config() override; + /** Used by ESPHome framework. */ + void update() override; + /** Used by ESPHome framework. */ + void setup() override; + /** Used by ESPHome framework. */ + float get_setup_priority() const override; + + protected: + LadComponent *parent_; + + uint8_t lad_status_[2] = {0x10, 0x7F}; // Returns 4 bytes + + binary_sensor::BinarySensor *ac_power_fault_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *battery_charged_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *battery_charging_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *battery_imbalance_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *battery_power_fault_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *battery_reversed_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *battery_switch_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *battery1_fault_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *battery2_fault_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *battery3_fault_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *battery4_fault_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *force_start_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *link_control_status_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *low_battery_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *output_overload_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *ovp_active_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *standby_power_active_binary_sensor_{nullptr}; +}; + +} // namespace lad_component +} // namespace esphome diff --git a/esphome/components/lad/lad_component.cpp b/esphome/components/lad/lad_component.cpp new file mode 100644 index 0000000..fe54bf5 --- /dev/null +++ b/esphome/components/lad/lad_component.cpp @@ -0,0 +1,50 @@ +#include "esphome/core/log.h" +#include "lad_component.h" + +namespace esphome { +namespace lad_component { + +static const char *TAG = "lad_component.component"; + +float LadComponent::get_setup_priority() const { return setup_priority::DATA; } + +void LadComponent::setup() { ESP_LOGCONFIG(TAG, "Setting up LAD..."); } + +void LadComponent::dump_config() { ESP_LOGCONFIG(TAG, "LAD component"); } + +uint8_t LadComponent::crc8_(const uint8_t *data, uint8_t len) { + uint8_t crc = 0; + for (uint8_t i = 0; i < len; i++) { + crc ^= data[i]; + for (uint8_t j = 0; j < 8; j++) { + if (crc & 0x80) { + crc = (crc << 1) ^ 0x07; + } else { + crc <<= 1; + } + } + } + return crc; +} + +bool LadComponent::read_register_(uint8_t *reg, uint8_t *data, uint8_t len) { + // All read requests are 5 bytes long with the same 3 byte prefix + this->write_array(this->read_register_prefix_, 3); + this->write_array(reg, 2); + this->flush(); + + // Read the response + bool resp = this->read_array(data, len); + + // Check the message CRC + uint8_t crc_result = this->crc8_(data, len - 1); + if (crc_result != data[len - 1]) { + ESP_LOGE(TAG, "CRC mismatch on read register %02X. Expected %02X, got %02X", reg[0], crc_result, data[len - 1]); + return false; + } + + return resp; +} + +} // namespace lad_component +} // namespace esphome \ No newline at end of file diff --git a/esphome/components/lad/lad_component.h b/esphome/components/lad/lad_component.h new file mode 100644 index 0000000..7fd7612 --- /dev/null +++ b/esphome/components/lad/lad_component.h @@ -0,0 +1,23 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace lad_component { + +class LadComponent : public uart::UARTDevice, public Component { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + uint8_t crc8_(const uint8_t *data, uint8_t len); + bool read_register_(uint8_t *reg, uint8_t *data, uint8_t len); + // void write_register_(bool state); // FIXME: Implement this function for controlling the LAD + + protected: + uint8_t read_register_prefix_[3] = {0x55, 0x03, 0x00}; +}; + +} // namespace lad_component +} // namespace esphome \ No newline at end of file diff --git a/esphome/components/lad/sensor/__init__.py b/esphome/components/lad/sensor/__init__.py new file mode 100644 index 0000000..6fe5c94 --- /dev/null +++ b/esphome/components/lad/sensor/__init__.py @@ -0,0 +1,116 @@ +import esphome.codegen as cg +from esphome.components import sensor, uart +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + CONF_BATTERY_VOLTAGE, + CONF_CURRENT, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_VOLTAGE, + ENTITY_CATEGORY_NONE, + ICON_FLASH, + STATE_CLASS_MEASUREMENT, + UNIT_AMPERE, + UNIT_VOLT, +) +from .. import ( + CONF_LAD_ID, + LAD_SCHEMA, + lad_component_ns, +) + +CODEOWNERS = ["@mikesmitty"] +DEPENDENCIES = ["lad"] + +CONF_BATTERY_1_VOLTAGE = "battery_1_voltage" +CONF_BATTERY_2_VOLTAGE = "battery_2_voltage" +CONF_BATTERY_3_VOLTAGE = "battery_3_voltage" +CONF_BATTERY_4_VOLTAGE = "battery_4_voltage" +CONF_INPUT_VOLTAGE = "input_voltage" + +ICON_CURRENT_DC = "mdi:current-dc" + +LadSensor = lad_component_ns.class_("LadSensor", uart.UARTDevice, cg.PollingComponent) + +CONFIG_SCHEMA = LAD_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(LadSensor), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + device_class=DEVICE_CLASS_CURRENT, + entity_category=ENTITY_CATEGORY_NONE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_AMPERE, + icon=ICON_CURRENT_DC, + ), + cv.Optional(CONF_INPUT_VOLTAGE): sensor.sensor_schema( + device_class=DEVICE_CLASS_VOLTAGE, + entity_category=ENTITY_CATEGORY_NONE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + ), + cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( + device_class=DEVICE_CLASS_VOLTAGE, + entity_category=ENTITY_CATEGORY_NONE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + ), + cv.Optional(CONF_BATTERY_1_VOLTAGE): sensor.sensor_schema( + device_class=DEVICE_CLASS_VOLTAGE, + entity_category=ENTITY_CATEGORY_NONE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + ), + cv.Optional(CONF_BATTERY_2_VOLTAGE): sensor.sensor_schema( + device_class=DEVICE_CLASS_VOLTAGE, + entity_category=ENTITY_CATEGORY_NONE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + ), + cv.Optional(CONF_BATTERY_3_VOLTAGE): sensor.sensor_schema( + device_class=DEVICE_CLASS_VOLTAGE, + entity_category=ENTITY_CATEGORY_NONE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + ), + cv.Optional(CONF_BATTERY_4_VOLTAGE): sensor.sensor_schema( + device_class=DEVICE_CLASS_VOLTAGE, + entity_category=ENTITY_CATEGORY_NONE, + state_class=STATE_CLASS_MEASUREMENT, + unit_of_measurement=UNIT_VOLT, + icon=ICON_FLASH, + ), + } +).extend(cv.polling_component_schema("60s")) + + +async def to_code(config): + paren = await cg.get_variable(config[CONF_LAD_ID]) + var = cg.new_Pvariable(config[CONF_ID], paren) + await cg.register_component(var, config) + + if current_config := config.get(CONF_CURRENT): + sens = await sensor.new_sensor(current_config) + cg.add(var.set_current_sensor(sens)) + if input_voltage_config := config.get(CONF_INPUT_VOLTAGE): + sens = await sensor.new_sensor(input_voltage_config) + cg.add(var.set_input_voltage_sensor(sens)) + if battery_voltage_config := config.get(CONF_BATTERY_VOLTAGE): + sens = await sensor.new_sensor(battery_voltage_config) + cg.add(var.set_battery_voltage_sensor(sens)) + if battery_1_voltage_config := config.get(CONF_BATTERY_1_VOLTAGE): + sens = await sensor.new_sensor(battery_1_voltage_config) + cg.add(var.set_battery_1_voltage_sensor(sens)) + if battery_2_voltage_config := config.get(CONF_BATTERY_2_VOLTAGE): + sens = await sensor.new_sensor(battery_2_voltage_config) + cg.add(var.set_battery_2_voltage_sensor(sens)) + if battery_3_voltage_config := config.get(CONF_BATTERY_3_VOLTAGE): + sens = await sensor.new_sensor(battery_3_voltage_config) + cg.add(var.set_battery_3_voltage_sensor(sens)) + if battery_4_voltage_config := config.get(CONF_BATTERY_4_VOLTAGE): + sens = await sensor.new_sensor(battery_4_voltage_config) + cg.add(var.set_battery_4_voltage_sensor(sens)) diff --git a/esphome/components/lad/sensor/lad_sensor.cpp b/esphome/components/lad/sensor/lad_sensor.cpp new file mode 100644 index 0000000..9d85d73 --- /dev/null +++ b/esphome/components/lad/sensor/lad_sensor.cpp @@ -0,0 +1,69 @@ +#include "lad_sensor.h" + +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" +#include "esphome/core/component.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace lad_component { + +static const char *TAG = "lad_component.sensor"; + +float LadSensor::get_setup_priority() const { return setup_priority::DATA; } + +void LadSensor::setup() { ESP_LOGCONFIG(TAG, "Setting up LadSensor..."); } + +void LadSensor::update() { + // Response is 5 bytes plus the length of the data. Largest response is 8 bytes + // R/W | LEN | CMD 2 bytes | DATA | CRC + uint8_t data[13] = {0}; + + // Read the current register and convert from 10 mA units to A + if (this->current_sensor_ != nullptr && this->parent_->read_register_(this->current_, data, 7)) { + this->current_sensor_->publish_state((data[4] << 8 | data[5]) * 0.01); + } + + // Read the input voltage register and convert from 100mV units to V + if (this->input_voltage_sensor_ != nullptr && this->parent_->read_register_(this->input_voltage_, data, 7)) { + this->input_voltage_sensor_->publish_state((data[4] << 8 | data[5]) * 0.1); + } + + // Read the battery voltage register and convert from 10mV units to V + if (this->battery_voltage_sensor_ != nullptr && this->parent_->read_register_(this->battery_voltage_, data, 7)) { + this->battery_voltage_sensor_->publish_state((data[4] << 8 | data[5]) * 0.01); + } + + // Read the cell voltage registers and convert from 10mV units to V + if (this->battery_1_voltage_sensor_ != nullptr || this->battery_2_voltage_sensor_ != nullptr || + this->battery_3_voltage_sensor_ != nullptr || this->battery_4_voltage_sensor_ != nullptr) { + if (this->parent_->read_register_(this->cell_voltage_, data, 13)) { + if (this->battery_1_voltage_sensor_ != nullptr) { + this->battery_1_voltage_sensor_->publish_state((data[4] << 8 | data[5]) * 0.01); + } + if (this->battery_2_voltage_sensor_ != nullptr) { + this->battery_2_voltage_sensor_->publish_state((data[6] << 8 | data[7]) * 0.01); + } + if (this->battery_3_voltage_sensor_ != nullptr) { + this->battery_3_voltage_sensor_->publish_state((data[8] << 8 | data[9]) * 0.01); + } + if (this->battery_4_voltage_sensor_ != nullptr) { + this->battery_4_voltage_sensor_->publish_state((data[10] << 8 | data[11]) * 0.01); + } + } + } +} + +void LadSensor::dump_config() { + ESP_LOGCONFIG(TAG, "LadSensor:"); + LOG_SENSOR(" ", "InputVoltageSensor", this->input_voltage_sensor_); + LOG_SENSOR(" ", "CurrentSensor", this->current_sensor_); + LOG_SENSOR(" ", "BatteryVoltageSensor", this->battery_voltage_sensor_); + LOG_SENSOR(" ", "Battery1VoltageSensor", this->battery_1_voltage_sensor_); + LOG_SENSOR(" ", "Battery2VoltageSensor", this->battery_2_voltage_sensor_); + LOG_SENSOR(" ", "Battery3VoltageSensor", this->battery_3_voltage_sensor_); + LOG_SENSOR(" ", "Battery4VoltageSensor", this->battery_4_voltage_sensor_); +} + +} // namespace lad_component +} // namespace esphome diff --git a/esphome/components/lad/sensor/lad_sensor.h b/esphome/components/lad/sensor/lad_sensor.h new file mode 100644 index 0000000..0046e18 --- /dev/null +++ b/esphome/components/lad/sensor/lad_sensor.h @@ -0,0 +1,66 @@ +#pragma once + +#include "../lad_component.h" +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace lad_component { + +/// This class includes UART support for the Meanwell LAD series of battery-backed +/// power supplies. +class LadSensor : public sensor::Sensor, public PollingComponent, public uart::UARTDevice { + public: + LadSensor(LadComponent *parent) : parent_(parent) {} + + /** Sets the sensor that will report the input voltage to the UPS. */ + void set_input_voltage_sensor(sensor::Sensor *sensor) { this->input_voltage_sensor_ = sensor; } + + /** Sets the sensor that will report the output current from the UPS. */ + void set_current_sensor(sensor::Sensor *sensor) { this->current_sensor_ = sensor; } + + /** Sets the sensor that will report the backup battery voltage from the UPS. */ + void set_battery_voltage_sensor(sensor::Sensor *sensor) { this->battery_voltage_sensor_ = sensor; } + + /** Sets the sensor that will report the voltage of battery 1 from the UPS. */ + void set_battery_1_voltage_sensor(sensor::Sensor *sensor) { this->battery_1_voltage_sensor_ = sensor; } + + /** Sets the sensor that will report the voltage of battery 2 from the UPS. */ + void set_battery_2_voltage_sensor(sensor::Sensor *sensor) { this->battery_2_voltage_sensor_ = sensor; } + + /** Sets the sensor that will report the voltage of battery 3 from the UPS. */ + void set_battery_3_voltage_sensor(sensor::Sensor *sensor) { this->battery_3_voltage_sensor_ = sensor; } + + /** Sets the sensor that will report the voltage of battery 4 from the UPS. */ + void set_battery_4_voltage_sensor(sensor::Sensor *sensor) { this->battery_4_voltage_sensor_ = sensor; } + + /** Used by ESPHome framework. */ + void dump_config() override; + /** Used by ESPHome framework. */ + void setup() override; + /** Used by ESPHome framework. */ + void update() override; + /** Used by ESPHome framework. */ + float get_setup_priority() const override; + + protected: + LadComponent *parent_; + + uint8_t input_voltage_[2] = {0x20, 0xEF}; // Returns 2 bytes + uint8_t current_[2] = {0x30, 0x9F}; // Returns 2 bytes + uint8_t battery_voltage_[2] = {0x40, 0xC8}; // Returns 2 bytes + uint8_t cell_voltage_[2] = {0x50, 0xB8}; // Returns 8 bytes + uint8_t battery_uvp_point_[2] = {0x60, 0x28}; // Returns 2 bytes + + sensor::Sensor *input_voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *battery_voltage_sensor_{nullptr}; + sensor::Sensor *battery_1_voltage_sensor_{nullptr}; + sensor::Sensor *battery_2_voltage_sensor_{nullptr}; + sensor::Sensor *battery_3_voltage_sensor_{nullptr}; + sensor::Sensor *battery_4_voltage_sensor_{nullptr}; +}; + +} // namespace lad_component +} // namespace esphome \ No newline at end of file