#pragma once #include #include #include "string_ref.h" #include "helpers.h" #include "log.h" #ifdef USE_DEVICES #include "device.h" #endif namespace esphome { // Forward declaration for friend access namespace api { class APIConnection; } // namespace api enum EntityCategory : uint8_t { ENTITY_CATEGORY_NONE = 0, ENTITY_CATEGORY_CONFIG = 1, ENTITY_CATEGORY_DIAGNOSTIC = 2, }; // The generic Entity base class that provides an interface common to all Entities. class EntityBase { public: // Get/set the name of this Entity const StringRef &get_name() const; void set_name(const char *name); // Get whether this Entity has its own name or it should use the device friendly_name. bool has_own_name() const { return this->flags_.has_own_name; } // Get the sanitized name of this Entity as an ID. std::string get_object_id() const; void set_object_id(const char *object_id); // Get the unique Object ID of this Entity uint32_t get_object_id_hash(); // Get/set whether this Entity should be hidden outside ESPHome bool is_internal() const { return this->flags_.internal; } void set_internal(bool internal) { this->flags_.internal = internal; } // Check if this object is declared to be disabled by default. // That means that when the device gets added to Home Assistant (or other clients) it should // not be added to the default view by default, and a user action is necessary to manually add it. bool is_disabled_by_default() const { return this->flags_.disabled_by_default; } void set_disabled_by_default(bool disabled_by_default) { this->flags_.disabled_by_default = disabled_by_default; } // Get/set the entity category. EntityCategory get_entity_category() const { return static_cast(this->flags_.entity_category); } void set_entity_category(EntityCategory entity_category) { this->flags_.entity_category = static_cast(entity_category); } // Get/set this entity's icon std::string get_icon() const; void set_icon(const char *icon); StringRef get_icon_ref() const { static constexpr auto EMPTY_STRING = StringRef::from_lit(""); #ifdef USE_ENTITY_ICON return this->icon_c_str_ == nullptr ? EMPTY_STRING : StringRef(this->icon_c_str_); #else return EMPTY_STRING; #endif } #ifdef USE_DEVICES // Get/set this entity's device id uint32_t get_device_id() const { if (this->device_ == nullptr) { return 0; // No device set, return 0 } return this->device_->get_device_id(); } void set_device(Device *device) { this->device_ = device; } #endif // Check if this entity has state bool has_state() const { return this->flags_.has_state; } // Set has_state - for components that need to manually set this void set_has_state(bool state) { this->flags_.has_state = state; } /** * @brief Get a unique hash for storing preferences/settings for this entity. * * This method returns a hash that uniquely identifies the entity for the purpose of * storing preferences (such as calibration, state, etc.). Unlike get_object_id_hash(), * this hash also incorporates the device_id (if devices are enabled), ensuring uniqueness * across multiple devices that may have entities with the same object_id. * * Use this method when storing or retrieving preferences/settings that should be unique * per device-entity pair. Use get_object_id_hash() when you need a hash that identifies * the entity regardless of the device it belongs to. * * For backward compatibility, if device_id is 0 (the main device), the hash is unchanged * 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. */ uint32_t get_preference_hash() { #ifdef USE_DEVICES // Combine object_id_hash with device_id to ensure uniqueness across devices // Note: device_id is 0 for the main device, so XORing with 0 preserves the original hash // This ensures backward compatibility for existing single-device configurations return this->get_object_id_hash() ^ this->get_device_id(); #else // Without devices, just use object_id_hash as before return this->get_object_id_hash(); #endif } protected: friend class api::APIConnection; // Get object_id as StringRef when it's static (for API usage) // Returns empty StringRef if object_id is dynamic (needs allocation) StringRef get_object_id_ref_for_api_() const; /// The hash_base() function has been deprecated. It is kept in this /// class for now, to prevent external components from not compiling. virtual uint32_t hash_base() { return 0L; } void calc_object_id_(); /// Check if the object_id is dynamic (changes with MAC suffix) bool is_object_id_dynamic_() const; StringRef name_; const char *object_id_c_str_{nullptr}; #ifdef USE_ENTITY_ICON const char *icon_c_str_{nullptr}; #endif uint32_t object_id_hash_{}; #ifdef USE_DEVICES Device *device_{}; #endif // Bit-packed flags to save memory (1 byte instead of 5) struct EntityFlags { uint8_t has_own_name : 1; uint8_t internal : 1; uint8_t disabled_by_default : 1; uint8_t has_state : 1; uint8_t entity_category : 2; // Supports up to 4 categories uint8_t reserved : 2; // Reserved for future use } flags_{}; }; class EntityBase_DeviceClass { // NOLINT(readability-identifier-naming) public: /// Get the device class, using the manual override if set. std::string get_device_class(); /// Manually set the device class. void set_device_class(const char *device_class); /// Get the device class as StringRef StringRef get_device_class_ref() const { static constexpr auto EMPTY_STRING = StringRef::from_lit(""); return this->device_class_ == nullptr ? EMPTY_STRING : StringRef(this->device_class_); } protected: const char *device_class_{nullptr}; ///< Device class override }; class EntityBase_UnitOfMeasurement { // NOLINT(readability-identifier-naming) public: /// Get the unit of measurement, using the manual override if set. std::string get_unit_of_measurement(); /// Manually set the unit of measurement. void set_unit_of_measurement(const char *unit_of_measurement); /// Get the unit of measurement as StringRef StringRef get_unit_of_measurement_ref() const { static constexpr auto EMPTY_STRING = StringRef::from_lit(""); return this->unit_of_measurement_ == nullptr ? EMPTY_STRING : StringRef(this->unit_of_measurement_); } protected: const char *unit_of_measurement_{nullptr}; ///< Unit of measurement override }; /** * An entity that has a state. * @tparam T The type of the state */ template class StatefulEntityBase : public EntityBase { public: virtual bool has_state() const { return this->state_.has_value(); } virtual const T &get_state() const { return this->state_.value(); } virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); } void invalidate_state() { this->set_state_({}); } void add_full_state_callback(std::function previous, optional current)> &&callback) { if (this->full_state_callbacks_ == nullptr) this->full_state_callbacks_ = new CallbackManager previous, optional current)>(); // NOLINT this->full_state_callbacks_->add(std::move(callback)); } void add_on_state_callback(std::function &&callback) { if (this->state_callbacks_ == nullptr) this->state_callbacks_ = new CallbackManager(); // NOLINT this->state_callbacks_->add(std::move(callback)); } void set_trigger_on_initial_state(bool trigger_on_initial_state) { this->trigger_on_initial_state_ = trigger_on_initial_state; } protected: optional state_{}; /** * Set a new state for this entity. This will trigger callbacks only if the new state is different from the previous. * * @param state The new state. * @return True if the state was changed, false if it was the same as before. */ bool set_state_(const optional &state) { if (this->state_ != state) { // call the full state callbacks with the previous and new state if (this->full_state_callbacks_ != nullptr) this->full_state_callbacks_->call(this->state_, state); // trigger legacy callbacks only if the new state is valid and either the trigger on initial state is enabled or // the previous state was valid auto had_state = this->has_state(); this->state_ = state; if (this->state_callbacks_ != nullptr && state.has_value() && (this->trigger_on_initial_state_ || had_state)) this->state_callbacks_->call(state.value()); return true; } return false; } bool trigger_on_initial_state_{true}; // callbacks with full state and previous state CallbackManager previous, optional current)> *full_state_callbacks_{}; CallbackManager *state_callbacks_{}; }; } // namespace esphome