Add support for controlling fan direction (#1051)

* Fix fan oscillation trait not being used

* Add fan direction support to SpeedFan

* Add fan direction to API

* Add fan direction support to BinaryFan

* Fix CI errors

* Fix python format

* Change some ordering to trigger CI

* Add test for the configuration
This commit is contained in:
Jim Persson 2020-06-14 21:54:31 +02:00 committed by GitHub
parent 35a2258f12
commit 0bb81e5b2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 156 additions and 17 deletions

View file

@ -301,12 +301,17 @@ message ListEntitiesFanResponse {
bool supports_oscillation = 5; bool supports_oscillation = 5;
bool supports_speed = 6; bool supports_speed = 6;
bool supports_direction = 7;
} }
enum FanSpeed { enum FanSpeed {
FAN_SPEED_LOW = 0; FAN_SPEED_LOW = 0;
FAN_SPEED_MEDIUM = 1; FAN_SPEED_MEDIUM = 1;
FAN_SPEED_HIGH = 2; FAN_SPEED_HIGH = 2;
} }
enum FanDirection {
FAN_DIRECTION_FORWARD = 0;
FAN_DIRECTION_REVERSE = 1;
}
message FanStateResponse { message FanStateResponse {
option (id) = 23; option (id) = 23;
option (source) = SOURCE_SERVER; option (source) = SOURCE_SERVER;
@ -317,6 +322,7 @@ message FanStateResponse {
bool state = 2; bool state = 2;
bool oscillating = 3; bool oscillating = 3;
FanSpeed speed = 4; FanSpeed speed = 4;
FanDirection direction = 5;
} }
message FanCommandRequest { message FanCommandRequest {
option (id) = 31; option (id) = 31;
@ -331,6 +337,8 @@ message FanCommandRequest {
FanSpeed speed = 5; FanSpeed speed = 5;
bool has_oscillating = 6; bool has_oscillating = 6;
bool oscillating = 7; bool oscillating = 7;
bool has_direction = 8;
FanDirection direction = 9;
} }
// ==================== LIGHT ==================== // ==================== LIGHT ====================

View file

@ -248,6 +248,8 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
resp.oscillating = fan->oscillating; resp.oscillating = fan->oscillating;
if (traits.supports_speed()) if (traits.supports_speed())
resp.speed = static_cast<enums::FanSpeed>(fan->speed); resp.speed = static_cast<enums::FanSpeed>(fan->speed);
if (traits.supports_direction())
resp.direction = static_cast<enums::FanDirection>(fan->direction);
return this->send_fan_state_response(resp); return this->send_fan_state_response(resp);
} }
bool APIConnection::send_fan_info(fan::FanState *fan) { bool APIConnection::send_fan_info(fan::FanState *fan) {
@ -259,6 +261,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
msg.unique_id = get_default_unique_id("fan", fan); msg.unique_id = get_default_unique_id("fan", fan);
msg.supports_oscillation = traits.supports_oscillation(); msg.supports_oscillation = traits.supports_oscillation();
msg.supports_speed = traits.supports_speed(); msg.supports_speed = traits.supports_speed();
msg.supports_direction = traits.supports_direction();
return this->send_list_entities_fan_response(msg); return this->send_list_entities_fan_response(msg);
} }
void APIConnection::fan_command(const FanCommandRequest &msg) { void APIConnection::fan_command(const FanCommandRequest &msg) {
@ -273,6 +276,8 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
call.set_oscillating(msg.oscillating); call.set_oscillating(msg.oscillating);
if (msg.has_speed) if (msg.has_speed)
call.set_speed(static_cast<fan::FanSpeed>(msg.speed)); call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
if (msg.has_direction)
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
call.perform(); call.perform();
} }
#endif #endif

View file

