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; | ||||
|  | ||||
| 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<uint8_t>{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<uint8_t>{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<uint8_t>{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<uint8_t>{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<uint8_t>{0x04}}); | ||||
| } | ||||
|  | ||||
| void Tuya::set_datapoint_value(TuyaDatapoint datapoint) { | ||||
|   std::vector<uint8_t> 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<void(TuyaDatapoint)> &func) { | ||||
|   | ||||
| @@ -61,6 +61,11 @@ enum class TuyaInitState : uint8_t { | ||||
|   INIT_DONE, | ||||
| }; | ||||
|  | ||||
| struct TuyaCommand { | ||||
|   TuyaCommandType cmd; | ||||
|   std::vector<uint8_t> 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::RealTimeClock *> time_id_{}; | ||||
| @@ -98,6 +104,7 @@ class Tuya : public Component, public uart::UARTDevice { | ||||
|   std::vector<TuyaDatapoint> datapoints_; | ||||
|   std::vector<uint8_t> rx_message_; | ||||
|   std::vector<uint8_t> ignore_mcu_update_on_datapoints_{}; | ||||
|   std::vector<TuyaCommand> command_queue_; | ||||
| }; | ||||
|  | ||||
| }  // namespace tuya | ||||
|   | ||||
		Reference in New Issue
	
	Block a user