From beeb0c7c5af85fa9d05a5338a36b76a3d95084cf Mon Sep 17 00:00:00 2001 From: Oxan van Leeuwen Date: Sun, 12 Dec 2021 21:15:23 +0100 Subject: [PATCH] Introduce hex parsing & formatting helper functions (#2882) --- esphome/components/api/api_frame_helper.cpp | 10 +- .../esp32_ble_tracker/esp32_ble_tracker.cpp | 6 +- .../esp32_improv/esp32_improv_component.cpp | 2 +- esphome/components/md5/md5.cpp | 18 +--- esphome/components/md5/md5.h | 2 +- esphome/components/modbus/modbus.cpp | 4 +- .../switch/modbus_switch.cpp | 2 +- esphome/components/pn532_spi/pn532_spi.cpp | 8 +- .../components/remote_base/midea_protocol.h | 2 +- esphome/components/tuya/light/tuya_light.cpp | 12 +-- .../tuya/text_sensor/tuya_text_sensor.cpp | 2 +- esphome/components/tuya/tuya.cpp | 10 +- esphome/components/xiaomi_ble/xiaomi_ble.cpp | 16 +-- .../components/xiaomi_cgd1/xiaomi_cgd1.cpp | 2 +- .../components/xiaomi_cgdk2/xiaomi_cgdk2.cpp | 2 +- .../components/xiaomi_cgg1/xiaomi_cgg1.cpp | 2 +- .../xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp | 2 +- .../xiaomi_mhoc401/xiaomi_mhoc401.cpp | 2 +- esphome/core/helpers.cpp | 99 ++++++++++--------- esphome/core/helpers.h | 92 +++++++++++++++-- 20 files changed, 186 insertions(+), 109 deletions(-) diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index 23766ec1b1..151c2512de 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -252,7 +252,7 @@ APIError APINoiseFrameHelper::try_read_frame_(ParsedFrame *frame) { // uncomment for even more debugging #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); + ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str()); #endif frame->msg = std::move(rx_buf_); // consume msg @@ -546,7 +546,8 @@ APIError APINoiseFrameHelper::write_raw_(const struct iovec *iov, int iovcnt) { size_t total_write_len = 0; for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", + format_hex_pretty(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif total_write_len += iov[i].iov_len; } @@ -855,7 +856,7 @@ APIError APIPlaintextFrameHelper::try_read_frame_(ParsedFrame *frame) { // uncomment for even more debugging #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Received frame: %s", hexencode(rx_buf_).c_str()); + ESP_LOGVV(TAG, "Received frame: %s", format_hex_pretty(rx_buf_).c_str()); #endif frame->msg = std::move(rx_buf_); // consume msg @@ -934,7 +935,8 @@ APIError APIPlaintextFrameHelper::write_raw_(const struct iovec *iov, int iovcnt size_t total_write_len = 0; for (int i = 0; i < iovcnt; i++) { #ifdef HELPER_LOG_PACKETS - ESP_LOGVV(TAG, "Sending raw: %s", hexencode(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); + ESP_LOGVV(TAG, "Sending raw: %s", + format_hex_pretty(reinterpret_cast(iov[i].iov_base), iov[i].iov_len).c_str()); #endif total_write_len += iov[i].iov_len; } diff --git a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp index 303cb34aa7..e3de02e2d7 100644 --- a/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +++ b/esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp @@ -524,7 +524,7 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e ESP_LOGVV(TAG, " Service UUID: %s", uuid.to_string().c_str()); } for (auto &data : this->manufacturer_datas_) { - ESP_LOGVV(TAG, " Manufacturer data: %s", hexencode(data.data).c_str()); + ESP_LOGVV(TAG, " Manufacturer data: %s", format_hex_pretty(data.data).c_str()); if (this->get_ibeacon().has_value()) { auto ibeacon = this->get_ibeacon().value(); ESP_LOGVV(TAG, " iBeacon data:"); @@ -537,10 +537,10 @@ void ESPBTDevice::parse_scan_rst(const esp_ble_gap_cb_param_t::ble_scan_result_e for (auto &data : this->service_datas_) { ESP_LOGVV(TAG, " Service data:"); ESP_LOGVV(TAG, " UUID: %s", data.uuid.to_string().c_str()); - ESP_LOGVV(TAG, " Data: %s", hexencode(data.data).c_str()); + ESP_LOGVV(TAG, " Data: %s", format_hex_pretty(data.data).c_str()); } - ESP_LOGVV(TAG, "Adv data: %s", hexencode(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); + ESP_LOGVV(TAG, "Adv data: %s", format_hex_pretty(param.ble_adv, param.adv_data_len + param.scan_rsp_len).c_str()); #endif } void ESPBTDevice::parse_adv_(const esp_ble_gap_cb_param_t::ble_scan_result_evt_param ¶m) { diff --git a/esphome/components/esp32_improv/esp32_improv_component.cpp b/esphome/components/esp32_improv/esp32_improv_component.cpp index 22bebdfe98..788e7a9460 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.cpp +++ b/esphome/components/esp32_improv/esp32_improv_component.cpp @@ -219,7 +219,7 @@ void ESP32ImprovComponent::dump_config() { void ESP32ImprovComponent::process_incoming_data_() { uint8_t length = this->incoming_data_[1]; - ESP_LOGD(TAG, "Processing bytes - %s", hexencode(this->incoming_data_).c_str()); + ESP_LOGD(TAG, "Processing bytes - %s", format_hex_pretty(this->incoming_data_).c_str()); if (this->incoming_data_.size() - 3 == length) { this->set_error_(improv::ERROR_NONE); improv::ImprovCommand command = improv::parse_improv_data(this->incoming_data_); diff --git a/esphome/components/md5/md5.cpp b/esphome/components/md5/md5.cpp index c6ff783439..0528a87d0e 100644 --- a/esphome/components/md5/md5.cpp +++ b/esphome/components/md5/md5.cpp @@ -23,7 +23,7 @@ void MD5Digest::get_hex(char *output) { } } -bool MD5Digest::equals_bytes(const char *expected) { +bool MD5Digest::equals_bytes(const uint8_t *expected) { for (size_t i = 0; i < 16; i++) { if (expected[i] != this->digest_[i]) { return false; @@ -33,18 +33,10 @@ bool MD5Digest::equals_bytes(const char *expected) { } bool MD5Digest::equals_hex(const char *expected) { - for (size_t i = 0; i < 16; i++) { - auto high = parse_hex(expected[i * 2]); - auto low = parse_hex(expected[i * 2 + 1]); - if (!high.has_value() || !low.has_value()) { - return false; - } - auto value = (*high << 4) | *low; - if (value != this->digest_[i]) { - return false; - } - } - return true; + uint8_t parsed[16]; + if (!parse_hex(expected, parsed, 16)) + return false; + return equals_bytes(parsed); } } // namespace md5 diff --git a/esphome/components/md5/md5.h b/esphome/components/md5/md5.h index e40f419347..1c15c9e57d 100644 --- a/esphome/components/md5/md5.h +++ b/esphome/components/md5/md5.h @@ -44,7 +44,7 @@ class MD5Digest { void get_hex(char *output); /// Compare the digest against a provided byte-encoded digest (16 bytes). - bool equals_bytes(const char *expected); + bool equals_bytes(const uint8_t *expected); /// Compare the digest against a provided hex-encoded digest (32 bytes). bool equals_hex(const char *expected); diff --git a/esphome/components/modbus/modbus.cpp b/esphome/components/modbus/modbus.cpp index 9524f9daf4..9ee3137be9 100644 --- a/esphome/components/modbus/modbus.cpp +++ b/esphome/components/modbus/modbus.cpp @@ -181,7 +181,7 @@ void Modbus::send(uint8_t address, uint8_t function_code, uint16_t start_address this->flow_control_pin_->digital_write(false); waiting_for_response = address; last_send_ = millis(); - ESP_LOGV(TAG, "Modbus write: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Modbus write: %s", format_hex_pretty(data).c_str()); } // Helper function for lambdas @@ -202,7 +202,7 @@ void Modbus::send_raw(const std::vector &payload) { if (this->flow_control_pin_ != nullptr) this->flow_control_pin_->digital_write(false); waiting_for_response = payload[0]; - ESP_LOGV(TAG, "Modbus write raw: %s", hexencode(payload).c_str()); + ESP_LOGV(TAG, "Modbus write raw: %s", format_hex_pretty(payload).c_str()); last_send_ = millis(); } diff --git a/esphome/components/modbus_controller/switch/modbus_switch.cpp b/esphome/components/modbus_controller/switch/modbus_switch.cpp index c7c3c419d4..ca8d0be720 100644 --- a/esphome/components/modbus_controller/switch/modbus_switch.cpp +++ b/esphome/components/modbus_controller/switch/modbus_switch.cpp @@ -61,7 +61,7 @@ void ModbusSwitch::write_state(bool state) { } } if (!data.empty()) { - ESP_LOGV(TAG, "Modbus Switch write raw: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Modbus Switch write raw: %s", format_hex_pretty(data).c_str()); cmd = ModbusCommandItem::create_custom_command( this->parent_, data, [this, cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector &data) { diff --git a/esphome/components/pn532_spi/pn532_spi.cpp b/esphome/components/pn532_spi/pn532_spi.cpp index ec32e45b3d..be58f265b9 100644 --- a/esphome/components/pn532_spi/pn532_spi.cpp +++ b/esphome/components/pn532_spi/pn532_spi.cpp @@ -26,7 +26,7 @@ bool PN532Spi::write_data(const std::vector &data) { delay(2); // First byte, communication mode: Write data this->write_byte(0x01); - ESP_LOGV(TAG, "Writing data: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Writing data: %s", format_hex_pretty(data).c_str()); this->write_array(data.data(), data.size()); this->disable(); @@ -65,7 +65,7 @@ bool PN532Spi::read_data(std::vector &data, uint8_t len) { this->read_array(data.data(), len); this->disable(); data.insert(data.begin(), 0x01); - ESP_LOGV(TAG, "Read data: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Read data: %s", format_hex_pretty(data).c_str()); return true; } @@ -97,7 +97,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector &data) { std::vector header(7); this->read_array(header.data(), 7); - ESP_LOGV(TAG, "Header data: %s", hexencode(header).c_str()); + ESP_LOGV(TAG, "Header data: %s", format_hex_pretty(header).c_str()); if (header[0] != 0x00 && header[1] != 0x00 && header[2] != 0xFF) { // invalid packet @@ -127,7 +127,7 @@ bool PN532Spi::read_response(uint8_t command, std::vector &data) { this->read_array(data.data(), len + 1); this->disable(); - ESP_LOGV(TAG, "Response data: %s", hexencode(data).c_str()); + ESP_LOGV(TAG, "Response data: %s", format_hex_pretty(data).c_str()); uint8_t checksum = header[5] + header[6]; // TFI + Command response code for (int i = 0; i < len - 1; i++) { diff --git a/esphome/components/remote_base/midea_protocol.h b/esphome/components/remote_base/midea_protocol.h index 35ea23acfb..61e511601b 100644 --- a/esphome/components/remote_base/midea_protocol.h +++ b/esphome/components/remote_base/midea_protocol.h @@ -26,7 +26,7 @@ class MideaData { bool is_valid() const { return this->data_[OFFSET_CS] == this->calc_cs_(); } void finalize() { this->data_[OFFSET_CS] = this->calc_cs_(); } bool check_compliment(const MideaData &rhs) const; - std::string to_string() const { return hexencode(*this); } + std::string to_string() const { return format_hex_pretty(this->data_, sizeof(this->data_)); } // compare only 40-bits bool operator==(const MideaData &rhs) const { return !memcmp(this->data_, rhs.data_, OFFSET_CS); } enum MideaDataType : uint8_t { diff --git a/esphome/components/tuya/light/tuya_light.cpp b/esphome/components/tuya/light/tuya_light.cpp index 133ee1e557..ecd3802839 100644 --- a/esphome/components/tuya/light/tuya_light.cpp +++ b/esphome/components/tuya/light/tuya_light.cpp @@ -37,9 +37,9 @@ void TuyaLight::setup() { } if (rgb_id_.has_value()) { this->parent_->register_listener(*this->rgb_id_, [this](const TuyaDatapoint &datapoint) { - auto red = parse_hex(datapoint.value_string, 0, 2); - auto green = parse_hex(datapoint.value_string, 2, 2); - auto blue = parse_hex(datapoint.value_string, 4, 2); + auto red = parse_hex(datapoint.value_string.substr(0, 2)); + auto green = parse_hex(datapoint.value_string.substr(2, 2)); + auto blue = parse_hex(datapoint.value_string.substr(4, 2)); if (red.has_value() && green.has_value() && blue.has_value()) { auto call = this->state_->make_call(); call.set_rgb(float(*red) / 255, float(*green) / 255, float(*blue) / 255); @@ -48,9 +48,9 @@ void TuyaLight::setup() { }); } else if (hsv_id_.has_value()) { this->parent_->register_listener(*this->hsv_id_, [this](const TuyaDatapoint &datapoint) { - auto hue = parse_hex(datapoint.value_string, 0, 4); - auto saturation = parse_hex(datapoint.value_string, 4, 4); - auto value = parse_hex(datapoint.value_string, 8, 4); + auto hue = parse_hex(datapoint.value_string.substr(0, 4)); + auto saturation = parse_hex(datapoint.value_string.substr(4, 4)); + auto value = parse_hex(datapoint.value_string.substr(8, 4)); if (hue.has_value() && saturation.has_value() && value.has_value()) { float red, green, blue; hsv_to_rgb(*hue, float(*saturation) / 1000, float(*value) / 1000, red, green, blue); diff --git a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp index e939225453..0b51ba90c4 100644 --- a/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp +++ b/esphome/components/tuya/text_sensor/tuya_text_sensor.cpp @@ -14,7 +14,7 @@ void TuyaTextSensor::setup() { this->publish_state(datapoint.value_string); break; case TuyaDatapointType::RAW: { - std::string data = hexencode(datapoint.value_raw); + std::string data = format_hex_pretty(datapoint.value_raw); ESP_LOGD(TAG, "MCU reported text sensor %u is: %s", datapoint.id, data.c_str()); this->publish_state(data); break; diff --git a/esphome/components/tuya/tuya.cpp b/esphome/components/tuya/tuya.cpp index 404a70a80e..7ff8c66c44 100644 --- a/esphome/components/tuya/tuya.cpp +++ b/esphome/components/tuya/tuya.cpp @@ -34,7 +34,7 @@ void Tuya::dump_config() { } for (auto &info : this->datapoints_) { if (info.type == TuyaDatapointType::RAW) - ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, hexencode(info.value_raw).c_str()); + ESP_LOGCONFIG(TAG, " Datapoint %u: raw (value: %s)", info.id, format_hex_pretty(info.value_raw).c_str()); else if (info.type == TuyaDatapointType::BOOLEAN) ESP_LOGCONFIG(TAG, " Datapoint %u: switch (value: %s)", info.id, ONOFF(info.value_bool)); else if (info.type == TuyaDatapointType::INTEGER) @@ -104,7 +104,7 @@ bool Tuya::validate_message_() { // valid message const uint8_t *message_data = data + 6; ESP_LOGV(TAG, "Received Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", command, version, - hexencode(message_data, length).c_str(), static_cast(this->init_state_)); + format_hex_pretty(message_data, length).c_str(), static_cast(this->init_state_)); this->handle_command_(command, version, message_data, length); // return false to reset rx buffer @@ -253,7 +253,7 @@ void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) { 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, hexencode(datapoint.value_raw).c_str()); + 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) { @@ -348,7 +348,7 @@ void Tuya::send_raw_command_(TuyaCommand command) { } ESP_LOGV(TAG, "Sending Tuya: CMD=0x%02X VERSION=%u DATA=[%s] INIT_STATE=%u", static_cast(command.cmd), - version, hexencode(command.payload).c_str(), static_cast(this->init_state_)); + version, format_hex_pretty(command.payload).c_str(), static_cast(this->init_state_)); this->write_array({0x55, 0xAA, version, (uint8_t) command.cmd, len_hi, len_lo}); if (!command.payload.empty()) @@ -526,7 +526,7 @@ void Tuya::set_numeric_datapoint_value_(uint8_t datapoint_id, TuyaDatapointType } void Tuya::set_raw_datapoint_value_(uint8_t datapoint_id, const std::vector &value, bool forced) { - ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, hexencode(value).c_str()); + ESP_LOGD(TAG, "Setting datapoint %u to %s", datapoint_id, format_hex_pretty(value).c_str()); optional datapoint = this->get_datapoint_(datapoint_id); if (!datapoint.has_value()) { ESP_LOGW(TAG, "Setting unknown datapoint %u", datapoint_id); diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index 7588198c70..583b68a77b 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -217,7 +217,7 @@ optional parse_xiaomi_header(const esp32_ble_tracker::Service bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, const uint64_t &address) { if (!((raw.size() == 19) || ((raw.size() >= 22) && (raw.size() <= 24)))) { ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): data packet has wrong size (%d)!", raw.size()); - ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str()); + ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); return false; } @@ -274,12 +274,12 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c memcpy(mac_address + 4, mac_reverse + 1, 1); memcpy(mac_address + 5, mac_reverse, 1); ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed."); - ESP_LOGVV(TAG, " MAC address : %s", hexencode(mac_address, 6).c_str()); - ESP_LOGVV(TAG, " Packet : %s", hexencode(raw.data(), raw.size()).c_str()); - ESP_LOGVV(TAG, " Key : %s", hexencode(vector.key, vector.keysize).c_str()); - ESP_LOGVV(TAG, " Iv : %s", hexencode(vector.iv, vector.ivsize).c_str()); - ESP_LOGVV(TAG, " Cipher : %s", hexencode(vector.ciphertext, vector.datasize).c_str()); - ESP_LOGVV(TAG, " Tag : %s", hexencode(vector.tag, vector.tagsize).c_str()); + ESP_LOGVV(TAG, " MAC address : %s", format_hex_pretty(mac_address, 6).c_str()); + ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); + ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str()); + ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str()); + ESP_LOGVV(TAG, " Cipher : %s", format_hex_pretty(vector.ciphertext, vector.datasize).c_str()); + ESP_LOGVV(TAG, " Tag : %s", format_hex_pretty(vector.tag, vector.tagsize).c_str()); mbedtls_ccm_free(&ctx); return false; } @@ -295,7 +295,7 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c raw[0] &= ~0x08; ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption passed."); - ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", hexencode(raw.data() + cipher_pos, vector.datasize).c_str(), + ESP_LOGVV(TAG, " Plaintext : %s, Packet : %d", format_hex_pretty(raw.data() + cipher_pos, vector.datasize).c_str(), static_cast(raw[4])); mbedtls_ccm_free(&ctx); diff --git a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp index 97bbd6e6d6..baf9cb8075 100644 --- a/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp +++ b/esphome/components/xiaomi_cgd1/xiaomi_cgd1.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgd1"; void XiaomiCGD1::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGD1"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp index a97ca93206..c74794f4f4 100644 --- a/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp +++ b/esphome/components/xiaomi_cgdk2/xiaomi_cgdk2.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgdk2"; void XiaomiCGDK2::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGDK2"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp index e1f83e4ddd..c20c7578d0 100644 --- a/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp +++ b/esphome/components/xiaomi_cgg1/xiaomi_cgg1.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_cgg1"; void XiaomiCGG1::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi CGG1"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp index 547cc7c114..d0319c9474 100644 --- a/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp +++ b/esphome/components/xiaomi_lywsd03mmc/xiaomi_lywsd03mmc.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_lywsd03mmc"; void XiaomiLYWSD03MMC::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi LYWSD03MMC"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp index 0cad5c67b2..9ec2b10e12 100644 --- a/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp +++ b/esphome/components/xiaomi_mhoc401/xiaomi_mhoc401.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "xiaomi_mhoc401"; void XiaomiMHOC401::dump_config() { ESP_LOGCONFIG(TAG, "Xiaomi MHOC401"); - ESP_LOGCONFIG(TAG, " Bindkey: %s", hexencode(this->bindkey_, 16).c_str()); + ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str()); LOG_SENSOR(" ", "Temperature", this->temperature_); LOG_SENSOR(" ", "Humidity", this->humidity_); LOG_SENSOR(" ", "Battery Level", this->battery_level_); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 6678eddbff..b82a2666e7 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -244,38 +244,6 @@ std::string to_string(long double val) { return buf; } -optional parse_hex(const char chr) { - int out = chr; - if (out >= '0' && out <= '9') - return (out - '0'); - if (out >= 'A' && out <= 'F') - return (10 + (out - 'A')); - if (out >= 'a' && out <= 'f') - return (10 + (out - 'a')); - return {}; -} - -optional parse_hex(const std::string &str, size_t start, size_t length) { - if (str.length() < start) { - return {}; - } - size_t end = start + length; - if (str.length() < end) { - return {}; - } - int out = 0; - for (size_t i = start; i < end; i++) { - char chr = str[i]; - auto digit = parse_hex(chr); - if (!digit.has_value()) { - ESP_LOGW(TAG, "Can't convert '%s' to number, invalid character %c!", str.substr(start, length).c_str(), chr); - return {}; - } - out = (out << 4) | *digit; - } - return out; -} - uint32_t fnv1_hash(const std::string &str) { uint32_t hash = 2166136261UL; for (char c : str) { @@ -355,22 +323,6 @@ std::string str_sprintf(const char *fmt, ...) { return str; } -std::string hexencode(const uint8_t *data, uint32_t len) { - char buf[20]; - std::string res; - for (size_t i = 0; i < len; i++) { - if (i + 1 != len) { - sprintf(buf, "%02X.", data[i]); - } else { - sprintf(buf, "%02X ", data[i]); - } - res += buf; - } - sprintf(buf, "(%u)", len); - res += buf; - return res; -} - void rgb_to_hsv(float red, float green, float blue, int &hue, float &saturation, float &value) { float max_color_value = std::max(std::max(red, green), blue); float min_color_value = std::min(std::min(red, green), blue); @@ -445,6 +397,8 @@ IRAM_ATTR InterruptLock::~InterruptLock() { portENABLE_INTERRUPTS(); } // --------------------------------------------------------------------------------------------------------------------- +// Strings + std::string str_truncate(const std::string &str, size_t length) { return str.length() > length ? str.substr(0, length) : str; } @@ -468,4 +422,53 @@ std::string str_sanitize(const std::string &str) { return out; } +// Parsing & formatting + +size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) { + uint8_t val; + size_t chars = std::min(length, 2 * count); + for (size_t i = 2 * count - chars; i < 2 * count; i++, str++) { + if (*str >= '0' && *str <= '9') + val = *str - '0'; + else if (*str >= 'A' && *str <= 'F') + val = 10 + (*str - 'A'); + else if (*str >= 'a' && *str <= 'f') + val = 10 + (*str - 'a'); + else + return 0; + data[i >> 1] = !(i & 1) ? val << 4 : data[i >> 1] | val; + } + return chars; +} + +static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } +std::string format_hex(const uint8_t *data, size_t length) { + std::string ret; + ret.resize(length * 2); + for (size_t i = 0; i < length; i++) { + ret[2 * i] = format_hex_char((data[i] & 0xF0) >> 4); + ret[2 * i + 1] = format_hex_char(data[i] & 0x0F); + } + return ret; +} +std::string format_hex(std::vector data) { return format_hex(data.data(), data.size()); } + +static char format_hex_pretty_char(uint8_t v) { return v >= 10 ? 'A' + (v - 10) : '0' + v; } +std::string format_hex_pretty(const uint8_t *data, size_t length) { + if (length == 0) + return ""; + std::string ret; + ret.resize(3 * length - 1); + for (size_t i = 0; i < length; i++) { + ret[3 * i] = format_hex_pretty_char((data[i] & 0xF0) >> 4); + ret[3 * i + 1] = format_hex_pretty_char(data[i] & 0x0F); + if (i != length - 1) + ret[3 * i + 2] = '.'; + } + if (length > 4) + return ret + " (" + to_string(length) + ")"; + return ret; +} +std::string format_hex_pretty(std::vector data) { return format_hex_pretty(data.data(), data.size()); } + } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index db507f824d..90f35ee4ca 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -45,8 +46,6 @@ std::string to_string(unsigned long long val); // NOLINT std::string to_string(float val); std::string to_string(double val); std::string to_string(long double val); -optional parse_hex(const std::string &str, size_t start, size_t length); -optional parse_hex(char chr); /// Compare string a to string b (ignoring case) and return whether they are equal. bool str_equals_case_insensitive(const std::string &a, const std::string &b); @@ -186,10 +185,6 @@ enum ParseOnOffState { ParseOnOffState parse_on_off(const char *str, const char *on = nullptr, const char *off = nullptr); -// Encode raw data to a human-readable string (for debugging) -std::string hexencode(const uint8_t *data, uint32_t len); -template std::string hexencode(const T &data) { return hexencode(data.data(), data.size()); } - // https://stackoverflow.com/questions/7858817/unpacking-a-tuple-to-call-a-matching-function-pointer/7858971#7858971 template struct seq {}; // NOLINT template struct gens : gens {}; // NOLINT @@ -408,6 +403,77 @@ optional parse_number(const std::string &str) { return parse_number(str.c_str()); } +/** Parse bytes from a hex-encoded string into a byte array. + * + * When \p len is less than \p 2*count, the result is written to the back of \p data (i.e. this function treats \p str + * as if it were padded with zeros at the front). + * + * @param str String to read from. + * @param len Length of \p str (excluding optional null-terminator), is a limit on the number of characters parsed. + * @param data Byte array to write to. + * @param count Length of \p data. + * @return The number of characters parsed from \p str. + */ +size_t parse_hex(const char *str, size_t len, uint8_t *data, size_t count); +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into array \p data. +inline bool parse_hex(const char *str, uint8_t *data, size_t count) { + return parse_hex(str, strlen(str), data, count) == 2 * count; +} +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into array \p data. +inline bool parse_hex(const std::string &str, uint8_t *data, size_t count) { + return parse_hex(str.c_str(), str.length(), data, count) == 2 * count; +} +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into vector \p data. +inline bool parse_hex(const char *str, std::vector &data, size_t count) { + data.resize(count); + return parse_hex(str, strlen(str), data.data(), count) == 2 * count; +} +/// Parse \p count bytes from the hex-encoded string \p str of at least \p 2*count characters into vector \p data. +inline bool parse_hex(const std::string &str, std::vector &data, size_t count) { + data.resize(count); + return parse_hex(str.c_str(), str.length(), data.data(), count) == 2 * count; +} +/** Parse a hex-encoded string into an unsigned integer. + * + * @param str String to read from, starting with the most significant byte. + * @param len Length of \p str (excluding optional null-terminator), is a limit on the number of characters parsed. + */ +template::value, int> = 0> +optional parse_hex(const char *str, size_t len) { + T val = 0; + if (len > 2 * sizeof(T) || parse_hex(str, len, reinterpret_cast(&val), sizeof(T)) == 0) + return {}; + return convert_big_endian(val); +} +/// Parse a hex-encoded null-terminated string (starting with the most significant byte) into an unsigned integer. +template::value, int> = 0> optional parse_hex(const char *str) { + return parse_hex(str, strlen(str)); +} +/// Parse a hex-encoded null-terminated string (starting with the most significant byte) into an unsigned integer. +template::value, int> = 0> optional parse_hex(const std::string &str) { + return parse_hex(str.c_str(), str.length()); +} + +/// Format the byte array \p data of length \p len in lowercased hex. +std::string format_hex(const uint8_t *data, size_t length); +/// Format the vector \p data in lowercased hex. +std::string format_hex(std::vector data); +/// Format an unsigned integer in lowercased hex, starting with the most significant byte. +template::value, int> = 0> std::string format_hex(T val) { + val = convert_big_endian(val); + return format_hex(reinterpret_cast(&val), sizeof(T)); +} + +/// Format the byte array \p data of length \p len in pretty-printed, human-readable hex. +std::string format_hex_pretty(const uint8_t *data, size_t length); +/// Format the vector \p data in pretty-printed, human-readable hex. +std::string format_hex_pretty(std::vector data); +/// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte. +template::value, int> = 0> std::string format_hex_pretty(T val) { + val = convert_big_endian(val); + return format_hex_pretty(reinterpret_cast(&val), sizeof(T)); +} + ///@} /// @name Number manipulation @@ -420,4 +486,18 @@ template T remap(U value, U min, U max, T min_out, T max ///@} +/// @name Deprecated functions +///@{ + +ESPDEPRECATED("hexencode() is deprecated, use format_hex_pretty() instead.", "2022.1") +inline std::string hexencode(const uint8_t *data, uint32_t len) { return format_hex_pretty(data, len); } + +template +ESPDEPRECATED("hexencode() is deprecated, use format_hex_pretty() instead.", "2022.1") +std::string hexencode(const T &data) { + return hexencode(data.data(), data.size()); +} + +///@} + } // namespace esphome