ESPHome 2026.3.0
Loading...
Searching...
No Matches
entity_base.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstdint>
4#include <span>
5#include <string>
6#include "string_ref.h"
7#include "helpers.h"
8#include "log.h"
9#include "preferences.h"
10
11#ifdef USE_DEVICES
12#include "device.h"
13#endif
14
15// Forward declarations for friend access from codegen-generated setup()
16void setup(); // NOLINT(readability-redundant-declaration) - may be declared in Arduino.h
17void original_setup(); // NOLINT(readability-redundant-declaration) - used by cpp unit tests
18
19namespace esphome {
20
21// Extern lookup functions for entity string tables.
22// Generated code provides strong definitions; weak defaults return "".
23extern const char *entity_device_class_lookup(uint8_t index);
24extern const char *entity_uom_lookup(uint8_t index);
25extern const char *entity_icon_lookup(uint8_t index);
26
27// Maximum device name length - keep in sync with validate_hostname() in esphome/core/config.py
28static constexpr size_t ESPHOME_DEVICE_NAME_MAX_LEN = 31;
29
30// Maximum friendly name length for entities and sub-devices - keep in sync with FRIENDLY_NAME_MAX_LEN in
31// esphome/core/config.py
32static constexpr size_t ESPHOME_FRIENDLY_NAME_MAX_LEN = 120;
33
34// Maximum domain length (longest: "alarm_control_panel" = 19)
35static constexpr size_t ESPHOME_DOMAIN_MAX_LEN = 20;
36
37// Maximum size for object_id buffer (friendly_name + null + margin)
38static constexpr size_t OBJECT_ID_MAX_LEN = 128;
39
40// Maximum state length that Home Assistant will accept without raising ValueError
41static constexpr size_t MAX_STATE_LEN = 255;
42
43// Maximum device class string buffer size (47 chars + null terminator)
44// Longest standard device class: "volatile_organic_compounds_parts" (32 chars)
45// Device classes are stored in PROGMEM; on ESP8266 they must be copied to a stack buffer.
46static constexpr size_t MAX_DEVICE_CLASS_LENGTH = 48;
47
48// Maximum icon string buffer size (63 chars + null terminator)
49// Icons are stored in PROGMEM; on ESP8266 they must be copied to a stack buffer.
50static constexpr size_t MAX_ICON_LENGTH = 64;
51
57
58// Bit layout for entity_fields parameter in configure_entity_().
59// Keep in sync with _*_SHIFT constants in esphome/core/entity_helpers.py
60static constexpr uint8_t ENTITY_FIELD_DC_SHIFT = 0;
61static constexpr uint8_t ENTITY_FIELD_UOM_SHIFT = 8;
62static constexpr uint8_t ENTITY_FIELD_ICON_SHIFT = 16;
63static constexpr uint8_t ENTITY_FIELD_INTERNAL_SHIFT = 24;
64static constexpr uint8_t ENTITY_FIELD_DISABLED_BY_DEFAULT_SHIFT = 25;
65static constexpr uint8_t ENTITY_FIELD_ENTITY_CATEGORY_SHIFT = 26;
66
67// The generic Entity base class that provides an interface common to all Entities.
69 public:
70 // Get the name of this Entity
71 const StringRef &get_name() const;
72
73 // Get whether this Entity has its own name or it should use the device friendly_name.
74 bool has_own_name() const { return this->flags_.has_own_name; }
75
76 // Get the sanitized name of this Entity as an ID.
77 // Deprecated: object_id mangles names and all object_id methods are planned for removal.
78 // See https://github.com/esphome/backlog/issues/76
79 // Now is the time to stop using object_id entirely. If you still need it temporarily,
80 // use get_object_id_to() which will remain available longer but will also eventually be removed.
81 ESPDEPRECATED("object_id mangles names and all object_id methods are planned for removal "
82 "(see https://github.com/esphome/backlog/issues/76). "
83 "Now is the time to stop using object_id. If still needed, use get_object_id_to() "
84 "which will remain available longer. get_object_id() will be removed in 2026.7.0",
85 "2025.12.0")
86 std::string get_object_id() const;
87
88 // Get the unique Object ID of this Entity
90
94 StringRef get_object_id_to(std::span<char, OBJECT_ID_MAX_LEN> buf) const;
95
98 size_t write_object_id_to(char *buf, size_t buf_size) const;
99
100 // Get whether this Entity should be hidden outside ESPHome
101 bool is_internal() const { return this->flags_.internal; }
102
103 // Deprecated: Calling set_internal() at runtime is undefined behavior. Components and clients
104 // are NOT notified of the change, the flag may have already been read during setup, and there
105 // is NO guarantee any consumer will observe the new value. Use the 'internal:' YAML key instead.
106 ESPDEPRECATED("set_internal() is undefined behavior at runtime — components and Home Assistant are NOT "
107 "notified. Use the 'internal:' YAML key instead. Will be removed in 2027.3.0.",
108 "2026.3.0")
109 void set_internal(bool internal) { this->flags_.internal = internal; }
110
111 // Check if this object is declared to be disabled by default.
112 // That means that when the device gets added to Home Assistant (or other clients) it should
113 // not be added to the default view by default, and a user action is necessary to manually add it.
114 bool is_disabled_by_default() const { return this->flags_.disabled_by_default; }
115
116 // Get the entity category.
118
119 // Get this entity's device class into a stack buffer.
120 // On non-ESP8266: returns pointer to PROGMEM string directly (buffer unused).
121 // On ESP8266: copies from PROGMEM to buffer, returns buffer pointer.
122 const char *get_device_class_to(std::span<char, MAX_DEVICE_CLASS_LENGTH> buffer) const;
123
124#ifdef USE_ESP8266
125 // On ESP8266, rodata is RAM. Device classes are in PROGMEM and cannot be accessed
126 // directly as const char*. Use get_device_class_to() with a stack buffer instead.
127 template<typename T = int> StringRef get_device_class_ref() const {
128 static_assert(sizeof(T) == 0, "get_device_class_ref() unavailable on ESP8266 (rodata is RAM). "
129 "Use get_device_class_to() with a stack buffer.");
130 return StringRef("");
131 }
132 template<typename T = int> std::string get_device_class() const {
133 static_assert(sizeof(T) == 0, "get_device_class() unavailable on ESP8266 (rodata is RAM). "
134 "Use get_device_class_to() with a stack buffer.");
135 return "";
136 }
137#else
138 // Deprecated: use get_device_class_to() instead. Device classes are in PROGMEM.
139 ESPDEPRECATED("Use get_device_class_to() instead. Will be removed in ESPHome 2026.9.0", "2026.3.0")
141 ESPDEPRECATED("Use get_device_class_to() instead. Will be removed in ESPHome 2026.9.0", "2026.3.0")
142 std::string get_device_class() const;
143#endif
144 // Get unit of measurement as StringRef (from packed index)
147 ESPDEPRECATED("Use get_unit_of_measurement_ref() instead for better performance (avoids string copy). Will be "
148 "removed in ESPHome 2026.9.0",
149 "2026.3.0")
150 std::string get_unit_of_measurement() const;
151
152 // Get this entity's icon into a stack buffer.
153 // On ESP32: returns pointer to PROGMEM string directly (buffer unused).
154 // On ESP8266: copies from PROGMEM to buffer, returns buffer pointer.
155 const char *get_icon_to(std::span<char, MAX_ICON_LENGTH> buffer) const;
156
157#ifdef USE_ESP8266
158 // On ESP8266, rodata is RAM. Icons are in PROGMEM and cannot be accessed
159 // directly as const char*. Use get_icon_to() with a stack buffer instead.
160 template<typename T = int> StringRef get_icon_ref() const {
161 static_assert(sizeof(T) == 0,
162 "get_icon_ref() unavailable on ESP8266 (rodata is RAM). Use get_icon_to() with a stack buffer.");
163 return StringRef("");
164 }
165 template<typename T = int> std::string get_icon() const {
166 static_assert(sizeof(T) == 0,
167 "get_icon() unavailable on ESP8266 (rodata is RAM). Use get_icon_to() with a stack buffer.");
168 return "";
169 }
170#else
171 // Deprecated: use get_icon_to() instead. Icons are in PROGMEM.
172 ESPDEPRECATED("Use get_icon_to() instead. Will be removed in ESPHome 2026.9.0", "2026.3.0")
173 StringRef get_icon_ref() const;
174 ESPDEPRECATED("Use get_icon_to() instead. Will be removed in ESPHome 2026.9.0", "2026.3.0")
175 std::string get_icon() const;
176#endif
177
178#ifdef USE_DEVICES
179 // Get this entity's device id
181 if (this->device_ == nullptr) {
182 return 0; // No device set, return 0
183 }
184 return this->device_->get_device_id();
185 }
186 // Get the device this entity belongs to (nullptr if main device)
187 Device *get_device() const { return this->device_; }
188#endif
189
190 // Check if this entity has state
191 bool has_state() const { return this->flags_.has_state; }
192
193 // Set has_state - for components that need to manually set this
194 void set_has_state(bool state) { this->flags_.has_state = state; }
195
215 ESPDEPRECATED("Use make_entity_preference<T>() instead, or preferences won't be migrated. "
216 "See https://github.com/esphome/backlog/issues/85. Will be removed in 2027.1.0.",
217 "2026.7.0")
218 uint32_t get_preference_hash() {
219#ifdef USE_DEVICES
220 // Combine object_id_hash with device_id to ensure uniqueness across devices
221 // Note: device_id is 0 for the main device, so XORing with 0 preserves the original hash
222 // This ensures backward compatibility for existing single-device configurations
223 return this->get_object_id_hash() ^ this->get_device_id();
224#else
225 // Without devices, just use object_id_hash as before
226 return this->get_object_id_hash();
227#endif
228 }
229
233 template<typename T> ESPPreferenceObject make_entity_preference(uint32_t version = 0) {
234 static_assert(std::is_trivially_copyable<T>::value, "T must be trivially copyable");
235 return this->make_entity_preference_(sizeof(T), version);
236 }
237
238 protected:
239 friend void ::setup();
240 friend void ::original_setup();
241
244 void configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_fields);
245
246#ifdef USE_DEVICES
247 // Codegen-only setter — only accessible from setup() via friend declaration.
248 void set_device_(Device *device) { this->device_ = device; }
249#endif
250
254
255 void calc_object_id_();
256
259#ifdef USE_DEVICES
261#endif
262
263 // Bit-packed flags to save memory (1 byte instead of 5)
264 struct EntityFlags {
265 uint8_t has_own_name : 1;
266 uint8_t internal : 1;
268 uint8_t has_state : 1;
269 uint8_t entity_category : 2; // Supports up to 4 categories
270 uint8_t reserved : 2; // Reserved for future use
272 // String table indices — packed into the 3 padding bytes after flags_
273#ifdef USE_ENTITY_DEVICE_CLASS
275#endif
276#ifdef USE_ENTITY_UNIT_OF_MEASUREMENT
277 uint8_t uom_idx_{};
278#endif
279#ifdef USE_ENTITY_ICON
280 uint8_t icon_idx_{};
281#endif
282};
283
285#ifdef USE_ENTITY_ICON
286#define LOG_ENTITY_ICON(tag, prefix, obj) log_entity_icon(tag, prefix, obj)
287void log_entity_icon(const char *tag, const char *prefix, const EntityBase &obj);
288#else
289#define LOG_ENTITY_ICON(tag, prefix, obj) ((void) 0)
290inline void log_entity_icon(const char *, const char *, const EntityBase &) {}
291#endif
293#define LOG_ENTITY_DEVICE_CLASS(tag, prefix, obj) log_entity_device_class(tag, prefix, obj)
294void log_entity_device_class(const char *tag, const char *prefix, const EntityBase &obj);
296#define LOG_ENTITY_UNIT_OF_MEASUREMENT(tag, prefix, obj) log_entity_unit_of_measurement(tag, prefix, obj)
297void log_entity_unit_of_measurement(const char *tag, const char *prefix, const EntityBase &obj);
298
303template<typename T> class StatefulEntityBase : public EntityBase {
304 public:
305 virtual bool has_state() const { return this->state_.has_value(); }
306 virtual const T &get_state() const { return this->state_.value(); } // NOLINT(bugprone-unchecked-optional-access)
307 virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); }
308 void invalidate_state() { this->set_new_state({}); }
309
310 void add_full_state_callback(std::function<void(optional<T> previous, optional<T> current)> &&callback) {
311 if (this->full_state_callbacks_ == nullptr)
312 this->full_state_callbacks_ = new CallbackManager<void(optional<T> previous, optional<T> current)>(); // NOLINT
313 this->full_state_callbacks_->add(std::move(callback));
314 }
315 void add_on_state_callback(std::function<void(T)> &&callback) {
316 if (this->state_callbacks_ == nullptr)
317 this->state_callbacks_ = new CallbackManager<void(T)>(); // NOLINT
318 this->state_callbacks_->add(std::move(callback));
319 }
320
321 void set_trigger_on_initial_state(bool trigger_on_initial_state) {
322 this->trigger_on_initial_state_ = trigger_on_initial_state;
323 }
324
325 protected:
326 optional<T> state_{};
333 virtual bool set_new_state(const optional<T> &new_state) {
334 if (this->state_ != new_state) {
335 // call the full state callbacks with the previous and new state
336 if (this->full_state_callbacks_ != nullptr)
337 this->full_state_callbacks_->call(this->state_, new_state);
338 // trigger legacy callbacks only if the new state is valid and either the trigger on initial state is enabled or
339 // the previous state was valid
340 auto had_state = this->has_state();
341 this->state_ = new_state;
342 if (this->state_callbacks_ != nullptr && new_state.has_value() && (this->trigger_on_initial_state_ || had_state))
343 this->state_callbacks_->call(new_state.value());
344 return true;
345 }
346 return false;
347 }
349 // callbacks with full state and previous state
350 CallbackManager<void(optional<T> previous, optional<T> current)> *full_state_callbacks_{};
352};
353} // namespace esphome
uint32_t get_device_id()
Definition device.h:8
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
bool has_own_name() const
Definition entity_base.h:74
bool is_internal() const
ESPDEPRECATED("Use get_device_class_to() instead. Will be removed in ESPHome 2026.9.0", "2026.3.0") StringRef get_device_class_ref() const
StringRef get_icon_ref() const
std::string get_device_class() const
ESPDEPRECATED("object_id mangles names and all object_id methods are planned for removal " "(see https://github.com/esphome/backlog/issues/76). " "Now is the time to stop using object_id. If still needed, use get_object_id_to() " "which will remain available longer. get_object_id() will be removed in 2026.7.0", "2025.12.0") std uint32_t get_object_id_hash()
const StringRef & get_name() 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())
void set_device_(Device *device)
std::string get_icon() const
ESPDEPRECATED("Use get_icon_to() instead. Will be removed in ESPHome 2026.9.0", "2026.3.0") std uint32_t get_device_id() 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...
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()
Get a unique hash for storing preferences/settings for this entity.
bool is_disabled_by_default() const
StringRef get_device_class_ref() const
ESPDEPRECATED("set_internal() is undefined behavior at runtime — components and Home Assistant are NOT " "notified. Use the 'internal:' YAML key instead. Will be removed in 2027.3.0.", "2026.3.0") void set_internal(bool internal)
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
ESPDEPRECATED("Use get_icon_to() instead. Will be removed in ESPHome 2026.9.0", "2026.3.0") StringRef get_icon_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
Device * get_device() const
void set_has_state(bool state)
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,...
bool has_state() const
EntityCategory get_entity_category() const
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...
An entity that has a state.
virtual const T & get_state() const
virtual bool set_new_state(const optional< T > &new_state)
Set a new state for this entity.
void add_full_state_callback(std::function< void(optional< T > previous, optional< T > current)> &&callback)
CallbackManager< void(T)> * state_callbacks_
void add_on_state_callback(std::function< void(T)> &&callback)
void set_trigger_on_initial_state(bool trigger_on_initial_state)
virtual bool has_state() const
virtual T get_state_default(T default_value) const
CallbackManager< void(optional< T > previous, optional< T > current)> * full_state_callbacks_
StringRef is a reference to a string owned by something else.
Definition string_ref.h:26
void original_setup()
void setup()
bool state
Definition fan.h:2
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)
void log_entity_unit_of_measurement(const char *tag, const char *prefix, const EntityBase &obj)
const char * entity_device_class_lookup(uint8_t index)
size_t size
Definition helpers.h:929
@ ENTITY_CATEGORY_NONE
Definition entity_base.h:53
@ ENTITY_CATEGORY_CONFIG
Definition entity_base.h:54
@ ENTITY_CATEGORY_DIAGNOSTIC
Definition entity_base.h:55
const char * entity_uom_lookup(uint8_t index)
const char * entity_icon_lookup(uint8_t index)
void log_entity_device_class(const char *tag, const char *prefix, const EntityBase &obj)
static void uint32_t