From ead0747b7bb89e276c4a8aa5c1850598304b8296 Mon Sep 17 00:00:00 2001 From: David Woodhouse Date: Sat, 7 Sep 2024 13:29:53 +0100 Subject: [PATCH] Add H-Bridge switch component --- esphome/components/hbridge/switch/__init__.py | 43 +++++++++ .../hbridge/switch/hbridge_switch.cpp | 89 +++++++++++++++++++ .../hbridge/switch/hbridge_switch.h | 48 ++++++++++ tests/components/hbridge/test.esp32-ard.yaml | 8 ++ .../components/hbridge/test.esp32-c3-idf.yaml | 7 ++ tests/components/hbridge/test.esp32-idf.yaml | 8 ++ .../components/hbridge/test.esp8266-ard.yaml | 8 ++ tests/components/hbridge/test.rp2040-ard.yaml | 7 ++ 8 files changed, 218 insertions(+) create mode 100644 esphome/components/hbridge/switch/__init__.py create mode 100644 esphome/components/hbridge/switch/hbridge_switch.cpp create mode 100644 esphome/components/hbridge/switch/hbridge_switch.h diff --git a/esphome/components/hbridge/switch/__init__.py b/esphome/components/hbridge/switch/__init__.py new file mode 100644 index 0000000000..4861635fe1 --- /dev/null +++ b/esphome/components/hbridge/switch/__init__.py @@ -0,0 +1,43 @@ +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_WAIT_TIME + +from .. import hbridge_ns + +HBridgeSwitch = hbridge_ns.class_("HBridgeSwitch", switch.Switch, cg.Component) + +CODEOWNERS = ["@dwmw2"] + +CONF_ON_PIN = "on_pin" +CONF_OFF_PIN = "off_pin" +CONF_PULSE_LENGTH = "pulse_length" + +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, + } + ) + .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])) + if wait_time := config.get(CONF_WAIT_TIME): + cg.add(var.set_wait_time(wait_time)) diff --git a/esphome/components/hbridge/switch/hbridge_switch.cpp b/esphome/components/hbridge/switch/hbridge_switch.cpp new file mode 100644 index 0000000000..2f5cebba55 --- /dev/null +++ b/esphome/components/hbridge/switch/hbridge_switch.cpp @@ -0,0 +1,89 @@ +#include "hbridge_switch.h" +#include "esphome/core/log.h" + +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 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: %d ms", this->pulse_length_); + if (this->wait_time_) + ESP_LOGCONFIG(TAG, " Wait time %d ms", this->wait_time_); +} + +void HBridgeSwitch::write_state(bool state) { + this->desired_state_ = state; + if (!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_; + break; + + case RELAY_STATE_SWITCHING_ON: + this->on_pin_->digital_write(false); + this->relay_state_ = RELAY_STATE_ON; + 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; + 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 diff --git a/esphome/components/hbridge/switch/hbridge_switch.h b/esphome/components/hbridge/switch/hbridge_switch.h new file mode 100644 index 0000000000..6c2abe1494 --- /dev/null +++ b/esphome/components/hbridge/switch/hbridge_switch.h @@ -0,0 +1,48 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/hal.h" +#include "esphome/components/switch/switch.h" + +#include + +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; } + + // ========== 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_; + RelayState relay_state_{RELAY_STATE_UNKNOWN}; + GPIOPin *on_pin_; + GPIOPin *off_pin_; + uint32_t pulse_length_; + uint32_t wait_time_{0}; +}; + +} // namespace hbridge +} // namespace esphome diff --git a/tests/components/hbridge/test.esp32-ard.yaml b/tests/components/hbridge/test.esp32-ard.yaml index 6a80aaaf3b..ef6321e1d1 100644 --- a/tests/components/hbridge/test.esp32-ard.yaml +++ b/tests/components/hbridge/test.esp32-ard.yaml @@ -31,3 +31,11 @@ fan: on_preset_set: then: - logger.log: Preset mode was changed! + +switch: + - platform: hbridge + id: switch_hbridge + on_pin: 14 + off_pin: 15 + pulse_length: 60ms + wait_time: 10ms diff --git a/tests/components/hbridge/test.esp32-c3-idf.yaml b/tests/components/hbridge/test.esp32-c3-idf.yaml index 70cfd6ab6f..609a155374 100644 --- a/tests/components/hbridge/test.esp32-c3-idf.yaml +++ b/tests/components/hbridge/test.esp32-c3-idf.yaml @@ -31,3 +31,10 @@ fan: on_preset_set: then: - logger.log: Preset mode was changed! + +switch: + - platform: hbridge + id: switch_hbridge + on_pin: 2 + off_pin: 3 + pulse_length: 60ms diff --git a/tests/components/hbridge/test.esp32-idf.yaml b/tests/components/hbridge/test.esp32-idf.yaml index 6a80aaaf3b..ef6321e1d1 100644 --- a/tests/components/hbridge/test.esp32-idf.yaml +++ b/tests/components/hbridge/test.esp32-idf.yaml @@ -31,3 +31,11 @@ fan: on_preset_set: then: - logger.log: Preset mode was changed! + +switch: + - platform: hbridge + id: switch_hbridge + on_pin: 14 + off_pin: 15 + pulse_length: 60ms + wait_time: 10ms diff --git a/tests/components/hbridge/test.esp8266-ard.yaml b/tests/components/hbridge/test.esp8266-ard.yaml index 4f8915879d..7616e0a919 100644 --- a/tests/components/hbridge/test.esp8266-ard.yaml +++ b/tests/components/hbridge/test.esp8266-ard.yaml @@ -31,3 +31,11 @@ fan: on_preset_set: then: - logger.log: Preset mode was changed! + +switch: + - platform: hbridge + id: switch_hbridge + on_pin: 14 + off_pin: 15 + pulse_length: 60ms + wait_time: 10ms diff --git a/tests/components/hbridge/test.rp2040-ard.yaml b/tests/components/hbridge/test.rp2040-ard.yaml index e21b55091d..9525da3e9c 100644 --- a/tests/components/hbridge/test.rp2040-ard.yaml +++ b/tests/components/hbridge/test.rp2040-ard.yaml @@ -31,3 +31,10 @@ fan: on_preset_set: then: - logger.log: Preset mode was changed! + +switch: + - platform: hbridge + id: switch_hbridge + on_pin: 2 + off_pin: 3 + wait_time: 10ms