Compare commits

..

3 Commits

Author SHA1 Message Date
J. Nick Koston
f42f222e82 Merge branch 'dev' into scanf_bloat 2026-02-03 03:14:23 +01:00
J. Nick Koston
e68b302bba [ci] Block new scanf() usage to prevent ~9.8KB flash bloat 2026-01-30 20:21:43 -06:00
J. Nick Koston
3e11a9d8a5 [ci] Block new scanf() usage to prevent ~9.8KB flash bloat 2026-01-30 20:20:24 -06:00
29 changed files with 124 additions and 236 deletions

View File

@@ -203,11 +203,10 @@ class ESP32Preferences : public ESPPreferences {
} }
}; };
static ESP32Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void setup_preferences() { void setup_preferences() {
s_preferences.open(); auto *prefs = new ESP32Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = &s_preferences; prefs->open();
global_preferences = prefs;
} }
} // namespace esp32 } // namespace esp32

View File

@@ -211,14 +211,11 @@ bool Esp32HostedUpdate::fetch_manifest_() {
int read_or_error = container->read(buf, sizeof(buf)); int read_or_error = container->read(buf, sizeof(buf));
App.feed_wdt(); App.feed_wdt();
yield(); yield();
auto result = auto result = http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout);
http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout, container->is_read_complete());
if (result == http_request::HttpReadLoopResult::RETRY) if (result == http_request::HttpReadLoopResult::RETRY)
continue; continue;
// Note: COMPLETE is currently unreachable since the loop condition checks bytes_read < content_length,
// but this is defensive code in case chunked transfer encoding support is added in the future.
if (result != http_request::HttpReadLoopResult::DATA) if (result != http_request::HttpReadLoopResult::DATA)
break; // COMPLETE, ERROR, or TIMEOUT break; // ERROR or TIMEOUT
json_str.append(reinterpret_cast<char *>(buf), read_or_error); json_str.append(reinterpret_cast<char *>(buf), read_or_error);
} }
container->end(); container->end();
@@ -339,14 +336,9 @@ bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() {
App.feed_wdt(); App.feed_wdt();
yield(); yield();
auto result = auto result = http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout);
http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout, container->is_read_complete());
if (result == http_request::HttpReadLoopResult::RETRY) if (result == http_request::HttpReadLoopResult::RETRY)
continue; continue;
// Note: COMPLETE is currently unreachable since the loop condition checks bytes_read < content_length,
// but this is defensive code in case chunked transfer encoding support is added in the future.
if (result == http_request::HttpReadLoopResult::COMPLETE)
break;
if (result != http_request::HttpReadLoopResult::DATA) { if (result != http_request::HttpReadLoopResult::DATA) {
if (result == http_request::HttpReadLoopResult::TIMEOUT) { if (result == http_request::HttpReadLoopResult::TIMEOUT) {
ESP_LOGE(TAG, "Timeout reading firmware data"); ESP_LOGE(TAG, "Timeout reading firmware data");

View File

@@ -17,6 +17,10 @@ namespace esphome::esp8266 {
static const char *const TAG = "esp8266.preferences"; static const char *const TAG = "esp8266.preferences";
static uint32_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200; static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
@@ -39,11 +43,6 @@ static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 128;
static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 64; static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 64;
#endif #endif
static uint32_t
s_flash_storage[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) { static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
return false; return false;
@@ -181,6 +180,7 @@ class ESP8266Preferences : public ESPPreferences {
uint32_t current_flash_offset = 0; // in words uint32_t current_flash_offset = 0; // in words
void setup() { void setup() {
s_flash_storage = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT
ESP_LOGVV(TAG, "Loading preferences from flash"); ESP_LOGVV(TAG, "Loading preferences from flash");
{ {
@@ -283,11 +283,10 @@ class ESP8266Preferences : public ESPPreferences {
} }
}; };
static ESP8266Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void setup_preferences() { void setup_preferences() {
s_preferences.setup(); auto *pref = new ESP8266Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = &s_preferences; pref->setup();
global_preferences = pref;
} }
void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; } void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }

View File

@@ -66,11 +66,10 @@ ESPPreferenceObject HostPreferences::make_preference(size_t length, uint32_t typ
return ESPPreferenceObject(backend); return ESPPreferenceObject(backend);
}; };
static HostPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void setup_preferences() { void setup_preferences() {
host_preferences = &s_preferences; auto *pref = new HostPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = &s_preferences; host_preferences = pref;
global_preferences = pref;
} }
bool HostPreferenceBackend::save(const uint8_t *data, size_t len) { bool HostPreferenceBackend::save(const uint8_t *data, size_t len) {

View File

@@ -26,7 +26,6 @@ struct Header {
enum HttpStatus { enum HttpStatus {
HTTP_STATUS_OK = 200, HTTP_STATUS_OK = 200,
HTTP_STATUS_NO_CONTENT = 204, HTTP_STATUS_NO_CONTENT = 204,
HTTP_STATUS_RESET_CONTENT = 205,
HTTP_STATUS_PARTIAL_CONTENT = 206, HTTP_STATUS_PARTIAL_CONTENT = 206,
/* 3xx - Redirection */ /* 3xx - Redirection */
@@ -127,21 +126,19 @@ struct HttpReadResult {
/// Result of processing a non-blocking read with timeout (for manual loops) /// Result of processing a non-blocking read with timeout (for manual loops)
enum class HttpReadLoopResult : uint8_t { enum class HttpReadLoopResult : uint8_t {
DATA, ///< Data was read, process it DATA, ///< Data was read, process it
COMPLETE, ///< All content has been read, caller should exit loop RETRY, ///< No data yet, already delayed, caller should continue loop
RETRY, ///< No data yet, already delayed, caller should continue loop ERROR, ///< Read error, caller should exit loop
ERROR, ///< Read error, caller should exit loop TIMEOUT, ///< Timeout waiting for data, caller should exit loop
TIMEOUT, ///< Timeout waiting for data, caller should exit loop
}; };
/// Process a read result with timeout tracking and delay handling /// Process a read result with timeout tracking and delay handling
/// @param bytes_read_or_error Return value from read() - positive for bytes read, negative for error /// @param bytes_read_or_error Return value from read() - positive for bytes read, negative for error
/// @param last_data_time Time of last successful read, updated when data received /// @param last_data_time Time of last successful read, updated when data received
/// @param timeout_ms Maximum time to wait for data /// @param timeout_ms Maximum time to wait for data
/// @param is_read_complete Whether all expected content has been read (from HttpContainer::is_read_complete()) /// @return DATA if data received, RETRY if should continue loop, ERROR/TIMEOUT if should exit
/// @return How the caller should proceed - see HttpReadLoopResult enum inline HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_t &last_data_time,
inline HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_t &last_data_time, uint32_t timeout_ms, uint32_t timeout_ms) {
bool is_read_complete) {
if (bytes_read_or_error > 0) { if (bytes_read_or_error > 0) {
last_data_time = millis(); last_data_time = millis();
return HttpReadLoopResult::DATA; return HttpReadLoopResult::DATA;
@@ -149,10 +146,7 @@ inline HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_
if (bytes_read_or_error < 0) { if (bytes_read_or_error < 0) {
return HttpReadLoopResult::ERROR; return HttpReadLoopResult::ERROR;
} }
// bytes_read_or_error == 0: either "no data yet" or "all content read" // bytes_read_or_error == 0: no data available yet
if (is_read_complete) {
return HttpReadLoopResult::COMPLETE;
}
if (millis() - last_data_time >= timeout_ms) { if (millis() - last_data_time >= timeout_ms) {
return HttpReadLoopResult::TIMEOUT; return HttpReadLoopResult::TIMEOUT;
} }
@@ -165,9 +159,9 @@ class HttpRequestComponent;
class HttpContainer : public Parented<HttpRequestComponent> { class HttpContainer : public Parented<HttpRequestComponent> {
public: public:
virtual ~HttpContainer() = default; virtual ~HttpContainer() = default;
size_t content_length{0}; size_t content_length;
int status_code{-1}; ///< -1 indicates no response received yet int status_code;
uint32_t duration_ms{0}; uint32_t duration_ms;
/** /**
* @brief Read data from the HTTP response body. * @brief Read data from the HTTP response body.
@@ -200,24 +194,9 @@ class HttpContainer : public Parented<HttpRequestComponent> {
virtual void end() = 0; virtual void end() = 0;
void set_secure(bool secure) { this->secure_ = secure; } void set_secure(bool secure) { this->secure_ = secure; }
void set_chunked(bool chunked) { this->is_chunked_ = chunked; }
size_t get_bytes_read() const { return this->bytes_read_; } size_t get_bytes_read() const { return this->bytes_read_; }
/// Check if all expected content has been read
/// For chunked responses, returns false (completion detected via read() returning error/EOF)
bool is_read_complete() const {
// Per RFC 9112, these responses have no body:
// - 1xx (Informational), 204 No Content, 205 Reset Content, 304 Not Modified
if ((this->status_code >= 100 && this->status_code < 200) || this->status_code == HTTP_STATUS_NO_CONTENT ||
this->status_code == HTTP_STATUS_RESET_CONTENT || this->status_code == HTTP_STATUS_NOT_MODIFIED) {
return true;
}
// For non-chunked responses, complete when bytes_read >= content_length
// This handles both Content-Length: 0 and Content-Length: N cases
return !this->is_chunked_ && this->bytes_read_ >= this->content_length;
}
/** /**
* @brief Get response headers. * @brief Get response headers.
* *
@@ -230,7 +209,6 @@ class HttpContainer : public Parented<HttpRequestComponent> {
protected: protected:
size_t bytes_read_{0}; size_t bytes_read_{0};
bool secure_{false}; bool secure_{false};
bool is_chunked_{false}; ///< True if response uses chunked transfer encoding
std::map<std::string, std::list<std::string>> response_headers_{}; std::map<std::string, std::list<std::string>> response_headers_{};
}; };
@@ -241,7 +219,7 @@ class HttpContainer : public Parented<HttpRequestComponent> {
/// @param total_size Total bytes to read /// @param total_size Total bytes to read
/// @param chunk_size Maximum bytes per read call /// @param chunk_size Maximum bytes per read call
/// @param timeout_ms Read timeout in milliseconds /// @param timeout_ms Read timeout in milliseconds
/// @return HttpReadResult with status and error_code on failure; use container->get_bytes_read() for total bytes read /// @return HttpReadResult with status and error_code on failure
inline HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer, size_t total_size, size_t chunk_size, inline HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer, size_t total_size, size_t chunk_size,
uint32_t timeout_ms) { uint32_t timeout_ms) {
size_t read_index = 0; size_t read_index = 0;
@@ -253,11 +231,9 @@ inline HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer,
App.feed_wdt(); App.feed_wdt();
yield(); yield();
auto result = http_read_loop_result(read_bytes_or_error, last_data_time, timeout_ms, container->is_read_complete()); auto result = http_read_loop_result(read_bytes_or_error, last_data_time, timeout_ms);
if (result == HttpReadLoopResult::RETRY) if (result == HttpReadLoopResult::RETRY)
continue; continue;
if (result == HttpReadLoopResult::COMPLETE)
break; // Server sent less data than requested, but transfer is complete
if (result == HttpReadLoopResult::ERROR) if (result == HttpReadLoopResult::ERROR)
return {HttpReadStatus::ERROR, read_bytes_or_error}; return {HttpReadStatus::ERROR, read_bytes_or_error};
if (result == HttpReadLoopResult::TIMEOUT) if (result == HttpReadLoopResult::TIMEOUT)
@@ -417,12 +393,11 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
int read_or_error = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512)); int read_or_error = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
App.feed_wdt(); App.feed_wdt();
yield(); yield();
auto result = auto result = http_read_loop_result(read_or_error, last_data_time, read_timeout);
http_read_loop_result(read_or_error, last_data_time, read_timeout, container->is_read_complete());
if (result == HttpReadLoopResult::RETRY) if (result == HttpReadLoopResult::RETRY)
continue; continue;
if (result != HttpReadLoopResult::DATA) if (result != HttpReadLoopResult::DATA)
break; // COMPLETE, ERROR, or TIMEOUT break; // ERROR or TIMEOUT
read_index += read_or_error; read_index += read_or_error;
} }
response_body.reserve(read_index); response_body.reserve(read_index);

View File

@@ -135,23 +135,9 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
// When cast to size_t, -1 becomes SIZE_MAX (4294967295 on 32-bit). // When cast to size_t, -1 becomes SIZE_MAX (4294967295 on 32-bit).
// The read() method handles this: bytes_read_ can never reach SIZE_MAX, so the // The read() method handles this: bytes_read_ can never reach SIZE_MAX, so the
// early return check (bytes_read_ >= content_length) will never trigger. // early return check (bytes_read_ >= content_length) will never trigger.
//
// TODO: Chunked transfer encoding is NOT properly supported on Arduino.
// The implementation in #7884 was incomplete - it only works correctly on ESP-IDF where
// esp_http_client_read() decodes chunks internally. On Arduino, using getStreamPtr()
// returns raw TCP data with chunk framing (e.g., "12a\r\n{json}\r\n0\r\n\r\n") instead
// of decoded content. This wasn't noticed because requests would complete and payloads
// were only examined on IDF. The long transfer times were also masked by the misleading
// "HTTP on Arduino version >= 3.1 is **very** slow" warning above. This causes two issues:
// 1. Response body is corrupted - contains chunk size headers mixed with data
// 2. Cannot detect end of transfer - connection stays open (keep-alive), causing timeout
// The proper fix would be to use getString() for chunked responses, which decodes chunks
// internally, but this buffers the entire response in memory.
int content_length = container->client_.getSize(); int content_length = container->client_.getSize();
ESP_LOGD(TAG, "Content-Length: %d", content_length); ESP_LOGD(TAG, "Content-Length: %d", content_length);
container->content_length = (size_t) content_length; container->content_length = (size_t) content_length;
// -1 (SIZE_MAX when cast to size_t) means chunked transfer encoding
container->set_chunked(content_length == -1);
container->duration_ms = millis() - start; container->duration_ms = millis() - start;
return container; return container;
@@ -192,9 +178,9 @@ int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
if (bufsize == 0) { if (bufsize == 0) {
this->duration_ms += (millis() - start); this->duration_ms += (millis() - start);
// Check if we've read all expected content (non-chunked only) // Check if we've read all expected content (only valid when content_length is known and not SIZE_MAX)
// For chunked encoding (content_length == SIZE_MAX), is_read_complete() returns false // For chunked encoding (content_length == SIZE_MAX), we can't use this check
if (this->is_read_complete()) { if (this->content_length > 0 && this->bytes_read_ >= this->content_length) {
return 0; // All content read successfully return 0; // All content read successfully
} }
// No data available - check if connection is still open // No data available - check if connection is still open

View File

@@ -160,7 +160,6 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
// esp_http_client_fetch_headers() returns 0 for chunked transfer encoding (no Content-Length header). // esp_http_client_fetch_headers() returns 0 for chunked transfer encoding (no Content-Length header).
// The read() method handles content_length == 0 specially to support chunked responses. // The read() method handles content_length == 0 specially to support chunked responses.
container->content_length = esp_http_client_fetch_headers(client); container->content_length = esp_http_client_fetch_headers(client);
container->set_chunked(esp_http_client_is_chunked_response(client));
container->feed_wdt(); container->feed_wdt();
container->status_code = esp_http_client_get_status_code(client); container->status_code = esp_http_client_get_status_code(client);
container->feed_wdt(); container->feed_wdt();
@@ -196,7 +195,6 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
container->feed_wdt(); container->feed_wdt();
container->content_length = esp_http_client_fetch_headers(client); container->content_length = esp_http_client_fetch_headers(client);
container->set_chunked(esp_http_client_is_chunked_response(client));
container->feed_wdt(); container->feed_wdt();
container->status_code = esp_http_client_get_status_code(client); container->status_code = esp_http_client_get_status_code(client);
container->feed_wdt(); container->feed_wdt();
@@ -241,9 +239,10 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
const uint32_t start = millis(); const uint32_t start = millis();
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout()); watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
// Check if we've already read all expected content (non-chunked only) // Check if we've already read all expected content
// For chunked responses (content_length == 0), esp_http_client_read() handles EOF // Skip this check when content_length is 0 (chunked transfer encoding or unknown length)
if (this->is_read_complete()) { // For chunked responses, esp_http_client_read() will return 0 when all data is received
if (this->content_length > 0 && this->bytes_read_ >= this->content_length) {
return 0; // All content read successfully return 0; // All content read successfully
} }

View File

@@ -130,13 +130,9 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
App.feed_wdt(); App.feed_wdt();
yield(); yield();
auto result = http_read_loop_result(bufsize_or_error, last_data_time, read_timeout, container->is_read_complete()); auto result = http_read_loop_result(bufsize_or_error, last_data_time, read_timeout);
if (result == HttpReadLoopResult::RETRY) if (result == HttpReadLoopResult::RETRY)
continue; continue;
// Note: COMPLETE is currently unreachable since the loop condition checks bytes_read < content_length,
// but this is defensive code in case chunked transfer encoding support is added for OTA in the future.
if (result == HttpReadLoopResult::COMPLETE)
break;
if (result != HttpReadLoopResult::DATA) { if (result != HttpReadLoopResult::DATA) {
if (result == HttpReadLoopResult::TIMEOUT) { if (result == HttpReadLoopResult::TIMEOUT) {
ESP_LOGE(TAG, "Timeout reading data"); ESP_LOGE(TAG, "Timeout reading data");

View File

@@ -189,11 +189,10 @@ class LibreTinyPreferences : public ESPPreferences {
} }
}; };
static LibreTinyPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void setup_preferences() { void setup_preferences() {
s_preferences.open(); auto *prefs = new LibreTinyPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = &s_preferences; prefs->open();
global_preferences = prefs;
} }
} // namespace libretiny } // namespace libretiny

View File

@@ -2,7 +2,6 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h" #include "esphome/core/controller_registry.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/progmem.h"
namespace esphome::lock { namespace esphome::lock {
@@ -85,21 +84,21 @@ LockCall &LockCall::set_state(optional<LockState> state) {
this->state_ = state; this->state_ = state;
return *this; return *this;
} }
LockCall &LockCall::set_state(const char *state) { LockCall &LockCall::set_state(const std::string &state) {
if (ESPHOME_strcasecmp_P(state, ESPHOME_PSTR("LOCKED")) == 0) { if (str_equals_case_insensitive(state, "LOCKED")) {
this->set_state(LOCK_STATE_LOCKED); this->set_state(LOCK_STATE_LOCKED);
} else if (ESPHOME_strcasecmp_P(state, ESPHOME_PSTR("UNLOCKED")) == 0) { } else if (str_equals_case_insensitive(state, "UNLOCKED")) {
this->set_state(LOCK_STATE_UNLOCKED); this->set_state(LOCK_STATE_UNLOCKED);
} else if (ESPHOME_strcasecmp_P(state, ESPHOME_PSTR("JAMMED")) == 0) { } else if (str_equals_case_insensitive(state, "JAMMED")) {
this->set_state(LOCK_STATE_JAMMED); this->set_state(LOCK_STATE_JAMMED);
} else if (ESPHOME_strcasecmp_P(state, ESPHOME_PSTR("LOCKING")) == 0) { } else if (str_equals_case_insensitive(state, "LOCKING")) {
this->set_state(LOCK_STATE_LOCKING); this->set_state(LOCK_STATE_LOCKING);
} else if (ESPHOME_strcasecmp_P(state, ESPHOME_PSTR("UNLOCKING")) == 0) { } else if (str_equals_case_insensitive(state, "UNLOCKING")) {
this->set_state(LOCK_STATE_UNLOCKING); this->set_state(LOCK_STATE_UNLOCKING);
} else if (ESPHOME_strcasecmp_P(state, ESPHOME_PSTR("NONE")) == 0) { } else if (str_equals_case_insensitive(state, "NONE")) {
this->set_state(LOCK_STATE_NONE); this->set_state(LOCK_STATE_NONE);
} else { } else {
ESP_LOGW(TAG, "'%s' - Unrecognized state %s", this->parent_->get_name().c_str(), state); ESP_LOGW(TAG, "'%s' - Unrecognized state %s", this->parent_->get_name().c_str(), state.c_str());
} }
return *this; return *this;
} }

View File

@@ -83,8 +83,7 @@ class LockCall {
/// Set the state of the lock device. /// Set the state of the lock device.
LockCall &set_state(optional<LockState> state); LockCall &set_state(optional<LockState> state);
/// Set the state of the lock device based on a string. /// Set the state of the lock device based on a string.
LockCall &set_state(const char *state); LockCall &set_state(const std::string &state);
LockCall &set_state(const std::string &state) { return this->set_state(state.c_str()); }
void perform(); void perform();

View File

@@ -2,7 +2,6 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h" #include "esphome/core/controller_registry.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/progmem.h"
namespace esphome { namespace esphome {
namespace media_player { namespace media_player {
@@ -108,25 +107,25 @@ MediaPlayerCall &MediaPlayerCall::set_command(optional<MediaPlayerCommand> comma
this->command_ = command; this->command_ = command;
return *this; return *this;
} }
MediaPlayerCall &MediaPlayerCall::set_command(const char *command) { MediaPlayerCall &MediaPlayerCall::set_command(const std::string &command) {
if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("PLAY")) == 0) { if (str_equals_case_insensitive(command, "PLAY")) {
this->set_command(MEDIA_PLAYER_COMMAND_PLAY); this->set_command(MEDIA_PLAYER_COMMAND_PLAY);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("PAUSE")) == 0) { } else if (str_equals_case_insensitive(command, "PAUSE")) {
this->set_command(MEDIA_PLAYER_COMMAND_PAUSE); this->set_command(MEDIA_PLAYER_COMMAND_PAUSE);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("STOP")) == 0) { } else if (str_equals_case_insensitive(command, "STOP")) {
this->set_command(MEDIA_PLAYER_COMMAND_STOP); this->set_command(MEDIA_PLAYER_COMMAND_STOP);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("MUTE")) == 0) { } else if (str_equals_case_insensitive(command, "MUTE")) {
this->set_command(MEDIA_PLAYER_COMMAND_MUTE); this->set_command(MEDIA_PLAYER_COMMAND_MUTE);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("UNMUTE")) == 0) { } else if (str_equals_case_insensitive(command, "UNMUTE")) {
this->set_command(MEDIA_PLAYER_COMMAND_UNMUTE); this->set_command(MEDIA_PLAYER_COMMAND_UNMUTE);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TOGGLE")) == 0) { } else if (str_equals_case_insensitive(command, "TOGGLE")) {
this->set_command(MEDIA_PLAYER_COMMAND_TOGGLE); this->set_command(MEDIA_PLAYER_COMMAND_TOGGLE);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TURN_ON")) == 0) { } else if (str_equals_case_insensitive(command, "TURN_ON")) {
this->set_command(MEDIA_PLAYER_COMMAND_TURN_ON); this->set_command(MEDIA_PLAYER_COMMAND_TURN_ON);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TURN_OFF")) == 0) { } else if (str_equals_case_insensitive(command, "TURN_OFF")) {
this->set_command(MEDIA_PLAYER_COMMAND_TURN_OFF); this->set_command(MEDIA_PLAYER_COMMAND_TURN_OFF);
} else { } else {
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command); ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command.c_str());
} }
return *this; return *this;
} }

View File

@@ -114,8 +114,7 @@ class MediaPlayerCall {
MediaPlayerCall &set_command(MediaPlayerCommand command); MediaPlayerCall &set_command(MediaPlayerCommand command);
MediaPlayerCall &set_command(optional<MediaPlayerCommand> command); MediaPlayerCall &set_command(optional<MediaPlayerCommand> command);
MediaPlayerCall &set_command(const char *command); MediaPlayerCall &set_command(const std::string &command);
MediaPlayerCall &set_command(const std::string &command) { return this->set_command(command.c_str()); }
MediaPlayerCall &set_media_url(const std::string &url); MediaPlayerCall &set_media_url(const std::string &url);

View File

@@ -94,29 +94,3 @@ DriverChip(
(0x29, 0x00), (0x29, 0x00),
], ],
) )
DriverChip(
"WAVESHARE-ESP32-P4-WIFI6-TOUCH-LCD-7B",
height=600,
width=1024,
hsync_back_porch=160,
hsync_pulse_width=10,
hsync_front_porch=160,
vsync_back_porch=23,
vsync_pulse_width=1,
vsync_front_porch=12,
pclk_frequency="52MHz",
lane_bit_rate="900Mbps",
no_transform=True,
color_order="RGB",
initsequence=[
(0x80, 0x8B),
(0x81, 0x78),
(0x82, 0x84),
(0x83, 0x88),
(0x84, 0xA8),
(0x85, 0xE3),
(0x86, 0x88),
(0xB2, 0x10),
],
)

View File

@@ -4,10 +4,8 @@ from typing import Any
from esphome import automation, pins from esphome import automation, pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import sensor from esphome.components import sensor
from esphome.components.esp32 import include_builtin_idf_component
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_TRIGGER_ID, PLATFORM_ESP32, PLATFORM_ESP8266 from esphome.const import CONF_ID, CONF_TRIGGER_ID, PLATFORM_ESP32, PLATFORM_ESP8266
from esphome.core import CORE
from . import const, generate, schema, validate from . import const, generate, schema, validate
@@ -85,12 +83,6 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config: dict[str, Any]) -> None: async def to_code(config: dict[str, Any]) -> None:
if CORE.is_esp32:
# Re-enable ESP-IDF's legacy driver component (excluded by default to save compile time)
# Provides driver/timer.h header for hardware timer API
# TODO: Remove this once opentherm migrates to GPTimer API (driver/gptimer.h)
include_builtin_idf_component("driver")
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)

View File

@@ -8,8 +8,6 @@
#include "opentherm.h" #include "opentherm.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include <cinttypes> #include <cinttypes>
// TODO: Migrate from legacy timer API (driver/timer.h) to GPTimer API (driver/gptimer.h)
// The legacy timer API is deprecated in ESP-IDF 5.x. See opentherm.h for details.
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "driver/timer.h" #include "driver/timer.h"
#include "esp_err.h" #include "esp_err.h"

View File

@@ -12,10 +12,6 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
// TODO: Migrate from legacy timer API (driver/timer.h) to GPTimer API (driver/gptimer.h)
// The legacy timer API is deprecated in ESP-IDF 5.x. Migration would allow removing the
// "driver" IDF component dependency. See:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/migration-guides/release-5.x/5.0/peripherals.html#id4
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "driver/timer.h" #include "driver/timer.h"
#endif #endif

View File

@@ -18,12 +18,11 @@ namespace rp2040 {
static const char *const TAG = "rp2040.preferences"; static const char *const TAG = "rp2040.preferences";
static constexpr uint32_t RP2040_FLASH_STORAGE_SIZE = 512; static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static uint8_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static const uint32_t RP2040_FLASH_STORAGE_SIZE = 512;
static uint8_t
s_flash_storage[RP2040_FLASH_STORAGE_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// Stack buffer size for preferences - covers virtually all real-world preferences without heap allocation // Stack buffer size for preferences - covers virtually all real-world preferences without heap allocation
static constexpr size_t PREF_BUFFER_SIZE = 64; static constexpr size_t PREF_BUFFER_SIZE = 64;
@@ -92,6 +91,7 @@ class RP2040Preferences : public ESPPreferences {
RP2040Preferences() : eeprom_sector_(&_EEPROM_start) {} RP2040Preferences() : eeprom_sector_(&_EEPROM_start) {}
void setup() { void setup() {
s_flash_storage = new uint8_t[RP2040_FLASH_STORAGE_SIZE]; // NOLINT
ESP_LOGVV(TAG, "Loading preferences from flash"); ESP_LOGVV(TAG, "Loading preferences from flash");
memcpy(s_flash_storage, this->eeprom_sector_, RP2040_FLASH_STORAGE_SIZE); memcpy(s_flash_storage, this->eeprom_sector_, RP2040_FLASH_STORAGE_SIZE);
} }
@@ -149,11 +149,10 @@ class RP2040Preferences : public ESPPreferences {
uint8_t *eeprom_sector_; uint8_t *eeprom_sector_;
}; };
static RP2040Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void setup_preferences() { void setup_preferences() {
s_preferences.setup(); auto *prefs = new RP2040Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = &s_preferences; prefs->setup();
global_preferences = prefs;
} }
void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; } void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }

View File

@@ -157,14 +157,8 @@ def _read_audio_file_and_type(file_config):
import puremagic import puremagic
try: file_type: str = puremagic.from_string(data)
file_type: str = puremagic.from_string(data) file_type = file_type.removeprefix(".")
file_type = file_type.removeprefix(".")
except puremagic.PureError as e:
raise cv.Invalid(
f"Unable to determine audio file type of '{path}'. "
f"Try re-encoding the file into a supported format. Details: {e}"
)
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["NONE"] media_file_type = audio.AUDIO_FILE_TYPE_ENUM["NONE"]
if file_type in ("wav"): if file_type in ("wav"):

View File

@@ -2,7 +2,6 @@
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/controller_registry.h" #include "esphome/core/controller_registry.h"
#include "esphome/core/progmem.h"
#include <cmath> #include <cmath>
@@ -23,23 +22,23 @@ WaterHeaterCall &WaterHeaterCall::set_mode(WaterHeaterMode mode) {
return *this; return *this;
} }
WaterHeaterCall &WaterHeaterCall::set_mode(const char *mode) { WaterHeaterCall &WaterHeaterCall::set_mode(const std::string &mode) {
if (ESPHOME_strcasecmp_P(mode, ESPHOME_PSTR("OFF")) == 0) { if (str_equals_case_insensitive(mode, "OFF")) {
this->set_mode(WATER_HEATER_MODE_OFF); this->set_mode(WATER_HEATER_MODE_OFF);
} else if (ESPHOME_strcasecmp_P(mode, ESPHOME_PSTR("ECO")) == 0) { } else if (str_equals_case_insensitive(mode, "ECO")) {
this->set_mode(WATER_HEATER_MODE_ECO); this->set_mode(WATER_HEATER_MODE_ECO);
} else if (ESPHOME_strcasecmp_P(mode, ESPHOME_PSTR("ELECTRIC")) == 0) { } else if (str_equals_case_insensitive(mode, "ELECTRIC")) {
this->set_mode(WATER_HEATER_MODE_ELECTRIC); this->set_mode(WATER_HEATER_MODE_ELECTRIC);
} else if (ESPHOME_strcasecmp_P(mode, ESPHOME_PSTR("PERFORMANCE")) == 0) { } else if (str_equals_case_insensitive(mode, "PERFORMANCE")) {
this->set_mode(WATER_HEATER_MODE_PERFORMANCE); this->set_mode(WATER_HEATER_MODE_PERFORMANCE);
} else if (ESPHOME_strcasecmp_P(mode, ESPHOME_PSTR("HIGH_DEMAND")) == 0) { } else if (str_equals_case_insensitive(mode, "HIGH_DEMAND")) {
this->set_mode(WATER_HEATER_MODE_HIGH_DEMAND); this->set_mode(WATER_HEATER_MODE_HIGH_DEMAND);
} else if (ESPHOME_strcasecmp_P(mode, ESPHOME_PSTR("HEAT_PUMP")) == 0) { } else if (str_equals_case_insensitive(mode, "HEAT_PUMP")) {
this->set_mode(WATER_HEATER_MODE_HEAT_PUMP); this->set_mode(WATER_HEATER_MODE_HEAT_PUMP);
} else if (ESPHOME_strcasecmp_P(mode, ESPHOME_PSTR("GAS")) == 0) { } else if (str_equals_case_insensitive(mode, "GAS")) {
this->set_mode(WATER_HEATER_MODE_GAS); this->set_mode(WATER_HEATER_MODE_GAS);
} else { } else {
ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode); ESP_LOGW(TAG, "'%s' - Unrecognized mode %s", this->parent_->get_name().c_str(), mode.c_str());
} }
return *this; return *this;
} }

View File

@@ -75,8 +75,7 @@ class WaterHeaterCall {
WaterHeaterCall(WaterHeater *parent); WaterHeaterCall(WaterHeater *parent);
WaterHeaterCall &set_mode(WaterHeaterMode mode); WaterHeaterCall &set_mode(WaterHeaterMode mode);
WaterHeaterCall &set_mode(const char *mode); WaterHeaterCall &set_mode(const std::string &mode);
WaterHeaterCall &set_mode(const std::string &mode) { return this->set_mode(mode.c_str()); }
WaterHeaterCall &set_target_temperature(float temperature); WaterHeaterCall &set_target_temperature(float temperature);
WaterHeaterCall &set_target_temperature_low(float temperature); WaterHeaterCall &set_target_temperature_low(float temperature);
WaterHeaterCall &set_target_temperature_high(float temperature); WaterHeaterCall &set_target_temperature_high(float temperature);

View File

@@ -585,13 +585,11 @@ async def to_code(config):
await cg.past_safe_mode() await cg.past_safe_mode()
if on_connect_config := config.get(CONF_ON_CONNECT): if on_connect_config := config.get(CONF_ON_CONNECT):
cg.add_define("USE_WIFI_CONNECT_TRIGGER")
await automation.build_automation( await automation.build_automation(
var.get_connect_trigger(), [], on_connect_config var.get_connect_trigger(), [], on_connect_config
) )
if on_disconnect_config := config.get(CONF_ON_DISCONNECT): if on_disconnect_config := config.get(CONF_ON_DISCONNECT):
cg.add_define("USE_WIFI_DISCONNECT_TRIGGER")
await automation.build_automation( await automation.build_automation(
var.get_disconnect_trigger(), [], on_disconnect_config var.get_disconnect_trigger(), [], on_disconnect_config
) )

View File

@@ -48,7 +48,7 @@ template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, publi
char ssid_buf[SSID_BUFFER_SIZE]; char ssid_buf[SSID_BUFFER_SIZE];
if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), ssid.c_str()) == 0) { if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), ssid.c_str()) == 0) {
// Callback to notify the user that the connection was successful // Callback to notify the user that the connection was successful
this->connect_trigger_.trigger(); this->connect_trigger_->trigger();
return; return;
} }
// Create a new WiFiAP object with the new SSID and password // Create a new WiFiAP object with the new SSID and password
@@ -79,13 +79,13 @@ template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, publi
// Start a timeout for the fallback if the connection to the old AP fails // Start a timeout for the fallback if the connection to the old AP fails
this->set_timeout("wifi-fallback-timeout", this->connection_timeout_.value(x...), [this]() { this->set_timeout("wifi-fallback-timeout", this->connection_timeout_.value(x...), [this]() {
this->connecting_ = false; this->connecting_ = false;
this->error_trigger_.trigger(); this->error_trigger_->trigger();
}); });
}); });
} }
Trigger<> *get_connect_trigger() { return &this->connect_trigger_; } Trigger<> *get_connect_trigger() const { return this->connect_trigger_; }
Trigger<> *get_error_trigger() { return &this->error_trigger_; } Trigger<> *get_error_trigger() const { return this->error_trigger_; }
void loop() override { void loop() override {
if (!this->connecting_) if (!this->connecting_)
@@ -98,10 +98,10 @@ template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, publi
char ssid_buf[SSID_BUFFER_SIZE]; char ssid_buf[SSID_BUFFER_SIZE];
if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), this->new_sta_.get_ssid().c_str()) == 0) { if (strcmp(global_wifi_component->wifi_ssid_to(ssid_buf), this->new_sta_.get_ssid().c_str()) == 0) {
// Callback to notify the user that the connection was successful // Callback to notify the user that the connection was successful
this->connect_trigger_.trigger(); this->connect_trigger_->trigger();
} else { } else {
// Callback to notify the user that the connection failed // Callback to notify the user that the connection failed
this->error_trigger_.trigger(); this->error_trigger_->trigger();
} }
} }
} }
@@ -110,8 +110,8 @@ template<typename... Ts> class WiFiConfigureAction : public Action<Ts...>, publi
bool connecting_{false}; bool connecting_{false};
WiFiAP new_sta_; WiFiAP new_sta_;
WiFiAP old_sta_; WiFiAP old_sta_;
Trigger<> connect_trigger_; Trigger<> *connect_trigger_{new Trigger<>()};
Trigger<> error_trigger_; Trigger<> *error_trigger_{new Trigger<>()};
}; };
} // namespace esphome::wifi } // namespace esphome::wifi

View File

@@ -651,21 +651,14 @@ void WiFiComponent::loop() {
const uint32_t now = App.get_loop_component_start_time(); const uint32_t now = App.get_loop_component_start_time();
if (this->has_sta()) { if (this->has_sta()) {
#if defined(USE_WIFI_CONNECT_TRIGGER) || defined(USE_WIFI_DISCONNECT_TRIGGER)
if (this->is_connected() != this->handled_connected_state_) { if (this->is_connected() != this->handled_connected_state_) {
#ifdef USE_WIFI_DISCONNECT_TRIGGER
if (this->handled_connected_state_) { if (this->handled_connected_state_) {
this->disconnect_trigger_.trigger(); this->disconnect_trigger_->trigger();
} else {
this->connect_trigger_->trigger();
} }
#endif
#ifdef USE_WIFI_CONNECT_TRIGGER
if (!this->handled_connected_state_) {
this->connect_trigger_.trigger();
}
#endif
this->handled_connected_state_ = this->is_connected(); this->handled_connected_state_ = this->is_connected();
} }
#endif // USE_WIFI_CONNECT_TRIGGER || USE_WIFI_DISCONNECT_TRIGGER
switch (this->state_) { switch (this->state_) {
case WIFI_COMPONENT_STATE_COOLDOWN: { case WIFI_COMPONENT_STATE_COOLDOWN: {

View File

@@ -454,12 +454,8 @@ class WiFiComponent : public Component {
void set_keep_scan_results(bool keep_scan_results) { this->keep_scan_results_ = keep_scan_results; } void set_keep_scan_results(bool keep_scan_results) { this->keep_scan_results_ = keep_scan_results; }
void set_post_connect_roaming(bool enabled) { this->post_connect_roaming_ = enabled; } void set_post_connect_roaming(bool enabled) { this->post_connect_roaming_ = enabled; }
#ifdef USE_WIFI_CONNECT_TRIGGER Trigger<> *get_connect_trigger() const { return this->connect_trigger_; };
Trigger<> *get_connect_trigger() { return &this->connect_trigger_; } Trigger<> *get_disconnect_trigger() const { return this->disconnect_trigger_; };
#endif
#ifdef USE_WIFI_DISCONNECT_TRIGGER
Trigger<> *get_disconnect_trigger() { return &this->disconnect_trigger_; }
#endif
int32_t get_wifi_channel(); int32_t get_wifi_channel();
@@ -710,9 +706,7 @@ class WiFiComponent : public Component {
// Group all boolean values together // Group all boolean values together
bool has_ap_{false}; bool has_ap_{false};
#if defined(USE_WIFI_CONNECT_TRIGGER) || defined(USE_WIFI_DISCONNECT_TRIGGER)
bool handled_connected_state_{false}; bool handled_connected_state_{false};
#endif
bool error_from_callback_{false}; bool error_from_callback_{false};
bool scan_done_{false}; bool scan_done_{false};
bool ap_setup_{false}; bool ap_setup_{false};
@@ -739,12 +733,9 @@ class WiFiComponent : public Component {
SemaphoreHandle_t high_performance_semaphore_{nullptr}; SemaphoreHandle_t high_performance_semaphore_{nullptr};
#endif #endif
#ifdef USE_WIFI_CONNECT_TRIGGER // Pointers at the end (naturally aligned)
Trigger<> connect_trigger_; Trigger<> *connect_trigger_{new Trigger<>()};
#endif Trigger<> *disconnect_trigger_{new Trigger<>()};
#ifdef USE_WIFI_DISCONNECT_TRIGGER
Trigger<> disconnect_trigger_;
#endif
private: private:
// Stores a pointer to a string literal (static storage duration). // Stores a pointer to a string literal (static storage duration).

View File

@@ -152,11 +152,10 @@ class ZephyrPreferences : public ESPPreferences {
} }
}; };
static ZephyrPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void setup_preferences() { void setup_preferences() {
global_preferences = &s_preferences; auto *prefs = new ZephyrPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
s_preferences.open(); global_preferences = prefs;
prefs->open();
} }
} // namespace zephyr } // namespace zephyr

View File

@@ -227,8 +227,6 @@
#define USE_WIFI_SCAN_RESULTS_LISTENERS #define USE_WIFI_SCAN_RESULTS_LISTENERS
#define USE_WIFI_CONNECT_STATE_LISTENERS #define USE_WIFI_CONNECT_STATE_LISTENERS
#define USE_WIFI_POWER_SAVE_LISTENERS #define USE_WIFI_POWER_SAVE_LISTENERS
#define USE_WIFI_CONNECT_TRIGGER
#define USE_WIFI_DISCONNECT_TRIGGER
#define ESPHOME_WIFI_IP_STATE_LISTENERS 2 #define ESPHOME_WIFI_IP_STATE_LISTENERS 2
#define ESPHOME_WIFI_SCAN_RESULTS_LISTENERS 2 #define ESPHOME_WIFI_SCAN_RESULTS_LISTENERS 2
#define ESPHOME_WIFI_CONNECT_STATE_LISTENERS 2 #define ESPHOME_WIFI_CONNECT_STATE_LISTENERS 2

View File

@@ -756,6 +756,28 @@ def lint_no_sprintf(fname, match):
) )
@lint_re_check(
# Match scanf family functions: scanf, sscanf, fscanf, vscanf, vsscanf, vfscanf
# Also match std:: prefixed versions
# [^\w] ensures we match function calls, not substrings
r"[^\w]((?:std::)?v?[fs]?scanf)\s*\(" + CPP_RE_EOL,
include=cpp_include,
)
def lint_no_scanf(fname, match):
func = match.group(1)
return (
f"{highlight(func + '()')} is not allowed in new ESPHome code. The scanf family "
f"pulls in ~7KB flash on ESP8266 and ~9KB on ESP32, and ESPHome doesn't otherwise "
f"need this code.\n"
f"Please use alternatives:\n"
f" - {highlight('parse_number<T>(str)')} for parsing integers/floats from strings\n"
f" - {highlight('strtol()/strtof()')} for C-style number parsing with error checking\n"
f" - {highlight('parse_hex()')} for hex string parsing\n"
f" - Manual parsing for simple fixed formats\n"
f"(If strictly necessary, add `// NOLINT` to the end of the line)"
)
@lint_content_find_check( @lint_content_find_check(
"ESP_LOG", "ESP_LOG",
include=["*.h", "*.tcc"], include=["*.h", "*.tcc"],

View File

@@ -26,7 +26,3 @@ wifi:
- ssid: MySSID3 - ssid: MySSID3
password: password3 password: password3
priority: 0 priority: 0
on_connect:
- logger.log: "WiFi connected!"
on_disconnect:
- logger.log: "WiFi disconnected!"