From 534584ab31c23870e66b5b77ee6dc27d530e9428 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 30 Jan 2026 22:35:10 -0600 Subject: [PATCH 1/5] reduce dupe code --- esphome/components/cover/cover.cpp | 5 +---- esphome/components/cover/cover.h | 2 -- esphome/components/logger/logger.cpp | 5 +---- esphome/components/sensor/sensor.cpp | 6 ++--- esphome/components/valve/valve.cpp | 5 +---- esphome/components/valve/valve.h | 2 -- .../wifi/wifi_component_esp8266.cpp | 18 ++++----------- esphome/core/progmem.h | 22 +++++++++++++------ 8 files changed, 24 insertions(+), 41 deletions(-) diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 186db73da5..372172c242 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -26,10 +26,7 @@ const LogString *cover_command_to_str(float pos) { PROGMEM_STRING_TABLE(CoverOperationStrings, "IDLE", "OPENING", "CLOSING", "UNKNOWN"); const LogString *cover_operation_to_str(CoverOperation op) { - uint8_t index = static_cast(op); - if (index > COVER_OPERATION_LAST) - index = COVER_OPERATION_UNKNOWN_INDEX; - return CoverOperationStrings::get_log_str(index); + return CoverOperationStrings::get_log_str(static_cast(op), CoverOperationStrings::LAST_INDEX); } Cover::Cover() : position{COVER_OPEN} {} diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index a94df7f771..e5427ceaa8 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -84,8 +84,6 @@ enum CoverOperation : uint8_t { /// The cover is currently closing. COVER_OPERATION_CLOSING, }; -constexpr uint8_t COVER_OPERATION_LAST = static_cast(COVER_OPERATION_CLOSING); -constexpr uint8_t COVER_OPERATION_UNKNOWN_INDEX = COVER_OPERATION_LAST + 1; const LogString *cover_operation_to_str(CoverOperation op); diff --git a/esphome/components/logger/logger.cpp b/esphome/components/logger/logger.cpp index 5aebe459ba..bee8e84833 100644 --- a/esphome/components/logger/logger.cpp +++ b/esphome/components/logger/logger.cpp @@ -294,12 +294,9 @@ float Logger::get_setup_priority() const { return setup_priority::BUS + 500.0f; // Log level strings - packed into flash on ESP8266, indexed by log level (0-7) PROGMEM_STRING_TABLE(LogLevelStrings, "NONE", "ERROR", "WARN", "INFO", "CONFIG", "DEBUG", "VERBOSE", "VERY_VERBOSE"); -constexpr uint8_t LOG_LEVEL_LAST = ESPHOME_LOG_LEVEL_VERY_VERBOSE; static const LogString *get_log_level_str(uint8_t level) { - if (level > LOG_LEVEL_LAST) - level = LOG_LEVEL_LAST; - return LogLevelStrings::get_log_str(level); + return LogLevelStrings::get_log_str(level, LogLevelStrings::LAST_INDEX); } void Logger::dump_config() { diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 52533cf6f6..0423eed795 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -35,10 +35,8 @@ void log_sensor(const char *tag, const char *prefix, const char *type, Sensor *o PROGMEM_STRING_TABLE(StateClassStrings, "", "measurement", "total_increasing", "total", "measurement_angle"); const LogString *state_class_to_string(StateClass state_class) { - uint8_t index = static_cast(state_class); - if (index > STATE_CLASS_LAST) - index = 0; // Default to empty string (STATE_CLASS_NONE) - return StateClassStrings::get_log_str(index); + // Fallback to index 0 (empty string for STATE_CLASS_NONE) if out of range + return StateClassStrings::get_log_str(static_cast(state_class), 0); } Sensor::Sensor() : state(NAN), raw_state(NAN) {} diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp index 9d07ddf8fc..493ffd8da2 100644 --- a/esphome/components/valve/valve.cpp +++ b/esphome/components/valve/valve.cpp @@ -27,10 +27,7 @@ const LogString *valve_command_to_str(float pos) { PROGMEM_STRING_TABLE(ValveOperationStrings, "IDLE", "OPENING", "CLOSING", "UNKNOWN"); const LogString *valve_operation_to_str(ValveOperation op) { - uint8_t index = static_cast(op); - if (index > VALVE_OPERATION_LAST) - index = VALVE_OPERATION_UNKNOWN_INDEX; - return ValveOperationStrings::get_log_str(index); + return ValveOperationStrings::get_log_str(static_cast(op), ValveOperationStrings::LAST_INDEX); } Valve::Valve() : position{VALVE_OPEN} {} diff --git a/esphome/components/valve/valve.h b/esphome/components/valve/valve.h index 9b904c45a8..cd46144372 100644 --- a/esphome/components/valve/valve.h +++ b/esphome/components/valve/valve.h @@ -79,8 +79,6 @@ enum ValveOperation : uint8_t { /// The valve is currently closing. VALVE_OPERATION_CLOSING, }; -constexpr uint8_t VALVE_OPERATION_LAST = static_cast(VALVE_OPERATION_CLOSING); -constexpr uint8_t VALVE_OPERATION_UNKNOWN_INDEX = VALVE_OPERATION_LAST + 1; const LogString *valve_operation_to_str(ValveOperation op); diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index f9ad5e1da9..392974456d 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -399,32 +399,22 @@ class WiFiMockClass : public ESP8266WiFiGenericClass { static void _event_callback(void *event) { ESP8266WiFiGenericClass::_eventCallback(event); } // NOLINT }; -// Auth mode strings indexed by AUTH_* constants (0-4), with UNKNOWN at index 5 +// Auth mode strings indexed by AUTH_* constants (0-4), with UNKNOWN at last index // Static asserts verify the SDK constants are contiguous as expected static_assert(AUTH_OPEN == 0 && AUTH_WEP == 1 && AUTH_WPA_PSK == 2 && AUTH_WPA2_PSK == 3 && AUTH_WPA_WPA2_PSK == 4, "AUTH_* constants are not contiguous"); -constexpr uint8_t AUTH_MODE_LAST = AUTH_WPA_WPA2_PSK; -constexpr uint8_t AUTH_MODE_UNKNOWN_INDEX = AUTH_MODE_LAST + 1; PROGMEM_STRING_TABLE(AuthModeStrings, "OPEN", "WEP", "WPA PSK", "WPA2 PSK", "WPA/WPA2 PSK", "UNKNOWN"); const LogString *get_auth_mode_str(uint8_t mode) { - if (mode > AUTH_MODE_LAST) - mode = AUTH_MODE_UNKNOWN_INDEX; - return AuthModeStrings::get_log_str(mode); + return AuthModeStrings::get_log_str(mode, AuthModeStrings::LAST_INDEX); } -// WiFi op mode strings indexed by WIFI_* constants (0-3), with UNKNOWN at index 4 +// WiFi op mode strings indexed by WIFI_* constants (0-3), with UNKNOWN at last index static_assert(WIFI_OFF == 0 && WIFI_STA == 1 && WIFI_AP == 2 && WIFI_AP_STA == 3, "WIFI_* op mode constants are not contiguous"); -constexpr uint8_t OP_MODE_LAST = WIFI_AP_STA; -constexpr uint8_t OP_MODE_UNKNOWN_INDEX = OP_MODE_LAST + 1; PROGMEM_STRING_TABLE(OpModeStrings, "OFF", "STA", "AP", "AP+STA", "UNKNOWN"); -const LogString *get_op_mode_str(uint8_t mode) { - if (mode > OP_MODE_LAST) - mode = OP_MODE_UNKNOWN_INDEX; - return OpModeStrings::get_log_str(mode); -} +const LogString *get_op_mode_str(uint8_t mode) { return OpModeStrings::get_log_str(mode, OpModeStrings::LAST_INDEX); } const LogString *get_disconnect_reason_str(uint8_t reason) { /* If this were one big switch statement, GCC would generate a lookup table for it. However, the values of the diff --git a/esphome/core/progmem.h b/esphome/core/progmem.h index 5755338cf2..8d2d0322b0 100644 --- a/esphome/core/progmem.h +++ b/esphome/core/progmem.h @@ -91,22 +91,30 @@ template struct ProgmemStringTable { struct LogString; /// Instantiate a ProgmemStringTable with PROGMEM storage. -/// Creates: Name::get_progmem_str(index), Name::get_log_str(index) +/// Creates: Name::get_progmem_str(index, fallback), Name::get_log_str(index, fallback) +/// If index >= COUNT, returns string at fallback_index (or nullptr if fallback >= COUNT) #define PROGMEM_STRING_TABLE(Name, ...) \ struct Name { \ using Table = ::esphome::ProgmemStringTable<__VA_ARGS__>; \ 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 auto BLOB PROGMEM = Table::make_blob(); \ static constexpr auto OFFSETS PROGMEM = Table::make_offsets(); \ - static ::ProgmemStr get_progmem_str(uint8_t index) { \ - if (index >= COUNT) \ - return nullptr; \ + static ::ProgmemStr get_progmem_str(uint8_t index, uint8_t fallback_index = COUNT) { \ + if (index >= COUNT) { \ + if (fallback_index >= COUNT) \ + return nullptr; \ + index = fallback_index; \ + } \ return reinterpret_cast<::ProgmemStr>(&BLOB[::esphome::progmem_read_byte(&OFFSETS[index])]); \ } \ - static const ::esphome::LogString *get_log_str(uint8_t index) { \ - if (index >= COUNT) \ - return nullptr; \ + static const ::esphome::LogString *get_log_str(uint8_t index, uint8_t fallback_index = COUNT) { \ + if (index >= COUNT) { \ + if (fallback_index >= COUNT) \ + return nullptr; \ + index = fallback_index; \ + } \ return reinterpret_cast(&BLOB[::esphome::progmem_read_byte(&OFFSETS[index])]); \ } \ } From d00bf3f49ddc026d4b330a090251ab04d23922fd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 30 Jan 2026 22:36:50 -0600 Subject: [PATCH 2/5] reduce dupe code --- esphome/core/progmem.h | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/esphome/core/progmem.h b/esphome/core/progmem.h index 8d2d0322b0..457cc359a1 100644 --- a/esphome/core/progmem.h +++ b/esphome/core/progmem.h @@ -101,21 +101,19 @@ struct LogString; static constexpr size_t BLOB_SIZE = Table::BLOB_SIZE; \ static constexpr auto BLOB PROGMEM = Table::make_blob(); \ static constexpr auto OFFSETS PROGMEM = Table::make_offsets(); \ - static ::ProgmemStr get_progmem_str(uint8_t index, uint8_t fallback_index = COUNT) { \ + static const char *get_(uint8_t index, uint8_t fallback_index) { \ if (index >= COUNT) { \ if (fallback_index >= COUNT) \ return nullptr; \ index = fallback_index; \ } \ - return reinterpret_cast<::ProgmemStr>(&BLOB[::esphome::progmem_read_byte(&OFFSETS[index])]); \ + return &BLOB[::esphome::progmem_read_byte(&OFFSETS[index])]; \ + } \ + static ::ProgmemStr get_progmem_str(uint8_t index, uint8_t fallback_index = COUNT) { \ + return reinterpret_cast<::ProgmemStr>(get_(index, fallback_index)); \ } \ static const ::esphome::LogString *get_log_str(uint8_t index, uint8_t fallback_index = COUNT) { \ - if (index >= COUNT) { \ - if (fallback_index >= COUNT) \ - return nullptr; \ - index = fallback_index; \ - } \ - return reinterpret_cast(&BLOB[::esphome::progmem_read_byte(&OFFSETS[index])]); \ + return reinterpret_cast(get_(index, fallback_index)); \ } \ } From da2b8aecf16ef3aeb9a776e9c67db24762560fe4 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 30 Jan 2026 22:38:43 -0600 Subject: [PATCH 3/5] more fixes --- esphome/core/progmem.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esphome/core/progmem.h b/esphome/core/progmem.h index 457cc359a1..7efd875b47 100644 --- a/esphome/core/progmem.h +++ b/esphome/core/progmem.h @@ -61,7 +61,7 @@ template struct FixedString { /// template struct ProgmemStringTable { static constexpr size_t COUNT = sizeof...(Strs); - static constexpr size_t BLOB_SIZE = (... + (Strs.size() + 1)); + static constexpr size_t BLOB_SIZE = (0 + ... + (Strs.size() + 1)); /// Generate packed string blob at compile time static constexpr auto make_blob() { From bd29e870ce1e8e139e7547db5d4a7134d178bfdb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 30 Jan 2026 22:42:07 -0600 Subject: [PATCH 4/5] tweak --- esphome/core/progmem.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/esphome/core/progmem.h b/esphome/core/progmem.h index 7efd875b47..dde093fd2e 100644 --- a/esphome/core/progmem.h +++ b/esphome/core/progmem.h @@ -92,7 +92,7 @@ struct LogString; /// Instantiate a ProgmemStringTable with PROGMEM storage. /// Creates: Name::get_progmem_str(index, fallback), Name::get_log_str(index, fallback) -/// If index >= COUNT, returns string at fallback_index (or nullptr if fallback >= COUNT) +/// If index >= COUNT, returns string at fallback_index. Use LAST_INDEX for common patterns. #define PROGMEM_STRING_TABLE(Name, ...) \ struct Name { \ using Table = ::esphome::ProgmemStringTable<__VA_ARGS__>; \ @@ -102,17 +102,14 @@ struct LogString; static constexpr auto BLOB PROGMEM = Table::make_blob(); \ static constexpr auto OFFSETS PROGMEM = Table::make_offsets(); \ static const char *get_(uint8_t index, uint8_t fallback_index) { \ - if (index >= COUNT) { \ - if (fallback_index >= COUNT) \ - return nullptr; \ + if (index >= COUNT) \ index = fallback_index; \ - } \ return &BLOB[::esphome::progmem_read_byte(&OFFSETS[index])]; \ } \ - static ::ProgmemStr get_progmem_str(uint8_t index, uint8_t fallback_index = COUNT) { \ + static ::ProgmemStr get_progmem_str(uint8_t index, uint8_t fallback_index) { \ return reinterpret_cast<::ProgmemStr>(get_(index, fallback_index)); \ } \ - static const ::esphome::LogString *get_log_str(uint8_t index, uint8_t fallback_index = COUNT) { \ + static const ::esphome::LogString *get_log_str(uint8_t index, uint8_t fallback_index) { \ return reinterpret_cast(get_(index, fallback_index)); \ } \ } From f78b6dd8c35308a1d6185da77efc6daccc8a5b9d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 30 Jan 2026 22:44:08 -0600 Subject: [PATCH 5/5] tweak --- esphome/components/light/light_json_schema.cpp | 3 ++- esphome/core/progmem.h | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/esphome/components/light/light_json_schema.cpp b/esphome/components/light/light_json_schema.cpp index b44609fc02..aaa1176f9f 100644 --- a/esphome/components/light/light_json_schema.cpp +++ b/esphome/components/light/light_json_schema.cpp @@ -20,7 +20,8 @@ static ProgmemStr get_color_mode_json_str(ColorMode mode) { unsigned bit = ColorModeBitPolicy::to_bit(mode); if (bit == 0) return nullptr; - return ColorModeStrings::get_progmem_str(bit - 1); + // bit is 1-9 for valid modes, so bit-1 is always valid (0-8). LAST_INDEX fallback never used. + return ColorModeStrings::get_progmem_str(bit - 1, ColorModeStrings::LAST_INDEX); } void LightJSONSchema::dump_json(LightState &state, JsonObject root) { diff --git a/esphome/core/progmem.h b/esphome/core/progmem.h index dde093fd2e..0962f4b684 100644 --- a/esphome/core/progmem.h +++ b/esphome/core/progmem.h @@ -56,8 +56,8 @@ template struct FixedString { /// /// Example: /// PROGMEM_STRING_TABLE(MyStrings, "foo", "bar", "baz"); -/// ProgmemStr str = MyStrings::get_progmem_str(index); // For ArduinoJson -/// const LogString *log_str = MyStrings::get_log_str(index); // For logging +/// ProgmemStr str = MyStrings::get_progmem_str(index, MyStrings::LAST_INDEX); // For ArduinoJson +/// const LogString *log_str = MyStrings::get_log_str(index, MyStrings::LAST_INDEX); // For logging /// template struct ProgmemStringTable { static constexpr size_t COUNT = sizeof...(Strs);