mirror of
https://github.com/esphome/esphome.git
synced 2026-06-01 17:57:37 +08:00
[core] Add uint32_to_str helper and use in preferences (#15597)
This commit is contained in:
@@ -4,7 +4,6 @@
|
|||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <nvs_flash.h>
|
#include <nvs_flash.h>
|
||||||
#include <cinttypes>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -12,9 +11,6 @@ namespace esphome::esp32 {
|
|||||||
|
|
||||||
static const char *const TAG = "preferences";
|
static const char *const TAG = "preferences";
|
||||||
|
|
||||||
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
|
|
||||||
static constexpr size_t KEY_BUFFER_SIZE = 12;
|
|
||||||
|
|
||||||
struct NVSData {
|
struct NVSData {
|
||||||
uint32_t key;
|
uint32_t key;
|
||||||
SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
|
SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
|
||||||
@@ -51,8 +47,8 @@ bool ESP32PreferenceBackend::load(uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char key_str[KEY_BUFFER_SIZE];
|
char key_str[UINT32_MAX_STR_SIZE];
|
||||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
|
uint32_to_str(key_str, this->key);
|
||||||
size_t actual_len;
|
size_t actual_len;
|
||||||
esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
|
esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len);
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
@@ -108,8 +104,8 @@ bool ESP32Preferences::sync() {
|
|||||||
uint32_t last_key = 0;
|
uint32_t last_key = 0;
|
||||||
|
|
||||||
for (const auto &save : s_pending_save) {
|
for (const auto &save : s_pending_save) {
|
||||||
char key_str[KEY_BUFFER_SIZE];
|
char key_str[UINT32_MAX_STR_SIZE];
|
||||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
|
uint32_to_str(key_str, save.key);
|
||||||
ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str);
|
ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str);
|
||||||
if (this->is_changed_(this->nvs_handle, save, key_str)) {
|
if (this->is_changed_(this->nvs_handle, save, key_str)) {
|
||||||
esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size());
|
esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size());
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
#include "preferences.h"
|
#include "preferences.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <cinttypes>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -11,9 +10,6 @@ namespace esphome::libretiny {
|
|||||||
|
|
||||||
static const char *const TAG = "preferences";
|
static const char *const TAG = "preferences";
|
||||||
|
|
||||||
// Buffer size for converting uint32_t to string: max "4294967295" (10 chars) + null terminator + 1 padding
|
|
||||||
static constexpr size_t KEY_BUFFER_SIZE = 12;
|
|
||||||
|
|
||||||
struct NVSData {
|
struct NVSData {
|
||||||
uint32_t key;
|
uint32_t key;
|
||||||
SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
|
SmallInlineBuffer<8> data; // Most prefs fit in 8 bytes (covers fan, cover, select, etc.)
|
||||||
@@ -50,8 +46,8 @@ bool LibreTinyPreferenceBackend::load(uint8_t *data, size_t len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char key_str[KEY_BUFFER_SIZE];
|
char key_str[UINT32_MAX_STR_SIZE];
|
||||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key);
|
uint32_to_str(key_str, this->key);
|
||||||
fdb_blob_make(this->blob, data, len);
|
fdb_blob_make(this->blob, data, len);
|
||||||
size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob);
|
size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob);
|
||||||
if (actual_len != len) {
|
if (actual_len != len) {
|
||||||
@@ -92,8 +88,8 @@ bool LibreTinyPreferences::sync() {
|
|||||||
uint32_t last_key = 0;
|
uint32_t last_key = 0;
|
||||||
|
|
||||||
for (const auto &save : s_pending_save) {
|
for (const auto &save : s_pending_save) {
|
||||||
char key_str[KEY_BUFFER_SIZE];
|
char key_str[UINT32_MAX_STR_SIZE];
|
||||||
snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key);
|
uint32_to_str(key_str, save.key);
|
||||||
ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str);
|
ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str);
|
||||||
if (this->is_changed_(&this->db, save, key_str)) {
|
if (this->is_changed_(&this->db, save, key_str)) {
|
||||||
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size());
|
ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size());
|
||||||
|
|||||||
@@ -380,6 +380,20 @@ static char *format_hex_internal(char *buffer, size_t buffer_size, const uint8_t
|
|||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char *uint32_to_str_unchecked(char *buf, uint32_t val) {
|
||||||
|
if (val == 0) {
|
||||||
|
*buf++ = '0';
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
char *start = buf;
|
||||||
|
while (val > 0) {
|
||||||
|
*buf++ = '0' + (val % 10);
|
||||||
|
val /= 10;
|
||||||
|
}
|
||||||
|
std::reverse(start, buf);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length) {
|
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');
|
return format_hex_internal(buffer, buffer_size, data, length, 0, 'a');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1295,6 +1295,21 @@ inline char *int8_to_str(char *buf, int8_t val) {
|
|||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Minimum buffer size for uint32_to_str: 10 digits + null terminator.
|
||||||
|
static constexpr size_t UINT32_MAX_STR_SIZE = 11;
|
||||||
|
|
||||||
|
/// Write unsigned 32-bit integer to buffer (internal, no size check).
|
||||||
|
/// Buffer must have at least 10 bytes free. Returns pointer past last char written.
|
||||||
|
char *uint32_to_str_unchecked(char *buf, uint32_t val);
|
||||||
|
|
||||||
|
/// Write unsigned 32-bit integer to buffer with compile-time size check.
|
||||||
|
/// Null-terminates the output. Returns number of chars written (excluding null).
|
||||||
|
inline size_t uint32_to_str(std::span<char, UINT32_MAX_STR_SIZE> buf, uint32_t val) {
|
||||||
|
char *end = uint32_to_str_unchecked(buf.data(), val);
|
||||||
|
*end = '\0';
|
||||||
|
return static_cast<size_t>(end - buf.data());
|
||||||
|
}
|
||||||
|
|
||||||
/// Format byte array as lowercase hex to buffer (base implementation).
|
/// Format byte array as lowercase hex to buffer (base implementation).
|
||||||
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length);
|
char *format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include <benchmark/benchmark.h>
|
#include <benchmark/benchmark.h>
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
@@ -307,4 +309,58 @@ static void Base64Decode_32Bytes(benchmark::State &state) {
|
|||||||
}
|
}
|
||||||
BENCHMARK(Base64Decode_32Bytes);
|
BENCHMARK(Base64Decode_32Bytes);
|
||||||
|
|
||||||
|
// --- uint32_to_str() vs snprintf ---
|
||||||
|
|
||||||
|
static void Uint32ToStr_Small(benchmark::State &state) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
for (auto _ : state) {
|
||||||
|
for (int i = 0; i < kInnerIterations; i++) {
|
||||||
|
uint32_to_str(buf, 12345);
|
||||||
|
benchmark::DoNotOptimize(buf);
|
||||||
|
benchmark::ClobberMemory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||||
|
}
|
||||||
|
BENCHMARK(Uint32ToStr_Small);
|
||||||
|
|
||||||
|
static void Snprintf_Uint32_Small(benchmark::State &state) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
for (auto _ : state) {
|
||||||
|
for (int i = 0; i < kInnerIterations; i++) {
|
||||||
|
snprintf(buf, sizeof(buf), "%" PRIu32, static_cast<uint32_t>(12345));
|
||||||
|
benchmark::DoNotOptimize(buf);
|
||||||
|
benchmark::ClobberMemory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||||
|
}
|
||||||
|
BENCHMARK(Snprintf_Uint32_Small);
|
||||||
|
|
||||||
|
static void Uint32ToStr_Large(benchmark::State &state) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
for (auto _ : state) {
|
||||||
|
for (int i = 0; i < kInnerIterations; i++) {
|
||||||
|
uint32_to_str(buf, 4294967295u);
|
||||||
|
benchmark::DoNotOptimize(buf);
|
||||||
|
benchmark::ClobberMemory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||||
|
}
|
||||||
|
BENCHMARK(Uint32ToStr_Large);
|
||||||
|
|
||||||
|
static void Snprintf_Uint32_Large(benchmark::State &state) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
for (auto _ : state) {
|
||||||
|
for (int i = 0; i < kInnerIterations; i++) {
|
||||||
|
snprintf(buf, sizeof(buf), "%" PRIu32, static_cast<uint32_t>(4294967295u));
|
||||||
|
benchmark::DoNotOptimize(buf);
|
||||||
|
benchmark::ClobberMemory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.SetItemsProcessed(state.iterations() * kInnerIterations);
|
||||||
|
}
|
||||||
|
BENCHMARK(Snprintf_Uint32_Large);
|
||||||
|
|
||||||
} // namespace esphome::benchmarks
|
} // namespace esphome::benchmarks
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
|
||||||
|
namespace esphome::core::testing {
|
||||||
|
|
||||||
|
// --- uint32_to_str_unchecked() (internal, raw pointer) ---
|
||||||
|
|
||||||
|
TEST(Uint32ToStr, InternalZero) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
char *end = uint32_to_str_unchecked(buf, 0);
|
||||||
|
*end = '\0';
|
||||||
|
EXPECT_STREQ(buf, "0");
|
||||||
|
EXPECT_EQ(end - buf, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Uint32ToStr, InternalSingleDigit) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
char *end = uint32_to_str_unchecked(buf, 7);
|
||||||
|
*end = '\0';
|
||||||
|
EXPECT_STREQ(buf, "7");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Uint32ToStr, InternalMultiDigit) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
char *end = uint32_to_str_unchecked(buf, 12345);
|
||||||
|
*end = '\0';
|
||||||
|
EXPECT_STREQ(buf, "12345");
|
||||||
|
EXPECT_EQ(end - buf, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Uint32ToStr, InternalMaxValue) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
char *end = uint32_to_str_unchecked(buf, 4294967295u);
|
||||||
|
*end = '\0';
|
||||||
|
EXPECT_STREQ(buf, "4294967295");
|
||||||
|
EXPECT_EQ(end - buf, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Uint32ToStr, InternalPowersOfTen) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
char *end;
|
||||||
|
|
||||||
|
end = uint32_to_str_unchecked(buf, 10);
|
||||||
|
*end = '\0';
|
||||||
|
EXPECT_STREQ(buf, "10");
|
||||||
|
|
||||||
|
end = uint32_to_str_unchecked(buf, 100);
|
||||||
|
*end = '\0';
|
||||||
|
EXPECT_STREQ(buf, "100");
|
||||||
|
|
||||||
|
end = uint32_to_str_unchecked(buf, 1000000);
|
||||||
|
*end = '\0';
|
||||||
|
EXPECT_STREQ(buf, "1000000");
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- uint32_to_str() (public, span API) ---
|
||||||
|
|
||||||
|
TEST(Uint32ToStr, SpanZero) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
EXPECT_EQ(uint32_to_str(buf, 0), 1u);
|
||||||
|
EXPECT_STREQ(buf, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Uint32ToStr, SpanMultiDigit) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
EXPECT_EQ(uint32_to_str(buf, 12345), 5u);
|
||||||
|
EXPECT_STREQ(buf, "12345");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(Uint32ToStr, SpanMaxValue) {
|
||||||
|
char buf[UINT32_MAX_STR_SIZE];
|
||||||
|
EXPECT_EQ(uint32_to_str(buf, 4294967295u), 10u);
|
||||||
|
EXPECT_STREQ(buf, "4294967295");
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace esphome::core::testing
|
||||||
Reference in New Issue
Block a user