Skip to content

Commit

Permalink
feat: add initial power-manifold esphome component
Browse files Browse the repository at this point in the history
  • Loading branch information
mikesmitty committed Sep 10, 2024
1 parent bf6c768 commit 5ffa713
Show file tree
Hide file tree
Showing 22 changed files with 1,019 additions and 0 deletions.
37 changes: 37 additions & 0 deletions esphome/components/pwrman_charger/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c
from esphome.const import CONF_ID, CONF_MAX_CURRENT

CODEOWNERS = ["@mikesmitty"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True

CONF_PORT_NUMBER = "port_number"
CONF_PWRMAN_CHARGER_ID = "pwrman_charger_id"

pwrman_charger_ns = cg.esphome_ns.namespace("pwrman_charger")
PwrmanCharger = pwrman_charger_ns.class_("PwrmanCharger", cg.Component, i2c.I2CDevice)

PWRMAN_CHARGER_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_PWRMAN_CHARGER_ID): cv.use_id(PwrmanCharger),
}
)

CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(PwrmanCharger),
# FIXME: add max_current
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(i2c.i2c_device_schema(0x4D))
)


async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await i2c.register_i2c_device(var, config)
80 changes: 80 additions & 0 deletions esphome/components/pwrman_charger/binary_sensor/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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, ENTITY_CATEGORY_NONE
from .. import (
CONF_PWRMAN_CHARGER_ID,
PWRMAN_CHARGER_SCHEMA,
pwrman_charger_ns,
)

CODEOWNERS = ["@mikesmitty"]
DEPENDENCIES = ["pwrman_charger"]

CONF_CABLE_5A_CAPABLE = "cable_5a_capable"
CONF_OTW_THRESHOLD_1 = "otw_threshold_1"
CONF_OTW_THRESHOLD_2 = "otw_threshold_2"
CONF_PPS_MODE = "pps_mode"
CONF_SINK_ATTACHED = "sink_attached"

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_THERMOMETER_ALERT = "mdi:thermometer-alert"
ICON_THERMOMETER_HIGH = "mdi:thermometer-high"
ICON_USB_C_PORT = "mdi:usb-c-port"

PwrmanChargerBinarySensor = pwrman_charger_ns.class_(
"PwrmanChargerBinarySensor", cg.PollingComponent
)

CONFIG_SCHEMA = PWRMAN_CHARGER_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(PwrmanChargerBinarySensor),
cv.Optional(CONF_CABLE_5A_CAPABLE): binary_sensor.binary_sensor_schema(
icon=ICON_NUMERIC_5_BOX_MULTIPLE,
),
cv.Optional(CONF_OTW_THRESHOLD_1): binary_sensor.binary_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_THERMOMETER_HIGH,
),
cv.Optional(CONF_OTW_THRESHOLD_2): binary_sensor.binary_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_THERMOMETER_ALERT,
),
cv.Optional(CONF_PPS_MODE): binary_sensor.binary_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_POWER_SETTINGS,
),
cv.Optional(CONF_SINK_ATTACHED): binary_sensor.binary_sensor_schema(
entity_category=ENTITY_CATEGORY_NONE,
icon=ICON_USB_C_PORT,
),
}
).extend(cv.polling_component_schema("60s"))


async def to_code(config):
paren = await cg.get_variable(config[CONF_PWRMAN_CHARGER_ID])
var = cg.new_Pvariable(config[CONF_ID], paren)
await cg.register_component(var, config)

if cable_5a_capable_config := config.get(CONF_CABLE_5A_CAPABLE):
sens = await binary_sensor.new_binary_sensor(cable_5a_capable_config)
cg.add(var.set_cable_5a_capable_binary_sensor(sens))

if otw_threshold_1_config := config.get(CONF_OTW_THRESHOLD_1):
sens = await binary_sensor.new_binary_sensor(otw_threshold_1_config)
cg.add(var.set_otw_threshold_1_binary_sensor(sens))

if otw_threshold_2_config := config.get(CONF_OTW_THRESHOLD_2):
sens = await binary_sensor.new_binary_sensor(otw_threshold_2_config)
cg.add(var.set_otw_threshold_2_binary_sensor(sens))

if pps_mode_config := config.get(CONF_PPS_MODE):
sens = await binary_sensor.new_binary_sensor(pps_mode_config)
cg.add(var.set_pps_mode_binary_sensor(sens))

if sink_attached_config := config.get(CONF_SINK_ATTACHED):
sens = await binary_sensor.new_binary_sensor(sink_attached_config)
cg.add(var.set_sink_attached_binary_sensor(sens))
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#include "pwrman_charger_binary_sensor.h"