@ -52,6 +52,16 @@ template<> const char *proto_enum_to_string<enums::FanSpeed>(enums::FanSpeed val
return "UNKNOWN"; return "UNKNOWN";
} }
} }
template<> const char *proto_enum_to_string<enums::FanDirection>(enums::FanDirection value) {
switch (value) {
case enums::FAN_DIRECTION_FORWARD:
return "FAN_DIRECTION_FORWARD";
case enums::FAN_DIRECTION_REVERSE:
return "FAN_DIRECTION_REVERSE";
default:
return "UNKNOWN";
}
}
template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) { template<> const char *proto_enum_to_string<enums::LogLevel>(enums::LogLevel value) {
switch (value) { switch (value) {
case enums::LOG_LEVEL_NONE: case enums::LOG_LEVEL_NONE:
@ -760,6 +770,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
this->supports_speed = value.as_bool(); this->supports_speed = value.as_bool();
return true; return true;
} }
case 7: {
this->supports_direction = value.as_bool();
return true;
}
default: default:
return false; return false;
} }
@ -799,6 +813,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(4, this->unique_id); buffer.encode_string(4, this->unique_id);
buffer.encode_bool(5, this->supports_oscillation); buffer.encode_bool(5, this->supports_oscillation);
buffer.encode_bool(6, this->supports_speed); buffer.encode_bool(6, this->supports_speed);
buffer.encode_bool(7, this->supports_direction);
} }
void ListEntitiesFanResponse::dump_to(std::string &out) const { void ListEntitiesFanResponse::dump_to(std::string &out) const {
char buffer[64]; char buffer[64];
@ -827,6 +842,10 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
out.append(" supports_speed: "); out.append(" supports_speed: ");
out.append(YESNO(this->supports_speed)); out.append(YESNO(this->supports_speed));
out.append("\n"); out.append("\n");
out.append(" supports_direction: ");
out.append(YESNO(this->supports_direction));
out.append("\n");
out.append("}"); out.append("}");
} }
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
@ -843,6 +862,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->speed = value.as_enum<enums::FanSpeed>(); this->speed = value.as_enum<enums::FanSpeed>();
return true; return true;
} }
case 5: {
this->direction = value.as_enum<enums::FanDirection>();
return true;
}
default: default:
return false; return false;
} }
@ -862,6 +885,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(2, this->state); buffer.encode_bool(2, this->state);
buffer.encode_bool(3, this->oscillating); buffer.encode_bool(3, this->oscillating);
buffer.encode_enum<enums::FanSpeed>(4, this->speed); buffer.encode_enum<enums::FanSpeed>(4, this->speed);
buffer.encode_enum<enums::FanDirection>(5, this->direction);
} }
void FanStateResponse::dump_to(std::string &out) const { void FanStateResponse::dump_to(std::string &out) const {
char buffer[64]; char buffer[64];
@ -882,6 +906,10 @@ void FanStateResponse::dump_to(std::string &out) const {
out.append(" speed: "); out.append(" speed: ");
out.append(proto_enum_to_string<enums::FanSpeed>(this->speed)); out.append(proto_enum_to_string<enums::FanSpeed>(this->speed));
out.append("\n"); out.append("\n");
out.append(" direction: ");
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
out.append("\n");
out.append("}"); out.append("}");
} }
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) { bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
@ -910,6 +938,14 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
this->oscillating = value.as_bool(); this->oscillating = value.as_bool();
return true; return true;
} }
case 8: {
this->has_direction = value.as_bool();
return true;
}
case 9: {
this->direction = value.as_enum<enums::FanDirection>();
return true;
}
default: default:
return false; return false;
} }
@ -932,6 +968,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_enum<enums::FanSpeed>(5, this->speed); buffer.encode_enum<enums::FanSpeed>(5, this->speed);
buffer.encode_bool(6, this->has_oscillating); buffer.encode_bool(6, this->has_oscillating);
buffer.encode_bool(7, this->oscillating); buffer.encode_bool(7, this->oscillating);
buffer.encode_bool(8, this->has_direction);
buffer.encode_enum<enums::FanDirection>(9, this->direction);
} }
void FanCommandRequest::dump_to(std::string &out) const { void FanCommandRequest::dump_to(std::string &out) const {
char buffer[64]; char buffer[64];
@ -964,6 +1002,14 @@ void FanCommandRequest::dump_to(std::string &out) const {
out.append(" oscillating: "); out.append(" oscillating: ");
out.append(YESNO(this->oscillating)); out.append(YESNO(this->oscillating));
out.append("\n"); out.append("\n");
out.append(" has_direction: ");
out.append(YESNO(this->has_direction));
out.append("\n");
out.append(" direction: ");
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
out.append("\n");
out.append("}"); out.append("}");
} }
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {

View file

@ -28,6 +28,10 @@ enum FanSpeed : uint32_t {
FAN_SPEED_MEDIUM = 1, FAN_SPEED_MEDIUM = 1,
FAN_SPEED_HIGH = 2, FAN_SPEED_HIGH = 2,
}; };
enum FanDirection : uint32_t {
FAN_DIRECTION_FORWARD = 0,
FAN_DIRECTION_REVERSE = 1,
};
enum LogLevel : uint32_t { enum LogLevel : uint32_t {
LOG_LEVEL_NONE = 0, LOG_LEVEL_NONE = 0,
LOG_LEVEL_ERROR = 1, LOG_LEVEL_ERROR = 1,
@ -279,6 +283,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
std::string unique_id{}; // NOLINT std::string unique_id{}; // NOLINT
bool supports_oscillation{false}; // NOLINT bool supports_oscillation{false}; // NOLINT
bool supports_speed{false}; // NOLINT bool supports_speed{false}; // NOLINT
bool supports_direction{false}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -289,10 +294,11 @@ class ListEntitiesFanResponse : public ProtoMessage {
}; };
class FanStateResponse : public ProtoMessage { class FanStateResponse : public ProtoMessage {
public: public:
uint32_t key{0}; // NOLINT uint32_t key{0}; // NOLINT
bool state{false}; // NOLINT bool state{false}; // NOLINT
bool oscillating{false}; // NOLINT bool oscillating{false}; // NOLINT
enums::FanSpeed speed{}; // NOLINT enums::FanSpeed speed{}; // NOLINT
enums::FanDirection direction{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -302,13 +308,15 @@ class FanStateResponse : public ProtoMessage {
}; };
class FanCommandRequest : public ProtoMessage { class FanCommandRequest : public ProtoMessage {
public: public:
uint32_t key{0}; // NOLINT uint32_t key{0}; // NOLINT
bool has_state{false}; // NOLINT bool has_state{false}; // NOLINT
bool state{false}; // NOLINT bool state{false}; // NOLINT
bool has_speed{false}; // NOLINT bool has_speed{false}; // NOLINT
enums::FanSpeed speed{}; // NOLINT enums::FanSpeed speed{}; // NOLINT
bool has_oscillating{false}; // NOLINT bool has_oscillating{false}; // NOLINT
bool oscillating{false}; // NOLINT bool oscillating{false}; // NOLINT
bool has_direction{false}; // NOLINT
enums::FanDirection direction{}; // NOLINT
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;

View file

@ -1,7 +1,8 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import fan, output from esphome.components import fan, output
from esphome.const import CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, CONF_OUTPUT_ID from esphome.const import CONF_DIRECTION_OUTPUT, CONF_OSCILLATION_OUTPUT, \
CONF_OUTPUT, CONF_OUTPUT_ID
from .. import binary_ns from .. import binary_ns
BinaryFan = binary_ns.class_('BinaryFan', cg.Component) BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
@ -9,6 +10,7 @@ BinaryFan = binary_ns.class_('BinaryFan', cg.Component)
CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan), cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(BinaryFan),
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput), cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
}).extend(cv.COMPONENT_SCHEMA) }).extend(cv.COMPONENT_SCHEMA)
@ -25,3 +27,7 @@ def to_code(config):
if CONF_OSCILLATION_OUTPUT in config: if CONF_OSCILLATION_OUTPUT in config:
oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
cg.add(var.set_oscillating(oscillation_output)) cg.add(var.set_oscillating(oscillation_output))
if CONF_DIRECTION_OUTPUT in config:
direction_output = yield cg.get_variable(config[CONF_DIRECTION_OUTPUT])
cg.add(var.set_direction(direction_output))

