Skip to content

Commit

Permalink
Add H-Bridge switch component (esphome#7421)
Browse files Browse the repository at this point in the history
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
  • Loading branch information
dwmw2 and kbx81 authored Dec 2, 2024
1 parent edd847e commit fb96e35
Show file tree
Hide file tree
Showing 11 changed files with 313 additions and 186 deletions.
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ esphome/components/haier/text_sensor/* @paveldn
esphome/components/havells_solar/* @sourabhjaiswal
esphome/components/hbridge/fan/* @WeekendWarrior
esphome/components/hbridge/light/* @DotNetDann
esphome/components/hbridge/switch/* @dwmw2
esphome/components/he60r/* @clydebarrow
esphome/components/heatpumpir/* @rob-deutsch
esphome/components/hitachi_ac424/* @sourabhjaiswal
Expand Down
44 changes: 44 additions & 0 deletions esphome/components/hbridge/switch/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import CONF_OPTIMISTIC, CONF_PULSE_LENGTH, CONF_WAIT_TIME

from .. import hbridge_ns

HBridgeSwitch = hbridge_ns.class_("HBridgeSwitch", switch.Switch, cg.Component)

CODEOWNERS = ["@dwmw2"]

CONF_OFF_PIN = "off_pin"
CONF_ON_PIN = "on_pin"

CONFIG_SCHEMA = (
switch.switch_schema(HBridgeSwitch)
.extend(
{
cv.Required(CONF_ON_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_OFF_PIN): pins.gpio_output_pin_schema,
cv.Optional(
CONF_PULSE_LENGTH, default="100ms"
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_WAIT_TIME): cv.positive_time_period_milliseconds,
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
}
)
.extend(cv.COMPONENT_SCHEMA)
)


async def to_code(config):
var = await switch.new_switch(config)
await cg.register_component(var, config)

on_pin = await cg.gpio_pin_expression(config[CONF_ON_PIN])
cg.add(var.set_on_pin(on_pin))
off_pin = await cg.gpio_pin_expression(config[CONF_OFF_PIN])
cg.add(var.set_off_pin(off_pin))
cg.add(var.set_pulse_length(config[CONF_PULSE_LENGTH]))
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
if wait_time := config.get(CONF_WAIT_TIME):
cg.add(var.set_wait_time(wait_time))
95 changes: 95 additions & 0 deletions esphome/components/hbridge/switch/hbridge_switch.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#include "hbridge_switch.h"
#include "esphome/core/log.h"

#include <cinttypes>

namespace esphome {
namespace hbridge {

static const char *const TAG = "switch.hbridge";

float HBridgeSwitch::get_setup_priority() const { return setup_priority::HARDWARE; }
void HBridgeSwitch::setup() {
ESP_LOGCONFIG(TAG, "Setting up H-Bridge Switch '%s'...", this->name_.c_str());

optional<bool> initial_state = this->get_initial_state_with_restore_mode().value_or(false);

// Like GPIOSwitch does, set the pin state both before and after pin setup()
this->on_pin_->digital_write(false);
this->on_pin_->setup();
this->on_pin_->digital_write(false);

this->off_pin_->digital_write(false);
this->off_pin_->setup();
this->off_pin_->digital_write(false);

if (initial_state.has_value())
this->write_state(initial_state);
}

void HBridgeSwitch::dump_config() {
LOG_SWITCH("", "H-Bridge Switch", this);
LOG_PIN(" On Pin: ", this->on_pin_);
LOG_PIN(" Off Pin: ", this->off_pin_);
ESP_LOGCONFIG(TAG, " Pulse length: %" PRId32 " ms", this->pulse_length_);
if (this->wait_time_)
ESP_LOGCONFIG(TAG, " Wait time %" PRId32 " ms", this->wait_time_);
}

void HBridgeSwitch::write_state(bool state) {
this->desired_state_ = state;
if (!this->timer_running_)
this->timer_fn_();
}

void HBridgeSwitch::timer_fn_() {
uint32_t next_timeout = 0;

while ((uint8_t) this->desired_state_ != this->relay_state_) {
switch (this->relay_state_) {
case RELAY_STATE_ON:
case RELAY_STATE_OFF:
case RELAY_STATE_UNKNOWN:
if (this->desired_state_) {
this->on_pin_->digital_write(true);
this->relay_state_ = RELAY_STATE_SWITCHING_ON;
} else {
this->off_pin_->digital_write(true);
this->relay_state_ = RELAY_STATE_SWITCHING_OFF;
}
next_timeout = this->pulse_length_;
if (!this->optimistic_)
this->publish_state(this->desired_state_);
break;

case RELAY_STATE_SWITCHING_ON:
this->on_pin_->digital_write(false);
this->relay_state_ = RELAY_STATE_ON;
if (this->optimistic_)
this->publish_state(true);
next_timeout = this->wait_time_;
break;

case RELAY_STATE_SWITCHING_OFF:
this->off_pin_->digital_write(false);
this->relay_state_ = RELAY_STATE_OFF;
if (this->optimistic_)
this->publish_state(false);
next_timeout = this->wait_time_;
break;
}

if (next_timeout) {
this->timer_running_ = true;
this->set_timeout(next_timeout, [this]() { this->timer_fn_(); });
return;
}

// In the case where ON/OFF state has been reached but we need to
// immediately change back again to reach desired_state_, we loop.
}
this->timer_running_ = false;
}

} // namespace hbridge
} // namespace esphome
50 changes: 50 additions & 0 deletions esphome/components/hbridge/switch/hbridge_switch.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once

#include "esphome/core/component.h"
#include "esphome/core/hal.h"
#include "esphome/components/switch/switch.h"

#include <vector>

namespace esphome {
namespace hbridge {

enum RelayState : uint8_t {
RELAY_STATE_OFF = 0,
RELAY_STATE_ON = 1,
RELAY_STATE_SWITCHING_ON = 2,
RELAY_STATE_SWITCHING_OFF = 3,
RELAY_STATE_UNKNOWN = 4,
};

class HBridgeSwitch : public switch_::Switch, public Component {
public:
void set_on_pin(GPIOPin *pin) { this->on_pin_ = pin; }
void set_off_pin(GPIOPin *pin) { this->off_pin_ = pin; }
void set_pulse_length(uint32_t pulse_length) { this->pulse_length_ = pulse_length; }
void set_wait_time(uint32_t wait_time) { this->wait_time_ = wait_time; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }

// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
float get_setup_priority() const override;

void setup() override;
void dump_config() override;

protected:
void write_state(bool state) override;
void timer_fn_();

bool timer_running_{false};
bool desired_state_{false};
RelayState relay_state_{RELAY_STATE_UNKNOWN};
GPIOPin *on_pin_{nullptr};
GPIOPin *off_pin_{nullptr};
uint32_t pulse_length_{0};
uint32_t wait_time_{0};
bool optimistic_{false};
};

} // namespace hbridge
} // namespace esphome
39 changes: 39 additions & 0 deletions tests/components/hbridge/common.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
output:
- platform: ${pwm_platform}
pin: ${output1_pin}
id: gpio_output1
- platform: ${pwm_platform}
pin: ${output2_pin}
id: gpio_output2
- platform: ${pwm_platform}
pin: ${output3_pin}
id: gpio_output3
- platform: ${pwm_platform}
pin: ${output4_pin}
id: gpio_output4

light:
- platform: hbridge
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4

fan:
- platform: hbridge
id: fan_hbridge
speed_count: 4
name: H-bridge Fan with Presets
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!

switch:
- platform: hbridge
id: switch_hbridge
on_pin: ${hbridge_on_pin}
off_pin: ${hbridge_off_pin}
46 changes: 15 additions & 31 deletions tests/components/hbridge/test.esp32-ard.yaml
Original file line number Diff line number Diff line change
@@ -1,33 +1,17 @@
output:
- platform: ledc
pin: 14
id: gpio_output1
- platform: ledc
pin: 15
id: gpio_output2
- platform: ledc
pin: 12
id: gpio_output3
- platform: ledc
pin: 13
id: gpio_output4
substitutions:
pwm_platform: ledc
output1_pin: "14"
output2_pin: "15"
output3_pin: "12"
output4_pin: "13"
hbridge_on_pin: "4"
hbridge_off_pin: "5"

light:
- platform: hbridge
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
packages:
common: !include common.yaml

fan:
- platform: hbridge
id: fan_hbridge
speed_count: 4
name: H-bridge Fan with Presets
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!
switch:
- id: !extend switch_hbridge
pulse_length: 60ms
wait_time: 10ms
optimistic: false
45 changes: 14 additions & 31 deletions tests/components/hbridge/test.esp32-c3-ard.yaml
Original file line number Diff line number Diff line change
@@ -1,33 +1,16 @@
output:
- platform: ledc
pin: 4
id: gpio_output1
- platform: ledc
pin: 5
id: gpio_output2
- platform: ledc
pin: 6
id: gpio_output3
- platform: ledc
pin: 7
id: gpio_output4
substitutions:
pwm_platform: "ledc"
output1_pin: "4"
output2_pin: "5"
output3_pin: "6"
output4_pin: "7"
hbridge_on_pin: "2"
hbridge_off_pin: "3"

light:
- platform: hbridge
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
packages:
common: !include common.yaml

fan:
- platform: hbridge
id: fan_hbridge
speed_count: 4
name: H-bridge Fan with Presets
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!
switch:
- id: !extend switch_hbridge
wait_time: 10ms
optimistic: true
44 changes: 13 additions & 31 deletions tests/components/hbridge/test.esp32-c3-idf.yaml
Original file line number Diff line number Diff line change
@@ -1,33 +1,15 @@
output:
- platform: ledc
pin: 4
id: gpio_output1
- platform: ledc
pin: 5
id: gpio_output2
- platform: ledc
pin: 6
id: gpio_output3
- platform: ledc
pin: 7
id: gpio_output4
substitutions:
pwm_platform: "ledc"
output1_pin: "4"
output2_pin: "5"
output3_pin: "6"
output4_pin: "7"
hbridge_on_pin: "2"
hbridge_off_pin: "3"

light:
- platform: hbridge
name: Icicle Lights
pin_a: gpio_output3
pin_b: gpio_output4
packages:
common: !include common.yaml

fan:
- platform: hbridge
id: fan_hbridge
speed_count: 4
name: H-bridge Fan with Presets
pin_a: gpio_output1
pin_b: gpio_output2
preset_modes:
- Preset 1
- Preset 2
on_preset_set:
then:
- logger.log: Preset mode was changed!
switch:
- id: !extend switch_hbridge
pulse_length: 60ms
Loading

0 comments on commit fb96e35

Please sign in to comment.