Add H-Bridge fan component (#2212)

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
WeekendWarrior1 2021-08-31 08:10:22 +10:00 committed by GitHub
parent 37f322585e
commit 03190611bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 222 additions and 1 deletions

View file

@ -52,6 +52,8 @@ esphome/components/globals/* @esphome/core
esphome/components/gpio/* @esphome/core
esphome/components/gps/* @coogle
esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
esphome/components/hitachi_ac424/* @sourabhjaiswal
esphome/components/homeassistant/* @OttoWinter
esphome/components/hrxl_maxsonar_wr/* @netmikey

View file

@ -0,0 +1,3 @@
import esphome.codegen as cg
hbridge_ns = cg.esphome_ns.namespace("hbridge")

View file

@ -0,0 +1,70 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id
from esphome.components import fan, output
from esphome.const import (
CONF_ID,
CONF_DECAY_MODE,
CONF_SPEED_COUNT,
CONF_PIN_A,
CONF_PIN_B,
CONF_ENABLE_PIN,
)
from .. import hbridge_ns
CODEOWNERS = ["@WeekendWarrior"]
HBridgeFan = hbridge_ns.class_("HBridgeFan", fan.FanState)
DecayMode = hbridge_ns.enum("DecayMode")
DECAY_MODE_OPTIONS = {
"SLOW": DecayMode.DECAY_MODE_SLOW,
"FAST": DecayMode.DECAY_MODE_FAST,
}
# Actions
BrakeAction = hbridge_ns.class_("BrakeAction", automation.Action)
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
{
cv.GenerateID(CONF_ID): cv.declare_id(HBridgeFan),
cv.Required(CONF_PIN_A): cv.use_id(output.FloatOutput),
cv.Required(CONF_PIN_B): cv.use_id(output.FloatOutput),
cv.Optional(CONF_DECAY_MODE, default="SLOW"): cv.enum(
DECAY_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
cv.Optional(CONF_ENABLE_PIN): cv.use_id(output.FloatOutput),
}
).extend(cv.COMPONENT_SCHEMA)
@automation.register_action(
"fan.hbridge.brake",
BrakeAction,
maybe_simple_id({cv.Required(CONF_ID): cv.use_id(HBridgeFan)}),
)
async def fan_hbridge_brake_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
async def to_code(config):
var = cg.new_Pvariable(
config[CONF_ID],
config[CONF_SPEED_COUNT],
config[CONF_DECAY_MODE],
)
await fan.register_fan(var, config)
pin_a_ = await cg.get_variable(config[CONF_PIN_A])
cg.add(var.set_pin_a(pin_a_))
pin_b_ = await cg.get_variable(config[CONF_PIN_B])
cg.add(var.set_pin_b(pin_b_))
if CONF_ENABLE_PIN in config:
enable_pin = await cg.get_variable(config[CONF_ENABLE_PIN])
cg.add(var.set_enable_pin(enable_pin))

View file

@ -0,0 +1,85 @@
#include "hbridge_fan.h"
#include "esphome/components/fan/fan_helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace hbridge {
static const char *const TAG = "fan.hbridge";
void HBridgeFan::set_hbridge_levels_(float a_level, float b_level) {
this->pin_a_->set_level(a_level);
this->pin_b_->set_level(b_level);
ESP_LOGD(TAG, "Setting speed: a: %.2f, b: %.2f", a_level, b_level);
}
// constant IN1/IN2, PWM on EN => power control, fast current decay
// constant IN1/EN, PWM on IN2 => power control, slow current decay
void HBridgeFan::set_hbridge_levels_(float a_level, float b_level, float enable) {
this->pin_a_->set_level(a_level);
this->pin_b_->set_level(b_level);
this->enable_->set_level(enable);
ESP_LOGD(TAG, "Setting speed: a: %.2f, b: %.2f, enable: %.2f", a_level, b_level, enable);
}
fan::FanStateCall HBridgeFan::brake() {
ESP_LOGD(TAG, "Braking");
(this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f, 1.0f) : this->set_hbridge_levels_(1.0f, 1.0f, 1.0f);
return this->make_call().set_state(false);
}
void HBridgeFan::dump_config() {
ESP_LOGCONFIG(TAG, "Fan '%s':", this->get_name().c_str());
if (this->get_traits().supports_oscillation()) {
ESP_LOGCONFIG(TAG, " Oscillation: YES");
}
if (this->get_traits().supports_direction()) {
ESP_LOGCONFIG(TAG, " Direction: YES");
}
if (this->decay_mode_ == DECAY_MODE_SLOW) {
ESP_LOGCONFIG(TAG, " Decay Mode: Slow");
} else {
ESP_LOGCONFIG(TAG, " Decay Mode: Fast");
}
}
void HBridgeFan::setup() {
auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_);
this->set_traits(traits);
this->add_on_state_callback([this]() { this->next_update_ = true; });
}
void HBridgeFan::loop() {
if (!this->next_update_) {
return;
}
this->next_update_ = false;
float speed = 0.0f;
if (this->state) {
speed = static_cast<float>(this->speed) / static_cast<float>(this->speed_count_);
}
if (speed == 0.0f) { // off means idle
(this->enable_ == nullptr) ? this->set_hbridge_levels_(speed, speed)
: this->set_hbridge_levels_(speed, speed, speed);
return;
}
if (this->direction == fan::FAN_DIRECTION_FORWARD) {
if (this->decay_mode_ == DECAY_MODE_SLOW) {
(this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f - speed, 1.0f)
: this->set_hbridge_levels_(1.0f - speed, 1.0f, 1.0f);
} else { // DECAY_MODE_FAST
(this->enable_ == nullptr) ? this->set_hbridge_levels_(0.0f, speed)
: this->set_hbridge_levels_(0.0f, 1.0f, speed);
}
} else { // fan::FAN_DIRECTION_REVERSE
if (this->decay_mode_ == DECAY_MODE_SLOW) {
(this->enable_ == nullptr) ? this->set_hbridge_levels_(1.0f, 1.0f - speed)
: this->set_hbridge_levels_(1.0f, 1.0f - speed, 1.0f);
} else { // DECAY_MODE_FAST
(this->enable_ == nullptr) ? this->set_hbridge_levels_(speed, 0.0f)
: this->set_hbridge_levels_(1.0f, 0.0f, speed);
}
}
}
} // namespace hbridge
} // namespace esphome

View file

@ -0,0 +1,58 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/components/output/binary_output.h"
#include "esphome/components/output/float_output.h"
#include "esphome/components/fan/fan_state.h"
namespace esphome {
namespace hbridge {
enum DecayMode {
DECAY_MODE_SLOW = 0,
DECAY_MODE_FAST = 1,
};
class HBridgeFan : public fan::FanState {
public:
HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {}
void set_pin_a(output::FloatOutput *pin_a) { pin_a_ = pin_a; }
void set_pin_b(output::FloatOutput *pin_b) { pin_b_ = pin_b; }
void set_enable_pin(output::FloatOutput *enable) { enable_ = enable; }
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
fan::FanStateCall brake();
int get_speed_count() { return this->speed_count_; }
// update Hbridge without a triggered FanState change, eg. for acceleration/deceleration ramping
void internal_update() { this->next_update_ = true; }
protected:
output::FloatOutput *pin_a_;
output::FloatOutput *pin_b_;
output::FloatOutput *enable_{nullptr};
output::BinaryOutput *oscillating_{nullptr};
bool next_update_{true};
int speed_count_{};
DecayMode decay_mode_{DECAY_MODE_SLOW};
void set_hbridge_levels_(float a_level, float b_level);
void set_hbridge_levels_(float a_level, float b_level, float enable);
};
template<typename... Ts> class BrakeAction : public Action<Ts...> {
public:
explicit BrakeAction(HBridgeFan *parent) : parent_(parent) {}
void play(Ts... x) override { this->parent_->brake(); }
HBridgeFan *parent_;
};
} // namespace hbridge
} // namespace esphome

View file

@ -2,8 +2,10 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import light, output
from esphome.const import CONF_OUTPUT_ID, CONF_PIN_A, CONF_PIN_B
from .. import hbridge_ns
CODEOWNERS = ["@DotNetDann"]
hbridge_ns = cg.esphome_ns.namespace("hbridge")
HBridgeLightOutput = hbridge_ns.class_(
"HBridgeLightOutput", cg.PollingComponent, light.LightOutput
)

View file

@ -165,6 +165,7 @@ CONF_DAYS_OF_WEEK = "days_of_week"
CONF_DC_PIN = "dc_pin"
CONF_DEASSERT_RTS_DTR = "deassert_rts_dtr"
CONF_DEBOUNCE = "debounce"
CONF_DECAY_MODE = "decay_mode"
CONF_DECELERATION = "deceleration"
CONF_DEFAULT_MODE = "default_mode"
CONF_DEFAULT_TARGET_TEMPERATURE_HIGH = "default_target_temperature_high"