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:
Oxan van Leeuwen 2022-01-23 10:21:54 +01:00 committed by GitHub
parent 8187a4bce9
commit 2a84db7f85
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 593 additions and 506 deletions

View File

@ -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;

View File

@ -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

View File

@ -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_)

View File

@ -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;

View File

@ -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); }

View File

@ -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;

View File

@ -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); }

View File

@ -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;

View File

@ -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;

View File

@ -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_))

View File

@ -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

View File

@ -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

View File

@ -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]:

View File

@ -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_;
};

View File

@ -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),
}
),
)

View File

@ -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

View File

@ -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_;

View 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

View 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

View File

@ -1,5 +1,6 @@
#pragma once
#include "fan_state.h"
#include "fan.h"
namespace esphome {
namespace fan {

View File

@ -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

View File

@ -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

View File

@ -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_))

View File

@ -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

View File

@ -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);
};

View File

@ -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_; }

View File

@ -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

View File

@ -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

View File

@ -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=\""));

View File

@ -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

View File

@ -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])

View File

@ -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

View File

@ -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_{};
};

View File

@ -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]))

View File

@ -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

View File

@ -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_{};
};

View File

@ -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(); });

View File

@ -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

View File

@ -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_{};

View File

@ -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){};

View File

@ -594,6 +594,7 @@ def lint_inclusive_language(fname, match):
"esphome/components/binary_sensor/binary_sensor.h",
"esphome/components/cover/cover.h",
"esphome/components/display/display_buffer.h",
"esphome/components/fan/fan.h",
"esphome/components/i2c/i2c.h",
"esphome/components/mqtt/mqtt_component.h",
"esphome/components/number/number.h",