mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +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:
		| @@ -255,7 +255,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { | ||||
| // Shut-up about usage of deprecated speed_level_to_enum/speed_enum_to_level functions for a bit. | ||||
| #pragma GCC diagnostic push | ||||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | ||||
| bool APIConnection::send_fan_state(fan::FanState *fan) { | ||||
| bool APIConnection::send_fan_state(fan::Fan *fan) { | ||||
|   if (!this->state_subscription_) | ||||
|     return false; | ||||
|  | ||||
| @@ -273,7 +273,7 @@ bool APIConnection::send_fan_state(fan::FanState *fan) { | ||||
|     resp.direction = static_cast<enums::FanDirection>(fan->direction); | ||||
|   return this->send_fan_state_response(resp); | ||||
| } | ||||
| bool APIConnection::send_fan_info(fan::FanState *fan) { | ||||
| bool APIConnection::send_fan_info(fan::Fan *fan) { | ||||
|   auto traits = fan->get_traits(); | ||||
|   ListEntitiesFanResponse msg; | ||||
|   msg.key = fan->get_object_id_hash(); | ||||
| @@ -290,7 +290,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) { | ||||
|   return this->send_list_entities_fan_response(msg); | ||||
| } | ||||
| void APIConnection::fan_command(const FanCommandRequest &msg) { | ||||
|   fan::FanState *fan = App.get_fan_by_key(msg.key); | ||||
|   fan::Fan *fan = App.get_fan_by_key(msg.key); | ||||
|   if (fan == nullptr) | ||||
|     return; | ||||
|  | ||||
|   | ||||
| @@ -32,8 +32,8 @@ class APIConnection : public APIServerConnection { | ||||
|   void cover_command(const CoverCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool send_fan_state(fan::FanState *fan); | ||||
|   bool send_fan_info(fan::FanState *fan); | ||||
|   bool send_fan_state(fan::Fan *fan); | ||||
|   bool send_fan_info(fan::Fan *fan); | ||||
|   void fan_command(const FanCommandRequest &msg) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   | ||||
| @@ -188,7 +188,7 @@ void APIServer::on_cover_update(cover::Cover *obj) { | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_FAN | ||||
| void APIServer::on_fan_update(fan::FanState *obj) { | ||||
| void APIServer::on_fan_update(fan::Fan *obj) { | ||||
|   if (obj->is_internal()) | ||||
|     return; | ||||
|   for (auto &c : this->clients_) | ||||
|   | ||||
| @@ -44,7 +44,7 @@ class APIServer : public Component, public Controller { | ||||
|   void on_cover_update(cover::Cover *obj) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   void on_fan_update(fan::FanState *obj) override; | ||||
|   void on_fan_update(fan::Fan *obj) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   void on_light_update(light::LightState *obj) override; | ||||
|   | ||||
| @@ -16,7 +16,7 @@ bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_ | ||||
| bool ListEntitiesIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_info(cover); } | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| bool ListEntitiesIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_info(fan); } | ||||
| bool ListEntitiesIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_info(fan); } | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| bool ListEntitiesIterator::on_light(light::LightState *light) { return this->client_->send_light_info(light); } | ||||
|   | ||||
| @@ -19,7 +19,7 @@ class ListEntitiesIterator : public ComponentIterator { | ||||
|   bool on_cover(cover::Cover *cover) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool on_fan(fan::FanState *fan) override; | ||||
|   bool on_fan(fan::Fan *fan) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool on_light(light::LightState *light) override; | ||||
|   | ||||
| @@ -14,7 +14,7 @@ bool InitialStateIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_ | ||||
| bool InitialStateIterator::on_cover(cover::Cover *cover) { return this->client_->send_cover_state(cover); } | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
| bool InitialStateIterator::on_fan(fan::FanState *fan) { return this->client_->send_fan_state(fan); } | ||||
| bool InitialStateIterator::on_fan(fan::Fan *fan) { return this->client_->send_fan_state(fan); } | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
| bool InitialStateIterator::on_light(light::LightState *light) { return this->client_->send_light_state(light); } | ||||
|   | ||||
| @@ -20,7 +20,7 @@ class InitialStateIterator : public ComponentIterator { | ||||
|   bool on_cover(cover::Cover *cover) override; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   bool on_fan(fan::FanState *fan) override; | ||||
|   bool on_fan(fan::Fan *fan) override; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   bool on_light(light::LightState *light) override; | ||||
|   | ||||
| @@ -27,7 +27,7 @@ class ComponentIterator { | ||||
|   virtual bool on_cover(cover::Cover *cover) = 0; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   virtual bool on_fan(fan::FanState *fan) = 0; | ||||
|   virtual bool on_fan(fan::Fan *fan) = 0; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   virtual bool on_light(light::LightState *light) = 0; | ||||
|   | ||||
| @@ -9,7 +9,7 @@ from esphome.const import ( | ||||
| ) | ||||
| from .. import binary_ns | ||||
|  | ||||
| BinaryFan = binary_ns.class_("BinaryFan", cg.Component) | ||||
| BinaryFan = binary_ns.class_("BinaryFan", fan.Fan, cg.Component) | ||||
|  | ||||
| CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( | ||||
|     { | ||||
| @@ -24,9 +24,8 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await fan.register_fan(var, config) | ||||
|  | ||||
|     fan_ = await fan.create_fan_state(config) | ||||
|     cg.add(var.set_fan(fan_)) | ||||
|     output_ = await cg.get_variable(config[CONF_OUTPUT]) | ||||
|     cg.add(var.set_output(output_)) | ||||
|  | ||||
|   | ||||
| @@ -6,59 +6,35 @@ namespace binary { | ||||
|  | ||||
| static const char *const TAG = "binary.fan"; | ||||
|  | ||||
| void binary::BinaryFan::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Fan '%s':", this->fan_->get_name().c_str()); | ||||
|   if (this->fan_->get_traits().supports_oscillation()) { | ||||
|     ESP_LOGCONFIG(TAG, "  Oscillation: YES"); | ||||
|   } | ||||
|   if (this->fan_->get_traits().supports_direction()) { | ||||
|     ESP_LOGCONFIG(TAG, "  Direction: YES"); | ||||
|   } | ||||
| } | ||||
| void BinaryFan::setup() { | ||||
|   auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0); | ||||
|   this->fan_->set_traits(traits); | ||||
|   this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); | ||||
| } | ||||
| void BinaryFan::loop() { | ||||
|   if (!this->next_update_) { | ||||
|     return; | ||||
|   } | ||||
|   this->next_update_ = false; | ||||
|  | ||||
|   { | ||||
|     bool enable = this->fan_->state; | ||||
|     if (enable) | ||||
|       this->output_->turn_on(); | ||||
|     else | ||||
|       this->output_->turn_off(); | ||||
|     ESP_LOGD(TAG, "Setting binary state: %s", ONOFF(enable)); | ||||
|   } | ||||
|  | ||||
|   if (this->oscillating_ != nullptr) { | ||||
|     bool enable = this->fan_->oscillating; | ||||
|     if (enable) { | ||||
|       this->oscillating_->turn_on(); | ||||
|     } else { | ||||
|       this->oscillating_->turn_off(); | ||||
|     } | ||||
|     ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); | ||||
|   } | ||||
|  | ||||
|   if (this->direction_ != nullptr) { | ||||
|     bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; | ||||
|     if (enable) { | ||||
|       this->direction_->turn_on(); | ||||
|     } else { | ||||
|       this->direction_->turn_off(); | ||||
|     } | ||||
|     ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable)); | ||||
|   auto restore = this->restore_state_(); | ||||
|   if (restore.has_value()) { | ||||
|     restore->apply(*this); | ||||
|     this->write_state_(); | ||||
|   } | ||||
| } | ||||
| void BinaryFan::dump_config() { LOG_FAN("", "Binary Fan", this); } | ||||
| fan::FanTraits BinaryFan::get_traits() { | ||||
|   return fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr, 0); | ||||
| } | ||||
| void BinaryFan::control(const fan::FanCall &call) { | ||||
|   if (call.get_state().has_value()) | ||||
|     this->state = *call.get_state(); | ||||
|   if (call.get_oscillating().has_value()) | ||||
|     this->oscillating = *call.get_oscillating(); | ||||
|   if (call.get_direction().has_value()) | ||||
|     this->direction = *call.get_direction(); | ||||
|  | ||||
| // We need a higher priority than the FanState component to make sure that the traits are set | ||||
| // when that component sets itself up. | ||||
| float BinaryFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } | ||||
|   this->write_state_(); | ||||
|   this->publish_state(); | ||||
| } | ||||
| void BinaryFan::write_state_() { | ||||
|   this->output_->set_state(this->state); | ||||
|   if (this->oscillating_ != nullptr) | ||||
|     this->oscillating_->set_state(this->oscillating); | ||||
|   if (this->direction_ != nullptr) | ||||
|     this->direction_->set_state(this->direction == fan::FanDirection::REVERSE); | ||||
| } | ||||
|  | ||||
| }  // namespace binary | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -2,28 +2,29 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/output/binary_output.h" | ||||
| #include "esphome/components/fan/fan_state.h" | ||||
| #include "esphome/components/fan/fan.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace binary { | ||||
|  | ||||
| class BinaryFan : public Component { | ||||
| class BinaryFan : public Component, public fan::Fan { | ||||
|  public: | ||||
|   void set_fan(fan::FanState *fan) { fan_ = fan; } | ||||
|   void set_output(output::BinaryOutput *output) { output_ = output; } | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override; | ||||
|  | ||||
|   void set_output(output::BinaryOutput *output) { this->output_ = output; } | ||||
|   void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } | ||||
|   void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; } | ||||
|  | ||||
|   fan::FanTraits get_traits() override; | ||||
|  | ||||
|  protected: | ||||
|   fan::FanState *fan_; | ||||
|   void control(const fan::FanCall &call) override; | ||||
|   void write_state_(); | ||||
|  | ||||
|   output::BinaryOutput *output_; | ||||
|   output::BinaryOutput *oscillating_{nullptr}; | ||||
|   output::BinaryOutput *direction_{nullptr}; | ||||
|   bool next_update_{true}; | ||||
| }; | ||||
|  | ||||
| }  // namespace binary | ||||
|   | ||||
| @@ -67,7 +67,7 @@ DemoClimate = demo_ns.class_("DemoClimate", climate.Climate, cg.Component) | ||||
| DemoClimateType = demo_ns.enum("DemoClimateType", is_class=True) | ||||
| DemoCover = demo_ns.class_("DemoCover", cover.Cover, cg.Component) | ||||
| DemoCoverType = demo_ns.enum("DemoCoverType", is_class=True) | ||||
| DemoFan = demo_ns.class_("DemoFan", cg.Component) | ||||
| DemoFan = demo_ns.class_("DemoFan", fan.Fan, cg.Component) | ||||
| DemoFanType = demo_ns.enum("DemoFanType", is_class=True) | ||||
| DemoLight = demo_ns.class_("DemoLight", light.LightOutput, cg.Component) | ||||
| DemoLightType = demo_ns.enum("DemoLightType", is_class=True) | ||||
| @@ -411,8 +411,7 @@ async def to_code(config): | ||||
|     for conf in config[CONF_FANS]: | ||||
|         var = cg.new_Pvariable(conf[CONF_OUTPUT_ID]) | ||||
|         await cg.register_component(var, conf) | ||||
|         fan_ = await fan.create_fan_state(conf) | ||||
|         cg.add(var.set_fan(fan_)) | ||||
|         await fan.register_fan(var, conf) | ||||
|         cg.add(var.set_type(conf[CONF_TYPE])) | ||||
|  | ||||
|     for conf in config[CONF_LIGHTS]: | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/fan/fan_state.h" | ||||
| #include "esphome/components/fan/fan.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace demo { | ||||
| @@ -13,11 +13,10 @@ enum class DemoFanType { | ||||
|   TYPE_4, | ||||
| }; | ||||
|  | ||||
| class DemoFan : public Component { | ||||
| class DemoFan : public fan::Fan, public Component { | ||||
|  public: | ||||
|   void set_type(DemoFanType type) { type_ = type; } | ||||
|   void set_fan(fan::FanState *fan) { fan_ = fan; } | ||||
|   void setup() override { | ||||
|   fan::FanTraits get_traits() override { | ||||
|     fan::FanTraits traits{}; | ||||
|  | ||||
|     // oscillation | ||||
| @@ -43,10 +42,23 @@ class DemoFan : public Component { | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     this->fan_->set_traits(traits); | ||||
|     return traits; | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   void control(const fan::FanCall &call) override { | ||||
|     if (call.get_state().has_value()) | ||||
|       this->state = *call.get_state(); | ||||
|     if (call.get_oscillating().has_value()) | ||||
|       this->oscillating = *call.get_oscillating(); | ||||
|     if (call.get_speed().has_value()) | ||||
|       this->speed = *call.get_speed(); | ||||
|     if (call.get_direction().has_value()) | ||||
|       this->direction = *call.get_direction(); | ||||
|  | ||||
|     this->publish_state(); | ||||
|   } | ||||
|  | ||||
|   fan::FanState *fan_; | ||||
|   DemoFanType type_; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -17,7 +17,7 @@ from .. import hbridge_ns | ||||
| CODEOWNERS = ["@WeekendWarrior"] | ||||
|  | ||||
|  | ||||
| HBridgeFan = hbridge_ns.class_("HBridgeFan", fan.FanState) | ||||
| HBridgeFan = hbridge_ns.class_("HBridgeFan", cg.Component, fan.Fan) | ||||
|  | ||||
| DecayMode = hbridge_ns.enum("DecayMode") | ||||
| DECAY_MODE_OPTIONS = { | ||||
| @@ -59,6 +59,7 @@ async def to_code(config): | ||||
|         config[CONF_SPEED_COUNT], | ||||
|         config[CONF_DECAY_MODE], | ||||
|     ) | ||||
|     await cg.register_component(var, config) | ||||
|     await fan.register_fan(var, config) | ||||
|     pin_a_ = await cg.get_variable(config[CONF_PIN_A]) | ||||
|     cg.add(var.set_pin_a(pin_a_)) | ||||
|   | ||||
| @@ -22,47 +22,49 @@ void HBridgeFan::set_hbridge_levels_(float a_level, float b_level, float enable) | ||||
|   ESP_LOGD(TAG, "Setting speed: a: %.2f, b: %.2f, enable: %.2f", a_level, b_level, enable); | ||||
| } | ||||
|  | ||||
| fan::FanStateCall HBridgeFan::brake() { | ||||
| fan::FanCall 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::setup() { | ||||
|   auto restore = this->restore_state_(); | ||||
|   if (restore.has_value()) { | ||||
|     restore->apply(*this); | ||||
|     this->write_state_(); | ||||
|   } | ||||
| } | ||||
| 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"); | ||||
|   } | ||||
|   LOG_FAN("", "H-Bridge Fan", this); | ||||
|   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; }); | ||||
| fan::FanTraits HBridgeFan::get_traits() { | ||||
|   return fan::FanTraits(this->oscillating_ != nullptr, true, true, this->speed_count_); | ||||
| } | ||||
| void HBridgeFan::loop() { | ||||
|   if (!this->next_update_) { | ||||
|     return; | ||||
|   } | ||||
|   this->next_update_ = false; | ||||
| void HBridgeFan::control(const fan::FanCall &call) { | ||||
|   if (call.get_state().has_value()) | ||||
|     this->state = *call.get_state(); | ||||
|   if (call.get_speed().has_value()) | ||||
|     this->speed = *call.get_speed(); | ||||
|   if (call.get_oscillating().has_value()) | ||||
|     this->oscillating = *call.get_oscillating(); | ||||
|   if (call.get_direction().has_value()) | ||||
|     this->direction = *call.get_direction(); | ||||
|  | ||||
|   float speed = 0.0f; | ||||
|   if (this->state) { | ||||
|     speed = static_cast<float>(this->speed) / static_cast<float>(this->speed_count_); | ||||
|   } | ||||
|   this->write_state_(); | ||||
|   this->publish_state(); | ||||
| } | ||||
| void HBridgeFan::write_state_() { | ||||
|   float speed = this->state ? static_cast<float>(this->speed) / static_cast<float>(this->speed_count_) : 0.0f; | ||||
|   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) { | ||||
|   } else if (this->direction == fan::FanDirection::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); | ||||
| @@ -79,6 +81,9 @@ void HBridgeFan::loop() { | ||||
|                                  : this->set_hbridge_levels_(1.0f, 0.0f, speed); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   if (this->oscillating_ != nullptr) | ||||
|     this->oscillating_->set_state(this->oscillating); | ||||
| } | ||||
|  | ||||
| }  // namespace hbridge | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| #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" | ||||
| #include "esphome/components/fan/fan.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace hbridge { | ||||
| @@ -13,7 +13,7 @@ enum DecayMode { | ||||
|   DECAY_MODE_FAST = 1, | ||||
| }; | ||||
|  | ||||
| class HBridgeFan : public fan::FanState { | ||||
| class HBridgeFan : public Component, public fan::Fan { | ||||
|  public: | ||||
|   HBridgeFan(int speed_count, DecayMode decay_mode) : speed_count_(speed_count), decay_mode_(decay_mode) {} | ||||
|  | ||||
| @@ -22,25 +22,22 @@ class HBridgeFan : public fan::FanState { | ||||
|   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::FanTraits get_traits() override; | ||||
|  | ||||
|   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; } | ||||
|   fan::FanCall brake(); | ||||
|  | ||||
|  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 control(const fan::FanCall &call) override; | ||||
|   void write_state_(); | ||||
|  | ||||
|   void set_hbridge_levels_(float a_level, float b_level); | ||||
|   void set_hbridge_levels_(float a_level, float b_level, float enable); | ||||
| }; | ||||
|   | ||||
| @@ -14,9 +14,9 @@ static const char *const TAG = "mqtt.fan"; | ||||
|  | ||||
| using namespace esphome::fan; | ||||
|  | ||||
| MQTTFanComponent::MQTTFanComponent(FanState *state) : state_(state) {} | ||||
| MQTTFanComponent::MQTTFanComponent(Fan *state) : state_(state) {} | ||||
|  | ||||
| FanState *MQTTFanComponent::get_state() const { return this->state_; } | ||||
| Fan *MQTTFanComponent::get_state() const { return this->state_; } | ||||
| std::string MQTTFanComponent::component_type() const { return "fan"; } | ||||
| const EntityBase *MQTTFanComponent::get_entity() const { return this->state_; } | ||||
|  | ||||
|   | ||||
| @@ -13,7 +13,7 @@ namespace mqtt { | ||||
|  | ||||
| class MQTTFanComponent : public mqtt::MQTTComponent { | ||||
|  public: | ||||
|   explicit MQTTFanComponent(fan::FanState *state); | ||||
|   explicit MQTTFanComponent(fan::Fan *state); | ||||
|  | ||||
|   MQTT_COMPONENT_CUSTOM_TOPIC(oscillation, command) | ||||
|   MQTT_COMPONENT_CUSTOM_TOPIC(oscillation, state) | ||||
| @@ -37,12 +37,12 @@ class MQTTFanComponent : public mqtt::MQTTComponent { | ||||
|   /// 'fan' component type for discovery. | ||||
|   std::string component_type() const override; | ||||
|  | ||||
|   fan::FanState *get_state() const; | ||||
|   fan::Fan *get_state() const; | ||||
|  | ||||
|  protected: | ||||
|   const EntityBase *get_entity() const override; | ||||
|  | ||||
|   fan::FanState *state_; | ||||
|   fan::Fan *state_; | ||||
| }; | ||||
|  | ||||
| }  // namespace mqtt | ||||
|   | ||||
| @@ -30,6 +30,14 @@ class BinaryOutput { | ||||
|   void set_power_supply(power_supply::PowerSupply *power_supply) { this->power_.set_parent(power_supply); } | ||||
| #endif | ||||
|  | ||||
|   /// Enable or disable this binary output. | ||||
|   virtual void set_state(bool state) { | ||||
|     if (state) | ||||
|       this->turn_on(); | ||||
|     else | ||||
|       this->turn_off(); | ||||
|   } | ||||
|  | ||||
|   /// Enable this binary output. | ||||
|   virtual void turn_on() { | ||||
| #ifdef USE_POWER_SUPPLY | ||||
|   | ||||
| @@ -127,7 +127,7 @@ void PrometheusHandler::fan_type_(AsyncResponseStream *stream) { | ||||
|   stream->print(F("#TYPE esphome_fan_speed GAUGE\n")); | ||||
|   stream->print(F("#TYPE esphome_fan_oscillation GAUGE\n")); | ||||
| } | ||||
| void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::FanState *obj) { | ||||
| void PrometheusHandler::fan_row_(AsyncResponseStream *stream, fan::Fan *obj) { | ||||
|   if (obj->is_internal()) | ||||
|     return; | ||||
|   stream->print(F("esphome_fan_failed{id=\"")); | ||||
|   | ||||
| @@ -52,7 +52,7 @@ class PrometheusHandler : public AsyncWebHandler, public Component { | ||||
|   /// Return the type for prometheus | ||||
|   void fan_type_(AsyncResponseStream *stream); | ||||
|   /// Return the sensor state as prometheus data point | ||||
|   void fan_row_(AsyncResponseStream *stream, fan::FanState *obj); | ||||
|   void fan_row_(AsyncResponseStream *stream, fan::Fan *obj); | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_LIGHT | ||||
|   | ||||
| @@ -11,7 +11,7 @@ from esphome.const import ( | ||||
| ) | ||||
| from .. import speed_ns | ||||
|  | ||||
| SpeedFan = speed_ns.class_("SpeedFan", cg.Component) | ||||
| SpeedFan = speed_ns.class_("SpeedFan", cg.Component, fan.Fan) | ||||
|  | ||||
| CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( | ||||
|     { | ||||
| @@ -29,11 +29,9 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend( | ||||
|  | ||||
| async def to_code(config): | ||||
|     output_ = await cg.get_variable(config[CONF_OUTPUT]) | ||||
|     state = await fan.create_fan_state(config) | ||||
|     var = cg.new_Pvariable( | ||||
|         config[CONF_OUTPUT_ID], state, output_, config[CONF_SPEED_COUNT] | ||||
|     ) | ||||
|     var = cg.new_Pvariable(config[CONF_OUTPUT_ID], output_, config[CONF_SPEED_COUNT]) | ||||
|     await cg.register_component(var, config) | ||||
|     await fan.register_fan(var, config) | ||||
|  | ||||
|     if CONF_OSCILLATION_OUTPUT in config: | ||||
|         oscillation_output = await cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) | ||||
|   | ||||
| @@ -7,59 +7,39 @@ namespace speed { | ||||
|  | ||||
| static const char *const TAG = "speed.fan"; | ||||
|  | ||||
| void SpeedFan::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Fan '%s':", this->fan_->get_name().c_str()); | ||||
|   if (this->fan_->get_traits().supports_oscillation()) { | ||||
|     ESP_LOGCONFIG(TAG, "  Oscillation: YES"); | ||||
|   } | ||||
|   if (this->fan_->get_traits().supports_direction()) { | ||||
|     ESP_LOGCONFIG(TAG, "  Direction: YES"); | ||||
|   } | ||||
| } | ||||
| void SpeedFan::setup() { | ||||
|   auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_); | ||||
|   this->fan_->set_traits(traits); | ||||
|   this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); | ||||
| } | ||||
| void SpeedFan::loop() { | ||||
|   if (!this->next_update_) { | ||||
|     return; | ||||
|   } | ||||
|   this->next_update_ = false; | ||||
|  | ||||
|   { | ||||
|     float speed = 0.0f; | ||||
|     if (this->fan_->state) { | ||||
|       speed = static_cast<float>(this->fan_->speed) / static_cast<float>(this->speed_count_); | ||||
|     } | ||||
|     ESP_LOGD(TAG, "Setting speed: %.2f", speed); | ||||
|     this->output_->set_level(speed); | ||||
|   } | ||||
|  | ||||
|   if (this->oscillating_ != nullptr) { | ||||
|     bool enable = this->fan_->oscillating; | ||||
|     if (enable) { | ||||
|       this->oscillating_->turn_on(); | ||||
|     } else { | ||||
|       this->oscillating_->turn_off(); | ||||
|     } | ||||
|     ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); | ||||
|   } | ||||
|  | ||||
|   if (this->direction_ != nullptr) { | ||||
|     bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; | ||||
|     if (enable) { | ||||
|       this->direction_->turn_on(); | ||||
|     } else { | ||||
|       this->direction_->turn_off(); | ||||
|     } | ||||
|     ESP_LOGD(TAG, "Setting reverse direction: %s", ONOFF(enable)); | ||||
|   auto restore = this->restore_state_(); | ||||
|   if (restore.has_value()) { | ||||
|     restore->apply(*this); | ||||
|     this->write_state_(); | ||||
|   } | ||||
| } | ||||
| void SpeedFan::dump_config() { LOG_FAN("", "Speed Fan", this); } | ||||
| fan::FanTraits SpeedFan::get_traits() { | ||||
|   return fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr, this->speed_count_); | ||||
| } | ||||
| void SpeedFan::control(const fan::FanCall &call) { | ||||
|   if (call.get_state().has_value()) | ||||
|     this->state = *call.get_state(); | ||||
|   if (call.get_speed().has_value()) | ||||
|     this->speed = *call.get_speed(); | ||||
|   if (call.get_oscillating().has_value()) | ||||
|     this->oscillating = *call.get_oscillating(); | ||||
|   if (call.get_direction().has_value()) | ||||
|     this->direction = *call.get_direction(); | ||||
|  | ||||
| // We need a higher priority than the FanState component to make sure that the traits are set | ||||
| // when that component sets itself up. | ||||
| float SpeedFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } | ||||
|   this->write_state_(); | ||||
|   this->publish_state(); | ||||
| } | ||||
| void SpeedFan::write_state_() { | ||||
|   float speed = this->state ? static_cast<float>(this->speed) / static_cast<float>(this->speed_count_) : 0.0f; | ||||
|   this->output_->set_level(speed); | ||||
|  | ||||
|   if (this->oscillating_ != nullptr) | ||||
|     this->oscillating_->set_state(this->oscillating); | ||||
|   if (this->direction_ != nullptr) | ||||
|     this->direction_->set_state(this->direction == fan::FanDirection::REVERSE); | ||||
| } | ||||
|  | ||||
| }  // namespace speed | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -3,28 +3,27 @@ | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/output/binary_output.h" | ||||
| #include "esphome/components/output/float_output.h" | ||||
| #include "esphome/components/fan/fan_state.h" | ||||
| #include "esphome/components/fan/fan.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace speed { | ||||
|  | ||||
| class SpeedFan : public Component { | ||||
| class SpeedFan : public Component, public fan::Fan { | ||||
|  public: | ||||
|   SpeedFan(fan::FanState *fan, output::FloatOutput *output, int speed_count) | ||||
|       : fan_(fan), output_(output), speed_count_(speed_count) {} | ||||
|   SpeedFan(output::FloatOutput *output, int speed_count) : output_(output), speed_count_(speed_count) {} | ||||
|   void setup() override; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override; | ||||
|   void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } | ||||
|   void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; } | ||||
|   fan::FanTraits get_traits() override; | ||||
|  | ||||
|  protected: | ||||
|   fan::FanState *fan_; | ||||
|   void control(const fan::FanCall &call) override; | ||||
|   void write_state_(); | ||||
|  | ||||
|   output::FloatOutput *output_; | ||||
|   output::BinaryOutput *oscillating_{nullptr}; | ||||
|   output::BinaryOutput *direction_{nullptr}; | ||||
|   bool next_update_{true}; | ||||
|   int speed_count_{}; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,7 @@ CONF_SPEED_DATAPOINT = "speed_datapoint" | ||||
| CONF_OSCILLATION_DATAPOINT = "oscillation_datapoint" | ||||
| CONF_DIRECTION_DATAPOINT = "direction_datapoint" | ||||
|  | ||||
| TuyaFan = tuya_ns.class_("TuyaFan", cg.Component) | ||||
| TuyaFan = tuya_ns.class_("TuyaFan", cg.Component, fan.Fan) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     fan.FAN_SCHEMA.extend( | ||||
| @@ -30,12 +30,10 @@ CONFIG_SCHEMA = cv.All( | ||||
|  | ||||
| async def to_code(config): | ||||
|     parent = await cg.get_variable(config[CONF_TUYA_ID]) | ||||
|     state = await fan.create_fan_state(config) | ||||
|  | ||||
|     var = cg.new_Pvariable( | ||||
|         config[CONF_OUTPUT_ID], parent, state, config[CONF_SPEED_COUNT] | ||||
|     ) | ||||
|     var = cg.new_Pvariable(config[CONF_OUTPUT_ID], parent, config[CONF_SPEED_COUNT]) | ||||
|     await cg.register_component(var, config) | ||||
|     await fan.register_fan(var, config) | ||||
|  | ||||
|     if CONF_SPEED_DATAPOINT in config: | ||||
|         cg.add(var.set_speed_id(config[CONF_SPEED_DATAPOINT])) | ||||
|   | ||||
| @@ -8,52 +8,48 @@ namespace tuya { | ||||
| static const char *const TAG = "tuya.fan"; | ||||
|  | ||||
| void TuyaFan::setup() { | ||||
|   auto traits = fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value(), | ||||
|                                this->direction_id_.has_value(), this->speed_count_); | ||||
|   this->fan_->set_traits(traits); | ||||
|  | ||||
|   if (this->speed_id_.has_value()) { | ||||
|     this->parent_->register_listener(*this->speed_id_, [this](const TuyaDatapoint &datapoint) { | ||||
|       ESP_LOGV(TAG, "MCU reported speed of: %d", datapoint.value_enum); | ||||
|       auto call = this->fan_->make_call(); | ||||
|       if (datapoint.value_enum < this->speed_count_) | ||||
|         call.set_speed(datapoint.value_enum + 1); | ||||
|       else | ||||
|         ESP_LOGCONFIG(TAG, "Speed has invalid value %d", datapoint.value_enum); | ||||
|       call.perform(); | ||||
|       if (datapoint.value_enum >= this->speed_count_) { | ||||
|         ESP_LOGE(TAG, "Speed has invalid value %d", datapoint.value_enum); | ||||
|       } else { | ||||
|         this->speed = datapoint.value_enum + 1; | ||||
|         this->publish_state(); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|   if (this->switch_id_.has_value()) { | ||||
|     this->parent_->register_listener(*this->switch_id_, [this](const TuyaDatapoint &datapoint) { | ||||
|       ESP_LOGV(TAG, "MCU reported switch is: %s", ONOFF(datapoint.value_bool)); | ||||
|       auto call = this->fan_->make_call(); | ||||
|       call.set_state(datapoint.value_bool); | ||||
|       call.perform(); | ||||
|       this->state = datapoint.value_bool; | ||||
|       this->publish_state(); | ||||
|     }); | ||||
|   } | ||||
|   if (this->oscillation_id_.has_value()) { | ||||
|     this->parent_->register_listener(*this->oscillation_id_, [this](const TuyaDatapoint &datapoint) { | ||||
|       ESP_LOGV(TAG, "MCU reported oscillation is: %s", ONOFF(datapoint.value_bool)); | ||||
|       auto call = this->fan_->make_call(); | ||||
|       call.set_oscillating(datapoint.value_bool); | ||||
|       call.perform(); | ||||
|       this->oscillating = datapoint.value_bool; | ||||
|       this->publish_state(); | ||||
|     }); | ||||
|   } | ||||
|   if (this->direction_id_.has_value()) { | ||||
|     this->parent_->register_listener(*this->direction_id_, [this](const TuyaDatapoint &datapoint) { | ||||
|       auto call = this->fan_->make_call(); | ||||
|       call.set_direction(datapoint.value_bool ? fan::FAN_DIRECTION_REVERSE : fan::FAN_DIRECTION_FORWARD); | ||||
|       call.perform(); | ||||
|       ESP_LOGD(TAG, "MCU reported reverse direction is: %s", ONOFF(datapoint.value_bool)); | ||||
|       this->direction = datapoint.value_bool ? fan::FanDirection::REVERSE : fan::FanDirection::FORWARD; | ||||
|       this->publish_state(); | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   this->fan_->add_on_state_callback([this]() { this->write_state(); }); | ||||
|   this->parent_->add_on_initialized_callback([this]() { | ||||
|     auto restored = this->restore_state_(); | ||||
|     if (restored) | ||||
|       restored->to_call(*this).perform(); | ||||
|   }); | ||||
| } | ||||
|  | ||||
| void TuyaFan::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Tuya Fan:"); | ||||
|   ESP_LOGCONFIG(TAG, "  Speed count %d", this->speed_count_); | ||||
|   LOG_FAN("", "Tuya Fan", this); | ||||
|   if (this->speed_id_.has_value()) | ||||
|     ESP_LOGCONFIG(TAG, "  Speed has datapoint ID %u", *this->speed_id_); | ||||
|   if (this->switch_id_.has_value()) | ||||
| @@ -64,29 +60,26 @@ void TuyaFan::dump_config() { | ||||
|     ESP_LOGCONFIG(TAG, "  Direction has datapoint ID %u", *this->direction_id_); | ||||
| } | ||||
|  | ||||
| void TuyaFan::write_state() { | ||||
|   if (this->switch_id_.has_value()) { | ||||
|     ESP_LOGV(TAG, "Setting switch: %s", ONOFF(this->fan_->state)); | ||||
|     this->parent_->set_boolean_datapoint_value(*this->switch_id_, this->fan_->state); | ||||
| fan::FanTraits TuyaFan::get_traits() { | ||||
|   return fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value(), this->direction_id_.has_value(), | ||||
|                         this->speed_count_); | ||||
| } | ||||
|  | ||||
| void TuyaFan::control(const fan::FanCall &call) { | ||||
|   if (this->switch_id_.has_value() && call.get_state().has_value()) { | ||||
|     this->parent_->set_boolean_datapoint_value(*this->switch_id_, *call.get_state()); | ||||
|   } | ||||
|   if (this->oscillation_id_.has_value()) { | ||||
|     ESP_LOGV(TAG, "Setting oscillating: %s", ONOFF(this->fan_->oscillating)); | ||||
|     this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, this->fan_->oscillating); | ||||
|   if (this->oscillation_id_.has_value() && call.get_oscillating().has_value()) { | ||||
|     this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating()); | ||||
|   } | ||||
|   if (this->direction_id_.has_value()) { | ||||
|     bool enable = this->fan_->direction == fan::FAN_DIRECTION_REVERSE; | ||||
|     ESP_LOGV(TAG, "Setting reverse direction: %s", ONOFF(enable)); | ||||
|   if (this->direction_id_.has_value() && call.get_direction().has_value()) { | ||||
|     bool enable = *call.get_direction() == fan::FanDirection::REVERSE; | ||||
|     this->parent_->set_enum_datapoint_value(*this->direction_id_, enable); | ||||
|   } | ||||
|   if (this->speed_id_.has_value()) { | ||||
|     ESP_LOGV(TAG, "Setting speed: %d", this->fan_->speed); | ||||
|     this->parent_->set_enum_datapoint_value(*this->speed_id_, this->fan_->speed - 1); | ||||
|   if (this->speed_id_.has_value() && call.get_speed().has_value()) { | ||||
|     this->parent_->set_enum_datapoint_value(*this->speed_id_, *call.get_speed() - 1); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // We need a higher priority than the FanState component to make sure that the traits are set | ||||
| // when that component sets itself up. | ||||
| float TuyaFan::get_setup_priority() const { return fan_->get_setup_priority() + 1.0f; } | ||||
|  | ||||
| }  // namespace tuya | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -2,35 +2,31 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/tuya/tuya.h" | ||||
| #include "esphome/components/fan/fan_state.h" | ||||
| #include "esphome/components/fan/fan.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace tuya { | ||||
|  | ||||
| class TuyaFan : public Component { | ||||
| class TuyaFan : public Component, public fan::Fan { | ||||
|  public: | ||||
|   TuyaFan(Tuya *parent, fan::FanState *fan, int speed_count) : parent_(parent), fan_(fan), speed_count_(speed_count) {} | ||||
|   TuyaFan(Tuya *parent, int speed_count) : parent_(parent), speed_count_(speed_count) {} | ||||
|   void setup() override; | ||||
|   float get_setup_priority() const override; | ||||
|   void dump_config() override; | ||||
|   void set_speed_id(uint8_t speed_id) { this->speed_id_ = speed_id; } | ||||
|   void set_switch_id(uint8_t switch_id) { this->switch_id_ = switch_id; } | ||||
|   void set_oscillation_id(uint8_t oscillation_id) { this->oscillation_id_ = oscillation_id; } | ||||
|   void set_direction_id(uint8_t direction_id) { this->direction_id_ = direction_id; } | ||||
|   void write_state(); | ||||
|  | ||||
|   fan::FanTraits get_traits() override; | ||||
|  | ||||
|  protected: | ||||
|   void update_speed_(uint32_t value); | ||||
|   void update_switch_(uint32_t value); | ||||
|   void update_oscillation_(uint32_t value); | ||||
|   void update_direction_(uint32_t value); | ||||
|   void control(const fan::FanCall &call) override; | ||||
|  | ||||
|   Tuya *parent_; | ||||
|   optional<uint8_t> speed_id_{}; | ||||
|   optional<uint8_t> switch_id_{}; | ||||
|   optional<uint8_t> oscillation_id_{}; | ||||
|   optional<uint8_t> direction_id_{}; | ||||
|   fan::FanState *fan_; | ||||
|   int speed_count_{}; | ||||
| }; | ||||
|  | ||||
|   | ||||
| @@ -440,8 +440,8 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_FAN | ||||
| void WebServer::on_fan_update(fan::FanState *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); } | ||||
| std::string WebServer::fan_json(fan::FanState *obj) { | ||||
| void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); } | ||||
| std::string WebServer::fan_json(fan::Fan *obj) { | ||||
|   return json::build_json([obj](JsonObject root) { | ||||
|     root["id"] = "fan-" + obj->get_object_id(); | ||||
|     root["state"] = obj->state ? "ON" : "OFF"; | ||||
| @@ -470,7 +470,7 @@ std::string WebServer::fan_json(fan::FanState *obj) { | ||||
|   }); | ||||
| } | ||||
| void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match) { | ||||
|   for (fan::FanState *obj : App.get_fans()) { | ||||
|   for (fan::Fan *obj : App.get_fans()) { | ||||
|     if (obj->get_object_id() != match.id) | ||||
|       continue; | ||||
|  | ||||
| @@ -516,7 +516,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc | ||||
|             return; | ||||
|         } | ||||
|       } | ||||
|       this->defer([call]() { call.perform(); }); | ||||
|       this->defer([call]() mutable { call.perform(); }); | ||||
|       request->send(200); | ||||
|     } else if (match.method == "turn_off") { | ||||
|       this->defer([obj]() { obj->turn_off().perform(); }); | ||||
|   | ||||
| @@ -128,13 +128,13 @@ class WebServer : public Controller, public Component, public AsyncWebHandler { | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_FAN | ||||
|   void on_fan_update(fan::FanState *obj) override; | ||||
|   void on_fan_update(fan::Fan *obj) override; | ||||
|  | ||||
|   /// Handle a fan request under '/fan/<id>/</turn_on/turn_off/toggle>'. | ||||
|   void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match); | ||||
|  | ||||
|   /// Dump the fan state as a JSON string. | ||||
|   std::string fan_json(fan::FanState *obj); | ||||
|   std::string fan_json(fan::Fan *obj); | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_LIGHT | ||||
|   | ||||
| @@ -81,7 +81,7 @@ class Application { | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_FAN | ||||
|   void register_fan(fan::FanState *state) { this->fans_.push_back(state); } | ||||
|   void register_fan(fan::Fan *state) { this->fans_.push_back(state); } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_COVER | ||||
| @@ -204,8 +204,8 @@ class Application { | ||||
|   } | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   const std::vector<fan::FanState *> &get_fans() { return this->fans_; } | ||||
|   fan::FanState *get_fan_by_key(uint32_t key, bool include_internal = false) { | ||||
|   const std::vector<fan::Fan *> &get_fans() { return this->fans_; } | ||||
|   fan::Fan *get_fan_by_key(uint32_t key, bool include_internal = false) { | ||||
|     for (auto *obj : this->fans_) | ||||
|       if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal())) | ||||
|         return obj; | ||||
| @@ -288,7 +288,7 @@ class Application { | ||||
|   std::vector<text_sensor::TextSensor *> text_sensors_{}; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   std::vector<fan::FanState *> fans_{}; | ||||
|   std::vector<fan::Fan *> fans_{}; | ||||
| #endif | ||||
| #ifdef USE_COVER | ||||
|   std::vector<cover::Cover *> covers_{}; | ||||
|   | ||||
| @@ -44,7 +44,7 @@ class Controller { | ||||
|   virtual void on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state){}; | ||||
| #endif | ||||
| #ifdef USE_FAN | ||||
|   virtual void on_fan_update(fan::FanState *obj){}; | ||||
|   virtual void on_fan_update(fan::Fan *obj){}; | ||||
| #endif | ||||
| #ifdef USE_LIGHT | ||||
|   virtual void on_light_update(light::LightState *obj){}; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user