mirror of
https://github.com/esphome/esphome.git
synced 2024-11-25 08:28:12 +01:00
Support fan speed levels (#1541)
* Add fan speed percentage support to the API * Add float fan speed percentage * Add percentage support to automation and configuration * Update Tuya fan * Fix pylint warning * Update API to use speed levels instead of percentage * Use speed levels * Fix type warnings * MQTT component now converts between speed levels and enums * Webserver now supports speed_level * Update prometheus * Remove low/medium/high settings from speed fan * Remove unused enum * Configurable speed levels for speed fan * Remove unused import * Rename speed_level->speed and speed_levels->speed_count * Rename supported_speed_levels -> supported_speed_count in API and FanTraits Field id stays the same in the protocol, so the change is not breaking for aioesphome.
This commit is contained in:
parent
08998caabc
commit
7708b81ef5
22 changed files with 182 additions and 99 deletions
|
@ -303,6 +303,7 @@ message ListEntitiesFanResponse {
|
|||
bool supports_oscillation = 5;
|
||||
bool supports_speed = 6;
|
||||
bool supports_direction = 7;
|
||||
int32 supported_speed_count = 8;
|
||||
}
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0;
|
||||
|
@ -322,8 +323,9 @@ message FanStateResponse {
|
|||
fixed32 key = 1;
|
||||
bool state = 2;
|
||||
bool oscillating = 3;
|
||||
FanSpeed speed = 4;
|
||||
FanSpeed speed = 4 [deprecated = true];
|
||||
FanDirection direction = 5;
|
||||
int32 speed_level = 6;
|
||||
}
|
||||
message FanCommandRequest {
|
||||
option (id) = 31;
|
||||
|
@ -334,12 +336,14 @@ message FanCommandRequest {
|
|||
fixed32 key = 1;
|
||||
bool has_state = 2;
|
||||
bool state = 3;
|
||||
bool has_speed = 4;
|
||||
FanSpeed speed = 5;
|
||||
bool has_speed = 4 [deprecated = true];
|
||||
FanSpeed speed = 5 [deprecated = true];
|
||||
bool has_oscillating = 6;
|
||||
bool oscillating = 7;
|
||||
bool has_direction = 8;
|
||||
FanDirection direction = 9;
|
||||
bool has_speed_level = 10;
|
||||
int32 speed_level = 11;
|
||||
}
|
||||
|
||||
// ==================== LIGHT ====================
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
#ifdef USE_HOMEASSISTANT_TIME
|
||||
#include "esphome/components/homeassistant/time/homeassistant_time.h"
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
#include "esphome/components/fan/fan_helpers.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
@ -246,8 +249,10 @@ bool APIConnection::send_fan_state(fan::FanState *fan) {
|
|||
resp.state = fan->state;
|
||||
if (traits.supports_oscillation())
|
||||
resp.oscillating = fan->oscillating;
|
||||
if (traits.supports_speed())
|
||||
resp.speed = static_cast<enums::FanSpeed>(fan->speed);
|
||||
if (traits.supports_speed()) {
|
||||
resp.speed_level = fan->speed;
|
||||
resp.speed = static_cast<enums::FanSpeed>(fan::speed_level_to_enum(fan->speed, traits.supported_speed_count()));
|
||||
}
|
||||
if (traits.supports_direction())
|
||||
resp.direction = static_cast<enums::FanDirection>(fan->direction);
|
||||
return this->send_fan_state_response(resp);
|
||||
|
@ -262,6 +267,7 @@ bool APIConnection::send_fan_info(fan::FanState *fan) {
|
|||
msg.supports_oscillation = traits.supports_oscillation();
|
||||
msg.supports_speed = traits.supports_speed();
|
||||
msg.supports_direction = traits.supports_direction();
|
||||
msg.supported_speed_count = traits.supported_speed_count();
|
||||
return this->send_list_entities_fan_response(msg);
|
||||
}
|
||||
void APIConnection::fan_command(const FanCommandRequest &msg) {
|
||||
|
@ -269,13 +275,19 @@ void APIConnection::fan_command(const FanCommandRequest &msg) {
|
|||
if (fan == nullptr)
|
||||
return;
|
||||
|
||||
auto traits = fan->get_traits();
|
||||
|
||||
auto call = fan->make_call();
|
||||
if (msg.has_state)
|
||||
call.set_state(msg.state);
|
||||
if (msg.has_oscillating)
|
||||
call.set_oscillating(msg.oscillating);
|
||||
if (msg.has_speed)
|
||||
call.set_speed(static_cast<fan::FanSpeed>(msg.speed));
|
||||
if (msg.has_speed_level) {
|
||||
// Prefer level
|
||||
call.set_speed(msg.speed_level);
|
||||
} else if (msg.has_speed) {
|
||||
call.set_speed(fan::speed_enum_to_level(static_cast<fan::FanSpeed>(msg.speed), traits.supported_speed_count()));
|
||||
}
|
||||
if (msg.has_direction)
|
||||
call.set_direction(static_cast<fan::FanDirection>(msg.direction));
|
||||
call.perform();
|
||||
|
@ -590,7 +602,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
|||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 3;
|
||||
resp.api_version_minor = 4;
|
||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||
this->connection_state_ = ConnectionState::CONNECTED;
|
||||
return resp;
|
||||
|
|
|
@ -774,6 +774,10 @@ bool ListEntitiesFanResponse::decode_varint(uint32_t field_id, ProtoVarInt value
|
|||
this->supports_direction = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->supported_speed_count = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -814,6 +818,7 @@ void ListEntitiesFanResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_bool(5, this->supports_oscillation);
|
||||
buffer.encode_bool(6, this->supports_speed);
|
||||
buffer.encode_bool(7, this->supports_direction);
|
||||
buffer.encode_int32(8, this->supported_speed_count);
|
||||
}
|
||||
void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
|
@ -846,6 +851,11 @@ void ListEntitiesFanResponse::dump_to(std::string &out) const {
|
|||
out.append(" supports_direction: ");
|
||||
out.append(YESNO(this->supports_direction));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supported_speed_count: ");
|
||||
sprintf(buffer, "%d", this->supported_speed_count);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
|
@ -866,6 +876,10 @@ bool FanStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||
this->direction = value.as_enum<enums::FanDirection>();
|
||||
return true;
|
||||
}
|
||||
case 6: {
|
||||
this->speed_level = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -886,6 +900,7 @@ void FanStateResponse::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_bool(3, this->oscillating);
|
||||
buffer.encode_enum<enums::FanSpeed>(4, this->speed);
|
||||
buffer.encode_enum<enums::FanDirection>(5, this->direction);
|
||||
buffer.encode_int32(6, this->speed_level);
|
||||
}
|
||||
void FanStateResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
|
@ -910,6 +925,11 @@ void FanStateResponse::dump_to(std::string &out) const {
|
|||
out.append(" direction: ");
|
||||
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" speed_level: ");
|
||||
sprintf(buffer, "%d", this->speed_level);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
|
@ -946,6 +966,14 @@ bool FanCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
|||
this->direction = value.as_enum<enums::FanDirection>();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->has_speed_level = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->speed_level = value.as_int32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -970,6 +998,8 @@ void FanCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
|||
buffer.encode_bool(7, this->oscillating);
|
||||
buffer.encode_bool(8, this->has_direction);
|
||||
buffer.encode_enum<enums::FanDirection>(9, this->direction);
|
||||
buffer.encode_bool(10, this->has_speed_level);
|
||||
buffer.encode_int32(11, this->speed_level);
|
||||
}
|
||||
void FanCommandRequest::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
|
@ -1010,6 +1040,15 @@ void FanCommandRequest::dump_to(std::string &out) const {
|
|||
out.append(" direction: ");
|
||||
out.append(proto_enum_to_string<enums::FanDirection>(this->direction));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_speed_level: ");
|
||||
out.append(YESNO(this->has_speed_level));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" speed_level: ");
|
||||
sprintf(buffer, "%d", this->speed_level);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
bool ListEntitiesLightResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
|
|
|
@ -284,6 +284,7 @@ class ListEntitiesFanResponse : public ProtoMessage {
|
|||
bool supports_oscillation{false}; // NOLINT
|
||||
bool supports_speed{false}; // NOLINT
|
||||
bool supports_direction{false}; // NOLINT
|
||||
int32_t supported_speed_count{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
@ -299,6 +300,7 @@ class FanStateResponse : public ProtoMessage {
|
|||
bool oscillating{false}; // NOLINT
|
||||
enums::FanSpeed speed{}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
int32_t speed_level{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
@ -317,6 +319,8 @@ class FanCommandRequest : public ProtoMessage {
|
|||
bool oscillating{false}; // NOLINT
|
||||
bool has_direction{false}; // NOLINT
|
||||
enums::FanDirection direction{}; // NOLINT
|
||||
bool has_speed_level{false}; // NOLINT
|
||||
int32_t speed_level{0}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
@ -401,9 +405,9 @@ class ListEntitiesSensorResponse : public ProtoMessage {
|
|||
std::string unique_id{}; // NOLINT
|
||||
std::string icon{}; // NOLINT
|
||||
std::string unit_of_measurement{}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
int32_t accuracy_decimals{0}; // NOLINT
|
||||
bool force_update{false}; // NOLINT
|
||||
std::string device_class{}; // NOLINT
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
void dump_to(std::string &out) const override;
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ void binary::BinaryFan::dump_config() {
|
|||
}
|
||||
}
|
||||
void BinaryFan::setup() {
|
||||
auto traits = fan::FanTraits(this->oscillating_ != nullptr, false, this->direction_ != nullptr);
|
||||
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; });
|
||||
}
|
||||
|
|
|
@ -28,14 +28,6 @@ TurnOnAction = fan_ns.class_("TurnOnAction", automation.Action)
|
|||
TurnOffAction = fan_ns.class_("TurnOffAction", automation.Action)
|
||||
ToggleAction = fan_ns.class_("ToggleAction", automation.Action)
|
||||
|
||||
FanSpeed = fan_ns.enum("FanSpeed")
|
||||
FAN_SPEEDS = {
|
||||
"OFF": FanSpeed.FAN_SPEED_OFF,
|
||||
"LOW": FanSpeed.FAN_SPEED_LOW,
|
||||
"MEDIUM": FanSpeed.FAN_SPEED_MEDIUM,
|
||||
"HIGH": FanSpeed.FAN_SPEED_HIGH,
|
||||
}
|
||||
|
||||
FAN_SCHEMA = cv.MQTT_COMMAND_COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(FanState),
|
||||
|
@ -128,7 +120,7 @@ def fan_turn_off_to_code(config, action_id, template_arg, args):
|
|||
{
|
||||
cv.Required(CONF_ID): cv.use_id(FanState),
|
||||
cv.Optional(CONF_OSCILLATING): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_SPEED): cv.templatable(cv.enum(FAN_SPEEDS, upper=True)),
|
||||
cv.Optional(CONF_SPEED): cv.templatable(cv.int_range(1)),
|
||||
}
|
||||
),
|
||||
)
|
||||
|
@ -139,7 +131,7 @@ def fan_turn_on_to_code(config, action_id, template_arg, args):
|
|||
template_ = yield cg.templatable(config[CONF_OSCILLATING], args, bool)
|
||||
cg.add(var.set_oscillating(template_))
|
||||
if CONF_SPEED in config:
|
||||
template_ = yield cg.templatable(config[CONF_SPEED], args, FanSpeed)
|
||||
template_ = yield cg.templatable(config[CONF_SPEED], args, int)
|
||||
cg.add(var.set_speed(template_))
|
||||
yield var
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ template<typename... Ts> class TurnOnAction : public Action<Ts...> {
|
|||
explicit TurnOnAction(FanState *state) : state_(state) {}
|
||||
|
||||
TEMPLATABLE_VALUE(bool, oscillating)
|
||||
TEMPLATABLE_VALUE(FanSpeed, speed)
|
||||
TEMPLATABLE_VALUE(int, speed)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto call = this->state_->turn_on();
|
||||
|
|
20
esphome/components/fan/fan_helpers.cpp
Normal file
20
esphome/components/fan/fan_helpers.cpp
Normal file
|
@ -0,0 +1,20 @@
|
|||
#include <cassert>
|
||||
#include "fan_helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace fan {
|
||||
|
||||
FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels) {
|
||||
const auto speed_ratio = static_cast<float>(speed_level) / (supported_speed_levels + 1);
|
||||
const auto legacy_level = static_cast<int>(clamp(ceilf(speed_ratio * 3), 1, 3));
|
||||
return static_cast<FanSpeed>(legacy_level - 1);
|
||||
}
|
||||
|
||||
int speed_enum_to_level(FanSpeed speed, int supported_speed_levels) {
|
||||
const auto enum_level = static_cast<int>(speed) + 1;
|
||||
const auto speed_level = roundf(enum_level / 3.0f * supported_speed_levels);
|
||||
return static_cast<int>(speed_level);
|
||||
}
|
||||
|
||||
} // namespace fan
|
||||
} // namespace esphome
|
11
esphome/components/fan/fan_helpers.h
Normal file
11
esphome/components/fan/fan_helpers.h
Normal file
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
#include "fan_state.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace fan {
|
||||
|
||||
FanSpeed speed_level_to_enum(int speed_level, int supported_speed_levels);
|
||||
int speed_enum_to_level(FanSpeed speed, int supported_speed_levels);
|
||||
|
||||
} // namespace fan
|
||||
} // namespace esphome
|
|
@ -1,4 +1,5 @@
|
|||
#include "fan_state.h"
|
||||
#include "fan_helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
|
@ -20,7 +21,7 @@ FanStateCall FanState::make_call() { return FanStateCall(this); }
|
|||
|
||||
struct FanStateRTCState {
|
||||
bool state;
|
||||
FanSpeed speed;
|
||||
int speed;
|
||||
bool oscillating;
|
||||
FanDirection direction;
|
||||
};
|
||||
|
@ -52,16 +53,8 @@ void FanStateCall::perform() const {
|
|||
this->state_->direction = *this->direction_;
|
||||
}
|
||||
if (this->speed_.has_value()) {
|
||||
switch (*this->speed_) {
|
||||
case FAN_SPEED_LOW:
|
||||
case FAN_SPEED_MEDIUM:
|
||||
case FAN_SPEED_HIGH:
|
||||
this->state_->speed = *this->speed_;
|
||||
break;
|
||||
default:
|
||||
// protect from invalid input
|
||||
break;
|
||||
}
|
||||
const int speed_count = this->state_->get_traits().supported_speed_count();
|
||||
this->state_->speed = static_cast<int>(clamp(*this->speed_, 1, speed_count));
|
||||
}
|
||||
|
||||
FanStateRTCState saved{};
|
||||
|
@ -73,13 +66,15 @@ void FanStateCall::perform() const {
|
|||
|
||||
this->state_->state_callback_.call();
|
||||
}
|
||||
FanStateCall &FanStateCall::set_speed(const char *speed) {
|
||||
if (strcasecmp(speed, "low") == 0) {
|
||||
this->set_speed(FAN_SPEED_LOW);
|
||||
} else if (strcasecmp(speed, "medium") == 0) {
|
||||
this->set_speed(FAN_SPEED_MEDIUM);
|
||||
} else if (strcasecmp(speed, "high") == 0) {
|
||||
this->set_speed(FAN_SPEED_HIGH);
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -3,12 +3,13 @@
|
|||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "fan_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace fan {
|
||||
|
||||
/// Simple enum to represent the speed of a fan.
|
||||
/// Simple enum to represent the speed of a fan. - DEPRECATED - Will be deleted soon
|
||||
enum FanSpeed {
|
||||
FAN_SPEED_LOW = 0, ///< The fan is running on low speed.
|
||||
FAN_SPEED_MEDIUM = 1, ///< The fan is running on medium speed.
|
||||
|
@ -40,15 +41,11 @@ class FanStateCall {
|
|||
this->oscillating_ = oscillating;
|
||||
return *this;
|
||||
}
|
||||
FanStateCall &set_speed(FanSpeed speed) {
|
||||
FanStateCall &set_speed(int speed) {
|
||||
this->speed_ = speed;
|
||||
return *this;
|
||||
}
|
||||
FanStateCall &set_speed(optional<FanSpeed> speed) {
|
||||
this->speed_ = speed;
|
||||
return *this;
|
||||
}
|
||||
FanStateCall &set_speed(const char *speed);
|
||||
FanStateCall &set_speed(const char *legacy_speed);
|
||||
FanStateCall &set_direction(FanDirection direction) {
|
||||
this->direction_ = direction;
|
||||
return *this;
|
||||
|
@ -63,8 +60,8 @@ class FanStateCall {
|
|||
protected:
|
||||
FanState *const state_;
|
||||
optional<bool> binary_state_;
|
||||
optional<bool> oscillating_{};
|
||||
optional<FanSpeed> speed_{};
|
||||
optional<bool> oscillating_;
|
||||
optional<int> speed_;
|
||||
optional<FanDirection> direction_{};
|
||||
};
|
||||
|
||||
|
@ -86,8 +83,8 @@ class FanState : public Nameable, public Component {
|
|||
bool state{false};
|
||||
/// The current oscillation state of the fan.
|
||||
bool oscillating{false};
|
||||
/// The current fan speed.
|
||||
FanSpeed speed{FAN_SPEED_HIGH};
|
||||
/// The current fan speed level
|
||||
int speed{};
|
||||
/// The current direction of the fan
|
||||
FanDirection direction{FAN_DIRECTION_FORWARD};
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ namespace fan {
|
|||
class FanTraits {
|
||||
public:
|
||||
FanTraits() = default;
|
||||
FanTraits(bool oscillation, bool speed, bool direction)
|
||||
: oscillation_(oscillation), speed_(speed), direction_(direction) {}
|
||||
FanTraits(bool oscillation, bool speed, bool direction, int speed_count)
|
||||
: oscillation_(oscillation), speed_(speed), direction_(direction), speed_count_(speed_count) {}
|
||||
|
||||
/// Return if this fan supports oscillation.
|
||||
bool supports_oscillation() const { return this->oscillation_; }
|
||||
|
@ -15,8 +15,12 @@ class FanTraits {
|
|||
void set_oscillation(bool oscillation) { this->oscillation_ = oscillation; }
|
||||
/// Return if this fan supports speed modes.
|
||||
bool supports_speed() const { return this->speed_; }
|
||||
/// Set whether this fan supports speed modes.
|
||||
/// Set whether this fan supports speed levels.
|
||||
void set_speed(bool speed) { this->speed_ = speed; }
|
||||
/// Return how many speed levels the fan has
|
||||
int supported_speed_count() const { return this->speed_count_; }
|
||||
/// Set how many speed levels this fan has.
|
||||
void set_supported_speed_count(int speed_count) { this->speed_count_ = speed_count; }
|
||||
/// Return if this fan supports changing direction
|
||||
bool supports_direction() const { return this->direction_; }
|
||||
/// Set whether this fan supports changing direction
|
||||
|
@ -26,6 +30,7 @@ class FanTraits {
|
|||
bool oscillation_{false};
|
||||
bool speed_{false};
|
||||
bool direction_{false};
|
||||
int speed_count_{};
|
||||
};
|
||||
|
||||
} // namespace fan
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_FAN
|
||||
#include "esphome/components/fan/fan_helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
@ -94,9 +95,10 @@ bool MQTTFanComponent::publish_state() {
|
|||
this->state_->oscillating ? "oscillate_on" : "oscillate_off");
|
||||
failed = failed || !success;
|
||||
}
|
||||
if (this->state_->get_traits().supports_speed()) {
|
||||
auto traits = this->state_->get_traits();
|
||||
if (traits.supports_speed()) {
|
||||
const char *payload;
|
||||
switch (this->state_->speed) {
|
||||
switch (fan::speed_level_to_enum(this->state_->speed, traits.supported_speed_count())) {
|
||||
case FAN_SPEED_LOW: {
|
||||
payload = "low";
|
||||
break;
|
||||
|
|
|
@ -7,9 +7,7 @@ from esphome.const import (
|
|||
CONF_DIRECTION_OUTPUT,
|
||||
CONF_OUTPUT_ID,
|
||||
CONF_SPEED,
|
||||
CONF_LOW,
|
||||
CONF_MEDIUM,
|
||||
CONF_HIGH,
|
||||
CONF_SPEED_COUNT,
|
||||
)
|
||||
from .. import speed_ns
|
||||
|
||||
|
@ -21,13 +19,10 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
|
|||
cv.Required(CONF_OUTPUT): cv.use_id(output.FloatOutput),
|
||||
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_LOW, default=0.33): cv.percentage,
|
||||
cv.Optional(CONF_MEDIUM, default=0.66): cv.percentage,
|
||||
cv.Optional(CONF_HIGH, default=1.0): cv.percentage,
|
||||
}
|
||||
cv.Optional(CONF_SPEED): cv.invalid(
|
||||
"Configuring individual speeds is deprecated."
|
||||
),
|
||||
cv.Optional(CONF_SPEED_COUNT, default=100): cv.int_range(min=1),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
@ -35,10 +30,10 @@ CONFIG_SCHEMA = fan.FAN_SCHEMA.extend(
|
|||
def to_code(config):
|
||||
output_ = yield cg.get_variable(config[CONF_OUTPUT])
|
||||
state = yield fan.create_fan_state(config)
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID], state, output_)
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_OUTPUT_ID], state, output_, config[CONF_SPEED_COUNT]
|
||||
)
|
||||
yield cg.register_component(var, config)
|
||||
speeds = config[CONF_SPEED]
|
||||
cg.add(var.set_speeds(speeds[CONF_LOW], speeds[CONF_MEDIUM], speeds[CONF_HIGH]))
|
||||
|
||||
if CONF_OSCILLATION_OUTPUT in config:
|
||||
oscillation_output = yield cg.get_variable(config[CONF_OSCILLATION_OUTPUT])
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "speed_fan.h"
|
||||
#include "esphome/components/fan/fan_helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
|
@ -16,7 +17,7 @@ void SpeedFan::dump_config() {
|
|||
}
|
||||
}
|
||||
void SpeedFan::setup() {
|
||||
auto traits = fan::FanTraits(this->oscillating_ != nullptr, true, this->direction_ != nullptr);
|
||||
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; });
|
||||
}
|
||||
|
@ -29,12 +30,7 @@ void SpeedFan::loop() {
|
|||
{
|
||||
float speed = 0.0f;
|
||||
if (this->fan_->state) {
|
||||
if (this->fan_->speed == fan::FAN_SPEED_LOW)
|
||||
speed = this->low_speed_;
|
||||
else if (this->fan_->speed == fan::FAN_SPEED_MEDIUM)
|
||||
speed = this->medium_speed_;
|
||||
else if (this->fan_->speed == fan::FAN_SPEED_HIGH)
|
||||
speed = this->high_speed_;
|
||||
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);
|
||||
|
|
|
@ -10,28 +10,22 @@ namespace speed {
|
|||
|
||||
class SpeedFan : public Component {
|
||||
public:
|
||||
SpeedFan(fan::FanState *fan, output::FloatOutput *output) : fan_(fan), output_(output) {}
|
||||
SpeedFan(fan::FanState *fan, output::FloatOutput *output, int speed_count)
|
||||
: fan_(fan), 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; }
|
||||
void set_speeds(float low, float medium, float high) {
|
||||
this->low_speed_ = low;
|
||||
this->medium_speed_ = medium;
|
||||
this->high_speed_ = high;
|
||||
}
|
||||
|
||||
protected:
|
||||
fan::FanState *fan_;
|
||||
output::FloatOutput *output_;
|
||||
output::BinaryOutput *oscillating_{nullptr};
|
||||
output::BinaryOutput *direction_{nullptr};
|
||||
float low_speed_{};
|
||||
float medium_speed_{};
|
||||
float high_speed_{};
|
||||
bool next_update_{true};
|
||||
int speed_count_{};
|
||||
};
|
||||
|
||||
} // namespace speed
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/fan/fan_helpers.h"
|
||||
#include "tuya_fan.h"
|
||||
|
||||
namespace esphome {
|
||||
|
@ -7,18 +8,18 @@ namespace tuya {
|
|||
static const char *TAG = "tuya.fan";
|
||||
|
||||
void TuyaFan::setup() {
|
||||
auto traits = fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value(), false);
|
||||
auto traits = fan::FanTraits(this->oscillation_id_.has_value(), this->speed_id_.has_value(), false, 3);
|
||||
this->fan_->set_traits(traits);
|
||||
|
||||
if (this->speed_id_.has_value()) {
|
||||
this->parent_->register_listener(*this->speed_id_, [this](TuyaDatapoint datapoint) {
|
||||
auto call = this->fan_->make_call();
|
||||
if (datapoint.value_enum == 0x0)
|
||||
call.set_speed(fan::FAN_SPEED_LOW);
|
||||
call.set_speed(1);
|
||||
else if (datapoint.value_enum == 0x1)
|
||||
call.set_speed(fan::FAN_SPEED_MEDIUM);
|
||||
call.set_speed(2);
|
||||
else if (datapoint.value_enum == 0x2)
|
||||
call.set_speed(fan::FAN_SPEED_HIGH);
|
||||
call.set_speed(3);
|
||||
else
|
||||
ESP_LOGCONFIG(TAG, "Speed has invalid value %d", datapoint.value_enum);
|
||||
ESP_LOGD(TAG, "MCU reported speed of: %d", datapoint.value_enum);
|
||||
|
@ -75,12 +76,7 @@ void TuyaFan::write_state() {
|
|||
TuyaDatapoint datapoint{};
|
||||
datapoint.id = *this->speed_id_;
|
||||
datapoint.type = TuyaDatapointType::ENUM;
|
||||
if (this->fan_->speed == fan::FAN_SPEED_LOW)
|
||||
datapoint.value_enum = 0;
|
||||
if (this->fan_->speed == fan::FAN_SPEED_MEDIUM)
|
||||
datapoint.value_enum = 1;
|
||||
if (this->fan_->speed == fan::FAN_SPEED_HIGH)
|
||||
datapoint.value_enum = 2;
|
||||
datapoint.value_enum = this->fan_->speed - 1;
|
||||
ESP_LOGD(TAG, "Setting speed: %d", datapoint.value_enum);
|
||||
this->parent_->set_datapoint_value(datapoint);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,10 @@
|
|||
#include <esphome/components/logger/logger.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
#include "esphome/components/fan/fan_helpers.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server {
|
||||
|
||||
|
@ -364,8 +368,10 @@ std::string WebServer::fan_json(fan::FanState *obj) {
|
|||
root["id"] = "fan-" + obj->get_object_id();
|
||||
root["state"] = obj->state ? "ON" : "OFF";
|
||||
root["value"] = obj->state;
|
||||
if (obj->get_traits().supports_speed()) {
|
||||
switch (obj->speed) {
|
||||
const auto traits = obj->get_traits();
|
||||
if (traits.supports_speed()) {
|
||||
root["speed_level"] = obj->speed;
|
||||
switch (fan::speed_level_to_enum(obj->speed, traits.supported_speed_count())) {
|
||||
case fan::FAN_SPEED_LOW:
|
||||
root["speed"] = "low";
|
||||
break;
|
||||
|
@ -400,6 +406,15 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, UrlMatch matc
|
|||
String speed = request->getParam("speed")->value();
|
||||
call.set_speed(speed.c_str());
|
||||
}
|
||||
if (request->hasParam("speed_level")) {
|
||||
String speed_level = request->getParam("speed_level")->value();
|
||||
auto val = parse_int(speed_level.c_str());
|
||||
if (!val.has_value()) {
|
||||
ESP_LOGW(TAG, "Can't convert '%s' to number!", speed_level.c_str());
|
||||
return;
|
||||
}
|
||||
call.set_speed(*val);
|
||||
}
|
||||
if (request->hasParam("oscillation")) {
|
||||
String speed = request->getParam("oscillation")->value();
|
||||
auto val = parse_on_off(speed.c_str());
|
||||
|
|
|
@ -483,6 +483,7 @@ CONF_SLEEP_WHEN_DONE = "sleep_when_done"
|
|||
CONF_SONY = "sony"
|
||||
CONF_SPEED = "speed"
|
||||
CONF_SPEED_COMMAND_TOPIC = "speed_command_topic"
|
||||
CONF_SPEED_COUNT = "speed_count"
|
||||
CONF_SPEED_STATE_TOPIC = "speed_state_topic"
|
||||
CONF_SPI_ID = "spi_id"
|
||||
CONF_SPIKE_REJECTION = "spike_rejection"
|
||||
|
|
|
@ -248,6 +248,13 @@ optional<float> parse_float(const std::string &str) {
|
|||
return {};
|
||||
return value;
|
||||
}
|
||||
optional<int> parse_int(const std::string &str) {
|
||||
char *end;
|
||||
int value = ::strtol(str.c_str(), &end, 10);
|
||||
if (end == nullptr || end != str.end().base())
|
||||
return {};
|
||||
return value;
|
||||
}
|
||||
uint32_t fnv1_hash(const std::string &str) {
|
||||
uint32_t hash = 2166136261UL;
|
||||
for (char c : str) {
|
||||
|
|
|
@ -42,6 +42,7 @@ std::string to_string(float val);
|
|||
std::string to_string(double val);
|
||||
std::string to_string(long double val);
|
||||
optional<float> parse_float(const std::string &str);
|
||||
optional<int> parse_int(const std::string &str);
|
||||
|
||||
/// Sanitize the hostname by removing characters that are not in the allowlist and truncating it to 63 chars.
|
||||
std::string sanitize_hostname(const std::string &hostname);
|
||||
|
|
|
@ -1708,13 +1708,10 @@ fan:
|
|||
direction_output: gpio_26
|
||||
- platform: speed
|
||||
output: pca_6
|
||||
speed_count: 10
|
||||
name: 'Living Room Fan 2'
|
||||
oscillation_output: gpio_19
|
||||
direction_output: gpio_26
|
||||
speed:
|
||||
low: 0.45
|
||||
medium: 0.75
|
||||
high: 1.0
|
||||
oscillation_state_topic: oscillation/state/topic
|
||||
oscillation_command_topic: oscillation/command/topic
|
||||
speed_state_topic: speed/state/topic
|
||||
|
|
Loading…
Reference in a new issue