12 #ifdef USE_CAPTIVE_PORTAL 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;
41 ESP_LOGCONFIG(TAG,
"Tuya:");
44 ESP_LOGCONFIG(TAG,
" Initialization failed. Current init_state: %u", static_cast<uint8_t>(this->
init_state_));
46 ESP_LOGCONFIG(TAG,
" Configuration will be reported when setup is complete. Current init_state: %u",
49 ESP_LOGCONFIG(TAG,
" If no further output is received, confirm that this is a supported Tuya device.");
54 ESP_LOGCONFIG(TAG,
" Datapoint %u: raw (value: %s)", info.id,
format_hex_pretty(info.value_raw).c_str());
56 ESP_LOGCONFIG(TAG,
" Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool));
58 ESP_LOGCONFIG(TAG,
" Datapoint %u: int value (value: %d)", info.id, info.value_int);
60 ESP_LOGCONFIG(TAG,
" Datapoint %u: string value (value: %s)", info.id, info.value_string.c_str());
62 ESP_LOGCONFIG(TAG,
" Datapoint %u: enum (value: %d)", info.id, info.value_enum);
64 ESP_LOGCONFIG(TAG,
" Datapoint %u: bitmask (value: %" PRIx32
")", info.id, info.value_bitmask);
66 ESP_LOGCONFIG(TAG,
" Datapoint %u: unknown", info.id);
70 ESP_LOGCONFIG(TAG,
" GPIO Configuration: status: pin %d, reset: pin %d", this->
status_pin_reported_,
74 LOG_PIN(
" Status Pin: ", this->
status_pin_.value());
76 ESP_LOGCONFIG(TAG,
" Product: '%s'", this->
product_.c_str());
82 uint8_t new_byte = data[at];
86 return new_byte == 0x55;
89 return new_byte == 0xAA;
97 uint8_t command = data[3];
108 uint16_t
length = (uint16_t(data[4]) << 8) | (uint16_t(data[5]));
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];
120 if (rx_checksum != calc_checksum) {
121 ESP_LOGW(TAG,
"Tuya Received invalid message checksum %02X!=%02X", rx_checksum, calc_checksum);
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,
153 switch (command_type) {
155 ESP_LOGV(TAG,
"MCU Heartbeat (0x%02X)", buffer[0]);
157 if (buffer[0] == 0) {
158 ESP_LOGI(TAG,
"MCU restarted");
169 for (
size_t i = 0; i <
len; i++) {
170 if (!std::isprint(buffer[i])) {
176 this->
product_ = std::string(reinterpret_cast<const char *>(buffer), len);
178 this->
product_ = R
"({"p":"INVALID"})"; 203 ESP_LOGW(TAG,
"Supplied status_pin does not equals the reported pin %i. TuyaMcu will work in limited mode.",
208 ESP_LOGV(TAG,
"Configured WIFI_STATE periodic send");
221 ESP_LOGE(TAG,
"WIFI_RESET is not handled");
224 ESP_LOGE(TAG,
"WIFI_SELECT is not handled");
259 ESP_LOGW(TAG,
"LOCAL_TIME_QUERY is not handled because time is not configured");
265 ESP_LOGW(TAG,
"Vacuum map upload requested, responding that it is not enabled.");
272 ESP_LOGV(TAG,
"Network status requested, reported as %i", wifi_status);
276 ESP_LOGE(TAG,
"Invalid command (0x%02X) received", command);
283 datapoint.
id = buffer[0];
285 datapoint.value_uint = 0;
287 size_t data_size = (buffer[2] << 8) + buffer[3];
288 const uint8_t *data = buffer + 4;
289 size_t data_len = len - 4;
290 if (data_size > data_len) {
291 ESP_LOGW(TAG,
"Datapoint %u is truncated and cannot be parsed (%zu > %zu)", datapoint.id, data_size, data_len);
297 switch (datapoint.type) {
299 datapoint.value_raw = std::vector<uint8_t>(data, data +
data_size);
300 ESP_LOGD(TAG,
"Datapoint %u update to %s", datapoint.id,
format_hex_pretty(datapoint.value_raw).c_str());
303 if (data_size != 1) {
304 ESP_LOGW(TAG,
"Datapoint %u has bad boolean len %zu", datapoint.id, data_size);
307 datapoint.value_bool = data[0];
308 ESP_LOGD(TAG,
"Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool));
311 if (data_size != 4) {
312 ESP_LOGW(TAG,
"Datapoint %u has bad integer len %zu", datapoint.id, data_size);
315 datapoint.value_uint =
encode_uint32(data[0], data[1], data[2], data[3]);
316 ESP_LOGD(TAG,
"Datapoint %u update to %d", datapoint.id, datapoint.value_int);
319 datapoint.value_string = std::string(reinterpret_cast<const char *>(data), data_size);
320 ESP_LOGD(TAG,
"Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str());
323 if (data_size != 1) {
324 ESP_LOGW(TAG,
"Datapoint %u has bad enum len %zu", datapoint.id, data_size);
327 datapoint.value_enum = data[0];
328 ESP_LOGD(TAG,
"Datapoint %u update to %d", datapoint.id, datapoint.value_enum);
336 datapoint.value_bitmask =
encode_uint32(0, 0, data[0], data[1]);
339 datapoint.value_bitmask =
encode_uint32(data[0], data[1], data[2], data[3]);
342 ESP_LOGW(TAG,
"Datapoint %u has bad bitmask len %zu", datapoint.id, data_size);
345 ESP_LOGD(TAG,
"Datapoint %u update to %#08" PRIX32, datapoint.id, datapoint.value_bitmask);
348 ESP_LOGW(TAG,
"Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast<uint8_t>(datapoint.type));
352 len -= data_size + 4;
358 if (datapoint.id == i) {
359 ESP_LOGV(TAG,
"Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id);
370 if (other.id == datapoint.id) {
376 this->datapoints_.push_back(datapoint);
381 if (listener.datapoint_id == datapoint.id)
382 listener.on_datapoint(datapoint);
388 uint8_t len_hi = (uint8_t) (command.
payload.size() >> 8);
389 uint8_t len_lo = (uint8_t) (command.
payload.size() & 0xFF);
393 switch (command.
cmd) {
411 ESP_LOGV(TAG,
"Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.
cmd),
418 uint8_t
checksum = 0x55 + 0xAA + (uint8_t) command.
cmd + len_hi + len_lo;
419 for (
auto &data : command.
payload)
437 ESP_LOGE(TAG,
"Initialization failed at init_state %u", static_cast<uint8_t>(this->
init_state_));
466 this->
status_pin_.value()->digital_write(is_network_ready);
480 #ifdef USE_CAPTIVE_PORTAL 506 ESP_LOGD(TAG,
"Sending WiFi Status");
513 std::vector<uint8_t> payload;
517 uint8_t year = now.
year - 2000;
518 uint8_t month = now.
month;
520 uint8_t hour = now.
hour;
521 uint8_t minute = now.
minute;
522 uint8_t second = now.
second;
525 if (day_of_week == 0) {
528 ESP_LOGD(TAG,
"Sending local time");
529 payload = std::vector<uint8_t>{0x01, year, month, day_of_month, hour, minute, second, day_of_week};
532 ESP_LOGW(TAG,
"Sending missing local time");
533 payload = std::vector<uint8_t>{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
589 if (datapoint.id == datapoint_id)
596 uint8_t
length,
bool forced) {
597 ESP_LOGD(TAG,
"Setting datapoint %u to %" PRIu32, datapoint_id, value);
600 ESP_LOGW(TAG,
"Setting unknown datapoint %u", datapoint_id);
601 }
else if (datapoint->type != datapoint_type) {
602 ESP_LOGE(TAG,
"Attempt to set datapoint %u with incorrect type", datapoint_id);
604 }
else if (!forced && datapoint->value_uint == value) {
605 ESP_LOGV(TAG,
"Not sending unchanged value");
609 std::vector<uint8_t> data;
612 data.push_back(value >> 24);
613 data.push_back(value >> 16);
615 data.push_back(value >> 8);
617 data.push_back(value >> 0);
620 ESP_LOGE(TAG,
"Unexpected datapoint length %u", length);
627 ESP_LOGD(TAG,
"Setting datapoint %u to %s", datapoint_id,
format_hex_pretty(value).c_str());
630 ESP_LOGW(TAG,
"Setting unknown datapoint %u", datapoint_id);
632 ESP_LOGE(TAG,
"Attempt to set datapoint %u with incorrect type", datapoint_id);
634 }
else if (!forced && datapoint->value_raw == value) {
635 ESP_LOGV(TAG,
"Not sending unchanged value");
642 ESP_LOGD(TAG,
"Setting datapoint %u to %s", datapoint_id, value.c_str());
644 if (!datapoint.has_value()) {
645 ESP_LOGW(TAG,
"Setting unknown datapoint %u", datapoint_id);
647 ESP_LOGE(TAG,
"Attempt to set datapoint %u with incorrect type", datapoint_id);
649 }
else if (!forced && datapoint->value_string == value) {
650 ESP_LOGV(TAG,
"Not sending unchanged value");
653 std::vector<uint8_t> data;
654 for (
char const &c : value) {
661 std::vector<uint8_t> buffer;
662 buffer.push_back(datapoint_id);
663 buffer.push_back(static_cast<uint8_t>(datapoint_type));
664 buffer.push_back(data.size() >> 8);
665 buffer.push_back(data.size() >> 0);
666 buffer.insert(buffer.end(), data.begin(), data.end());
674 .on_datapoint = func,
680 if (datapoint.id == datapoint_id)
uint8_t protocol_version_
void set_interval(const std::string &name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a unique name.
void set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value)
void set_raw_datapoint_value(uint8_t datapoint_id, const std::vector< uint8_t > &value)
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.
void write_array(const uint8_t *data, size_t len)
TuyaInitState init_state_
CallbackManager< void()> initialized_callback_
void write_byte(uint8_t data)
void handle_char_(uint8_t c)
void handle_datapoints_(const uint8_t *buffer, size_t len)
A more user-friendly version of struct tm from time.h.
void set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector< uint8_t > &value, bool forced)
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
void force_set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length)
uint32_t last_command_timestamp_
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.
void force_set_string_datapoint_value(uint8_t datapoint_id, const std::string &value)
void force_set_raw_datapoint_value(uint8_t datapoint_id, const std::vector< uint8_t > &value)
std::vector< uint8_t > ignore_mcu_update_on_datapoints_
void process_command_queue_()
void register_listener(uint8_t datapoint_id, const std::function< void(TuyaDatapoint)> &func)
optional< TuyaCommandType > expected_response_
uint8_t get_wifi_status_code_()
bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
std::vector< TuyaDatapointListener > listeners_
uint32_t IRAM_ATTR HOT millis()
void force_set_enum_datapoint_value(uint8_t datapoint_id, uint8_t value)
std::vector< TuyaDatapoint > datapoints_
void set_string_datapoint_value_(uint8_t datapoint_id, const std::string &value, bool forced)
CaptivePortal * global_captive_portal
std::vector< uint8_t > rx_message_
optional< TuyaDatapoint > get_datapoint_(uint8_t datapoint_id)
uint8_t second
seconds after the minute [0-60]
bool read_byte(uint8_t *data)
TuyaInitState get_init_state()
bool remote_is_connected()
Return whether the node has any form of "remote" connection via the API or to an MQTT broker...
uint8_t minute
minutes after the hour [0-59]
WiFiComponent * global_wifi_component
void force_set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value)
optional< time::RealTimeClock * > time_id_
bool is_valid() const
Check if this ESPTime is valid (all fields in range and year is greater than 2018) ...
uint8_t day_of_week
day of the week; sunday=1 [1-7]
bool time_sync_callback_registered_
std::vector< uint8_t > payload
void set_boolean_datapoint_value(uint8_t datapoint_id, bool value)
void set_integer_datapoint_value(uint8_t datapoint_id, uint32_t value)
void set_bitmask_datapoint_value(uint8_t datapoint_id, uint32_t value, uint8_t length)
void send_datapoint_command_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, std::vector< uint8_t > data)
void send_raw_command_(TuyaCommand command)
void set_string_datapoint_value(uint8_t datapoint_id, const std::string &value)
void send_command_(const TuyaCommand &command)
Implementation of SPI Controller mode.
std::vector< TuyaCommand > command_queue_
void send_empty_command_(TuyaCommandType command)
uint8_t month
month; january=1 [1-12]
void set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType datapoint_type, uint32_t value, uint8_t length, bool forced)
optional< InternalGPIOPin * > status_pin_
uint8_t hour
hours since midnight [0-23]
uint32_t last_rx_char_timestamp_
void force_set_boolean_datapoint_value(uint8_t datapoint_id, bool value)
void handle_command_(uint8_t command, uint8_t version, const uint8_t *buffer, size_t len)
void dump_config() override
uint8_t day_of_month
day of the month [1-31]
void IRAM_ATTR HOT delay(uint32_t ms)