mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Refactor fan platform to resemble climate/cover platforms (#2848)
Co-authored-by: Oxan van Leeuwen <oxan@oxanvanleeuwen.nl> Co-authored-by: rob-deutsch <robzyb+altgithub@gmail.com> Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		| @@ -27,23 +27,24 @@ from esphome.cpp_helpers import setup_entity | ||||
| IS_PLATFORM_COMPONENT = True | ||||
|  | ||||
| fan_ns = cg.esphome_ns.namespace("fan") | ||||
| FanState = fan_ns.class_("FanState", cg.EntityBase, cg.Component) | ||||
| MakeFan = cg.Application.struct("MakeFan") | ||||
| Fan = fan_ns.class_("Fan", cg.EntityBase) | ||||
| FanState = fan_ns.class_("Fan", Fan, cg.Component) | ||||
|  | ||||
| FanDirection = fan_ns.enum("FanDirection") | ||||
| FanDirection = fan_ns.enum("FanDirection", is_class=True) | ||||
| FAN_DIRECTION_ENUM = { | ||||
|     "FORWARD": FanDirection.FAN_DIRECTION_FORWARD, | ||||
|     "REVERSE": FanDirection.FAN_DIRECTION_REVERSE, | ||||
|     "FORWARD": FanDirection.FORWARD, | ||||
|     "REVERSE": FanDirection.REVERSE, | ||||
| } | ||||
|  | ||||
| FanRestoreMode = fan_ns.enum("FanRestoreMode") | ||||
| FanRestoreMode = fan_ns.enum("FanRestoreMode", is_class=True) | ||||
| RESTORE_MODES = { | ||||
|     "RESTORE_DEFAULT_OFF": FanRestoreMode.FAN_RESTORE_DEFAULT_OFF, | ||||
|     "RESTORE_DEFAULT_ON": FanRestoreMode.FAN_RESTORE_DEFAULT_ON, | ||||
|     "ALWAYS_OFF": FanRestoreMode.FAN_ALWAYS_OFF, | ||||
|     "ALWAYS_ON": FanRestoreMode.FAN_ALWAYS_ON, | ||||
|     "RESTORE_INVERTED_DEFAULT_OFF": FanRestoreMode.FAN_RESTORE_INVERTED_DEFAULT_OFF, | ||||
|     "RESTORE_INVERTED_DEFAULT_ON": FanRestoreMode.FAN_RESTORE_INVERTED_DEFAULT_ON, | ||||
|     "NO_RESTORE": FanRestoreMode.NO_RESTORE, | ||||
|     "ALWAYS_OFF": FanRestoreMode.ALWAYS_OFF, | ||||
|     "ALWAYS_ON": FanRestoreMode.ALWAYS_ON, | ||||
|     "RESTORE_DEFAULT_OFF": FanRestoreMode.RESTORE_DEFAULT_OFF, | ||||
|     "RESTORE_DEFAULT_ON": FanRestoreMode.RESTORE_DEFAULT_ON, | ||||
|     "RESTORE_INVERTED_DEFAULT_OFF": FanRestoreMode.RESTORE_INVERTED_DEFAULT_OFF, | ||||
|     "RESTORE_INVERTED_DEFAULT_ON": FanRestoreMode.RESTORE_INVERTED_DEFAULT_ON, | ||||
| } | ||||
|  | ||||
| # Actions | ||||
| @@ -61,8 +62,8 @@ FanIsOffCondition = fan_ns.class_("FanIsOffCondition", automation.Condition.temp | ||||
|  | ||||
| FAN_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend( | ||||
|     { | ||||
|         cv.GenerateID(): cv.declare_id(FanState), | ||||
|         cv.Optional(CONF_RESTORE_MODE, default="restore_default_off"): cv.enum( | ||||
|         cv.GenerateID(): cv.declare_id(Fan), | ||||
|         cv.Optional(CONF_RESTORE_MODE, default="RESTORE_DEFAULT_OFF"): cv.enum( | ||||
|             RESTORE_MODES, upper=True, space="_" | ||||
|         ), | ||||
|         cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent), | ||||
| @@ -158,19 +159,19 @@ async def register_fan(var, config): | ||||
|     if not CORE.has_id(config[CONF_ID]): | ||||
|         var = cg.Pvariable(config[CONF_ID], var) | ||||
|     cg.add(cg.App.register_fan(var)) | ||||
|     await cg.register_component(var, config) | ||||
|     await setup_fan_core_(var, config) | ||||
|  | ||||
|  | ||||
| async def create_fan_state(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await register_fan(var, config) | ||||
|     await cg.register_component(var, config) | ||||
|     return var | ||||
|  | ||||
|  | ||||
| FAN_ACTION_SCHEMA = maybe_simple_id( | ||||
|     { | ||||
|         cv.Required(CONF_ID): cv.use_id(FanState), | ||||
|         cv.Required(CONF_ID): cv.use_id(Fan), | ||||
|     } | ||||
| ) | ||||
|  | ||||
| @@ -192,7 +193,7 @@ async def fan_turn_off_to_code(config, action_id, template_arg, args): | ||||
|     TurnOnAction, | ||||
|     maybe_simple_id( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.use_id(FanState), | ||||
|             cv.Required(CONF_ID): cv.use_id(Fan), | ||||
|             cv.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean), | ||||
|             cv.Optional(CONF_SPEED): cv.templatable(cv.int_range(1)), | ||||
|             cv.Optional(CONF_DIRECTION): cv.templatable( | ||||
| @@ -227,7 +228,7 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args): | ||||
|     FanIsOnCondition, | ||||
|     automation.maybe_simple_id( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.use_id(FanState), | ||||
|             cv.Required(CONF_ID): cv.use_id(Fan), | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
| @@ -236,7 +237,7 @@ async def fan_cycle_speed_to_code(config, action_id, template_arg, args): | ||||
|     FanIsOffCondition, | ||||
|     automation.maybe_simple_id( | ||||
|         { | ||||
|             cv.Required(CONF_ID): cv.use_id(FanState), | ||||
|             cv.Required(CONF_ID): cv.use_id(Fan), | ||||
|         } | ||||
|     ), | ||||
| ) | ||||
|   | ||||
| @@ -1,10 +0,0 @@ | ||||
| #include "automation.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace fan { | ||||
|  | ||||
| static const char *const TAG = "fan.automation"; | ||||
|  | ||||
| }  // namespace fan | ||||
| }  // namespace esphome | ||||
| @@ -9,7 +9,7 @@ namespace fan { | ||||
|  | ||||
| template<typename... Ts> class TurnOnAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit TurnOnAction(FanState *state) : state_(state) {} | ||||
|   explicit TurnOnAction(Fan *state) : state_(state) {} | ||||
|  | ||||
|   TEMPLATABLE_VALUE(bool, oscillating) | ||||
|   TEMPLATABLE_VALUE(int, speed) | ||||
| @@ -29,30 +29,30 @@ template<typename... Ts> class TurnOnAction : public Action<Ts...> { | ||||
|     call.perform(); | ||||
|   } | ||||
|  | ||||
|   FanState *state_; | ||||
|   Fan *state_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class TurnOffAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit TurnOffAction(FanState *state) : state_(state) {} | ||||
|   explicit TurnOffAction(Fan *state) : state_(state) {} | ||||
|  | ||||
|   void play(Ts... x) override { this->state_->turn_off().perform(); } | ||||
|  | ||||
|   FanState *state_; | ||||
|   Fan *state_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class ToggleAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit ToggleAction(FanState *state) : state_(state) {} | ||||
|   explicit ToggleAction(Fan *state) : state_(state) {} | ||||
|  | ||||
|   void play(Ts... x) override { this->state_->toggle().perform(); } | ||||
|  | ||||
|   FanState *state_; | ||||
|   Fan *state_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class CycleSpeedAction : public Action<Ts...> { | ||||
|  public: | ||||
|   explicit CycleSpeedAction(FanState *state) : state_(state) {} | ||||
|   explicit CycleSpeedAction(Fan *state) : state_(state) {} | ||||
|  | ||||
|   void play(Ts... x) override { | ||||
|     // check to see if fan supports speeds and is on | ||||
| @@ -83,29 +83,29 @@ template<typename... Ts> class CycleSpeedAction : public Action<Ts...> { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   FanState *state_; | ||||
|   Fan *state_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class FanIsOnCondition : public Condition<Ts...> { | ||||
|  public: | ||||
|   explicit FanIsOnCondition(FanState *state) : state_(state) {} | ||||
|   explicit FanIsOnCondition(Fan *state) : state_(state) {} | ||||
|   bool check(Ts... x) override { return this->state_->state; } | ||||
|  | ||||
|  protected: | ||||
|   FanState *state_; | ||||
|   Fan *state_; | ||||
| }; | ||||
| template<typename... Ts> class FanIsOffCondition : public Condition<Ts...> { | ||||
|  public: | ||||
|   explicit FanIsOffCondition(FanState *state) : state_(state) {} | ||||
|   explicit FanIsOffCondition(Fan *state) : state_(state) {} | ||||
|   bool check(Ts... x) override { return !this->state_->state; } | ||||
|  | ||||
|  protected: | ||||
|   FanState *state_; | ||||
|   Fan *state_; | ||||
| }; | ||||
|  | ||||
| class FanTurnOnTrigger : public Trigger<> { | ||||
|  public: | ||||
|   FanTurnOnTrigger(FanState *state) { | ||||
|   FanTurnOnTrigger(Fan *state) { | ||||
|     state->add_on_state_callback([this, state]() { | ||||
|       auto is_on = state->state; | ||||
|       auto should_trigger = is_on && !this->last_on_; | ||||
| @@ -123,7 +123,7 @@ class FanTurnOnTrigger : public Trigger<> { | ||||
|  | ||||
| class FanTurnOffTrigger : public Trigger<> { | ||||
|  public: | ||||
|   FanTurnOffTrigger(FanState *state) { | ||||
|   FanTurnOffTrigger(Fan *state) { | ||||
|     state->add_on_state_callback([this, state]() { | ||||
|       auto is_on = state->state; | ||||
|       auto should_trigger = !is_on && this->last_on_; | ||||
| @@ -141,7 +141,7 @@ class FanTurnOffTrigger : public Trigger<> { | ||||
|  | ||||
| class FanSpeedSetTrigger : public Trigger<> { | ||||
|  public: | ||||
|   FanSpeedSetTrigger(FanState *state) { | ||||
|   FanSpeedSetTrigger(Fan *state) { | ||||
|     state->add_on_state_callback([this, state]() { | ||||
|       auto speed = state->speed; | ||||
|       auto should_trigger = speed != !this->last_speed_; | ||||
|   | ||||
							
								
								
									
										175
									
								
								esphome/components/fan/fan.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										175
									
								
								esphome/components/fan/fan.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,175 @@ | ||||
| #include "fan.h" | ||||
| #include "fan_helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace fan { | ||||
|  | ||||
| static const char *const TAG = "fan"; | ||||
|  | ||||
| const LogString *fan_direction_to_string(FanDirection direction) { | ||||
|   switch (direction) { | ||||
|     case FanDirection::FORWARD: | ||||
|       return LOG_STR("FORWARD"); | ||||
|     case FanDirection::REVERSE: | ||||
|       return LOG_STR("REVERSE"); | ||||
|     default: | ||||
|       return LOG_STR("UNKNOWN"); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void FanCall::perform() { | ||||
|   ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str()); | ||||
|   this->validate_(); | ||||
|   if (this->binary_state_.has_value()) | ||||
|     ESP_LOGD(TAG, "  State: %s", ONOFF(*this->binary_state_)); | ||||
|   if (this->oscillating_.has_value()) | ||||
|     ESP_LOGD(TAG, "  Oscillating: %s", YESNO(*this->oscillating_)); | ||||
|   if (this->speed_.has_value()) | ||||
|     ESP_LOGD(TAG, "  Speed: %d", *this->speed_); | ||||
|   if (this->direction_.has_value()) | ||||
|     ESP_LOGD(TAG, "  Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_))); | ||||
|  | ||||
|   this->parent_.control(*this); | ||||
| } | ||||
| void FanCall::validate_() { | ||||
|   auto traits = this->parent_.get_traits(); | ||||
|  | ||||
|   if (this->speed_.has_value()) | ||||
|     this->speed_ = clamp(*this->speed_, 1, traits.supported_speed_count()); | ||||
|  | ||||
|   if (this->binary_state_.has_value() && *this->binary_state_) { | ||||
|     // when turning on, if current speed is zero, set speed to 100% | ||||
|     if (traits.supports_speed() && !this->parent_.state && this->parent_.speed == 0) { | ||||
|       this->speed_ = traits.supported_speed_count(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (this->oscillating_.has_value() && !traits.supports_oscillation()) { | ||||
|     ESP_LOGW(TAG, "'%s' - This fan does not support oscillation!", this->parent_.get_name().c_str()); | ||||
|     this->oscillating_.reset(); | ||||
|   } | ||||
|  | ||||
|   if (this->speed_.has_value() && !traits.supports_speed()) { | ||||
|     ESP_LOGW(TAG, "'%s' - This fan does not support speeds!", this->parent_.get_name().c_str()); | ||||
|     this->speed_.reset(); | ||||
|   } | ||||
|  | ||||
|   if (this->direction_.has_value() && !traits.supports_direction()) { | ||||
|     ESP_LOGW(TAG, "'%s' - This fan does not support directions!", this->parent_.get_name().c_str()); | ||||
|     this->direction_.reset(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // This whole method is deprecated, don't warn about usage of deprecated methods inside of it. | ||||
| #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||
| FanCall &FanCall::set_speed(const char *legacy_speed) { | ||||
|   const auto supported_speed_count = this->parent_.get_traits().supported_speed_count(); | ||||
|   if (strcasecmp(legacy_speed, "low") == 0) { | ||||
|     this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count)); | ||||
|   } else if (strcasecmp(legacy_speed, "medium") == 0) { | ||||
|     this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count)); | ||||
|   } else if (strcasecmp(legacy_speed, "high") == 0) { | ||||
|     this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count)); | ||||
|   } | ||||
|   return *this; | ||||
| } | ||||
| #pragma GCC diagnostic pop | ||||
|  | ||||
| FanCall FanRestoreState::to_call(Fan &fan) { | ||||
|   auto call = fan.make_call(); | ||||
|   call.set_state(this->state); | ||||
|   call.set_oscillating(this->oscillating); | ||||
|   call.set_speed(this->speed); | ||||
|   call.set_direction(this->direction); | ||||
|   return call; | ||||
| } | ||||
| void FanRestoreState::apply(Fan &fan) { | ||||
|   fan.state = this->state; | ||||
|   fan.oscillating = this->oscillating; | ||||
|   fan.speed = this->speed; | ||||
|   fan.direction = this->direction; | ||||
|   fan.publish_state(); | ||||
| } | ||||
|  | ||||
| Fan::Fan() : EntityBase("") {} | ||||
| Fan::Fan(const std::string &name) : EntityBase(name) {} | ||||
|  | ||||
| FanCall Fan::turn_on() { return this->make_call().set_state(true); } | ||||
| FanCall Fan::turn_off() { return this->make_call().set_state(false); } | ||||
| FanCall Fan::toggle() { return this->make_call().set_state(!this->state); } | ||||
| FanCall Fan::make_call() { return FanCall(*this); } | ||||
|  | ||||
| void Fan::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); } | ||||
| void Fan::publish_state() { | ||||
|   auto traits = this->get_traits(); | ||||
|  | ||||
|   ESP_LOGD(TAG, "'%s' - Sending state:", this->name_.c_str()); | ||||
|   ESP_LOGD(TAG, "  State: %s", ONOFF(this->state)); | ||||
|   if (traits.supports_speed()) | ||||
|     ESP_LOGD(TAG, "  Speed: %d", this->speed); | ||||
|   if (traits.supports_oscillation()) | ||||
|     ESP_LOGD(TAG, "  Oscillating: %s", YESNO(this->oscillating)); | ||||
|   if (traits.supports_direction()) | ||||
|     ESP_LOGD(TAG, "  Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction))); | ||||
|  | ||||
|   this->state_callback_.call(); | ||||
|   this->save_state_(); | ||||
| } | ||||
|  | ||||
| // Random 32-bit value, change this every time the layout of the FanRestoreState struct changes. | ||||
| constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA; | ||||
| optional<FanRestoreState> Fan::restore_state_() { | ||||
|   FanRestoreState recovered{}; | ||||
|   this->rtc_ = global_preferences->make_preference<FanRestoreState>(this->get_object_id_hash() ^ RESTORE_STATE_VERSION); | ||||
|   bool restored = this->rtc_.load(&recovered); | ||||
|  | ||||
|   switch (this->restore_mode_) { | ||||
|     case FanRestoreMode::NO_RESTORE: | ||||
|       return {}; | ||||
|     case FanRestoreMode::ALWAYS_OFF: | ||||
|       recovered.state = false; | ||||
|       return recovered; | ||||
|     case FanRestoreMode::ALWAYS_ON: | ||||
|       recovered.state = true; | ||||
|       return recovered; | ||||
|     case FanRestoreMode::RESTORE_DEFAULT_OFF: | ||||
|       recovered.state = restored ? recovered.state : false; | ||||
|       return recovered; | ||||
|     case FanRestoreMode::RESTORE_DEFAULT_ON: | ||||
|       recovered.state = restored ? recovered.state : true; | ||||
|       return recovered; | ||||
|     case FanRestoreMode::RESTORE_INVERTED_DEFAULT_OFF: | ||||
|       recovered.state = restored ? !recovered.state : false; | ||||
|       return recovered; | ||||
|     case FanRestoreMode::RESTORE_INVERTED_DEFAULT_ON: | ||||
|       recovered.state = restored ? !recovered.state : true; | ||||
|       return recovered; | ||||
|   } | ||||
|  | ||||
|   return {}; | ||||
| } | ||||
| void Fan::save_state_() { | ||||
|   FanRestoreState state{}; | ||||
|   state.state = this->state; | ||||
|   state.oscillating = this->oscillating; | ||||
|   state.speed = this->speed; | ||||
|   state.direction = this->direction; | ||||
|   this->rtc_.save(&state); | ||||
| } | ||||
|  | ||||
| void Fan::dump_traits_(const char *tag, const char *prefix) { | ||||
|   if (this->get_traits().supports_speed()) { | ||||
|     ESP_LOGCONFIG(tag, "%s  Speed: YES", prefix); | ||||
|     ESP_LOGCONFIG(tag, "%s  Speed count: %d", prefix, this->get_traits().supported_speed_count()); | ||||
|   } | ||||
|   if (this->get_traits().supports_oscillation()) | ||||
|     ESP_LOGCONFIG(tag, "%s  Oscillation: YES", prefix); | ||||
|   if (this->get_traits().supports_direction()) | ||||
|     ESP_LOGCONFIG(tag, "%s  Direction: YES", prefix); | ||||
| } | ||||
| uint32_t Fan::hash_base() { return 418001110UL; } | ||||
|  | ||||
| }  // namespace fan | ||||
| }  // namespace esphome | ||||
							
								
								
									
										154
									
								
								esphome/components/fan/fan.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								esphome/components/fan/fan.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,154 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/entity_base.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/optional.h" | ||||
| #include "esphome/core/preferences.h" | ||||
| #include "fan_traits.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace fan { | ||||
|  | ||||
| #define LOG_FAN(prefix, type, obj) \ | ||||
|   if ((obj) != nullptr) { \ | ||||
|     ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \ | ||||
|     (obj)->dump_traits_(TAG, prefix); \ | ||||
|   } | ||||
|  | ||||
| /// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon | ||||
| enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed { | ||||
|   FAN_SPEED_LOW = 0,     ///< The fan is running on low speed. | ||||
|   FAN_SPEED_MEDIUM = 1,  ///< The fan is running on medium speed. | ||||
|   FAN_SPEED_HIGH = 2     ///< The fan is running on high/full speed. | ||||
| }; | ||||
|  | ||||
| /// Simple enum to represent the direction of a fan. | ||||
| enum class FanDirection { FORWARD = 0, REVERSE = 1 }; | ||||
|  | ||||
| /// Restore mode of a fan. | ||||
| enum class FanRestoreMode { | ||||
|   NO_RESTORE, | ||||
|   ALWAYS_OFF, | ||||
|   ALWAYS_ON, | ||||
|   RESTORE_DEFAULT_OFF, | ||||
|   RESTORE_DEFAULT_ON, | ||||
|   RESTORE_INVERTED_DEFAULT_OFF, | ||||
|   RESTORE_INVERTED_DEFAULT_ON, | ||||
| }; | ||||
|  | ||||
| const LogString *fan_direction_to_string(FanDirection direction); | ||||
|  | ||||
| class Fan; | ||||
|  | ||||
| class FanCall { | ||||
|  public: | ||||
|   explicit FanCall(Fan &parent) : parent_(parent) {} | ||||
|  | ||||
|   FanCall &set_state(bool binary_state) { | ||||
|     this->binary_state_ = binary_state; | ||||
|     return *this; | ||||
|   } | ||||
|   FanCall &set_state(optional<bool> binary_state) { | ||||
|     this->binary_state_ = binary_state; | ||||
|     return *this; | ||||
|   } | ||||
|   optional<bool> get_state() const { return this->binary_state_; } | ||||
|   FanCall &set_oscillating(bool oscillating) { | ||||
|     this->oscillating_ = oscillating; | ||||
|     return *this; | ||||
|   } | ||||
|   FanCall &set_oscillating(optional<bool> oscillating) { | ||||
|     this->oscillating_ = oscillating; | ||||
|     return *this; | ||||
|   } | ||||
|   optional<bool> get_oscillating() const { return this->oscillating_; } | ||||
|   FanCall &set_speed(int speed) { | ||||
|     this->speed_ = speed; | ||||
|     return *this; | ||||
|   } | ||||
|   ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9") | ||||
|   FanCall &set_speed(const char *legacy_speed); | ||||
|   optional<int> get_speed() const { return this->speed_; } | ||||
|   FanCall &set_direction(FanDirection direction) { | ||||
|     this->direction_ = direction; | ||||
|     return *this; | ||||
|   } | ||||
|   FanCall &set_direction(optional<FanDirection> direction) { | ||||
|     this->direction_ = direction; | ||||
|     return *this; | ||||
|   } | ||||
|   optional<FanDirection> get_direction() const { return this->direction_; } | ||||
|  | ||||
|   void perform(); | ||||
|  | ||||
|  protected: | ||||
|   void validate_(); | ||||
|  | ||||
|   Fan &parent_; | ||||
|   optional<bool> binary_state_; | ||||
|   optional<bool> oscillating_; | ||||
|   optional<int> speed_; | ||||
|   optional<FanDirection> direction_{}; | ||||
| }; | ||||
|  | ||||
| struct FanRestoreState { | ||||
|   bool state; | ||||
|   int speed; | ||||
|   bool oscillating; | ||||
|   FanDirection direction; | ||||
|  | ||||
|   /// Convert this struct to a fan call that can be performed. | ||||
|   FanCall to_call(Fan &fan); | ||||
|   /// Apply these settings to the fan. | ||||
|   void apply(Fan &fan); | ||||
| } __attribute__((packed)); | ||||
|  | ||||
| class Fan : public EntityBase { | ||||
|  public: | ||||
|   Fan(); | ||||
|   /// Construct the fan with name. | ||||
|   explicit Fan(const std::string &name); | ||||
|  | ||||
|   /// The current on/off state of the fan. | ||||
|   bool state{false}; | ||||
|   /// The current oscillation state of the fan. | ||||
|   bool oscillating{false}; | ||||
|   /// The current fan speed level | ||||
|   int speed{0}; | ||||
|   /// The current direction of the fan | ||||
|   FanDirection direction{FanDirection::FORWARD}; | ||||
|  | ||||
|   FanCall turn_on(); | ||||
|   FanCall turn_off(); | ||||
|   FanCall toggle(); | ||||
|   FanCall make_call(); | ||||
|  | ||||
|   /// Register a callback that will be called each time the state changes. | ||||
|   void add_on_state_callback(std::function<void()> &&callback); | ||||
|  | ||||
|   void publish_state(); | ||||
|  | ||||
|   virtual FanTraits get_traits() = 0; | ||||
|  | ||||
|   /// Set the restore mode of this fan. | ||||
|   void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } | ||||
|  | ||||
|  protected: | ||||
|   friend FanCall; | ||||
|  | ||||
|   virtual void control(const FanCall &call) = 0; | ||||
|  | ||||
|   optional<FanRestoreState> restore_state_(); | ||||
|   void save_state_(); | ||||
|  | ||||
|   void dump_traits_(const char *tag, const char *prefix); | ||||
|   uint32_t hash_base() override; | ||||
|  | ||||
|   CallbackManager<void()> state_callback_{}; | ||||
|   ESPPreferenceObject rtc_; | ||||
|   FanRestoreMode restore_mode_; | ||||
| }; | ||||
|  | ||||
| }  // namespace fan | ||||
| }  // namespace esphome | ||||
| @@ -1,5 +1,6 @@ | ||||
| #pragma once | ||||
| #include "fan_state.h" | ||||
|  | ||||
| #include "fan.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace fan { | ||||
|   | ||||
| @@ -1,121 +1,16 @@ | ||||
| #include "fan_state.h" | ||||
| #include "fan_helpers.h" | ||||
| #include "esphome/core/log.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace fan { | ||||
|  | ||||
| static const char *const TAG = "fan"; | ||||
|  | ||||
| const FanTraits &FanState::get_traits() const { return this->traits_; } | ||||
| void FanState::set_traits(const FanTraits &traits) { this->traits_ = traits; } | ||||
| void FanState::add_on_state_callback(std::function<void()> &&callback) { | ||||
|   this->state_callback_.add(std::move(callback)); | ||||
| } | ||||
| FanState::FanState(const std::string &name) : EntityBase(name) {} | ||||
|  | ||||
| FanStateCall FanState::turn_on() { return this->make_call().set_state(true); } | ||||
| FanStateCall FanState::turn_off() { return this->make_call().set_state(false); } | ||||
| FanStateCall FanState::toggle() { return this->make_call().set_state(!this->state); } | ||||
| FanStateCall FanState::make_call() { return FanStateCall(this); } | ||||
|  | ||||
| struct FanStateRTCState { | ||||
|   bool state; | ||||
|   int speed; | ||||
|   bool oscillating; | ||||
|   FanDirection direction; | ||||
| }; | ||||
|  | ||||
| void FanState::setup() { | ||||
|   auto call = this->make_call(); | ||||
|   FanStateRTCState recovered{}; | ||||
|  | ||||
|   switch (this->restore_mode_) { | ||||
|     case FAN_RESTORE_DEFAULT_OFF: | ||||
|     case FAN_RESTORE_DEFAULT_ON: | ||||
|     case FAN_RESTORE_INVERTED_DEFAULT_OFF: | ||||
|     case FAN_RESTORE_INVERTED_DEFAULT_ON: | ||||
|       this->rtc_ = global_preferences->make_preference<FanStateRTCState>(this->get_object_id_hash()); | ||||
|       if (!this->rtc_.load(&recovered)) { | ||||
|         if (this->restore_mode_ == FAN_RESTORE_DEFAULT_ON || this->restore_mode_ == FAN_RESTORE_INVERTED_DEFAULT_ON) { | ||||
|           call.set_state(true); | ||||
|         } else { | ||||
|           call.set_state(false); | ||||
|         } | ||||
|       } else { | ||||
|         if (this->restore_mode_ == FAN_RESTORE_INVERTED_DEFAULT_OFF || | ||||
|             this->restore_mode_ == FAN_RESTORE_INVERTED_DEFAULT_ON) { | ||||
|           call.set_state(!recovered.state); | ||||
|         } else { | ||||
|           call.set_state(recovered.state); | ||||
|         } | ||||
|  | ||||
|         call.set_speed(recovered.speed); | ||||
|         call.set_oscillating(recovered.oscillating); | ||||
|         call.set_direction(recovered.direction); | ||||
|       } | ||||
|       break; | ||||
|     case FAN_ALWAYS_OFF: | ||||
|     case FAN_ALWAYS_ON: | ||||
|       if (this->restore_mode_ == FAN_ALWAYS_OFF) { | ||||
|         call.set_state(false); | ||||
|       } else if (this->restore_mode_ == FAN_ALWAYS_ON) { | ||||
|         call.set_state(true); | ||||
|       } | ||||
|  | ||||
|       this->rtc_ = global_preferences->make_preference<FanStateRTCState>(this->get_object_id_hash()); | ||||
|       if (this->rtc_.load(&recovered)) { | ||||
|         call.set_speed(recovered.speed); | ||||
|         call.set_oscillating(recovered.oscillating); | ||||
|         call.set_direction(recovered.direction); | ||||
|       } | ||||
|  | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   call.perform(); | ||||
|   auto restore = this->restore_state_(); | ||||
|   if (restore) | ||||
|     restore->to_call(*this).perform(); | ||||
| } | ||||
| float FanState::get_setup_priority() const { return setup_priority::DATA - 1.0f; } | ||||
| uint32_t FanState::hash_base() { return 418001110UL; } | ||||
|  | ||||
| void FanStateCall::perform() const { | ||||
|   if (this->binary_state_.has_value()) { | ||||
|     this->state_->state = *this->binary_state_; | ||||
|   } | ||||
|   if (this->oscillating_.has_value()) { | ||||
|     this->state_->oscillating = *this->oscillating_; | ||||
|   } | ||||
|   if (this->direction_.has_value()) { | ||||
|     this->state_->direction = *this->direction_; | ||||
|   } | ||||
|   if (this->speed_.has_value()) { | ||||
|     const int speed_count = this->state_->get_traits().supported_speed_count(); | ||||
|     this->state_->speed = clamp(*this->speed_, 1, speed_count); | ||||
|   } | ||||
|  | ||||
|   FanStateRTCState saved{}; | ||||
|   saved.state = this->state_->state; | ||||
|   saved.speed = this->state_->speed; | ||||
|   saved.oscillating = this->state_->oscillating; | ||||
|   saved.direction = this->state_->direction; | ||||
|   this->state_->rtc_.save(&saved); | ||||
|  | ||||
|   this->state_->state_callback_.call(); | ||||
| } | ||||
|  | ||||
| // This whole method is deprecated, don't warn about usage of deprecated methods inside of it. | ||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||
| FanStateCall &FanStateCall::set_speed(const char *legacy_speed) { | ||||
|   const auto supported_speed_count = this->state_->get_traits().supported_speed_count(); | ||||
|   if (strcasecmp(legacy_speed, "low") == 0) { | ||||
|     this->set_speed(fan::speed_enum_to_level(FAN_SPEED_LOW, supported_speed_count)); | ||||
|   } else if (strcasecmp(legacy_speed, "medium") == 0) { | ||||
|     this->set_speed(fan::speed_enum_to_level(FAN_SPEED_MEDIUM, supported_speed_count)); | ||||
|   } else if (strcasecmp(legacy_speed, "high") == 0) { | ||||
|     this->set_speed(fan::speed_enum_to_level(FAN_SPEED_HIGH, supported_speed_count)); | ||||
|   } | ||||
|   return *this; | ||||
| } | ||||
|  | ||||
| }  // namespace fan | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -1,126 +1,34 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/entity_base.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/preferences.h" | ||||
| #include "esphome/core/log.h" | ||||
| #include "fan_traits.h" | ||||
| #include "fan.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace fan { | ||||
|  | ||||
| /// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon | ||||
| enum ESPDEPRECATED("FanSpeed is deprecated.", "2021.9") FanSpeed { | ||||
|   FAN_SPEED_LOW = 0,     ///< The fan is running on low speed. | ||||
|   FAN_SPEED_MEDIUM = 1,  ///< The fan is running on medium speed. | ||||
|   FAN_SPEED_HIGH = 2     ///< The fan is running on high/full speed. | ||||
| enum ESPDEPRECATED("LegacyFanDirection members are deprecated, use FanDirection instead.", | ||||
|                    "2022.2") LegacyFanDirection { | ||||
|   FAN_DIRECTION_FORWARD = 0, | ||||
|   FAN_DIRECTION_REVERSE = 1 | ||||
| }; | ||||
|  | ||||
| /// Simple enum to represent the direction of a fan | ||||
| enum FanDirection { FAN_DIRECTION_FORWARD = 0, FAN_DIRECTION_REVERSE = 1 }; | ||||
|  | ||||
| enum FanRestoreMode { | ||||
|   FAN_RESTORE_DEFAULT_OFF, | ||||
|   FAN_RESTORE_DEFAULT_ON, | ||||
|   FAN_ALWAYS_OFF, | ||||
|   FAN_ALWAYS_ON, | ||||
|   FAN_RESTORE_INVERTED_DEFAULT_OFF, | ||||
|   FAN_RESTORE_INVERTED_DEFAULT_ON, | ||||
| }; | ||||
|  | ||||
| class FanState; | ||||
|  | ||||
| class FanStateCall { | ||||
|  public: | ||||
|   explicit FanStateCall(FanState *state) : state_(state) {} | ||||
|  | ||||
|   FanStateCall &set_state(bool binary_state) { | ||||
|     this->binary_state_ = binary_state; | ||||
|     return *this; | ||||
|   } | ||||
|   FanStateCall &set_state(optional<bool> binary_state) { | ||||
|     this->binary_state_ = binary_state; | ||||
|     return *this; | ||||
|   } | ||||
|   FanStateCall &set_oscillating(bool oscillating) { | ||||
|     this->oscillating_ = oscillating; | ||||
|     return *this; | ||||
|   } | ||||
|   FanStateCall &set_oscillating(optional<bool> oscillating) { | ||||
|     this->oscillating_ = oscillating; | ||||
|     return *this; | ||||
|   } | ||||
|   FanStateCall &set_speed(int speed) { | ||||
|     this->speed_ = speed; | ||||
|     return *this; | ||||
|   } | ||||
|   ESPDEPRECATED("set_speed() with string argument is deprecated, use integer argument instead.", "2021.9") | ||||
|   FanStateCall &set_speed(const char *legacy_speed); | ||||
|   FanStateCall &set_direction(FanDirection direction) { | ||||
|     this->direction_ = direction; | ||||
|     return *this; | ||||
|   } | ||||
|   FanStateCall &set_direction(optional<FanDirection> direction) { | ||||
|     this->direction_ = direction; | ||||
|     return *this; | ||||
|   } | ||||
|  | ||||
|   void perform() const; | ||||
|  | ||||
|  protected: | ||||
|   FanState *const state_; | ||||
|   optional<bool> binary_state_; | ||||
|   optional<bool> oscillating_; | ||||
|   optional<int> speed_; | ||||
|   optional<FanDirection> direction_{}; | ||||
| }; | ||||
|  | ||||
| class FanState : public EntityBase, public Component { | ||||
| class ESPDEPRECATED("FanState is deprecated, use Fan instead.", "2022.2") FanState : public Fan, public Component { | ||||
|  public: | ||||
|   FanState() = default; | ||||
|   /// Construct the fan state with name. | ||||
|   explicit FanState(const std::string &name); | ||||
|   explicit FanState(const std::string &name) : Fan(name) {} | ||||
|  | ||||
|   /// Register a callback that will be called each time the state changes. | ||||
|   void add_on_state_callback(std::function<void()> &&callback); | ||||
|  | ||||
|   /// Get the traits of this fan (i.e. what features it supports). | ||||
|   const FanTraits &get_traits() const; | ||||
|   /// Get the traits of this fan. | ||||
|   FanTraits get_traits() override { return this->traits_; } | ||||
|   /// Set the traits of this fan (i.e. what features it supports). | ||||
|   void set_traits(const FanTraits &traits); | ||||
|  | ||||
|   /// Set the restore mode of this fan | ||||
|   void set_restore_mode(FanRestoreMode restore_mode) { this->restore_mode_ = restore_mode; } | ||||
|  | ||||
|   /// The current ON/OFF state of the fan. | ||||
|   bool state{false}; | ||||
|   /// The current oscillation state of the fan. | ||||
|   bool oscillating{false}; | ||||
|   /// The current fan speed level | ||||
|   int speed{}; | ||||
|   /// The current direction of the fan | ||||
|   FanDirection direction{FAN_DIRECTION_FORWARD}; | ||||
|  | ||||
|   FanStateCall turn_on(); | ||||
|   FanStateCall turn_off(); | ||||
|   FanStateCall toggle(); | ||||
|   FanStateCall make_call(); | ||||
|   void set_traits(const FanTraits &traits) { this->traits_ = traits; } | ||||
|  | ||||
|   void setup() override; | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|  protected: | ||||
|   friend FanStateCall; | ||||
|  | ||||
|   uint32_t hash_base() override; | ||||
|   void control(const FanCall &call) override { this->publish_state(); } | ||||
|  | ||||
|   FanTraits traits_{}; | ||||
|   CallbackManager<void()> state_callback_{}; | ||||
|   ESPPreferenceObject rtc_; | ||||
|  | ||||
|   /// Restore mode of the fan. | ||||
|   FanRestoreMode restore_mode_; | ||||
| }; | ||||
|  | ||||
| }  // namespace fan | ||||
|   | ||||
		Reference in New Issue
	
	Block a user