Compare commits

...

9 Commits

58 changed files with 285 additions and 150 deletions

View File

@@ -1 +1 @@
d565b0589e35e692b5f2fc0c14723a99595b4828a3a3ef96c442e86a23176c00 a172e2f65981e98354cc6b5ecf69bdb055dd13602226042ab2c7acd037a2bf41

View File

@@ -9,7 +9,7 @@ static const char *const TAG = "bl0940.number";
void CalibrationNumber::setup() { void CalibrationNumber::setup() {
float value = 0.0f; float value = 0.0f;
if (this->restore_value_) { if (this->restore_value_) {
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<float>();
if (!this->pref_.load(&value)) { if (!this->pref_.load(&value)) {
value = 0.0f; value = 0.0f;
} }

View File

@@ -360,8 +360,7 @@ void Climate::add_on_control_callback(std::function<void(ClimateCall &)> &&callb
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
optional<ClimateDeviceRestoreState> Climate::restore_state_() { optional<ClimateDeviceRestoreState> Climate::restore_state_() {
this->rtc_ = global_preferences->make_preference<ClimateDeviceRestoreState>(this->get_preference_hash() ^ this->rtc_ = this->make_entity_preference<ClimateDeviceRestoreState>(RESTORE_STATE_VERSION);
RESTORE_STATE_VERSION);
ClimateDeviceRestoreState recovered{}; ClimateDeviceRestoreState recovered{};
if (!this->rtc_.load(&recovered)) if (!this->rtc_.load(&recovered))
return {}; return {};

View File

@@ -187,7 +187,7 @@ void Cover::publish_state(bool save) {
} }
} }
optional<CoverRestoreState> Cover::restore_state_() { optional<CoverRestoreState> Cover::restore_state_() {
this->rtc_ = global_preferences->make_preference<CoverRestoreState>(this->get_preference_hash()); this->rtc_ = this->make_entity_preference<CoverRestoreState>();
CoverRestoreState recovered{}; CoverRestoreState recovered{};
if (!this->rtc_.load(&recovered)) if (!this->rtc_.load(&recovered))
return {}; return {};

View File

@@ -41,7 +41,7 @@ void DutyTimeSensor::setup() {
uint32_t seconds = 0; uint32_t seconds = 0;
if (this->restore_) { if (this->restore_) {
this->pref_ = global_preferences->make_preference<uint32_t>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<uint32_t>();
this->pref_.load(&seconds); this->pref_.load(&seconds);
} }

View File

@@ -3,6 +3,7 @@
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <cinttypes> #include <cinttypes>
@@ -19,7 +20,8 @@ static bool was_power_cycled() {
#endif #endif
#ifdef USE_ESP8266 #ifdef USE_ESP8266
auto reset_reason = EspClass::getResetReason(); auto reset_reason = EspClass::getResetReason();
return strcasecmp(reset_reason.c_str(), "power On") == 0 || strcasecmp(reset_reason.c_str(), "external system") == 0; return ESPHOME_strcasecmp_P(reset_reason.c_str(), ESPHOME_PSTR("power On")) == 0 ||
ESPHOME_strcasecmp_P(reset_reason.c_str(), ESPHOME_PSTR("external system")) == 0;
#endif #endif
#ifdef USE_LIBRETINY #ifdef USE_LIBRETINY
auto reason = lt_get_reboot_reason(); auto reason = lt_get_reboot_reason();

View File

@@ -227,8 +227,7 @@ void Fan::publish_state() {
constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA; constexpr uint32_t RESTORE_STATE_VERSION = 0x71700ABA;
optional<FanRestoreState> Fan::restore_state_() { optional<FanRestoreState> Fan::restore_state_() {
FanRestoreState recovered{}; FanRestoreState recovered{};
this->rtc_ = this->rtc_ = this->make_entity_preference<FanRestoreState>(RESTORE_STATE_VERSION);
global_preferences->make_preference<FanRestoreState>(this->get_preference_hash() ^ RESTORE_STATE_VERSION);
bool restored = this->rtc_.load(&recovered); bool restored = this->rtc_.load(&recovered);
switch (this->restore_mode_) { switch (this->restore_mode_) {

View File

@@ -350,8 +350,7 @@ ClimateTraits HaierClimateBase::traits() { return traits_; }
void HaierClimateBase::initialization() { void HaierClimateBase::initialization() {
constexpr uint32_t restore_settings_version = 0xA77D21EF; constexpr uint32_t restore_settings_version = 0xA77D21EF;
this->base_rtc_ = this->base_rtc_ = this->make_entity_preference<HaierBaseSettings>(restore_settings_version);
global_preferences->make_preference<HaierBaseSettings>(this->get_preference_hash() ^ restore_settings_version);
HaierBaseSettings recovered; HaierBaseSettings recovered;
if (!this->base_rtc_.load(&recovered)) { if (!this->base_rtc_.load(&recovered)) {
recovered = {false, true}; recovered = {false, true};

View File

@@ -515,8 +515,7 @@ haier_protocol::HaierMessage HonClimate::get_power_message(bool state) {
void HonClimate::initialization() { void HonClimate::initialization() {
HaierClimateBase::initialization(); HaierClimateBase::initialization();
constexpr uint32_t restore_settings_version = 0x57EB59DDUL; constexpr uint32_t restore_settings_version = 0x57EB59DDUL;
this->hon_rtc_ = this->hon_rtc_ = this->make_entity_preference<HonSettings>(restore_settings_version);
global_preferences->make_preference<HonSettings>(this->get_preference_hash() ^ restore_settings_version);
HonSettings recovered; HonSettings recovered;
if (this->hon_rtc_.load(&recovered)) { if (this->hon_rtc_.load(&recovered)) {
this->settings_ = recovered; this->settings_ = recovered;

View File

@@ -10,7 +10,7 @@ static const char *const TAG = "integration";
void IntegrationSensor::setup() { void IntegrationSensor::setup() {
if (this->restore_) { if (this->restore_) {
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<float>();
float preference_value = 0; float preference_value = 0;
this->pref_.load(&preference_value); this->pref_.load(&preference_value);
this->result_ = preference_value; this->result_ = preference_value;

View File

@@ -184,7 +184,7 @@ static inline bool validate_header_footer(const uint8_t *header_footer, const ui
void LD2450Component::setup() { void LD2450Component::setup() {
#ifdef USE_NUMBER #ifdef USE_NUMBER
if (this->presence_timeout_number_ != nullptr) { if (this->presence_timeout_number_ != nullptr) {
this->pref_ = global_preferences->make_preference<float>(this->presence_timeout_number_->get_preference_hash()); this->pref_ = this->presence_timeout_number_->make_entity_preference<float>();
this->set_presence_timeout(); this->set_presence_timeout();
} }
#endif #endif

View File

@@ -44,7 +44,7 @@ void LightState::setup() {
case LIGHT_RESTORE_DEFAULT_ON: case LIGHT_RESTORE_DEFAULT_ON:
case LIGHT_RESTORE_INVERTED_DEFAULT_OFF: case LIGHT_RESTORE_INVERTED_DEFAULT_OFF:
case LIGHT_RESTORE_INVERTED_DEFAULT_ON: case LIGHT_RESTORE_INVERTED_DEFAULT_ON:
this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_preference_hash()); this->rtc_ = this->make_entity_preference<LightStateRTCState>();
// Attempt to load from preferences, else fall back to default values // Attempt to load from preferences, else fall back to default values
if (!this->rtc_.load(&recovered)) { if (!this->rtc_.load(&recovered)) {
recovered.state = (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON || recovered.state = (this->restore_mode_ == LIGHT_RESTORE_DEFAULT_ON ||
@@ -57,7 +57,7 @@ void LightState::setup() {
break; break;
case LIGHT_RESTORE_AND_OFF: case LIGHT_RESTORE_AND_OFF:
case LIGHT_RESTORE_AND_ON: case LIGHT_RESTORE_AND_ON:
this->rtc_ = global_preferences->make_preference<LightStateRTCState>(this->get_preference_hash()); this->rtc_ = this->make_entity_preference<LightStateRTCState>();
this->rtc_.load(&recovered); this->rtc_.load(&recovered);
recovered.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON); recovered.state = (this->restore_mode_ == LIGHT_RESTORE_AND_ON);
break; break;

View File

@@ -21,7 +21,7 @@ class LVGLNumber : public number::Number, public Component {
void setup() override { void setup() override {
float value = this->value_lambda_(); float value = this->value_lambda_();
if (this->restore_) { if (this->restore_) {
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<float>();
if (this->pref_.load(&value)) { if (this->pref_.load(&value)) {
this->control_lambda_(value); this->control_lambda_(value);
} }

View File

@@ -20,7 +20,7 @@ class LVGLSelect : public select::Select, public Component {
this->set_options_(); this->set_options_();
if (this->restore_) { if (this->restore_) {
size_t index; size_t index;
this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<size_t>();
if (this->pref_.load(&index)) if (this->pref_.load(&index))
this->widget_->set_selected_index(index, LV_ANIM_OFF); this->widget_->set_selected_index(index, LV_ANIM_OFF);
} }

View File

@@ -1,5 +1,6 @@
#include "mqtt_alarm_control_panel.h" #include "mqtt_alarm_control_panel.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include "mqtt_const.h" #include "mqtt_const.h"
@@ -18,21 +19,21 @@ void MQTTAlarmControlPanelComponent::setup() {
this->alarm_control_panel_->add_on_state_callback([this]() { this->publish_state(); }); this->alarm_control_panel_->add_on_state_callback([this]() { this->publish_state(); });
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
auto call = this->alarm_control_panel_->make_call(); auto call = this->alarm_control_panel_->make_call();
if (strcasecmp(payload.c_str(), "ARM_AWAY") == 0) { if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_AWAY")) == 0) {
call.arm_away(); call.arm_away();
} else if (strcasecmp(payload.c_str(), "ARM_HOME") == 0) { } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_HOME")) == 0) {
call.arm_home(); call.arm_home();
} else if (strcasecmp(payload.c_str(), "ARM_NIGHT") == 0) { } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_NIGHT")) == 0) {
call.arm_night(); call.arm_night();
} else if (strcasecmp(payload.c_str(), "ARM_VACATION") == 0) { } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_VACATION")) == 0) {
call.arm_vacation(); call.arm_vacation();
} else if (strcasecmp(payload.c_str(), "ARM_CUSTOM_BYPASS") == 0) { } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("ARM_CUSTOM_BYPASS")) == 0) {
call.arm_custom_bypass(); call.arm_custom_bypass();
} else if (strcasecmp(payload.c_str(), "DISARM") == 0) { } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("DISARM")) == 0) {
call.disarm(); call.disarm();
} else if (strcasecmp(payload.c_str(), "PENDING") == 0) { } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("PENDING")) == 0) {
call.pending(); call.pending();
} else if (strcasecmp(payload.c_str(), "TRIGGERED") == 0) { } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("TRIGGERED")) == 0) {
call.triggered(); call.triggered();
} else { } else {
ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name_().c_str(), payload.c_str()); ESP_LOGW(TAG, "'%s': Received unknown command payload %s", this->friendly_name_().c_str(), payload.c_str());

View File

@@ -1,5 +1,6 @@
#include "mqtt_lock.h" #include "mqtt_lock.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include "mqtt_const.h" #include "mqtt_const.h"
@@ -16,11 +17,11 @@ MQTTLockComponent::MQTTLockComponent(lock::Lock *a_lock) : lock_(a_lock) {}
void MQTTLockComponent::setup() { void MQTTLockComponent::setup() {
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) { this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
if (strcasecmp(payload.c_str(), "LOCK") == 0) { if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("LOCK")) == 0) {
this->lock_->lock(); this->lock_->lock();
} else if (strcasecmp(payload.c_str(), "UNLOCK") == 0) { } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("UNLOCK")) == 0) {
this->lock_->unlock(); this->lock_->unlock();
} else if (strcasecmp(payload.c_str(), "OPEN") == 0) { } else if (ESPHOME_strcasecmp_P(payload.c_str(), ESPHOME_PSTR("OPEN")) == 0) {
this->lock_->open(); this->lock_->open();
} else { } else {
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str()); ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name_().c_str(), payload.c_str());

View File

@@ -40,7 +40,7 @@ void NfcTagBinarySensor::set_tag_name(const std::string &str) {
this->match_tag_name_ = true; this->match_tag_name_ = true;
} }
void NfcTagBinarySensor::set_uid(const std::vector<uint8_t> &uid) { this->uid_ = uid; } void NfcTagBinarySensor::set_uid(const NfcTagUid &uid) { this->uid_ = uid; }
bool NfcTagBinarySensor::tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg) { bool NfcTagBinarySensor::tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg) {
for (const auto &record : msg->get_records()) { for (const auto &record : msg->get_records()) {
@@ -63,7 +63,7 @@ bool NfcTagBinarySensor::tag_match_tag_name(const std::shared_ptr<NdefMessage> &
return false; return false;
} }
bool NfcTagBinarySensor::tag_match_uid(const std::vector<uint8_t> &data) { bool NfcTagBinarySensor::tag_match_uid(const NfcTagUid &data) {
if (data.size() != this->uid_.size()) { if (data.size() != this->uid_.size()) {
return false; return false;
} }

View File

@@ -19,11 +19,11 @@ class NfcTagBinarySensor : public binary_sensor::BinarySensor,
void set_ndef_match_string(const std::string &str); void set_ndef_match_string(const std::string &str);
void set_tag_name(const std::string &str); void set_tag_name(const std::string &str);
void set_uid(const std::vector<uint8_t> &uid); void set_uid(const NfcTagUid &uid);
bool tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg); bool tag_match_ndef_string(const std::shared_ptr<NdefMessage> &msg);
bool tag_match_tag_name(const std::shared_ptr<NdefMessage> &msg); bool tag_match_tag_name(const std::shared_ptr<NdefMessage> &msg);
bool tag_match_uid(const std::vector<uint8_t> &data); bool tag_match_uid(const NfcTagUid &data);
void tag_off(NfcTag &tag) override; void tag_off(NfcTag &tag) override;
void tag_on(NfcTag &tag) override; void tag_on(NfcTag &tag) override;
@@ -31,7 +31,7 @@ class NfcTagBinarySensor : public binary_sensor::BinarySensor,
protected: protected:
bool match_tag_name_{false}; bool match_tag_name_{false};
std::string match_string_; std::string match_string_;
std::vector<uint8_t> uid_; NfcTagUid uid_;
}; };
} // namespace nfc } // namespace nfc

View File

@@ -8,19 +8,23 @@ namespace nfc {
static const char *const TAG = "nfc"; static const char *const TAG = "nfc";
char *format_uid_to(char *buffer, const std::vector<uint8_t> &uid) { char *format_uid_to(char *buffer, std::span<const uint8_t> uid) {
return format_hex_pretty_to(buffer, FORMAT_UID_BUFFER_SIZE, uid.data(), uid.size(), '-'); return format_hex_pretty_to(buffer, FORMAT_UID_BUFFER_SIZE, uid.data(), uid.size(), '-');
} }
char *format_bytes_to(char *buffer, const std::vector<uint8_t> &bytes) { char *format_bytes_to(char *buffer, std::span<const uint8_t> bytes) {
return format_hex_pretty_to(buffer, FORMAT_BYTES_BUFFER_SIZE, bytes.data(), bytes.size(), ' '); return format_hex_pretty_to(buffer, FORMAT_BYTES_BUFFER_SIZE, bytes.data(), bytes.size(), ' ');
} }
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations" #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
// Deprecated wrappers intentionally use heap-allocating version for backward compatibility // Deprecated wrappers intentionally use heap-allocating version for backward compatibility
std::string format_uid(const std::vector<uint8_t> &uid) { return format_hex_pretty(uid, '-', false); } // NOLINT std::string format_uid(std::span<const uint8_t> uid) {
std::string format_bytes(const std::vector<uint8_t> &bytes) { return format_hex_pretty(bytes, ' ', false); } // NOLINT return format_hex_pretty(uid.data(), uid.size(), '-', false); // NOLINT
}
std::string format_bytes(std::span<const uint8_t> bytes) {
return format_hex_pretty(bytes.data(), bytes.size(), ' ', false); // NOLINT
}
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
uint8_t guess_tag_type(uint8_t uid_length) { uint8_t guess_tag_type(uint8_t uid_length) {

View File

@@ -6,6 +6,7 @@
#include "ndef_record.h" #include "ndef_record.h"
#include "nfc_tag.h" #include "nfc_tag.h"
#include <span>
#include <vector> #include <vector>
namespace esphome { namespace esphome {
@@ -56,19 +57,19 @@ static const uint8_t MAD_KEY[6] = {0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5};
/// Max UID size is 10 bytes, formatted as "XX-XX-XX-XX-XX-XX-XX-XX-XX-XX\0" = 30 chars /// Max UID size is 10 bytes, formatted as "XX-XX-XX-XX-XX-XX-XX-XX-XX-XX\0" = 30 chars
static constexpr size_t FORMAT_UID_BUFFER_SIZE = 30; static constexpr size_t FORMAT_UID_BUFFER_SIZE = 30;
/// Format UID to buffer with '-' separator (e.g., "04-11-22-33"). Returns buffer for inline use. /// Format UID to buffer with '-' separator (e.g., "04-11-22-33"). Returns buffer for inline use.
char *format_uid_to(char *buffer, const std::vector<uint8_t> &uid); char *format_uid_to(char *buffer, std::span<const uint8_t> uid);
/// Buffer size for format_bytes_to (64 bytes max = 192 chars with space separator) /// Buffer size for format_bytes_to (64 bytes max = 192 chars with space separator)
static constexpr size_t FORMAT_BYTES_BUFFER_SIZE = 192; static constexpr size_t FORMAT_BYTES_BUFFER_SIZE = 192;
/// Format bytes to buffer with ' ' separator (e.g., "04 11 22 33"). Returns buffer for inline use. /// Format bytes to buffer with ' ' separator (e.g., "04 11 22 33"). Returns buffer for inline use.
char *format_bytes_to(char *buffer, const std::vector<uint8_t> &bytes); char *format_bytes_to(char *buffer, std::span<const uint8_t> bytes);
// Remove before 2026.6.0 // Remove before 2026.6.0
ESPDEPRECATED("Use format_uid_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0") ESPDEPRECATED("Use format_uid_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0")
std::string format_uid(const std::vector<uint8_t> &uid); std::string format_uid(std::span<const uint8_t> uid);
// Remove before 2026.6.0 // Remove before 2026.6.0
ESPDEPRECATED("Use format_bytes_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0") ESPDEPRECATED("Use format_bytes_to() with stack buffer instead. Removed in 2026.6.0", "2025.12.0")
std::string format_bytes(const std::vector<uint8_t> &bytes); std::string format_bytes(std::span<const uint8_t> bytes);
uint8_t guess_tag_type(uint8_t uid_length); uint8_t guess_tag_type(uint8_t uid_length);
uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data); uint8_t get_mifare_classic_ndef_start_index(std::vector<uint8_t> &data);

View File

@@ -10,26 +10,27 @@
namespace esphome { namespace esphome {
namespace nfc { namespace nfc {
// NFC UIDs are 4, 7, or 10 bytes depending on tag type
static constexpr size_t NFC_UID_MAX_LENGTH = 10;
using NfcTagUid = StaticVector<uint8_t, NFC_UID_MAX_LENGTH>;
class NfcTag { class NfcTag {
public: public:
NfcTag() { NfcTag() { this->tag_type_ = "Unknown"; };
this->uid_ = {}; NfcTag(const NfcTagUid &uid) {
this->tag_type_ = "Unknown";
};
NfcTag(std::vector<uint8_t> &uid) {
this->uid_ = uid; this->uid_ = uid;
this->tag_type_ = "Unknown"; this->tag_type_ = "Unknown";
}; };
NfcTag(std::vector<uint8_t> &uid, const std::string &tag_type) { NfcTag(const NfcTagUid &uid, const std::string &tag_type) {
this->uid_ = uid; this->uid_ = uid;
this->tag_type_ = tag_type; this->tag_type_ = tag_type;
}; };
NfcTag(std::vector<uint8_t> &uid, const std::string &tag_type, std::unique_ptr<nfc::NdefMessage> ndef_message) { NfcTag(const NfcTagUid &uid, const std::string &tag_type, std::unique_ptr<nfc::NdefMessage> ndef_message) {
this->uid_ = uid; this->uid_ = uid;
this->tag_type_ = tag_type; this->tag_type_ = tag_type;
this->ndef_message_ = std::move(ndef_message); this->ndef_message_ = std::move(ndef_message);
}; };
NfcTag(std::vector<uint8_t> &uid, const std::string &tag_type, std::vector<uint8_t> &ndef_data) { NfcTag(const NfcTagUid &uid, const std::string &tag_type, std::vector<uint8_t> &ndef_data) {
this->uid_ = uid; this->uid_ = uid;
this->tag_type_ = tag_type; this->tag_type_ = tag_type;
this->ndef_message_ = make_unique<NdefMessage>(ndef_data); this->ndef_message_ = make_unique<NdefMessage>(ndef_data);
@@ -41,14 +42,14 @@ class NfcTag {
ndef_message_ = make_unique<NdefMessage>(*rhs.ndef_message_); ndef_message_ = make_unique<NdefMessage>(*rhs.ndef_message_);
} }
std::vector<uint8_t> &get_uid() { return this->uid_; }; NfcTagUid &get_uid() { return this->uid_; };
const std::string &get_tag_type() { return this->tag_type_; }; const std::string &get_tag_type() { return this->tag_type_; };
bool has_ndef_message() { return this->ndef_message_ != nullptr; }; bool has_ndef_message() { return this->ndef_message_ != nullptr; };
const std::shared_ptr<NdefMessage> &get_ndef_message() { return this->ndef_message_; }; const std::shared_ptr<NdefMessage> &get_ndef_message() { return this->ndef_message_; };
void set_ndef_message(std::unique_ptr<NdefMessage> ndef_message) { this->ndef_message_ = std::move(ndef_message); }; void set_ndef_message(std::unique_ptr<NdefMessage> ndef_message) { this->ndef_message_ = std::move(ndef_message); };
protected: protected:
std::vector<uint8_t> uid_; NfcTagUid uid_;
std::string tag_type_; std::string tag_type_;
std::shared_ptr<NdefMessage> ndef_message_; std::shared_ptr<NdefMessage> ndef_message_;
}; };

View File

@@ -14,8 +14,7 @@ void ValueRangeTrigger::setup() {
float local_min = this->min_.value(0.0); float local_min = this->min_.value(0.0);
float local_max = this->max_.value(0.0); float local_max = this->max_.value(0.0);
convert hash = {.from = (local_max - local_min)}; convert hash = {.from = (local_max - local_min)};
uint32_t myhash = hash.to ^ this->parent_->get_preference_hash(); this->rtc_ = this->parent_->make_entity_preference<bool>(hash.to);
this->rtc_ = global_preferences->make_preference<bool>(myhash);
bool initial_state; bool initial_state;
if (this->rtc_.load(&initial_state)) { if (this->rtc_.load(&initial_state)) {
this->previous_in_range_ = initial_state; this->previous_in_range_ = initial_state;

View File

@@ -17,7 +17,7 @@ void OpenthermNumber::setup() {
if (!this->restore_value_) { if (!this->restore_value_) {
value = this->initial_value_; value = this->initial_value_;
} else { } else {
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<float>();
if (!this->pref_.load(&value)) { if (!this->pref_.load(&value)) {
if (!std::isnan(this->initial_value_)) { if (!std::isnan(this->initial_value_)) {
value = this->initial_value_; value = this->initial_value_;

View File

@@ -168,11 +168,11 @@ void PN532::loop() {
} }
uint8_t nfcid_length = read[5]; uint8_t nfcid_length = read[5];
std::vector<uint8_t> nfcid(read.begin() + 6, read.begin() + 6 + nfcid_length); if (nfcid_length > nfc::NFC_UID_MAX_LENGTH || read.size() < 6U + nfcid_length) {
if (read.size() < 6U + nfcid_length) {
// oops, pn532 returned invalid data // oops, pn532 returned invalid data
return; return;
} }
nfc::NfcTagUid nfcid(read.begin() + 6, read.begin() + 6 + nfcid_length);
bool report = true; bool report = true;
for (auto *bin_sens : this->binary_sensors_) { for (auto *bin_sens : this->binary_sensors_) {
@@ -358,7 +358,7 @@ void PN532::turn_off_rf_() {
}); });
} }
std::unique_ptr<nfc::NfcTag> PN532::read_tag_(std::vector<uint8_t> &uid) { std::unique_ptr<nfc::NfcTag> PN532::read_tag_(nfc::NfcTagUid &uid) {
uint8_t type = nfc::guess_tag_type(uid.size()); uint8_t type = nfc::guess_tag_type(uid.size());
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
@@ -393,7 +393,7 @@ void PN532::write_mode(nfc::NdefMessage *message) {
ESP_LOGD(TAG, "Waiting to write next tag"); ESP_LOGD(TAG, "Waiting to write next tag");
} }
bool PN532::clean_tag_(std::vector<uint8_t> &uid) { bool PN532::clean_tag_(nfc::NfcTagUid &uid) {
uint8_t type = nfc::guess_tag_type(uid.size()); uint8_t type = nfc::guess_tag_type(uid.size());
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
return this->format_mifare_classic_mifare_(uid); return this->format_mifare_classic_mifare_(uid);
@@ -404,7 +404,7 @@ bool PN532::clean_tag_(std::vector<uint8_t> &uid) {
return false; return false;
} }
bool PN532::format_tag_(std::vector<uint8_t> &uid) { bool PN532::format_tag_(nfc::NfcTagUid &uid) {
uint8_t type = nfc::guess_tag_type(uid.size()); uint8_t type = nfc::guess_tag_type(uid.size());
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
return this->format_mifare_classic_ndef_(uid); return this->format_mifare_classic_ndef_(uid);
@@ -415,7 +415,7 @@ bool PN532::format_tag_(std::vector<uint8_t> &uid) {
return false; return false;
} }
bool PN532::write_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message) { bool PN532::write_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message) {
uint8_t type = nfc::guess_tag_type(uid.size()); uint8_t type = nfc::guess_tag_type(uid.size());
if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) { if (type == nfc::TAG_TYPE_MIFARE_CLASSIC) {
return this->write_mifare_classic_tag_(uid, message); return this->write_mifare_classic_tag_(uid, message);
@@ -448,7 +448,7 @@ void PN532::dump_config() {
} }
} }
bool PN532BinarySensor::process(std::vector<uint8_t> &data) { bool PN532BinarySensor::process(const nfc::NfcTagUid &data) {
if (data.size() != this->uid_.size()) if (data.size() != this->uid_.size())
return false; return false;

View File

@@ -69,28 +69,28 @@ class PN532 : public PollingComponent {
virtual bool read_data(std::vector<uint8_t> &data, uint8_t len) = 0; virtual bool read_data(std::vector<uint8_t> &data, uint8_t len) = 0;
virtual bool read_response(uint8_t command, std::vector<uint8_t> &data) = 0; virtual bool read_response(uint8_t command, std::vector<uint8_t> &data) = 0;
std::unique_ptr<nfc::NfcTag> read_tag_(std::vector<uint8_t> &uid); std::unique_ptr<nfc::NfcTag> read_tag_(nfc::NfcTagUid &uid);
bool format_tag_(std::vector<uint8_t> &uid); bool format_tag_(nfc::NfcTagUid &uid);
bool clean_tag_(std::vector<uint8_t> &uid); bool clean_tag_(nfc::NfcTagUid &uid);
bool write_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message); bool write_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
std::unique_ptr<nfc::NfcTag> read_mifare_classic_tag_(std::vector<uint8_t> &uid); std::unique_ptr<nfc::NfcTag> read_mifare_classic_tag_(nfc::NfcTagUid &uid);
bool read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data); bool read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
bool write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data); bool write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &data);
bool auth_mifare_classic_block_(std::vector<uint8_t> &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key); 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_(std::vector<uint8_t> &uid); bool format_mifare_classic_mifare_(nfc::NfcTagUid &uid);
bool format_mifare_classic_ndef_(std::vector<uint8_t> &uid); bool format_mifare_classic_ndef_(nfc::NfcTagUid &uid);
bool write_mifare_classic_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message); bool write_mifare_classic_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
std::unique_ptr<nfc::NfcTag> read_mifare_ultralight_tag_(std::vector<uint8_t> &uid); std::unique_ptr<nfc::NfcTag> read_mifare_ultralight_tag_(nfc::NfcTagUid &uid);
bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data); bool read_mifare_ultralight_bytes_(uint8_t start_page, uint16_t num_bytes, std::vector<uint8_t> &data);
bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6); bool is_mifare_ultralight_formatted_(const std::vector<uint8_t> &page_3_to_6);
uint16_t read_mifare_ultralight_capacity_(); uint16_t read_mifare_ultralight_capacity_();
bool find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length, bool find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
uint8_t &message_start_index); uint8_t &message_start_index);
bool write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data); bool write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message); bool write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message);
bool clean_mifare_ultralight_(); bool clean_mifare_ultralight_();
bool updates_enabled_{true}; bool updates_enabled_{true};
@@ -98,7 +98,7 @@ class PN532 : public PollingComponent {
std::vector<PN532BinarySensor *> binary_sensors_; std::vector<PN532BinarySensor *> binary_sensors_;
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_; std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_; std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
std::vector<uint8_t> current_uid_; nfc::NfcTagUid current_uid_;
nfc::NdefMessage *next_task_message_to_write_; nfc::NdefMessage *next_task_message_to_write_;
uint32_t rd_start_time_{0}; uint32_t rd_start_time_{0};
enum PN532ReadReady rd_ready_ { WOULDBLOCK }; enum PN532ReadReady rd_ready_ { WOULDBLOCK };
@@ -118,9 +118,9 @@ class PN532 : public PollingComponent {
class PN532BinarySensor : public binary_sensor::BinarySensor { class PN532BinarySensor : public binary_sensor::BinarySensor {
public: public:
void set_uid(const std::vector<uint8_t> &uid) { uid_ = uid; } void set_uid(const nfc::NfcTagUid &uid) { uid_ = uid; }
bool process(std::vector<uint8_t> &data); bool process(const nfc::NfcTagUid &data);
void on_scan_end() { void on_scan_end() {
if (!this->found_) { if (!this->found_) {
@@ -130,7 +130,7 @@ class PN532BinarySensor : public binary_sensor::BinarySensor {
} }
protected: protected:
std::vector<uint8_t> uid_; nfc::NfcTagUid uid_;
bool found_{false}; bool found_{false};
}; };

View File

@@ -8,7 +8,7 @@ namespace pn532 {
static const char *const TAG = "pn532.mifare_classic"; static const char *const TAG = "pn532.mifare_classic";
std::unique_ptr<nfc::NfcTag> PN532::read_mifare_classic_tag_(std::vector<uint8_t> &uid) { std::unique_ptr<nfc::NfcTag> PN532::read_mifare_classic_tag_(nfc::NfcTagUid &uid) {
uint8_t current_block = 4; uint8_t current_block = 4;
uint8_t message_start_index = 0; uint8_t message_start_index = 0;
uint32_t message_length = 0; uint32_t message_length = 0;
@@ -82,8 +82,7 @@ bool PN532::read_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t> &
return true; return true;
} }
bool PN532::auth_mifare_classic_block_(std::vector<uint8_t> &uid, uint8_t block_num, uint8_t key_num, bool PN532::auth_mifare_classic_block_(nfc::NfcTagUid &uid, uint8_t block_num, uint8_t key_num, const uint8_t *key) {
const uint8_t *key) {
std::vector<uint8_t> data({ std::vector<uint8_t> data({
PN532_COMMAND_INDATAEXCHANGE, PN532_COMMAND_INDATAEXCHANGE,
0x01, // One card 0x01, // One card
@@ -106,7 +105,7 @@ bool PN532::auth_mifare_classic_block_(std::vector<uint8_t> &uid, uint8_t block_
return true; return true;
} }
bool PN532::format_mifare_classic_mifare_(std::vector<uint8_t> &uid) { bool PN532::format_mifare_classic_mifare_(nfc::NfcTagUid &uid) {
std::vector<uint8_t> blank_buffer( std::vector<uint8_t> blank_buffer(
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
std::vector<uint8_t> trailer_buffer( std::vector<uint8_t> trailer_buffer(
@@ -141,7 +140,7 @@ bool PN532::format_mifare_classic_mifare_(std::vector<uint8_t> &uid) {
return !error; return !error;
} }
bool PN532::format_mifare_classic_ndef_(std::vector<uint8_t> &uid) { bool PN532::format_mifare_classic_ndef_(nfc::NfcTagUid &uid) {
std::vector<uint8_t> empty_ndef_message( std::vector<uint8_t> empty_ndef_message(
{0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}); {0x03, 0x03, 0xD0, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
std::vector<uint8_t> blank_block( std::vector<uint8_t> blank_block(
@@ -216,7 +215,7 @@ bool PN532::write_mifare_classic_block_(uint8_t block_num, std::vector<uint8_t>
return true; return true;
} }
bool PN532::write_mifare_classic_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message) { bool PN532::write_mifare_classic_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message) {
auto encoded = message->encode(); auto encoded = message->encode();
uint32_t message_length = encoded.size(); uint32_t message_length = encoded.size();

View File

@@ -8,7 +8,7 @@ namespace pn532 {
static const char *const TAG = "pn532.mifare_ultralight"; static const char *const TAG = "pn532.mifare_ultralight";
std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(std::vector<uint8_t> &uid) { std::unique_ptr<nfc::NfcTag> PN532::read_mifare_ultralight_tag_(nfc::NfcTagUid &uid) {
std::vector<uint8_t> data; std::vector<uint8_t> data;
// pages 3 to 6 contain various info we are interested in -- do one read to grab it all // pages 3 to 6 contain various info we are interested in -- do one read to grab it all
if (!this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE, if (!this->read_mifare_ultralight_bytes_(3, nfc::MIFARE_ULTRALIGHT_PAGE_SIZE * nfc::MIFARE_ULTRALIGHT_READ_SIZE,
@@ -114,7 +114,7 @@ bool PN532::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6
return false; return false;
} }
bool PN532::write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message) { bool PN532::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, nfc::NdefMessage *message) {
uint32_t capacity = this->read_mifare_ultralight_capacity_(); uint32_t capacity = this->read_mifare_ultralight_capacity_();
auto encoded = message->encode(); auto encoded = message->encode();

View File

@@ -478,7 +478,7 @@ uint8_t PN7150::read_endpoint_data_(nfc::NfcTag &tag) {
return nfc::STATUS_FAILED; return nfc::STATUS_FAILED;
} }
uint8_t PN7150::clean_endpoint_(std::vector<uint8_t> &uid) { uint8_t PN7150::clean_endpoint_(nfc::NfcTagUid &uid) {
uint8_t type = nfc::guess_tag_type(uid.size()); uint8_t type = nfc::guess_tag_type(uid.size());
switch (type) { switch (type) {
case nfc::TAG_TYPE_MIFARE_CLASSIC: case nfc::TAG_TYPE_MIFARE_CLASSIC:
@@ -494,7 +494,7 @@ uint8_t PN7150::clean_endpoint_(std::vector<uint8_t> &uid) {
return nfc::STATUS_FAILED; return nfc::STATUS_FAILED;
} }
uint8_t PN7150::format_endpoint_(std::vector<uint8_t> &uid) { uint8_t PN7150::format_endpoint_(nfc::NfcTagUid &uid) {
uint8_t type = nfc::guess_tag_type(uid.size()); uint8_t type = nfc::guess_tag_type(uid.size());
switch (type) { switch (type) {
case nfc::TAG_TYPE_MIFARE_CLASSIC: case nfc::TAG_TYPE_MIFARE_CLASSIC:
@@ -510,7 +510,7 @@ uint8_t PN7150::format_endpoint_(std::vector<uint8_t> &uid) {
return nfc::STATUS_FAILED; return nfc::STATUS_FAILED;
} }
uint8_t PN7150::write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message) { uint8_t PN7150::write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr<nfc::NdefMessage> &message) {
uint8_t type = nfc::guess_tag_type(uid.size()); uint8_t type = nfc::guess_tag_type(uid.size());
switch (type) { switch (type) {
case nfc::TAG_TYPE_MIFARE_CLASSIC: case nfc::TAG_TYPE_MIFARE_CLASSIC:
@@ -534,7 +534,7 @@ std::unique_ptr<nfc::NfcTag> PN7150::build_tag_(const uint8_t mode_tech, const s
ESP_LOGE(TAG, "UID length cannot be zero"); ESP_LOGE(TAG, "UID length cannot be zero");
return nullptr; return nullptr;
} }
std::vector<uint8_t> uid(data.begin() + 3, data.begin() + 3 + uid_length); nfc::NfcTagUid uid(data.begin() + 3, data.begin() + 3 + uid_length);
const auto *tag_type_str = const auto *tag_type_str =
nfc::guess_tag_type(uid_length) == nfc::TAG_TYPE_MIFARE_CLASSIC ? nfc::MIFARE_CLASSIC : nfc::NFC_FORUM_TYPE_2; nfc::guess_tag_type(uid_length) == nfc::TAG_TYPE_MIFARE_CLASSIC ? nfc::MIFARE_CLASSIC : nfc::NFC_FORUM_TYPE_2;
return make_unique<nfc::NfcTag>(uid, tag_type_str); return make_unique<nfc::NfcTag>(uid, tag_type_str);
@@ -543,7 +543,7 @@ std::unique_ptr<nfc::NfcTag> PN7150::build_tag_(const uint8_t mode_tech, const s
return nullptr; return nullptr;
} }
optional<size_t> PN7150::find_tag_uid_(const std::vector<uint8_t> &uid) { optional<size_t> PN7150::find_tag_uid_(const nfc::NfcTagUid &uid) {
if (!this->discovered_endpoint_.empty()) { if (!this->discovered_endpoint_.empty()) {
for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) { for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) {
auto existing_tag_uid = this->discovered_endpoint_[i].tag->get_uid(); auto existing_tag_uid = this->discovered_endpoint_[i].tag->get_uid();

View File

@@ -203,12 +203,12 @@ class PN7150 : public nfc::Nfcc, public Component {
void select_endpoint_(); void select_endpoint_();
uint8_t read_endpoint_data_(nfc::NfcTag &tag); uint8_t read_endpoint_data_(nfc::NfcTag &tag);
uint8_t clean_endpoint_(std::vector<uint8_t> &uid); uint8_t clean_endpoint_(nfc::NfcTagUid &uid);
uint8_t format_endpoint_(std::vector<uint8_t> &uid); uint8_t format_endpoint_(nfc::NfcTagUid &uid);
uint8_t write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message); uint8_t write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr<nfc::NdefMessage> &message);
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data); std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
optional<size_t> find_tag_uid_(const std::vector<uint8_t> &uid); optional<size_t> find_tag_uid_(const nfc::NfcTagUid &uid);
void purge_old_tags_(); void purge_old_tags_();
void erase_tag_(uint8_t tag_index); void erase_tag_(uint8_t tag_index);
@@ -251,7 +251,7 @@ class PN7150 : public nfc::Nfcc, public Component {
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length, uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
uint8_t &message_start_index); uint8_t &message_start_index);
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data); uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
uint8_t write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, const std::shared_ptr<nfc::NdefMessage> &message); uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
uint8_t clean_mifare_ultralight_(); uint8_t clean_mifare_ultralight_();
enum NfcTask : uint8_t { enum NfcTask : uint8_t {

View File

@@ -115,8 +115,7 @@ uint8_t PN7150::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_
return nfc::STATUS_FAILED; return nfc::STATUS_FAILED;
} }
uint8_t PN7150::write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, uint8_t PN7150::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message) {
const std::shared_ptr<nfc::NdefMessage> &message) {
uint32_t capacity = this->read_mifare_ultralight_capacity_(); uint32_t capacity = this->read_mifare_ultralight_capacity_();
auto encoded = message->encode(); auto encoded = message->encode();

View File

@@ -506,7 +506,7 @@ uint8_t PN7160::read_endpoint_data_(nfc::NfcTag &tag) {
return nfc::STATUS_FAILED; return nfc::STATUS_FAILED;
} }
uint8_t PN7160::clean_endpoint_(std::vector<uint8_t> &uid) { uint8_t PN7160::clean_endpoint_(nfc::NfcTagUid &uid) {
uint8_t type = nfc::guess_tag_type(uid.size()); uint8_t type = nfc::guess_tag_type(uid.size());
switch (type) { switch (type) {
case nfc::TAG_TYPE_MIFARE_CLASSIC: case nfc::TAG_TYPE_MIFARE_CLASSIC:
@@ -522,7 +522,7 @@ uint8_t PN7160::clean_endpoint_(std::vector<uint8_t> &uid) {
return nfc::STATUS_FAILED; return nfc::STATUS_FAILED;
} }
uint8_t PN7160::format_endpoint_(std::vector<uint8_t> &uid) { uint8_t PN7160::format_endpoint_(nfc::NfcTagUid &uid) {
uint8_t type = nfc::guess_tag_type(uid.size()); uint8_t type = nfc::guess_tag_type(uid.size());
switch (type) { switch (type) {
case nfc::TAG_TYPE_MIFARE_CLASSIC: case nfc::TAG_TYPE_MIFARE_CLASSIC:
@@ -538,7 +538,7 @@ uint8_t PN7160::format_endpoint_(std::vector<uint8_t> &uid) {
return nfc::STATUS_FAILED; return nfc::STATUS_FAILED;
} }
uint8_t PN7160::write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message) { uint8_t PN7160::write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr<nfc::NdefMessage> &message) {
uint8_t type = nfc::guess_tag_type(uid.size()); uint8_t type = nfc::guess_tag_type(uid.size());
switch (type) { switch (type) {
case nfc::TAG_TYPE_MIFARE_CLASSIC: case nfc::TAG_TYPE_MIFARE_CLASSIC:
@@ -562,7 +562,7 @@ std::unique_ptr<nfc::NfcTag> PN7160::build_tag_(const uint8_t mode_tech, const s
ESP_LOGE(TAG, "UID length cannot be zero"); ESP_LOGE(TAG, "UID length cannot be zero");
return nullptr; return nullptr;
} }
std::vector<uint8_t> uid(data.begin() + 3, data.begin() + 3 + uid_length); nfc::NfcTagUid uid(data.begin() + 3, data.begin() + 3 + uid_length);
const auto *tag_type_str = const auto *tag_type_str =
nfc::guess_tag_type(uid_length) == nfc::TAG_TYPE_MIFARE_CLASSIC ? nfc::MIFARE_CLASSIC : nfc::NFC_FORUM_TYPE_2; nfc::guess_tag_type(uid_length) == nfc::TAG_TYPE_MIFARE_CLASSIC ? nfc::MIFARE_CLASSIC : nfc::NFC_FORUM_TYPE_2;
return make_unique<nfc::NfcTag>(uid, tag_type_str); return make_unique<nfc::NfcTag>(uid, tag_type_str);
@@ -571,7 +571,7 @@ std::unique_ptr<nfc::NfcTag> PN7160::build_tag_(const uint8_t mode_tech, const s
return nullptr; return nullptr;
} }
optional<size_t> PN7160::find_tag_uid_(const std::vector<uint8_t> &uid) { optional<size_t> PN7160::find_tag_uid_(const nfc::NfcTagUid &uid) {
if (!this->discovered_endpoint_.empty()) { if (!this->discovered_endpoint_.empty()) {
for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) { for (size_t i = 0; i < this->discovered_endpoint_.size(); i++) {
auto existing_tag_uid = this->discovered_endpoint_[i].tag->get_uid(); auto existing_tag_uid = this->discovered_endpoint_[i].tag->get_uid();

View File

@@ -220,12 +220,12 @@ class PN7160 : public nfc::Nfcc, public Component {
void select_endpoint_(); void select_endpoint_();
uint8_t read_endpoint_data_(nfc::NfcTag &tag); uint8_t read_endpoint_data_(nfc::NfcTag &tag);
uint8_t clean_endpoint_(std::vector<uint8_t> &uid); uint8_t clean_endpoint_(nfc::NfcTagUid &uid);
uint8_t format_endpoint_(std::vector<uint8_t> &uid); uint8_t format_endpoint_(nfc::NfcTagUid &uid);
uint8_t write_endpoint_(std::vector<uint8_t> &uid, std::shared_ptr<nfc::NdefMessage> &message); uint8_t write_endpoint_(nfc::NfcTagUid &uid, std::shared_ptr<nfc::NdefMessage> &message);
std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data); std::unique_ptr<nfc::NfcTag> build_tag_(uint8_t mode_tech, const std::vector<uint8_t> &data);
optional<size_t> find_tag_uid_(const std::vector<uint8_t> &uid); optional<size_t> find_tag_uid_(const nfc::NfcTagUid &uid);
void purge_old_tags_(); void purge_old_tags_();
void erase_tag_(uint8_t tag_index); void erase_tag_(uint8_t tag_index);
@@ -268,7 +268,7 @@ class PN7160 : public nfc::Nfcc, public Component {
uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length, uint8_t find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_to_6, uint8_t &message_length,
uint8_t &message_start_index); uint8_t &message_start_index);
uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data); uint8_t write_mifare_ultralight_page_(uint8_t page_num, std::vector<uint8_t> &write_data);
uint8_t write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, const std::shared_ptr<nfc::NdefMessage> &message); uint8_t write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message);
uint8_t clean_mifare_ultralight_(); uint8_t clean_mifare_ultralight_();
enum NfcTask : uint8_t { enum NfcTask : uint8_t {

View File

@@ -115,8 +115,7 @@ uint8_t PN7160::find_mifare_ultralight_ndef_(const std::vector<uint8_t> &page_3_
return nfc::STATUS_FAILED; return nfc::STATUS_FAILED;
} }
uint8_t PN7160::write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, uint8_t PN7160::write_mifare_ultralight_tag_(nfc::NfcTagUid &uid, const std::shared_ptr<nfc::NdefMessage> &message) {
const std::shared_ptr<nfc::NdefMessage> &message) {
uint32_t capacity = this->read_mifare_ultralight_capacity_(); uint32_t capacity = this->read_mifare_ultralight_capacity_();
auto encoded = message->encode(); auto encoded = message->encode();

View File

@@ -132,7 +132,7 @@ void RotaryEncoderSensor::setup() {
int32_t initial_value = 0; int32_t initial_value = 0;
switch (this->restore_mode_) { switch (this->restore_mode_) {
case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO: case ROTARY_ENCODER_RESTORE_DEFAULT_ZERO:
this->rtc_ = global_preferences->make_preference<int32_t>(this->get_preference_hash()); this->rtc_ = this->make_entity_preference<int32_t>();
if (!this->rtc_.load(&initial_value)) { if (!this->rtc_.load(&initial_value)) {
initial_value = 0; initial_value = 0;
} }

View File

@@ -8,7 +8,6 @@
#include "preferences.h" #include "preferences.h"
#include <cstring> #include <cstring>
#include <vector>
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
@@ -25,6 +24,9 @@ static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-no
static const uint32_t RP2040_FLASH_STORAGE_SIZE = 512; static const uint32_t RP2040_FLASH_STORAGE_SIZE = 512;
// Stack buffer size for preferences - covers virtually all real-world preferences without heap allocation
static constexpr size_t PREF_BUFFER_SIZE = 64;
extern "C" uint8_t _EEPROM_start; extern "C" uint8_t _EEPROM_start;
template<class It> uint8_t calculate_crc(It first, It last, uint32_t type) { template<class It> uint8_t calculate_crc(It first, It last, uint32_t type) {
@@ -42,12 +44,14 @@ class RP2040PreferenceBackend : public ESPPreferenceBackend {
uint32_t type = 0; uint32_t type = 0;
bool save(const uint8_t *data, size_t len) override { bool save(const uint8_t *data, size_t len) override {
std::vector<uint8_t> buffer; const size_t buffer_size = len + 1;
buffer.resize(len + 1); SmallBufferWithHeapFallback<PREF_BUFFER_SIZE> buffer_alloc(buffer_size);
memcpy(buffer.data(), data, len); uint8_t *buffer = buffer_alloc.get();
buffer[buffer.size() - 1] = calculate_crc(buffer.begin(), buffer.end() - 1, type);
for (uint32_t i = 0; i < len + 1; i++) { memcpy(buffer, data, len);
buffer[len] = calculate_crc(buffer, buffer + len, type);
for (size_t i = 0; i < buffer_size; i++) {
uint32_t j = offset + i; uint32_t j = offset + i;
if (j >= RP2040_FLASH_STORAGE_SIZE) if (j >= RP2040_FLASH_STORAGE_SIZE)
return false; return false;
@@ -60,22 +64,23 @@ class RP2040PreferenceBackend : public ESPPreferenceBackend {
return true; return true;
} }
bool load(uint8_t *data, size_t len) override { bool load(uint8_t *data, size_t len) override {
std::vector<uint8_t> buffer; const size_t buffer_size = len + 1;
buffer.resize(len + 1); SmallBufferWithHeapFallback<PREF_BUFFER_SIZE> buffer_alloc(buffer_size);
uint8_t *buffer = buffer_alloc.get();
for (size_t i = 0; i < len + 1; i++) { for (size_t i = 0; i < buffer_size; i++) {
uint32_t j = offset + i; uint32_t j = offset + i;
if (j >= RP2040_FLASH_STORAGE_SIZE) if (j >= RP2040_FLASH_STORAGE_SIZE)
return false; return false;
buffer[i] = s_flash_storage[j]; buffer[i] = s_flash_storage[j];
} }
uint8_t crc = calculate_crc(buffer.begin(), buffer.end() - 1, type); uint8_t crc = calculate_crc(buffer, buffer + len, type);
if (buffer[buffer.size() - 1] != crc) { if (buffer[len] != crc) {
return false; return false;
} }
memcpy(data, buffer.data(), len); memcpy(data, buffer, len);
return true; return true;
} }
}; };

View File

@@ -39,7 +39,7 @@ class ValueRangeTrigger : public Trigger<float>, public Component {
template<typename V> void set_max(V max) { this->max_ = max; } template<typename V> void set_max(V max) { this->max_ = max; }
void setup() override { void setup() override {
this->rtc_ = global_preferences->make_preference<bool>(this->parent_->get_preference_hash()); this->rtc_ = this->parent_->make_entity_preference<bool>();
bool initial_state; bool initial_state;
if (this->rtc_.load(&initial_state)) { if (this->rtc_.load(&initial_state)) {
this->previous_in_range_ = initial_state; this->previous_in_range_ = initial_state;

View File

@@ -29,6 +29,14 @@ void socket_delay(uint32_t ms) {
// Use esp_delay with a callback that checks if socket data arrived. // Use esp_delay with a callback that checks if socket data arrived.
// This allows the delay to exit early when socket_wake() is called by // This allows the delay to exit early when socket_wake() is called by
// lwip recv_fn/accept_fn callbacks, reducing socket latency. // lwip recv_fn/accept_fn callbacks, reducing socket latency.
//
// When ms is 0, we must use delay(0) because esp_delay(0, callback)
// exits immediately without yielding, which can cause watchdog timeouts
// when the main loop runs in high-frequency mode (e.g., during light effects).
if (ms == 0) {
delay(0);
return;
}
s_socket_woke = false; s_socket_woke = false;
esp_delay(ms, []() { return !s_socket_woke; }); esp_delay(ms, []() { return !s_socket_woke; });
} }

View File

@@ -55,7 +55,7 @@ void SpeakerMediaPlayer::setup() {
this->media_control_command_queue_ = xQueueCreate(MEDIA_CONTROLS_QUEUE_LENGTH, sizeof(MediaCallCommand)); this->media_control_command_queue_ = xQueueCreate(MEDIA_CONTROLS_QUEUE_LENGTH, sizeof(MediaCallCommand));
this->pref_ = global_preferences->make_preference<VolumeRestoreState>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<VolumeRestoreState>();
VolumeRestoreState volume_restore_state; VolumeRestoreState volume_restore_state;
if (this->pref_.load(&volume_restore_state)) { if (this->pref_.load(&volume_restore_state)) {

View File

@@ -16,7 +16,7 @@ void SprinklerControllerNumber::setup() {
if (!this->restore_value_) { if (!this->restore_value_) {
value = this->initial_value_; value = this->initial_value_;
} else { } else {
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<float>();
if (!this->pref_.load(&value)) { if (!this->pref_.load(&value)) {
if (!std::isnan(this->initial_value_)) { if (!std::isnan(this->initial_value_)) {
value = this->initial_value_; value = this->initial_value_;

View File

@@ -34,7 +34,7 @@ optional<bool> Switch::get_initial_state() {
if (!(restore_mode & RESTORE_MODE_PERSISTENT_MASK)) if (!(restore_mode & RESTORE_MODE_PERSISTENT_MASK))
return {}; return {};
this->rtc_ = global_preferences->make_preference<bool>(this->get_preference_hash()); this->rtc_ = this->make_entity_preference<bool>();
bool initial_state; bool initial_state;
if (!this->rtc_.load(&initial_state)) if (!this->rtc_.load(&initial_state))
return {}; return {};

View File

@@ -82,7 +82,7 @@ void TemplateAlarmControlPanel::setup() {
this->current_state_ = ACP_STATE_DISARMED; this->current_state_ = ACP_STATE_DISARMED;
if (this->restore_mode_ == ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED) { if (this->restore_mode_ == ALARM_CONTROL_PANEL_RESTORE_DEFAULT_DISARMED) {
uint8_t value; uint8_t value;
this->pref_ = global_preferences->make_preference<uint8_t>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<uint8_t>();
if (this->pref_.load(&value)) { if (this->pref_.load(&value)) {
this->current_state_ = static_cast<alarm_control_panel::AlarmControlPanelState>(value); this->current_state_ = static_cast<alarm_control_panel::AlarmControlPanelState>(value);
} }

View File

@@ -18,8 +18,7 @@ void TemplateDate::setup() {
state = this->initial_value_; state = this->initial_value_;
} else { } else {
datetime::DateEntityRestoreState temp; datetime::DateEntityRestoreState temp;
this->pref_ = this->pref_ = this->make_entity_preference<datetime::DateEntityRestoreState>(194434030U);
global_preferences->make_preference<datetime::DateEntityRestoreState>(194434030U ^ this->get_preference_hash());
if (this->pref_.load(&temp)) { if (this->pref_.load(&temp)) {
temp.apply(this); temp.apply(this);
return; return;

View File

@@ -18,8 +18,7 @@ void TemplateDateTime::setup() {
state = this->initial_value_; state = this->initial_value_;
} else { } else {
datetime::DateTimeEntityRestoreState temp; datetime::DateTimeEntityRestoreState temp;
this->pref_ = global_preferences->make_preference<datetime::DateTimeEntityRestoreState>( this->pref_ = this->make_entity_preference<datetime::DateTimeEntityRestoreState>(194434090U);
194434090U ^ this->get_preference_hash());
if (this->pref_.load(&temp)) { if (this->pref_.load(&temp)) {
temp.apply(this); temp.apply(this);
return; return;

View File

@@ -18,8 +18,7 @@ void TemplateTime::setup() {
state = this->initial_value_; state = this->initial_value_;
} else { } else {
datetime::TimeEntityRestoreState temp; datetime::TimeEntityRestoreState temp;
this->pref_ = this->pref_ = this->make_entity_preference<datetime::TimeEntityRestoreState>(194434060U);
global_preferences->make_preference<datetime::TimeEntityRestoreState>(194434060U ^ this->get_preference_hash());
if (this->pref_.load(&temp)) { if (this->pref_.load(&temp)) {
temp.apply(this); temp.apply(this);
return; return;

View File

@@ -13,7 +13,7 @@ void TemplateNumber::setup() {
if (!this->restore_value_) { if (!this->restore_value_) {
value = this->initial_value_; value = this->initial_value_;
} else { } else {
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<float>();
if (!this->pref_.load(&value)) { if (!this->pref_.load(&value)) {
if (!std::isnan(this->initial_value_)) { if (!std::isnan(this->initial_value_)) {
value = this->initial_value_; value = this->initial_value_;

View File

@@ -11,7 +11,7 @@ void TemplateSelect::setup() {
size_t index = this->initial_option_index_; size_t index = this->initial_option_index_;
if (this->restore_value_) { if (this->restore_value_) {
this->pref_ = global_preferences->make_preference<size_t>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<size_t>();
size_t restored_index; size_t restored_index;
if (this->pref_.load(&restored_index) && this->has_index(restored_index)) { if (this->pref_.load(&restored_index) && this->has_index(restored_index)) {
index = restored_index; index = restored_index;

View File

@@ -20,7 +20,14 @@ void TemplateText::setup() {
// Need std::string for pref_->setup() to fill from flash // Need std::string for pref_->setup() to fill from flash
std::string value{this->initial_value_ != nullptr ? this->initial_value_ : ""}; std::string value{this->initial_value_ != nullptr ? this->initial_value_ : ""};
// For future hash migration: use migrate_entity_preference_() with:
// old_key = get_preference_hash() + extra
// new_key = get_preference_hash_v2() + extra
// See: https://github.com/esphome/backlog/issues/85
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
uint32_t key = this->get_preference_hash(); uint32_t key = this->get_preference_hash();
#pragma GCC diagnostic pop
key += this->traits.get_min_length() << 2; key += this->traits.get_min_length() << 2;
key += this->traits.get_max_length() << 4; key += this->traits.get_max_length() << 4;
key += fnv1_hash(this->traits.get_pattern_c_str()) << 6; key += fnv1_hash(this->traits.get_pattern_c_str()) << 6;

View File

@@ -10,7 +10,7 @@ void TotalDailyEnergy::setup() {
float initial_value = 0; float initial_value = 0;
if (this->restore_) { if (this->restore_) {
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<float>();
this->pref_.load(&initial_value); this->pref_.load(&initial_value);
} }
this->publish_state_and_save(initial_value); this->publish_state_and_save(initial_value);

View File

@@ -8,7 +8,7 @@ static const char *const TAG = "tuya.number";
void TuyaNumber::setup() { void TuyaNumber::setup() {
if (this->restore_value_) { if (this->restore_value_) {
this->pref_ = global_preferences->make_preference<float>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<float>();
} }
this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) { this->parent_->register_listener(this->number_id_, [this](const TuyaDatapoint &datapoint) {

View File

@@ -163,7 +163,7 @@ void Valve::publish_state(bool save) {
} }
} }
optional<ValveRestoreState> Valve::restore_state_() { optional<ValveRestoreState> Valve::restore_state_() {
this->rtc_ = global_preferences->make_preference<ValveRestoreState>(this->get_preference_hash()); this->rtc_ = this->make_entity_preference<ValveRestoreState>();
ValveRestoreState recovered{}; ValveRestoreState recovered{};
if (!this->rtc_.load(&recovered)) if (!this->rtc_.load(&recovered))
return {}; return {};

View File

@@ -185,7 +185,7 @@ void WaterHeater::publish_state() {
} }
optional<WaterHeaterCall> WaterHeater::restore_state_() { optional<WaterHeaterCall> WaterHeater::restore_state_() {
this->pref_ = global_preferences->make_preference<SavedWaterHeaterState>(this->get_preference_hash()); this->pref_ = this->make_entity_preference<SavedWaterHeaterState>();
SavedWaterHeaterState recovered{}; SavedWaterHeaterState recovered{};
if (!this->pref_.load(&recovered)) if (!this->pref_.load(&recovered))
return {}; return {};

View File

@@ -48,4 +48,4 @@ async def to_code(config):
if CORE.is_libretiny: if CORE.is_libretiny:
CORE.add_platformio_option("lib_ignore", ["ESPAsyncTCP", "RPAsyncTCP"]) CORE.add_platformio_option("lib_ignore", ["ESPAsyncTCP", "RPAsyncTCP"])
# https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json # https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json
cg.add_library("ESP32Async/ESPAsyncWebServer", "3.7.10") cg.add_library("ESP32Async/ESPAsyncWebServer", "3.9.5")

View File

@@ -746,16 +746,32 @@ void WiFiComponent::setup_ap_config_() {
return; return;
if (this->ap_.get_ssid().empty()) { if (this->ap_.get_ssid().empty()) {
std::string name = App.get_name(); // Build AP SSID from app name without heap allocation
if (name.length() > 32) { // WiFi SSID max is 32 bytes, with MAC suffix we keep first 25 + last 7
static constexpr size_t AP_SSID_MAX_LEN = 32;
static constexpr size_t AP_SSID_PREFIX_LEN = 25;
static constexpr size_t AP_SSID_SUFFIX_LEN = 7;
const std::string &app_name = App.get_name();
const char *name_ptr = app_name.c_str();
size_t name_len = app_name.length();
if (name_len <= AP_SSID_MAX_LEN) {
// Name fits, use directly
this->ap_.set_ssid(name_ptr);
} else {
// Name too long, need to truncate into stack buffer
char ssid_buf[AP_SSID_MAX_LEN + 1];
if (App.is_name_add_mac_suffix_enabled()) { if (App.is_name_add_mac_suffix_enabled()) {
// Keep first 25 chars and last 7 chars (MAC suffix), remove middle // Keep first 25 chars and last 7 chars (MAC suffix), remove middle
name.erase(25, name.length() - 32); memcpy(ssid_buf, name_ptr, AP_SSID_PREFIX_LEN);
memcpy(ssid_buf + AP_SSID_PREFIX_LEN, name_ptr + name_len - AP_SSID_SUFFIX_LEN, AP_SSID_SUFFIX_LEN);
} else { } else {
name.resize(32); memcpy(ssid_buf, name_ptr, AP_SSID_MAX_LEN);
} }
ssid_buf[AP_SSID_MAX_LEN] = '\0';
this->ap_.set_ssid(ssid_buf);
} }
this->ap_.set_ssid(name);
} }
this->ap_setup_ = this->wifi_start_ap_(this->ap_); this->ap_setup_ = this->wifi_start_ap_(this->ap_);

View File

@@ -92,6 +92,48 @@ StringRef EntityBase::get_object_id_to(std::span<char, OBJECT_ID_MAX_LEN> buf) c
uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; } uint32_t EntityBase::get_object_id_hash() { return this->object_id_hash_; }
// Migrate preference data from old_key to new_key if they differ.
// This helper is exposed so callers with custom key computation (like TextPrefs)
// can use it for manual migration. See: https://github.com/esphome/backlog/issues/85
//
// FUTURE IMPLEMENTATION:
// This will require raw load/save methods on ESPPreferenceObject that take uint8_t* and size.
// void EntityBase::migrate_entity_preference_(size_t size, uint32_t old_key, uint32_t new_key) {
// if (old_key == new_key)
// return;
// auto old_pref = global_preferences->make_preference(size, old_key);
// auto new_pref = global_preferences->make_preference(size, new_key);
// SmallBufferWithHeapFallback<64> buffer(size);
// if (old_pref.load(buffer.data(), size)) {
// new_pref.save(buffer.data(), size);
// }
// }
ESPPreferenceObject EntityBase::make_entity_preference_(size_t size, uint32_t version) {
// This helper centralizes preference creation to enable fixing hash collisions.
// See: https://github.com/esphome/backlog/issues/85
//
// COLLISION PROBLEM: get_preference_hash() uses fnv1_hash on sanitized object_id.
// Multiple entity names can sanitize to the same object_id:
// - "Living Room" and "living_room" both become "living_room"
// - UTF-8 names like "温度" and "湿度" both become "__" (underscores)
// This causes entities to overwrite each other's stored preferences.
//
// FUTURE MIGRATION: When implementing get_preference_hash_v2() that hashes
// the original entity name (not sanitized object_id):
//
// uint32_t old_key = this->get_preference_hash() ^ version;
// uint32_t new_key = this->get_preference_hash_v2() ^ version;
// this->migrate_entity_preference_(size, old_key, new_key);
// return global_preferences->make_preference(size, new_key);
//
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
uint32_t key = this->get_preference_hash() ^ version;
#pragma GCC diagnostic pop
return global_preferences->make_preference(size, key);
}
std::string EntityBase_DeviceClass::get_device_class() { std::string EntityBase_DeviceClass::get_device_class() {
if (this->device_class_ == nullptr) { if (this->device_class_ == nullptr) {
return ""; return "";

View File

@@ -6,6 +6,7 @@
#include "string_ref.h" #include "string_ref.h"
#include "helpers.h" #include "helpers.h"
#include "log.h" #include "log.h"
#include "preferences.h"
#ifdef USE_DEVICES #ifdef USE_DEVICES
#include "device.h" #include "device.h"
@@ -138,7 +139,12 @@ class EntityBase {
* from previous versions, so existing single-device configurations will continue to work. * from previous versions, so existing single-device configurations will continue to work.
* *
* @return uint32_t The unique hash for preferences, including device_id if available. * @return uint32_t The unique hash for preferences, including device_id if available.
* @deprecated Use make_entity_preference<T>() instead, or preferences won't be migrated.
* See https://github.com/esphome/backlog/issues/85
*/ */
ESPDEPRECATED("Use make_entity_preference<T>() instead, or preferences won't be migrated. "
"See https://github.com/esphome/backlog/issues/85. Will be removed in 2027.1.0.",
"2026.7.0")
uint32_t get_preference_hash() { uint32_t get_preference_hash() {
#ifdef USE_DEVICES #ifdef USE_DEVICES
// Combine object_id_hash with device_id to ensure uniqueness across devices // Combine object_id_hash with device_id to ensure uniqueness across devices
@@ -151,7 +157,19 @@ class EntityBase {
#endif #endif
} }
/// Create a preference object for storing this entity's state/settings.
/// @tparam T The type of data to store (must be trivially copyable)
/// @param version Optional version hash XORed with preference key (change when struct layout changes)
template<typename T> ESPPreferenceObject make_entity_preference(uint32_t version = 0) {
static_assert(std::is_trivially_copyable<T>::value, "T must be trivially copyable");
return this->make_entity_preference_(sizeof(T), version);
}
protected: protected:
/// Non-template helper for make_entity_preference() to avoid code bloat.
/// When preference hash algorithm changes, migration logic goes here.
ESPPreferenceObject make_entity_preference_(size_t size, uint32_t version);
void calc_object_id_(); void calc_object_id_();
StringRef name_; StringRef name_;

View File

@@ -148,6 +148,25 @@ template<typename T, size_t N> class StaticVector {
size_t count_{0}; size_t count_{0};
public: public:
// Default constructor
StaticVector() = default;
// Iterator range constructor
template<typename InputIt> StaticVector(InputIt first, InputIt last) {
while (first != last && count_ < N) {
data_[count_++] = *first++;
}
}
// Initializer list constructor
StaticVector(std::initializer_list<T> init) {
for (const auto &val : init) {
if (count_ >= N)
break;
data_[count_++] = val;
}
}
// Minimal vector-compatible interface - only what we actually use // Minimal vector-compatible interface - only what we actually use
void push_back(const T &value) { void push_back(const T &value) {
if (count_ < N) { if (count_ < N) {
@@ -155,6 +174,17 @@ template<typename T, size_t N> class StaticVector {
} }
} }
// Clear all elements
void clear() { count_ = 0; }
// Assign from iterator range
template<typename InputIt> void assign(InputIt first, InputIt last) {
count_ = 0;
while (first != last && count_ < N) {
data_[count_++] = *first++;
}
}
// Return reference to next element and increment count (with bounds checking) // Return reference to next element and increment count (with bounds checking)
T &emplace_next() { T &emplace_next() {
if (count_ >= N) { if (count_ >= N) {
@@ -186,6 +216,10 @@ template<typename T, size_t N> class StaticVector {
reverse_iterator rend() { return reverse_iterator(begin()); } reverse_iterator rend() { return reverse_iterator(begin()); }
const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
// Conversion to std::span for compatibility with span-based APIs
operator std::span<T>() { return std::span<T>(data_.data(), count_); }
operator std::span<const T>() const { return std::span<const T>(data_.data(), count_); }
}; };
/// Fixed-capacity vector - allocates once at runtime, never reallocates /// Fixed-capacity vector - allocates once at runtime, never reallocates
@@ -655,9 +689,11 @@ inline uint32_t fnv1_hash_object_id(const char *str, size_t len) {
} }
/// snprintf-like function returning std::string of maximum length \p len (excluding null terminator). /// snprintf-like function returning std::string of maximum length \p len (excluding null terminator).
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...); std::string __attribute__((format(printf, 1, 3))) str_snprintf(const char *fmt, size_t len, ...);
/// sprintf-like function returning std::string. /// sprintf-like function returning std::string.
/// @warning Allocates heap memory. Use snprintf() with a stack buffer instead.
std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...); std::string __attribute__((format(printf, 1, 2))) str_sprintf(const char *fmt, ...);
#ifdef USE_ESP8266 #ifdef USE_ESP8266

View File

@@ -114,7 +114,7 @@ lib_deps =
ESP8266WiFi ; wifi (Arduino built-in) ESP8266WiFi ; wifi (Arduino built-in)
Update ; ota (Arduino built-in) Update ; ota (Arduino built-in)
ESP32Async/ESPAsyncTCP@2.0.0 ; async_tcp ESP32Async/ESPAsyncTCP@2.0.0 ; async_tcp
ESP32Async/ESPAsyncWebServer@3.7.8 ; web_server_base ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base
makuna/NeoPixelBus@2.7.3 ; neopixelbus makuna/NeoPixelBus@2.7.3 ; neopixelbus
ESP8266HTTPClient ; http_request (Arduino built-in) ESP8266HTTPClient ; http_request (Arduino built-in)
ESP8266mDNS ; mdns (Arduino built-in) ESP8266mDNS ; mdns (Arduino built-in)
@@ -201,7 +201,7 @@ framework = arduino
lib_deps = lib_deps =
${common:arduino.lib_deps} ${common:arduino.lib_deps}
bblanchon/ArduinoJson@7.4.2 ; json bblanchon/ArduinoJson@7.4.2 ; json
ESP32Async/ESPAsyncWebServer@3.7.8 ; web_server_base ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base
build_flags = build_flags =
${common:arduino.build_flags} ${common:arduino.build_flags}
-DUSE_RP2040 -DUSE_RP2040
@@ -217,7 +217,7 @@ framework = arduino
lib_compat_mode = soft lib_compat_mode = soft
lib_deps = lib_deps =
bblanchon/ArduinoJson@7.4.2 ; json bblanchon/ArduinoJson@7.4.2 ; json
ESP32Async/ESPAsyncWebServer@3.7.8 ; web_server_base ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base
droscy/esp_wireguard@0.4.2 ; wireguard droscy/esp_wireguard@0.4.2 ; wireguard
build_flags = build_flags =
${common:arduino.build_flags} ${common:arduino.build_flags}

View File

@@ -692,6 +692,8 @@ HEAP_ALLOCATING_HELPERS = {
"str_truncate": "removal (function is unused)", "str_truncate": "removal (function is unused)",
"str_upper_case": "removal (function is unused)", "str_upper_case": "removal (function is unused)",
"str_snake_case": "removal (function is unused)", "str_snake_case": "removal (function is unused)",
"str_sprintf": "snprintf() with a stack buffer",
"str_snprintf": "snprintf() with a stack buffer",
} }
@@ -710,7 +712,9 @@ HEAP_ALLOCATING_HELPERS = {
r"str_sanitize(?!_)|" r"str_sanitize(?!_)|"
r"str_truncate|" r"str_truncate|"
r"str_upper_case|" r"str_upper_case|"
r"str_snake_case" r"str_snake_case|"
r"str_sprintf|"
r"str_snprintf"
r")\s*\(" + CPP_RE_EOL, r")\s*\(" + CPP_RE_EOL,
include=cpp_include, include=cpp_include,
exclude=[ exclude=[