From f62d5d3b9d150891a4f80ed9ea24b3a65e6c2981 Mon Sep 17 00:00:00 2001 From: Maxim Ocheretianko Date: Sun, 15 May 2022 22:49:40 +0300 Subject: [PATCH] Add Tuya select (#3469) --- CODEOWNERS | 1 + esphome/components/tuya/select/__init__.py | 47 +++++++++++++++++ .../components/tuya/select/tuya_select.cpp | 52 +++++++++++++++++++ esphome/components/tuya/select/tuya_select.h | 30 +++++++++++ esphome/const.py | 1 + tests/test4.yaml | 9 ++++ 6 files changed, 140 insertions(+) create mode 100644 esphome/components/tuya/select/__init__.py create mode 100644 esphome/components/tuya/select/tuya_select.cpp create mode 100644 esphome/components/tuya/select/tuya_select.h diff --git a/CODEOWNERS b/CODEOWNERS index e2b29547cb..be6e8be3f7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -222,6 +222,7 @@ esphome/components/tsl2591/* @wjcarpenter esphome/components/tuya/binary_sensor/* @jesserockz esphome/components/tuya/climate/* @jesserockz esphome/components/tuya/number/* @frankiboy1 +esphome/components/tuya/select/* @bearpawmaxim esphome/components/tuya/sensor/* @jesserockz esphome/components/tuya/switch/* @jesserockz esphome/components/tuya/text_sensor/* @dentra diff --git a/esphome/components/tuya/select/__init__.py b/esphome/components/tuya/select/__init__.py new file mode 100644 index 0000000000..3d65eda301 --- /dev/null +++ b/esphome/components/tuya/select/__init__.py @@ -0,0 +1,47 @@ +from esphome.components import select +import esphome.config_validation as cv +import esphome.codegen as cg +from esphome.const import CONF_OPTIONS, CONF_OPTIMISTIC, CONF_ENUM_DATAPOINT +from .. import tuya_ns, CONF_TUYA_ID, Tuya + +DEPENDENCIES = ["tuya"] +CODEOWNERS = ["@bearpawmaxim"] + +TuyaSelect = tuya_ns.class_("TuyaSelect", select.Select, cg.Component) + + +def ensure_option_map(value): + cv.check_not_templatable(value) + option = cv.All(cv.int_range(0, 2**8 - 1)) + mapping = cv.All(cv.string_strict) + options_map_schema = cv.Schema({option: mapping}) + value = options_map_schema(value) + + all_values = list(value.keys()) + unique_values = set(value.keys()) + if len(all_values) != len(unique_values): + raise cv.Invalid("Mapping values must be unique.") + + return value + + +CONFIG_SCHEMA = select.SELECT_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(TuyaSelect), + cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya), + cv.Required(CONF_ENUM_DATAPOINT): cv.uint8_t, + cv.Required(CONF_OPTIONS): ensure_option_map, + cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean, + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + options_map = config[CONF_OPTIONS] + var = await select.new_select(config, options=list(options_map.values())) + await cg.register_component(var, config) + cg.add(var.set_select_mappings(list(options_map.keys()))) + parent = await cg.get_variable(config[CONF_TUYA_ID]) + cg.add(var.set_tuya_parent(parent)) + cg.add(var.set_select_id(config[CONF_ENUM_DATAPOINT])) + cg.add(var.set_optimistic(config[CONF_OPTIMISTIC])) diff --git a/esphome/components/tuya/select/tuya_select.cpp b/esphome/components/tuya/select/tuya_select.cpp new file mode 100644 index 0000000000..a4df0873b0 --- /dev/null +++ b/esphome/components/tuya/select/tuya_select.cpp @@ -0,0 +1,52 @@ +#include "esphome/core/log.h" +#include "tuya_select.h" + +namespace esphome { +namespace tuya { + +static const char *const TAG = "tuya.select"; + +void TuyaSelect::setup() { + this->parent_->register_listener(this->select_id_, [this](const TuyaDatapoint &datapoint) { + uint8_t enum_value = datapoint.value_enum; + ESP_LOGV(TAG, "MCU reported select %u value %u", this->select_id_, enum_value); + auto options = this->traits.get_options(); + auto mappings = this->mappings_; + auto it = std::find(mappings.cbegin(), mappings.cend(), enum_value); + if (it == mappings.end()) { + ESP_LOGW(TAG, "Invalid value %u", enum_value); + return; + } + size_t mapping_idx = std::distance(mappings.cbegin(), it); + auto value = this->at(mapping_idx); + this->publish_state(value.value()); + }); +} + +void TuyaSelect::control(const std::string &value) { + if (this->optimistic_) + this->publish_state(value); + + auto idx = this->index_of(value); + if (idx.has_value()) { + uint8_t mapping = this->mappings_.at(idx.value()); + ESP_LOGV(TAG, "Setting %u datapoint value to %u:%s", this->select_id_, mapping, value.c_str()); + this->parent_->set_enum_datapoint_value(this->select_id_, mapping); + return; + } + + ESP_LOGW(TAG, "Invalid value %s", value.c_str()); +} + +void TuyaSelect::dump_config() { + LOG_SELECT("", "Tuya Select", this); + ESP_LOGCONFIG(TAG, " Select has datapoint ID %u", this->select_id_); + ESP_LOGCONFIG(TAG, " Options are:"); + auto options = this->traits.get_options(); + for (auto i = 0; i < this->mappings_.size(); i++) { + ESP_LOGCONFIG(TAG, " %i: %s", this->mappings_.at(i), options.at(i).c_str()); + } +} + +} // namespace tuya +} // namespace esphome diff --git a/esphome/components/tuya/select/tuya_select.h b/esphome/components/tuya/select/tuya_select.h new file mode 100644 index 0000000000..ab233dc501 --- /dev/null +++ b/esphome/components/tuya/select/tuya_select.h @@ -0,0 +1,30 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/tuya/tuya.h" +#include "esphome/components/select/select.h" + +namespace esphome { +namespace tuya { + +class TuyaSelect : public select::Select, public Component { + public: + void setup() override; + void dump_config() override; + + void set_tuya_parent(Tuya *parent) { this->parent_ = parent; } + void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } + void set_select_id(uint8_t select_id) { this->select_id_ = select_id; } + void set_select_mappings(std::vector mappings) { this->mappings_ = std::move(mappings); } + + protected: + void control(const std::string &value) override; + + Tuya *parent_; + bool optimistic_ = false; + uint8_t select_id_; + std::vector mappings_; +}; + +} // namespace tuya +} // namespace esphome diff --git a/esphome/const.py b/esphome/const.py index 9a2e41d69f..c2aa53be70 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -198,6 +198,7 @@ CONF_ENABLE_TIME = "enable_time" CONF_ENERGY = "energy" CONF_ENTITY_CATEGORY = "entity_category" CONF_ENTITY_ID = "entity_id" +CONF_ENUM_DATAPOINT = "enum_datapoint" CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support" CONF_ESPHOME = "esphome" CONF_ETHERNET = "ethernet" diff --git a/tests/test4.yaml b/tests/test4.yaml index 82bb9e2f85..0e9f14e0d6 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -61,6 +61,15 @@ tuya: number: 14 inverted: true +select: + - platform: tuya + id: tuya_select + enum_datapoint: 42 + options: + 0: Internal + 1: Floor + 2: Both + pipsolar: id: inverter0