ESPHome  2024.12.2
tuya.cpp
Go to the documentation of this file.
1 #include "tuya.h"
3 #include "esphome/core/gpio.h"
4 #include "esphome/core/helpers.h"
5 #include "esphome/core/log.h"
6 #include "esphome/core/util.h"
7 
8 #ifdef USE_WIFI
10 #endif
11 
12 #ifdef USE_CAPTIVE_PORTAL
14 #endif
15 
16 namespace esphome {
17 namespace tuya {
18 
19 static const char *const TAG = "tuya";
20 static const int COMMAND_DELAY = 10;
21 static const int RECEIVE_TIMEOUT = 300;
22 static const int MAX_RETRIES = 5;
23 
24 void Tuya::setup() {
25  this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
26  if (this->status_pin_ != nullptr) {
27  this->status_pin_->digital_write(false);
28  }
29 }
30 
31 void Tuya::loop() {
32  while (this->available()) {
33  uint8_t c;
34  this->read_byte(&c);
35  this->handle_char_(c);
36  }
38 }
39 
41  ESP_LOGCONFIG(TAG, "Tuya:");
43  if (this->init_failed_) {
44  ESP_LOGCONFIG(TAG, " Initialization failed. Current init_state: %u", static_cast<uint8_t>(this->init_state_));
45  } else {
46  ESP_LOGCONFIG(TAG, " Configuration will be reported when setup is complete. Current init_state: %u",
47  static_cast<uint8_t>(this->init_state_));
48  }
49  ESP_LOGCONFIG(TAG, " If no further output is received, confirm that this is a supported Tuya device.");
50  return;
51  }
52  for (auto &info : this->datapoints_) {
53  if (info.type == TuyaDatapointType::RAW) {
54  ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str());
55  } else if (info.type == TuyaDatapointType::BOOLEAN) {
56  ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool));
57  } else if (info.type == TuyaDatapointType::INTEGER) {
58  ESP_LOGCONFIG(TAG, " Datapoint %u: int value (value: %d)", info.id, info.value_int);
59  } else if (info.type == TuyaDatapointType::STRING) {
60  ESP_LOGCONFIG(TAG, " Datapoint %u: string value (value: %s)", info.id, info.value_string.c_str());
61  } else if (info.type == TuyaDatapointType::ENUM) {
62  ESP_LOGCONFIG(TAG, " Datapoint %u: enum (value: %d)", info.id, info.value_enum);
63  } else if (info.type == TuyaDatapointType::BITMASK) {
64  ESP_LOGCONFIG(TAG, " Datapoint %u: bitmask (value: %" PRIx32 ")", info.id, info.value_bitmask);
65  } else {
66  ESP_LOGCONFIG(TAG, " Datapoint %u: unknown", info.id);
67  }
68  }
69  if ((this->status_pin_reported_ != -1) || (this->reset_pin_reported_ != -1)) {
70  ESP_LOGCONFIG(TAG, " GPIO Configuration: status: pin %d, reset: pin %d", this->status_pin_reported_,
71  this->reset_pin_reported_);
72  }
73  LOG_PIN(" Status Pin: ", this->status_pin_);
74  ESP_LOGCONFIG(TAG, " Product: '%s'", this->product_.c_str());
75 }
76 
78  uint32_t at = this->rx_message_.size() - 1;
79  auto *data = &this->rx_message_[0];
80  uint8_t new_byte = data[at];
81 
82  // Byte 0: HEADER1 (always 0x55)
83  if (at == 0)
84  return new_byte == 0x55;
85  // Byte 1: HEADER2 (always 0xAA)
86  if (at == 1)
87  return new_byte == 0xAA;
88 
89  // Byte 2: VERSION
90  // no validation for the following fields:
91  uint8_t version = data[2];
92  if (at == 2)
93  return true;
94  // Byte 3: COMMAND
95  uint8_t command = data[3];
96  if (at == 3)
97  return true;
98 
99  // Byte 4: LENGTH1
100  // Byte 5: LENGTH2
101  if (at <= 5) {
102  // no validation for these fields
103  return true;
104  }
105 
106  uint16_t length = (uint16_t(data[4]) << 8) | (uint16_t(data[5]));
107 
108  // wait until all data is read
109  if (at - 6 < length)
110  return true;
111 
112  // Byte 6+LEN: CHECKSUM - sum of all bytes (including header) modulo 256
113  uint8_t rx_checksum = new_byte;
114  uint8_t calc_checksum = 0;
115  for (uint32_t i = 0; i < 6 + length; i++)
116  calc_checksum += data[i];
117 
118  if (rx_checksum != calc_checksum) {
119  ESP_LOGW(TAG, "Tuya Received invalid message checksum %02X!=%02X", rx_checksum, calc_checksum);
120  return false;
121  }
122 
123  // valid message
124  const uint8_t *message_data = data + 6;
125  ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version,
126  format_hex_pretty(message_data, length).c_str(), static_cast<uint8_t>(this->init_state_));
127  this->handle_command_(command, version, message_data, length);
128 
129  // return false to reset rx buffer
130  return false;
131 }
132 
133 void Tuya::handle_char_(uint8_t c) {
134  this->rx_message_.push_back(c);
135  if (!this->validate_message_()) {
136  this->rx_message_.clear();
137  } else {
139  }
140 }
141 
142 void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) {
143  TuyaCommandType command_type = (TuyaCommandType) command;
144 
145  if (this->expected_response_.has_value() && this->expected_response_ == command_type) {
146  this->expected_response_.reset();
147  this->command_queue_.erase(command_queue_.begin());
148  this->init_retries_ = 0;
149  }
150 
151  switch (command_type) {
153  ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]);
154  this->protocol_version_ = version;
155  if (buffer[0] == 0) {
156  ESP_LOGI(TAG, "MCU restarted");
158  }
162  }
163  break;
165  // check it is a valid string made up of printable characters
166  bool valid = true;
167  for (size_t i = 0; i < len; i++) {
168  if (!std::isprint(buffer[i])) {
169  valid = false;
170  break;
171  }
172  }
173  if (valid) {
174  this->product_ = std::string(reinterpret_cast<const char *>(buffer), len);
175  } else {
176  this->product_ = R"({"p":"INVALID"})";
177  }
181  }
182  break;
183  }
185  if (len >= 2) {
186  this->status_pin_reported_ = buffer[0];
187  this->reset_pin_reported_ = buffer[1];
188  }
189  if (this->init_state_ == TuyaInitState::INIT_CONF) {
190  // If mcu returned status gpio, then we can omit sending wifi state
191  if (this->status_pin_reported_ != -1) {
194  bool is_pin_equals =
195  this->status_pin_ != nullptr && this->status_pin_->get_pin() == this->status_pin_reported_;
196  // Configure status pin toggling (if reported and configured) or WIFI_STATE periodic send
197  if (is_pin_equals) {
198  ESP_LOGV(TAG, "Configured status pin %i", this->status_pin_reported_);
199  this->set_interval("wifi", 1000, [this] { this->set_status_pin_(); });
200  } else {
201  ESP_LOGW(TAG, "Supplied status_pin does not equals the reported pin %i. TuyaMcu will work in limited mode.",
202  this->status_pin_reported_);
203  }
204  } else {
206  ESP_LOGV(TAG, "Configured WIFI_STATE periodic send");
207  this->set_interval("wifi", 1000, [this] { this->send_wifi_status_(); });
208  }
209  }
210  break;
211  }
213  if (this->init_state_ == TuyaInitState::INIT_WIFI) {
216  }
217  break;
219  ESP_LOGE(TAG, "WIFI_RESET is not handled");
220  break;
222  ESP_LOGE(TAG, "WIFI_SELECT is not handled");
223  break;
225  break;
230  this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); });
231  this->initialized_callback_.call();
232  }
233  this->handle_datapoints_(buffer, len);
234 
235  if (command_type == TuyaCommandType::DATAPOINT_REPORT_SYNC) {
236  this->send_command_(
237  TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_REPORT_ACK, .payload = std::vector<uint8_t>{0x01}});
238  }
239  break;
241  break;
243  this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_TEST, .payload = std::vector<uint8_t>{0x00, 0x00}});
244  break;
246  this->send_command_(
247  TuyaCommand{.cmd = TuyaCommandType::WIFI_RSSI, .payload = std::vector<uint8_t>{get_wifi_rssi_()}});
248  break;
250 #ifdef USE_TIME
251  if (this->time_id_ != nullptr) {
252  this->send_local_time_();
253 
254  if (!this->time_sync_callback_registered_) {
255  // tuya mcu supports time, so we let them know when our time changed
256  this->time_id_->add_on_time_sync_callback([this] { this->send_local_time_(); });
257  this->time_sync_callback_registered_ = true;
258  }
259  } else
260 #endif
261  {
262  ESP_LOGW(TAG, "LOCAL_TIME_QUERY is not handled because time is not configured");
263  }
264  break;
266  this->send_command_(
267  TuyaCommand{.cmd = TuyaCommandType::VACUUM_MAP_UPLOAD, .payload = std::vector<uint8_t>{0x01}});
268  ESP_LOGW(TAG, "Vacuum map upload requested, responding that it is not enabled.");
269  break;
271  uint8_t wifi_status = this->get_wifi_status_code_();
272 
273  this->send_command_(
274  TuyaCommand{.cmd = TuyaCommandType::GET_NETWORK_STATUS, .payload = std::vector<uint8_t>{wifi_status}});
275  ESP_LOGV(TAG, "Network status requested, reported as %i", wifi_status);
276  break;
277  }
279  uint8_t subcommand = buffer[0];
280  switch ((TuyaExtendedServicesCommandType) subcommand) {
282  this->send_command_(
284  .payload = std::vector<uint8_t>{
285  static_cast<uint8_t>(TuyaExtendedServicesCommandType::RESET_NOTIFICATION), 0x00}});
286  ESP_LOGV(TAG, "Reset status notification enabled");
287  break;
288  }
290  ESP_LOGE(TAG, "EXTENDED_SERVICES::MODULE_RESET is not handled");
291  break;
292  }
294  ESP_LOGE(TAG, "EXTENDED_SERVICES::UPDATE_IN_PROGRESS is not handled");
295  break;
296  }
297  default:
298  ESP_LOGE(TAG, "Invalid extended services subcommand (0x%02X) received", subcommand);
299  }
300  break;
301  }
302  default:
303  ESP_LOGE(TAG, "Invalid command (0x%02X) received", command);
304  }
305 }
306 
307 void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) {
308  while (len >= 4) {
309  TuyaDatapoint datapoint{};
310  datapoint.id = buffer[0];
311  datapoint.type = (TuyaDatapointType) buffer[1];
312  datapoint.value_uint = 0;
313 
314  size_t data_size = (buffer[2] << 8) + buffer[3];
315  const uint8_t *data = buffer + 4;
316  size_t data_len = len - 4;
317  if (data_size > data_len) {
318  ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu > %zu)", datapoint.id, data_size, data_len);
319  return;
320  }
321 
322  datapoint.len = data_size;
323 
324  switch (datapoint.type) {
326  datapoint.value_raw = std::vector<uint8_t>(data, data + data_size);
327  ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
328  break;
330  if (data_size != 1) {
331  ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_size);
332  return;
333  }
334  datapoint.value_bool = data[0];
335  ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool));
336  break;
338  if (data_size != 4) {
339  ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_size);
340  return;
341  }
342  datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]);
343  ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int);
344  break;
346  datapoint.value_string = std::string(reinterpret_cast<const char *>(data), data_size);
347  ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str());
348  break;
350  if (data_size != 1) {
351  ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_size);
352  return;
353  }
354  datapoint.value_enum = data[0];
355  ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum);
356  break;
358  switch (data_size) {
359  case 1:
360  datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]);
361  break;
362  case 2:
363  datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]);
364  break;
365  case 4:
366  datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]);
367  break;
368  default:
369  ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_size);
370  return;
371  }
372  ESP_LOGD(TAG, "Datapoint %u update to %#08" PRIX32, datapoint.id, datapoint.value_bitmask);
373  break;
374  default:
375  ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast<uint8_t>(datapoint.type));
376  return;
377  }
378 
379  len -= data_size + 4;
380  buffer = data + data_size;
381 
382  // drop update if datapoint is in ignore_mcu_datapoint_update list
383  bool skip = false;
384  for (auto i : this->ignore_mcu_update_on_datapoints_) {
385  if (datapoint.id == i) {
386  ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id);
387  skip = true;
388  break;
389  }
390  }
391  if (skip)
392  continue;
393 
394  // Update internal datapoints
395  bool found = false;
396  for (auto &other : this->datapoints_) {
397  if (other.id == datapoint.id) {
398  other = datapoint;
399  found = true;
400  }
401  }
402  if (!found) {
403  this->datapoints_.push_back(datapoint);
404  }
405 
406  // Run through listeners
407  for (auto &listener : this->listeners_) {
408  if (listener.datapoint_id == datapoint.id)
409  listener.on_datapoint(datapoint);
410  }
411  }
412 }
413 
415  uint8_t len_hi = (uint8_t) (command.payload.size() >> 8);
416  uint8_t len_lo = (uint8_t) (command.payload.size() & 0xFF);
417  uint8_t version = 0;
418 
420  switch (command.cmd) {
423  break;
426  break;
429  break;
433  break;
434  default:
435  break;
436  }
437 
438  ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
439  version, format_hex_pretty(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
440 
441  this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo});
442  if (!command.payload.empty())
443  this->write_array(command.payload.data(), command.payload.size());
444 
445  uint8_t checksum = 0x55 + 0xAA + (uint8_t) command.cmd + len_hi + len_lo;
446  for (auto &data : command.payload)
447  checksum += data;
448  this->write_byte(checksum);
449 }
450 
452  uint32_t now = millis();
453  uint32_t delay = now - this->last_command_timestamp_;
454 
455  if (now - this->last_rx_char_timestamp_ > RECEIVE_TIMEOUT) {
456  this->rx_message_.clear();
457  }
458 
459  if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) {
460  this->expected_response_.reset();
462  if (++this->init_retries_ >= MAX_RETRIES) {
463  this->init_failed_ = true;
464  ESP_LOGE(TAG, "Initialization failed at init_state %u", static_cast<uint8_t>(this->init_state_));
465  this->command_queue_.erase(command_queue_.begin());
466  this->init_retries_ = 0;
467  }
468  } else {
469  this->command_queue_.erase(command_queue_.begin());
470  }
471  }
472 
473  // Left check of delay since last command in case there's ever a command sent by calling send_raw_command_ directly
474  if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() &&
475  !this->expected_response_.has_value()) {
476  this->send_raw_command_(command_queue_.front());
477  if (!this->expected_response_.has_value())
478  this->command_queue_.erase(command_queue_.begin());
479  }
480 }
481 
482 void Tuya::send_command_(const TuyaCommand &command) {
483  command_queue_.push_back(command);
485 }
486 
488  send_command_(TuyaCommand{.cmd = command, .payload = std::vector<uint8_t>{}});
489 }
490 
492  bool is_network_ready = network::is_connected() && remote_is_connected();
493  this->status_pin_->digital_write(is_network_ready);
494 }
495 
497  uint8_t status = 0x02;
498 
499  if (network::is_connected()) {
500  status = 0x03;
501 
502  // Protocol version 3 also supports specifying when connected to "the cloud"
503  if (this->protocol_version_ >= 0x03 && remote_is_connected()) {
504  status = 0x04;
505  }
506  } else {
507 #ifdef USE_CAPTIVE_PORTAL
509  status = 0x01;
510  }
511 #endif
512  };
513 
514  return status;
515 }
516 
518 #ifdef USE_WIFI
519  if (wifi::global_wifi_component != nullptr)
521 #endif
522 
523  return 0;
524 }
525 
527  uint8_t status = this->get_wifi_status_code_();
528 
529  if (status == this->wifi_status_) {
530  return;
531  }
532 
533  ESP_LOGD(TAG, "Sending WiFi Status");
534  this->wifi_status_ = status;
535  this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_STATE, .payload = std::vector<uint8_t>{status}});
536 }
537 
538 #ifdef USE_TIME
540  std::vector<uint8_t> payload;
541  ESPTime now = this->time_id_->now();
542  if (now.is_valid()) {
543  uint8_t year = now.year - 2000;
544  uint8_t month = now.month;
545  uint8_t day_of_month = now.day_of_month;
546  uint8_t hour = now.hour;
547  uint8_t minute = now.minute;
548  uint8_t second = now.second;
549  // Tuya days starts from Monday, esphome uses Sunday as day 1
550  uint8_t day_of_week = now.day_of_week - 1;
551  if (day_of_week == 0) {
552  day_of_week = 7;
553  }
554  ESP_LOGD(TAG, "Sending local time");
555  payload = std::vector<uint8_t>{0x01, year, month, day_of_month, hour, minute, second, day_of_week};
556  } else {
557  // By spec we need to notify MCU that the time was not obtained if this is a response to a query
558  ESP_LOGW(TAG, "Sending missing local time");
559  payload = std::vector<uint8_t>{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
560  }
561  this->send_command_(TuyaCommand{.cmd = TuyaCommandType::LOCAL_TIME_QUERY, .payload = payload});
562 }
563 #endif
564 
565 void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value) {
566  this->set_raw_datapoint_value_(datapoint_id, value, false);
567 }
568 
569 void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) {
570  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, false);
571 }
572 
573 void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) {
574  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, false);
575 }
576 
577 void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) {
578  this->set_string_datapoint_value_(datapoint_id, value, false);
579 }
580 
581 void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) {
582  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, false);
583 }
584 
585 void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) {
586  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, false);
587 }
588 
589 void Tuya::force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value) {
590  this->set_raw_datapoint_value_(datapoint_id, value, true);
591 }
592 
593 void Tuya::force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value) {
594  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, true);
595 }
596 
597 void Tuya::force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) {
598  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, true);
599 }
600 
601 void Tuya::force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) {
602  this->set_string_datapoint_value_(datapoint_id, value, true);
603 }
604 
605 void Tuya::force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) {
606  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, true);
607 }
608 
609 void Tuya::force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) {
610  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, true);
611 }
612 
614  for (auto &datapoint : this->datapoints_) {
615  if (datapoint.id == datapoint_id)
616  return datapoint;
617  }
618  return {};
619 }
620 
621 void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value,
622  uint8_t length, bool forced) {
623  ESP_LOGD(TAG, "Setting datapoint %u to %" PRIu32, datapoint_id, value);
624  optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
625  if (!datapoint.has_value()) {
626  ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
627  } else if (datapoint->type != datapoint_type) {
628  ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
629  return;
630  } else if (!forced && datapoint->value_uint == value) {
631  ESP_LOGV(TAG, "Not sending unchanged value");
632  return;
633  }
634 
635  std::vector<uint8_t> data;
636  switch (length) {
637  case 4:
638  data.push_back(value >> 24);
639  data.push_back(value >> 16);
640  case 2:
641  data.push_back(value >> 8);
642  case 1:
643  data.push_back(value >> 0);
644  break;
645  default:
646  ESP_LOGE(TAG, "Unexpected datapoint length %u", length);
647  return;
648  }
649  this->send_datapoint_command_(datapoint_id, datapoint_type, data);
650 }
651 
652 void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector<uint8_t> &value, bool forced) {
653  ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str());
654  optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
655  if (!datapoint.has_value()) {
656  ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
657  } else if (datapoint->type != TuyaDatapointType::RAW) {
658  ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
659  return;
660  } else if (!forced && datapoint->value_raw == value) {
661  ESP_LOGV(TAG, "Not sending unchanged value");
662  return;
663  }
664  this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value);
665 }
666 
667 void Tuya::set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced) {
668  ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str());
669  optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
670  if (!datapoint.has_value()) {
671  ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
672  } else if (datapoint->type != TuyaDatapointType::STRING) {
673  ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
674  return;
675  } else if (!forced && datapoint->value_string == value) {
676  ESP_LOGV(TAG, "Not sending unchanged value");
677  return;
678  }
679  std::vector<uint8_t> data;
680  for (char const &c : value) {
681  data.push_back(c);
682  }
683  this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data);
684 }
685 
686 void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data) {
687  std::vector<uint8_t> buffer;
688  buffer.push_back(datapoint_id);
689  buffer.push_back(static_cast<uint8_t>(datapoint_type));
690  buffer.push_back(data.size() >> 8);
691  buffer.push_back(data.size() >> 0);
692  buffer.insert(buffer.end(), data.begin(), data.end());
693 
694  this->send_command_(TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_DELIVER, .payload = buffer});
695 }
696 
697 void Tuya::register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func) {
698  auto listener = TuyaDatapointListener{
699  .datapoint_id = datapoint_id,
700  .on_datapoint = func,
701  };
702  this->listeners_.push_back(listener);
703 
704  // Run through existing datapoints
705  for (auto &datapoint : this->datapoints_) {
706  if (datapoint.id == datapoint_id)
707  func(datapoint);
708  }
709 }
710 
712 
713 } // namespace tuya
714 } // namespace esphome
TuyaExtendedServicesCommandType
Definition: tuya.h:68
virtual void digital_write(bool value)=0
ESPTime now()
Get the time in the currently defined timezone.
uint16_t year
Definition: date_entity.h:122
uint8_t protocol_version_
Definition: tuya.h:148
void set_interval(const std::string &name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a unique name.
Definition: component.cpp:52
void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value)
Definition: tuya.cpp:581
void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector< uint8_t > &value)
Definition: tuya.cpp:565
uint8_t get_wifi_rssi_()
Definition: tuya.cpp:517
std::string format_hex_pretty(const uint8_t *data, size_t length)
Format the byte array data of length len in pretty-printed, human-readable hex.
Definition: helpers.cpp:369
uint8_t wifi_status_
Definition: tuya.h:161
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
TuyaInitState init_state_
Definition: tuya.h:145
void setup() override
Definition: tuya.cpp:24
CallbackManager< void()> initialized_callback_
Definition: tuya.h:162
void write_byte(uint8_t data)
Definition: uart.h:19
TuyaCommandType
Definition: tuya.h:48
void handle_char_(uint8_t c)
Definition: tuya.cpp:133
uint8_t checksum
Definition: bl0906.h:210
time::RealTimeClock * time_id_
Definition: tuya.h:142
void handle_datapoints_(const uint8_t *buffer, size_t len)
Definition: tuya.cpp:307
A more user-friendly version of struct tm from time.h.
Definition: time.h:15
void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector< uint8_t > &value, bool forced)
Definition: tuya.cpp:652
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
Definition: component.cpp:69
void force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length)
Definition: tuya.cpp:609
uint32_t last_command_timestamp_
Definition: tuya.h:152
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
Definition: helpers.h:187
void add_on_time_sync_callback(std::function< void()> callback)
void force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value)
Definition: tuya.cpp:601
void force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector< uint8_t > &value)
Definition: tuya.cpp:589
void send_local_time_()
Definition: tuya.cpp:539
std::vector< uint8_t > ignore_mcu_update_on_datapoints_
Definition: tuya.h:158
TuyaDatapointType
Definition: tuya.h:19
void process_command_queue_()
Definition: tuya.cpp:451
bool has_value() const
Definition: optional.h:87
void register_listener(uint8_t datapoint_id, const std::function< void(TuyaDatapoint)> &func)
Definition: tuya.cpp:697
optional< TuyaCommandType > expected_response_
Definition: tuya.h:160
InternalGPIOPin * status_pin_
Definition: tuya.h:149
uint8_t get_wifi_status_code_()
Definition: tuya.cpp:496
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition: util.cpp:15
uint8_t minute
std::vector< TuyaDatapointListener > listeners_
Definition: tuya.h:155
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:25
void force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value)
Definition: tuya.cpp:605
std::vector< TuyaDatapoint > datapoints_
Definition: tuya.h:156
void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced)
Definition: tuya.cpp:667
CaptivePortal * global_captive_portal
virtual uint8_t get_pin() const =0
const char *const TAG
Definition: spi.cpp:8
std::vector< uint8_t > rx_message_
Definition: tuya.h:157
TuyaCommandType cmd
Definition: tuya.h:84
optional< TuyaDatapoint > get_datapoint_(uint8_t datapoint_id)
Definition: tuya.cpp:613
bool init_failed_
Definition: tuya.h:146
uint8_t second
seconds after the minute [0-60]
Definition: time.h:19
bool read_byte(uint8_t *data)
Definition: uart.h:29
uint8_t hour
TuyaInitState get_init_state()
Definition: tuya.cpp:711
bool remote_is_connected()
Return whether the node has any form of "remote" connection via the API or to an MQTT broker...
Definition: util.cpp:35
uint8_t second
int reset_pin_reported_
Definition: tuya.h:151
uint8_t minute
minutes after the hour [0-59]
Definition: time.h:21
std::string product_
Definition: tuya.h:154
void send_wifi_status_()
Definition: tuya.cpp:526
WiFiComponent * global_wifi_component
void force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value)
Definition: tuya.cpp:597
bool is_valid() const
Check if this ESPTime is valid (all fields in range and year is greater than 2018) ...
Definition: time.h:59
uint8_t day_of_week
day of the week; sunday=1 [1-7]
Definition: time.h:25
uint16_t year
year
Definition: time.h:33
bool time_sync_callback_registered_
Definition: tuya.h:143
std::vector< uint8_t > payload
Definition: tuya.h:85
int status_pin_reported_
Definition: tuya.h:150
uint8_t status
Definition: bl0942.h:74
void loop() override
Definition: tuya.cpp:31
void set_boolean_datapoint_value(uint8_t datapoint_id, bool value)
Definition: tuya.cpp:569
std::string size_t len
Definition: helpers.h:293
void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value)
Definition: tuya.cpp:573
void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length)
Definition: tuya.cpp:585
void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector< uint8_t > data)
Definition: tuya.cpp:686
void send_raw_command_(TuyaCommand command)
Definition: tuya.cpp:414
bool validate_message_()
Definition: tuya.cpp:77
void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value)
Definition: tuya.cpp:577
uint16_t length
Definition: tt21100.cpp:12
void send_command_(const TuyaCommand &command)
Definition: tuya.cpp:482
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7
std::vector< TuyaCommand > command_queue_
Definition: tuya.h:159
void send_empty_command_(TuyaCommandType command)
Definition: tuya.cpp:487
uint8_t month
month; january=1 [1-12]
Definition: time.h:31
void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value, uint8_t length, bool forced)
Definition: tuya.cpp:621
TuyaInitState
Definition: tuya.h:74
uint8_t hour
hours since midnight [0-23]
Definition: time.h:23
uint32_t last_rx_char_timestamp_
Definition: tuya.h:153
void force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value)
Definition: tuya.cpp:593
void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len)
Definition: tuya.cpp:142
void dump_config() override
Definition: tuya.cpp:40
uint8_t day_of_month
day of the month [1-31]
Definition: time.h:27
uint8_t month
Definition: date_entity.h:123
void set_status_pin_()
Definition: tuya.cpp:491
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:26