ESPHome 2025.5.0
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;
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
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_(); });
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
307void 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) {
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
482void 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
565void 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
569void 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
573void 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
577void 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
581void 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
585void 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
589void 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
593void 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
597void 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
601void 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
605void 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
609void 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
621void 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
652void 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
667void 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
686void 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
697void 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
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:55
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:72
virtual void digital_write(bool value)=0
virtual uint8_t get_pin() const =0
bool has_value() const
Definition optional.h:87
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:686
void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value)
Definition tuya.cpp:577
optional< TuyaDatapoint > get_datapoint_(uint8_t datapoint_id)
Definition tuya.cpp:613
void send_empty_command_(TuyaCommandType command)
Definition tuya.cpp:487
void setup() override
Definition tuya.cpp:24
uint8_t get_wifi_rssi_()
Definition tuya.cpp:517
uint8_t get_wifi_status_code_()
Definition tuya.cpp:496
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:569
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:652
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:589
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:667
void force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value)
Definition tuya.cpp:597
uint32_t last_rx_char_timestamp_
Definition tuya.h:153
void process_command_queue_()
Definition tuya.cpp:451
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:539
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:581
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 get_init_state()
Definition tuya.cpp:711
void set_status_pin_()
Definition tuya.cpp:491
void register_listener(uint8_t datapoint_id, const std::function< void(TuyaDatapoint)> &func)
Definition tuya.cpp:697
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:609
void handle_datapoints_(const uint8_t *buffer, size_t len)
Definition tuya.cpp:307
std::string product_
Definition tuya.h:154
void send_wifi_status_()
Definition tuya.cpp:526
void send_command_(const TuyaCommand &command)
Definition tuya.cpp:482
void force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value)
Definition tuya.cpp:593
void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector< uint8_t > &value)
Definition tuya.cpp:565
optional< TuyaCommandType > expected_response_
Definition tuya.h:160
void send_raw_command_(TuyaCommand command)
Definition tuya.cpp:414
void force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value)
Definition tuya.cpp:605
void force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value)
Definition tuya.cpp:601
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:585
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:573
void loop() override
Definition tuya.cpp:31
bool read_byte(uint8_t *data)
Definition uart.h:29
void write_byte(uint8_t data)
Definition uart.h:19
void write_array(const uint8_t *data, size_t len)
Definition uart.h:21
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:15
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:301
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:195
void IRAM_ATTR HOT delay(uint32_t ms)
Definition core.cpp:28
uint32_t IRAM_ATTR HOT millis()
Definition core.cpp:27
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:372
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
uint16_t length
Definition tt21100.cpp:0