From 87154e9b6f7fa35a715353e69465c045d89748e2 Mon Sep 17 00:00:00 2001 From: stubs12 <43162406+stubs12@users.noreply.github.com> Date: Thu, 25 Feb 2021 17:52:40 -0600 Subject: [PATCH] Tuya: Use queue for sending command messages (#1404) --- esphome/components/tuya/tuya.cpp | 116 ++++++++++++++++--------------- esphome/components/tuya/tuya.h | 13 +++- 2 files changed, 71 insertions(+), 58 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 75514dde19..f4a72e8109 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -9,7 +9,7 @@ static const char *TAG = "tuya"; static const int COMMAND_DELAY = 50; void Tuya::setup() { - this->set_interval("heartbeat", 1000, [this] { this->schedule_empty_command_(TuyaCommandType::HEARTBEAT); }); + this->set_interval("heartbeat", 1000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); }); } void Tuya::loop() { @@ -18,15 +18,7 @@ void Tuya::loop() { this->read_byte(&c); this->handle_char_(c); } -} - -void Tuya::schedule_empty_command_(TuyaCommandType command) { - uint32_t delay = millis() - this->last_command_timestamp_; - if (delay > COMMAND_DELAY) { - send_empty_command_(command); - } else { - this->set_timeout(COMMAND_DELAY - delay, [this, command] { this->send_empty_command_(command); }); - } + process_command_queue_(); } void Tuya::dump_config() { @@ -122,7 +114,6 @@ void Tuya::handle_char_(uint8_t c) { } void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) { - this->last_command_timestamp_ = millis(); switch ((TuyaCommandType) command) { case TuyaCommandType::HEARTBEAT: ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]); @@ -132,7 +123,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff } if (this->init_state_ == TuyaInitState::INIT_HEARTBEAT) { this->init_state_ = TuyaInitState::INIT_PRODUCT; - this->schedule_empty_command_(TuyaCommandType::PRODUCT_QUERY); + this->send_empty_command_(TuyaCommandType::PRODUCT_QUERY); } break; case TuyaCommandType::PRODUCT_QUERY: { @@ -151,7 +142,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff } if (this->init_state_ == TuyaInitState::INIT_PRODUCT) { this->init_state_ = TuyaInitState::INIT_CONF; - this->schedule_empty_command_(TuyaCommandType::CONF_QUERY); + this->send_empty_command_(TuyaCommandType::CONF_QUERY); } break; } @@ -164,16 +155,13 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff // If mcu returned status gpio, then we can ommit sending wifi state if (this->gpio_status_ != -1) { this->init_state_ = TuyaInitState::INIT_DATAPOINT; - this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY); + this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY); } else { this->init_state_ = TuyaInitState::INIT_WIFI; - this->set_timeout(COMMAND_DELAY, [this] { - // If we were following the spec to the letter we would send - // state updates until connected to both WiFi and API/MQTT. - // Instead we just claim to be connected immediately and move on. - uint8_t c[] = {0x04}; - this->send_command_(TuyaCommandType::WIFI_STATE, c, 1); - }); + // If we were following the spec to the letter we would send + // state updates until connected to both WiFi and API/MQTT. + // Instead we just claim to be connected immediately and move on. + this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_STATE, .payload = std::vector{0x04}}); } } break; @@ -181,7 +169,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff case TuyaCommandType::WIFI_STATE: if (this->init_state_ == TuyaInitState::INIT_WIFI) { this->init_state_ = TuyaInitState::INIT_DATAPOINT; - this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY); + this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY); } break; case TuyaCommandType::WIFI_RESET: @@ -202,8 +190,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff case TuyaCommandType::DATAPOINT_QUERY: break; case TuyaCommandType::WIFI_TEST: { - uint8_t c[] = {0x00, 0x00}; - this->send_command_(TuyaCommandType::WIFI_TEST, c, 2); + this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_TEST, .payload = std::vector{0x00, 0x00}}); break; } case TuyaCommandType::LOCAL_TIME_QUERY: { @@ -213,28 +200,26 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff auto now = time_id->now(); if (now.is_valid()) { - this->set_timeout(COMMAND_DELAY, [this, now] { - uint8_t year = now.year - 2000; - uint8_t month = now.month; - uint8_t day_of_month = now.day_of_month; - uint8_t hour = now.hour; - uint8_t minute = now.minute; - uint8_t second = now.second; - // Tuya days starts from Monday, esphome uses Sunday as day 1 - uint8_t day_of_week = now.day_of_week - 1; - if (day_of_week == 0) { - day_of_week = 7; - } - uint8_t c[] = {0x01, year, month, day_of_month, hour, minute, second, day_of_week}; - this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8); - }); + uint8_t year = now.year - 2000; + uint8_t month = now.month; + uint8_t day_of_month = now.day_of_month; + uint8_t hour = now.hour; + uint8_t minute = now.minute; + uint8_t second = now.second; + // Tuya days starts from Monday, esphome uses Sunday as day 1 + uint8_t day_of_week = now.day_of_week - 1; + if (day_of_week == 0) { + day_of_week = 7; + } + this->send_command_(TuyaCommand{ + .cmd = TuyaCommandType::LOCAL_TIME_QUERY, + .payload = std::vector{0x01, year, month, day_of_month, hour, minute, second, day_of_week}}); } else { ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not valid"); // By spec we need to notify MCU that the time was not obtained - this->set_timeout(COMMAND_DELAY, [this] { - uint8_t c[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8); - }); + this->send_command_( + TuyaCommand{.cmd = TuyaCommandType::LOCAL_TIME_QUERY, + .payload = std::vector{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}); } } else { ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not configured"); @@ -321,24 +306,44 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { listener.on_datapoint(datapoint); } -void Tuya::send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len) { - uint8_t len_hi = len >> 8; - uint8_t len_lo = len >> 0; +void Tuya::send_raw_command_(TuyaCommand command) { + uint8_t len_hi = (uint8_t)(command.payload.size() >> 8); + uint8_t len_lo = (uint8_t)(command.payload.size() & 0xFF); uint8_t version = 0; - ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version, // NOLINT - hexencode(buffer, len).c_str(), this->init_state_); + this->last_command_timestamp_ = millis(); - this->write_array({0x55, 0xAA, version, (uint8_t) command, len_hi, len_lo}); - if (len != 0) - this->write_array(buffer, len); + ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command.cmd, version, // NOLINT + hexencode(command.payload).c_str(), this->init_state_); - uint8_t checksum = 0x55 + 0xAA + (uint8_t) command + len_hi + len_lo; - for (int i = 0; i < len; i++) - checksum += buffer[i]; + this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo}); + if (!command.payload.empty()) + this->write_array(command.payload.data(), command.payload.size()); + + uint8_t checksum = 0x55 + 0xAA + (uint8_t) command.cmd + len_hi + len_lo; + for (auto &data : command.payload) + checksum += data; this->write_byte(checksum); } +void Tuya::process_command_queue_() { + uint32_t delay = millis() - this->last_command_timestamp_; + // Left check of delay since last command in case theres ever a command sent by calling send_raw_command_ directly + if (delay > COMMAND_DELAY && !command_queue_.empty()) { + this->send_raw_command_(command_queue_.front()); + this->command_queue_.erase(command_queue_.begin()); + } +} + +void Tuya::send_command_(TuyaCommand command) { + command_queue_.push_back(command); + process_command_queue_(); +} + +void Tuya::send_empty_command_(TuyaCommandType command) { + send_command_(TuyaCommand{.cmd = command, .payload = std::vector{0x04}}); +} + void Tuya::set_datapoint_value(TuyaDatapoint datapoint) { std::vector buffer; ESP_LOGV(TAG, "Datapoint %u set to %u", datapoint.id, datapoint.value_uint); @@ -389,7 +394,8 @@ void Tuya::set_datapoint_value(TuyaDatapoint datapoint) { buffer.push_back(data.size() >> 8); buffer.push_back(data.size() >> 0); buffer.insert(buffer.end(), data.begin(), data.end()); - this->send_command_(TuyaCommandType::DATAPOINT_DELIVER, buffer.data(), buffer.size()); + + this->send_command_(TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_DELIVER, .payload = buffer}); } void Tuya::register_listener(uint8_t datapoint_id, const std::function &func) { diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index ddbbb48edf..a2b4040eb3 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -61,6 +61,11 @@ enum class TuyaInitState : uint8_t { INIT_DONE, }; +struct TuyaCommand { + TuyaCommandType cmd; + std::vector payload; +}; + class Tuya : public Component, public uart::UARTDevice { public: float get_setup_priority() const override { return setup_priority::LATE; } @@ -82,9 +87,10 @@ class Tuya : public Component, public uart::UARTDevice { bool validate_message_(); void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len); - void send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len); - void send_empty_command_(TuyaCommandType command) { this->send_command_(command, nullptr, 0); } - void schedule_empty_command_(TuyaCommandType command); + void send_raw_command_(TuyaCommand command); + void process_command_queue_(); + void send_command_(TuyaCommand command); + void send_empty_command_(TuyaCommandType command); #ifdef USE_TIME optional time_id_{}; @@ -98,6 +104,7 @@ class Tuya : public Component, public uart::UARTDevice { std::vector datapoints_; std::vector rx_message_; std::vector ignore_mcu_update_on_datapoints_{}; + std::vector command_queue_; }; } // namespace tuya