diff --git a/esphome/components/tuya/fan/__init__.py b/esphome/components/tuya/fan/__init__.py new file mode 100644 index 0000000000..8b4a0fa25f --- /dev/null +++ b/esphome/components/tuya/fan/__init__.py @@ -0,0 +1,39 @@ +from esphome.components import fan +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_OUTPUT_ID +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ['tuya'] + +CONF_SPEED_DATAPOINT = "speed_datapoint" +CONF_SWITCH_DATAPOINT = "switch_datapoint" +CONF_OSCILLATION_DATAPOINT = "oscillation_datapoint" + +TuyaFan = tuya_ns.class_('TuyaFan', cg.Component) + +CONFIG_SCHEMA = cv.All(fan.FAN_SCHEMA.extend({ + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaFan), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Optional(CONF_OSCILLATION_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_SPEED_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, +}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key( + CONF_SPEED_DATAPOINT, CONF_SWITCH_DATAPOINT)) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + yield cg.register_component(var, config) + + paren = yield cg.get_variable(config[CONF_TUYA_ID]) + fan_ = yield fan.create_fan_state(config) + cg.add(var.set_tuya_parent(paren)) + cg.add(var.set_fan(fan_)) + + if CONF_SPEED_DATAPOINT in config: + cg.add(var.set_speed_id(config[CONF_SPEED_DATAPOINT])) + if CONF_SWITCH_DATAPOINT in config: + cg.add(var.set_switch_id(config[CONF_SWITCH_DATAPOINT])) + if CONF_OSCILLATION_DATAPOINT in config: + cg.add(var.set_oscillation_id(config[CONF_OSCILLATION_DATAPOINT])) diff --git a/esphome/components/tuya/fan/tuya_fan.cpp b/esphome/components/tuya/fan/tuya_fan.cpp new file mode 100644 index 0000000000..b9fe2c0829 --- /dev/null +++ b/esphome/components/tuya/fan/tuya_fan.cpp @@ -0,0 +1,90 @@ +#include "esphome/core/log.h" +#include "tuya_fan.h" + +namespace esphome { +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()); + 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); + else if (datapoint.value_enum == 0x1) + call.set_speed(fan::FAN_SPEED_MEDIUM); + else if (datapoint.value_enum == 0x2) + call.set_speed(fan::FAN_SPEED_HIGH); + else + ESP_LOGCONFIG(TAG, "Speed has invalid value %d", datapoint.value_enum); + ESP_LOGD(TAG, "MCU reported speed of: %d", datapoint.value_enum); + call.perform(); + }); + } + if (this->switch_id_.has_value()) { + this->parent_->register_listener(*this->switch_id_, [this](TuyaDatapoint datapoint) { + auto call = this->fan_->make_call(); + call.set_state(datapoint.value_bool); + call.perform(); + ESP_LOGD(TAG, "MCU reported switch is: %s", ONOFF(datapoint.value_bool)); + }); + } + if (this->oscillation_id_.has_value()) { + this->parent_->register_listener(*this->oscillation_id_, [this](TuyaDatapoint datapoint) { + auto call = this->fan_->make_call(); + call.set_oscillating(datapoint.value_bool); + call.perform(); + ESP_LOGD(TAG, "MCU reported oscillation is: %s", ONOFF(datapoint.value_bool)); + }); + } + this->fan_->add_on_state_callback([this]() { this->write_state(); }); +} + +void TuyaFan::dump_config() { + ESP_LOGCONFIG(TAG, "Tuya Fan:"); + if (this->speed_id_.has_value()) + ESP_LOGCONFIG(TAG, " Speed has datapoint ID %u", *this->speed_id_); + if (this->switch_id_.has_value()) + ESP_LOGCONFIG(TAG, " Switch has datapoint ID %u", *this->switch_id_); + if (this->oscillation_id_.has_value()) + ESP_LOGCONFIG(TAG, " Oscillation has datapoint ID %u", *this->oscillation_id_); +} + +void TuyaFan::write_state() { + if (this->switch_id_.has_value()) { + TuyaDatapoint datapoint{}; + datapoint.id = *this->switch_id_; + datapoint.type = TuyaDatapointType::BOOLEAN; + datapoint.value_bool = this->fan_->state; + this->parent_->set_datapoint_value(datapoint); + ESP_LOGD(TAG, "Setting switch: %s", ONOFF(this->fan_->state)); + } + if (this->oscillation_id_.has_value()) { + TuyaDatapoint datapoint{}; + datapoint.id = *this->oscillation_id_; + datapoint.type = TuyaDatapointType::BOOLEAN; + datapoint.value_bool = this->fan_->oscillating; + this->parent_->set_datapoint_value(datapoint); + ESP_LOGD(TAG, "Setting oscillating: %s", ONOFF(this->fan_->oscillating)); + } + if (this->speed_id_.has_value()) { + 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; + ESP_LOGD(TAG, "Setting speed: %d", datapoint.value_enum); + this->parent_->set_datapoint_value(datapoint); + } +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/fan/tuya_fan.h b/esphome/components/tuya/fan/tuya_fan.h new file mode 100644 index 0000000000..d31d490e1a --- /dev/null +++ b/esphome/components/tuya/fan/tuya_fan.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/fan/fan_state.h" + +namespace esphome { +namespace tuya { + +class TuyaFan : public Component { + public: + void setup() 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_fan(fan::FanState *fan) { this->fan_ = fan; } + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + void write_state(); + + protected: + void update_speed_(uint32_t value); + void update_switch_(uint32_t value); + void update_oscillation_(uint32_t value); + + Tuya *parent_; + optional speed_id_{}; + optional switch_id_{}; + optional oscillation_id_{}; + fan::FanState *fan_; +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/light/__init__.py b/esphome/components/tuya/light/__init__.py index 605bdae32e..adaeb52531 100644 --- a/esphome/components/tuya/light/__init__.py +++ b/esphome/components/tuya/light/__init__.py @@ -12,10 +12,10 @@ CONF_SWITCH_DATAPOINT = "switch_datapoint" TuyaLight = tuya_ns.class_('TuyaLight', light.LightOutput, cg.Component) -CONFIG_SCHEMA = light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend({ +CONFIG_SCHEMA = cv.All(light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend({ cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaLight), cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), - cv.Required(CONF_DIMMER_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_DIMMER_DATAPOINT): cv.uint8_t, cv.Optional(CONF_SWITCH_DATAPOINT): cv.uint8_t, cv.Optional(CONF_MIN_VALUE): cv.int_, cv.Optional(CONF_MAX_VALUE): cv.int_, @@ -24,7 +24,8 @@ CONFIG_SCHEMA = light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend({ # The Tuya MCU handles transitions and gamma correction on its own. cv.Optional(CONF_GAMMA_CORRECT, default=1.0): cv.positive_float, cv.Optional(CONF_DEFAULT_TRANSITION_LENGTH, default='0s'): cv.positive_time_period_milliseconds, -}).extend(cv.COMPONENT_SCHEMA) +}).extend(cv.COMPONENT_SCHEMA), cv.has_at_least_one_key(CONF_DIMMER_DATAPOINT, + CONF_SWITCH_DATAPOINT)) def to_code(config):