From f24e7709aca9e0cb7fc5951ea82141fec1b5dd9e Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 13 Feb 2026 16:21:50 -0600 Subject: [PATCH 01/15] [core] Make LOG_ENTITY_ICON a no-op when icons are compiled out (#13973) --- esphome/core/entity_base.cpp | 2 ++ esphome/core/entity_base.h | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 811b856b5e..f6a7ec1dfd 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -152,11 +152,13 @@ void EntityBase_UnitOfMeasurement::set_unit_of_measurement(const char *unit_of_m this->unit_of_measurement_ = unit_of_measurement; } +#ifdef USE_ENTITY_ICON void log_entity_icon(const char *tag, const char *prefix, const EntityBase &obj) { if (!obj.get_icon_ref().empty()) { ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, obj.get_icon_ref().c_str()); } } +#endif void log_entity_device_class(const char *tag, const char *prefix, const EntityBase_DeviceClass &obj) { if (!obj.get_device_class_ref().empty()) { diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 86cb75495b..cbc07cc44c 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -231,8 +231,13 @@ class EntityBase_UnitOfMeasurement { // NOLINT(readability-identifier-naming) }; /// Log entity icon if set (for use in dump_config) +#ifdef USE_ENTITY_ICON #define LOG_ENTITY_ICON(tag, prefix, obj) log_entity_icon(tag, prefix, obj) void log_entity_icon(const char *tag, const char *prefix, const EntityBase &obj); +#else +#define LOG_ENTITY_ICON(tag, prefix, obj) ((void) 0) +inline void log_entity_icon(const char *, const char *, const EntityBase &) {} +#endif /// Log entity device class if set (for use in dump_config) #define LOG_ENTITY_DEVICE_CLASS(tag, prefix, obj) log_entity_device_class(tag, prefix, obj) void log_entity_device_class(const char *tag, const char *prefix, const EntityBase_DeviceClass &obj); From 79d9fbf64579bee42d14adc9a4e17f8ddac3ac0b Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 13 Feb 2026 16:22:05 -0600 Subject: [PATCH 02/15] [nfc] Replace constant std::vector with static constexpr std::array (#13978) --- esphome/components/pn532/pn532.h | 4 +- .../components/pn532/pn532_mifare_classic.cpp | 68 +++++++++---------- .../pn532/pn532_mifare_ultralight.cpp | 16 ++--- esphome/components/pn7150/pn7150.h | 4 +- .../pn7150/pn7150_mifare_classic.cpp | 66 +++++++++--------- .../pn7150/pn7150_mifare_ultralight.cpp | 13 ++-- esphome/components/pn7160/pn7160.h | 4 +- .../pn7160/pn7160_mifare_classic.cpp | 66 +++++++++--------- .../pn7160/pn7160_mifare_ultralight.cpp | 13 ++-- 9 files changed, 130 insertions(+), 124 deletions(-) diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index 73a6c15164..f98c0f9322 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -76,7 +76,7 @@ class PN532 : public PollingComponent { std::unique_ptr read_mifare_classic_tag_(nfc::NfcTagUid &uid); bool read_mifare_classic_block_(uint8_t block_num, std::vector &data); - bool write_mifare_classic_block_(uint8_t block_num, std::vector &data); + bool write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len); bool auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key); bool format_mifare_classic_mifare_(nfc::NfcTagUid &uid); bool format_mifare_classic_ndef_(nfc::NfcTagUid &uid); @@ -88,7 +88,7 @@ class PN532 : public PollingComponent { uint16_t read_mifare_ultralight_capacity_(); bool find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, uint8_t &message_start_index); - bool write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data); + bool write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len); bool write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message); bool clean_mifare_ultralight_(); diff --git a/esphome/components/pn532/pn532_mifare_classic.cpp b/esphome/components/pn532/pn532_mifare_classic.cpp index b762d5d936..cca6acd96d 100644 --- a/esphome/components/pn532/pn532_mifare_classic.cpp +++ b/esphome/components/pn532/pn532_mifare_classic.cpp @@ -1,3 +1,4 @@ +#include #include #include "pn532.h" @@ -106,10 +107,10 @@ bool PN532::auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, u } bool PN532::format_mifare_classic_mifare_(nfc::NfcTagUid &uid) { - std::vector blank_buffer( - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - std::vector trailer_buffer( - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + static constexpr std::array BLANK_BUFFER = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + static constexpr std::array TRAILER_BUFFER = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; bool error = false; @@ -118,20 +119,20 @@ bool PN532::format_mifare_classic_mifare_(nfc::NfcTagUid &uid) { continue; } if (block != 0) { - if (!this->write_mifare_classic_block_(block, blank_buffer)) { + if (!this->write_mifare_classic_block_(block, BLANK_BUFFER.data(), BLANK_BUFFER.size())) { ESP_LOGE(TAG, "Unable to write block %d", block); error = true; } } - if (!this->write_mifare_classic_block_(block + 1, blank_buffer)) { + if (!this->write_mifare_classic_block_(block + 1, BLANK_BUFFER.data(), BLANK_BUFFER.size())) { ESP_LOGE(TAG, "Unable to write block %d", block + 1); error = true; } - if (!this->write_mifare_classic_block_(block + 2, blank_buffer)) { + if (!this->write_mifare_classic_block_(block + 2, BLANK_BUFFER.data(), BLANK_BUFFER.size())) { ESP_LOGE(TAG, "Unable to write block %d", block + 2); error = true; } - if (!this->write_mifare_classic_block_(block + 3, trailer_buffer)) { + if (!this->write_mifare_classic_block_(block + 3, TRAILER_BUFFER.data(), TRAILER_BUFFER.size())) { ESP_LOGE(TAG, "Unable to write block %d", block + 3); error = true; } @@ -141,28 +142,28 @@ bool PN532::format_mifare_classic_mifare_(nfc::NfcTagUid &uid) { } bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) { - std::vector empty_ndef_message( - {0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - std::vector blank_block( - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - std::vector block_1_data( - {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); - std::vector block_2_data( - {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); - std::vector block_3_trailer( - {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); - std::vector ndef_trailer( - {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + static constexpr std::array EMPTY_NDEF_MESSAGE = { + 0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + static constexpr std::array BLANK_BLOCK = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + static constexpr std::array BLOCK_1_DATA = { + 0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + static constexpr std::array BLOCK_2_DATA = { + 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + static constexpr std::array BLOCK_3_TRAILER = { + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + static constexpr std::array NDEF_TRAILER = { + 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; if (!this->auth_mifare_classic_block_(uid, 0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY)) { ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting!"); return false; } - if (!this->write_mifare_classic_block_(1, block_1_data)) + if (!this->write_mifare_classic_block_(1, BLOCK_1_DATA.data(), BLOCK_1_DATA.size())) return false; - if (!this->write_mifare_classic_block_(2, block_2_data)) + if (!this->write_mifare_classic_block_(2, BLOCK_2_DATA.data(), BLOCK_2_DATA.size())) return false; - if (!this->write_mifare_classic_block_(3, block_3_trailer)) + if (!this->write_mifare_classic_block_(3, BLOCK_3_TRAILER.data(), BLOCK_3_TRAILER.size())) return false; ESP_LOGD(TAG, "Sector 0 formatted to NDEF"); @@ -172,36 +173,36 @@ bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) { return false; } if (block == 4) { - if (!this->write_mifare_classic_block_(block, empty_ndef_message)) { + if (!this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE.data(), EMPTY_NDEF_MESSAGE.size())) { ESP_LOGE(TAG, "Unable to write block %d", block); } } else { - if (!this->write_mifare_classic_block_(block, blank_block)) { + if (!this->write_mifare_classic_block_(block, BLANK_BLOCK.data(), BLANK_BLOCK.size())) { ESP_LOGE(TAG, "Unable to write block %d", block); } } - if (!this->write_mifare_classic_block_(block + 1, blank_block)) { + if (!this->write_mifare_classic_block_(block + 1, BLANK_BLOCK.data(), BLANK_BLOCK.size())) { ESP_LOGE(TAG, "Unable to write block %d", block + 1); } - if (!this->write_mifare_classic_block_(block + 2, blank_block)) { + if (!this->write_mifare_classic_block_(block + 2, BLANK_BLOCK.data(), BLANK_BLOCK.size())) { ESP_LOGE(TAG, "Unable to write block %d", block + 2); } - if (!this->write_mifare_classic_block_(block + 3, ndef_trailer)) { + if (!this->write_mifare_classic_block_(block + 3, NDEF_TRAILER.data(), NDEF_TRAILER.size())) { ESP_LOGE(TAG, "Unable to write trailer block %d", block + 3); } } return true; } -bool PN532::write_mifare_classic_block_(uint8_t block_num, std::vector &write_data) { - std::vector data({ +bool PN532::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) { + std::vector cmd({ PN532_COMMAND_INDATAEXCHANGE, 0x01, // One card nfc::MIFARE_CMD_WRITE, block_num, }); - data.insert(data.end(), write_data.begin(), write_data.end()); - if (!this->write_command_(data)) { + cmd.insert(cmd.end(), data, data + len); + if (!this->write_command_(cmd)) { ESP_LOGE(TAG, "Error writing block %d", block_num); return false; } @@ -243,8 +244,7 @@ bool PN532::write_mifare_classic_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *mes } } - std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE); - if (!this->write_mifare_classic_block_(current_block, data)) { + if (!this->write_mifare_classic_block_(current_block, encoded.data() + index, nfc::MIFARE_CLASSIC_BLOCK_SIZE)) { return false; } index += nfc::MIFARE_CLASSIC_BLOCK_SIZE; diff --git a/esphome/components/pn532/pn532_mifare_ultralight.cpp b/esphome/components/pn532/pn532_mifare_ultralight.cpp index 01e41df5c0..a8a8e2d573 100644 --- a/esphome/components/pn532/pn532_mifare_ultralight.cpp +++ b/esphome/components/pn532/pn532_mifare_ultralight.cpp @@ -1,3 +1,4 @@ +#include #include #include "pn532.h" @@ -143,8 +144,7 @@ bool PN532::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage * uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; while (index < buffer_length) { - std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE); - if (!this->write_mifare_ultralight_page_(current_page, data)) { + if (!this->write_mifare_ultralight_page_(current_page, encoded.data() + index, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE)) { return false; } index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; @@ -157,25 +157,25 @@ bool PN532::clean_mifare_ultralight_() { uint32_t capacity = this->read_mifare_ultralight_capacity_(); uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; - std::vector blank_data = {0x00, 0x00, 0x00, 0x00}; + static constexpr std::array BLANK_DATA = {0x00, 0x00, 0x00, 0x00}; for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) { - if (!this->write_mifare_ultralight_page_(i, blank_data)) { + if (!this->write_mifare_ultralight_page_(i, BLANK_DATA.data(), BLANK_DATA.size())) { return false; } } return true; } -bool PN532::write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data) { - std::vector data({ +bool PN532::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) { + std::vector cmd({ PN532_COMMAND_INDATAEXCHANGE, 0x01, // One card nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num, }); - data.insert(data.end(), write_data.begin(), write_data.end()); - if (!this->write_command_(data)) { + cmd.insert(cmd.end(), write_data, write_data + len); + if (!this->write_command_(cmd)) { ESP_LOGE(TAG, "Error writing page %u", page_num); return false; } diff --git a/esphome/components/pn7150/pn7150.h b/esphome/components/pn7150/pn7150.h index a5dcef9f99..5feba17d21 100644 --- a/esphome/components/pn7150/pn7150.h +++ b/esphome/components/pn7150/pn7150.h @@ -236,7 +236,7 @@ class PN7150 : public nfc::Nfcc, public Component { uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag); uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector &data); - uint8_t write_mifare_classic_block_(uint8_t block_num, std::vector &data); + uint8_t write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len); uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key); uint8_t sect_to_auth_(uint8_t block_num); uint8_t format_mifare_classic_mifare_(); @@ -250,7 +250,7 @@ class PN7150 : public nfc::Nfcc, public Component { uint16_t read_mifare_ultralight_capacity_(); uint8_t find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, uint8_t &message_start_index); - uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data); + uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len); uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr &message); uint8_t clean_mifare_ultralight_(); diff --git a/esphome/components/pn7150/pn7150_mifare_classic.cpp b/esphome/components/pn7150/pn7150_mifare_classic.cpp index dee81b610a..61434cdb28 100644 --- a/esphome/components/pn7150/pn7150_mifare_classic.cpp +++ b/esphome/components/pn7150/pn7150_mifare_classic.cpp @@ -1,3 +1,4 @@ +#include #include #include "pn7150.h" @@ -139,10 +140,10 @@ uint8_t PN7150::sect_to_auth_(const uint8_t block_num) { } uint8_t PN7150::format_mifare_classic_mifare_() { - std::vector blank_buffer( - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - std::vector trailer_buffer( - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + static constexpr std::array BLANK_BUFFER = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + static constexpr std::array TRAILER_BUFFER = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; auto status = nfc::STATUS_OK; @@ -151,20 +152,20 @@ uint8_t PN7150::format_mifare_classic_mifare_() { continue; } if (block != 0) { - if (this->write_mifare_classic_block_(block, blank_buffer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block); status = nfc::STATUS_FAILED; } } - if (this->write_mifare_classic_block_(block + 1, blank_buffer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 1, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block + 1); status = nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(block + 2, blank_buffer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 2, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block + 2); status = nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(block + 3, trailer_buffer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 3, TRAILER_BUFFER.data(), TRAILER_BUFFER.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block + 3); status = nfc::STATUS_FAILED; } @@ -174,30 +175,30 @@ uint8_t PN7150::format_mifare_classic_mifare_() { } uint8_t PN7150::format_mifare_classic_ndef_() { - std::vector empty_ndef_message( - {0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - std::vector blank_block( - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - std::vector block_1_data( - {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); - std::vector block_2_data( - {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); - std::vector block_3_trailer( - {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); - std::vector ndef_trailer( - {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + static constexpr std::array EMPTY_NDEF_MESSAGE = { + 0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + static constexpr std::array BLANK_BLOCK = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + static constexpr std::array BLOCK_1_DATA = { + 0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + static constexpr std::array BLOCK_2_DATA = { + 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + static constexpr std::array BLOCK_3_TRAILER = { + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + static constexpr std::array NDEF_TRAILER = { + 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting"); return nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(1, block_1_data) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(1, BLOCK_1_DATA.data(), BLOCK_1_DATA.size()) != nfc::STATUS_OK) { return nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(2, block_2_data) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(2, BLOCK_2_DATA.data(), BLOCK_2_DATA.size()) != nfc::STATUS_OK) { return nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(3, block_3_trailer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(3, BLOCK_3_TRAILER.data(), BLOCK_3_TRAILER.size()) != nfc::STATUS_OK) { return nfc::STATUS_FAILED; } @@ -210,25 +211,26 @@ uint8_t PN7150::format_mifare_classic_ndef_() { return nfc::STATUS_FAILED; } if (block == 4) { - if (this->write_mifare_classic_block_(block, empty_ndef_message) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE.data(), EMPTY_NDEF_MESSAGE.size()) != + nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block); status = nfc::STATUS_FAILED; } } else { - if (this->write_mifare_classic_block_(block, blank_block) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block); status = nfc::STATUS_FAILED; } } - if (this->write_mifare_classic_block_(block + 1, blank_block) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 1, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block + 1); status = nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(block + 2, blank_block) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 2, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block + 2); status = nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(block + 3, ndef_trailer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 3, NDEF_TRAILER.data(), NDEF_TRAILER.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3); status = nfc::STATUS_FAILED; } @@ -236,7 +238,7 @@ uint8_t PN7150::format_mifare_classic_ndef_() { return status; } -uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vector &write_data) { +uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) { nfc::NciMessage rx; nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num}); @@ -248,7 +250,7 @@ uint8_t PN7150::write_mifare_classic_block_(uint8_t block_num, std::vectortransceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { @@ -294,8 +296,8 @@ uint8_t PN7150::write_mifare_classic_tag_(const std::shared_ptr data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE); - if (this->write_mifare_classic_block_(current_block, data) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(current_block, encoded.data() + index, nfc::MIFARE_CLASSIC_BLOCK_SIZE) != + nfc::STATUS_OK) { return nfc::STATUS_FAILED; } index += nfc::MIFARE_CLASSIC_BLOCK_SIZE; diff --git a/esphome/components/pn7150/pn7150_mifare_ultralight.cpp b/esphome/components/pn7150/pn7150_mifare_ultralight.cpp index 166065f6c1..46f5dba2b7 100644 --- a/esphome/components/pn7150/pn7150_mifare_ultralight.cpp +++ b/esphome/components/pn7150/pn7150_mifare_ultralight.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -144,8 +145,8 @@ uint8_t PN7150::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::sha uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; while (index < buffer_length) { - std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE); - if (this->write_mifare_ultralight_page_(current_page, data) != nfc::STATUS_OK) { + if (this->write_mifare_ultralight_page_(current_page, encoded.data() + index, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) != + nfc::STATUS_OK) { return nfc::STATUS_FAILED; } index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; @@ -158,19 +159,19 @@ uint8_t PN7150::clean_mifare_ultralight_() { uint32_t capacity = this->read_mifare_ultralight_capacity_(); uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; - std::vector blank_data = {0x00, 0x00, 0x00, 0x00}; + static constexpr std::array BLANK_DATA = {0x00, 0x00, 0x00, 0x00}; for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) { - if (this->write_mifare_ultralight_page_(i, blank_data) != nfc::STATUS_OK) { + if (this->write_mifare_ultralight_page_(i, BLANK_DATA.data(), BLANK_DATA.size()) != nfc::STATUS_OK) { return nfc::STATUS_FAILED; } } return nfc::STATUS_OK; } -uint8_t PN7150::write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data) { +uint8_t PN7150::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) { std::vector payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num}; - payload.insert(payload.end(), write_data.begin(), write_data.end()); + payload.insert(payload.end(), write_data, write_data + len); nfc::NciMessage rx; nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload); diff --git a/esphome/components/pn7160/pn7160.h b/esphome/components/pn7160/pn7160.h index 572fab3351..9f2d10c2d5 100644 --- a/esphome/components/pn7160/pn7160.h +++ b/esphome/components/pn7160/pn7160.h @@ -253,7 +253,7 @@ class PN7160 : public nfc::Nfcc, public Component { uint8_t read_mifare_classic_tag_(nfc::NfcTag &tag); uint8_t read_mifare_classic_block_(uint8_t block_num, std::vector &data); - uint8_t write_mifare_classic_block_(uint8_t block_num, std::vector &data); + uint8_t write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len); uint8_t auth_mifare_classic_block_(uint8_t block_num, uint8_t key_num, const uint8_t *key); uint8_t sect_to_auth_(uint8_t block_num); uint8_t format_mifare_classic_mifare_(); @@ -267,7 +267,7 @@ class PN7160 : public nfc::Nfcc, public Component { uint16_t read_mifare_ultralight_capacity_(); uint8_t find_mifare_ultralight_ndef_(const std::vector &page_3_to_6, uint8_t &message_length, uint8_t &message_start_index); - uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data); + uint8_t write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len); uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr &message); uint8_t clean_mifare_ultralight_(); diff --git a/esphome/components/pn7160/pn7160_mifare_classic.cpp b/esphome/components/pn7160/pn7160_mifare_classic.cpp index 57d2042eaa..710a7198c6 100644 --- a/esphome/components/pn7160/pn7160_mifare_classic.cpp +++ b/esphome/components/pn7160/pn7160_mifare_classic.cpp @@ -1,3 +1,4 @@ +#include #include #include "pn7160.h" @@ -139,10 +140,10 @@ uint8_t PN7160::sect_to_auth_(const uint8_t block_num) { } uint8_t PN7160::format_mifare_classic_mifare_() { - std::vector blank_buffer( - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - std::vector trailer_buffer( - {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + static constexpr std::array BLANK_BUFFER = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + static constexpr std::array TRAILER_BUFFER = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x07, 0x80, 0x69, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; auto status = nfc::STATUS_OK; @@ -151,20 +152,20 @@ uint8_t PN7160::format_mifare_classic_mifare_() { continue; } if (block != 0) { - if (this->write_mifare_classic_block_(block, blank_buffer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block); status = nfc::STATUS_FAILED; } } - if (this->write_mifare_classic_block_(block + 1, blank_buffer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 1, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block + 1); status = nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(block + 2, blank_buffer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 2, BLANK_BUFFER.data(), BLANK_BUFFER.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block + 2); status = nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(block + 3, trailer_buffer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 3, TRAILER_BUFFER.data(), TRAILER_BUFFER.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block + 3); status = nfc::STATUS_FAILED; } @@ -174,30 +175,30 @@ uint8_t PN7160::format_mifare_classic_mifare_() { } uint8_t PN7160::format_mifare_classic_ndef_() { - std::vector empty_ndef_message( - {0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - std::vector blank_block( - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); - std::vector block_1_data( - {0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); - std::vector block_2_data( - {0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}); - std::vector block_3_trailer( - {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); - std::vector ndef_trailer( - {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}); + static constexpr std::array EMPTY_NDEF_MESSAGE = { + 0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + static constexpr std::array BLANK_BLOCK = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + static constexpr std::array BLOCK_1_DATA = { + 0x14, 0x01, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + static constexpr std::array BLOCK_2_DATA = { + 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1, 0x03, 0xE1}; + static constexpr std::array BLOCK_3_TRAILER = { + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x78, 0x77, 0x88, 0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + static constexpr std::array NDEF_TRAILER = { + 0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7, 0x7F, 0x07, 0x88, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; if (this->auth_mifare_classic_block_(0, nfc::MIFARE_CMD_AUTH_B, nfc::DEFAULT_KEY) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to authenticate block 0 for formatting"); return nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(1, block_1_data) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(1, BLOCK_1_DATA.data(), BLOCK_1_DATA.size()) != nfc::STATUS_OK) { return nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(2, block_2_data) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(2, BLOCK_2_DATA.data(), BLOCK_2_DATA.size()) != nfc::STATUS_OK) { return nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(3, block_3_trailer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(3, BLOCK_3_TRAILER.data(), BLOCK_3_TRAILER.size()) != nfc::STATUS_OK) { return nfc::STATUS_FAILED; } @@ -210,25 +211,26 @@ uint8_t PN7160::format_mifare_classic_ndef_() { return nfc::STATUS_FAILED; } if (block == 4) { - if (this->write_mifare_classic_block_(block, empty_ndef_message) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block, EMPTY_NDEF_MESSAGE.data(), EMPTY_NDEF_MESSAGE.size()) != + nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block); status = nfc::STATUS_FAILED; } } else { - if (this->write_mifare_classic_block_(block, blank_block) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block); status = nfc::STATUS_FAILED; } } - if (this->write_mifare_classic_block_(block + 1, blank_block) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 1, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block + 1); status = nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(block + 2, blank_block) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 2, BLANK_BLOCK.data(), BLANK_BLOCK.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write block %u", block + 2); status = nfc::STATUS_FAILED; } - if (this->write_mifare_classic_block_(block + 3, ndef_trailer) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(block + 3, NDEF_TRAILER.data(), NDEF_TRAILER.size()) != nfc::STATUS_OK) { ESP_LOGE(TAG, "Unable to write trailer block %u", block + 3); status = nfc::STATUS_FAILED; } @@ -236,7 +238,7 @@ uint8_t PN7160::format_mifare_classic_ndef_() { return status; } -uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vector &write_data) { +uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, const uint8_t *data, size_t len) { nfc::NciMessage rx; nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, {XCHG_DATA_OID, nfc::MIFARE_CMD_WRITE, block_num}); char buf[nfc::FORMAT_BYTES_BUFFER_SIZE]; @@ -248,7 +250,7 @@ uint8_t PN7160::write_mifare_classic_block_(uint8_t block_num, std::vectortransceive_(tx, rx, NFCC_TAG_WRITE_TIMEOUT) != nfc::STATUS_OK) { @@ -294,8 +296,8 @@ uint8_t PN7160::write_mifare_classic_tag_(const std::shared_ptr data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_CLASSIC_BLOCK_SIZE); - if (this->write_mifare_classic_block_(current_block, data) != nfc::STATUS_OK) { + if (this->write_mifare_classic_block_(current_block, encoded.data() + index, nfc::MIFARE_CLASSIC_BLOCK_SIZE) != + nfc::STATUS_OK) { return nfc::STATUS_FAILED; } index += nfc::MIFARE_CLASSIC_BLOCK_SIZE; diff --git a/esphome/components/pn7160/pn7160_mifare_ultralight.cpp b/esphome/components/pn7160/pn7160_mifare_ultralight.cpp index c473ff48d9..9dc8d3dd2d 100644 --- a/esphome/components/pn7160/pn7160_mifare_ultralight.cpp +++ b/esphome/components/pn7160/pn7160_mifare_ultralight.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -144,8 +145,8 @@ uint8_t PN7160::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::sha uint8_t current_page = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; while (index < buffer_length) { - std::vector data(encoded.begin() + index, encoded.begin() + index + nfc::MIFARE_ULTRALIGHT_PAGE_SIZE); - if (this->write_mifare_ultralight_page_(current_page, data) != nfc::STATUS_OK) { + if (this->write_mifare_ultralight_page_(current_page, encoded.data() + index, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) != + nfc::STATUS_OK) { return nfc::STATUS_FAILED; } index += nfc::MIFARE_ULTRALIGHT_PAGE_SIZE; @@ -158,19 +159,19 @@ uint8_t PN7160::clean_mifare_ultralight_() { uint32_t capacity = this->read_mifare_ultralight_capacity_(); uint8_t pages = (capacity / nfc::MIFARE_ULTRALIGHT_PAGE_SIZE) + nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; - std::vector blank_data = {0x00, 0x00, 0x00, 0x00}; + static constexpr std::array BLANK_DATA = {0x00, 0x00, 0x00, 0x00}; for (int i = nfc::MIFARE_ULTRALIGHT_DATA_START_PAGE; i < pages; i++) { - if (this->write_mifare_ultralight_page_(i, blank_data) != nfc::STATUS_OK) { + if (this->write_mifare_ultralight_page_(i, BLANK_DATA.data(), BLANK_DATA.size()) != nfc::STATUS_OK) { return nfc::STATUS_FAILED; } } return nfc::STATUS_OK; } -uint8_t PN7160::write_mifare_ultralight_page_(uint8_t page_num, std::vector &write_data) { +uint8_t PN7160::write_mifare_ultralight_page_(uint8_t page_num, const uint8_t *write_data, size_t len) { std::vector payload = {nfc::MIFARE_CMD_WRITE_ULTRALIGHT, page_num}; - payload.insert(payload.end(), write_data.begin(), write_data.end()); + payload.insert(payload.end(), write_data, write_data + len); nfc::NciMessage rx; nfc::NciMessage tx(nfc::NCI_PKT_MT_DATA, payload); From 931b47673c49e3f29324290b52d591aacab5faed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 16:22:26 -0600 Subject: [PATCH 03/15] Bump github/codeql-action from 4.32.2 to 4.32.3 (#13981) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 51ea4085e0..376825bad6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -58,7 +58,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2 + uses: github/codeql-action/init@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -86,6 +86,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2 + uses: github/codeql-action/analyze@9e907b5e64f6b83e7804b09294d44122997950d6 # v4.32.3 with: category: "/language:${{matrix.language}}" From 7c70b2e04ed80804eb38feac3b4c39abcbc61409 Mon Sep 17 00:00:00 2001 From: Guillermo Ruffino Date: Thu, 12 Feb 2026 13:23:59 -0300 Subject: [PATCH 04/15] [schema-gen] fix Windows: ensure UTF-8 encoding when reading component files (#13952) --- script/build_language_schema.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/build_language_schema.py b/script/build_language_schema.py index c9501cb193..bea540dc63 100755 --- a/script/build_language_schema.py +++ b/script/build_language_schema.py @@ -369,7 +369,7 @@ def get_logger_tags(): "api.service", ] for file in CORE_COMPONENTS_PATH.rglob("*.cpp"): - data = file.read_text() + data = file.read_text(encoding="utf-8") match = pattern.search(data) if match: tags.append(match.group(1)) From 844210519a4a662c76e159a45c7bf09760053254 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Feb 2026 10:26:10 -0600 Subject: [PATCH 05/15] [uart] Remove redundant mutex, fix flush race, conditional event queue (#13955) --- .../uart/uart_component_esp_idf.cpp | 49 ++++++++++--------- .../components/uart/uart_component_esp_idf.h | 17 +++++-- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/esphome/components/uart/uart_component_esp_idf.cpp b/esphome/components/uart/uart_component_esp_idf.cpp index 19b9a4077f..6c242220a6 100644 --- a/esphome/components/uart/uart_component_esp_idf.cpp +++ b/esphome/components/uart/uart_component_esp_idf.cpp @@ -90,7 +90,6 @@ void IDFUARTComponent::setup() { return; } this->uart_num_ = static_cast(next_uart_num++); - this->lock_ = xSemaphoreCreateMutex(); #if (SOC_UART_LP_NUM >= 1) size_t fifo_len = ((this->uart_num_ < SOC_UART_HP_NUM) ? SOC_UART_FIFO_LEN : SOC_LP_UART_FIFO_LEN); @@ -102,11 +101,7 @@ void IDFUARTComponent::setup() { this->rx_buffer_size_ = fifo_len * 2; } - xSemaphoreTake(this->lock_, portMAX_DELAY); - this->load_settings(false); - - xSemaphoreGive(this->lock_); } void IDFUARTComponent::load_settings(bool dump_config) { @@ -126,13 +121,20 @@ void IDFUARTComponent::load_settings(bool dump_config) { return; } } +#ifdef USE_UART_WAKE_LOOP_ON_RX + constexpr int event_queue_size = 20; + QueueHandle_t *event_queue_ptr = &this->uart_event_queue_; +#else + constexpr int event_queue_size = 0; + QueueHandle_t *event_queue_ptr = nullptr; +#endif err = uart_driver_install(this->uart_num_, // UART number this->rx_buffer_size_, // RX ring buffer size - 0, // TX ring buffer size. If zero, driver will not use a TX buffer and TX function will - // block task until all data has been sent out - 20, // event queue size/depth - &this->uart_event_queue_, // event queue - 0 // Flags used to allocate the interrupt + 0, // TX ring buffer size. If zero, driver will not use a TX buffer and TX function will + // block task until all data has been sent out + event_queue_size, // event queue size/depth + event_queue_ptr, // event queue + 0 // Flags used to allocate the interrupt ); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_driver_install failed: %s", esp_err_to_name(err)); @@ -282,9 +284,7 @@ void IDFUARTComponent::set_rx_timeout(size_t rx_timeout) { } void IDFUARTComponent::write_array(const uint8_t *data, size_t len) { - xSemaphoreTake(this->lock_, portMAX_DELAY); int32_t write_len = uart_write_bytes(this->uart_num_, data, len); - xSemaphoreGive(this->lock_); if (write_len != (int32_t) len) { ESP_LOGW(TAG, "uart_write_bytes failed: %d != %zu", write_len, len); this->mark_failed(); @@ -299,7 +299,6 @@ void IDFUARTComponent::write_array(const uint8_t *data, size_t len) { bool IDFUARTComponent::peek_byte(uint8_t *data) { if (!this->check_read_timeout_()) return false; - xSemaphoreTake(this->lock_, portMAX_DELAY); if (this->has_peek_) { *data = this->peek_byte_; } else { @@ -311,7 +310,6 @@ bool IDFUARTComponent::peek_byte(uint8_t *data) { this->peek_byte_ = *data; } } - xSemaphoreGive(this->lock_); return true; } @@ -320,7 +318,6 @@ bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { int32_t read_len = 0; if (!this->check_read_timeout_(len)) return false; - xSemaphoreTake(this->lock_, portMAX_DELAY); if (this->has_peek_) { length_to_read--; *data = this->peek_byte_; @@ -329,7 +326,6 @@ bool IDFUARTComponent::read_array(uint8_t *data, size_t len) { } if (length_to_read > 0) read_len = uart_read_bytes(this->uart_num_, data, length_to_read, 20 / portTICK_PERIOD_MS); - xSemaphoreGive(this->lock_); #ifdef USE_UART_DEBUGGER for (size_t i = 0; i < len; i++) { this->debug_callback_.call(UART_DIRECTION_RX, data[i]); @@ -342,9 +338,7 @@ size_t IDFUARTComponent::available() { size_t available = 0; esp_err_t err; - xSemaphoreTake(this->lock_, portMAX_DELAY); err = uart_get_buffered_data_len(this->uart_num_, &available); - xSemaphoreGive(this->lock_); if (err != ESP_OK) { ESP_LOGW(TAG, "uart_get_buffered_data_len failed: %s", esp_err_to_name(err)); @@ -358,9 +352,7 @@ size_t IDFUARTComponent::available() { void IDFUARTComponent::flush() { ESP_LOGVV(TAG, " Flushing"); - xSemaphoreTake(this->lock_, portMAX_DELAY); uart_wait_tx_done(this->uart_num_, portMAX_DELAY); - xSemaphoreGive(this->lock_); } void IDFUARTComponent::check_logger_conflict() {} @@ -384,6 +376,13 @@ void IDFUARTComponent::start_rx_event_task_() { ESP_LOGV(TAG, "RX event task started"); } +// FreeRTOS task that relays UART ISR events to the main loop. +// This task exists because wake_loop_threadsafe() is not ISR-safe (it uses a +// UDP loopback socket), so we need a task as an ISR-to-main-loop trampoline. +// IMPORTANT: This task must NOT call any UART wrapper methods (read_array, +// write_array, peek_byte, etc.) or touch has_peek_/peek_byte_ — all reading +// is done by the main loop. This task only reads from the event queue and +// calls App.wake_loop_threadsafe(). void IDFUARTComponent::rx_event_task_func(void *param) { auto *self = static_cast(param); uart_event_t event; @@ -405,8 +404,14 @@ void IDFUARTComponent::rx_event_task_func(void *param) { case UART_FIFO_OVF: case UART_BUFFER_FULL: - ESP_LOGW(TAG, "FIFO overflow or ring buffer full - clearing"); - uart_flush_input(self->uart_num_); + // Don't call uart_flush_input() here — this task does not own the read side. + // ESP-IDF examples flush on overflow because the same task handles both events + // and reads, so flush and read are serialized. Here, reads happen on the main + // loop, so flushing from this task races with read_array() and can destroy data + // mid-read. The driver self-heals without an explicit flush: uart_read_bytes() + // calls uart_check_buf_full() after each chunk, which moves stashed FIFO bytes + // into the ring buffer and re-enables RX interrupts once space is freed. + ESP_LOGW(TAG, "FIFO overflow or ring buffer full"); #if defined(USE_SOCKET_SELECT_SUPPORT) && defined(USE_WAKE_LOOP_THREADSAFE) App.wake_loop_threadsafe(); #endif diff --git a/esphome/components/uart/uart_component_esp_idf.h b/esphome/components/uart/uart_component_esp_idf.h index 1ecb02d7ab..1517eab509 100644 --- a/esphome/components/uart/uart_component_esp_idf.h +++ b/esphome/components/uart/uart_component_esp_idf.h @@ -8,6 +8,13 @@ namespace esphome::uart { +/// ESP-IDF UART driver wrapper. +/// +/// Thread safety: All public methods must only be called from the main loop. +/// The ESP-IDF UART driver API does not guarantee thread safety, and ESPHome's +/// peek byte state (has_peek_/peek_byte_) is not synchronized. The rx_event_task +/// (when enabled) must not call any of these methods — it communicates with the +/// main loop exclusively via App.wake_loop_threadsafe(). class IDFUARTComponent : public UARTComponent, public Component { public: void setup() override; @@ -26,7 +33,9 @@ class IDFUARTComponent : public UARTComponent, public Component { void flush() override; uint8_t get_hw_serial_number() { return this->uart_num_; } +#ifdef USE_UART_WAKE_LOOP_ON_RX QueueHandle_t *get_uart_event_queue() { return &this->uart_event_queue_; } +#endif /** * Load the UART with the current settings. @@ -46,18 +55,20 @@ class IDFUARTComponent : public UARTComponent, public Component { protected: void check_logger_conflict() override; uart_port_t uart_num_; - QueueHandle_t uart_event_queue_; uart_config_t get_config_(); - SemaphoreHandle_t lock_; bool has_peek_{false}; uint8_t peek_byte_; #ifdef USE_UART_WAKE_LOOP_ON_RX - // RX notification support + // RX notification support — runs on a separate FreeRTOS task. + // IMPORTANT: rx_event_task_func must NOT call any UART wrapper methods (read_array, + // write_array, etc.) or touch has_peek_/peek_byte_. It must only read from the + // event queue and call App.wake_loop_threadsafe(). void start_rx_event_task_(); static void rx_event_task_func(void *param); + QueueHandle_t uart_event_queue_; TaskHandle_t rx_event_task_handle_{nullptr}; #endif // USE_UART_WAKE_LOOP_ON_RX }; From ead7937dbf74a26b92c9c2dbf14b0d875356edcd Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Feb 2026 11:04:23 -0600 Subject: [PATCH 06/15] [api] Extract cold code from APIServer::loop() hot path (#13902) --- esphome/components/api/api_server.cpp | 130 ++++++++++++++------------ esphome/components/api/api_server.h | 5 + 2 files changed, 74 insertions(+), 61 deletions(-) diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 53b41a5c14..5503cf4db8 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -117,37 +117,7 @@ void APIServer::setup() { void APIServer::loop() { // Accept new clients only if the socket exists and has incoming connections if (this->socket_ && this->socket_->ready()) { - while (true) { - struct sockaddr_storage source_addr; - socklen_t addr_len = sizeof(source_addr); - - auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len); - if (!sock) - break; - - char peername[socket::SOCKADDR_STR_LEN]; - sock->getpeername_to(peername); - - // Check if we're at the connection limit - if (this->clients_.size() >= this->max_connections_) { - ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername); - // Immediately close - socket destructor will handle cleanup - sock.reset(); - continue; - } - - ESP_LOGD(TAG, "Accept %s", peername); - - auto *conn = new APIConnection(std::move(sock), this); - this->clients_.emplace_back(conn); - conn->start(); - - // First client connected - clear warning and update timestamp - if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) { - this->status_clear_warning(); - this->last_connected_ = App.get_loop_component_start_time(); - } - } + this->accept_new_connections_(); } if (this->clients_.empty()) { @@ -178,46 +148,84 @@ void APIServer::loop() { while (client_index < this->clients_.size()) { auto &client = this->clients_[client_index]; - if (!client->flags_.remove) { + if (client->flags_.remove) { + // Rare case: handle disconnection (don't increment - swapped element needs processing) + this->remove_client_(client_index); + } else { // Common case: process active client client->loop(); client_index++; + } + } +} + +void APIServer::remove_client_(size_t client_index) { + auto &client = this->clients_[client_index]; + +#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES + this->unregister_active_action_calls_for_connection(client.get()); +#endif + ESP_LOGV(TAG, "Remove connection %s", client->get_name()); + +#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER + // Save client info before closing socket and removal for the trigger + char peername_buf[socket::SOCKADDR_STR_LEN]; + std::string client_name(client->get_name()); + std::string client_peername(client->get_peername_to(peername_buf)); +#endif + + // Close socket now (was deferred from on_fatal_error to allow getpeername) + client->helper_->close(); + + // Swap with the last element and pop (avoids expensive vector shifts) + if (client_index < this->clients_.size() - 1) { + std::swap(this->clients_[client_index], this->clients_.back()); + } + this->clients_.pop_back(); + + // Last client disconnected - set warning and start tracking for reboot timeout + if (this->clients_.empty() && this->reboot_timeout_ != 0) { + this->status_set_warning(); + this->last_connected_ = App.get_loop_component_start_time(); + } + +#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER + // Fire trigger after client is removed so api.connected reflects the true state + this->client_disconnected_trigger_.trigger(client_name, client_peername); +#endif +} + +void APIServer::accept_new_connections_() { + while (true) { + struct sockaddr_storage source_addr; + socklen_t addr_len = sizeof(source_addr); + + auto sock = this->socket_->accept_loop_monitored((struct sockaddr *) &source_addr, &addr_len); + if (!sock) + break; + + char peername[socket::SOCKADDR_STR_LEN]; + sock->getpeername_to(peername); + + // Check if we're at the connection limit + if (this->clients_.size() >= this->max_connections_) { + ESP_LOGW(TAG, "Max connections (%d), rejecting %s", this->max_connections_, peername); + // Immediately close - socket destructor will handle cleanup + sock.reset(); continue; } - // Rare case: handle disconnection -#ifdef USE_API_USER_DEFINED_ACTION_RESPONSES - this->unregister_active_action_calls_for_connection(client.get()); -#endif - ESP_LOGV(TAG, "Remove connection %s", client->get_name()); + ESP_LOGD(TAG, "Accept %s", peername); -#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER - // Save client info before closing socket and removal for the trigger - char peername_buf[socket::SOCKADDR_STR_LEN]; - std::string client_name(client->get_name()); - std::string client_peername(client->get_peername_to(peername_buf)); -#endif + auto *conn = new APIConnection(std::move(sock), this); + this->clients_.emplace_back(conn); + conn->start(); - // Close socket now (was deferred from on_fatal_error to allow getpeername) - client->helper_->close(); - - // Swap with the last element and pop (avoids expensive vector shifts) - if (client_index < this->clients_.size() - 1) { - std::swap(this->clients_[client_index], this->clients_.back()); - } - this->clients_.pop_back(); - - // Last client disconnected - set warning and start tracking for reboot timeout - if (this->clients_.empty() && this->reboot_timeout_ != 0) { - this->status_set_warning(); + // First client connected - clear warning and update timestamp + if (this->clients_.size() == 1 && this->reboot_timeout_ != 0) { + this->status_clear_warning(); this->last_connected_ = App.get_loop_component_start_time(); } - -#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER - // Fire trigger after client is removed so api.connected reflects the true state - this->client_disconnected_trigger_.trigger(client_name, client_peername); -#endif - // Don't increment client_index since we need to process the swapped element } } diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 6ab3cdc576..28f60343e0 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -234,6 +234,11 @@ class APIServer : public Component, #endif protected: + // Accept incoming socket connections. Only called when socket has pending connections. + void __attribute__((noinline)) accept_new_connections_(); + // Remove a disconnected client by index. Swaps with last element and pops. + void __attribute__((noinline)) remove_client_(size_t client_index); + #ifdef USE_API_NOISE bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg, const psk_t &active_psk, bool make_active); From e9bf9bc691196e4fff13a2c0d866c1e588968f50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Ma=C5=88as?= Date: Thu, 12 Feb 2026 18:20:54 +0100 Subject: [PATCH 07/15] [pulse_meter] Fix early edge detection (#12360) Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> --- .../pulse_meter/pulse_meter_sensor.cpp | 75 ++++++++++--------- .../pulse_meter/pulse_meter_sensor.h | 11 ++- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.cpp b/esphome/components/pulse_meter/pulse_meter_sensor.cpp index 007deb66e5..433e1f0b7e 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.cpp +++ b/esphome/components/pulse_meter/pulse_meter_sensor.cpp @@ -38,8 +38,7 @@ void PulseMeterSensor::setup() { } void PulseMeterSensor::loop() { - // Reset the count in get before we pass it back to the ISR as set - this->get_->count_ = 0; + State state; { // Lock the interrupt so the interrupt code doesn't interfere with itself @@ -58,31 +57,35 @@ void PulseMeterSensor::loop() { } this->last_pin_val_ = current; - // Swap out set and get to get the latest state from the ISR - std::swap(this->set_, this->get_); + // Get the latest state from the ISR and reset the count in the ISR + state.last_detected_edge_us_ = this->state_.last_detected_edge_us_; + state.last_rising_edge_us_ = this->state_.last_rising_edge_us_; + state.count_ = this->state_.count_; + this->state_.count_ = 0; } const uint32_t now = micros(); // If an edge was peeked, repay the debt - if (this->peeked_edge_ && this->get_->count_ > 0) { + if (this->peeked_edge_ && state.count_ > 0) { this->peeked_edge_ = false; - this->get_->count_--; // NOLINT(clang-diagnostic-deprecated-volatile) + state.count_--; } - // If there is an unprocessed edge, and filter_us_ has passed since, count this edge early - if (this->get_->last_rising_edge_us_ != this->get_->last_detected_edge_us_ && - now - this->get_->last_rising_edge_us_ >= this->filter_us_) { + // If there is an unprocessed edge, and filter_us_ has passed since, count this edge early. + // Wait for the debt to be repaid before counting another unprocessed edge early. + if (!this->peeked_edge_ && state.last_rising_edge_us_ != state.last_detected_edge_us_ && + now - state.last_rising_edge_us_ >= this->filter_us_) { this->peeked_edge_ = true; - this->get_->last_detected_edge_us_ = this->get_->last_rising_edge_us_; - this->get_->count_++; // NOLINT(clang-diagnostic-deprecated-volatile) + state.last_detected_edge_us_ = state.last_rising_edge_us_; + state.count_++; } // Check if we detected a pulse this loop - if (this->get_->count_ > 0) { + if (state.count_ > 0) { // Keep a running total of pulses if a total sensor is configured if (this->total_sensor_ != nullptr) { - this->total_pulses_ += this->get_->count_; + this->total_pulses_ += state.count_; const uint32_t total = this->total_pulses_; this->total_sensor_->publish_state(total); } @@ -94,15 +97,15 @@ void PulseMeterSensor::loop() { this->meter_state_ = MeterState::RUNNING; } break; case MeterState::RUNNING: { - uint32_t delta_us = this->get_->last_detected_edge_us_ - this->last_processed_edge_us_; - float pulse_width_us = delta_us / float(this->get_->count_); - ESP_LOGV(TAG, "New pulse, delta: %" PRIu32 " µs, count: %" PRIu32 ", width: %.5f µs", delta_us, - this->get_->count_, pulse_width_us); + uint32_t delta_us = state.last_detected_edge_us_ - this->last_processed_edge_us_; + float pulse_width_us = delta_us / float(state.count_); + ESP_LOGV(TAG, "New pulse, delta: %" PRIu32 " µs, count: %" PRIu32 ", width: %.5f µs", delta_us, state.count_, + pulse_width_us); this->publish_state((60.0f * 1000000.0f) / pulse_width_us); } break; } - this->last_processed_edge_us_ = this->get_->last_detected_edge_us_; + this->last_processed_edge_us_ = state.last_detected_edge_us_; } // No detected edges this loop else { @@ -141,14 +144,14 @@ void IRAM_ATTR PulseMeterSensor::edge_intr(PulseMeterSensor *sensor) { // This is an interrupt handler - we can't call any virtual method from this method // Get the current time before we do anything else so the measurements are consistent const uint32_t now = micros(); - auto &state = sensor->edge_state_; - auto &set = *sensor->set_; + auto &edge_state = sensor->edge_state_; + auto &state = sensor->state_; - if ((now - state.last_sent_edge_us_) >= sensor->filter_us_) { - state.last_sent_edge_us_ = now; - set.last_detected_edge_us_ = now; - set.last_rising_edge_us_ = now; - set.count_++; // NOLINT(clang-diagnostic-deprecated-volatile) + if ((now - edge_state.last_sent_edge_us_) >= sensor->filter_us_) { + edge_state.last_sent_edge_us_ = now; + state.last_detected_edge_us_ = now; + state.last_rising_edge_us_ = now; + state.count_++; // NOLINT(clang-diagnostic-deprecated-volatile) } // This ISR is bound to rising edges, so the pin is high @@ -160,26 +163,26 @@ void IRAM_ATTR PulseMeterSensor::pulse_intr(PulseMeterSensor *sensor) { // Get the current time before we do anything else so the measurements are consistent const uint32_t now = micros(); const bool pin_val = sensor->isr_pin_.digital_read(); - auto &state = sensor->pulse_state_; - auto &set = *sensor->set_; + auto &pulse_state = sensor->pulse_state_; + auto &state = sensor->state_; // Filter length has passed since the last interrupt - const bool length = now - state.last_intr_ >= sensor->filter_us_; + const bool length = now - pulse_state.last_intr_ >= sensor->filter_us_; - if (length && state.latched_ && !sensor->last_pin_val_) { // Long enough low edge - state.latched_ = false; - } else if (length && !state.latched_ && sensor->last_pin_val_) { // Long enough high edge - state.latched_ = true; - set.last_detected_edge_us_ = state.last_intr_; - set.count_++; // NOLINT(clang-diagnostic-deprecated-volatile) + if (length && pulse_state.latched_ && !sensor->last_pin_val_) { // Long enough low edge + pulse_state.latched_ = false; + } else if (length && !pulse_state.latched_ && sensor->last_pin_val_) { // Long enough high edge + pulse_state.latched_ = true; + state.last_detected_edge_us_ = pulse_state.last_intr_; + state.count_++; // NOLINT(clang-diagnostic-deprecated-volatile) } // Due to order of operations this includes // length && latched && rising (just reset from a long low edge) // !latched && (rising || high) (noise on the line resetting the potential rising edge) - set.last_rising_edge_us_ = !state.latched_ && pin_val ? now : set.last_detected_edge_us_; + state.last_rising_edge_us_ = !pulse_state.latched_ && pin_val ? now : state.last_detected_edge_us_; - state.last_intr_ = now; + pulse_state.last_intr_ = now; sensor->last_pin_val_ = pin_val; } diff --git a/esphome/components/pulse_meter/pulse_meter_sensor.h b/esphome/components/pulse_meter/pulse_meter_sensor.h index 5800c4ec42..e46f1e615f 100644 --- a/esphome/components/pulse_meter/pulse_meter_sensor.h +++ b/esphome/components/pulse_meter/pulse_meter_sensor.h @@ -46,17 +46,16 @@ class PulseMeterSensor : public sensor::Sensor, public Component { uint32_t total_pulses_ = 0; uint32_t last_processed_edge_us_ = 0; - // This struct (and the two pointers) are used to pass data between the ISR and loop. - // These two pointers are exchanged each loop. - // Use these to send data from the ISR to the loop not the other way around (except for resetting the values). + // This struct and variable are used to pass data between the ISR and loop. + // The data from state_ is read and then count_ in state_ is reset in each loop. + // This must be done while guarded by an InterruptLock. Use this variable to send data + // from the ISR to the loop not the other way around (except for resetting count_). struct State { uint32_t last_detected_edge_us_ = 0; uint32_t last_rising_edge_us_ = 0; uint32_t count_ = 0; }; - State state_[2]; - volatile State *set_ = state_; - volatile State *get_ = state_ + 1; + volatile State state_{}; // Only use the following variables in the ISR or while guarded by an InterruptLock ISRInternalGPIOPin isr_pin_; From c08356b0c1d973963cf7d615849b45184b47e4ed Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:04:39 -0500 Subject: [PATCH 08/15] [alarm_control_panel] Fix flaky integration test race condition (#13964) Co-authored-by: Claude Opus 4.6 --- ...t_alarm_control_panel_state_transitions.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/tests/integration/test_alarm_control_panel_state_transitions.py b/tests/integration/test_alarm_control_panel_state_transitions.py index 09348f5bea..0b07710961 100644 --- a/tests/integration/test_alarm_control_panel_state_transitions.py +++ b/tests/integration/test_alarm_control_panel_state_transitions.py @@ -270,6 +270,14 @@ async def test_alarm_control_panel_state_transitions( # The chime_sensor has chime: true, so opening it while disarmed # should trigger on_chime callback + # Set up future for the on_ready from opening the chime sensor + # (alarm becomes "not ready" when chime sensor opens). + # We must wait for this BEFORE creating the close future, otherwise + # the open event's log can arrive late and resolve the close future, + # causing the test to proceed before the chime close is processed. + ready_after_chime_open: asyncio.Future[bool] = loop.create_future() + ready_futures.append(ready_after_chime_open) + # We're currently DISARMED - open the chime sensor client.switch_command(chime_switch_info.key, True) @@ -279,11 +287,18 @@ async def test_alarm_control_panel_state_transitions( except TimeoutError: pytest.fail(f"on_chime callback not fired. Log lines: {log_lines[-20:]}") - # Close the chime sensor and wait for alarm to become ready again - # We need to wait for this transition before testing door sensor, - # otherwise there's a race where the door sensor state change could - # arrive before the chime sensor state change, leaving the alarm in - # a continuous "not ready" state with no on_ready callback fired. + # Wait for the on_ready from the chime sensor opening + try: + await asyncio.wait_for(ready_after_chime_open, timeout=2.0) + except TimeoutError: + pytest.fail( + f"on_ready callback not fired when chime sensor opened. " + f"Log lines: {log_lines[-20:]}" + ) + + # Now create the future for the close event and close the sensor. + # Since we waited for the open event above, the close event's + # on_ready log cannot be confused with the open event's. ready_after_chime_close: asyncio.Future[bool] = loop.create_future() ready_futures.append(ready_after_chime_close) From 297dfb0db41174bdb786e419e8100f0c30233d23 Mon Sep 17 00:00:00 2001 From: Jonathan Swoboda <154711427+swoboda1337@users.noreply.github.com> Date: Thu, 12 Feb 2026 16:12:17 -0500 Subject: [PATCH 09/15] [docker] Suppress git detached HEAD advice (#13962) Co-authored-by: Claude Opus 4.6 --- docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 8ebdd1e49b..540d28be7f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -9,7 +9,8 @@ FROM ghcr.io/esphome/docker-base:${BUILD_OS}-ha-addon-${BUILD_BASE_VERSION} AS b ARG BUILD_TYPE FROM base-source-${BUILD_TYPE} AS base -RUN git config --system --add safe.directory "*" +RUN git config --system --add safe.directory "*" \ + && git config --system advice.detachedHead false # Install build tools for Python packages that require compilation # (e.g., ruamel.yaml.clibz used by ESP-IDF's idf-component-manager) From f6aeef2e68f1c891f401f80c5886215baf811892 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 12 Feb 2026 18:20:58 -0600 Subject: [PATCH 10/15] [api] Fix ESP8266 noise API handshake deadlock and prompt socket cleanup (#13972) --- esphome/components/api/api_frame_helper_noise.cpp | 10 ++++++---- esphome/components/api/api_server.cpp | 8 ++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index c1641b398a..1ae848dead 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -138,10 +138,12 @@ APIError APINoiseFrameHelper::handle_noise_error_(int err, const LogString *func /// Run through handshake messages (if in that phase) APIError APINoiseFrameHelper::loop() { - // During handshake phase, process as many actions as possible until we can't progress - // socket_->ready() stays true until next main loop, but state_action() will return - // WOULD_BLOCK when no more data is available to read - while (state_ != State::DATA && this->socket_->ready()) { + // Cache ready() outside the loop. On ESP8266 LWIP raw TCP, ready() returns false once + // the rx buffer is consumed. Re-checking each iteration would block handshake writes + // that must follow reads, deadlocking the handshake. state_action() will return + // WOULD_BLOCK when no more data is available to read. + bool socket_ready = this->socket_->ready(); + while (state_ != State::DATA && socket_ready) { APIError err = state_action_(); if (err == APIError::WOULD_BLOCK) { break; diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 5503cf4db8..28128d39bc 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -148,12 +148,16 @@ void APIServer::loop() { while (client_index < this->clients_.size()) { auto &client = this->clients_[client_index]; + // Common case: process active client + if (!client->flags_.remove) { + client->loop(); + } + // Handle disconnection promptly - close socket to free LWIP PCB + // resources and prevent retransmit crashes on ESP8266. if (client->flags_.remove) { // Rare case: handle disconnection (don't increment - swapped element needs processing) this->remove_client_(client_index); } else { - // Common case: process active client - client->loop(); client_index++; } } From a8a324cbfbdcb2b41de9de3a544230bf372a104a Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Sat, 14 Feb 2026 13:53:54 +1300 Subject: [PATCH 11/15] Bump version to 2026.2.0b2 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 16516a387f..572e20a694 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2026.2.0b1 +PROJECT_NUMBER = 2026.2.0b2 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 3b5cccfb25..247b2b7e4e 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2026.2.0b1" +__version__ = "2026.2.0b2" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 36776b40c2010b6635c9bad297e4985c9db4cbcb Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Feb 2026 08:21:04 -0700 Subject: [PATCH 12/15] [wifi] Fix ESP8266 DHCP state corruption from premature dhcp_renew() (#13983) Co-authored-by: Claude Opus 4.6 --- .../components/wifi/wifi_component_esp8266.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index c87345f0bf..cbf7d7d80f 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -216,23 +216,16 @@ bool WiFiComponent::wifi_apply_hostname_() { ESP_LOGV(TAG, "Set hostname failed"); } - // inform dhcp server of hostname change using dhcp_renew() + // Update hostname on all lwIP interfaces so DHCP packets include it. + // lwIP includes the hostname in DHCP DISCOVER/REQUEST automatically + // via LWIP_NETIF_HOSTNAME — no dhcp_renew() needed. The hostname is + // fixed at compile time and never changes at runtime. for (netif *intf = netif_list; intf; intf = intf->next) { - // unconditionally update all known interfaces #if LWIP_VERSION_MAJOR == 1 intf->hostname = (char *) wifi_station_get_hostname(); #else intf->hostname = wifi_station_get_hostname(); #endif - if (netif_dhcp_data(intf) != nullptr) { - // renew already started DHCP leases - err_t lwipret = dhcp_renew(intf); - if (lwipret != ERR_OK) { - ESP_LOGW(TAG, "wifi_apply_hostname_(%s): lwIP error %d on interface %c%c (index %d)", intf->hostname, - (int) lwipret, intf->name[0], intf->name[1], intf->num); - ret = false; - } - } } return ret; From 5a6d64814a3a5f51f6e238620938416523947e94 Mon Sep 17 00:00:00 2001 From: AndreKR Date: Sat, 14 Feb 2026 18:08:26 +0100 Subject: [PATCH 13/15] [http_request] Improve TLS logging on ESP8266 (#13985) --- .../http_request/http_request_arduino.cpp | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/esphome/components/http_request/http_request_arduino.cpp b/esphome/components/http_request/http_request_arduino.cpp index aee1f651bf..e5b919e380 100644 --- a/esphome/components/http_request/http_request_arduino.cpp +++ b/esphome/components/http_request/http_request_arduino.cpp @@ -9,9 +9,20 @@ #include "esphome/core/defines.h" #include "esphome/core/log.h" +// Include BearSSL error constants for TLS failure diagnostics +#ifdef USE_ESP8266 +#include +#endif + namespace esphome::http_request { static const char *const TAG = "http_request.arduino"; +#ifdef USE_ESP8266 +static constexpr int RX_BUFFER_SIZE = 512; +static constexpr int TX_BUFFER_SIZE = 512; +// ESP8266 Arduino core (WiFiClientSecureBearSSL.cpp) returns -1000 on OOM +static constexpr int ESP8266_SSL_ERR_OOM = -1000; +#endif std::shared_ptr HttpRequestArduino::perform(const std::string &url, const std::string &method, const std::string &body, @@ -47,7 +58,7 @@ std::shared_ptr HttpRequestArduino::perform(const std::string &ur ESP_LOGV(TAG, "ESP8266 HTTPS connection with WiFiClientSecure"); stream_ptr = std::make_unique(); WiFiClientSecure *secure_client = static_cast(stream_ptr.get()); - secure_client->setBufferSizes(512, 512); + secure_client->setBufferSizes(RX_BUFFER_SIZE, TX_BUFFER_SIZE); secure_client->setInsecure(); } else { stream_ptr = std::make_unique(); @@ -107,13 +118,42 @@ std::shared_ptr HttpRequestArduino::perform(const std::string &ur container->status_code = container->client_.sendRequest(method.c_str(), body.c_str()); App.feed_wdt(); if (container->status_code < 0) { +#if defined(USE_ESP8266) && defined(USE_HTTP_REQUEST_ESP8266_HTTPS) + if (secure) { + WiFiClientSecure *secure_client = static_cast(stream_ptr.get()); + int last_error = secure_client->getLastSSLError(); + + if (last_error != 0) { + const LogString *error_msg; + switch (last_error) { + case ESP8266_SSL_ERR_OOM: + error_msg = LOG_STR("Unable to allocate buffer memory"); + break; + case BR_ERR_TOO_LARGE: + error_msg = LOG_STR("Incoming TLS record does not fit in receive buffer (BR_ERR_TOO_LARGE)"); + break; + default: + error_msg = LOG_STR("Unknown SSL error"); + break; + } + ESP_LOGW(TAG, "SSL failure: %s (Code: %d)", LOG_STR_ARG(error_msg), last_error); + if (last_error == ESP8266_SSL_ERR_OOM) { + ESP_LOGW(TAG, "Heap free: %u bytes, configured buffer sizes: %u bytes", ESP.getFreeHeap(), + static_cast(RX_BUFFER_SIZE + TX_BUFFER_SIZE)); + } + } else { + ESP_LOGW(TAG, "Connection failure with no error code"); + } + } +#endif + ESP_LOGW(TAG, "HTTP Request failed; URL: %s; Error: %s", url.c_str(), HTTPClient::errorToString(container->status_code).c_str()); + this->status_momentary_error("failed", 1000); container->end(); return nullptr; } - if (!is_success(container->status_code)) { ESP_LOGE(TAG, "HTTP Request failed; URL: %s; Code: %d", url.c_str(), container->status_code); this->status_momentary_error("failed", 1000); From 38404b2013f031c921ac43cbec4f3d4b898deccf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 14 Feb 2026 15:11:17 -0700 Subject: [PATCH 14/15] Bump ruff from 0.15.0 to 0.15.1 (#13980) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston --- .pre-commit-config.yaml | 2 +- requirements_test.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 991e053d5a..6d89060b0d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.15.0 + rev: v0.15.1 hooks: # Run the linter. - id: ruff diff --git a/requirements_test.txt b/requirements_test.txt index 2cf6f6456e..9e99855f6f 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ pylint==4.0.4 flake8==7.3.0 # also change in .pre-commit-config.yaml when updating -ruff==0.15.0 # also change in .pre-commit-config.yaml when updating +ruff==0.15.1 # also change in .pre-commit-config.yaml when updating pyupgrade==3.21.2 # also change in .pre-commit-config.yaml when updating pre-commit From 062f223876c023cfed04b48140c6841019c0d0ca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sat, 14 Feb 2026 15:43:50 -0700 Subject: [PATCH 15/15] [json, core] Remove stored RAMAllocator, make constructors constexpr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RAMAllocator with default flags is stateless — it's just a dispatch wrapper over heap_caps_malloc/realloc/free. Remove the stored member from SpiRamAllocator, using stack-local instances at each call site. Also make RAMAllocator constructors constexpr so the compiler can fully evaluate flag logic at compile time. Note: SpiRamAllocator was initialized with RAMAllocator::NONE (0), which is equivalent to default construction since the constructor preserves the default ALLOC_INTERNAL | ALLOC_EXTERNAL flags when no valid allocation flags are provided. Co-Authored-By: J. Nick Koston --- esphome/components/json/json_util.h | 11 ++++++----- esphome/core/helpers.h | 11 ++++------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/esphome/components/json/json_util.h b/esphome/components/json/json_util.h index ca074926bd..c472b9a9ec 100644 --- a/esphome/components/json/json_util.h +++ b/esphome/components/json/json_util.h @@ -18,7 +18,10 @@ namespace json { // Build an allocator for the JSON Library using the RAMAllocator class // This is only compiled when PSRAM is enabled struct SpiRamAllocator : ArduinoJson::Allocator { - void *allocate(size_t size) override { return allocator_.allocate(size); } + void *allocate(size_t size) override { + RAMAllocator allocator; + return allocator.allocate(size); + } void deallocate(void *ptr) override { // ArduinoJson's Allocator interface doesn't provide the size parameter in deallocate. @@ -31,11 +34,9 @@ struct SpiRamAllocator : ArduinoJson::Allocator { } void *reallocate(void *ptr, size_t new_size) override { - return allocator_.reallocate(static_cast(ptr), new_size); + RAMAllocator allocator; + return allocator.reallocate(static_cast(ptr), new_size); } - - protected: - RAMAllocator allocator_{RAMAllocator::NONE}; }; #endif diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 34c7452484..298b93fbc4 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1673,13 +1673,10 @@ template class RAMAllocator { ALLOW_FAILURE = 1 << 2, // Does nothing. Kept for compatibility. }; - RAMAllocator() = default; - RAMAllocator(uint8_t flags) { - // default is both external and internal - flags &= ALLOC_INTERNAL | ALLOC_EXTERNAL; - if (flags != 0) - this->flags_ = flags; - } + constexpr RAMAllocator() = default; + constexpr RAMAllocator(uint8_t flags) + : flags_((flags & (ALLOC_INTERNAL | ALLOC_EXTERNAL)) != 0 ? (flags & (ALLOC_INTERNAL | ALLOC_EXTERNAL)) + : (ALLOC_INTERNAL | ALLOC_EXTERNAL)) {} template constexpr RAMAllocator(const RAMAllocator &other) : flags_{other.flags_} {} T *allocate(size_t n) { return this->allocate(n, sizeof(T)); }