ESPHome 2026.11.0-dev
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 entity name length - keep in sync with NAME_MAX_LENGTH in esphome/config_validation.py
35static constexpr size_t ESPHOME_ENTITY_NAME_MAX_LEN = 120;
36
37static constexpr size_t ENTITY_NAME_BUF_SIZE = ESPHOME_ENTITY_NAME_MAX_LEN + 1;
38
39// Maximum domain length (longest: "alarm_control_panel" = 19)
40static constexpr size_t ESPHOME_DOMAIN_MAX_LEN = 20;
41
42// Maximum size for object_id buffer (friendly_name + null + margin)
43static constexpr size_t OBJECT_ID_MAX_LEN = 128;
44
45// Maximum state length that Home Assistant will accept without raising ValueError
46static constexpr size_t MAX_STATE_LEN = 255;
47
48// Maximum device class string buffer size (47 chars + null terminator)
49// Longest standard device class: "volatile_organic_compounds_parts" (32 chars)
50// Device classes are stored in PROGMEM; on ESP8266 they must be copied to a stack buffer.
51static constexpr size_t MAX_DEVICE_CLASS_LENGTH = 48;
52
53// Maximum icon string buffer size (63 chars + null terminator)
54// Icons are stored in PROGMEM; on ESP8266 they must be copied to a stack buffer.
55static constexpr size_t MAX_ICON_LENGTH = 64;
56
62
63// Bit layout for entity_fields parameter in configure_entity_().
64// Keep in sync with _*_SHIFT constants in esphome/core/entity_helpers.py
65static constexpr uint8_t ENTITY_FIELD_DC_SHIFT = 0;
66static constexpr uint8_t ENTITY_FIELD_UOM_SHIFT = 8;
67static constexpr uint8_t ENTITY_FIELD_ICON_SHIFT = 16;
68static constexpr uint8_t ENTITY_FIELD_INTERNAL_SHIFT = 24;
69static constexpr uint8_t ENTITY_FIELD_DISABLED_BY_DEFAULT_SHIFT = 25;
70static constexpr uint8_t ENTITY_FIELD_ENTITY_CATEGORY_SHIFT = 26;
71
72// The generic Entity base class that provides an interface common to all Entities.
74 public:
75#ifdef USE_ESP8266
77 // Remove before 2027.5.0
78 ESPDEPRECATED("Use get_name_to() or get_log_name() instead. Will be removed in ESPHome 2027.5.0", "2026.11.0")
79 const StringRef &get_name() const;
81 StringRef get_name_to(std::span<char, ENTITY_NAME_BUF_SIZE> buffer) const {
82 return StringRef(buffer.data(), this->write_name_to(buffer.data(), buffer.size()));
83 }
84#else
85 // Remove before 2027.5.0
86 ESPDEPRECATED("Use get_name_to() or get_log_name() instead. Will be removed in ESPHome 2027.5.0", "2026.11.0")
87 const StringRef &get_name() const { return this->name_.ram_ref(); }
88 const StringRef &get_name_to(std::span<char, ENTITY_NAME_BUF_SIZE> /*buffer*/) const { return this->name_.ram_ref(); }
89#endif
90
91 bool name_equals(const StringRef &other) const { return this->name_.equals(other); }
92
94 const ProgmemStringRef &get_name_progmem() const { return this->name_; }
95
97 size_t write_name_to(char *buf, size_t buf_size) const { return this->name_.write_to(buf, buf_size); }
98
100 const LogString *get_log_name() const { return reinterpret_cast<const LogString *>(this->name_.progmem_ptr()); }
101
102 // Get whether this Entity has its own name or it should use the device friendly_name.
103 bool has_own_name() const { return this->flags_.has_own_name; }
104
105 // Get the unique Object ID of this Entity
107
111 StringRef get_object_id_to(std::span<char, OBJECT_ID_MAX_LEN> buf) const;
112
115 size_t write_object_id_to(char *buf, size_t buf_size) const;
116
117 // Get whether this Entity should be hidden outside ESPHome
118 bool is_internal() const { return this->flags_.internal; }
119
120 // Set whether this Entity should be hidden outside ESPHome. Prefer the 'internal:' YAML key
121 // whenever possible: it is guaranteed and has none of the limitations below. Use this only when
122 // the decision can only be made at boot. Must be called before MQTT and the API read the flag:
123 // from on_boot at the default priority, or a setup() that runs above setup_priority::AFTER_WIFI.
124 // If the answer comes from a device handshake, hold setup with can_proceed() until it arrives.
125 // Calls after setup finishes are undefined behavior: the flag is still written and an error is
126 // logged, and from 2027.3.0 the call will be ignored.
127 //
128 // Known limitations. Not bugs, so no issue reports please; a PR that removes one with no RAM
129 // or performance cost would be considered.
130 // - No consumer is notified of a change, so the flag can only be decided once per boot.
131 // - The guard is coarse: a call from a priority below AFTER_WIFI (an on_boot with a low priority,
132 // or a setup() at LATE) still passes, but the API camera listener is already registered, MQTT
133 // (AFTER_CONNECTION) has cached the flag, and an API client that connected while setup was
134 // stalled on a slow component has already listed the entities, so they keep the old value.
135 // - Un-hiding an entity declared 'internal: true' in YAML skips the duplicate name check that
136 // codegen runs for exposed entities, so a name collision can surface at runtime. Entities with
137 // only an 'id:' are forced internal and use the id as their name.
138 // - Zigbee codegen skips YAML internal entities entirely, so un-hiding cannot add them to Zigbee.
139 void set_internal(bool internal);
140
141 // Check if this object is declared to be disabled by default.
142 // That means that when the device gets added to Home Assistant (or other clients) it should
143 // not be added to the default view by default, and a user action is necessary to manually add it.
144 bool is_disabled_by_default() const { return this->flags_.disabled_by_default; }
145
146 // Get the entity category.
148
149 // Get this entity's device class into a stack buffer.
150 // On non-ESP8266: returns pointer to PROGMEM string directly (buffer unused).
151 // On ESP8266: copies from PROGMEM to buffer, returns buffer pointer.
152 const char *get_device_class_to(std::span<char, MAX_DEVICE_CLASS_LENGTH> buffer) const;
155
156 // Get unit of measurement as StringRef (from packed index)
158
159 // Get this entity's icon into a stack buffer.
160 // On ESP32: returns pointer to PROGMEM string directly (buffer unused).
161 // On ESP8266: copies from PROGMEM to buffer, returns buffer pointer.
162 const char *get_icon_to(std::span<char, MAX_ICON_LENGTH> buffer) const;
165
166#ifdef USE_DEVICES
167 // Get this entity's device id
169 if (this->device_ == nullptr) {
170 return 0; // No device set, return 0
171 }
172 return this->device_->get_device_id();
173 }
174 // Get the device this entity belongs to (nullptr if main device)
175 Device *get_device() const { return this->device_; }
176#endif
177
178 // Check if this entity has state
179 bool has_state() const { return this->flags_.has_state; }
180
181 // Set has_state - for components that need to manually set this
182 void set_has_state(bool state) { this->flags_.has_state = state; }
183
203 ESPDEPRECATED("Use make_entity_preference<T>() instead, or preferences won't be migrated. "
204 "See https://github.com/esphome/backlog/issues/85. Will be removed in 2027.1.0.",
205 "2026.7.0")
206 uint32_t get_preference_hash() {
207#ifdef USE_DEVICES
208 // Combine object_id_hash with device_id to ensure uniqueness across devices
209 // Note: device_id is 0 for the main device, so XORing with 0 preserves the original hash
210 // This ensures backward compatibility for existing single-device configurations
211 return this->get_object_id_hash() ^ this->get_device_id();
212#else
213 // Without devices, just use object_id_hash as before
214 return this->get_object_id_hash();
215#endif
216 }
217
221 template<typename T> ESPPreferenceObject make_entity_preference(uint32_t version = 0) {
222 static_assert(std::is_trivially_copyable<T>::value, "T must be trivially copyable");
223 return this->make_entity_preference_(sizeof(T), version);
224 }
225
227 template<typename T> ESPPreferenceObject make_entity_preference(uint32_t version, bool in_flash) {
228 static_assert(std::is_trivially_copyable<T>::value, "T must be trivially copyable");
229 return this->make_entity_preference_(sizeof(T), version, in_flash);
230 }
231
232 protected:
233 friend void ::setup();
234 friend void ::original_setup();
235 // Application's register_<entity>(obj, name, hash, fields) overloads call configure_entity_
236 // before push_back, so codegen can emit a single combined call per entity.
237 friend class Application;
238
241 void configure_entity_(const char *name, uint32_t object_id_hash, uint32_t entity_fields);
242
243#ifdef USE_DEVICES
244 // Codegen-only setter — only accessible from setup() via friend declaration.
245 void set_device_(Device *device) { this->device_ = device; }
246#endif
247
250 ESPPreferenceObject make_entity_preference_(size_t size, uint32_t version, bool in_flash);
253
256#ifdef USE_DEVICES
258#endif
259
260 // Bit-packed flags to save memory (1 byte instead of 5)
261 struct EntityFlags {
262 uint8_t has_own_name : 1;
263 uint8_t internal : 1;
265 uint8_t has_state : 1;
266 uint8_t entity_category : 2; // Supports up to 4 categories
267 uint8_t reserved : 2; // Reserved for future use
269 // String table indices — packed into the 3 padding bytes after flags_
270#ifdef USE_ENTITY_DEVICE_CLASS
272#endif
273#ifdef USE_ENTITY_UNIT_OF_MEASUREMENT
274 uint8_t uom_idx_{};
275#endif
276#ifdef USE_ENTITY_ICON
277 uint8_t icon_idx_{};
278#endif
279};
280
282bool log_entity_header(const char *tag, const char *prefix, const char *type, const EntityBase *obj);
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
322template<typename T> class StatefulEntityBase : public EntityBase {
323 public:
325 virtual const T &get_state() const = 0;
327 T get_state_default(T default_value) const { return this->has_state() ? this->get_state() : default_value; }
329 void invalidate_state() { this->set_new_state({}); }
330
331 template<typename F> void add_full_state_callback(F &&callback) {
332 this->full_state_callbacks_.add(std::forward<F>(callback));
333 }
334 template<typename F> void add_on_state_callback(F &&callback) {
335 this->state_callbacks_.add(std::forward<F>(callback));
336 }
337
338 protected:
340 virtual bool get_trigger_on_initial_state() const = 0;
341
348 virtual bool set_new_state(const optional<T> &new_state) {
349 // Access flags_ directly to avoid function call overhead in this hot path
350 bool had_state = this->flags_.has_state;
351 // Use pointer to avoid requiring T to be default-constructible
352 const T *current = had_state ? &this->get_state() : nullptr;
353 if (new_state.has_value()) {
354 if (current != nullptr && *current == new_state.value())
355 return false; // same value, no change
356 } else if (!had_state) {
357 return false; // already invalidated, no change
358 }
359 // Capture old_state before set_state_value — current pointer aliases subclass storage
360 bool has_full_cbs = !this->full_state_callbacks_.empty();
361 optional<T> old_state;
362 if (has_full_cbs)
363 old_state = current != nullptr ? optional<T>(*current) : nullopt;
364 // Update storage before firing callbacks so callback code can inspect current state
365 this->flags_.has_state = new_state.has_value();
366 if (new_state.has_value()) {
367 this->set_state_value(new_state.value());
368 }
369 if (has_full_cbs)
370 this->full_state_callbacks_.call(old_state, new_state);
371 // had_state first: on every change except the first, skips the virtual call
372 if (new_state.has_value() && (had_state || this->get_trigger_on_initial_state()))
373 this->state_callbacks_.call(new_state.value());
374 return true;
375 }
377 virtual void set_state_value(const T &value) = 0;
378 LazyCallbackManager<void(optional<T> previous, optional<T> current)> full_state_callbacks_;
380};
381} // namespace esphome
uint32_t get_device_id()
Definition device.h:20
struct esphome::EntityBase::EntityFlags flags_
const ProgmemStringRef & get_name_progmem() const
The name as stored, for consumers that read flash themselves (e.g. API progmem fields).
Definition entity_base.h:94
ESPPreferenceObject make_entity_preference_(size_t size, uint32_t version)
Non-template helpers for make_entity_preference() to avoid code bloat.
const char * get_device_class_to(std::span< char, MAX_DEVICE_CLASS_LENGTH > buffer) const
ESPDEPRECATED("Use get_name_to() or get_log_name() instead. Will be removed in ESPHome 2027.5.0", "2026.11.0") const StringRef &get_name() const
The name is in flash here; the first call makes a RAM copy that is kept for the life of the device.
ProgmemStringRef get_device_class_progmem() const
The device class as stored (in flash on ESP8266), for consumers that read flash themselves.
bool has_own_name() const
size_t write_name_to(char *buf, size_t buf_size) const
Copy the name into buf (null terminated), returns its length.
Definition entity_base.h:97
bool is_internal() const
StringRef get_name_to(std::span< char, ENTITY_NAME_BUF_SIZE > buffer) const
Get the name of this Entity, copied out of flash into buffer.
Definition entity_base.h:81
ProgmemStringRef name_
const char * get_icon_to(std::span< char, MAX_ICON_LENGTH > buffer) const
void set_device_(Device *device)
bool name_equals(const StringRef &other) const
Definition entity_base.h:91
uint32_t get_object_id_hash() const
ProgmemStringRef get_icon_progmem() const
The icon as stored (in flash on ESP8266), for consumers that read flash themselves.
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.
uint32_t get_device_id() const
bool is_disabled_by_default() const
uint32_t entity_preference_key_(uint32_t version)
Key for both helpers; preference hash migration goes here.
const LogString * get_log_name() const
Get the name for a "%s" log argument (LOG_STR_ARG).
ESPPreferenceObject make_entity_preference(uint32_t version=0)
Create a preference object for storing this entity's state/settings.
ESPPreferenceObject make_entity_preference(uint32_t version, bool in_flash)
As make_entity_preference(), stored in flash or RTC memory where the backend supports the choice.
StringRef get_unit_of_measurement_ref() const
Device * get_device() const
void set_has_state(bool state)
const StringRef & get_name_to(std::span< char, ENTITY_NAME_BUF_SIZE >) const
Definition entity_base.h:88
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...
void set_internal(bool internal)
A string that may live in flash (on ESP8266): pointer and length, with no byte-reading operations.
Definition string_ref.h:230
constexpr const char * progmem_ptr() const
Definition string_ref.h:238
size_t write_to(char *buf, size_t buf_size) const
Copy into buf (null terminated, truncated to fit), returns the length copied.
Definition string_ref.h:243
constexpr const StringRef & ram_ref() const
The string is in RAM here, so it can be viewed directly.
Definition string_ref.h:268
bool equals(const StringRef &other) const
Definition string_ref.h:256
Base class for entities that track a typed state value with change-detection and callbacks.
virtual bool get_trigger_on_initial_state() const =0
Subclasses return whether callbacks should fire on the very first state.
virtual const T & get_state() const =0
Return the current state value. Only valid when has_state() is true.
virtual bool set_new_state(const optional< T > &new_state)
Apply a new state, de-duplicating and firing callbacks as needed.
void add_full_state_callback(F &&callback)
virtual void set_state_value(const T &value)=0
Subclasses implement this to store the actual value into their own storage.
void invalidate_state()
Clear the state — sets has_state() to false and fires callbacks with nullopt.
LazyCallbackManager< void(T)> state_callbacks_
void add_on_state_callback(F &&callback)
T get_state_default(T default_value) const
Return the current state if available, otherwise return the provided default.
LazyCallbackManager< 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:31
uint8_t type
void original_setup()
void setup()
bool state
Definition fan.h:2
uint32_t buffer
Definition gif.cpp:50
bool log_entity_header(const char *tag, const char *prefix, const char *type, const EntityBase *obj)
Log "<prefix><type> '<name>'" for a dump_config header; returns false when obj is null.
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)
uint16_t size
Definition helpers.cpp:34
@ ENTITY_CATEGORY_NONE
Definition entity_base.h:58
@ ENTITY_CATEGORY_CONFIG
Definition entity_base.h:59
@ ENTITY_CATEGORY_DIAGNOSTIC
Definition entity_base.h:60
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)
STL namespace.
static void uint32_t