From 0665acd1901f8ea1b644d89ab9933d032b288ba7 Mon Sep 17 00:00:00 2001 From: Maxim Ocheretianko Date: Sun, 15 May 2022 22:44:14 +0300 Subject: [PATCH] Tuya status gpio support (#3466) --- esphome/components/tuya/__init__.py | 6 +++++ esphome/components/tuya/tuya.cpp | 41 ++++++++++++++++++++++------- esphome/components/tuya/tuya.h | 7 +++-- tests/test4.yaml | 3 +++ 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/esphome/components/tuya/__init__.py b/esphome/components/tuya/__init__.py index 965893e012..2eaaa2a625 100644 --- a/esphome/components/tuya/__init__.py +++ b/esphome/components/tuya/__init__.py @@ -1,5 +1,6 @@ from esphome.components import time from esphome import automation +from esphome import pins import esphome.codegen as cg import esphome.config_validation as cv from esphome.components import uart @@ -11,6 +12,7 @@ CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS = "ignore_mcu_update_on_datapoints" CONF_ON_DATAPOINT_UPDATE = "on_datapoint_update" CONF_DATAPOINT_TYPE = "datapoint_type" +CONF_STATUS_PIN = "status_pin" tuya_ns = cg.esphome_ns.namespace("tuya") Tuya = tuya_ns.class_("Tuya", cg.Component, uart.UARTDevice) @@ -88,6 +90,7 @@ CONFIG_SCHEMA = ( cv.Optional(CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS): cv.ensure_list( cv.uint8_t ), + cv.Optional(CONF_STATUS_PIN): pins.gpio_output_pin_schema, cv.Optional(CONF_ON_DATAPOINT_UPDATE): automation.validate_automation( { cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id( @@ -114,6 +117,9 @@ async def to_code(config): if CONF_TIME_ID in config: time_ = await cg.get_variable(config[CONF_TIME_ID]) cg.add(var.set_time_id(time_)) + if CONF_STATUS_PIN in config: + status_pin_ = await cg.gpio_pin_expression(config[CONF_STATUS_PIN]) + cg.add(var.set_status_pin(status_pin_)) if CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS in config: for dp in config[CONF_IGNORE_MCU_UPDATE_ON_DATAPOINTS]: cg.add(var.add_ignore_mcu_update_on_datapoints(dp)) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 1fbca7796d..1b35121c57 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -3,6 +3,7 @@ #include "esphome/components/network/util.h" #include "esphome/core/helpers.h" #include "esphome/core/util.h" +#include "esphome/core/gpio.h" namespace esphome { namespace tuya { @@ -13,6 +14,9 @@ static const int RECEIVE_TIMEOUT = 300; void Tuya::setup() { this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); + if (this->status_pin_.has_value()) { + this->status_pin_.value()->digital_write(false); + } } void Tuya::loop() { @@ -49,9 +53,12 @@ void Tuya::dump_config() { ESP_LOGCONFIG(TAG, " Datapoint %u: unknown", info.id); } } - if ((this->gpio_status_ != -1) || (this->gpio_reset_ != -1)) { - ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d (not supported)", this->gpio_status_, - this->gpio_reset_); + if ((this->status_pin_reported_ != -1) || (this->reset_pin_reported_ != -1)) { + ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d (not supported)", + this->status_pin_reported_, this->reset_pin_reported_); + } + if (this->status_pin_.has_value()) { + LOG_PIN(" Status Pin: ", this->status_pin_.value()); } ESP_LOGCONFIG(TAG, " Product: '%s'", this->product_.c_str()); this->check_uart_settings(9600); @@ -164,16 +171,27 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff } case TuyaCommandType::CONF_QUERY: { if (len >= 2) { - this->gpio_status_ = buffer[0]; - this->gpio_reset_ = buffer[1]; + this->status_pin_reported_ = buffer[0]; + this->reset_pin_reported_ = buffer[1]; } if (this->init_state_ == TuyaInitState::INIT_CONF) { // If mcu returned status gpio, then we can omit sending wifi state - if (this->gpio_status_ != -1) { + if (this->status_pin_reported_ != -1) { this->init_state_ = TuyaInitState::INIT_DATAPOINT; this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY); + bool is_pin_equals = + this->status_pin_.has_value() && this->status_pin_.value()->get_pin() == this->status_pin_reported_; + // Configure status pin toggling (if reported and configured) or WIFI_STATE periodic send + if (is_pin_equals) { + ESP_LOGV(TAG, "Configured status pin %i", this->status_pin_reported_); + this->set_interval("wifi", 1000, [this] { this->set_status_pin_(); }); + } else { + ESP_LOGW(TAG, "Supplied status_pin does not equals the reported pin %i. TuyaMcu will work in limited mode.", + this->status_pin_reported_); + } } else { this->init_state_ = TuyaInitState::INIT_WIFI; + ESP_LOGV(TAG, "Configured WIFI_STATE periodic send"); this->set_interval("wifi", 1000, [this] { this->send_wifi_status_(); }); } } @@ -397,16 +415,19 @@ void Tuya::send_empty_command_(TuyaCommandType command) { send_command_(TuyaCommand{.cmd = command, .payload = std::vector{}}); } +void Tuya::set_status_pin_() { + bool is_network_ready = network::is_connected() && remote_is_connected(); + this->status_pin_.value()->digital_write(is_network_ready); +} + void Tuya::send_wifi_status_() { uint8_t status = 0x02; if (network::is_connected()) { status = 0x03; // Protocol version 3 also supports specifying when connected to "the cloud" - if (this->protocol_version_ >= 0x03) { - if (remote_is_connected()) { - status = 0x04; - } + if (this->protocol_version_ >= 0x03 && remote_is_connected()) { + status = 0x04; } } diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 3828c49b48..3a267d75a7 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -79,6 +79,7 @@ class Tuya : public Component, public uart::UARTDevice { void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector &value); void set_boolean_datapoint_value(uint8_t datapoint_id, bool value); void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value); + void set_status_pin(InternalGPIOPin *status_pin) { this->status_pin_ = status_pin; } void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value); void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value); void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length); @@ -115,6 +116,7 @@ class Tuya : public Component, public uart::UARTDevice { void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced); void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced); void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector data); + void set_status_pin_(); void send_wifi_status_(); #ifdef USE_TIME @@ -123,8 +125,9 @@ class Tuya : public Component, public uart::UARTDevice { #endif TuyaInitState init_state_ = TuyaInitState::INIT_HEARTBEAT; uint8_t protocol_version_ = -1; - int gpio_status_ = -1; - int gpio_reset_ = -1; + optional status_pin_{}; + int status_pin_reported_ = -1; + int reset_pin_reported_ = -1; uint32_t last_command_timestamp_ = 0; uint32_t last_rx_char_timestamp_ = 0; std::string product_ = ""; diff --git a/tests/test4.yaml b/tests/test4.yaml index 54412222b5..82bb9e2f85 100644 --- a/tests/test4.yaml +++ b/tests/test4.yaml @@ -57,6 +57,9 @@ time: tuya: time_id: sntp_time + status_pin: + number: 14 + inverted: true pipsolar: id: inverter0