#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/core/component.h"
#include "esphome/core/log.h"

namespace esphome {
namespace pwrman_charger {

static const char *TAG = "pwrman_charger.binary_sensor";

float PwrmanChargerBinarySensor::get_setup_priority() const { return setup_priority::DATA; }

void PwrmanChargerBinarySensor::setup() { ESP_LOGCONFIG(TAG, "Setting up PwrmanChargerBinarySensor..."); }

void PwrmanChargerBinarySensor::update() {
uint8_t fault_state = 0;
this->parent_->read_register(PWRMAN_CHARGER_REGISTER_FAULT, &fault_state, 1);

if (this->cable_5a_capable_binary_sensor_ != nullptr) {
this->parent_->read_register(PWRMAN_CHARGER_REGISTER_CABLE_5A_FLAG, this->resp_bytes_, 1);
this->cable_5a_capable_binary_sensor_->publish_state(this->resp_bytes_[0]);
}

if (this->otw_threshold_1_binary_sensor_ != nullptr) {
this->otw_threshold_1_binary_sensor_->publish_state(CHECK_BIT(fault_state, 1));
}

if (this->otw_threshold_2_binary_sensor_ != nullptr) {
this->otw_threshold_2_binary_sensor_->publish_state(CHECK_BIT(fault_state, 2));
}

if (this->pps_mode_binary_sensor_ != nullptr) {
this->parent_->read_register(PWRMAN_CHARGER_REGISTER_CUR_PDO_MIN_VOLT, this->resp_bytes_, 1);
this->pps_mode_binary_sensor_->publish_state(this->resp_bytes_[0] != 0);
}

if (this->sink_attached_binary_sensor_ != nullptr) {
this->parent_->read_register(PWRMAN_CHARGER_REGISTER_SINK_ATTACHED, this->resp_bytes_, 1);
this->sink_attached_binary_sensor_->publish_state(this->resp_bytes_[0]);
}
}

void PwrmanChargerBinarySensor::dump_config() {
ESP_LOGCONFIG(TAG, "PwrmanCharger Sensor:");
LOG_BINARY_SENSOR(" ", "Cable5ACapableBinarySensor", this->cable_5a_capable_binary_sensor_);
LOG_BINARY_SENSOR(" ", "OtwThreshold1BinarySensor", this->otw_threshold_1_binary_sensor_);
LOG_BINARY_SENSOR(" ", "OtwThreshold2BinarySensor", this->otw_threshold_2_binary_sensor_);
LOG_BINARY_SENSOR(" ", "PpsModeBinarySensor", this->pps_mode_binary_sensor_);
LOG_BINARY_SENSOR(" ", "SinkAttachedBinarySensor", this->sink_attached_binary_sensor_);
}

} // namespace pwrman_charger
} // namespace esphome
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#pragma once

#include "../pwrman_charger.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/core/defines.h"
#include "esphome/core/component.h"

namespace esphome {
namespace pwrman_charger {

/// This class includes i2c support for the Power Manifold Charger Module.
/// This hot-swappable module can be remotely managed and monitored via
/// I2C and is capable of up to 100W charging. This class is for the
/// binary sensor configuration.
class PwrmanChargerBinarySensor : public PollingComponent {
public:
PwrmanChargerBinarySensor(PwrmanCharger *parent) : parent_(parent) {}

/** Sets the binary sensor indicating if a 5A-capable cable is attached. */
void set_cable_5a_capable_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
this->cable_5a_capable_binary_sensor_ = binary_sensor;
}

/** Sets the binary sensor indicating if the otw threshold 1 has been reached. */
void set_otw_threshold_1_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
this->otw_threshold_1_binary_sensor_ = binary_sensor;
}

/** Sets the binary sensor indicating if the otw threshold 2 has been reached. */
void set_otw_threshold_2_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
this->otw_threshold_2_binary_sensor_ = binary_sensor;
}

/** Sets the binary sensor indicating if the selected PDO uses PPS. */
void set_pps_mode_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
this->pps_mode_binary_sensor_ = binary_sensor;
}

/** Sets the binary sensor indicating if a sink is attached. */
void set_sink_attached_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
this->sink_attached_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:
PwrmanCharger *parent_;

uint8_t resp_bytes_[1] = {0};

