1
0
mirror of https://github.com/esphome/esphome.git synced 2026-02-08 08:41:59 +00:00

Merge branch 'tuya_format_hex' into integration

This commit is contained in:
J. Nick Koston
2025-12-27 16:34:37 -10:00
5 changed files with 21 additions and 219 deletions

View File

@@ -30,7 +30,7 @@ class HmacMD5 {
void get_bytes(uint8_t *output);
/// Retrieve the HMAC-MD5 digest as hex characters.
/// The output must be able to hold 32 bytes or more.
/// The output must be able to hold 33 bytes or more (32 hex chars + null terminator).
void get_hex(char *output);
/// Compare the digest against a provided byte-encoded digest (16 bytes).

View File

@@ -35,7 +35,7 @@ class HmacSHA256 {
void get_bytes(uint8_t *output);
/// Retrieve the HMAC-SHA256 digest as hex characters.
/// The output must be able to hold 64 bytes or more.
/// The output must be able to hold 65 bytes or more (64 hex chars + null terminator).
void get_hex(char *output);
/// Compare the digest against a provided byte-encoded digest (32 bytes).

View File

@@ -20,6 +20,8 @@ static const char *const TAG = "tuya";
static const int COMMAND_DELAY = 10;
static const int RECEIVE_TIMEOUT = 300;
static const int MAX_RETRIES = 5;
// Max bytes to log for datapoint values (larger values are truncated)
static constexpr size_t MAX_DATAPOINT_LOG_BYTES = 16;
void Tuya::setup() {
this->set_interval("heartbeat", 15000, [this] { this->send_empty_command_(TuyaCommandType::HEARTBEAT); });
@@ -122,8 +124,11 @@ bool Tuya::validate_message_() {
// valid message
const uint8_t *message_data = data + 6;
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version,
format_hex_pretty(message_data, length).c_str(), static_cast<uint8_t>(this->init_state_));
format_hex_pretty_to(hex_buf, message_data, length), static_cast<uint8_t>(this->init_state_));
#endif
this->handle_command_(command, version, message_data, length);
// return false to reset rx buffer
@@ -349,7 +354,11 @@ void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) {
switch (datapoint.type) {
case TuyaDatapointType::RAW:
datapoint.value_raw = std::vector<uint8_t>(data, data + data_size);
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
{
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id,
format_hex_pretty_to(hex_buf, datapoint.value_raw.data(), datapoint.value_raw.size()));
}
break;
case TuyaDatapointType::BOOLEAN:
if (data_size != 1) {
@@ -460,8 +469,12 @@ void Tuya::send_raw_command_(TuyaCommand command) {
break;
}
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast<uint8_t>(command.cmd),
version, format_hex_pretty(command.payload).c_str(), static_cast<uint8_t>(this->init_state_));
version, format_hex_pretty_to(hex_buf, command.payload.data(), command.payload.size()),
static_cast<uint8_t>(this->init_state_));
#endif
this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo});
if (!command.payload.empty())
@@ -675,7 +688,8 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType
}
void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector<uint8_t> &value, bool forced) {
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str());
char hex_buf[format_hex_pretty_size(MAX_DATAPOINT_LOG_BYTES)];
ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty_to(hex_buf, value.data(), value.size()));
optional<TuyaDatapoint> datapoint = this->get_datapoint_(datapoint_id);
if (!datapoint.has_value()) {
ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id);

View File

