mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Tuya: Use queue for sending command messages (#1404)
This commit is contained in:
		| @@ -9,7 +9,7 @@ static const char *TAG = "tuya"; | |||||||
| static const int COMMAND_DELAY = 50; | static const int COMMAND_DELAY = 50; | ||||||
|  |  | ||||||
| void Tuya::setup() { | 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() { | void Tuya::loop() { | ||||||
| @@ -18,15 +18,7 @@ void Tuya::loop() { | |||||||
|     this->read_byte(&c); |     this->read_byte(&c); | ||||||
|     this->handle_char_(c); |     this->handle_char_(c); | ||||||
|   } |   } | ||||||
| } |   process_command_queue_(); | ||||||
|  |  | ||||||
| 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); }); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tuya::dump_config() { | 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) { | 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) { |   switch ((TuyaCommandType) command) { | ||||||
|     case TuyaCommandType::HEARTBEAT: |     case TuyaCommandType::HEARTBEAT: | ||||||
|       ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]); |       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) { |       if (this->init_state_ == TuyaInitState::INIT_HEARTBEAT) { | ||||||
|         this->init_state_ = TuyaInitState::INIT_PRODUCT; |         this->init_state_ = TuyaInitState::INIT_PRODUCT; | ||||||
|         this->schedule_empty_command_(TuyaCommandType::PRODUCT_QUERY); |         this->send_empty_command_(TuyaCommandType::PRODUCT_QUERY); | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case TuyaCommandType::PRODUCT_QUERY: { |     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) { |       if (this->init_state_ == TuyaInitState::INIT_PRODUCT) { | ||||||
|         this->init_state_ = TuyaInitState::INIT_CONF; |         this->init_state_ = TuyaInitState::INIT_CONF; | ||||||
|         this->schedule_empty_command_(TuyaCommandType::CONF_QUERY); |         this->send_empty_command_(TuyaCommandType::CONF_QUERY); | ||||||
|       } |       } | ||||||
|       break; |       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 mcu returned status gpio, then we can ommit sending wifi state | ||||||
|         if (this->gpio_status_ != -1) { |         if (this->gpio_status_ != -1) { | ||||||
|           this->init_state_ = TuyaInitState::INIT_DATAPOINT; |           this->init_state_ = TuyaInitState::INIT_DATAPOINT; | ||||||
|           this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY); |           this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY); | ||||||
|         } else { |         } else { | ||||||
|           this->init_state_ = TuyaInitState::INIT_WIFI; |           this->init_state_ = TuyaInitState::INIT_WIFI; | ||||||
|           this->set_timeout(COMMAND_DELAY, [this] { |           // If we were following the spec to the letter we would send | ||||||
|             // If we were following the spec to the letter we would send |           // state updates until connected to both WiFi and API/MQTT. | ||||||
|             // state updates until connected to both WiFi and API/MQTT. |           // Instead we just claim to be connected immediately and move on. | ||||||
|             // Instead we just claim to be connected immediately and move on. |           this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_STATE, .payload = std::vector<uint8_t>{0x04}}); | ||||||
|             uint8_t c[] = {0x04}; |  | ||||||
|             this->send_command_(TuyaCommandType::WIFI_STATE, c, 1); |  | ||||||
|           }); |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
| @@ -181,7 +169,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff | |||||||
|     case TuyaCommandType::WIFI_STATE: |     case TuyaCommandType::WIFI_STATE: | ||||||
|       if (this->init_state_ == TuyaInitState::INIT_WIFI) { |       if (this->init_state_ == TuyaInitState::INIT_WIFI) { | ||||||
|         this->init_state_ = TuyaInitState::INIT_DATAPOINT; |         this->init_state_ = TuyaInitState::INIT_DATAPOINT; | ||||||
|         this->schedule_empty_command_(TuyaCommandType::DATAPOINT_QUERY); |         this->send_empty_command_(TuyaCommandType::DATAPOINT_QUERY); | ||||||
|       } |       } | ||||||
|       break; |       break; | ||||||
|     case TuyaCommandType::WIFI_RESET: |     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: |     case TuyaCommandType::DATAPOINT_QUERY: | ||||||
|       break; |       break; | ||||||
|     case TuyaCommandType::WIFI_TEST: { |     case TuyaCommandType::WIFI_TEST: { | ||||||
|       uint8_t c[] = {0x00, 0x00}; |       this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_TEST, .payload = std::vector<uint8_t>{0x00, 0x00}}); | ||||||
|       this->send_command_(TuyaCommandType::WIFI_TEST, c, 2); |  | ||||||
|       break; |       break; | ||||||
|     } |     } | ||||||
|     case TuyaCommandType::LOCAL_TIME_QUERY: { |     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(); |         auto now = time_id->now(); | ||||||
|  |  | ||||||
|         if (now.is_valid()) { |         if (now.is_valid()) { | ||||||
|           this->set_timeout(COMMAND_DELAY, [this, now] { |           uint8_t year = now.year - 2000; | ||||||
|             uint8_t year = now.year - 2000; |           uint8_t month = now.month; | ||||||
|             uint8_t month = now.month; |           uint8_t day_of_month = now.day_of_month; | ||||||
|             uint8_t day_of_month = now.day_of_month; |           uint8_t hour = now.hour; | ||||||
|             uint8_t hour = now.hour; |           uint8_t minute = now.minute; | ||||||
|             uint8_t minute = now.minute; |           uint8_t second = now.second; | ||||||
|             uint8_t second = now.second; |           // Tuya days starts from Monday, esphome uses Sunday as day 1 | ||||||
|             // Tuya days starts from Monday, esphome uses Sunday as day 1 |           uint8_t day_of_week = now.day_of_week - 1; | ||||||
|             uint8_t day_of_week = now.day_of_week - 1; |           if (day_of_week == 0) { | ||||||
|             if (day_of_week == 0) { |             day_of_week = 7; | ||||||
|               day_of_week = 7; |           } | ||||||
|             } |           this->send_command_(TuyaCommand{ | ||||||
|             uint8_t c[] = {0x01, year, month, day_of_month, hour, minute, second, day_of_week}; |               .cmd = TuyaCommandType::LOCAL_TIME_QUERY, | ||||||
|             this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8); |               .payload = std::vector<uint8_t>{0x01, year, month, day_of_month, hour, minute, second, day_of_week}}); | ||||||
|           }); |  | ||||||
|         } else { |         } else { | ||||||
|           ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not valid"); |           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 |           // By spec we need to notify MCU that the time was not obtained | ||||||
|           this->set_timeout(COMMAND_DELAY, [this] { |           this->send_command_( | ||||||
|             uint8_t c[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |               TuyaCommand{.cmd = TuyaCommandType::LOCAL_TIME_QUERY, | ||||||
|             this->send_command_(TuyaCommandType::LOCAL_TIME_QUERY, c, 8); |                           .payload = std::vector<uint8_t>{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}); | ||||||
|           }); |  | ||||||
|         } |         } | ||||||
|       } else { |       } else { | ||||||
|         ESP_LOGW(TAG, "TUYA_CMD_LOCAL_TIME_QUERY is not handled because time is not configured"); |         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); |       listener.on_datapoint(datapoint); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Tuya::send_command_(TuyaCommandType command, const uint8_t *buffer, uint16_t len) { | void Tuya::send_raw_command_(TuyaCommand command) { | ||||||
|   uint8_t len_hi = len >> 8; |   uint8_t len_hi = (uint8_t)(command.payload.size() >> 8); | ||||||
|   uint8_t len_lo = len >> 0; |   uint8_t len_lo = (uint8_t)(command.payload.size() & 0xFF); | ||||||
|   uint8_t version = 0; |   uint8_t version = 0; | ||||||
|  |  | ||||||
|   ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version,  // NOLINT |   this->last_command_timestamp_ = millis(); | ||||||
|            hexencode(buffer, len).c_str(), this->init_state_); |  | ||||||
|  |  | ||||||
|   this->write_array({0x55, 0xAA, version, (uint8_t) command, len_hi, len_lo}); |   ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command.cmd, version,  // NOLINT | ||||||
|   if (len != 0) |            hexencode(command.payload).c_str(), this->init_state_); | ||||||
|     this->write_array(buffer, len); |  | ||||||
|  |  | ||||||
|   uint8_t checksum = 0x55 + 0xAA + (uint8_t) command + len_hi + len_lo; |   this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo}); | ||||||
|   for (int i = 0; i < len; i++) |   if (!command.payload.empty()) | ||||||
|     checksum += buffer[i]; |     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); |   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<uint8_t>{0x04}}); | ||||||
|  | } | ||||||
|  |  | ||||||
| void Tuya::set_datapoint_value(TuyaDatapoint datapoint) { | void Tuya::set_datapoint_value(TuyaDatapoint datapoint) { | ||||||
|   std::vector<uint8_t> buffer; |   std::vector<uint8_t> buffer; | ||||||
|   ESP_LOGV(TAG, "Datapoint %u set to %u", datapoint.id, datapoint.value_uint); |   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() >> 8); | ||||||
|   buffer.push_back(data.size() >> 0); |   buffer.push_back(data.size() >> 0); | ||||||
|   buffer.insert(buffer.end(), data.begin(), data.end()); |   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<void(TuyaDatapoint)> &func) { | void Tuya::register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func) { | ||||||
|   | |||||||
| @@ -61,6 +61,11 @@ enum class TuyaInitState : uint8_t { | |||||||
|   INIT_DONE, |   INIT_DONE, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | struct TuyaCommand { | ||||||
|  |   TuyaCommandType cmd; | ||||||
|  |   std::vector<uint8_t> payload; | ||||||
|  | }; | ||||||
|  |  | ||||||
| class Tuya : public Component, public uart::UARTDevice { | class Tuya : public Component, public uart::UARTDevice { | ||||||
|  public: |  public: | ||||||
|   float get_setup_priority() const override { return setup_priority::LATE; } |   float get_setup_priority() const override { return setup_priority::LATE; } | ||||||
| @@ -82,9 +87,10 @@ class Tuya : public Component, public uart::UARTDevice { | |||||||
|   bool validate_message_(); |   bool validate_message_(); | ||||||
|  |  | ||||||
|   void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len); |   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_raw_command_(TuyaCommand command); | ||||||
|   void send_empty_command_(TuyaCommandType command) { this->send_command_(command, nullptr, 0); } |   void process_command_queue_(); | ||||||
|   void schedule_empty_command_(TuyaCommandType command); |   void send_command_(TuyaCommand command); | ||||||
|  |   void send_empty_command_(TuyaCommandType command); | ||||||
|  |  | ||||||
| #ifdef USE_TIME | #ifdef USE_TIME | ||||||
|   optional<time::RealTimeClock *> time_id_{}; |   optional<time::RealTimeClock *> time_id_{}; | ||||||
| @@ -98,6 +104,7 @@ class Tuya : public Component, public uart::UARTDevice { | |||||||
|   std::vector<TuyaDatapoint> datapoints_; |   std::vector<TuyaDatapoint> datapoints_; | ||||||
|   std::vector<uint8_t> rx_message_; |   std::vector<uint8_t> rx_message_; | ||||||
|   std::vector<uint8_t> ignore_mcu_update_on_datapoints_{}; |   std::vector<uint8_t> ignore_mcu_update_on_datapoints_{}; | ||||||
|  |   std::vector<TuyaCommand> command_queue_; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace tuya | }  // namespace tuya | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user