ESPHome 2025.10.0-dev
Loading...
Searching...
No Matches
tuya.cpp
Go to the documentation of this file.
1#include "tuya.h"
3#include "esphome/core/gpio.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
16namespace esphome {
17namespace tuya {
18
19static const char *const TAG = "tuya";
20static const int COMMAND_DELAY = 10;
21static const int RECEIVE_TIMEOUT = 300;
22static const int MAX_RETRIES = 5;
23
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
31void 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_,
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
133void 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
142void 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) {
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 }
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.",
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 }
216 }
217 break;
220 const bool is_select = (len >= 1);
221 // Send WIFI_SELECT ACK
224 ack.payload.clear();
225 this->send_command_(ack);
226 // Establish pairing mode for correct first WIFI_STATE byte, EZ (0x00) default
227 uint8_t first = 0x00;
228 const char *mode_str = "EZ";
229 if (is_select && buffer[0] == 0x01) {
230 first = 0x01;
231 mode_str = "AP";
232 }
233 // Send WIFI_STATE response, MCU exits pairing mode
234 TuyaCommand st;
236 st.payload.resize(1);
237 st.payload[0] = first;
238 this->send_command_(st);
239 st.payload[0] = 0x02;
240 this->send_command_(st);
241 st.payload[0] = 0x03;
242 this->send_command_(st);
243 st.payload[0] = 0x04;
244 this->send_command_(st);
245 ESP_LOGI(TAG, "%s received (%s), replied with WIFI_STATE confirming connection established",
246 is_select ? "WIFI_SELECT" : "WIFI_RESET", mode_str);
247 break;
248 }
250 break;
255 this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); });
256 this->initialized_callback_.call();
257 }
258 this->handle_datapoints_(buffer, len);
259
260 if (command_type == TuyaCommandType::DATAPOINT_REPORT_SYNC) {
261 this->send_command_(
262 TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_REPORT_ACK, .payload = std::vector<uint8_t>{0x01}});
263 }
264 break;
266 break;
268 this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_TEST, .payload = std::vector<uint8_t>{0x00, 0x00}});
269 break;
271 this->send_command_(
272 TuyaCommand{.cmd = TuyaCommandType::WIFI_RSSI, .payload = std::vector<uint8_t>{get_wifi_rssi_()}});
273 break;
275#ifdef USE_TIME
276 if (this->time_id_ != nullptr) {
277 this->send_local_time_();
278
280 // tuya mcu supports time, so we let them know when our time changed
281 this->time_id_->add_on_time_sync_callback([this] { this->send_local_time_(); });
283 }
284 } else
285#endif
286 {
287 ESP_LOGW(TAG, "LOCAL_TIME_QUERY is not handled because time is not configured");
288 }
289 break;
291 this->send_command_(
292 TuyaCommand{.cmd = TuyaCommandType::VACUUM_MAP_UPLOAD, .payload = std::vector<uint8_t>{0x01}});
293 ESP_LOGW(TAG, "Vacuum map upload requested, responding that it is not enabled.");
294 break;
296 uint8_t wifi_status = this->get_wifi_status_code_();
297
298 this->send_command_(
299 TuyaCommand{.cmd = TuyaCommandType::GET_NETWORK_STATUS, .payload = std::vector<uint8_t>{wifi_status}});
300 ESP_LOGV(TAG, "Network status requested, reported as %i", wifi_status);
301 break;
302 }
304 uint8_t subcommand = buffer[0];
305 switch ((TuyaExtendedServicesCommandType) subcommand) {
307 this->send_command_(
309 .payload = std::vector<uint8_t>{
310 static_cast<uint8_t>(TuyaExtendedServicesCommandType::RESET_NOTIFICATION), 0x00}});
311 ESP_LOGV(TAG, "Reset status notification enabled");
312 break;
313 }
315 ESP_LOGE(TAG, "EXTENDED_SERVICES::MODULE_RESET is not handled");
316 break;
317 }
319 ESP_LOGE(TAG, "EXTENDED_SERVICES::UPDATE_IN_PROGRESS is not handled");
320 break;
321 }
322 default:
323 ESP_LOGE(TAG, "Invalid extended services subcommand (0x%02X) received", subcommand);
324 }
325 break;
326 }
327 default:
328 ESP_LOGE(TAG, "Invalid command (0x%02X) received", command);
329 }
330}
331
332void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) {
333 while (len >= 4) {
334 TuyaDatapoint datapoint{};
335 datapoint.id = buffer[0];
336 datapoint.type = (TuyaDatapointType) buffer[1];
337 datapoint.value_uint = 0;
338
339 size_t data_size = (buffer[2] << 8) + buffer[3];
340 const uint8_t *data = buffer + 4;
341 size_t data_len = len - 4;
342 if (data_size > data_len) {
343 ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu > %zu)", datapoint.id, data_size, data_len);
344 return;
345 }
346
347 datapoint.len = data_size;
348
349 switch (datapoint.type) {
351 datapoint.value_raw = std::vector<uint8_t>(data, data + data_size);
352 ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
353 break;
355 if (data_size != 1) {
356 ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_size);
357 return;
358 }
359 datapoint.value_bool = data[0];
360 ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool));
361 break;
363 if (data_size != 4) {
364 ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_size);
365 return;
366 }
367 datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]);
368 ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int);
369 break;
371 datapoint.value_string = std::string(reinterpret_cast<const char *>(data), data_size);
372 ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str());
373 break;
375 if (data_size != 1) {
376 ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_size);
377 return;
378 }
379 datapoint.value_enum = data[0];
380 ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum);
381 break;
383 switch (data_size) {
384 case 1:
385 datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]);
386 break;
387 case 2:
388 datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]);
389 break;
390 case 4:
391 datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]);
392 break;
393 default:
394 ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_size);
395 return;
396 }
397 ESP_LOGD(TAG, "Datapoint %u update to %#08" PRIX32, datapoint.id, datapoint.value_bitmask);
398 break;
399 default:
400 ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast<uint8_t>(datapoint.type));
401 return;
402 }
403
404 len -= data_size + 4;
405 buffer = data + data_size;
406
407 // drop update if datapoint is in ignore_mcu_datapoint_update list
408 bool skip = false;
409 for (auto i : this->ignore_mcu_update_on_datapoints_) {
410 if (datapoint.id == i) {
411 ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id);
412 skip = true;
413 break;
414 }
415 }
416 if (skip)
417 continue;
418
419 // Update internal datapoints
420 bool found = false;
421 for (auto &other : this->datapoints_) {
422 if (other.id == datapoint.id) {
423 other = datapoint;
424 found = true;
425 }
426 }
427 if (!found) {
428 this->datapoints_.push_back(datapoint);
429 }
430
431 // Run through listeners
432 for (auto &listener : this->listeners_) {
433 if (listener.datapoint_id == datapoint.id)
434 listener.on_datapoint(datapoint);
435 }
436 }
437}
438
440 uint8_t len_hi = (uint8_t) (command.payload.size() >> 8);
441 uint8_t len_lo = (uint8_t) (command.payload.size() & 0xFF);
442 uint8_t version = 0;
443
445 switch (command.cmd) {
448 break;
451 break;
454 break;
458 break;
459 default:
460 break;
461 }
462
463 ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
464 version, format_hex_pretty(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
465
466 this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo});
467 if (!command.payload.empty())
468 this->write_array(command.payload.data(), command.payload.size());
469
470 uint8_t checksum = 0x55 + 0xAA + (uint8_t) command.cmd + len_hi + len_lo;
471 for (auto &data : command.payload)
472 checksum += data;
473 this->write_byte(checksum);
474}
475
477 uint32_t now = millis();
478 uint32_t delay = now - this->last_command_timestamp_;
479
480 if (now - this->last_rx_char_timestamp_ > RECEIVE_TIMEOUT) {
481 this->rx_message_.clear();
482 }
483
484 if (this->expected_response_.has_value() && delay > RECEIVE_TIMEOUT) {
487 if (++this->init_retries_ >= MAX_RETRIES) {
488 this->init_failed_ = true;
489 ESP_LOGE(TAG, "Initialization failed at init_state %u", static_cast<uint8_t>(this->init_state_));
490 this->command_queue_.erase(command_queue_.begin());
491 this->init_retries_ = 0;
492 }
493 } else {
494 this->command_queue_.erase(command_queue_.begin());
495 }
496 }
497
498 // Left check of delay since last command in case there's ever a command sent by calling send_raw_command_ directly
499 if (delay > COMMAND_DELAY && !this->command_queue_.empty() && this->rx_message_.empty() &&
500 !this->expected_response_.has_value()) {
501 this->send_raw_command_(command_queue_.front());
502 if (!this->expected_response_.has_value())
503 this->command_queue_.erase(command_queue_.begin());
504 }
505}
506
507void Tuya::send_command_(const TuyaCommand &command) {
508 command_queue_.push_back(command);
510}
511
513 send_command_(TuyaCommand{.cmd = command, .payload = std::vector<uint8_t>{}});
514}
515
517 bool is_network_ready = network::is_connected() && remote_is_connected();
518 this->status_pin_->digital_write(is_network_ready);
519}
520
522 uint8_t status = 0x02;
523
524 if (network::is_connected()) {
525 status = 0x03;
526
527 // Protocol version 3 also supports specifying when connected to "the cloud"
528 if (this->protocol_version_ >= 0x03 && remote_is_connected()) {
529 status = 0x04;
530 }
531 } else {
532#ifdef USE_CAPTIVE_PORTAL
534 status = 0x01;
535 }
536#endif
537 };
538
539 return status;
540}
541
543#ifdef USE_WIFI
544 if (wifi::global_wifi_component != nullptr)
546#endif
547
548 return 0;
549}
550
552 uint8_t status = this->get_wifi_status_code_();
553
554 if (status == this->wifi_status_) {
555 return;
556 }
557
558 ESP_LOGD(TAG, "Sending WiFi Status");
559 this->wifi_status_ = status;
560 this->send_command_(TuyaCommand{.cmd = TuyaCommandType::WIFI_STATE, .payload = std::vector<uint8_t>{status}});
561}
562
563#ifdef USE_TIME
565 std::vector<uint8_t> payload;
566 ESPTime now = this->time_id_->now();
567 if (now.is_valid()) {
568 uint8_t year = now.year - 2000;
569 uint8_t month = now.month;
570 uint8_t day_of_month = now.day_of_month;
571 uint8_t hour = now.hour;
572 uint8_t minute = now.minute;
573 uint8_t second = now.second;
574 // Tuya days starts from Monday, esphome uses Sunday as day 1
575 uint8_t day_of_week = now.day_of_week - 1;
576 if (day_of_week == 0) {
577 day_of_week = 7;
578 }
579 ESP_LOGD(TAG, "Sending local time");
580 payload = std::vector<uint8_t>{0x01, year, month, day_of_month, hour, minute, second, day_of_week};
581 } else {
582 // By spec we need to notify MCU that the time was not obtained if this is a response to a query
583 ESP_LOGW(TAG, "Sending missing local time");
584 payload = std::vector<uint8_t>{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
585 }
586 this->send_command_(TuyaCommand{.cmd = TuyaCommandType::LOCAL_TIME_QUERY, .payload = payload});
587}
588#endif
589
590void Tuya::set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value) {
591 this->set_raw_datapoint_value_(datapoint_id, value, false);
592}
593
594void Tuya::set_boolean_datapoint_value(uint8_t datapoint_id, bool value) {
595 this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, false);
596}
597
598void Tuya::set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) {
599 this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, false);
600}
601
602void Tuya::set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) {
603 this->set_string_datapoint_value_(datapoint_id, value, false);
604}
605
606void Tuya::set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) {
607 this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, false);
608}
609
610void Tuya::set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) {
611 this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, false);
612}
613
614void Tuya::force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector<uint8_t> &value) {
615 this->set_raw_datapoint_value_(datapoint_id, value, true);
616}
617
618void Tuya::force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value) {
619 this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BOOLEAN, value, 1, true);
620}
621
622void Tuya::force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value) {
623 this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::INTEGER, value, 4, true);
624}
625
626void Tuya::force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value) {
627 this->set_string_datapoint_value_(datapoint_id, value, true);
628}
629
630void Tuya::force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value) {
631 this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::ENUM, value, 1, true);
632}
633
634void Tuya::force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length) {
635 this->set_numeric_datapoint_value_(datapoint_id, TuyaDatapointType::BITMASK, value, length, true);
636}
637
639 for (auto &datapoint : this->datapoints_) {
640 if (datapoint.id == datapoint_id)
641 return datapoint;
642 }
643 return {};
644}
645
646void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, const uint32_t value,
647 uint8_t length, bool forced) {
648 ESP_LOGD(TAG, "Setting datapoint %u to %" PRIu32, datapoint_id, value);
649 optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
650 if (!datapoint.has_value()) {
651 ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
652 } else if (datapoint->type != datapoint_type) {
653 ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
654 return;
655 } else if (!forced && datapoint->value_uint == value) {
656 ESP_LOGV(TAG, "Not sending unchanged value");
657 return;
658 }
659
660 std::vector<uint8_t> data;
661 switch (length) {
662 case 4:
663 data.push_back(value >> 24);
664 data.push_back(value >> 16);
665 case 2:
666 data.push_back(value >> 8);
667 case 1:
668 data.push_back(value >> 0);
669 break;
670 default:
671 ESP_LOGE(TAG, "Unexpected datapoint length %u", length);
672 return;
673 }
674 this->send_datapoint_command_(datapoint_id, datapoint_type, data);
675}
676
677void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector<uint8_t> &value, bool forced) {
678 ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str());
679 optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
680 if (!datapoint.has_value()) {
681 ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
682 } else if (datapoint->type != TuyaDatapointType::RAW) {
683 ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
684 return;
685 } else if (!forced && datapoint->value_raw == value) {
686 ESP_LOGV(TAG, "Not sending unchanged value");
687 return;
688 }
689 this->send_datapoint_command_(datapoint_id, TuyaDatapointType::RAW, value);
690}
691
692void Tuya::set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced) {
693 ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, value.c_str());
694 optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
695 if (!datapoint.has_value()) {
696 ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);
697 } else if (datapoint->type != TuyaDatapointType::STRING) {
698 ESP_LOGE(TAG, "Attempt to set datapoint %u with incorrect type", datapoint_id);
699 return;
700 } else if (!forced && datapoint->value_string == value) {
701 ESP_LOGV(TAG, "Not sending unchanged value");
702 return;
703 }
704 std::vector<uint8_t> data;
705 for (char const &c : value) {
706 data.push_back(c);
707 }
708 this->send_datapoint_command_(datapoint_id, TuyaDatapointType::STRING, data);
709}
710
711void Tuya::send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector<uint8_t> data) {
712 std::vector<uint8_t> buffer;
713 buffer.push_back(datapoint_id);
714 buffer.push_back(static_cast<uint8_t>(datapoint_type));
715 buffer.push_back(data.size() >> 8);
716 buffer.push_back(data.size() >> 0);
717 buffer.insert(buffer.end(), data.begin(), data.end());
718
719 this->send_command_(TuyaCommand{.cmd = TuyaCommandType::DATAPOINT_DELIVER, .payload = buffer});
720}
721
722void Tuya::register_listener(uint8_t datapoint_id, const std::function<void(TuyaDatapoint)> &func) {
723 auto listener = TuyaDatapointListener{
724 .datapoint_id = datapoint_id,
725 .on_datapoint = func,
726 };
727 this->listeners_.push_back(listener);
728
729 // Run through existing datapoints
730 for (auto &datapoint : this->datapoints_) {
731 if (datapoint.id == datapoint_id)
732 func(datapoint);
733 }
734}
735
737
738} // namespace tuya
739} // namespace esphome
uint8_t checksum
Definition bl0906.h:3
uint8_t status
Definition bl0942.h:8
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:88
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
virtual void digital_write(bool value)=0
virtual uint8_t get_pin() const =0
bool has_value() const
Definition optional.h:92
void add_on_time_sync_callback(std::function< void()> &&callback)
ESPTime now()
Get the time in the currently defined timezone.
void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector< uint8_t > data)
Definition tuya.cpp:711
void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value)
Definition tuya.cpp:602
optional< TuyaDatapoint > get_datapoint_(uint8_t datapoint_id)
Definition tuya.cpp:638
void send_empty_command_(TuyaCommandType command)
Definition tuya.cpp:512
void setup() override
Definition tuya.cpp:24
uint8_t get_wifi_rssi_()
Definition tuya.cpp:542
uint8_t get_wifi_status_code_()
Definition tuya.cpp:521
void dump_config() override
Definition tuya.cpp:40
time::RealTimeClock * time_id_
Definition tuya.h:142
CallbackManager< void()> initialized_callback_
Definition tuya.h:162
void set_boolean_datapoint_value(uint8_t datapoint_id, bool value)
Definition tuya.cpp:594
TuyaInitState init_state_
Definition tuya.h:145
int status_pin_reported_
Definition tuya.h:150
void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector< uint8_t > &value, bool forced)
Definition tuya.cpp:677
bool validate_message_()
Definition tuya.cpp:77
InternalGPIOPin * status_pin_
Definition tuya.h:149
void force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector< uint8_t > &value)
Definition tuya.cpp:614
uint8_t protocol_version_
Definition tuya.h:148
void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced)
Definition tuya.cpp:692
void force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value)
Definition tuya.cpp:622
uint32_t last_rx_char_timestamp_
Definition tuya.h:153
void process_command_queue_()
Definition tuya.cpp:476
uint32_t last_command_timestamp_
Definition tuya.h:152
std::vector< TuyaDatapointListener > listeners_
Definition tuya.h:155
std::vector< TuyaCommand > command_queue_
Definition tuya.h:159
std::vector< TuyaDatapoint > datapoints_
Definition tuya.h:156
void send_local_time_()
Definition tuya.cpp:564
std::vector< uint8_t > ignore_mcu_update_on_datapoints_
Definition tuya.h:158
std::vector< uint8_t > rx_message_
Definition tuya.h:157
void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len)
Definition tuya.cpp:142
void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value)
Definition tuya.cpp:606
void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value, uint8_t length, bool forced)
Definition tuya.cpp:646
TuyaInitState get_init_state()
Definition tuya.cpp:736
void set_status_pin_()
Definition tuya.cpp:516
void register_listener(uint8_t datapoint_id, const std::function< void(TuyaDatapoint)> &func)
Definition tuya.cpp:722
void handle_char_(uint8_t c)
Definition tuya.cpp:133
void force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length)
Definition tuya.cpp:634
void handle_datapoints_(const uint8_t *buffer, size_t len)
Definition tuya.cpp:332
std::string product_
Definition tuya.h:154
void send_wifi_status_()
Definition tuya.cpp:551
void send_command_(const TuyaCommand &command)
Definition tuya.cpp:507
void force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value)
Definition tuya.cpp:618
void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector< uint8_t > &value)
Definition tuya.cpp:590
optional< TuyaCommandType > expected_response_
Definition tuya.h:160
void send_raw_command_(TuyaCommand command)
Definition tuya.cpp:439
void force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value)
Definition tuya.cpp:630
void force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value)
Definition tuya.cpp:626
bool time_sync_callback_registered_
Definition tuya.h:143
void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length)
Definition tuya.cpp:610
int reset_pin_reported_
Definition tuya.h:151
uint8_t wifi_status_
Definition tuya.h:161
void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value)
Definition tuya.cpp:598
void loop() override
Definition tuya.cpp:31
bool read_byte(uint8_t *data)
Definition uart.h:35
void write_byte(uint8_t data)
Definition uart.h:19
void write_array(const uint8_t *data, size_t len)
Definition uart.h:27
uint8_t month
Definition date_entity.h:1
uint16_t year
Definition date_entity.h:0
uint8_t second
uint8_t minute
uint8_t hour
CaptivePortal * global_captive_portal
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
Definition util.cpp:26
const char *const TAG
Definition spi.cpp:8
TuyaExtendedServicesCommandType
Definition tuya.h:68
TuyaCommandType
Definition tuya.h:48
TuyaDatapointType
Definition tuya.h:19
WiFiComponent * global_wifi_component
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
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
std::string size_t len
Definition helpers.h:291
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length)
Format a byte array in pretty-printed, human-readable hex format.
Definition helpers.cpp:292
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:193
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:29
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:28
A more user-friendly version of struct tm from time.h.
Definition time.h:15
uint8_t minute
minutes after the hour [0-59]
Definition time.h:21
uint8_t second
seconds after the minute [0-60]
Definition time.h:19
uint8_t hour
hours since midnight [0-23]
Definition time.h:23
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_month
day of the month [1-31]
Definition time.h:27
uint16_t year
year
Definition time.h:33
uint8_t month
month; january=1 [1-12]
Definition time.h:31
uint8_t day_of_week
day of the week; sunday=1 [1-7]
Definition time.h:25
TuyaCommandType cmd
Definition tuya.h:84
std::vector< uint8_t > payload
Definition tuya.h:85
uint8_t ack
uint16_t length
Definition tt21100.cpp:0