View file

@ -11,9 +11,12 @@ void binary::BinaryFan::dump_config() {
if (this->fan_->get_traits().supports_oscillation()) { if (this->fan_->get_traits().supports_oscillation()) {
ESP_LOGCONFIG(TAG, " Oscillation: YES"); ESP_LOGCONFIG(TAG, " Oscillation: YES");
} }
if (this->fan_->get_traits().supports_direction()) {
ESP_LOGCONFIG(TAG, " Direction: YES");
}
} }
void BinaryFan::setup() { void BinaryFan::setup() {
auto traits = fan::FanTraits(this->oscillating_ != nullptr, false); auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr);
this->fan_->set_traits(traits); this->fan_->set_traits(traits);
this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); this->fan_->add_on_state_callback([this]() { this->next_update_ = true; });
} }
@ -41,6 +44,16 @@ void BinaryFan::loop() {
} }
ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); 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));
}
} }
float BinaryFan::get_setup_priority() const { return setup_priority::DATA; } float BinaryFan::get_setup_priority() const { return setup_priority::DATA; }

View file

@ -16,11 +16,13 @@ class BinaryFan : public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; }
void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; }
protected: protected:
fan::FanState *fan_; fan::FanState *fan_;
output::BinaryOutput *output_; output::BinaryOutput *output_;
output::BinaryOutput *oscillating_{nullptr}; output::BinaryOutput *oscillating_{nullptr};
output::BinaryOutput *direction_{nullptr};
bool next_update_{true}; bool next_update_{true};
}; };

