ESPHome 2026.5.0-dev
Loading...
Searching...
No Matches
entity_base.cpp
Go to the documentation of this file.
6
7namespace esphome {
8
9static const char *const TAG = "entity_base";
10
11void EntityBase::configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_fields) {
12 this->name_ = StringRef(name);
13 if (this->name_.empty()) {
14#ifdef USE_DEVICES
15 if (this->device_ != nullptr) {
16 this->name_ = StringRef(this->device_->get_name());
17 } else
18#endif
19 {
20 // Bug-for-bug compatibility with OLD behavior:
21 // - With MAC suffix: OLD code used App.get_friendly_name() directly (no fallback)
22 // - Without MAC suffix: OLD code used pre-computed object_id with fallback to device name
23 const auto &friendly = App.get_friendly_name();
25 // MAC suffix enabled - use friendly_name directly (even if empty) for compatibility
26 this->name_ = friendly;
27 } else {
28 // No MAC suffix - fallback to device name if friendly_name is empty
29 this->name_ = !friendly.empty() ? friendly : App.get_name();
30 }
31 }
32 this->flags_.has_own_name = false;
33 // Dynamic name - must calculate hash at runtime
34 this->calc_object_id_();
35 } else {
36 this->flags_.has_own_name = true;
37 // Static name - use pre-computed hash if provided
38 if (object_id_hash != 0) {
39 this->object_id_hash_ = object_id_hash;
40 } else {
41 this->calc_object_id_();
42 }
43 }
44 // Unpack entity string table indices and flags from entity_fields.
45#ifdef USE_ENTITY_DEVICE_CLASS
46 this->device_class_idx_ = (entity_fields >> ENTITY_FIELD_DC_SHIFT) & 0xFF;
47#endif
48#ifdef USE_ENTITY_UNIT_OF_MEASUREMENT
49 this->uom_idx_ = (entity_fields >> ENTITY_FIELD_UOM_SHIFT) & 0xFF;
50#endif
51#ifdef USE_ENTITY_ICON
52 this->icon_idx_ = (entity_fields >> ENTITY_FIELD_ICON_SHIFT) & 0xFF;
53#endif
54 this->flags_.internal = (entity_fields >> ENTITY_FIELD_INTERNAL_SHIFT) & 1;
55 this->flags_.disabled_by_default = (entity_fields >> ENTITY_FIELD_DISABLED_BY_DEFAULT_SHIFT) & 1;
56 this->flags_.entity_category = (entity_fields >> ENTITY_FIELD_ENTITY_CATEGORY_SHIFT) & 0x3;
57}
58
59// Weak default lookup functions — overridden by generated code in main.cpp
60__attribute__((weak)) const char *entity_device_class_lookup(uint8_t) { return ""; }
61__attribute__((weak)) const char *entity_uom_lookup(uint8_t) { return ""; }
62__attribute__((weak)) const char *entity_icon_lookup(uint8_t) { return ""; }
63
64// Entity device class — buffer-based API for PROGMEM safety on ESP8266
65const char *EntityBase::get_device_class_to([[maybe_unused]] std::span<char, MAX_DEVICE_CLASS_LENGTH> buffer) const {
66#ifdef USE_ENTITY_DEVICE_CLASS
67 const uint8_t idx = this->device_class_idx_;
68#else
69 const uint8_t idx = 0;
70#endif
71#ifdef USE_ESP8266
72 if (idx == 0)
73 return "";
74 const char *dc = entity_device_class_lookup(idx);
75 ESPHOME_strncpy_P(buffer.data(), dc, buffer.size() - 1);
76 buffer[buffer.size() - 1] = '\0';
77 return buffer.data();
78#else
80#endif
81}
82
83#ifndef USE_ESP8266
84// Deprecated device class accessors — not available on ESP8266 (rodata is RAM)
86#ifdef USE_ENTITY_DEVICE_CLASS
88#else
90#endif
91}
92std::string EntityBase::get_device_class() const {
93#ifdef USE_ENTITY_DEVICE_CLASS
94 return std::string(entity_device_class_lookup(this->device_class_idx_));
95#else
96 return std::string(entity_device_class_lookup(0));
97#endif
98}
99#endif // !USE_ESP8266
100
101// Entity unit of measurement (from index)
103#ifdef USE_ENTITY_UNIT_OF_MEASUREMENT
104 return StringRef(entity_uom_lookup(this->uom_idx_));
105#else
106 return StringRef(entity_uom_lookup(0));
107#endif
108}
109std::string EntityBase::get_unit_of_measurement() const {
110 return std::string(this->get_unit_of_measurement_ref().c_str());
111}
112
113// Entity icon — buffer-based API for PROGMEM safety on ESP8266
114const char *EntityBase::get_icon_to([[maybe_unused]] std::span<char, MAX_ICON_LENGTH> buffer) const {
115#ifdef USE_ENTITY_ICON
116 const uint8_t idx = this->icon_idx_;
117#else
118 const uint8_t idx = 0;
119#endif
120#ifdef USE_ESP8266
121 if (idx == 0)
122 return "";
123 const char *icon = entity_icon_lookup(idx);
124 ESPHOME_strncpy_P(buffer.data(), icon, buffer.size() - 1);
125 buffer[buffer.size() - 1] = '\0';
126 return buffer.data();
127#else
128 return entity_icon_lookup(idx);
129#endif
130}
131
132#ifndef USE_ESP8266
133// Deprecated icon accessors — not available on ESP8266 (rodata is RAM)
135#ifdef USE_ENTITY_ICON
137#else
138 return StringRef(entity_icon_lookup(0));
139#endif
140}
141std::string EntityBase::get_icon() const {
142#ifdef USE_ENTITY_ICON
143 return std::string(entity_icon_lookup(this->icon_idx_));
144#else
145 return std::string(entity_icon_lookup(0));
146#endif
147}
148#endif // !USE_ESP8266
149
150// Entity Object ID - computed on-demand from name
151std::string EntityBase::get_object_id() const {
152 char buf[OBJECT_ID_MAX_LEN];
153 size_t len = this->write_object_id_to(buf, sizeof(buf));
154 return std::string(buf, len);
155}
156
157// Calculate Object ID Hash directly from name using snake_case + sanitize
159 this->object_id_hash_ = fnv1_hash_object_id(this->name_.c_str(), this->name_.size());
160}
161
162size_t EntityBase::write_object_id_to(char *buf, size_t buf_size) const {
163 size_t len = std::min(this->name_.size(), buf_size - 1);
164 for (size_t i = 0; i < len; i++) {
165 buf[i] = to_sanitized_char(to_snake_case_char(this->name_[i]));
166 }
167 buf[len] = '\0';
168 return len;
169}
170
171StringRef EntityBase::get_object_id_to(std::span<char, OBJECT_ID_MAX_LEN> buf) const {
172 size_t len = this->write_object_id_to(buf.data(), buf.size());
173 return StringRef(buf.data(), len);
174}
175
176// Migrate preference data from old_key to new_key if they differ.
177// This helper is exposed so callers with custom key computation (like TextPrefs)
178// can use it for manual migration. See: https://github.com/esphome/backlog/issues/85
179//
180// FUTURE IMPLEMENTATION:
181// This will require raw load/save methods on ESPPreferenceObject that take uint8_t* and size.
182// void EntityBase::migrate_entity_preference_(size_t size, uint32_t old_key, uint32_t new_key) {
183// if (old_key == new_key)
184// return;
185// auto old_pref = global_preferences->make_preference(size, old_key);
186// auto new_pref = global_preferences->make_preference(size, new_key);
187// SmallBufferWithHeapFallback<64> buffer(size);
188// if (old_pref.load(buffer.data(), size)) {
189// new_pref.save(buffer.data(), size);
190// }
191// }
192
194 // This helper centralizes preference creation to enable fixing hash collisions.
195 // See: https://github.com/esphome/backlog/issues/85
196 //
197 // COLLISION PROBLEM: get_preference_hash() uses fnv1_hash on sanitized object_id.
198 // Multiple entity names can sanitize to the same object_id:
199 // - "Living Room" and "living_room" both become "living_room"
200 // - UTF-8 names like "温度" and "湿度" both become "__" (underscores)
201 // This causes entities to overwrite each other's stored preferences.
202 //
203 // FUTURE MIGRATION: When implementing get_preference_hash_v2() that hashes
204 // the original entity name (not sanitized object_id):
205 //
206 // uint32_t old_key = this->get_preference_hash() ^ version;
207 // uint32_t new_key = this->get_preference_hash_v2() ^ version;
208 // this->migrate_entity_preference_(size, old_key, new_key);
209 // return global_preferences->make_preference(size, new_key);
210 //
211#pragma GCC diagnostic push
212#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
213 uint32_t key = this->get_preference_hash() ^ version;
214#pragma GCC diagnostic pop
216}
217
218#ifdef USE_ENTITY_ICON
219void log_entity_icon(const char *tag, const char *prefix, const EntityBase &obj) {
220 char icon_buf[MAX_ICON_LENGTH];
221 const char *icon = obj.get_icon_to(icon_buf);
222 if (icon[0] != '\0') {
223 ESP_LOGCONFIG(tag, "%s Icon: '%s'", prefix, icon);
224 }
225}
226#endif
227
228void log_entity_device_class(const char *tag, const char *prefix, const EntityBase &obj) {
229 char dc_buf[MAX_DEVICE_CLASS_LENGTH];
230 const char *dc = obj.get_device_class_to(dc_buf);
231 if (dc[0] != '\0') {
232 ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, dc);
233 }
234}
235
236void log_entity_unit_of_measurement(const char *tag, const char *prefix, const EntityBase &obj) {
237 if (!obj.get_unit_of_measurement_ref().empty()) {
238 ESP_LOGCONFIG(tag, "%s Unit of Measurement: '%s'", prefix, obj.get_unit_of_measurement_ref().c_str());
239 }
240}
241
242} // namespace esphome
const StringRef & get_name() const
Get the name of this Application set by pre_setup().
const StringRef & get_friendly_name() const
Get the friendly name of this Application set by pre_setup().
bool is_name_add_mac_suffix_enabled() const
const char * get_name()
Definition device.h:10
struct esphome::EntityBase::EntityFlags flags_
ESPPreferenceObject make_entity_preference_(size_t size, uint32_t version)
Non-template helper for make_entity_preference() to avoid code bloat.
const char * get_device_class_to(std::span< char, MAX_DEVICE_CLASS_LENGTH > buffer) const
StringRef get_icon_ref() const
std::string get_device_class() const
ESPDEPRECATED("Use get_unit_of_measurement_ref() instead for better performance (avoids string copy). Will be " "removed in ESPHome 2026.9.0", "2026.3.0") std const char * get_icon_to(std::span< char, MAX_ICON_LENGTH > buffer) const
Get the unit of measurement as std::string (deprecated, prefer get_unit_of_measurement_ref())
std::string get_icon() const
size_t write_object_id_to(char *buf, size_t buf_size) const
Write object_id directly to buffer, returns length written (excluding null) Useful for building compo...
StringRef get_device_class_ref() const
ESPDEPRECATED("Use get_device_class_to() instead. Will be removed in ESPHome 2026.9.0", "2026.3.0") std StringRef get_unit_of_measurement_ref() const
void configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_fields)
Combined entity setup from codegen: set name, object_id hash, entity string indices,...
StringRef get_object_id_to(std::span< char, OBJECT_ID_MAX_LEN > buf) const
Get object_id with zero heap allocation For static case: returns StringRef to internal storage (buffe...
StringRef is a reference to a string owned by something else.
Definition string_ref.h:26
constexpr const char * c_str() const
Definition string_ref.h:73
constexpr bool empty() const
Definition string_ref.h:76
constexpr size_type size() const
Definition string_ref.h:74
struct @65::@66 __attribute__
Wake the main loop task from an ISR. ISR-safe.
Definition main_task.h:32
const char *const TAG
Definition spi.cpp:7
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7
const char * tag
Definition log.h:74
void log_entity_icon(const char *tag, const char *prefix, const EntityBase &obj)
constexpr char to_sanitized_char(char c)
Sanitize a single char: keep alphanumerics, dashes, underscores; replace others with underscore.
Definition helpers.h:1005
void log_entity_unit_of_measurement(const char *tag, const char *prefix, const EntityBase &obj)
const char * entity_device_class_lookup(uint8_t index)
std::string size_t len
Definition helpers.h:1045
uint16_t size
Definition helpers.cpp:25
ESPPreferences * global_preferences
uint32_t fnv1_hash_object_id(const char *str, size_t len)
Calculate FNV-1 hash of a string while applying snake_case + sanitize transformations.
Definition helpers.h:1033
const char * entity_uom_lookup(uint8_t index)
const char * entity_icon_lookup(uint8_t index)
constexpr char to_snake_case_char(char c)
Convert a single char to snake_case: lowercase and space to underscore.
Definition helpers.h:999
Application App
Global storage of Application pointer - only one Application can exist.
void log_entity_device_class(const char *tag, const char *prefix, const EntityBase &obj)
static void uint32_t
ESPPreferenceObject make_preference(size_t, uint32_t, bool)
Definition preferences.h:24