From ca6a05bfddde5911b6bb1bbdbb8bb0c3364bedcf Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 3 Mar 2026 10:50:39 -1000 Subject: [PATCH] move device class strings to PROGMEM on ESP8266 Same treatment as icons in #14437. Device class strings were in .rodata (RAM on ESP8266). Now stored as individual PROGMEM char arrays. - Add get_device_class_to(std::span) buffer API matching get_icon_to() pattern - Add fill_and_encode_entity_info_with_device_class() wrapper to deduplicate buffer handling across 10 API entity types - Centralize MQTT device_class in send_discovery_() lambda where buffer lifetime outlives ArduinoJson serialization - Deprecate get_device_class_ref()/get_device_class() on non-ESP8266 - static_assert error on ESP8266 directing to get_device_class_to() - Update all callers: api, mqtt, web_server, log helper --- esphome/components/api/api_connection.cpp | 42 +++++++++---------- esphome/components/api/api_connection.h | 9 ++++ .../components/mqtt/mqtt_binary_sensor.cpp | 6 --- esphome/components/mqtt/mqtt_button.cpp | 6 --- esphome/components/mqtt/mqtt_component.cpp | 5 +++ esphome/components/mqtt/mqtt_cover.cpp | 7 ---- esphome/components/mqtt/mqtt_event.cpp | 7 ---- esphome/components/mqtt/mqtt_number.cpp | 4 -- esphome/components/mqtt/mqtt_sensor.cpp | 5 --- esphome/components/mqtt/mqtt_text_sensor.cpp | 6 --- esphome/components/mqtt/mqtt_valve.cpp | 7 ---- esphome/components/web_server/web_server.cpp | 3 +- esphome/core/entity_base.cpp | 37 ++++++++++++++-- esphome/core/entity_base.h | 33 ++++++++++++--- esphome/core/entity_helpers.py | 2 +- 15 files changed, 98 insertions(+), 81 deletions(-) diff --git a/esphome/components/api/api_connection.cpp b/esphome/components/api/api_connection.cpp index 59476fac25..da2f021e01 100644 --- a/esphome/components/api/api_connection.cpp +++ b/esphome/components/api/api_connection.cpp @@ -413,10 +413,9 @@ uint16_t APIConnection::try_send_binary_sensor_state(EntityBase *entity, APIConn uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) { auto *binary_sensor = static_cast(entity); ListEntitiesBinarySensorResponse msg; - msg.device_class = binary_sensor->get_device_class_ref(); msg.is_status_binary_sensor = binary_sensor->is_status_binary_sensor(); - return fill_and_encode_entity_info(binary_sensor, msg, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn, - remaining_size); + return fill_and_encode_entity_info_with_device_class( + binary_sensor, msg, msg.device_class, ListEntitiesBinarySensorResponse::MESSAGE_TYPE, conn, remaining_size); } #endif @@ -442,8 +441,8 @@ uint16_t APIConnection::try_send_cover_info(EntityBase *entity, APIConnection *c msg.supports_position = traits.get_supports_position(); msg.supports_tilt = traits.get_supports_tilt(); msg.supports_stop = traits.get_supports_stop(); - msg.device_class = cover->get_device_class_ref(); - return fill_and_encode_entity_info(cover, msg, ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size); + return fill_and_encode_entity_info_with_device_class(cover, msg, msg.device_class, + ListEntitiesCoverResponse::MESSAGE_TYPE, conn, remaining_size); } void APIConnection::on_cover_command_request(const CoverCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(cover::Cover, cover, cover) @@ -608,9 +607,9 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection * msg.unit_of_measurement = sensor->get_unit_of_measurement_ref(); msg.accuracy_decimals = sensor->get_accuracy_decimals(); msg.force_update = sensor->get_force_update(); - msg.device_class = sensor->get_device_class_ref(); msg.state_class = static_cast(sensor->get_state_class()); - return fill_and_encode_entity_info(sensor, msg, ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size); + return fill_and_encode_entity_info_with_device_class(sensor, msg, msg.device_class, + ListEntitiesSensorResponse::MESSAGE_TYPE, conn, remaining_size); } #endif @@ -630,8 +629,8 @@ uint16_t APIConnection::try_send_switch_info(EntityBase *entity, APIConnection * auto *a_switch = static_cast(entity); ListEntitiesSwitchResponse msg; msg.assumed_state = a_switch->assumed_state(); - msg.device_class = a_switch->get_device_class_ref(); - return fill_and_encode_entity_info(a_switch, msg, ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size); + return fill_and_encode_entity_info_with_device_class(a_switch, msg, msg.device_class, + ListEntitiesSwitchResponse::MESSAGE_TYPE, conn, remaining_size); } void APIConnection::on_switch_command_request(const SwitchCommandRequest &msg) { ENTITY_COMMAND_GET(switch_::Switch, a_switch, switch) @@ -660,9 +659,8 @@ uint16_t APIConnection::try_send_text_sensor_state(EntityBase *entity, APIConnec uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) { auto *text_sensor = static_cast(entity); ListEntitiesTextSensorResponse msg; - msg.device_class = text_sensor->get_device_class_ref(); - return fill_and_encode_entity_info(text_sensor, msg, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn, - remaining_size); + return fill_and_encode_entity_info_with_device_class( + text_sensor, msg, msg.device_class, ListEntitiesTextSensorResponse::MESSAGE_TYPE, conn, remaining_size); } #endif @@ -775,11 +773,11 @@ uint16_t APIConnection::try_send_number_info(EntityBase *entity, APIConnection * ListEntitiesNumberResponse msg; msg.unit_of_measurement = number->get_unit_of_measurement_ref(); msg.mode = static_cast(number->traits.get_mode()); - msg.device_class = number->get_device_class_ref(); msg.min_value = number->traits.get_min_value(); msg.max_value = number->traits.get_max_value(); msg.step = number->traits.get_step(); - return fill_and_encode_entity_info(number, msg, ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size); + return fill_and_encode_entity_info_with_device_class(number, msg, msg.device_class, + ListEntitiesNumberResponse::MESSAGE_TYPE, conn, remaining_size); } void APIConnection::on_number_command_request(const NumberCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(number::Number, number, number) @@ -924,8 +922,8 @@ void APIConnection::on_select_command_request(const SelectCommandRequest &msg) { uint16_t APIConnection::try_send_button_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) { auto *button = static_cast(entity); ListEntitiesButtonResponse msg; - msg.device_class = button->get_device_class_ref(); - return fill_and_encode_entity_info(button, msg, ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size); + return fill_and_encode_entity_info_with_device_class(button, msg, msg.device_class, + ListEntitiesButtonResponse::MESSAGE_TYPE, conn, remaining_size); } void esphome::api::APIConnection::on_button_command_request(const ButtonCommandRequest &msg) { ENTITY_COMMAND_GET(button::Button, button, button) @@ -985,11 +983,11 @@ uint16_t APIConnection::try_send_valve_info(EntityBase *entity, APIConnection *c auto *valve = static_cast(entity); ListEntitiesValveResponse msg; auto traits = valve->get_traits(); - msg.device_class = valve->get_device_class_ref(); msg.assumed_state = traits.get_is_assumed_state(); msg.supports_position = traits.get_supports_position(); msg.supports_stop = traits.get_supports_stop(); - return fill_and_encode_entity_info(valve, msg, ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size); + return fill_and_encode_entity_info_with_device_class(valve, msg, msg.device_class, + ListEntitiesValveResponse::MESSAGE_TYPE, conn, remaining_size); } void APIConnection::on_valve_command_request(const ValveCommandRequest &msg) { ENTITY_COMMAND_MAKE_CALL(valve::Valve, valve, valve) @@ -1433,9 +1431,9 @@ uint16_t APIConnection::try_send_event_response(event::Event *event, StringRef e uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) { auto *event = static_cast(entity); ListEntitiesEventResponse msg; - msg.device_class = event->get_device_class_ref(); msg.event_types = &event->get_event_types(); - return fill_and_encode_entity_info(event, msg, ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size); + return fill_and_encode_entity_info_with_device_class(event, msg, msg.device_class, + ListEntitiesEventResponse::MESSAGE_TYPE, conn, remaining_size); } #endif @@ -1491,8 +1489,8 @@ uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection uint16_t APIConnection::try_send_update_info(EntityBase *entity, APIConnection *conn, uint32_t remaining_size) { auto *update = static_cast(entity); ListEntitiesUpdateResponse msg; - msg.device_class = update->get_device_class_ref(); - return fill_and_encode_entity_info(update, msg, ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size); + return fill_and_encode_entity_info_with_device_class(update, msg, msg.device_class, + ListEntitiesUpdateResponse::MESSAGE_TYPE, conn, remaining_size); } void APIConnection::on_update_command_request(const UpdateCommandRequest &msg) { ENTITY_COMMAND_GET(update::UpdateEntity, update, update) diff --git a/esphome/components/api/api_connection.h b/esphome/components/api/api_connection.h index 7f2bce757f..41b3dfb717 100644 --- a/esphome/components/api/api_connection.h +++ b/esphome/components/api/api_connection.h @@ -359,6 +359,15 @@ class APIConnection final : public APIServerConnectionBase { return encode_message_to_buffer(msg, message_type, conn, remaining_size); } + // Wrapper for entity types that have a device_class field + static uint16_t fill_and_encode_entity_info_with_device_class(EntityBase *entity, InfoResponseProtoMessage &msg, + StringRef &device_class_field, uint8_t message_type, + APIConnection *conn, uint32_t remaining_size) { + char dc_buf[MAX_DEVICE_CLASS_LENGTH]; + device_class_field = StringRef(entity->get_device_class_to(dc_buf)); + return fill_and_encode_entity_info(entity, msg, message_type, conn, remaining_size); + } + #ifdef USE_VOICE_ASSISTANT // Helper to check voice assistant validity and connection ownership inline bool check_voice_assistant_api_connection_() const; diff --git a/esphome/components/mqtt/mqtt_binary_sensor.cpp b/esphome/components/mqtt/mqtt_binary_sensor.cpp index 75995f61e0..f64f269663 100644 --- a/esphome/components/mqtt/mqtt_binary_sensor.cpp +++ b/esphome/components/mqtt/mqtt_binary_sensor.cpp @@ -29,12 +29,6 @@ MQTTBinarySensorComponent::MQTTBinarySensorComponent(binary_sensor::BinarySensor } void MQTTBinarySensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - const auto device_class = this->binary_sensor_->get_device_class_ref(); - if (!device_class.empty()) { - root[MQTT_DEVICE_CLASS] = device_class; - } - // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) if (this->binary_sensor_->is_status_binary_sensor()) root[MQTT_PAYLOAD_ON] = mqtt::global_mqtt_client->get_availability().payload_available; if (this->binary_sensor_->is_status_binary_sensor()) diff --git a/esphome/components/mqtt/mqtt_button.cpp b/esphome/components/mqtt/mqtt_button.cpp index 718fe93016..7e0ae7d06e 100644 --- a/esphome/components/mqtt/mqtt_button.cpp +++ b/esphome/components/mqtt/mqtt_button.cpp @@ -30,13 +30,7 @@ void MQTTButtonComponent::dump_config() { } void MQTTButtonComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson config.state_topic = false; - const auto device_class = this->button_->get_device_class_ref(); - if (!device_class.empty()) { - root[MQTT_DEVICE_CLASS] = device_class; - } - // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) } MQTT_COMPONENT_TYPE(MQTTButtonComponent, "button") diff --git a/esphome/components/mqtt/mqtt_component.cpp b/esphome/components/mqtt/mqtt_component.cpp index 98fa10def9..1a30b2c77b 100644 --- a/esphome/components/mqtt/mqtt_component.cpp +++ b/esphome/components/mqtt/mqtt_component.cpp @@ -214,6 +214,11 @@ bool MQTTComponent::send_discovery_() { if (icon[0] != '\0') { root[MQTT_ICON] = icon; } + char dc_buf[MAX_DEVICE_CLASS_LENGTH]; + const char *dc = this->get_entity()->get_device_class_to(dc_buf); + if (dc[0] != '\0') { + root[MQTT_DEVICE_CLASS] = dc; + } const auto entity_category = this->get_entity()->get_entity_category(); if (entity_category != ENTITY_CATEGORY_NONE) { diff --git a/esphome/components/mqtt/mqtt_cover.cpp b/esphome/components/mqtt/mqtt_cover.cpp index 9752004094..59422116f6 100644 --- a/esphome/components/mqtt/mqtt_cover.cpp +++ b/esphome/components/mqtt/mqtt_cover.cpp @@ -90,13 +90,6 @@ void MQTTCoverComponent::dump_config() { } } void MQTTCoverComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - const auto device_class = this->cover_->get_device_class_ref(); - if (!device_class.empty()) { - root[MQTT_DEVICE_CLASS] = device_class; - } - // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) - auto traits = this->cover_->get_traits(); if (traits.get_is_assumed_state()) { root[MQTT_OPTIMISTIC] = true; diff --git a/esphome/components/mqtt/mqtt_event.cpp b/esphome/components/mqtt/mqtt_event.cpp index 37d5c2551a..93ff6971b3 100644 --- a/esphome/components/mqtt/mqtt_event.cpp +++ b/esphome/components/mqtt/mqtt_event.cpp @@ -20,13 +20,6 @@ void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConf for (const auto &event_type : this->event_->get_event_types()) event_types.add(event_type); - // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - const auto device_class = this->event_->get_device_class_ref(); - if (!device_class.empty()) { - root[MQTT_DEVICE_CLASS] = device_class; - } - // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) - config.command_topic = false; } diff --git a/esphome/components/mqtt/mqtt_number.cpp b/esphome/components/mqtt/mqtt_number.cpp index a2734f2beb..b0bac8b3d7 100644 --- a/esphome/components/mqtt/mqtt_number.cpp +++ b/esphome/components/mqtt/mqtt_number.cpp @@ -57,10 +57,6 @@ void MQTTNumberComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCon root[MQTT_MODE] = NumberMqttModeStrings::get_progmem_str(static_cast(mode), static_cast(NUMBER_MODE_BOX)); } - const auto device_class = this->number_->get_device_class_ref(); - if (!device_class.empty()) { - root[MQTT_DEVICE_CLASS] = device_class; - } // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) config.command_topic = true; diff --git a/esphome/components/mqtt/mqtt_sensor.cpp b/esphome/components/mqtt/mqtt_sensor.cpp index a7d311d194..c66465dd16 100644 --- a/esphome/components/mqtt/mqtt_sensor.cpp +++ b/esphome/components/mqtt/mqtt_sensor.cpp @@ -44,11 +44,6 @@ void MQTTSensorComponent::disable_expire_after() { this->expire_after_ = 0; } void MQTTSensorComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - const auto device_class = this->sensor_->get_device_class_ref(); - if (!device_class.empty()) { - root[MQTT_DEVICE_CLASS] = device_class; - } - if (this->sensor_->has_accuracy_decimals()) { root[MQTT_SUGGESTED_DISPLAY_PRECISION] = this->sensor_->get_accuracy_decimals(); } diff --git a/esphome/components/mqtt/mqtt_text_sensor.cpp b/esphome/components/mqtt/mqtt_text_sensor.cpp index a6b9f90b68..3acd71b50d 100644 --- a/esphome/components/mqtt/mqtt_text_sensor.cpp +++ b/esphome/components/mqtt/mqtt_text_sensor.cpp @@ -14,12 +14,6 @@ using namespace esphome::text_sensor; MQTTTextSensor::MQTTTextSensor(TextSensor *sensor) : sensor_(sensor) {} void MQTTTextSensor::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - const auto device_class = this->sensor_->get_device_class_ref(); - if (!device_class.empty()) { - root[MQTT_DEVICE_CLASS] = device_class; - } - // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) config.command_topic = false; } void MQTTTextSensor::setup() { diff --git a/esphome/components/mqtt/mqtt_valve.cpp b/esphome/components/mqtt/mqtt_valve.cpp index 2b9f02858b..47b06259ac 100644 --- a/esphome/components/mqtt/mqtt_valve.cpp +++ b/esphome/components/mqtt/mqtt_valve.cpp @@ -63,13 +63,6 @@ void MQTTValveComponent::dump_config() { } } void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) { - // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) false positive with ArduinoJson - const auto device_class = this->valve_->get_device_class_ref(); - if (!device_class.empty()) { - root[MQTT_DEVICE_CLASS] = device_class; - } - // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) - auto traits = this->valve_->get_traits(); if (traits.get_is_assumed_state()) { root[MQTT_OPTIMISTIC] = true; diff --git a/esphome/components/web_server/web_server.cpp b/esphome/components/web_server/web_server.cpp index bc90c88e57..5590e67b82 100644 --- a/esphome/components/web_server/web_server.cpp +++ b/esphome/components/web_server/web_server.cpp @@ -2137,7 +2137,8 @@ json::SerializationBuffer<> WebServer::event_json_(event::Event *obj, StringRef for (const char *event_type : obj->get_event_types()) { event_types.add(event_type); } - root[ESPHOME_F("device_class")] = obj->get_device_class_ref(); + char dc_buf[MAX_DEVICE_CLASS_LENGTH]; + root[ESPHOME_F("device_class")] = obj->get_device_class_to(dc_buf); this->add_sorting_info_(root, obj); } diff --git a/esphome/core/entity_base.cpp b/esphome/core/entity_base.cpp index 1265277572..4eb016aa8a 100644 --- a/esphome/core/entity_base.cpp +++ b/esphome/core/entity_base.cpp @@ -51,7 +51,27 @@ __attribute__((weak)) const char *entity_device_class_lookup(uint8_t) { return " __attribute__((weak)) const char *entity_uom_lookup(uint8_t) { return ""; } __attribute__((weak)) const char *entity_icon_lookup(uint8_t) { return ""; } -// Entity device class (from index) +// Entity device class — buffer-based API for PROGMEM safety on ESP8266 +const char *EntityBase::get_device_class_to([[maybe_unused]] std::span buffer) const { +#ifdef USE_ENTITY_DEVICE_CLASS + const uint8_t idx = this->device_class_idx_; +#else + const uint8_t idx = 0; +#endif +#ifdef USE_ESP8266 + if (idx == 0) + return ""; + const char *dc = entity_device_class_lookup(idx); + ESPHOME_strncpy_P(buffer.data(), dc, buffer.size() - 1); + buffer[buffer.size() - 1] = '\0'; + return buffer.data(); +#else + return entity_device_class_lookup(idx); +#endif +} + +#ifndef USE_ESP8266 +// Deprecated device class accessors — not available on ESP8266 (rodata is RAM) StringRef EntityBase::get_device_class_ref() const { #ifdef USE_ENTITY_DEVICE_CLASS return StringRef(entity_device_class_lookup(this->device_class_idx_)); @@ -59,7 +79,14 @@ StringRef EntityBase::get_device_class_ref() const { return StringRef(entity_device_class_lookup(0)); #endif } -std::string EntityBase::get_device_class() const { return std::string(this->get_device_class_ref().c_str()); } +std::string EntityBase::get_device_class() const { +#ifdef USE_ENTITY_DEVICE_CLASS + return std::string(entity_device_class_lookup(this->device_class_idx_)); +#else + return std::string(entity_device_class_lookup(0)); +#endif +} +#endif // !USE_ESP8266 // Entity unit of measurement (from index) StringRef EntityBase::get_unit_of_measurement_ref() const { @@ -191,8 +218,10 @@ void log_entity_icon(const char *tag, const char *prefix, const EntityBase &obj) #endif void log_entity_device_class(const char *tag, const char *prefix, const EntityBase &obj) { - if (!obj.get_device_class_ref().empty()) { - ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, obj.get_device_class_ref().c_str()); + char dc_buf[MAX_DEVICE_CLASS_LENGTH]; + const char *dc = obj.get_device_class_to(dc_buf); + if (dc[0] != '\0') { + ESP_LOGCONFIG(tag, "%s Device Class: '%s'", prefix, dc); } } diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index a03d615c02..30073fed15 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -36,6 +36,11 @@ static constexpr size_t OBJECT_ID_MAX_LEN = 128; // Maximum state length that Home Assistant will accept without raising ValueError static constexpr size_t MAX_STATE_LEN = 255; +// Maximum device class string buffer size (47 chars + null terminator) +// Longest standard device class: "volatile_organic_compounds_parts" (32 chars) +// Device classes are stored in PROGMEM; on ESP8266 they must be copied to a stack buffer. +static constexpr size_t MAX_DEVICE_CLASS_LENGTH = 48; + // Maximum icon string buffer size (63 chars + null terminator) // Icons are stored in PROGMEM; on ESP8266 they must be copied to a stack buffer. static constexpr size_t MAX_ICON_LENGTH = 64; @@ -113,13 +118,31 @@ class EntityBase { #endif } - // Get device class as StringRef (from packed index) + // Get this entity's device class into a stack buffer. + // On ESP32: returns pointer to PROGMEM string directly (buffer unused). + // On ESP8266: copies from PROGMEM to buffer, returns buffer pointer. + const char *get_device_class_to(std::span buffer) const; + +#ifdef USE_ESP8266 + // On ESP8266, rodata is RAM. Device classes are in PROGMEM and cannot be accessed + // directly as const char*. Use get_device_class_to() with a stack buffer instead. + template StringRef get_device_class_ref() const { + static_assert(sizeof(T) == 0, "get_device_class_ref() unavailable on ESP8266 (rodata is RAM). " + "Use get_device_class_to() with a stack buffer."); + return StringRef(""); + } + template std::string get_device_class() const { + static_assert(sizeof(T) == 0, "get_device_class() unavailable on ESP8266 (rodata is RAM). " + "Use get_device_class_to() with a stack buffer."); + return ""; + } +#else + // Deprecated: use get_device_class_to() instead. Device classes are in PROGMEM. + ESPDEPRECATED("Use get_device_class_to() instead. Will be removed in ESPHome 2026.9.0", "2026.3.0") StringRef get_device_class_ref() const; - /// Get the device class as std::string (deprecated, prefer get_device_class_ref()) - ESPDEPRECATED("Use get_device_class_ref() instead for better performance (avoids string copy). Will be removed in " - "ESPHome 2026.9.0", - "2026.3.0") + ESPDEPRECATED("Use get_device_class_to() instead. Will be removed in ESPHome 2026.9.0", "2026.3.0") std::string get_device_class() const; +#endif // Get unit of measurement as StringRef (from packed index) StringRef get_unit_of_measurement_ref() const; /// Get the unit of measurement as std::string (deprecated, prefer get_unit_of_measurement_ref()) diff --git a/esphome/core/entity_helpers.py b/esphome/core/entity_helpers.py index a8ca2f7432..7f112bf3bf 100644 --- a/esphome/core/entity_helpers.py +++ b/esphome/core/entity_helpers.py @@ -131,7 +131,7 @@ def _generate_category_code( _CATEGORY_CONFIGS = ( - ("ENTITY_DC_TABLE", "entity_device_class_lookup", "device_classes", False), + ("ENTITY_DC_TABLE", "entity_device_class_lookup", "device_classes", True), ("ENTITY_UOM_TABLE", "entity_uom_lookup", "units", False), ("ENTITY_ICON_TABLE", "entity_icon_lookup", "icons", True), )