diff --git a/esphome/components/tuya/cover/__init__.py b/esphome/components/tuya/cover/__init__.py new file mode 100644 index 0000000000..7816f39bf8 --- /dev/null +++ b/esphome/components/tuya/cover/__init__.py @@ -0,0 +1,54 @@ +from esphome.components import cover +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import ( + CONF_OUTPUT_ID, + CONF_MIN_VALUE, + CONF_MAX_VALUE, +) +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ["tuya"] + +CONF_POSITION_DATAPOINT = "position_datapoint" +CONF_INVERT_POSITION = "invert_position" + +TuyaCover = tuya_ns.class_("TuyaCover", cover.Cover, cg.Component) + + +def validate_range(config): + if config[CONF_MIN_VALUE] > config[CONF_MAX_VALUE]: + raise cv.Invalid( + "min_value({}) cannot be greater than max_value({})".format( + config[CONF_MIN_VALUE], config[CONF_MAX_VALUE] + ) + ) + return config + + +CONFIG_SCHEMA = cv.All( + cover.COVER_SCHEMA.extend( + { + cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(TuyaCover), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_POSITION_DATAPOINT): cv.uint8_t, + cv.Optional(CONF_MIN_VALUE, default=0): cv.int_, + cv.Optional(CONF_MAX_VALUE, default=100): cv.int_, + cv.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + }, + ).extend(cv.COMPONENT_SCHEMA), + validate_range, +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) + await cg.register_component(var, config) + await cover.register_cover(var, config) + + cg.add(var.set_position_id(config[CONF_POSITION_DATAPOINT])) + cg.add(var.set_min_value(config[CONF_MIN_VALUE])) + cg.add(var.set_max_value(config[CONF_MAX_VALUE])) + cg.add(var.set_invert_position(config[CONF_INVERT_POSITION])) + paren = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(paren)) diff --git a/esphome/components/tuya/cover/tuya_cover.cpp b/esphome/components/tuya/cover/tuya_cover.cpp new file mode 100644 index 0000000000..7da1312938 --- /dev/null +++ b/esphome/components/tuya/cover/tuya_cover.cpp @@ -0,0 +1,58 @@ +#include "esphome/core/log.h" +#include "tuya_cover.h" + +namespace esphome { +namespace tuya { + +static const char *const TAG = "tuya.cover"; + +void TuyaCover::setup() { + this->value_range_ = this->max_value_ - this->min_value_; + if (this->position_id_.has_value()) { + this->parent_->register_listener(*this->position_id_, [this](const TuyaDatapoint &datapoint) { + auto pos = float(datapoint.value_uint - this->min_value_) / this->value_range_; + if (this->invert_position_) + pos = 1.0f - pos; + this->position = pos; + this->publish_state(); + }); + } +} + +void TuyaCover::control(const cover::CoverCall &call) { + if (call.get_stop()) { + auto pos = this->position; + if (this->invert_position_) + pos = 1.0f - pos; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; + + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } + if (call.get_position().has_value()) { + auto pos = *call.get_position(); + if (this->invert_position_) + pos = 1.0f - pos; + auto position_int = static_cast(pos * this->value_range_); + position_int = position_int + this->min_value_; + + parent_->set_integer_datapoint_value(*this->position_id_, position_int); + } + + this->publish_state(); +} + +void TuyaCover::dump_config() { + ESP_LOGCONFIG(TAG, "Tuya Cover:"); + if (this->position_id_.has_value()) + ESP_LOGCONFIG(TAG, " Position has datapoint ID %u", *this->position_id_); +} + +cover::CoverTraits TuyaCover::get_traits() { + auto traits = cover::CoverTraits(); + traits.set_supports_position(true); + return traits; +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/cover/tuya_cover.h b/esphome/components/tuya/cover/tuya_cover.h new file mode 100644 index 0000000000..b62e58dc1b --- /dev/null +++ b/esphome/components/tuya/cover/tuya_cover.h @@ -0,0 +1,33 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/cover/cover.h" + +namespace esphome { +namespace tuya { + +class TuyaCover : public cover::Cover, public Component { + public: + void setup() override; + void dump_config() override; + void set_position_id(uint8_t dimmer_id) { this->position_id_ = dimmer_id; } + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + void set_min_value(uint32_t min_value) { min_value_ = min_value; } + void set_max_value(uint32_t max_value) { max_value_ = max_value; } + void set_invert_position(bool invert_position) { invert_position_ = invert_position; } + + protected: + void control(const cover::CoverCall &call) override; + cover::CoverTraits get_traits() override; + + Tuya *parent_; + optional position_id_{}; + uint32_t min_value_ = 0; + uint32_t max_value_ = 100; + uint32_t value_range_; + bool invert_position_ = false; +}; + +} // namespace tuya +} // namespace esphome diff --git a/tests/test4.yaml b/tests/test4.yaml index a205f0802e..4f2025ad74 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -358,6 +358,11 @@ light: warm_white_color_temperature: 500 mireds gamma_correct: 1 +cover: + - platform: tuya + id: tuya_cover + position_datapoint: 2 + display: - platform: addressable_light id: led_matrix_32x8_display