From 42984fa72a9541bc3e36d537893360248965f80e Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Thu, 3 Feb 2022 18:50:42 -0800 Subject: [PATCH] Handle Tuya multi-datapoint messages (#3159) Co-authored-by: Samuel Sieb --- esphome/components/tuya/tuya.cpp | 189 ++++++++++++++++--------------- esphome/components/tuya/tuya.h | 2 +- 2 files changed, 98 insertions(+), 93 deletions(-) diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index f2dceed33f..1fbca7796d 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -199,7 +199,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); }); this->initialized_callback_.call(); } - this->handle_datapoint_(buffer, len); + this->handle_datapoints_(buffer, len); break; case TuyaCommandType::DATAPOINT_QUERY: break; @@ -224,105 +224,110 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff } } -void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { - if (len < 2) - return; +void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) { + while (len >= 4) { + TuyaDatapoint datapoint{}; + datapoint.id = buffer[0]; + datapoint.type = (TuyaDatapointType) buffer[1]; + datapoint.value_uint = 0; - TuyaDatapoint datapoint{}; - datapoint.id = buffer[0]; - datapoint.type = (TuyaDatapointType) buffer[1]; - datapoint.value_uint = 0; - - // Drop update if datapoint is in ignore_mcu_datapoint_update list - for (uint8_t i : this->ignore_mcu_update_on_datapoints_) { - if (datapoint.id == i) { - ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id); + size_t data_size = (buffer[2] << 8) + buffer[3]; + const uint8_t *data = buffer + 4; + size_t data_len = len - 4; + if (data_size > data_len) { + ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu > %zu)", datapoint.id, data_size, data_len); return; } - } - size_t data_size = (buffer[2] << 8) + buffer[3]; - const uint8_t *data = buffer + 4; - size_t data_len = len - 4; - if (data_size > data_len) { - ESP_LOGW(TAG, "Datapoint %u has extra bytes that will be ignored (%zu > %zu)", datapoint.id, data_size, data_len); - } else if (data_size < data_len) { - ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu < %zu)", datapoint.id, data_size, data_len); - return; - } - datapoint.len = data_len; + datapoint.len = data_size; - switch (datapoint.type) { - case TuyaDatapointType::RAW: - datapoint.value_raw = std::vector(data, data + data_len); - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str()); - break; - case TuyaDatapointType::BOOLEAN: - if (data_len != 1) { - ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_len); - return; - } - datapoint.value_bool = data[0]; - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool)); - break; - case TuyaDatapointType::INTEGER: - if (data_len != 4) { - ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_len); - return; - } - datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]); - ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int); - break; - case TuyaDatapointType::STRING: - datapoint.value_string = std::string(reinterpret_cast(data), data_len); - ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str()); - break; - case TuyaDatapointType::ENUM: - if (data_len != 1) { - ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_len); - return; - } - datapoint.value_enum = data[0]; - ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum); - break; - case TuyaDatapointType::BITMASK: - switch (data_len) { - case 1: - datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]); - break; - case 2: - datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]); - break; - case 4: - datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]); - break; - default: - ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_len); + switch (datapoint.type) { + case TuyaDatapointType::RAW: + datapoint.value_raw = std::vector(data, data + data_size); + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str()); + break; + case TuyaDatapointType::BOOLEAN: + if (data_size != 1) { + ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_size); return; - } - ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask); - break; - default: - ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast(datapoint.type)); - return; - } - - // Update internal datapoints - bool found = false; - for (auto &other : this->datapoints_) { - if (other.id == datapoint.id) { - other = datapoint; - found = true; + } + datapoint.value_bool = data[0]; + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool)); + break; + case TuyaDatapointType::INTEGER: + if (data_size != 4) { + ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_size); + return; + } + datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]); + ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int); + break; + case TuyaDatapointType::STRING: + datapoint.value_string = std::string(reinterpret_cast(data), data_size); + ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str()); + break; + case TuyaDatapointType::ENUM: + if (data_size != 1) { + ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_size); + return; + } + datapoint.value_enum = data[0]; + ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum); + break; + case TuyaDatapointType::BITMASK: + switch (data_size) { + case 1: + datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]); + break; + case 2: + datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]); + break; + case 4: + datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]); + break; + default: + ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_size); + return; + } + ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask); + break; + default: + ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast(datapoint.type)); + return; } - } - if (!found) { - this->datapoints_.push_back(datapoint); - } - // Run through listeners - for (auto &listener : this->listeners_) { - if (listener.datapoint_id == datapoint.id) - listener.on_datapoint(datapoint); + len -= data_size + 4; + buffer = data + data_size; + + // drop update if datapoint is in ignore_mcu_datapoint_update list + bool skip = false; + for (auto i : this->ignore_mcu_update_on_datapoints_) { + if (datapoint.id == i) { + ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id); + skip = true; + break; + } + } + if (skip) + continue; + + // Update internal datapoints + bool found = false; + for (auto &other : this->datapoints_) { + if (other.id == datapoint.id) { + other = datapoint; + found = true; + } + } + if (!found) { + this->datapoints_.push_back(datapoint); + } + + // Run through listeners + for (auto &listener : this->listeners_) { + if (listener.datapoint_id == datapoint.id) + listener.on_datapoint(datapoint); + } } } diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index c46d61119e..3828c49b48 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -101,7 +101,7 @@ class Tuya : public Component, public uart::UARTDevice { protected: void handle_char_(uint8_t c); - void handle_datapoint_(const uint8_t *buffer, size_t len); + void handle_datapoints_(const uint8_t *buffer, size_t len); optional get_datapoint_(uint8_t datapoint_id); bool validate_message_();