@@ -25,7 +25,7 @@ class HashBase {
/// Retrieve the hash as bytes
void get_bytes(uint8_t *output) { memcpy(output, this->digest_, this->get_size()); }
/// Retrieve the hash as hex characters
/// Retrieve the hash as hex characters. Output buffer must hold get_size() * 2 + 1 bytes.
void get_hex(char *output) { format_hex_to(output, this->get_size() * 2 + 1, this->digest_, this->get_size()); }
/// Compare the hash against a provided byte-encoded hash

View File

@@ -1,212 +0,0 @@
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <cstring>
// Copy the implementations to test them in isolation
inline char format_hex_char(uint8_t v, char base) { return v >= 10 ? base + (v - 10) : '0' + v; }
inline char format_hex_char(uint8_t v) { return format_hex_char(v, 'a'); }
inline char format_hex_pretty_char(uint8_t v) { return format_hex_char(v, 'A'); }
constexpr size_t format_hex_pretty_size(size_t byte_count) { return byte_count * 3; }
static char *format_hex_internal(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator,
char base) {
if (length == 0) {
buffer[0] = '\0';
return buffer;
}
uint8_t stride = separator ? 3 : 2;
size_t max_bytes = separator ? (buffer_size / stride) : ((buffer_size - 1) / stride);
if (max_bytes == 0) {
buffer[0] = '\0';
return buffer;
}
if (length > max_bytes) {
length = max_bytes;
}
for (size_t i = 0; i < length; i++) {
size_t pos = i * stride;
buffer[pos] = format_hex_char(data[i] >> 4, base);
buffer[pos + 1] = format_hex_char(data[i] & 0x0F, base);
if (separator && i < length - 1) {
buffer[pos + 2] = separator;
}
}
buffer[length * stride - (separator ? 1 : 0)] = '\0';
return buffer;
}
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) {
return format_hex_internal(buffer, buffer_size, data, length, 0, 'a');
}
char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator) {
return format_hex_internal(buffer, buffer_size, data, length, separator, 'A');
}
template<size_t N>
char *format_hex_pretty_to(char (&buffer)[N], const uint8_t *data, size_t length, char separator = ':') {
return format_hex_pretty_to(buffer, N, data, length, separator);
}
static constexpr size_t MAC_ADDRESS_SIZE = 6;
static constexpr size_t MAC_ADDRESS_PRETTY_BUFFER_SIZE = format_hex_pretty_size(MAC_ADDRESS_SIZE);
static constexpr size_t MAC_ADDRESS_BUFFER_SIZE = MAC_ADDRESS_SIZE * 2 + 1;
inline void format_mac_addr_upper(const uint8_t *mac, char *output) {
format_hex_pretty_to(output, MAC_ADDRESS_PRETTY_BUFFER_SIZE, mac, MAC_ADDRESS_SIZE, ':');
}
inline void format_mac_addr_lower_no_sep(const uint8_t *mac, char *output) {
format_hex_to(output, MAC_ADDRESS_BUFFER_SIZE, mac, MAC_ADDRESS_SIZE);
}
// Tests
void test_format_hex_char_base() {
assert(format_hex_char(0, 'a') == '0');
assert(format_hex_char(9, 'a') == '9');
assert(format_hex_char(10, 'a') == 'a');
assert(format_hex_char(15, 'a') == 'f');
assert(format_hex_char(0, 'A') == '0');
assert(format_hex_char(10, 'A') == 'A');
assert(format_hex_char(15, 'A') == 'F');
printf("✓ format_hex_char with base\n");
}
void test_format_hex_char_lowercase() {
assert(format_hex_char(0) == '0');
assert(format_hex_char(10) == 'a');
assert(format_hex_char(15) == 'f');
printf("✓ format_hex_char lowercase\n");
}
void test_format_hex_pretty_char_uppercase() {
assert(format_hex_pretty_char(0) == '0');
assert(format_hex_pretty_char(10) == 'A');
assert(format_hex_pretty_char(15) == 'F');
printf("✓ format_hex_pretty_char uppercase\n");
}
void test_format_hex_to() {
uint8_t data[] = {0xde, 0xad, 0xbe, 0xef};
char buf[9];
format_hex_to(buf, sizeof(buf), data, 4);
assert(strcmp(buf, "deadbeef") == 0);
printf("✓ format_hex_to lowercase\n");
}
void test_format_hex_pretty_to_colon() {
uint8_t data[] = {0xDE, 0xAD, 0xBE, 0xEF};
char buf[12];
format_hex_pretty_to(buf, sizeof(buf), data, 4, ':');
assert(strcmp(buf, "DE:AD:BE:EF") == 0);
printf("✓ format_hex_pretty_to with colon\n");
}
void test_format_hex_pretty_to_dot() {
uint8_t data[] = {0xAA, 0xBB, 0xCC};
char buf[9];
format_hex_pretty_to(buf, sizeof(buf), data, 3, '.');
assert(strcmp(buf, "AA.BB.CC") == 0);
printf("✓ format_hex_pretty_to with dot\n");
}
void test_buffer_overflow_protection() {
uint8_t data[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE};
char buf[11];
format_hex_pretty_to(buf, 11, data, 5, ':');
assert(strcmp(buf, "AA:BB:CC") == 0);
printf("✓ buffer overflow protection\n");
}
void test_exact_fit() {
uint8_t data[] = {0xAA, 0xBB, 0xCC, 0xDD};
char buf[12];
format_hex_pretty_to(buf, 12, data, 4, ':');
assert(strcmp(buf, "AA:BB:CC:DD") == 0);
printf("✓ exact fit buffer\n");
}
void test_mac_addr_upper() {
uint8_t mac[] = {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
char buf[18];
format_mac_addr_upper(mac, buf);
assert(strcmp(buf, "AA:BB:CC:DD:EE:FF") == 0);
printf("✓ format_mac_addr_upper\n");
}
void test_mac_addr_lower_no_sep() {
uint8_t mac[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
char buf[13];
format_mac_addr_lower_no_sep(mac, buf);
assert(strcmp(buf, "aabbccddeeff") == 0);
printf("✓ format_mac_addr_lower_no_sep\n");
}
void test_empty() {
uint8_t data[] = {0xAA};
char buf[12];
format_hex_pretty_to(buf, sizeof(buf), data, 0, ':');
assert(strcmp(buf, "") == 0);
printf("✓ empty data\n");
}
void test_single_byte() {
uint8_t data[] = {0x42};
char buf[3];
format_hex_pretty_to(buf, sizeof(buf), data, 1, ':');
assert(strcmp(buf, "42") == 0);
printf("✓ single byte\n");
}
void test_template() {
uint8_t data[] = {0xDE, 0xAD, 0xBE, 0xEF};
char buf[format_hex_pretty_size(4)];
format_hex_pretty_to(buf, data, 4);
assert(strcmp(buf, "DE:AD:BE:EF") == 0);
printf("✓ template version\n");
}
void test_no_separator() {
uint8_t data[] = {0xDE, 0xAD};
char buf[5];
format_hex_pretty_to(buf, sizeof(buf), data, 2, 0);
assert(strcmp(buf, "DEAD") == 0);
printf("✓ no separator\n");
}
void test_constexpr() {
static_assert(format_hex_pretty_size(1) == 3, "");
static_assert(format_hex_pretty_size(4) == 12, "");
static_assert(format_hex_pretty_size(6) == 18, "");
static_assert(MAC_ADDRESS_SIZE == 6, "");
static_assert(MAC_ADDRESS_PRETTY_BUFFER_SIZE == 18, "");
static_assert(MAC_ADDRESS_BUFFER_SIZE == 13, "");
printf("✓ constexpr values\n");
}
int main() {
printf("Running hex formatting tests...\n\n");
test_format_hex_char_base();
test_format_hex_char_lowercase();
test_format_hex_pretty_char_uppercase();
test_format_hex_to();
test_format_hex_pretty_to_colon();
test_format_hex_pretty_to_dot();
test_buffer_overflow_protection();
test_exact_fit();
test_mac_addr_upper();
test_mac_addr_lower_no_sep();
test_empty();
test_single_byte();
test_template();
test_no_separator();
test_constexpr();
printf("\n✅ All 15 tests passed!\n");
return 0;
}