mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 10:12:52 +00:00
Merge remote-tracking branch 'origin/progmem-string-table-uint16' into bme680_bsec-tables-progmem
This commit is contained in:
+28
-7
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -68,6 +69,7 @@ template<size_t N> struct FixedString {
|
||||
};
|
||||
|
||||
/// Compile-time string table that packs strings into a single blob with offset lookup.
|
||||
/// Blobs over 255 bytes have no offsets and are scanned, so keep those for rarely read strings.
|
||||
/// Use PROGMEM_STRING_TABLE macro to instantiate with proper flash placement on ESP8266.
|
||||
///
|
||||
/// Example:
|
||||
@@ -78,6 +80,11 @@ template<size_t N> struct FixedString {
|
||||
template<FixedString... Strs> struct ProgmemStringTable {
|
||||
static constexpr size_t COUNT = sizeof...(Strs);
|
||||
static constexpr size_t BLOB_SIZE = (0 + ... + (Strs.size() + 1));
|
||||
/// Length of the longest string, for sizing a RAM copy on ESP8266
|
||||
static constexpr size_t MAX_LENGTH = std::max({Strs.size()...});
|
||||
/// One-byte offsets address up to 255 bytes; a larger blob has no offset table and is scanned instead,
|
||||
/// which costs less flash than wider offsets for the rarely read tables that get this big.
|
||||
static constexpr bool INDEXED = BLOB_SIZE <= UINT8_MAX;
|
||||
|
||||
/// Generate packed string blob at compile time
|
||||
static constexpr auto make_blob() {
|
||||
@@ -91,18 +98,28 @@ template<FixedString... Strs> struct ProgmemStringTable {
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Generate offset table at compile time (uint8_t limits blob to 255 bytes)
|
||||
/// Generate offset table at compile time
|
||||
static constexpr auto make_offsets() {
|
||||
static_assert(COUNT > 0, "PROGMEM_STRING_TABLE must contain at least one string");
|
||||
static_assert(COUNT <= 255, "PROGMEM_STRING_TABLE supports at most 255 strings with uint8_t indices");
|
||||
static_assert(BLOB_SIZE <= 255, "PROGMEM_STRING_TABLE blob exceeds 255 bytes; use fewer/shorter strings");
|
||||
std::array<uint8_t, COUNT> result{};
|
||||
size_t pos = 0, idx = 0;
|
||||
((result[idx++] = static_cast<uint8_t>(pos), pos += Strs.size() + 1), ...);
|
||||
return result;
|
||||
if constexpr (INDEXED) {
|
||||
std::array<uint8_t, COUNT> result{};
|
||||
size_t pos = 0, idx = 0;
|
||||
((result[idx++] = static_cast<uint8_t>(pos), pos += Strs.size() + 1), ...);
|
||||
return result;
|
||||
} else {
|
||||
return std::array<uint8_t, 0>{};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/// Skip idx strings in a packed NUL-separated blob that may live in flash.
|
||||
inline const char *progmem_skip_strings(const char *blob, uint8_t idx) {
|
||||
while (idx-- > 0)
|
||||
blob += ESPHOME_strlen_P(blob) + 1;
|
||||
return blob;
|
||||
}
|
||||
|
||||
// Forward declaration for LogString (defined in log.h)
|
||||
struct LogString;
|
||||
|
||||
@@ -115,6 +132,7 @@ struct LogString;
|
||||
static constexpr size_t COUNT = Table::COUNT; \
|
||||
static constexpr uint8_t LAST_INDEX = COUNT - 1; \
|
||||
static constexpr size_t BLOB_SIZE = Table::BLOB_SIZE; \
|
||||
static constexpr size_t MAX_LENGTH = Table::MAX_LENGTH; \
|
||||
static constexpr auto BLOB ESPHOME_PROGMEM_STRING_TABLE_STORAGE = Table::make_blob(); \
|
||||
static constexpr auto OFFSETS ESPHOME_PROGMEM_STRING_TABLE_STORAGE = Table::make_offsets(); \
|
||||
static const char *get_(uint8_t idx, uint8_t fallback) { \
|
||||
@@ -122,7 +140,10 @@ struct LogString;
|
||||
idx = fallback; \
|
||||
/* std::launder is used here to prevent the inter-procedural analysis that */ \
|
||||
/* causes the false positive that the string is not null terminated */ \
|
||||
return std::launder(&BLOB[::esphome::progmem_read_byte(&OFFSETS[idx])]); \
|
||||
if constexpr (Table::INDEXED) \
|
||||
return std::launder(&BLOB[::esphome::progmem_read_byte(&OFFSETS[idx])]); \
|
||||
else \
|
||||
return std::launder(::esphome::progmem_skip_strings(BLOB.data(), idx)); \
|
||||
} \
|
||||
static ::ProgmemStr get_progmem_str(uint8_t idx, uint8_t fallback) { \
|
||||
return reinterpret_cast<::ProgmemStr>(get_(idx, fallback)); \
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
|
||||
namespace esphome::core::testing {
|
||||
|
||||
PROGMEM_STRING_TABLE(SmallStrings, "OFF", "HEAT", "COOL", "UNKNOWN");
|
||||
|
||||
// Eight strings of 40 characters: a 328-byte blob, past what one-byte offsets can address, so it is scanned.
|
||||
PROGMEM_STRING_TABLE(LargeStrings, "0123456789012345678901234567890123456789",
|
||||
"1123456789012345678901234567890123456789", "2123456789012345678901234567890123456789",
|
||||
"3123456789012345678901234567890123456789", "4123456789012345678901234567890123456789",
|
||||
"5123456789012345678901234567890123456789", "6123456789012345678901234567890123456789",
|
||||
"7123456789012345678901234567890123456789");
|
||||
|
||||
// Five 50-character strings pack into exactly 255 bytes; one more byte switches to scanning.
|
||||
PROGMEM_STRING_TABLE(Exactly255, "01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789");
|
||||
PROGMEM_STRING_TABLE(Exactly256, "01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789",
|
||||
"01234567890123456789012345678901234567890123456789x");
|
||||
|
||||
TEST(ProgmemStringTable, ScansInsteadOfIndexingAfter255Bytes) {
|
||||
static_assert(Exactly255::BLOB_SIZE == 255);
|
||||
static_assert(Exactly255::Table::INDEXED);
|
||||
static_assert(Exactly256::BLOB_SIZE == 256);
|
||||
static_assert(!Exactly256::Table::INDEXED);
|
||||
static_assert(Exactly256::OFFSETS.empty());
|
||||
EXPECT_EQ(Exactly255::OFFSETS[4], 204);
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(Exactly256::get_progmem_str(4, 0)),
|
||||
"01234567890123456789012345678901234567890123456789x");
|
||||
}
|
||||
|
||||
TEST(ProgmemStringTable, SmallTableKeepsOneByteOffsets) {
|
||||
static_assert(SmallStrings::BLOB_SIZE <= 255);
|
||||
static_assert(SmallStrings::Table::INDEXED);
|
||||
static_assert(SmallStrings::MAX_LENGTH == 7);
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(SmallStrings::get_log_str(0, SmallStrings::LAST_INDEX)), "OFF");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(SmallStrings::get_log_str(2, SmallStrings::LAST_INDEX)), "COOL");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(SmallStrings::get_log_str(200, SmallStrings::LAST_INDEX)), "UNKNOWN");
|
||||
}
|
||||
|
||||
TEST(ProgmemStringTable, LargeTableIsScanned) {
|
||||
static_assert(LargeStrings::BLOB_SIZE > 255);
|
||||
static_assert(!LargeStrings::Table::INDEXED);
|
||||
static_assert(LargeStrings::MAX_LENGTH == 40);
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(LargeStrings::get_progmem_str(0, LargeStrings::LAST_INDEX)),
|
||||
"0123456789012345678901234567890123456789");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(LargeStrings::get_progmem_str(7, LargeStrings::LAST_INDEX)),
|
||||
"7123456789012345678901234567890123456789");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(LargeStrings::get_progmem_str(8, LargeStrings::LAST_INDEX)),
|
||||
"7123456789012345678901234567890123456789");
|
||||
EXPECT_STREQ(reinterpret_cast<const char *>(LargeStrings::get_progmem_str(3, LargeStrings::LAST_INDEX)),
|
||||
"3123456789012345678901234567890123456789");
|
||||
}
|
||||
|
||||
} // namespace esphome::core::testing
|
||||
Reference in New Issue
Block a user