ESPHome  2023.3.1
tuya.cpp
Go to the documentation of this file.
1 #include "tuya.h"
3 #include "esphome/core/helpers.h"
4 #include "esphome/core/log.h"
5 #include "esphome/core/util.h"
6 #include "esphome/core/gpio.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_.has_value()) {
27  this->status_pin_.value()->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: %x)", 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  if (this->status_pin_.has_value()) {
74  LOG_PIN(" Status Pin: ", this->status_pin_.value());
75  }
76  ESP_LOGCONFIG(TAG, " Product: '%s'", this->product_.c_str());
77 }
78 
80  uint32_t at = this->rx_message_.size() - 1;
81  auto *data = &this->rx_message_[0];
82  uint8_t new_byte = data[at];
83 
84  // Byte 0: HEADER1 (always 0x55)
85  if (at == 0)
86  return new_byte == 0x55;
87  // Byte 1: HEADER2 (always 0xAA)
88  if (at == 1)
89  return new_byte == 0xAA;
90 
91  // Byte 2: VERSION
92  // no validation for the following fields:
93  uint8_t version = data[2];
94  if (at == 2)
95  return true;
96  // Byte 3: COMMAND
97  uint8_t command = data[3];
98  if (at == 3)
99  return true;
100 
101  // Byte 4: LENGTH1
102  // Byte 5: LENGTH2
103  if (at <= 5) {
104  // no validation for these fields
105  return true;
106  }
107 
108  uint16_t length = (uint16_t(data[4]) << 8) | (uint16_t(data[5]));
109 
110  // wait until all data is read
111  if (at - 6 < length)
112  return true;
113 
114  // Byte 6+LEN: CHECKSUM - sum of all bytes (including header) modulo 256
115  uint8_t rx_checksum = new_byte;
116  uint8_t calc_checksum = 0;
117  for (uint32_t i = 0; i < 6 + length; i++)
118  calc_checksum += data[i];
119 
120  if (rx_checksum != calc_checksum) {
121  ESP_LOGW(TAG, "Tuya Received invalid message checksum %02X!=%02X", rx_checksum, calc_checksum);
122  return false;
123  }
124 
125  // valid message
126  const uint8_t *message_data = data + 6;
127  ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version,
128  format_hex_pretty(message_data, length).c_str(), static_cast<uint8_t>(this->init_state_));
129  this->handle_command_(command, version, message_data, length);
130 
131  // return false to reset rx buffer
132  return false;
133 }
134 
135 void Tuya::handle_char_(uint8_t c) {
136  this->rx_message_.push_back(c);
137  if (!this->validate_message_()) {
138  this->rx_message_.clear();
139  } else {
141  }
142 }
143 
144 void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len) {
145  TuyaCommandType command_type = (TuyaCommandType) command;
146 
147  if (this->expected_response_.has_value() && this->expected_response_ == command_type) {
148  this->expected_response_.reset();
149  this->command_queue_.erase(command_queue_.begin());
150  this->init_retries_ = 0;
151  }
152 
153  switch (command_type) {
155  ESP_LOGV(TAG, "MCU Heartbeat (0x%02X)", buffer[0]);
156  this->protocol_version_ = version;
157  if (buffer[0] == 0) {
158  ESP_LOGI(TAG, "MCU restarted");
160  }
164  }
165  break;
167  // check it is a valid string made up of printable characters
168  bool valid = true;
169  for (size_t i = 0; i < len; i++) {
170  if (!std::isprint(buffer[i])) {
171  valid = false;
172  break;
173  }
174  }
175  if (valid) {
176  this->product_ = std::string(reinterpret_cast<const char *>(buffer), len);
177  } else {
178  this->product_ = R"({"p":"INVALID"})";
179  }
183  }
184  break;
185  }
187  if (len >= 2) {
188  this->status_pin_reported_ = buffer[0];
189  this->reset_pin_reported_ = buffer[1];
190  }
191  if (this->init_state_ == TuyaInitState::INIT_CONF) {
192  // If mcu returned status gpio, then we can omit sending wifi state
193  if (this->status_pin_reported_ != -1) {
196  bool is_pin_equals =
197  this->status_pin_.has_value() && this->status_pin_.value()->get_pin() == this->status_pin_reported_;
198  // Configure status pin toggling (if reported and configured) or WIFI_STATE periodic send
199  if (is_pin_equals) {
200  ESP_LOGV(TAG, "Configured status pin %i", this->status_pin_reported_);
201  this->set_interval("wifi", 1000, [this] { this->set_status_pin_(); });
202  } else {
203  ESP_LOGW(TAG, "Supplied status_pin does not equals the reported pin %i. TuyaMcu will work in limited mode.",
204  this->status_pin_reported_);
205  }
206  } else {
208  ESP_LOGV(TAG, "Configured WIFI_STATE periodic send");
209  this->set_interval("wifi", 1000, [this] { this->send_wifi_status_(); });
210  }
211  }
212  break;
213  }
215  if (this->init_state_ == TuyaInitState::INIT_WIFI) {
218  }
219  break;
221  ESP_LOGE(TAG, "WIFI_RESET is not handled");
222  break;
224  ESP_LOGE(TAG, "WIFI_SELECT is not handled");
225  break;
227  break;
231  this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); });
232  this->initialized_callback_.call();
233  }
234  this->handle_datapoints_(buffer, len);
235  break;
237  break;
239  this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_TEST, .payload = std::vector<uint8_t>{0x00, 0x00}});
240  break;
242  this->send_command_(
243  TuyaCommand{.cmd = TuyaCommandType::WIFI_RSSI, .payload = std::vector<uint8_t>{get_wifi_rssi_()}});
244  break;
246 #ifdef USE_TIME
247  if (this->time_id_.has_value()) {
248  this->send_local_time_();
249  auto *time_id = *this->time_id_;
250  time_id->add_on_time_sync_callback([this] { this->send_local_time_(); });
251  } else {
252  ESP_LOGW(TAG, "LOCAL_TIME_QUERY is not handled because time is not configured");
253  }
254 #else
255  ESP_LOGE(TAG, "LOCAL_TIME_QUERY is not handled");
256 #endif
257  break;
259  this->send_command_(
260  TuyaCommand{.cmd = TuyaCommandType::VACUUM_MAP_UPLOAD, .payload = std::vector<uint8_t>{0x01}});
261  ESP_LOGW(TAG, "Vacuum map upload requested, responding that it is not enabled.");
262  break;
264  uint8_t wifi_status = this->get_wifi_status_code_();
265 
266  this->send_command_(
267  TuyaCommand{.cmd = TuyaCommandType::GET_NETWORK_STATUS, .payload = std::vector<uint8_t>{wifi_status}});
268  ESP_LOGV(TAG, "Network status requested, reported as %i", wifi_status);
269  break;
270  }
271  default:
272  ESP_LOGE(TAG, "Invalid command (0x%02X) received", command);
273  }
274 }
275 
276 void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) {
277  while (len >= 4) {
278  TuyaDatapoint datapoint{};
279  datapoint.id = buffer[0];
280  datapoint.type = (TuyaDatapointType) buffer[1];
281  datapoint.value_uint = 0;
282 
283  size_t data_size = (buffer[2] << 8) + buffer[3];
284  const uint8_t *data = buffer + 4;
285  size_t data_len = len - 4;
286  if (data_size > data_len) {
287  ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu > %zu)", datapoint.id, data_size, data_len);
288  return;
289  }
290 
291  datapoint.len = data_size;
292 
293  switch (datapoint.type) {
295  datapoint.value_raw = std::vector<uint8_t>(data, data + data_size);
296  ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
297  break;
299  if (data_size != 1) {
300  ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_size);
301  return;
302  }
303  datapoint.value_bool = data[0];
304  ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool));
305  break;
307  if (data_size != 4) {
308  ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_size);
309  return;
310  }
311  datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]);
312  ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int);
313  break;
315  datapoint.value_string = std::string(reinterpret_cast<const char *>(data), data_size);
316  ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str());
317  break;
319  if (data_size != 1) {
320  ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_size);
321  return;
322  }
323  datapoint.value_enum = data[0];
324  ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum);
325  break;
327  switch (data_size) {
328  case 1:
329  datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]);
330  break;
331  case 2:
332  datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]);
333  break;
334  case 4:
335  datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]);
336  break;
337  default:
338  ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_size);
339  return;
340  }
341  ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask);
342  break;
343  default:
344  ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast<uint8_t>(datapoint.type));
345  return;
346  }
347 
348  len -= data_size + 4;
349  buffer = data + data_size;
350 
351  // drop update if datapoint is in ignore_mcu_datapoint_update list
352  bool skip = false;
353  for (auto i : this->ignore_mcu_update_on_datapoints_) {
354  if (datapoint.id == i) {
355  ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id);
356  skip = true;
357  break;
358  }
359  }
360  if (skip)
361  continue;
362 
363  // Update internal datapoints
364  bool found = false;
365  for (auto &other : this->datapoints_) {
366  if (other.id == datapoint.id) {
367  other = datapoint;
368  found = true;
369  }
370  }
371  if (!found) {
372  this->datapoints_.push_back(datapoint);
373  }
374 
375  // Run through listeners
376  for (auto &listener : this->listeners_) {
377  if (listener.datapoint_id == datapoint.id)
378  listener.on_datapoint(datapoint);
379  }
380  }
381 }
382 
384  uint8_t len_hi = (uint8_t)(command.payload.size() >> 8);
385  uint8_t len_lo = (uint8_t)(command.payload.size() & 0xFF);
386  uint8_t version = 0;
387 
389  switch (command.cmd) {
392  break;
395  break;
398  break;
402  break;
403  default:
404  break;
405  }
406 
407  ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
408  version, format_hex_pretty(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
409 
410  this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo});
411  if (!command.payload.empty())
412  this->write_array(command.payload.data(), command.payload.size());
413 
414  uint8_t checksum = 0x55 + 0xAA + (uint8_t) command.cmd + len_hi + len_lo;
415  for (auto &data : command.payload)
416  checksum += data;
417  this->write_byte(checksum);
418 }
419 
421  uint32_t now = millis();
422  uint32_t delay = now - this->last_command_timestamp_;
423 
424  if (now - this->last_rx_char_timestamp_ > RECEIVE_TIMEOUT) {
425  this->rx_message_.clear();
426  }
427 
428  if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) {
429  this->expected_response_.reset();
431  if (++this->init_retries_ >= MAX_RETRIES) {
432  this->init_failed_ = true;
433  ESP_LOGE(TAG, "Initialization failed at init_state %u", static_cast<uint8_t>(this->init_state_));
434  this->command_queue_.erase(command_queue_.begin());
435  this->init_retries_ = 0;
436  }
437  } else {
438  this->command_queue_.erase(command_queue_.begin());
439  }
440  }
441 
442  // Left check of delay since last command in case there's ever a command sent by calling send_raw_command_ directly
443  if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() &&
444  !this->expected_response_.has_value()) {
445  this->send_raw_command_(command_queue_.front());
446  if (!this->expected_response_.has_value())
447  this->command_queue_.erase(command_queue_.begin());
448  }
449 }
450 
451 void Tuya::send_command_(const TuyaCommand &command) {
452  command_queue_.push_back(command);
454 }
455 
457  send_command_(TuyaCommand{.cmd = command, .payload = std::vector<uint8_t>{}});
458 }
459 
461  bool is_network_ready = network::is_connected() && remote_is_connected();
462  this->status_pin_.value()->digital_write(is_network_ready);
463 }
464 
466  uint8_t status = 0x02;
467 
468  if (network::is_connected()) {
469  status = 0x03;
470 
471  // Protocol version 3 also supports specifying when connected to "the cloud"
472  if (this->protocol_version_ >= 0x03 && remote_is_connected()) {
473  status = 0x04;
474  }
475  } else {
476 #ifdef USE_CAPTIVE_PORTAL
478  status = 0x01;
479  }
480 #endif
481  };
482 
483  return status;
484 }
485 
487 #ifdef USE_WIFI
488  if (wifi::global_wifi_component != nullptr)
490 #endif
491 
492  return 0;
493 }
494 
496  uint8_t status = this->get_wifi_status_code_();
497 
498  if (status == this->wifi_status_) {
499  return;
500  }
501 
502  ESP_LOGD(TAG, "Sending WiFi Status");
503  this->wifi_status_ = status;
504  this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_STATE, .payload = std::vector<uint8_t>{status}});
505 }
506 
507 #ifdef USE_TIME
509  std::vector<uint8_t> payload;
510  auto *time_id = *this->time_id_;
511  time::ESPTime now = time_id->now();
512  if (now.is_valid()) {
513  uint8_t year = now.year - 2000;
514  uint8_t month = now.month;
515  uint8_t day_of_month = now.day_of_month;
516  uint8_t hour = now.hour;
517  uint8_t minute = now.minute;
518  uint8_t second = now.second;
519  // Tuya days starts from Monday, esphome uses Sunday as day 1
520  uint8_t day_of_week = now.day_of_week - 1;
521  if (day_of_week == 0) {
522  day_of_week = 7;
523  }
524  ESP_LOGD(TAG, "Sending local time");
525  payload = std::vector<uint8_t>{0x01, year, month, day_of_month, hour, minute, second, day_of_week};
526  } else {
527  // By spec we need to notify MCU that the time was not obtained if this is a response to a query
528  ESP_LOGW(TAG, "Sending missing local time");
529  payload = std::vector<uint8_t>{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
530  }
531  this->send_command_(TuyaCommand{.cmd = TuyaCommandType::LOCAL_TIME_QUERY, .payload = payload});
532 }
533 #endif
534 
535 void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value) {
536  this->set_raw_datapoint_value_(datapoint_id, value, false);
537 }
538 
539 void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) {
540  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, false);
541 }
542 
543 void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) {
544  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, false);
545 }
546 
547 void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) {
548  this->set_string_datapoint_value_(datapoint_id, value, false);
549 }
550 
551 void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) {
552  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, false);
553 }
554 
555 void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) {
556  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, false);
557 }
558 
559 void Tuya::force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value) {
560  this->set_raw_datapoint_value_(datapoint_id, value, true);
561 }
562 
563 void Tuya::force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value) {
564  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, true);
565 }
566 
567 void Tuya::force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) {
568  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, true);
569 }
570 
571 void Tuya::force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) {
572  this->set_string_datapoint_value_(datapoint_id, value, true);
573 }
574 
575 void Tuya::force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) {
576  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, true);
577 }
578 
579 void Tuya::force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) {
580  this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, true);
581 }
582 
584  for (auto &datapoint : this->datapoints_) {
585  if (datapoint.id == datapoint_id)
586  return datapoint;
587  }
588  return {};
589 }
590 
591 void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value,
592  uint8_t length, bool forced) {
593  ESP_LOGD(TAG, "Setting datapoint %u to %u", datapoint_id, value);
594  optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
595  if (!datapoint.has_value()) {
596  ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
597  } else if (datapoint->type != datapoint_type) {
598  ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
599  return;
600  } else if (!forced && datapoint->value_uint == value) {
601  ESP_LOGV(TAG, "Not sending unchanged value");
602  return;
603  }
604 
605  std::vector<uint8_t> data;
606  switch (length) {
607  case 4:
608  data.push_back(value >> 24);
609  data.push_back(value >> 16);
610  case 2:
611  data.push_back(value >> 8);
612  case 1:
613  data.push_back(value >> 0);
614  break;
615  default:
616  ESP_LOGE(TAG, "Unexpected datapoint length %u", length);
617  return;
618  }
619  this->send_datapoint_command_(datapoint_id, datapoint_type, data);
620 }
621 
622 void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector<uint8_t> &value, bool forced) {
623  ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str());
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 != TuyaDatapointType::RAW) {
628  ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
629  return;
630  } else if (!forced && datapoint->value_raw == value) {
631  ESP_LOGV(TAG, "Not sending unchanged value");
632  return;
633  }
634  this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value);
635 }
636 
637 void Tuya::set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced) {
638  ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str());
639  optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
640  if (!datapoint.has_value()) {
641  ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
642  } else if (datapoint->type != TuyaDatapointType::STRING) {
643  ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
644  return;
645  } else if (!forced && datapoint->value_string == value) {
646  ESP_LOGV(TAG, "Not sending unchanged value");
647  return;
648  }
649  std::vector<uint8_t> data;
650  for (char const &c : value) {
651  data.push_back(c);
652  }
653  this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data);
654 }
655 
656 void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data) {
657  std::vector<uint8_t> buffer;
658  buffer.push_back(datapoint_id);
659  buffer.push_back(static_cast<uint8_t>(datapoint_type));
660  buffer.push_back(data.size() >> 8);
661  buffer.push_back(data.size() >> 0);
662  buffer.insert(buffer.end(), data.begin(), data.end());
663 
664  this->send_command_(TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_DELIVER, .payload = buffer});
665 }
666 
667 void Tuya::register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func) {
668  auto listener = TuyaDatapointListener{
669  .datapoint_id = datapoint_id,
670  .on_datapoint = func,
671  };
672  this->listeners_.push_back(listener);
673 
674  // Run through existing datapoints
675  for (auto &datapoint : this->datapoints_) {
676  if (datapoint.id == datapoint_id)
677  func(datapoint);
678  }
679 }
680 
682 
683 } // namespace tuya
684 } // namespace esphome
uint8_t month
month; january=1 [1-12]
uint8_t protocol_version_
Definition: tuya.h:136
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:51
void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value)
Definition: tuya.cpp:551
void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector< uint8_t > &value)
Definition: tuya.cpp:535
uint8_t get_wifi_rssi_()
Definition: tuya.cpp:486
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:237
uint8_t wifi_status_
Definition: tuya.h:149
void write_array(const uint8_t *data, size_t len)
Definition: uart.h:21
TuyaInitState init_state_
Definition: tuya.h:133
void setup() override
Definition: tuya.cpp:24
CallbackManager< void()> initialized_callback_
Definition: tuya.h:150
bool is_valid() const
Check if this ESPTime is valid (all fields in range and year is greater than 2018) ...
void write_byte(uint8_t data)
Definition: uart.h:19
TuyaCommandType
Definition: tuya.h:46
void handle_char_(uint8_t c)
Definition: tuya.cpp:135
void handle_datapoints_(const uint8_t *buffer, size_t len)
Definition: tuya.cpp:276
void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector< uint8_t > &value, bool forced)
Definition: tuya.cpp:622
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:68
void force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length)
Definition: tuya.cpp:579
uint32_t last_command_timestamp_
Definition: tuya.h:140
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:180
void force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value)
Definition: tuya.cpp:571
void force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector< uint8_t > &value)
Definition: tuya.cpp:559
void send_local_time_()
Definition: tuya.cpp:508
std::vector< uint8_t > ignore_mcu_update_on_datapoints_
Definition: tuya.h:146
TuyaDatapointType
Definition: tuya.h:17
void process_command_queue_()
Definition: tuya.cpp:420
bool has_value() const
Definition: optional.h:87
void register_listener(uint8_t datapoint_id, const std::function< void(TuyaDatapoint)> &func)
Definition: tuya.cpp:667
optional< TuyaCommandType > expected_response_
Definition: tuya.h:148
uint8_t get_wifi_status_code_()
Definition: tuya.cpp:465
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition: util.cpp:15
std::vector< TuyaDatapointListener > listeners_
Definition: tuya.h:143
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:26
void force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value)
Definition: tuya.cpp:575
std::vector< TuyaDatapoint > datapoints_
Definition: tuya.h:144
uint8_t minute
minutes after the hour [0-59]
void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced)
Definition: tuya.cpp:637
CaptivePortal * global_captive_portal
std::vector< uint8_t > rx_message_
Definition: tuya.h:145
TuyaCommandType cmd
Definition: tuya.h:73
optional< TuyaDatapoint > get_datapoint_(uint8_t datapoint_id)
Definition: tuya.cpp:583
A more user-friendly version of struct tm from time.h.
bool init_failed_
Definition: tuya.h:134
bool read_byte(uint8_t *data)
Definition: uart.h:29
TuyaInitState get_init_state()
Definition: tuya.cpp:681
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
int reset_pin_reported_
Definition: tuya.h:139
std::string product_
Definition: tuya.h:142
uint8_t second
seconds after the minute [0-60]
void send_wifi_status_()
Definition: tuya.cpp:495
uint8_t checksum
Definition: bl0939.h:35
WiFiComponent * global_wifi_component
void force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value)
Definition: tuya.cpp:567
optional< time::RealTimeClock * > time_id_
Definition: tuya.h:131
std::vector< uint8_t > payload
Definition: tuya.h:74
int status_pin_reported_
Definition: tuya.h:138
uint8_t status
Definition: bl0942.h:23
uint8_t day_of_week
day of the week; sunday=1 [1-7]
void loop() override
Definition: tuya.cpp:31
void set_boolean_datapoint_value(uint8_t datapoint_id, bool value)
Definition: tuya.cpp:539
std::string size_t len
Definition: helpers.h:286
void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value)
Definition: tuya.cpp:543
void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length)
Definition: tuya.cpp:555
void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector< uint8_t > data)
Definition: tuya.cpp:656
void send_raw_command_(TuyaCommand command)
Definition: tuya.cpp:383
bool validate_message_()
Definition: tuya.cpp:79
void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value)
Definition: tuya.cpp:547
void send_command_(const TuyaCommand &command)
Definition: tuya.cpp:451
Definition: a4988.cpp:4
std::vector< TuyaCommand > command_queue_
Definition: tuya.h:147
void send_empty_command_(TuyaCommandType command)
Definition: tuya.cpp:456
uint8_t day_of_month
day of the month [1-31]
void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value, uint8_t length, bool forced)
Definition: tuya.cpp:591
optional< InternalGPIOPin * > status_pin_
Definition: tuya.h:137
TuyaInitState
Definition: tuya.h:63
uint32_t last_rx_char_timestamp_
Definition: tuya.h:141
void force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value)
Definition: tuya.cpp:563
void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len)
Definition: tuya.cpp:144
void dump_config() override
Definition: tuya.cpp:40
uint8_t hour
hours since midnight [0-23]
void set_status_pin_()
Definition: tuya.cpp:460
void IRAM_ATTR HOT delay(uint32_t ms)
Definition: core.cpp:27