mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Add H-Bridge fan component (#2212)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -0,0 +1,3 @@ | ||||
| import esphome.codegen as cg | ||||
|  | ||||
| hbridge_ns = cg.esphome_ns.namespace("hbridge") | ||||
|   | ||||
							
								
								
									
										70
									
								
								esphome/components/hbridge/fan/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								esphome/components/hbridge/fan/__init__.py
									
									
									
									
									
										Normal 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)) | ||||
							
								
								
									
										85
									
								
								esphome/components/hbridge/fan/hbridge_fan.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								esphome/components/hbridge/fan/hbridge_fan.cpp
									
									
									
									
									
										Normal 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 | ||||
							
								
								
									
										58
									
								
								esphome/components/hbridge/fan/hbridge_fan.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								esphome/components/hbridge/fan/hbridge_fan.h
									
									
									
									
									
										Normal 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 | ||||
| @@ -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 | ||||
| ) | ||||
| @@ -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" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user