From 365d325feb3a9a0d24fe397ac7962a1d48e65eb2 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 25 Jan 2026 23:21:09 -1000 Subject: [PATCH 1/2] [core] Add PROGMEM string comparison helpers and use in cover/valve/helpers --- esphome/components/cover/cover.cpp | 12 ++++++------ esphome/components/valve/valve.cpp | 10 ++++++---- esphome/core/helpers.cpp | 7 ++++--- esphome/core/progmem.h | 8 ++++++++ 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 97b8c2213e..68688794d7 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -1,11 +1,11 @@ #include "cover.h" #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" +#include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include -#include "esphome/core/log.h" - namespace esphome::cover { static const char *const TAG = "cover"; @@ -39,13 +39,13 @@ Cover::Cover() : position{COVER_OPEN} {} CoverCall::CoverCall(Cover *parent) : parent_(parent) {} CoverCall &CoverCall::set_command(const char *command) { - if (strcasecmp(command, "OPEN") == 0) { + if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("OPEN")) == 0) { this->set_command_open(); - } else if (strcasecmp(command, "CLOSE") == 0) { + } else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("CLOSE")) == 0) { this->set_command_close(); - } else if (strcasecmp(command, "STOP") == 0) { + } else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("STOP")) == 0) { this->set_command_stop(); - } else if (strcasecmp(command, "TOGGLE") == 0) { + } else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TOGGLE")) == 0) { this->set_command_toggle(); } else { ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command); diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp index a9086747ce..3a7c3cbf88 100644 --- a/esphome/components/valve/valve.cpp +++ b/esphome/components/valve/valve.cpp @@ -2,6 +2,8 @@ #include "esphome/core/defines.h" #include "esphome/core/controller_registry.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" + #include namespace esphome { @@ -38,13 +40,13 @@ Valve::Valve() : position{VALVE_OPEN} {} ValveCall::ValveCall(Valve *parent) : parent_(parent) {} ValveCall &ValveCall::set_command(const char *command) { - if (strcasecmp(command, "OPEN") == 0) { + if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("OPEN")) == 0) { this->set_command_open(); - } else if (strcasecmp(command, "CLOSE") == 0) { + } else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("CLOSE")) == 0) { this->set_command_close(); - } else if (strcasecmp(command, "STOP") == 0) { + } else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("STOP")) == 0) { this->set_command_stop(); - } else if (strcasecmp(command, "TOGGLE") == 0) { + } else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TOGGLE")) == 0) { this->set_command_toggle(); } else { ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command); diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index e7b901d71f..1a5d22f8d8 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -3,6 +3,7 @@ #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include "esphome/core/string_ref.h" #include @@ -451,15 +452,15 @@ std::string format_bin(const uint8_t *data, size_t length) { } ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) { - if (on == nullptr && strcasecmp(str, "on") == 0) + if (on == nullptr && ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("on")) == 0) return PARSE_ON; if (on != nullptr && strcasecmp(str, on) == 0) return PARSE_ON; - if (off == nullptr && strcasecmp(str, "off") == 0) + if (off == nullptr && ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("off")) == 0) return PARSE_OFF; if (off != nullptr && strcasecmp(str, off) == 0) return PARSE_OFF; - if (strcasecmp(str, "toggle") == 0) + if (ESPHOME_strcasecmp_P(str, ESPHOME_PSTR("toggle")) == 0) return PARSE_TOGGLE; return PARSE_NONE; diff --git a/esphome/core/progmem.h b/esphome/core/progmem.h index 6c3e4cec96..4b897fb2de 100644 --- a/esphome/core/progmem.h +++ b/esphome/core/progmem.h @@ -12,6 +12,10 @@ #define ESPHOME_strncpy_P strncpy_P #define ESPHOME_strncat_P strncat_P #define ESPHOME_snprintf_P snprintf_P +#define ESPHOME_strcmp_P strcmp_P +#define ESPHOME_strcasecmp_P strcasecmp_P +#define ESPHOME_strncmp_P strncmp_P +#define ESPHOME_strncasecmp_P strncasecmp_P // Type for pointers to PROGMEM strings (for use with ESPHOME_F return values) using ProgmemStr = const __FlashStringHelper *; #else @@ -21,6 +25,10 @@ using ProgmemStr = const __FlashStringHelper *; #define ESPHOME_strncpy_P strncpy #define ESPHOME_strncat_P strncat #define ESPHOME_snprintf_P snprintf +#define ESPHOME_strcmp_P strcmp +#define ESPHOME_strcasecmp_P strcasecmp +#define ESPHOME_strncmp_P strncmp +#define ESPHOME_strncasecmp_P strncasecmp // Type for pointers to strings (no PROGMEM on non-ESP8266 platforms) using ProgmemStr = const char *; #endif From 3930f21fad8d36b330a705ebd3678dcc1bd0cd8c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 25 Jan 2026 23:23:15 -1000 Subject: [PATCH 2/2] [mqtt] Store command comparison strings in flash on ESP8266 --- .../mqtt/mqtt_alarm_control_panel.cpp | 17 +++++++++-------- esphome/components/mqtt/mqtt_lock.cpp | 7 ++++--- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp index 715e6feed8..e1f601bc4c 100644 --- a/esphome/components/mqtt/mqtt_alarm_control_panel.cpp +++ b/esphome/components/mqtt/mqtt_alarm_control_panel.cpp @@ -1,5 +1,6 @@ #include "mqtt_alarm_control_panel.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include "mqtt_const.h" @@ -18,21 +19,21 @@ void MQTTAlarmControlPanelComponent::setup() { this->alarm_control_panel_->add_on_state_callback([this]() { this->publish_state(); }); this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { auto call = this->alarm_control_panel_->make_call(); - if (strcasecmp(payload.c_str(), "ARM_AWAY") == 0) { + if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_AWAY")) == 0) { call.arm_away(); - } else if (strcasecmp(payload.c_str(), "ARM_HOME") == 0) { + } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_HOME")) == 0) { call.arm_home(); - } else if (strcasecmp(payload.c_str(), "ARM_NIGHT") == 0) { + } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_NIGHT")) == 0) { call.arm_night(); - } else if (strcasecmp(payload.c_str(), "ARM_VACATION") == 0) { + } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_VACATION")) == 0) { call.arm_vacation(); - } else if (strcasecmp(payload.c_str(), "ARM_CUSTOM_BYPASS") == 0) { + } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_CUSTOM_BYPASS")) == 0) { call.arm_custom_bypass(); - } else if (strcasecmp(payload.c_str(), "DISARM") == 0) { + } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("DISARM")) == 0) { call.disarm(); - } else if (strcasecmp(payload.c_str(), "PENDING") == 0) { + } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("PENDING")) == 0) { call.pending(); - } else if (strcasecmp(payload.c_str(), "TRIGGERED") == 0) { + } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("TRIGGERED")) == 0) { call.triggered(); } else { ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name_().c_str(), payload.c_str()); diff --git a/esphome/components/mqtt/mqtt_lock.cpp b/esphome/components/mqtt/mqtt_lock.cpp index 43ef60bdf4..564d3879d3 100644 --- a/esphome/components/mqtt/mqtt_lock.cpp +++ b/esphome/components/mqtt/mqtt_lock.cpp @@ -1,5 +1,6 @@ #include "mqtt_lock.h" #include "esphome/core/log.h" +#include "esphome/core/progmem.h" #include "mqtt_const.h" @@ -16,11 +17,11 @@ MQTTLockComponent::MQTTLockComponent(lock::Lock *a_lock) : lock_(a_lock) {} void MQTTLockComponent::setup() { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { - if (strcasecmp(payload.c_str(), "LOCK") == 0) { + if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("LOCK")) == 0) { this->lock_->lock(); - } else if (strcasecmp(payload.c_str(), "UNLOCK") == 0) { + } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("UNLOCK")) == 0) { this->lock_->unlock(); - } else if (strcasecmp(payload.c_str(), "OPEN") == 0) { + } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("OPEN")) == 0) { this->lock_->open(); } else { ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str());