View file

@ -22,6 +22,7 @@ struct FanStateRTCState {
bool state; bool state;
FanSpeed speed; FanSpeed speed;
bool oscillating; bool oscillating;
FanDirection direction;
}; };
void FanState::setup() { void FanState::setup() {
@ -34,6 +35,7 @@ void FanState::setup() {
call.set_state(recovered.state); call.set_state(recovered.state);
call.set_speed(recovered.speed); call.set_speed(recovered.speed);
call.set_oscillating(recovered.oscillating); call.set_oscillating(recovered.oscillating);
call.set_direction(recovered.direction);
call.perform(); call.perform();
} }
float FanState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; } float FanState::get_setup_priority() const { return setup_priority::HARDWARE - 1.0f; }
@ -46,6 +48,9 @@ void FanStateCall::perform() const {
if (this->oscillating_.has_value()) { if (this->oscillating_.has_value()) {
this->state_->oscillating = *this->oscillating_; this->state_->oscillating = *this->oscillating_;
} }
if (this->direction_.has_value()) {
this->state_->direction = *this->direction_;
}
if (this->speed_.has_value()) { if (this->speed_.has_value()) {
switch (*this->speed_) { switch (*this->speed_) {
case FAN_SPEED_LOW: case FAN_SPEED_LOW:
@ -63,6 +68,7 @@ void FanStateCall::perform() const {
saved.state = this->state_->state; saved.state = this->state_->state;
saved.speed = this->state_->speed; saved.speed = this->state_->speed;
saved.oscillating = this->state_->oscillating; saved.oscillating = this->state_->oscillating;
saved.direction = this->state_->direction;
this->state_->rtc_.save(&saved); this->state_->rtc_.save(&saved);
this->state_->state_callback_.call(); this->state_->state_callback_.call();

View file

@ -15,6 +15,9 @@ enum FanSpeed {
FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed. FAN_SPEED_HIGH = 2 ///< The fan is running on high/full speed.
}; };
/// Simple enum to represent the direction of a fan
enum FanDirection { FAN_DIRECTION_FORWARD = 0, FAN_DIRECTION_REVERSE = 1 };
class FanState; class FanState;
class FanStateCall { class FanStateCall {
@ -46,6 +49,14 @@ class FanStateCall {
return *this; return *this;
} }
FanStateCall &set_speed(const char *speed); FanStateCall &set_speed(const char *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; void perform() const;
@ -54,6 +65,7 @@ class FanStateCall {
optional<bool> binary_state_; optional<bool> binary_state_;
optional<bool> oscillating_{}; optional<bool> oscillating_{};
optional<FanSpeed> speed_{}; optional<FanSpeed> speed_{};
optional<FanDirection> direction_{};
}; };
class FanState : public Nameable, public Component { class FanState : public Nameable, public Component {
@ -76,6 +88,8 @@ class FanState : public Nameable, public Component {
bool oscillating{false}; bool oscillating{false};
/// The current fan speed. /// The current fan speed.
FanSpeed speed{FAN_SPEED_HIGH}; FanSpeed speed{FAN_SPEED_HIGH};
/// The current direction of the fan
FanDirection direction{FAN_DIRECTION_FORWARD};
FanStateCall turn_on(); FanStateCall turn_on();
FanStateCall turn_off(); FanStateCall turn_off();

View file

@ -6,7 +6,8 @@ namespace fan {
class FanTraits { class FanTraits {
public: public:
FanTraits() = default; FanTraits() = default;
FanTraits(bool oscillation, bool speed) : oscillation_(oscillation), speed_(speed) {} FanTraits(bool oscillation, bool speed, bool direction)
: oscillation_(oscillation), speed_(speed), direction_(direction) {}
/// Return if this fan supports oscillation. /// Return if this fan supports oscillation.
bool supports_oscillation() const { return this->oscillation_; } bool supports_oscillation() const { return this->oscillation_; }
@ -16,10 +17,15 @@ class FanTraits {
bool supports_speed() const { return this->speed_; } bool supports_speed() const { return this->speed_; }
/// Set whether this fan supports speed modes. /// Set whether this fan supports speed modes.
void set_speed(bool speed) { this->speed_ = speed; } void set_speed(bool speed) { this->speed_ = speed; }
/// Return if this fan supports changing direction
bool supports_direction() const { return this->direction_; }
/// Set whether this fan supports changing direction
void set_direction(bool direction) { this->direction_ = direction; }
protected: protected:
bool oscillation_{false}; bool oscillation_{false};
bool speed_{false}; bool speed_{false};
bool direction_{false};
}; };
} // namespace fan } // namespace fan

View file

@ -1,7 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import fan, output from esphome.components import fan, output
from esphome.const import CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, \ from esphome.const import CONF_OSCILLATION_OUTPUT, CONF_OUTPUT, CONF_DIRECTION_OUTPUT, \
CONF_OUTPUT_ID, CONF_SPEED, CONF_LOW, CONF_MEDIUM, CONF_HIGH CONF_OUTPUT_ID, CONF_SPEED, CONF_LOW, CONF_MEDIUM, CONF_HIGH
from .. import speed_ns from .. import speed_ns
@ -11,6 +11,7 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend({
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpeedFan), cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SpeedFan),
cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput), cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput),
cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput), cv.Optional(CONF_OSCILLATION_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_DIRECTION_OUTPUT): cv.use_id(output.BinaryOutput),
cv.Optional(CONF_SPEED, default={}): cv.Schema({ cv.Optional(CONF_SPEED, default={}): cv.Schema({
cv.Optional(CONF_LOW, default=0.33): cv.percentage, cv.Optional(CONF_LOW, default=0.33): cv.percentage,
cv.Optional(CONF_MEDIUM, default=0.66): cv.percentage, cv.Optional(CONF_MEDIUM, default=0.66): cv.percentage,
@ -30,3 +31,7 @@ def to_code(config):
if CONF_OSCILLATION_OUTPUT in config: if CONF_OSCILLATION_OUTPUT in config:
oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT]) oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
cg.add(var.set_oscillating(oscillation_output)) cg.add(var.set_oscillating(oscillation_output))
if CONF_DIRECTION_OUTPUT in config:
direction_output = yield cg.get_variable(config[CONF_DIRECTION_OUTPUT])
cg.add(var.set_direction(direction_output))

View file

@ -11,9 +11,12 @@ void SpeedFan::dump_config() {
if (this->fan_->get_traits().supports_oscillation()) { if (this->fan_->get_traits().supports_oscillation()) {
ESP_LOGCONFIG(TAG, " Oscillation: YES"); ESP_LOGCONFIG(TAG, " Oscillation: YES");
} }
if (this->fan_->get_traits().supports_direction()) {
ESP_LOGCONFIG(TAG, " Direction: YES");
}
} }
void SpeedFan::setup() { void SpeedFan::setup() {
auto traits = fan::FanTraits(this->oscillating_ != nullptr, true); auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr);
this->fan_->set_traits(traits); this->fan_->set_traits(traits);
this->fan_->add_on_state_callback([this]() { this->next_update_ = true; }); this->fan_->add_on_state_callback([this]() { this->next_update_ = true; });
} }
@ -46,6 +49,16 @@ void SpeedFan::loop() {
} }
ESP_LOGD(TAG, "Setting oscillation: %s", ONOFF(enable)); 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));
}
} }
float SpeedFan::get_setup_priority() const { return setup_priority::DATA; } float SpeedFan::get_setup_priority() const { return setup_priority::DATA; }

View file

@ -16,6 +16,7 @@ class SpeedFan : public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; } void set_oscillating(output::BinaryOutput *oscillating) { this->oscillating_ = oscillating; }
void set_direction(output::BinaryOutput *direction) { this->direction_ = direction; }
void set_speeds(float low, float medium, float high) { void set_speeds(float low, float medium, float high) {
this->low_speed_ = low; this->low_speed_ = low;
this->medium_speed_ = medium; this->medium_speed_ = medium;
@ -26,6 +27,7 @@ class SpeedFan : public Component {
fan::FanState *fan_; fan::FanState *fan_;
output::FloatOutput *output_; output::FloatOutput *output_;
output::BinaryOutput *oscillating_{nullptr}; output::BinaryOutput *oscillating_{nullptr};
output::BinaryOutput *direction_{nullptr};
float low_speed_{}; float low_speed_{};
float medium_speed_{}; float medium_speed_{};
float high_speed_{}; float high_speed_{};

View file

@ -7,7 +7,7 @@ namespace tuya {
static const char *TAG = "tuya.fan"; static const char *TAG = "tuya.fan";
void TuyaFan::setup() { void TuyaFan::setup() {
auto traits = fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value()); auto traits = fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value(), false);
this->fan_->set_traits(traits); this->fan_->set_traits(traits);
if (this->speed_id_.has_value()) { if (this->speed_id_.has_value()) {

View file

@ -130,6 +130,7 @@ CONF_DIMENSIONS = 'dimensions'
CONF_DIO_PIN = 'dio_pin' CONF_DIO_PIN = 'dio_pin'
CONF_DIR_PIN = 'dir_pin' CONF_DIR_PIN = 'dir_pin'
CONF_DIRECTION = 'direction' CONF_DIRECTION = 'direction'
CONF_DIRECTION_OUTPUT = 'direction_output'
CONF_DISCOVERY = 'discovery' CONF_DISCOVERY = 'discovery'
CONF_DISCOVERY_PREFIX = 'discovery_prefix' CONF_DISCOVERY_PREFIX = 'discovery_prefix'
CONF_DISCOVERY_RETAIN = 'discovery_retain' CONF_DISCOVERY_RETAIN = 'discovery_retain'

View file

@ -1467,9 +1467,13 @@ fan:
- platform: binary - platform: binary
output: gpio_26 output: gpio_26
name: "Living Room Fan 1" name: "Living Room Fan 1"
oscillation_output: gpio_19
direction_output: gpio_26
- platform: speed - platform: speed
output: pca_6 output: pca_6
name: "Living Room Fan 2" name: "Living Room Fan 2"
oscillation_output: gpio_19
direction_output: gpio_26
speed: speed:
low: 0.45 low: 0.45
medium: 0.75 medium: 0.75