From bc5fed883e2e888145f6b588186b3ff9ad98126a Mon Sep 17 00:00:00 2001 From: jbouwh Date: Sat, 24 Jun 2023 12:15:45 +0000 Subject: [PATCH 01/17] Set has_entity_name if device_name is set --- .../components/mqtt/alarm_control_panel.py | 3 ++- .../components/mqtt/binary_sensor.py | 10 ++++++++-- homeassistant/components/mqtt/button.py | 3 ++- homeassistant/components/mqtt/camera.py | 3 ++- homeassistant/components/mqtt/climate.py | 3 ++- homeassistant/components/mqtt/cover.py | 3 ++- .../components/mqtt/device_tracker.py | 1 + homeassistant/components/mqtt/fan.py | 3 ++- homeassistant/components/mqtt/humidifier.py | 3 ++- .../components/mqtt/light/schema_basic.py | 3 ++- .../components/mqtt/light/schema_json.py | 3 ++- .../components/mqtt/light/schema_template.py | 3 ++- homeassistant/components/mqtt/lock.py | 3 ++- homeassistant/components/mqtt/mixins.py | 18 ++++++++++++++++- homeassistant/components/mqtt/number.py | 3 ++- homeassistant/components/mqtt/scene.py | 3 ++- homeassistant/components/mqtt/select.py | 3 ++- homeassistant/components/mqtt/sensor.py | 3 ++- homeassistant/components/mqtt/siren.py | 3 ++- homeassistant/components/mqtt/switch.py | 3 ++- homeassistant/components/mqtt/text.py | 3 ++- homeassistant/components/mqtt/update.py | 3 ++- .../components/mqtt/vacuum/schema_legacy.py | 3 ++- .../components/mqtt/vacuum/schema_state.py | 3 ++- homeassistant/components/mqtt/water_heater.py | 3 ++- tests/components/mqtt/test_common.py | 10 +++++----- tests/components/mqtt/test_discovery.py | 20 +++++++++---------- 27 files changed, 84 insertions(+), 41 deletions(-) diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index dbed1c8aa9e8b..df7c55f57b613 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -89,7 +89,7 @@ CONF_COMMAND_TEMPLATE, default=DEFAULT_COMMAND_TEMPLATE ): cv.template, vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string, vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string, vol.Optional(CONF_PAYLOAD_ARM_NIGHT, default=DEFAULT_ARM_NIGHT): cv.string, @@ -136,6 +136,7 @@ async def _async_setup_entity( class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity): """Representation of a MQTT alarm status.""" + _default_name = DEFAULT_NAME _entity_id_format = alarm.ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_ALARM_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index 50af9ef8a555e..8f34625eeda30 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -36,7 +36,12 @@ from . import subscription from .config import MQTT_RO_SCHEMA -from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC, PAYLOAD_NONE +from .const import ( + CONF_ENCODING, + CONF_QOS, + CONF_STATE_TOPIC, + PAYLOAD_NONE, +) from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, @@ -61,7 +66,7 @@ vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None), vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_OFF_DELAY): cv.positive_int, vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, @@ -97,6 +102,7 @@ async def _async_setup_entity( class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): """Representation a binary sensor that is updated by MQTT.""" + _default_name = DEFAULT_NAME _entity_id_format = binary_sensor.ENTITY_ID_FORMAT _expired: bool | None _expire_after: int | None diff --git a/homeassistant/components/mqtt/button.py b/homeassistant/components/mqtt/button.py index 46ecc16d38551..af84e6c91496b 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -35,7 +35,7 @@ vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PAYLOAD_PRESS, default=DEFAULT_PAYLOAD_PRESS): cv.string, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, } @@ -70,6 +70,7 @@ async def _async_setup_entity( class MqttButton(MqttEntity, ButtonEntity): """Representation of a switch that can be toggled using MQTT.""" + _default_name = DEFAULT_NAME _entity_id_format = button.ENTITY_ID_FORMAT def __init__( diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index 75ab25efcfa28..32aa641a734cc 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -41,7 +41,7 @@ PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend( { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_TOPIC): valid_subscribe_topic, vol.Optional(CONF_IMAGE_ENCODING): "b64", } @@ -80,6 +80,7 @@ async def _async_setup_entity( class MqttCamera(MqttEntity, Camera): """representation of a MQTT camera.""" + _default_name = DEFAULT_NAME _entity_id_format: str = camera.ENTITY_ID_FORMAT _attributes_extra_blocked: frozenset[str] = MQTT_CAMERA_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 676e5b50f4944..41c68809fd5d1 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -296,7 +296,7 @@ def valid_humidity_state_configuration(config: ConfigType) -> ConfigType: ): cv.ensure_list, vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, @@ -597,6 +597,7 @@ async def async_set_temperature(self, **kwargs: Any) -> None: class MqttClimate(MqttTemperatureControlEntity, ClimateEntity): """Representation of an MQTT climate device.""" + _default_name = DEFAULT_NAME _entity_id_format = climate.ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_CLIMATE_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index 0b435db0b7a97..b6324efb5e856 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -159,7 +159,7 @@ def validate_options(config: ConfigType) -> ConfigType: vol.Optional(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None), vol.Optional(CONF_GET_POSITION_TOPIC): valid_subscribe_topic, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): vol.Any( cv.string, None @@ -236,6 +236,7 @@ async def _async_setup_entity( class MqttCover(MqttEntity, CoverEntity): """Representation of a cover that can be controlled using MQTT.""" + _default_name = DEFAULT_NAME _entity_id_format: str = cover.ENTITY_ID_FORMAT _attributes_extra_blocked: frozenset[str] = MQTT_COVER_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/device_tracker.py b/homeassistant/components/mqtt/device_tracker.py index a9c4017593c9a..f207783e2f3c8 100644 --- a/homeassistant/components/mqtt/device_tracker.py +++ b/homeassistant/components/mqtt/device_tracker.py @@ -104,6 +104,7 @@ async def _async_setup_entity( class MqttDeviceTracker(MqttEntity, TrackerEntity): """Representation of a device tracker using MQTT.""" + _default_name = None _entity_id_format = device_tracker.ENTITY_ID_FORMAT _value_template: Callable[..., ReceivePayloadType] diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index f5e92d8ecf958..82dd707de215b 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -127,7 +127,7 @@ def valid_preset_mode_configuration(config: ConfigType) -> ConfigType: _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend( { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_DIRECTION_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_DIRECTION_COMMAND_TEMPLATE): cv.template, @@ -215,6 +215,7 @@ async def _async_setup_entity( class MqttFan(MqttEntity, FanEntity): """A MQTT fan component.""" + _default_name = DEFAULT_NAME _entity_id_format = fan.ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_FAN_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index 392a112bcdb79..5df4d9329df7a 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -136,7 +136,7 @@ def valid_humidity_range_configuration(config: ConfigType) -> ConfigType: vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, @@ -207,6 +207,7 @@ async def _async_setup_entity( class MqttHumidifier(MqttEntity, HumidifierEntity): """A MQTT humidifier component.""" + _default_name = DEFAULT_NAME _entity_id_format = humidifier.ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_HUMIDIFIER_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index fe09667ca4aaa..c010f221bb2c8 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -190,7 +190,7 @@ vol.Optional(CONF_HS_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_MAX_MIREDS): cv.positive_int, vol.Optional(CONF_MIN_MIREDS): cv.positive_int, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_ON_COMMAND_TYPE, default=DEFAULT_ON_COMMAND_TYPE): vol.In( VALUES_ON_COMMAND_TYPE ), @@ -242,6 +242,7 @@ async def async_setup_entity_basic( class MqttLight(MqttEntity, LightEntity, RestoreEntity): """Representation of a MQTT light.""" + _default_name = DEFAULT_NAME _entity_id_format = ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED _topic: dict[str, str | None] diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index 70992887ca7f2..cff14e19471aa 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -132,7 +132,7 @@ def valid_color_configuration(config: ConfigType) -> ConfigType: vol.Optional(CONF_HS, default=DEFAULT_HS): cv.boolean, vol.Optional(CONF_MAX_MIREDS): cv.positive_int, vol.Optional(CONF_MIN_MIREDS): cv.positive_int, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_QOS, default=DEFAULT_QOS): vol.All( vol.Coerce(int), vol.In([0, 1, 2]) ), @@ -180,6 +180,7 @@ async def async_setup_entity_json( class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): """Representation of a MQTT JSON light.""" + _default_name = DEFAULT_NAME _entity_id_format = ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index 063895d738c99..ffbb245b25f63 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -100,7 +100,7 @@ vol.Optional(CONF_GREEN_TEMPLATE): cv.template, vol.Optional(CONF_MAX_MIREDS): cv.positive_int, vol.Optional(CONF_MIN_MIREDS): cv.positive_int, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_RED_TEMPLATE): cv.template, vol.Optional(CONF_STATE_TEMPLATE): cv.template, } @@ -128,6 +128,7 @@ async def async_setup_entity_template( class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity): """Representation of a MQTT Template light.""" + _default_name = DEFAULT_NAME _entity_id_format = ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED _optimistic: bool diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index 966cbc211055e..d117422d3996d 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -76,7 +76,7 @@ { vol.Optional(CONF_CODE_FORMAT): cv.is_regex, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK): cv.string, vol.Optional(CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK): cv.string, vol.Optional(CONF_PAYLOAD_OPEN): cv.string, @@ -126,6 +126,7 @@ async def _async_setup_entity( class MqttLock(MqttEntity, LockEntity): """Representation of a lock that can be toggled using MQTT.""" + _default_name = DEFAULT_NAME _entity_id_format = lock.ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_LOCK_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 314800f33f2d6..b88bcb2468b89 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1008,6 +1008,7 @@ class MqttEntity( """Representation of an MQTT entity.""" _attr_should_poll = False + _default_name: str | None _entity_id_format: str def __init__( @@ -1122,7 +1123,22 @@ def _setup_common_attributes_from_config(self, config: ConfigType) -> None: config.get(CONF_ENABLED_BY_DEFAULT) ) self._attr_icon = config.get(CONF_ICON) - self._attr_name = config.get(CONF_NAME) + entity_name: str | None = config.get(CONF_NAME) + if CONF_DEVICE in config: + device_name: str | None = config[CONF_DEVICE].get(CONF_NAME) + if device_name is not None and entity_name is None: + self._attr_name = None + self._attr_has_entity_name = True + return + if device_name is not None and entity_name is not None: + self._attr_name = entity_name + self._attr_has_entity_name = True + return + self._attr_name = self._default_name + self._attr_has_entity_name = False + else: + self._attr_name = entity_name or self._default_name + self._attr_has_entity_name = False def _setup_from_config(self, config: ConfigType) -> None: """(Re)Setup the entity.""" diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index 5986eab1207e6..dbe763ba95446 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -87,7 +87,7 @@ def validate_config(config: ConfigType) -> ConfigType: vol.Optional(CONF_MAX, default=DEFAULT_MAX_VALUE): vol.Coerce(float), vol.Optional(CONF_MIN, default=DEFAULT_MIN_VALUE): vol.Coerce(float), vol.Optional(CONF_MODE, default=NumberMode.AUTO): vol.Coerce(NumberMode), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PAYLOAD_RESET, default=DEFAULT_PAYLOAD_RESET): cv.string, vol.Optional(CONF_STEP, default=DEFAULT_STEP): vol.All( vol.Coerce(float), vol.Range(min=1e-3) @@ -134,6 +134,7 @@ async def _async_setup_entity( class MqttNumber(MqttEntity, RestoreNumber): """representation of an MQTT number.""" + _default_name = DEFAULT_NAME _entity_id_format = number.ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_NUMBER_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/scene.py b/homeassistant/components/mqtt/scene.py index f716e4fe46f8d..5e12f67a698f3 100644 --- a/homeassistant/components/mqtt/scene.py +++ b/homeassistant/components/mqtt/scene.py @@ -34,7 +34,7 @@ { vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PAYLOAD_ON): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, @@ -77,6 +77,7 @@ class MqttScene( ): """Representation of a scene that can be activated using MQTT.""" + _default_name = DEFAULT_NAME _entity_id_format = scene.DOMAIN + ".{}" def __init__( diff --git a/homeassistant/components/mqtt/select.py b/homeassistant/components/mqtt/select.py index 26e72af919239..5a8a24859175c 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -54,7 +54,7 @@ PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Required(CONF_OPTIONS): cv.ensure_list, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, }, @@ -89,6 +89,7 @@ async def _async_setup_entity( class MqttSelect(MqttEntity, SelectEntity, RestoreEntity): """representation of an MQTT select.""" + _default_name = DEFAULT_NAME _entity_id_format = select.ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_SELECT_ATTRIBUTES_BLOCKED _command_template: Callable[[PublishPayloadType], PublishPayloadType] diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index e4b5f61bda0c2..d90b2ff3402fd 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -78,7 +78,7 @@ vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, vol.Optional(CONF_LAST_RESET_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_SUGGESTED_DISPLAY_PRECISION): cv.positive_int, vol.Optional(CONF_STATE_CLASS): vol.Any(STATE_CLASSES_SCHEMA, None), vol.Optional(CONF_UNIT_OF_MEASUREMENT): vol.Any(cv.string, None), @@ -126,6 +126,7 @@ async def _async_setup_entity( class MqttSensor(MqttEntity, RestoreSensor): """Representation of a sensor that can be updated using MQTT.""" + _default_name = DEFAULT_NAME _entity_id_format = ENTITY_ID_FORMAT _attr_last_reset: datetime | None = None _attributes_extra_blocked = MQTT_SENSOR_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index 4134dd9714863..ad1521af4f73c 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -80,7 +80,7 @@ vol.Optional(CONF_AVAILABLE_TONES): cv.ensure_list, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_COMMAND_OFF_TEMPLATE): cv.template, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_STATE_OFF): cv.string, @@ -139,6 +139,7 @@ async def _async_setup_entity( class MqttSiren(MqttEntity, SirenEntity): """Representation of a siren that can be controlled using MQTT.""" + _default_name = DEFAULT_NAME _entity_id_format = ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_SIREN_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index 7f4f609f265ef..5120ad466668e 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -49,7 +49,7 @@ PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend( { - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_STATE_OFF): cv.string, @@ -88,6 +88,7 @@ async def _async_setup_entity( class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): """Representation of a switch that can be toggled using MQTT.""" + _default_name = DEFAULT_NAME _entity_id_format = switch.ENTITY_ID_FORMAT _optimistic: bool diff --git a/homeassistant/components/mqtt/text.py b/homeassistant/components/mqtt/text.py index 01622c10a6de6..41c5c49dd45c5 100644 --- a/homeassistant/components/mqtt/text.py +++ b/homeassistant/components/mqtt/text.py @@ -78,7 +78,7 @@ def valid_text_size_configuration(config: ConfigType) -> ConfigType: _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_MAX, default=MAX_LENGTH_STATE_STATE): cv.positive_int, vol.Optional(CONF_MIN, default=0): cv.positive_int, vol.Optional(CONF_MODE, default=text.TextMode.TEXT): vol.In( @@ -125,6 +125,7 @@ class MqttTextEntity(MqttEntity, TextEntity): """Representation of the MQTT text entity.""" _attributes_extra_blocked = MQTT_TEXT_ATTRIBUTES_BLOCKED + _default_name = DEFAULT_NAME _entity_id_format = text.ENTITY_ID_FORMAT _compiled_pattern: re.Pattern[Any] | None diff --git a/homeassistant/components/mqtt/update.py b/homeassistant/components/mqtt/update.py index 930f4d225063c..b293c865eaca0 100644 --- a/homeassistant/components/mqtt/update.py +++ b/homeassistant/components/mqtt/update.py @@ -57,7 +57,7 @@ vol.Optional(CONF_ENTITY_PICTURE): cv.string, vol.Optional(CONF_LATEST_VERSION_TEMPLATE): cv.template, vol.Optional(CONF_LATEST_VERSION_TOPIC): valid_subscribe_topic, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PAYLOAD_INSTALL): cv.string, vol.Optional(CONF_RELEASE_SUMMARY): cv.string, vol.Optional(CONF_RELEASE_URL): cv.string, @@ -107,6 +107,7 @@ async def _async_setup_entity( class MqttUpdate(MqttEntity, UpdateEntity, RestoreEntity): """Representation of the MQTT update entity.""" + _default_name = DEFAULT_NAME _entity_id_format = update.ENTITY_ID_FORMAT def __init__( diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index 7c73e57911248..ef176c149c98d 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -131,7 +131,7 @@ ), vol.Inclusive(CONF_FAN_SPEED_TEMPLATE, "fan_speed"): cv.template, vol.Inclusive(CONF_FAN_SPEED_TOPIC, "fan_speed"): valid_publish_topic, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional( CONF_PAYLOAD_CLEAN_SPOT, default=DEFAULT_PAYLOAD_CLEAN_SPOT ): cv.string, @@ -215,6 +215,7 @@ async def async_setup_entity_legacy( class MqttVacuum(MqttEntity, VacuumEntity): """Representation of a MQTT-controlled legacy vacuum.""" + _default_name = DEFAULT_NAME _entity_id_format = ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_LEGACY_VACUUM_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index ee06131af0251..5a6c8e78b3d72 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -126,7 +126,7 @@ vol.Optional(CONF_FAN_SPEED_LIST, default=[]): vol.All( cv.ensure_list, [cv.string] ), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional( CONF_PAYLOAD_CLEAN_SPOT, default=DEFAULT_PAYLOAD_CLEAN_SPOT ): cv.string, @@ -170,6 +170,7 @@ async def async_setup_entity_state( class MqttStateVacuum(MqttEntity, StateVacuumEntity): """Representation of a MQTT-controlled state vacuum.""" + _default_name = DEFAULT_NAME _entity_id_format = ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_VACUUM_ATTRIBUTES_BLOCKED diff --git a/homeassistant/components/mqtt/water_heater.py b/homeassistant/components/mqtt/water_heater.py index 0f622d55b84f7..38d540895defa 100644 --- a/homeassistant/components/mqtt/water_heater.py +++ b/homeassistant/components/mqtt/water_heater.py @@ -123,7 +123,7 @@ ): cv.ensure_list, vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, @@ -180,6 +180,7 @@ async def _async_setup_entity( class MqttWaterHeater(MqttTemperatureControlEntity, WaterHeaterEntity): """Representation of an MQTT water heater device.""" + _default_name = DEFAULT_NAME _entity_id_format = water_heater.ENTITY_ID_FORMAT _attributes_extra_blocked = MQTT_WATER_HEATER_ATTRIBUTES_BLOCKED diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index cfd714725c43e..b4f7fd4f842f7 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -1390,7 +1390,7 @@ async def help_test_entity_debug_info_message( with patch("homeassistant.util.dt.utcnow") as dt_utcnow: dt_utcnow.return_value = start_dt if service: - service_data = {ATTR_ENTITY_ID: f"{domain}.test"} + service_data = {ATTR_ENTITY_ID: f"{domain}.beer_test"} if service_parameters: service_data.update(service_parameters) @@ -1458,7 +1458,7 @@ async def help_test_entity_debug_info_remove( "subscriptions" ] assert len(debug_info_data["triggers"]) == 0 - assert debug_info_data["entities"][0]["entity_id"] == f"{domain}.test" + assert debug_info_data["entities"][0]["entity_id"] == f"{domain}.beer_test" entity_id = debug_info_data["entities"][0]["entity_id"] async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", "") @@ -1503,7 +1503,7 @@ async def help_test_entity_debug_info_update_entity_id( == f"homeassistant/{domain}/bla/config" ) assert debug_info_data["entities"][0]["discovery_data"]["payload"] == config - assert debug_info_data["entities"][0]["entity_id"] == f"{domain}.test" + assert debug_info_data["entities"][0]["entity_id"] == f"{domain}.beer_test" assert len(debug_info_data["entities"][0]["subscriptions"]) == 1 assert {"topic": "test-topic", "messages": []} in debug_info_data["entities"][0][ "subscriptions" @@ -1511,7 +1511,7 @@ async def help_test_entity_debug_info_update_entity_id( assert len(debug_info_data["triggers"]) == 0 entity_registry.async_update_entity( - f"{domain}.test", new_entity_id=f"{domain}.milk" + f"{domain}.beer_test", new_entity_id=f"{domain}.milk" ) await hass.async_block_till_done() await hass.async_block_till_done() @@ -1529,7 +1529,7 @@ async def help_test_entity_debug_info_update_entity_id( "subscriptions" ] assert len(debug_info_data["triggers"]) == 0 - assert f"{domain}.test" not in hass.data["mqtt"].debug_info_entities + assert f"{domain}.beer_test" not in hass.data["mqtt"].debug_info_entities async def help_test_entity_disabled_by_default( diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index 62b87bdb791c0..b742d5e8ed72a 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -1474,13 +1474,12 @@ async def test_clear_config_topic_disabled_entity( mqtt_mock = await mqtt_mock_entry() # discover an entity that is not enabled by default config = { - "name": "sbfspot_12345", "state_topic": "homeassistant_test/sensor/sbfspot_0/sbfspot_12345/", "unique_id": "sbfspot_12345", "enabled_by_default": False, "device": { "identifiers": ["sbfspot_12345"], - "name": "sbfspot_12345", + "name": "abc123", "sw_version": "1.0", "connections": [["mac", "12:34:56:AB:CD:EF"]], }, @@ -1512,9 +1511,9 @@ async def test_clear_config_topic_disabled_entity( await hass.async_block_till_done() assert "Platform mqtt does not generate unique IDs" in caplog.text - assert hass.states.get("sensor.sbfspot_12345") is None # disabled - assert hass.states.get("sensor.sbfspot_12345_1") is not None # enabled - assert hass.states.get("sensor.sbfspot_12345_2") is None # not unique + assert hass.states.get("sensor.abc123_sbfspot_12345") is None # disabled + assert hass.states.get("sensor.abc123_sbfspot_12345_1") is not None # enabled + assert hass.states.get("sensor.abc123_sbfspot_12345_2") is None # not unique # Verify device is created device_entry = device_registry.async_get_device( @@ -1603,13 +1602,12 @@ async def test_unique_id_collission_has_priority( """Test the unique_id collision detection has priority over registry disabled items.""" await mqtt_mock_entry() config = { - "name": "sbfspot_12345", "state_topic": "homeassistant_test/sensor/sbfspot_0/sbfspot_12345/", "unique_id": "sbfspot_12345", "enabled_by_default": False, "device": { "identifiers": ["sbfspot_12345"], - "name": "sbfspot_12345", + "name": "abc123", "sw_version": "1.0", "connections": [["mac", "12:34:56:AB:CD:EF"]], }, @@ -1633,13 +1631,13 @@ async def test_unique_id_collission_has_priority( ) await hass.async_block_till_done() - assert hass.states.get("sensor.sbfspot_12345_1") is None # not enabled - assert hass.states.get("sensor.sbfspot_12345_2") is None # not unique + assert hass.states.get("sensor.abc123_sbfspot_12345_1") is None # not enabled + assert hass.states.get("sensor.abc123_sbfspot_12345_2") is None # not unique # Verify the first entity is created - assert entity_registry.async_get("sensor.sbfspot_12345_1") is not None + assert entity_registry.async_get("sensor.abc123_sbfspot_12345_1") is not None # Verify the second entity is not created because it is not unique - assert entity_registry.async_get("sensor.sbfspot_12345_2") is None + assert entity_registry.async_get("sensor.abc123_sbfspot_12345_2") is None @patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) From 28cb5336374cd8db636e88cdebec8e7e4dc6fd55 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Sat, 24 Jun 2023 12:33:43 +0000 Subject: [PATCH 02/17] revert unneeded formatting change --- homeassistant/components/mqtt/binary_sensor.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index 8f34625eeda30..c806cfec65598 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -36,12 +36,7 @@ from . import subscription from .config import MQTT_RO_SCHEMA -from .const import ( - CONF_ENCODING, - CONF_QOS, - CONF_STATE_TOPIC, - PAYLOAD_NONE, -) +from .const import CONF_ENCODING, CONF_QOS, CONF_STATE_TOPIC, PAYLOAD_NONE from .debug_info import log_messages from .mixins import ( MQTT_ENTITY_COMMON_SCHEMA, From 59ddf4ec9e69dfdbd82ca800c22c695173aaed8d Mon Sep 17 00:00:00 2001 From: jbouwh Date: Tue, 27 Jun 2023 15:17:11 +0200 Subject: [PATCH 03/17] Add image platform --- homeassistant/components/mqtt/image.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/image.py b/homeassistant/components/mqtt/image.py index 2764539770daf..c89e565708dc7 100644 --- a/homeassistant/components/mqtt/image.py +++ b/homeassistant/components/mqtt/image.py @@ -61,7 +61,7 @@ def validate_topic_required(config: ConfigType) -> ConfigType: PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend( { vol.Optional(CONF_CONTENT_TYPE): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_NAME): cv.string, vol.Exclusive(CONF_URL_TOPIC, "image_topic"): valid_subscribe_topic, vol.Exclusive(CONF_IMAGE_TOPIC, "image_topic"): valid_subscribe_topic, vol.Optional(CONF_IMAGE_ENCODING): "b64", @@ -102,6 +102,7 @@ async def _async_setup_entity( class MqttImage(MqttEntity, ImageEntity): """representation of a MQTT image.""" + _default_name = DEFAULT_NAME _entity_id_format: str = image.ENTITY_ID_FORMAT _last_image: bytes | None = None _client: httpx.AsyncClient From 401d5b6adcc829ddbf308b33d63cd481e9ad2e07 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Wed, 28 Jun 2023 18:53:04 +0000 Subject: [PATCH 04/17] Follow up comment --- homeassistant/components/mqtt/mixins.py | 34 ++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index b88bcb2468b89..193e0b869d1c2 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -50,7 +50,12 @@ async_track_device_registry_updated_event, async_track_entity_registry_updated_event, ) -from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType +from homeassistant.helpers.typing import ( + UNDEFINED, + ConfigType, + DiscoveryInfoType, + UndefinedType, +) from homeassistant.util.json import json_loads from . import debug_info, subscription @@ -1008,7 +1013,7 @@ class MqttEntity( """Representation of an MQTT entity.""" _attr_should_poll = False - _default_name: str | None + _default_name: str | None | UndefinedType _entity_id_format: str def __init__( @@ -1123,22 +1128,17 @@ def _setup_common_attributes_from_config(self, config: ConfigType) -> None: config.get(CONF_ENABLED_BY_DEFAULT) ) self._attr_icon = config.get(CONF_ICON) - entity_name: str | None = config.get(CONF_NAME) - if CONF_DEVICE in config: - device_name: str | None = config[CONF_DEVICE].get(CONF_NAME) - if device_name is not None and entity_name is None: - self._attr_name = None - self._attr_has_entity_name = True - return - if device_name is not None and entity_name is not None: - self._attr_name = entity_name - self._attr_has_entity_name = True - return + entity_name: str | None | UndefinedType = config.get(CONF_NAME, UNDEFINED) + # Only set _attr_name if it is needed + if entity_name is not UNDEFINED: + self._attr_name = entity_name + elif ( + self._default_name is not UNDEFINED + and not self._default_to_device_class_name() + ): self._attr_name = self._default_name - self._attr_has_entity_name = False - else: - self._attr_name = entity_name or self._default_name - self._attr_has_entity_name = False + if CONF_DEVICE in config: + self._attr_has_entity_name = True def _setup_from_config(self, config: ConfigType) -> None: """(Re)Setup the entity.""" From 55f45ac2c2cc5d061cae79fd6ed0bbb443ac8fcc Mon Sep 17 00:00:00 2001 From: jbouwh Date: Fri, 30 Jun 2023 07:29:17 +0000 Subject: [PATCH 05/17] Don't set `has_entity_name` without device name --- homeassistant/components/mqtt/mixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 193e0b869d1c2..3aa913f38d4f9 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1138,7 +1138,7 @@ def _setup_common_attributes_from_config(self, config: ConfigType) -> None: ): self._attr_name = self._default_name if CONF_DEVICE in config: - self._attr_has_entity_name = True + self._attr_has_entity_name = CONF_NAME in config[CONF_DEVICE] def _setup_from_config(self, config: ConfigType) -> None: """(Re)Setup the entity.""" From 95b6c53215bb0153e82ba923e0509e56a10c9131 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Fri, 30 Jun 2023 10:33:29 +0000 Subject: [PATCH 06/17] Only set has_entity_name if a valid name is set --- homeassistant/components/mqtt/mixins.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 3aa913f38d4f9..a1c116514f461 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1138,7 +1138,8 @@ def _setup_common_attributes_from_config(self, config: ConfigType) -> None: ): self._attr_name = self._default_name if CONF_DEVICE in config: - self._attr_has_entity_name = CONF_NAME in config[CONF_DEVICE] + # Only set has_entity_name if a valid name is set + self._attr_has_entity_name = bool(config[CONF_DEVICE].get(CONF_NAME)) def _setup_from_config(self, config: ConfigType) -> None: """(Re)Setup the entity.""" From 24535822f51114c8b74b0acf45ecfa13d695f79a Mon Sep 17 00:00:00 2001 From: jbouwh Date: Fri, 30 Jun 2023 17:30:24 +0000 Subject: [PATCH 07/17] Follow device_class name and add tests --- homeassistant/components/mqtt/mixins.py | 13 +++++-- .../mqtt/test_alarm_control_panel.py | 19 +++++++++ tests/components/mqtt/test_binary_sensor.py | 19 +++++++++ tests/components/mqtt/test_button.py | 24 ++++++++++++ tests/components/mqtt/test_common.py | 39 +++++++++++++++++++ tests/components/mqtt/test_number.py | 19 +++++++++ tests/components/mqtt/test_sensor.py | 19 +++++++++ 7 files changed, 148 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index a1c116514f461..2ade009911c96 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1030,8 +1030,8 @@ def __init__( self._sub_state: dict[str, EntitySubscription] = {} # Load config - self._setup_common_attributes_from_config(self._config) self._setup_from_config(self._config) + self._setup_common_attributes_from_config(self._config) # Initialize entity_id from config self._init_entity_id() @@ -1072,8 +1072,8 @@ async def discovery_update(self, discovery_payload: MQTTDiscoveryPayload) -> Non async_handle_schema_error(discovery_payload, err) return self._config = config - self._setup_common_attributes_from_config(self._config) self._setup_from_config(self._config) + self._setup_common_attributes_from_config(self._config) # Prepare MQTT subscriptions self.attributes_prepare_discovery_update(config) @@ -1133,10 +1133,15 @@ def _setup_common_attributes_from_config(self, config: ConfigType) -> None: if entity_name is not UNDEFINED: self._attr_name = entity_name elif ( - self._default_name is not UNDEFINED - and not self._default_to_device_class_name() + not (use_device_class_name := self._default_to_device_class_name()) + and self._default_name is not UNDEFINED ): + # Assign the default name self._attr_name = self._default_name + elif use_device_class_name: + # Follow name of device class + self._attr_has_entity_name = True + return if CONF_DEVICE in config: # Only set has_entity_name if a valid name is set self._attr_has_entity_name = bool(config[CONF_DEVICE].get(CONF_NAME)) diff --git a/tests/components/mqtt/test_alarm_control_panel.py b/tests/components/mqtt/test_alarm_control_panel.py index d1b1d6b68b38a..e69839e6b1631 100644 --- a/tests/components/mqtt/test_alarm_control_panel.py +++ b/tests/components/mqtt/test_alarm_control_panel.py @@ -55,6 +55,7 @@ help_test_entity_device_info_with_identifier, help_test_entity_id_update_discovery_update, help_test_entity_id_update_subscriptions, + help_test_entity_name, help_test_publishing_with_custom_encoding, help_test_reloadable, help_test_setting_attribute_via_mqtt_json_message, @@ -1120,3 +1121,21 @@ async def test_unload_entry( await help_test_unload_config_entry_with_platform( hass, mqtt_mock_entry, domain, config ) + + +@pytest.mark.parametrize( + ("expected_friendly_name", "device_class"), + [("test", None)], +) +async def test_entity_name( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + expected_friendly_name: str | None, + device_class: str | None, +) -> None: + """Test the entity name setup.""" + domain = alarm_control_panel.DOMAIN + config = DEFAULT_CONFIG + await help_test_entity_name( + hass, mqtt_mock_entry, domain, config, expected_friendly_name, device_class + ) diff --git a/tests/components/mqtt/test_binary_sensor.py b/tests/components/mqtt/test_binary_sensor.py index d32754625f423..28bf5f558cbcf 100644 --- a/tests/components/mqtt/test_binary_sensor.py +++ b/tests/components/mqtt/test_binary_sensor.py @@ -41,6 +41,7 @@ help_test_entity_device_info_with_identifier, help_test_entity_id_update_discovery_update, help_test_entity_id_update_subscriptions, + help_test_entity_name, help_test_reload_with_config, help_test_reloadable, help_test_setting_attribute_via_mqtt_json_message, @@ -1227,3 +1228,21 @@ async def test_unload_entry( await help_test_unload_config_entry_with_platform( hass, mqtt_mock_entry, domain, config ) + + +@pytest.mark.parametrize( + ("expected_friendly_name", "device_class"), + [("test", None), ("Door", "door"), ("Battery", "battery"), ("Motion", "motion")], +) +async def test_entity_name( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + expected_friendly_name: str | None, + device_class: str | None, +) -> None: + """Test the entity name setup.""" + domain = binary_sensor.DOMAIN + config = DEFAULT_CONFIG + await help_test_entity_name( + hass, mqtt_mock_entry, domain, config, expected_friendly_name, device_class + ) diff --git a/tests/components/mqtt/test_button.py b/tests/components/mqtt/test_button.py index fa16ef7781780..481e98f00997e 100644 --- a/tests/components/mqtt/test_button.py +++ b/tests/components/mqtt/test_button.py @@ -30,6 +30,7 @@ help_test_entity_device_info_with_connection, help_test_entity_device_info_with_identifier, help_test_entity_id_update_discovery_update, + help_test_entity_name, help_test_publishing_with_custom_encoding, help_test_reloadable, help_test_setting_attribute_via_mqtt_json_message, @@ -569,3 +570,26 @@ async def test_unload_entry( await help_test_unload_config_entry_with_platform( hass, mqtt_mock_entry, domain, config ) + + +@pytest.mark.parametrize( + ("expected_friendly_name", "device_class"), + [ + ("test", None), + ("Update", "update"), + ("Identify", "identify"), + ("Restart", "restart"), + ], +) +async def test_entity_name( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + expected_friendly_name: str | None, + device_class: str | None, +) -> None: + """Test the entity name setup.""" + domain = button.DOMAIN + config = DEFAULT_CONFIG + await help_test_entity_name( + hass, mqtt_mock_entry, domain, config, expected_friendly_name, device_class + ) diff --git a/tests/components/mqtt/test_common.py b/tests/components/mqtt/test_common.py index b4f7fd4f842f7..9eba5266b7163 100644 --- a/tests/components/mqtt/test_common.py +++ b/tests/components/mqtt/test_common.py @@ -1117,6 +1117,45 @@ async def help_test_entity_device_info_update( assert device.name == "Milk" +async def help_test_entity_name( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + domain: str, + config: ConfigType, + expected_friendly_name: str | None = None, + device_class: str | None = None, +) -> None: + """Test device name setup with and without a device_class set. + + This is a test helper for the _setup_common_attributes_from_config mixin. + """ + await mqtt_mock_entry() + # Add device settings to config + config = copy.deepcopy(config[mqtt.DOMAIN][domain]) + config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID) + config["unique_id"] = "veryunique" + expected_entity_name = "test" + if device_class is not None: + config["device_class"] = device_class + # Do not set a name + config.pop("name") + expected_entity_name = device_class + + registry = dr.async_get(hass) + + data = json.dumps(config) + async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data) + await hass.async_block_till_done() + + device = registry.async_get_device({("mqtt", "helloworld")}) + assert device is not None + + entity_id = f"{domain}.beer_{expected_entity_name}" + state = hass.states.get(entity_id) + assert state is not None + assert state.name == f"Beer {expected_friendly_name}" + + async def help_test_entity_id_update_subscriptions( hass: HomeAssistant, mqtt_mock_entry: MqttMockHAClientGenerator, diff --git a/tests/components/mqtt/test_number.py b/tests/components/mqtt/test_number.py index 96d9cdcef6417..dbdd373a659d8 100644 --- a/tests/components/mqtt/test_number.py +++ b/tests/components/mqtt/test_number.py @@ -48,6 +48,7 @@ help_test_entity_device_info_with_identifier, help_test_entity_id_update_discovery_update, help_test_entity_id_update_subscriptions, + help_test_entity_name, help_test_publishing_with_custom_encoding, help_test_reloadable, help_test_setting_attribute_via_mqtt_json_message, @@ -1121,3 +1122,21 @@ async def test_unload_entry( await help_test_unload_config_entry_with_platform( hass, mqtt_mock_entry, domain, config ) + + +@pytest.mark.parametrize( + ("expected_friendly_name", "device_class"), + [("test", None), ("Humidity", "humidity"), ("Temperature", "temperature")], +) +async def test_entity_name( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + expected_friendly_name: str | None, + device_class: str | None, +) -> None: + """Test the entity name setup.""" + domain = number.DOMAIN + config = DEFAULT_CONFIG + await help_test_entity_name( + hass, mqtt_mock_entry, domain, config, expected_friendly_name, device_class + ) diff --git a/tests/components/mqtt/test_sensor.py b/tests/components/mqtt/test_sensor.py index d6ab692af5274..30eb0fd193971 100644 --- a/tests/components/mqtt/test_sensor.py +++ b/tests/components/mqtt/test_sensor.py @@ -53,6 +53,7 @@ help_test_entity_disabled_by_default, help_test_entity_id_update_discovery_update, help_test_entity_id_update_subscriptions, + help_test_entity_name, help_test_reload_with_config, help_test_reloadable, help_test_setting_attribute_via_mqtt_json_message, @@ -1409,3 +1410,21 @@ async def test_unload_entry( await help_test_unload_config_entry_with_platform( hass, mqtt_mock_entry, domain, config ) + + +@pytest.mark.parametrize( + ("expected_friendly_name", "device_class"), + [("test", None), ("Humidity", "humidity"), ("Temperature", "temperature")], +) +async def test_entity_name( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + expected_friendly_name: str | None, + device_class: str | None, +) -> None: + """Test the entity name setup.""" + domain = sensor.DOMAIN + config = DEFAULT_CONFIG + await help_test_entity_name( + hass, mqtt_mock_entry, domain, config, expected_friendly_name, device_class + ) From 11cd886b78a89954673435fefc7b87c7271a60c5 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Fri, 30 Jun 2023 22:44:51 +0000 Subject: [PATCH 08/17] Follow up comments add extra tests --- homeassistant/components/mqtt/mixins.py | 21 +++- tests/components/mqtt/test_mixins.py | 142 ++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 2ade009911c96..782a2af8be82c 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1141,10 +1141,27 @@ def _setup_common_attributes_from_config(self, config: ConfigType) -> None: elif use_device_class_name: # Follow name of device class self._attr_has_entity_name = True + if CONF_DEVICE in config and CONF_NAME not in config[CONF_DEVICE]: + _LOGGER.info( + "No device name set in config: %s," + "'has_entity_name' was set to True, please set a device name, see also " + "https://developers.home-assistant.io/docs/core/entity/#entity-naming", + config, + ) return - if CONF_DEVICE in config: + if (device_config := CONF_DEVICE in config) and CONF_NAME in config[ + CONF_DEVICE + ]: # Only set has_entity_name if a valid name is set - self._attr_has_entity_name = bool(config[CONF_DEVICE].get(CONF_NAME)) + self._attr_has_entity_name = True + elif device_config: + self._attr_has_entity_name = False + _LOGGER.info( + "No device name set in config: %s," + "'has_entity_name' was set to False, this is not recommended, see also " + "https://developers.home-assistant.io/docs/core/entity/#entity-naming", + config, + ) def _setup_from_config(self, config: ConfigType) -> None: """(Re)Setup the entity.""" diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index c7285f0fa5f49..ffacfca616609 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -5,8 +5,12 @@ import pytest from homeassistant.components import mqtt, sensor +from homeassistant.components.mqtt.sensor import DEFAULT_NAME as DEFAULT_SENSOR_NAME from homeassistant.const import EVENT_STATE_CHANGED, Platform from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import ( + device_registry as dr, +) from tests.common import async_fire_mqtt_message from tests.typing import MqttMockHAClientGenerator @@ -73,3 +77,141 @@ def test_callback(event) -> None: # The availability is changed but the topic is shared, # hence there the state will be written when the value is updated assert len(events) == 1 + + +@pytest.mark.parametrize( + ("hass_config", "entity_id", "friendly_name", "device_name", "assert_log"), + [ + ( # default_entity_name_without_device_name + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "state_topic": "test-topic", + "unique_id": "veryunique", + "device": {"identifiers": ["helloworld"]}, + } + } + }, + "sensor.mqtt_sensor", + DEFAULT_SENSOR_NAME, + None, + True, + ), + ( # default_entity_name_with_device_name + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "state_topic": "test-topic", + "unique_id": "veryunique", + "device": {"name": "Test", "identifiers": ["helloworld"]}, + } + } + }, + "sensor.test_mqtt_sensor", + "Test MQTT Sensor", + "Test", + False, + ), + ( # name_follows_device_class + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "state_topic": "test-topic", + "unique_id": "veryunique", + "device_class": "humidity", + "device": {"name": "Test", "identifiers": ["helloworld"]}, + } + } + }, + "sensor.test_humidity", + "Test Humidity", + "Test", + False, + ), + ( # name_follows_device_class_without_device_name + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "state_topic": "test-topic", + "unique_id": "veryunique", + "device_class": "humidity", + "device": {"identifiers": ["helloworld"]}, + } + } + }, + "sensor.none_humidity", + "None Humidity", + None, + True, + ), + ( # name_overrides_device_class + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "name": "MySensor", + "state_topic": "test-topic", + "unique_id": "veryunique", + "device_class": "humidity", + "device": {"name": "Test", "identifiers": ["helloworld"]}, + } + } + }, + "sensor.test_mysensor", + "Test MySensor", + "Test", + False, + ), + ( # name_set_no_device_name_set + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "name": "MySensor", + "state_topic": "test-topic", + "unique_id": "veryunique", + "device_class": "humidity", + "device": {"identifiers": ["helloworld"]}, + } + } + }, + "sensor.mysensor", + "MySensor", + None, + True, + ), + ], + ids=[ + "default_entity_name_without_device_name", + "default_entity_name_with_device_name", + "name_follows_device_class", + "name_follows_device_class_without_device_name", + "name_overrides_device_class", + "name_set_no_device_name_set", + ], +) +@patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) +async def test_default_entity_and_device_name( + hass: HomeAssistant, + mqtt_mock_entry: MqttMockHAClientGenerator, + caplog: pytest.LogCaptureFixture, + entity_id: str, + friendly_name: str, + device_name: str | None, + assert_log: bool, +) -> None: + """Test device name setup with and without a device_class set. + + This is a test helper for the _setup_common_attributes_from_config mixin. + """ + await mqtt_mock_entry() + + registry = dr.async_get(hass) + + device = registry.async_get_device({("mqtt", "helloworld")}) + assert device is not None + assert device.name == device_name + + state = hass.states.get(entity_id) + assert state is not None + assert state.name == friendly_name + + assert ("No device name set in config" in caplog.text) is assert_log From 335eebf535c0033569ee80ce976d6a997c9f31a2 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Mon, 3 Jul 2023 13:05:19 +0000 Subject: [PATCH 09/17] Move to helper - Log a warning --- homeassistant/components/mqtt/mixins.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 782a2af8be82c..c6264dee17a67 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1121,13 +1121,8 @@ async def async_publish( def config_schema() -> vol.Schema: """Return the config schema.""" - def _setup_common_attributes_from_config(self, config: ConfigType) -> None: - """(Re)Setup the common attributes for the entity.""" - self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) - self._attr_entity_registry_enabled_default = bool( - config.get(CONF_ENABLED_BY_DEFAULT) - ) - self._attr_icon = config.get(CONF_ICON) + def _set_entity_name(self, config: ConfigType) -> None: + """Help setting the entity name if needed.""" entity_name: str | None | UndefinedType = config.get(CONF_NAME, UNDEFINED) # Only set _attr_name if it is needed if entity_name is not UNDEFINED: @@ -1142,7 +1137,7 @@ def _setup_common_attributes_from_config(self, config: ConfigType) -> None: # Follow name of device class self._attr_has_entity_name = True if CONF_DEVICE in config and CONF_NAME not in config[CONF_DEVICE]: - _LOGGER.info( + _LOGGER.warning( "No device name set in config: %s," "'has_entity_name' was set to True, please set a device name, see also " "https://developers.home-assistant.io/docs/core/entity/#entity-naming", @@ -1156,13 +1151,23 @@ def _setup_common_attributes_from_config(self, config: ConfigType) -> None: self._attr_has_entity_name = True elif device_config: self._attr_has_entity_name = False - _LOGGER.info( + _LOGGER.warning( "No device name set in config: %s," "'has_entity_name' was set to False, this is not recommended, see also " "https://developers.home-assistant.io/docs/core/entity/#entity-naming", config, ) + def _setup_common_attributes_from_config(self, config: ConfigType) -> None: + """(Re)Setup the common attributes for the entity.""" + self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) + self._attr_entity_registry_enabled_default = bool( + config.get(CONF_ENABLED_BY_DEFAULT) + ) + self._attr_icon = config.get(CONF_ICON) + # Set the entity name if needed + self._set_entity_name(config) + def _setup_from_config(self, config: ConfigType) -> None: """(Re)Setup the entity.""" From 97faaca11342b9fc021d79fc6076801b92c721d3 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Tue, 11 Jul 2023 10:41:07 +0000 Subject: [PATCH 10/17] fix test --- tests/components/mqtt/test_mixins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index ffacfca616609..46f0e06a6be2a 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -140,7 +140,7 @@ def test_callback(event) -> None: } }, "sensor.none_humidity", - "None Humidity", + "Humidity", None, True, ), From 7cfd69acb3e7a4bcff5330e645766e73bf16983b Mon Sep 17 00:00:00 2001 From: jbouwh Date: Tue, 18 Jul 2023 11:40:22 +0000 Subject: [PATCH 11/17] Allow to assign None as name explictly --- .../components/mqtt/alarm_control_panel.py | 2 +- .../components/mqtt/binary_sensor.py | 2 +- homeassistant/components/mqtt/button.py | 2 +- homeassistant/components/mqtt/camera.py | 2 +- homeassistant/components/mqtt/climate.py | 2 +- homeassistant/components/mqtt/cover.py | 2 +- .../components/mqtt/device_tracker.py | 2 +- homeassistant/components/mqtt/fan.py | 2 +- homeassistant/components/mqtt/humidifier.py | 2 +- homeassistant/components/mqtt/image.py | 2 +- .../components/mqtt/light/schema_basic.py | 2 +- .../components/mqtt/light/schema_json.py | 2 +- .../components/mqtt/light/schema_template.py | 2 +- homeassistant/components/mqtt/lock.py | 2 +- homeassistant/components/mqtt/mixins.py | 9 ++--- homeassistant/components/mqtt/number.py | 2 +- homeassistant/components/mqtt/scene.py | 2 +- homeassistant/components/mqtt/select.py | 2 +- homeassistant/components/mqtt/sensor.py | 2 +- homeassistant/components/mqtt/siren.py | 2 +- homeassistant/components/mqtt/switch.py | 2 +- homeassistant/components/mqtt/text.py | 2 +- homeassistant/components/mqtt/update.py | 2 +- .../components/mqtt/vacuum/schema_legacy.py | 2 +- .../components/mqtt/vacuum/schema_state.py | 2 +- homeassistant/components/mqtt/water_heater.py | 2 +- tests/components/mqtt/test_mixins.py | 36 +++++++++++++++++++ 27 files changed, 64 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/mqtt/alarm_control_panel.py b/homeassistant/components/mqtt/alarm_control_panel.py index df7c55f57b613..06f91403057b0 100644 --- a/homeassistant/components/mqtt/alarm_control_panel.py +++ b/homeassistant/components/mqtt/alarm_control_panel.py @@ -89,7 +89,7 @@ CONF_COMMAND_TEMPLATE, default=DEFAULT_COMMAND_TEMPLATE ): cv.template, vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_PAYLOAD_ARM_AWAY, default=DEFAULT_ARM_AWAY): cv.string, vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): cv.string, vol.Optional(CONF_PAYLOAD_ARM_NIGHT, default=DEFAULT_ARM_NIGHT): cv.string, diff --git a/homeassistant/components/mqtt/binary_sensor.py b/homeassistant/components/mqtt/binary_sensor.py index c806cfec65598..0d4b2c4a7b457 100644 --- a/homeassistant/components/mqtt/binary_sensor.py +++ b/homeassistant/components/mqtt/binary_sensor.py @@ -61,7 +61,7 @@ vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None), vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_OFF_DELAY): cv.positive_int, vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, diff --git a/homeassistant/components/mqtt/button.py b/homeassistant/components/mqtt/button.py index af84e6c91496b..9b3b04a54f5c6 100644 --- a/homeassistant/components/mqtt/button.py +++ b/homeassistant/components/mqtt/button.py @@ -35,7 +35,7 @@ vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None), - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_PAYLOAD_PRESS, default=DEFAULT_PAYLOAD_PRESS): cv.string, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, } diff --git a/homeassistant/components/mqtt/camera.py b/homeassistant/components/mqtt/camera.py index 32aa641a734cc..166bfdd38ccdc 100644 --- a/homeassistant/components/mqtt/camera.py +++ b/homeassistant/components/mqtt/camera.py @@ -41,7 +41,7 @@ PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend( { - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Required(CONF_TOPIC): valid_subscribe_topic, vol.Optional(CONF_IMAGE_ENCODING): "b64", } diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py index 41c68809fd5d1..f29a114620ace 100644 --- a/homeassistant/components/mqtt/climate.py +++ b/homeassistant/components/mqtt/climate.py @@ -296,7 +296,7 @@ def valid_humidity_state_configuration(config: ConfigType) -> ConfigType: ): cv.ensure_list, vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, diff --git a/homeassistant/components/mqtt/cover.py b/homeassistant/components/mqtt/cover.py index b6324efb5e856..c11cf2dfb85d6 100644 --- a/homeassistant/components/mqtt/cover.py +++ b/homeassistant/components/mqtt/cover.py @@ -159,7 +159,7 @@ def validate_options(config: ConfigType) -> ConfigType: vol.Optional(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None), vol.Optional(CONF_GET_POSITION_TOPIC): valid_subscribe_topic, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): vol.Any( cv.string, None diff --git a/homeassistant/components/mqtt/device_tracker.py b/homeassistant/components/mqtt/device_tracker.py index f207783e2f3c8..dd4eca9878a4e 100644 --- a/homeassistant/components/mqtt/device_tracker.py +++ b/homeassistant/components/mqtt/device_tracker.py @@ -61,7 +61,7 @@ def valid_config(config: ConfigType) -> ConfigType: { vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_PAYLOAD_HOME, default=STATE_HOME): cv.string, vol.Optional(CONF_PAYLOAD_NOT_HOME, default=STATE_NOT_HOME): cv.string, vol.Optional(CONF_PAYLOAD_RESET, default=DEFAULT_PAYLOAD_RESET): cv.string, diff --git a/homeassistant/components/mqtt/fan.py b/homeassistant/components/mqtt/fan.py index 82dd707de215b..58189c3cb3e8c 100644 --- a/homeassistant/components/mqtt/fan.py +++ b/homeassistant/components/mqtt/fan.py @@ -127,7 +127,7 @@ def valid_preset_mode_configuration(config: ConfigType) -> ConfigType: _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend( { - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_DIRECTION_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_DIRECTION_COMMAND_TEMPLATE): cv.template, diff --git a/homeassistant/components/mqtt/humidifier.py b/homeassistant/components/mqtt/humidifier.py index 5df4d9329df7a..aebb05c19f7cd 100644 --- a/homeassistant/components/mqtt/humidifier.py +++ b/homeassistant/components/mqtt/humidifier.py @@ -136,7 +136,7 @@ def valid_humidity_range_configuration(config: ConfigType) -> ConfigType: vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, diff --git a/homeassistant/components/mqtt/image.py b/homeassistant/components/mqtt/image.py index c89e565708dc7..a21d45369f8fd 100644 --- a/homeassistant/components/mqtt/image.py +++ b/homeassistant/components/mqtt/image.py @@ -61,7 +61,7 @@ def validate_topic_required(config: ConfigType) -> ConfigType: PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend( { vol.Optional(CONF_CONTENT_TYPE): cv.string, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Exclusive(CONF_URL_TOPIC, "image_topic"): valid_subscribe_topic, vol.Exclusive(CONF_IMAGE_TOPIC, "image_topic"): valid_subscribe_topic, vol.Optional(CONF_IMAGE_ENCODING): "b64", diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index c010f221bb2c8..2a726075bb0da 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -190,7 +190,7 @@ vol.Optional(CONF_HS_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_MAX_MIREDS): cv.positive_int, vol.Optional(CONF_MIN_MIREDS): cv.positive_int, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_ON_COMMAND_TYPE, default=DEFAULT_ON_COMMAND_TYPE): vol.In( VALUES_ON_COMMAND_TYPE ), diff --git a/homeassistant/components/mqtt/light/schema_json.py b/homeassistant/components/mqtt/light/schema_json.py index cff14e19471aa..8f710eb5ea66b 100644 --- a/homeassistant/components/mqtt/light/schema_json.py +++ b/homeassistant/components/mqtt/light/schema_json.py @@ -132,7 +132,7 @@ def valid_color_configuration(config: ConfigType) -> ConfigType: vol.Optional(CONF_HS, default=DEFAULT_HS): cv.boolean, vol.Optional(CONF_MAX_MIREDS): cv.positive_int, vol.Optional(CONF_MIN_MIREDS): cv.positive_int, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_QOS, default=DEFAULT_QOS): vol.All( vol.Coerce(int), vol.In([0, 1, 2]) ), diff --git a/homeassistant/components/mqtt/light/schema_template.py b/homeassistant/components/mqtt/light/schema_template.py index ffbb245b25f63..98ee7648eebd8 100644 --- a/homeassistant/components/mqtt/light/schema_template.py +++ b/homeassistant/components/mqtt/light/schema_template.py @@ -100,7 +100,7 @@ vol.Optional(CONF_GREEN_TEMPLATE): cv.template, vol.Optional(CONF_MAX_MIREDS): cv.positive_int, vol.Optional(CONF_MIN_MIREDS): cv.positive_int, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_RED_TEMPLATE): cv.template, vol.Optional(CONF_STATE_TEMPLATE): cv.template, } diff --git a/homeassistant/components/mqtt/lock.py b/homeassistant/components/mqtt/lock.py index d117422d3996d..cb586c0630929 100644 --- a/homeassistant/components/mqtt/lock.py +++ b/homeassistant/components/mqtt/lock.py @@ -76,7 +76,7 @@ { vol.Optional(CONF_CODE_FORMAT): cv.is_regex, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK): cv.string, vol.Optional(CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK): cv.string, vol.Optional(CONF_PAYLOAD_OPEN): cv.string, diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index c6264dee17a67..9cc2eb2f34fe1 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -210,7 +210,7 @@ def validate_device_has_at_least_one_identifier(value: ConfigType) -> ConfigType ), vol.Optional(CONF_MANUFACTURER): cv.string, vol.Optional(CONF_MODEL): cv.string, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_HW_VERSION): cv.string, vol.Optional(CONF_SW_VERSION): cv.string, vol.Optional(CONF_VIA_DEVICE): cv.string, @@ -1013,7 +1013,7 @@ class MqttEntity( """Representation of an MQTT entity.""" _attr_should_poll = False - _default_name: str | None | UndefinedType + _default_name: str | None _entity_id_format: str def __init__( @@ -1127,10 +1127,7 @@ def _set_entity_name(self, config: ConfigType) -> None: # Only set _attr_name if it is needed if entity_name is not UNDEFINED: self._attr_name = entity_name - elif ( - not (use_device_class_name := self._default_to_device_class_name()) - and self._default_name is not UNDEFINED - ): + elif not (use_device_class_name := self._default_to_device_class_name()): # Assign the default name self._attr_name = self._default_name elif use_device_class_name: diff --git a/homeassistant/components/mqtt/number.py b/homeassistant/components/mqtt/number.py index dbe763ba95446..971b44b43bfcc 100644 --- a/homeassistant/components/mqtt/number.py +++ b/homeassistant/components/mqtt/number.py @@ -87,7 +87,7 @@ def validate_config(config: ConfigType) -> ConfigType: vol.Optional(CONF_MAX, default=DEFAULT_MAX_VALUE): vol.Coerce(float), vol.Optional(CONF_MIN, default=DEFAULT_MIN_VALUE): vol.Coerce(float), vol.Optional(CONF_MODE, default=NumberMode.AUTO): vol.Coerce(NumberMode), - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_PAYLOAD_RESET, default=DEFAULT_PAYLOAD_RESET): cv.string, vol.Optional(CONF_STEP, default=DEFAULT_STEP): vol.All( vol.Coerce(float), vol.Range(min=1e-3) diff --git a/homeassistant/components/mqtt/scene.py b/homeassistant/components/mqtt/scene.py index 5e12f67a698f3..9fb92f00c94ec 100644 --- a/homeassistant/components/mqtt/scene.py +++ b/homeassistant/components/mqtt/scene.py @@ -34,7 +34,7 @@ { vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_PAYLOAD_ON): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, diff --git a/homeassistant/components/mqtt/select.py b/homeassistant/components/mqtt/select.py index 5a8a24859175c..df8cf024bd26c 100644 --- a/homeassistant/components/mqtt/select.py +++ b/homeassistant/components/mqtt/select.py @@ -54,7 +54,7 @@ PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Required(CONF_OPTIONS): cv.ensure_list, vol.Optional(CONF_VALUE_TEMPLATE): cv.template, }, diff --git a/homeassistant/components/mqtt/sensor.py b/homeassistant/components/mqtt/sensor.py index d90b2ff3402fd..ae94b0df0ce68 100644 --- a/homeassistant/components/mqtt/sensor.py +++ b/homeassistant/components/mqtt/sensor.py @@ -78,7 +78,7 @@ vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, vol.Optional(CONF_LAST_RESET_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_SUGGESTED_DISPLAY_PRECISION): cv.positive_int, vol.Optional(CONF_STATE_CLASS): vol.Any(STATE_CLASSES_SCHEMA, None), vol.Optional(CONF_UNIT_OF_MEASUREMENT): vol.Any(cv.string, None), diff --git a/homeassistant/components/mqtt/siren.py b/homeassistant/components/mqtt/siren.py index ad1521af4f73c..9eb707a4c394a 100644 --- a/homeassistant/components/mqtt/siren.py +++ b/homeassistant/components/mqtt/siren.py @@ -80,7 +80,7 @@ vol.Optional(CONF_AVAILABLE_TONES): cv.ensure_list, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_COMMAND_OFF_TEMPLATE): cv.template, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_STATE_OFF): cv.string, diff --git a/homeassistant/components/mqtt/switch.py b/homeassistant/components/mqtt/switch.py index 5120ad466668e..107b0b1cb10d9 100644 --- a/homeassistant/components/mqtt/switch.py +++ b/homeassistant/components/mqtt/switch.py @@ -49,7 +49,7 @@ PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend( { - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_STATE_OFF): cv.string, diff --git a/homeassistant/components/mqtt/text.py b/homeassistant/components/mqtt/text.py index 41c5c49dd45c5..13677b7f35b22 100644 --- a/homeassistant/components/mqtt/text.py +++ b/homeassistant/components/mqtt/text.py @@ -78,7 +78,7 @@ def valid_text_size_configuration(config: ConfigType) -> ConfigType: _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend( { vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_MAX, default=MAX_LENGTH_STATE_STATE): cv.positive_int, vol.Optional(CONF_MIN, default=0): cv.positive_int, vol.Optional(CONF_MODE, default=text.TextMode.TEXT): vol.In( diff --git a/homeassistant/components/mqtt/update.py b/homeassistant/components/mqtt/update.py index b293c865eaca0..f6db0d3fd64c4 100644 --- a/homeassistant/components/mqtt/update.py +++ b/homeassistant/components/mqtt/update.py @@ -57,7 +57,7 @@ vol.Optional(CONF_ENTITY_PICTURE): cv.string, vol.Optional(CONF_LATEST_VERSION_TEMPLATE): cv.template, vol.Optional(CONF_LATEST_VERSION_TOPIC): valid_subscribe_topic, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_PAYLOAD_INSTALL): cv.string, vol.Optional(CONF_RELEASE_SUMMARY): cv.string, vol.Optional(CONF_RELEASE_URL): cv.string, diff --git a/homeassistant/components/mqtt/vacuum/schema_legacy.py b/homeassistant/components/mqtt/vacuum/schema_legacy.py index ef176c149c98d..516a7772c1117 100644 --- a/homeassistant/components/mqtt/vacuum/schema_legacy.py +++ b/homeassistant/components/mqtt/vacuum/schema_legacy.py @@ -131,7 +131,7 @@ ), vol.Inclusive(CONF_FAN_SPEED_TEMPLATE, "fan_speed"): cv.template, vol.Inclusive(CONF_FAN_SPEED_TOPIC, "fan_speed"): valid_publish_topic, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional( CONF_PAYLOAD_CLEAN_SPOT, default=DEFAULT_PAYLOAD_CLEAN_SPOT ): cv.string, diff --git a/homeassistant/components/mqtt/vacuum/schema_state.py b/homeassistant/components/mqtt/vacuum/schema_state.py index 5a6c8e78b3d72..5113e19f097e1 100644 --- a/homeassistant/components/mqtt/vacuum/schema_state.py +++ b/homeassistant/components/mqtt/vacuum/schema_state.py @@ -126,7 +126,7 @@ vol.Optional(CONF_FAN_SPEED_LIST, default=[]): vol.All( cv.ensure_list, [cv.string] ), - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional( CONF_PAYLOAD_CLEAN_SPOT, default=DEFAULT_PAYLOAD_CLEAN_SPOT ): cv.string, diff --git a/homeassistant/components/mqtt/water_heater.py b/homeassistant/components/mqtt/water_heater.py index 38d540895defa..17e9430dba34f 100644 --- a/homeassistant/components/mqtt/water_heater.py +++ b/homeassistant/components/mqtt/water_heater.py @@ -123,7 +123,7 @@ ): cv.ensure_list, vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME): vol.Any(cv.string, None), vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index 46f0e06a6be2a..81e733bd5e46e 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -178,6 +178,40 @@ def test_callback(event) -> None: None, True, ), + ( # none_entity_name_with_device_name + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "name": None, + "state_topic": "test-topic", + "unique_id": "veryunique", + "device_class": "humidity", + "device": {"name": "Test", "identifiers": ["helloworld"]}, + } + } + }, + "sensor.test", + "Test", + "Test", + False, + ), + ( # none_entity_name_without_device_name + { + mqtt.DOMAIN: { + sensor.DOMAIN: { + "name": None, + "state_topic": "test-topic", + "unique_id": "veryunique", + "device_class": "humidity", + "device": {"identifiers": ["helloworld"]}, + } + } + }, + "sensor.mqtt_veryunique", + "mqtt veryunique", + None, + True, + ), ], ids=[ "default_entity_name_without_device_name", @@ -186,6 +220,8 @@ def test_callback(event) -> None: "name_follows_device_class_without_device_name", "name_overrides_device_class", "name_set_no_device_name_set", + "none_entity_name_with_device_name", + "none_entity_name_without_device_name", ], ) @patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR]) From 1bdcc2926a91e699851571adfd5c5efdad387775 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Tue, 18 Jul 2023 12:54:58 +0000 Subject: [PATCH 12/17] Refactor --- homeassistant/components/mqtt/mixins.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 9cc2eb2f34fe1..48f193da19146 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1141,19 +1141,15 @@ def _set_entity_name(self, config: ConfigType) -> None: config, ) return - if (device_config := CONF_DEVICE in config) and CONF_NAME in config[ - CONF_DEVICE - ]: - # Only set has_entity_name if a valid name is set - self._attr_has_entity_name = True - elif device_config: - self._attr_has_entity_name = False - _LOGGER.warning( - "No device name set in config: %s," - "'has_entity_name' was set to False, this is not recommended, see also " - "https://developers.home-assistant.io/docs/core/entity/#entity-naming", - config, - ) + if CONF_DEVICE in config: + self._attr_has_entity_name = device_named = CONF_NAME in config[CONF_DEVICE] + if not device_named: + _LOGGER.warning( + "No device name set in config: %s," + "'has_entity_name' was set to False, this is not recommended, see also " + "https://developers.home-assistant.io/docs/core/entity/#entity-naming", + config, + ) def _setup_common_attributes_from_config(self, config: ConfigType) -> None: """(Re)Setup the common attributes for the entity.""" From 0f8a5651641053fb3bcb36da0bd313b0a2a7003e Mon Sep 17 00:00:00 2001 From: jbouwh Date: Tue, 18 Jul 2023 13:32:22 +0000 Subject: [PATCH 13/17] Log info messages when device name is not set --- homeassistant/components/mqtt/mixins.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 48f193da19146..4f297277dbeb2 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1134,9 +1134,10 @@ def _set_entity_name(self, config: ConfigType) -> None: # Follow name of device class self._attr_has_entity_name = True if CONF_DEVICE in config and CONF_NAME not in config[CONF_DEVICE]: - _LOGGER.warning( + _LOGGER.info( "No device name set in config: %s," - "'has_entity_name' was set to True, please set a device name, see also " + "'has_entity_name' was set to True, assuming the entity is linked " + "to an existing shared device, see also " "https://developers.home-assistant.io/docs/core/entity/#entity-naming", config, ) @@ -1144,9 +1145,10 @@ def _set_entity_name(self, config: ConfigType) -> None: if CONF_DEVICE in config: self._attr_has_entity_name = device_named = CONF_NAME in config[CONF_DEVICE] if not device_named: - _LOGGER.warning( + _LOGGER.info( "No device name set in config: %s," - "'has_entity_name' was set to False, this is not recommended, see also " + "'has_entity_name' was set to True, assuming the entity is linked " + "to an existing shared device, see also " "https://developers.home-assistant.io/docs/core/entity/#entity-naming", config, ) From e6bcee8fcbaf1c50e5d6688853766f60016d5999 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Tue, 18 Jul 2023 13:56:34 +0000 Subject: [PATCH 14/17] Revert scene schema change - no device link --- homeassistant/components/mqtt/scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/mqtt/scene.py b/homeassistant/components/mqtt/scene.py index 9fb92f00c94ec..5e12f67a698f3 100644 --- a/homeassistant/components/mqtt/scene.py +++ b/homeassistant/components/mqtt/scene.py @@ -34,7 +34,7 @@ { vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_NAME): vol.Any(cv.string, None), + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_PAYLOAD_ON): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, From 974a63ef362610851809a46740acae853833c368 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Thu, 20 Jul 2023 10:19:09 +0000 Subject: [PATCH 15/17] Always set has_entity_name with device mapping --- homeassistant/components/mqtt/mixins.py | 19 ++++++-------- tests/components/mqtt/test_diagnostics.py | 6 ++--- tests/components/mqtt/test_discovery.py | 32 +++++++++++------------ tests/components/mqtt/test_init.py | 4 +-- tests/components/mqtt/test_mixins.py | 8 +++--- 5 files changed, 34 insertions(+), 35 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 4f297277dbeb2..17b0d87170f91 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1135,22 +1135,19 @@ def _set_entity_name(self, config: ConfigType) -> None: self._attr_has_entity_name = True if CONF_DEVICE in config and CONF_NAME not in config[CONF_DEVICE]: _LOGGER.info( - "No device name set in config: %s," - "'has_entity_name' was set to True, assuming the entity is linked " - "to an existing shared device, see also " - "https://developers.home-assistant.io/docs/core/entity/#entity-naming", + "MQTT device information always needs to include a name, got %s, " + "if device information is shared between multiple entities, the device " + "name must be included in each entity's device configuration", config, ) return if CONF_DEVICE in config: - self._attr_has_entity_name = device_named = CONF_NAME in config[CONF_DEVICE] - if not device_named: + self._attr_has_entity_name = True + if CONF_NAME not in config[CONF_DEVICE]: _LOGGER.info( - "No device name set in config: %s," - "'has_entity_name' was set to True, assuming the entity is linked " - "to an existing shared device, see also " - "https://developers.home-assistant.io/docs/core/entity/#entity-naming", - config, + "MQTT device information always needs to include a name, got %s, " + "if device information is shared between multiple entities, the device " + "name must be included in each entity's device configuration", ) def _setup_common_attributes_from_config(self, config: ConfigType) -> None: diff --git a/tests/components/mqtt/test_diagnostics.py b/tests/components/mqtt/test_diagnostics.py index fb1033848743c..eb923ac2f0768 100644 --- a/tests/components/mqtt/test_diagnostics.py +++ b/tests/components/mqtt/test_diagnostics.py @@ -80,7 +80,7 @@ async def test_entry_diagnostics( expected_debug_info = { "entities": [ { - "entity_id": "sensor.mqtt_sensor", + "entity_id": "sensor.none_mqtt_sensor", "subscriptions": [{"topic": "foobar/sensor", "messages": []}], "discovery_data": { "payload": config_sensor, @@ -109,13 +109,13 @@ async def test_entry_diagnostics( "disabled": False, "disabled_by": None, "entity_category": None, - "entity_id": "sensor.mqtt_sensor", + "entity_id": "sensor.none_mqtt_sensor", "icon": None, "original_device_class": None, "original_icon": None, "state": { "attributes": {"friendly_name": "MQTT Sensor"}, - "entity_id": "sensor.mqtt_sensor", + "entity_id": "sensor.none_mqtt_sensor", "last_changed": ANY, "last_updated": ANY, "state": "unknown", diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py index b742d5e8ed72a..d3b8a145df7d8 100644 --- a/tests/components/mqtt/test_discovery.py +++ b/tests/components/mqtt/test_discovery.py @@ -729,10 +729,10 @@ async def test_cleanup_device( # Verify device and registry entries are created device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) assert device_entry is not None - entity_entry = entity_registry.async_get("sensor.mqtt_sensor") + entity_entry = entity_registry.async_get("sensor.none_mqtt_sensor") assert entity_entry is not None - state = hass.states.get("sensor.mqtt_sensor") + state = hass.states.get("sensor.none_mqtt_sensor") assert state is not None # Remove MQTT from the device @@ -753,11 +753,11 @@ async def test_cleanup_device( # Verify device and registry entries are cleared device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) assert device_entry is None - entity_entry = entity_registry.async_get("sensor.mqtt_sensor") + entity_entry = entity_registry.async_get("sensor.none_mqtt_sensor") assert entity_entry is None # Verify state is removed - state = hass.states.get("sensor.mqtt_sensor") + state = hass.states.get("sensor.none_mqtt_sensor") assert state is None await hass.async_block_till_done() @@ -788,10 +788,10 @@ async def test_cleanup_device_mqtt( # Verify device and registry entries are created device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) assert device_entry is not None - entity_entry = entity_registry.async_get("sensor.mqtt_sensor") + entity_entry = entity_registry.async_get("sensor.none_mqtt_sensor") assert entity_entry is not None - state = hass.states.get("sensor.mqtt_sensor") + state = hass.states.get("sensor.none_mqtt_sensor") assert state is not None async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", "") @@ -801,11 +801,11 @@ async def test_cleanup_device_mqtt( # Verify device and registry entries are cleared device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) assert device_entry is None - entity_entry = entity_registry.async_get("sensor.mqtt_sensor") + entity_entry = entity_registry.async_get("sensor.none_mqtt_sensor") assert entity_entry is None # Verify state is removed - state = hass.states.get("sensor.mqtt_sensor") + state = hass.states.get("sensor.none_mqtt_sensor") assert state is None await hass.async_block_till_done() @@ -873,10 +873,10 @@ async def test_cleanup_device_multiple_config_entries( mqtt_config_entry.entry_id, config_entry.entry_id, } - entity_entry = entity_registry.async_get("sensor.mqtt_sensor") + entity_entry = entity_registry.async_get("sensor.none_mqtt_sensor") assert entity_entry is not None - state = hass.states.get("sensor.mqtt_sensor") + state = hass.states.get("sensor.none_mqtt_sensor") assert state is not None # Remove MQTT from the device @@ -900,12 +900,12 @@ async def test_cleanup_device_multiple_config_entries( connections={("mac", "12:34:56:AB:CD:EF")} ) assert device_entry is not None - entity_entry = entity_registry.async_get("sensor.mqtt_sensor") + entity_entry = entity_registry.async_get("sensor.none_mqtt_sensor") assert device_entry.config_entries == {config_entry.entry_id} assert entity_entry is None # Verify state is removed - state = hass.states.get("sensor.mqtt_sensor") + state = hass.states.get("sensor.none_mqtt_sensor") assert state is None await hass.async_block_till_done() @@ -973,10 +973,10 @@ async def test_cleanup_device_multiple_config_entries_mqtt( mqtt_config_entry.entry_id, config_entry.entry_id, } - entity_entry = entity_registry.async_get("sensor.mqtt_sensor") + entity_entry = entity_registry.async_get("sensor.none_mqtt_sensor") assert entity_entry is not None - state = hass.states.get("sensor.mqtt_sensor") + state = hass.states.get("sensor.none_mqtt_sensor") assert state is not None # Send MQTT messages to remove @@ -992,12 +992,12 @@ async def test_cleanup_device_multiple_config_entries_mqtt( connections={("mac", "12:34:56:AB:CD:EF")} ) assert device_entry is not None - entity_entry = entity_registry.async_get("sensor.mqtt_sensor") + entity_entry = entity_registry.async_get("sensor.none_mqtt_sensor") assert device_entry.config_entries == {config_entry.entry_id} assert entity_entry is None # Verify state is removed - state = hass.states.get("sensor.mqtt_sensor") + state = hass.states.get("sensor.none_mqtt_sensor") assert state is None await hass.async_block_till_done() diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 3395dc0825f23..c0d7a94de5b79 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -2821,7 +2821,7 @@ async def test_mqtt_ws_get_device_debug_info( expected_result = { "entities": [ { - "entity_id": "sensor.mqtt_sensor", + "entity_id": "sensor.none_mqtt_sensor", "subscriptions": [{"topic": "foobar/sensor", "messages": []}], "discovery_data": { "payload": config_sensor, @@ -2884,7 +2884,7 @@ async def test_mqtt_ws_get_device_debug_info_binary( expected_result = { "entities": [ { - "entity_id": "camera.mqtt_camera", + "entity_id": "camera.none_mqtt_camera", "subscriptions": [ { "topic": "foobar/image", diff --git a/tests/components/mqtt/test_mixins.py b/tests/components/mqtt/test_mixins.py index 81e733bd5e46e..5a30a3a65de05 100644 --- a/tests/components/mqtt/test_mixins.py +++ b/tests/components/mqtt/test_mixins.py @@ -92,7 +92,7 @@ def test_callback(event) -> None: } } }, - "sensor.mqtt_sensor", + "sensor.none_mqtt_sensor", DEFAULT_SENSOR_NAME, None, True, @@ -173,7 +173,7 @@ def test_callback(event) -> None: } } }, - "sensor.mysensor", + "sensor.none_mysensor", "MySensor", None, True, @@ -250,4 +250,6 @@ async def test_default_entity_and_device_name( assert state is not None assert state.name == friendly_name - assert ("No device name set in config" in caplog.text) is assert_log + assert ( + "MQTT device information always needs to include a name" in caplog.text + ) is assert_log From a398a0a9d59de9f8ed528523c11891fd328e69a7 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Fri, 21 Jul 2023 08:54:52 +0000 Subject: [PATCH 16/17] Always set `_attr_has_entity_name` --- homeassistant/components/mqtt/mixins.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 17b0d87170f91..900c7ac50b696 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -1012,6 +1012,7 @@ class MqttEntity( ): """Representation of an MQTT entity.""" + _attr_has_entity_name = True _attr_should_poll = False _default_name: str | None _entity_id_format: str @@ -1132,7 +1133,6 @@ def _set_entity_name(self, config: ConfigType) -> None: self._attr_name = self._default_name elif use_device_class_name: # Follow name of device class - self._attr_has_entity_name = True if CONF_DEVICE in config and CONF_NAME not in config[CONF_DEVICE]: _LOGGER.info( "MQTT device information always needs to include a name, got %s, " @@ -1142,7 +1142,6 @@ def _set_entity_name(self, config: ConfigType) -> None: ) return if CONF_DEVICE in config: - self._attr_has_entity_name = True if CONF_NAME not in config[CONF_DEVICE]: _LOGGER.info( "MQTT device information always needs to include a name, got %s, " From ad5db3cff2a1b3bd36339c7a528bd3dc15beace2 Mon Sep 17 00:00:00 2001 From: jbouwh Date: Fri, 21 Jul 2023 09:03:46 +0000 Subject: [PATCH 17/17] Cleanup --- homeassistant/components/mqtt/mixins.py | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/homeassistant/components/mqtt/mixins.py b/homeassistant/components/mqtt/mixins.py index 900c7ac50b696..bf742d724bff8 100644 --- a/homeassistant/components/mqtt/mixins.py +++ b/homeassistant/components/mqtt/mixins.py @@ -210,7 +210,7 @@ def validate_device_has_at_least_one_identifier(value: ConfigType) -> ConfigType ), vol.Optional(CONF_MANUFACTURER): cv.string, vol.Optional(CONF_MODEL): cv.string, - vol.Optional(CONF_NAME): vol.Any(cv.string, None), + vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_HW_VERSION): cv.string, vol.Optional(CONF_SW_VERSION): cv.string, vol.Optional(CONF_VIA_DEVICE): cv.string, @@ -1128,19 +1128,9 @@ def _set_entity_name(self, config: ConfigType) -> None: # Only set _attr_name if it is needed if entity_name is not UNDEFINED: self._attr_name = entity_name - elif not (use_device_class_name := self._default_to_device_class_name()): + elif not self._default_to_device_class_name(): # Assign the default name self._attr_name = self._default_name - elif use_device_class_name: - # Follow name of device class - if CONF_DEVICE in config and CONF_NAME not in config[CONF_DEVICE]: - _LOGGER.info( - "MQTT device information always needs to include a name, got %s, " - "if device information is shared between multiple entities, the device " - "name must be included in each entity's device configuration", - config, - ) - return if CONF_DEVICE in config: if CONF_NAME not in config[CONF_DEVICE]: _LOGGER.info(