binary_sensor::BinarySensor *cable_5a_capable_binary_sensor_{nullptr};
binary_sensor::BinarySensor *otw_threshold_1_binary_sensor_{nullptr};
binary_sensor::BinarySensor *otw_threshold_2_binary_sensor_{nullptr};
binary_sensor::BinarySensor *pps_mode_binary_sensor_{nullptr};
binary_sensor::BinarySensor *sink_attached_binary_sensor_{nullptr};
};

} // namespace pwrman_charger
} // namespace esphome
45 changes: 45 additions & 0 deletions esphome/components/pwrman_charger/button/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import (
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_DIAGNOSTIC,
ENTITY_CATEGORY_CONFIG,
ICON_RESTART,
)
from .. import CONF_PWRMAN_CHARGER_ID, PwrmanCharger, pwrman_charger_ns

HardResetButton = pwrman_charger_ns.class_("HardResetButton", button.Button)
SrcCapButton = pwrman_charger_ns.class_("SrcCapButton", button.Button)

CONF_SEND_HARD_RESET = "send_hard_reset"
CONF_SEND_SRC_CAP = "send_src_cap"

ICON_DATABASE_EXPORT = "mdi:database-export"

CONFIG_SCHEMA = {
cv.GenerateID(CONF_PWRMAN_CHARGER_ID): cv.use_id(PwrmanCharger),
cv.Optional(CONF_SEND_HARD_RESET): button.button_schema(
HardResetButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART,
),
cv.Optional(CONF_SEND_SRC_CAP): button.button_schema(
SrcCapButton,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_DATABASE_EXPORT,
),
}


async def to_code(config):
paren = await cg.get_variable(config[CONF_PWRMAN_CHARGER_ID])
if hard_reset_config := config.get(CONF_SEND_HARD_RESET):
b = await button.new_button(hard_reset_config)
await cg.register_parented(b, config[CONF_PWRMAN_CHARGER_ID])
cg.add(paren.set_hard_reset_button(b))
if src_cap_config := config.get(CONF_SEND_SRC_CAP):
b = await button.new_button(src_cap_config)
await cg.register_parented(b, config[CONF_PWRMAN_CHARGER_ID])
cg.add(paren.set_src_cap_button(b))
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "hard_reset_button.h"

namespace esphome {
namespace pwrman_charger {

void HardResetButton::press_action() { this->parent_->send_hard_reset(); }

} // namespace pwrman_charger
} // namespace esphome
18 changes: 18 additions & 0 deletions esphome/components/pwrman_charger/button/hard_reset_button.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "esphome/components/button/button.h"
#include "../pwrman_charger.h"

namespace esphome {
namespace pwrman_charger {

class HardResetButton : public button::Button, public Parented<PwrmanCharger> {
public:
HardResetButton() = default;

protected:
void press_action() override;
};

} // namespace pwrman_charger
} // namespace esphome
9 changes: 9 additions & 0 deletions esphome/components/pwrman_charger/button/src_cap_button.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "src_cap_button.h"

namespace esphome {
namespace pwrman_charger {

void SrcCapButton::press_action() { this->parent_->send_src_cap(); }

} // namespace pwrman_charger
} // namespace esphome
18 changes: 18 additions & 0 deletions esphome/components/pwrman_charger/button/src_cap_button.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#pragma once

#include "esphome/components/button/button.h"
#include "../pwrman_charger.h"

namespace esphome {
namespace pwrman_charger {

class SrcCapButton : public button::Button, public Parented<PwrmanCharger> {
public:
SrcCapButton() = default;

protected:
void press_action() override;
};

} // namespace pwrman_charger
} // namespace esphome
29 changes: 29 additions & 0 deletions esphome/components/pwrman_charger/light/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import light
from esphome.const import CONF_OUTPUT_ID
from .. import CONF_PWRMAN_CHARGER_ID, PWRMAN_CHARGER_SCHEMA, pwrman_charger_ns

PwrmanChargerLightOutput = pwrman_charger_ns.class_(
"PwrmanChargerLight", light.LightOutput, cg.Component
)

CONFIG_SCHEMA = (
light.RGB_LIGHT_SCHEMA.extend(
{
cv.GenerateID(CONF_PWRMAN_CHARGER_ID): cv.declare_id(
PwrmanChargerLightOutput
),
}
)
.extend(PWRMAN_CHARGER_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)


async def to_code(config):
var = cg.new_Pvariable(config[CONF_PWRMAN_CHARGER_ID])
await light.register_light(var, config)

# out = await cg.get_variable(config[CONF_OUTPUT])
# cg.add(var.set_output(out))
Empty file.
Empty file.
Loading

0 comments on commit 5ffa713

Please sign in to comment.