From 0948e0359fd31fd96ddd4d1480e6f7b616186d86 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 7 Jan 2026 08:27:58 -1000 Subject: [PATCH] [core] Add integer overload for fnv1a_hash_extend (#13054) --- .../components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp | 6 +++++- esphome/components/sen5x/sen5x.cpp | 2 +- esphome/components/sgp30/sgp30.cpp | 2 +- esphome/components/sgp4x/sgp4x.cpp | 2 +- esphome/core/helpers.h | 10 ++++++++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp b/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp index 50eaf33add..2d74ba6b12 100644 --- a/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp +++ b/esphome/components/bme68x_bsec2_i2c/bme68x_bsec2_i2c.cpp @@ -31,7 +31,11 @@ void BME68xBSEC2I2CComponent::dump_config() { BME68xBSEC2Component::dump_config(); } -uint32_t BME68xBSEC2I2CComponent::get_hash() { return fnv1_hash("bme68x_bsec_state_" + to_string(this->address_)); } +uint32_t BME68xBSEC2I2CComponent::get_hash() { + char buf[22]; // "bme68x_bsec_state_" (18) + uint8_t max (3) + null + snprintf(buf, sizeof(buf), "bme68x_bsec_state_%u", this->address_); + return fnv1_hash(buf); +} int8_t BME68xBSEC2I2CComponent::read_bytes_wrapper(uint8_t a_register, uint8_t *data, uint32_t len, void *intfPtr) { ESP_LOGVV(TAG, "read_bytes_wrapper: reg = %u", a_register); diff --git a/esphome/components/sen5x/sen5x.cpp b/esphome/components/sen5x/sen5x.cpp index c72ccf2595..d5c9dfa3ae 100644 --- a/esphome/components/sen5x/sen5x.cpp +++ b/esphome/components/sen5x/sen5x.cpp @@ -158,7 +158,7 @@ void SEN5XComponent::setup() { // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict - uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), std::to_string(combined_serial)); + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), combined_serial); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/components/sgp30/sgp30.cpp b/esphome/components/sgp30/sgp30.cpp index 1326356437..18814405d4 100644 --- a/esphome/components/sgp30/sgp30.cpp +++ b/esphome/components/sgp30/sgp30.cpp @@ -75,7 +75,7 @@ void SGP30Component::setup() { // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict - uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), std::to_string(this->serial_number_)); + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), this->serial_number_); this->pref_ = global_preferences->make_preference(hash, true); if (this->store_baseline_ && this->pref_.load(&this->baselines_storage_)) { diff --git a/esphome/components/sgp4x/sgp4x.cpp b/esphome/components/sgp4x/sgp4x.cpp index 7c0f51c782..23589265ca 100644 --- a/esphome/components/sgp4x/sgp4x.cpp +++ b/esphome/components/sgp4x/sgp4x.cpp @@ -60,7 +60,7 @@ void SGP4xComponent::setup() { // Hash with config hash, version, and serial number // This ensures the baseline storage is cleared after OTA // Serial numbers are unique to each sensor, so multiple sensors can be used without conflict - uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), std::to_string(this->serial_number_)); + uint32_t hash = fnv1a_hash_extend(App.get_config_version_hash(), this->serial_number_); this->pref_ = global_preferences->make_preference(hash, true); if (this->pref_.load(&this->voc_baselines_storage_)) { diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 6c338797a9..9916078efb 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -404,6 +404,16 @@ constexpr uint32_t fnv1a_hash_extend(uint32_t hash, const char *str) { inline uint32_t fnv1a_hash_extend(uint32_t hash, const std::string &str) { return fnv1a_hash_extend(hash, str.c_str()); } +/// Extend a FNV-1a hash with an integer (hashes each byte). +template constexpr uint32_t fnv1a_hash_extend(uint32_t hash, T value) { + using UnsignedT = std::make_unsigned_t; + UnsignedT uvalue = static_cast(value); + for (size_t i = 0; i < sizeof(T); i++) { + hash ^= (uvalue >> (i * 8)) & 0xFF; + hash *= FNV1_PRIME; + } + return hash; +} /// Calculate a FNV-1a hash of \p str. constexpr uint32_t fnv1a_hash(const char *str) { return fnv1a_hash_extend(FNV1_OFFSET_BASIS, str); } inline uint32_t fnv1a_hash(const std::string &str) { return fnv1a_hash(str.c_str()); }