Change naming of MQTT entities to correspond with HA guidelines (#95159)

* Set has_entity_name if device_name is set

* revert unneeded formatting change

* Add image platform

* Follow up comment

* Don't set `has_entity_name` without device name

* Only set has_entity_name if a valid name is set

* Follow device_class name and add tests

* Follow up comments add extra tests

* Move to helper - Log a warning

* fix test

* Allow to assign None as name explictly

* Refactor

* Log info messages when device name is not set

* Revert scene schema change - no device link

* Always set has_entity_name with device mapping

* Always set `_attr_has_entity_name`

* Cleanup
This commit is contained in:
Jan Bouwhuis 2023-07-21 12:52:10 +02:00 committed by GitHub
parent 747f4d4a73
commit 447fbf58c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 433 additions and 66 deletions

View file

@ -89,7 +89,7 @@ PLATFORM_SCHEMA_MODERN = MQTT_BASE_SCHEMA.extend(
CONF_COMMAND_TEMPLATE, default=DEFAULT_COMMAND_TEMPLATE CONF_COMMAND_TEMPLATE, default=DEFAULT_COMMAND_TEMPLATE
): cv.template, ): cv.template,
vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_NAME, default=DEFAULT_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_AWAY, default=DEFAULT_ARM_AWAY): cv.string,
vol.Optional(CONF_PAYLOAD_ARM_HOME, default=DEFAULT_ARM_HOME): 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, 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): class MqttAlarm(MqttEntity, alarm.AlarmControlPanelEntity):
"""Representation of a MQTT alarm status.""" """Representation of a MQTT alarm status."""
_default_name = DEFAULT_NAME
_entity_id_format = alarm.ENTITY_ID_FORMAT _entity_id_format = alarm.ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_ALARM_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_ALARM_ATTRIBUTES_BLOCKED

View file

@ -61,7 +61,7 @@ PLATFORM_SCHEMA_MODERN = MQTT_RO_SCHEMA.extend(
vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None), vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None),
vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Optional(CONF_OFF_DELAY): cv.positive_int, vol.Optional(CONF_OFF_DELAY): cv.positive_int,
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): 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_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
@ -97,6 +97,7 @@ async def _async_setup_entity(
class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity): class MqttBinarySensor(MqttEntity, BinarySensorEntity, RestoreEntity):
"""Representation a binary sensor that is updated by MQTT.""" """Representation a binary sensor that is updated by MQTT."""
_default_name = DEFAULT_NAME
_entity_id_format = binary_sensor.ENTITY_ID_FORMAT _entity_id_format = binary_sensor.ENTITY_ID_FORMAT
_expired: bool | None _expired: bool | None
_expire_after: int | None _expire_after: int | None

View file

@ -35,7 +35,7 @@ PLATFORM_SCHEMA_MODERN = MQTT_BASE_SCHEMA.extend(
vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template,
vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None), vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None),
vol.Optional(CONF_NAME, default=DEFAULT_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_PAYLOAD_PRESS, default=DEFAULT_PAYLOAD_PRESS): cv.string,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
} }
@ -70,6 +70,7 @@ async def _async_setup_entity(
class MqttButton(MqttEntity, ButtonEntity): class MqttButton(MqttEntity, ButtonEntity):
"""Representation of a switch that can be toggled using MQTT.""" """Representation of a switch that can be toggled using MQTT."""
_default_name = DEFAULT_NAME
_entity_id_format = button.ENTITY_ID_FORMAT _entity_id_format = button.ENTITY_ID_FORMAT
def __init__( def __init__(

View file

@ -41,7 +41,7 @@ MQTT_CAMERA_ATTRIBUTES_BLOCKED = frozenset(
PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend( PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend(
{ {
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Required(CONF_TOPIC): valid_subscribe_topic, vol.Required(CONF_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_IMAGE_ENCODING): "b64", vol.Optional(CONF_IMAGE_ENCODING): "b64",
} }
@ -80,6 +80,7 @@ async def _async_setup_entity(
class MqttCamera(MqttEntity, Camera): class MqttCamera(MqttEntity, Camera):
"""representation of a MQTT camera.""" """representation of a MQTT camera."""
_default_name = DEFAULT_NAME
_entity_id_format: str = camera.ENTITY_ID_FORMAT _entity_id_format: str = camera.ENTITY_ID_FORMAT
_attributes_extra_blocked: frozenset[str] = MQTT_CAMERA_ATTRIBUTES_BLOCKED _attributes_extra_blocked: frozenset[str] = MQTT_CAMERA_ATTRIBUTES_BLOCKED

View file

@ -296,7 +296,7 @@ _PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend(
): cv.ensure_list, ): cv.ensure_list,
vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
@ -597,6 +597,7 @@ class MqttTemperatureControlEntity(MqttEntity, ABC):
class MqttClimate(MqttTemperatureControlEntity, ClimateEntity): class MqttClimate(MqttTemperatureControlEntity, ClimateEntity):
"""Representation of an MQTT climate device.""" """Representation of an MQTT climate device."""
_default_name = DEFAULT_NAME
_entity_id_format = climate.ENTITY_ID_FORMAT _entity_id_format = climate.ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_CLIMATE_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_CLIMATE_ATTRIBUTES_BLOCKED

View file

@ -159,7 +159,7 @@ _PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend(
vol.Optional(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None), vol.Optional(CONF_DEVICE_CLASS): vol.Any(DEVICE_CLASSES_SCHEMA, None),
vol.Optional(CONF_GET_POSITION_TOPIC): valid_subscribe_topic, vol.Optional(CONF_GET_POSITION_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): vol.Any( vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): vol.Any(
cv.string, None cv.string, None
@ -236,6 +236,7 @@ async def _async_setup_entity(
class MqttCover(MqttEntity, CoverEntity): class MqttCover(MqttEntity, CoverEntity):
"""Representation of a cover that can be controlled using MQTT.""" """Representation of a cover that can be controlled using MQTT."""
_default_name = DEFAULT_NAME
_entity_id_format: str = cover.ENTITY_ID_FORMAT _entity_id_format: str = cover.ENTITY_ID_FORMAT
_attributes_extra_blocked: frozenset[str] = MQTT_COVER_ATTRIBUTES_BLOCKED _attributes_extra_blocked: frozenset[str] = MQTT_COVER_ATTRIBUTES_BLOCKED

View file

@ -61,7 +61,7 @@ PLATFORM_SCHEMA_MODERN_BASE = MQTT_BASE_SCHEMA.extend(
{ {
vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, 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_HOME, default=STATE_HOME): cv.string,
vol.Optional(CONF_PAYLOAD_NOT_HOME, default=STATE_NOT_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, vol.Optional(CONF_PAYLOAD_RESET, default=DEFAULT_PAYLOAD_RESET): cv.string,
@ -104,6 +104,7 @@ async def _async_setup_entity(
class MqttDeviceTracker(MqttEntity, TrackerEntity): class MqttDeviceTracker(MqttEntity, TrackerEntity):
"""Representation of a device tracker using MQTT.""" """Representation of a device tracker using MQTT."""
_default_name = None
_entity_id_format = device_tracker.ENTITY_ID_FORMAT _entity_id_format = device_tracker.ENTITY_ID_FORMAT
_value_template: Callable[..., ReceivePayloadType] _value_template: Callable[..., ReceivePayloadType]

View file

@ -127,7 +127,7 @@ def valid_preset_mode_configuration(config: ConfigType) -> ConfigType:
_PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend( _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend(
{ {
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template,
vol.Optional(CONF_DIRECTION_COMMAND_TOPIC): valid_publish_topic, vol.Optional(CONF_DIRECTION_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_DIRECTION_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_DIRECTION_COMMAND_TEMPLATE): cv.template,
@ -215,6 +215,7 @@ async def _async_setup_entity(
class MqttFan(MqttEntity, FanEntity): class MqttFan(MqttEntity, FanEntity):
"""A MQTT fan component.""" """A MQTT fan component."""
_default_name = DEFAULT_NAME
_entity_id_format = fan.ENTITY_ID_FORMAT _entity_id_format = fan.ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_FAN_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_FAN_ATTRIBUTES_BLOCKED

View file

@ -136,7 +136,7 @@ _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend(
vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template,
vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_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_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_STATE_VALUE_TEMPLATE): cv.template,
@ -207,6 +207,7 @@ async def _async_setup_entity(
class MqttHumidifier(MqttEntity, HumidifierEntity): class MqttHumidifier(MqttEntity, HumidifierEntity):
"""A MQTT humidifier component.""" """A MQTT humidifier component."""
_default_name = DEFAULT_NAME
_entity_id_format = humidifier.ENTITY_ID_FORMAT _entity_id_format = humidifier.ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_HUMIDIFIER_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_HUMIDIFIER_ATTRIBUTES_BLOCKED

View file

@ -61,7 +61,7 @@ def validate_topic_required(config: ConfigType) -> ConfigType:
PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend( PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend(
{ {
vol.Optional(CONF_CONTENT_TYPE): cv.string, vol.Optional(CONF_CONTENT_TYPE): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_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_URL_TOPIC, "image_topic"): valid_subscribe_topic,
vol.Exclusive(CONF_IMAGE_TOPIC, "image_topic"): valid_subscribe_topic, vol.Exclusive(CONF_IMAGE_TOPIC, "image_topic"): valid_subscribe_topic,
vol.Optional(CONF_IMAGE_ENCODING): "b64", vol.Optional(CONF_IMAGE_ENCODING): "b64",
@ -102,6 +102,7 @@ async def _async_setup_entity(
class MqttImage(MqttEntity, ImageEntity): class MqttImage(MqttEntity, ImageEntity):
"""representation of a MQTT image.""" """representation of a MQTT image."""
_default_name = DEFAULT_NAME
_entity_id_format: str = image.ENTITY_ID_FORMAT _entity_id_format: str = image.ENTITY_ID_FORMAT
_last_image: bytes | None = None _last_image: bytes | None = None
_client: httpx.AsyncClient _client: httpx.AsyncClient

View file

@ -190,7 +190,7 @@ PLATFORM_SCHEMA_MODERN_BASIC = (
vol.Optional(CONF_HS_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_HS_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_MAX_MIREDS): cv.positive_int, vol.Optional(CONF_MAX_MIREDS): cv.positive_int,
vol.Optional(CONF_MIN_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): vol.Any(cv.string, None),
vol.Optional(CONF_ON_COMMAND_TYPE, default=DEFAULT_ON_COMMAND_TYPE): vol.In( vol.Optional(CONF_ON_COMMAND_TYPE, default=DEFAULT_ON_COMMAND_TYPE): vol.In(
VALUES_ON_COMMAND_TYPE VALUES_ON_COMMAND_TYPE
), ),
@ -242,6 +242,7 @@ async def async_setup_entity_basic(
class MqttLight(MqttEntity, LightEntity, RestoreEntity): class MqttLight(MqttEntity, LightEntity, RestoreEntity):
"""Representation of a MQTT light.""" """Representation of a MQTT light."""
_default_name = DEFAULT_NAME
_entity_id_format = ENTITY_ID_FORMAT _entity_id_format = ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED
_topic: dict[str, str | None] _topic: dict[str, str | None]

View file

@ -132,7 +132,7 @@ _PLATFORM_SCHEMA_BASE = (
vol.Optional(CONF_HS, default=DEFAULT_HS): cv.boolean, vol.Optional(CONF_HS, default=DEFAULT_HS): cv.boolean,
vol.Optional(CONF_MAX_MIREDS): cv.positive_int, vol.Optional(CONF_MAX_MIREDS): cv.positive_int,
vol.Optional(CONF_MIN_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): vol.Any(cv.string, None),
vol.Optional(CONF_QOS, default=DEFAULT_QOS): vol.All( vol.Optional(CONF_QOS, default=DEFAULT_QOS): vol.All(
vol.Coerce(int), vol.In([0, 1, 2]) vol.Coerce(int), vol.In([0, 1, 2])
), ),
@ -180,6 +180,7 @@ async def async_setup_entity_json(
class MqttLightJson(MqttEntity, LightEntity, RestoreEntity): class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
"""Representation of a MQTT JSON light.""" """Representation of a MQTT JSON light."""
_default_name = DEFAULT_NAME
_entity_id_format = ENTITY_ID_FORMAT _entity_id_format = ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED

View file

@ -100,7 +100,7 @@ PLATFORM_SCHEMA_MODERN_TEMPLATE = (
vol.Optional(CONF_GREEN_TEMPLATE): cv.template, vol.Optional(CONF_GREEN_TEMPLATE): cv.template,
vol.Optional(CONF_MAX_MIREDS): cv.positive_int, vol.Optional(CONF_MAX_MIREDS): cv.positive_int,
vol.Optional(CONF_MIN_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): vol.Any(cv.string, None),
vol.Optional(CONF_RED_TEMPLATE): cv.template, vol.Optional(CONF_RED_TEMPLATE): cv.template,
vol.Optional(CONF_STATE_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): class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity):
"""Representation of a MQTT Template light.""" """Representation of a MQTT Template light."""
_default_name = DEFAULT_NAME
_entity_id_format = ENTITY_ID_FORMAT _entity_id_format = ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_LIGHT_ATTRIBUTES_BLOCKED
_optimistic: bool _optimistic: bool

View file

@ -76,7 +76,7 @@ PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend(
{ {
vol.Optional(CONF_CODE_FORMAT): cv.is_regex, vol.Optional(CONF_CODE_FORMAT): cv.is_regex,
vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_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_LOCK, default=DEFAULT_PAYLOAD_LOCK): cv.string,
vol.Optional(CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK): cv.string, vol.Optional(CONF_PAYLOAD_UNLOCK, default=DEFAULT_PAYLOAD_UNLOCK): cv.string,
vol.Optional(CONF_PAYLOAD_OPEN): cv.string, vol.Optional(CONF_PAYLOAD_OPEN): cv.string,
@ -126,6 +126,7 @@ async def _async_setup_entity(
class MqttLock(MqttEntity, LockEntity): class MqttLock(MqttEntity, LockEntity):
"""Representation of a lock that can be toggled using MQTT.""" """Representation of a lock that can be toggled using MQTT."""
_default_name = DEFAULT_NAME
_entity_id_format = lock.ENTITY_ID_FORMAT _entity_id_format = lock.ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_LOCK_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_LOCK_ATTRIBUTES_BLOCKED

View file

@ -50,7 +50,12 @@ from homeassistant.helpers.event import (
async_track_device_registry_updated_event, async_track_device_registry_updated_event,
async_track_entity_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 homeassistant.util.json import json_loads
from . import debug_info, subscription from . import debug_info, subscription
@ -999,7 +1004,9 @@ class MqttEntity(
): ):
"""Representation of an MQTT entity.""" """Representation of an MQTT entity."""
_attr_has_entity_name = True
_attr_should_poll = False _attr_should_poll = False
_default_name: str | None
_entity_id_format: str _entity_id_format: str
def __init__( def __init__(
@ -1016,8 +1023,8 @@ class MqttEntity(
self._sub_state: dict[str, EntitySubscription] = {} self._sub_state: dict[str, EntitySubscription] = {}
# Load config # Load config
self._setup_common_attributes_from_config(self._config)
self._setup_from_config(self._config) self._setup_from_config(self._config)
self._setup_common_attributes_from_config(self._config)
# Initialize entity_id from config # Initialize entity_id from config
self._init_entity_id() self._init_entity_id()
@ -1058,8 +1065,8 @@ class MqttEntity(
async_handle_schema_error(discovery_payload, err) async_handle_schema_error(discovery_payload, err)
return return
self._config = config self._config = config
self._setup_common_attributes_from_config(self._config)
self._setup_from_config(self._config) self._setup_from_config(self._config)
self._setup_common_attributes_from_config(self._config)
# Prepare MQTT subscriptions # Prepare MQTT subscriptions
self.attributes_prepare_discovery_update(config) self.attributes_prepare_discovery_update(config)
@ -1107,6 +1114,23 @@ class MqttEntity(
def config_schema() -> vol.Schema: def config_schema() -> vol.Schema:
"""Return the config schema.""" """Return the config schema."""
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:
self._attr_name = entity_name
elif not self._default_to_device_class_name():
# Assign the default name
self._attr_name = self._default_name
if CONF_DEVICE in config:
if 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",
)
def _setup_common_attributes_from_config(self, config: ConfigType) -> None: def _setup_common_attributes_from_config(self, config: ConfigType) -> None:
"""(Re)Setup the common attributes for the entity.""" """(Re)Setup the common attributes for the entity."""
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
@ -1114,7 +1138,8 @@ class MqttEntity(
config.get(CONF_ENABLED_BY_DEFAULT) config.get(CONF_ENABLED_BY_DEFAULT)
) )
self._attr_icon = config.get(CONF_ICON) self._attr_icon = config.get(CONF_ICON)
self._attr_name = config.get(CONF_NAME) # Set the entity name if needed
self._set_entity_name(config)
def _setup_from_config(self, config: ConfigType) -> None: def _setup_from_config(self, config: ConfigType) -> None:
"""(Re)Setup the entity.""" """(Re)Setup the entity."""

View file

@ -87,7 +87,7 @@ _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend(
vol.Optional(CONF_MAX, default=DEFAULT_MAX_VALUE): vol.Coerce(float), 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_MIN, default=DEFAULT_MIN_VALUE): vol.Coerce(float),
vol.Optional(CONF_MODE, default=NumberMode.AUTO): vol.Coerce(NumberMode), vol.Optional(CONF_MODE, default=NumberMode.AUTO): vol.Coerce(NumberMode),
vol.Optional(CONF_NAME, default=DEFAULT_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_PAYLOAD_RESET, default=DEFAULT_PAYLOAD_RESET): cv.string,
vol.Optional(CONF_STEP, default=DEFAULT_STEP): vol.All( vol.Optional(CONF_STEP, default=DEFAULT_STEP): vol.All(
vol.Coerce(float), vol.Range(min=1e-3) vol.Coerce(float), vol.Range(min=1e-3)
@ -134,6 +134,7 @@ async def _async_setup_entity(
class MqttNumber(MqttEntity, RestoreNumber): class MqttNumber(MqttEntity, RestoreNumber):
"""representation of an MQTT number.""" """representation of an MQTT number."""
_default_name = DEFAULT_NAME
_entity_id_format = number.ENTITY_ID_FORMAT _entity_id_format = number.ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_NUMBER_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_NUMBER_ATTRIBUTES_BLOCKED

View file

@ -34,7 +34,7 @@ PLATFORM_SCHEMA_MODERN = MQTT_BASE_SCHEMA.extend(
{ {
vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic, vol.Required(CONF_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_ICON): cv.icon, 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_PAYLOAD_ON): cv.string,
vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, 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.""" """Representation of a scene that can be activated using MQTT."""
_default_name = DEFAULT_NAME
_entity_id_format = scene.DOMAIN + ".{}" _entity_id_format = scene.DOMAIN + ".{}"
def __init__( def __init__(

View file

@ -54,7 +54,7 @@ MQTT_SELECT_ATTRIBUTES_BLOCKED = frozenset(
PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend( PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend(
{ {
vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Required(CONF_OPTIONS): cv.ensure_list, vol.Required(CONF_OPTIONS): cv.ensure_list,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}, },
@ -89,6 +89,7 @@ async def _async_setup_entity(
class MqttSelect(MqttEntity, SelectEntity, RestoreEntity): class MqttSelect(MqttEntity, SelectEntity, RestoreEntity):
"""representation of an MQTT select.""" """representation of an MQTT select."""
_default_name = DEFAULT_NAME
_entity_id_format = select.ENTITY_ID_FORMAT _entity_id_format = select.ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_SELECT_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_SELECT_ATTRIBUTES_BLOCKED
_command_template: Callable[[PublishPayloadType], PublishPayloadType] _command_template: Callable[[PublishPayloadType], PublishPayloadType]

View file

@ -78,7 +78,7 @@ _PLATFORM_SCHEMA_BASE = MQTT_RO_SCHEMA.extend(
vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int, vol.Optional(CONF_EXPIRE_AFTER): cv.positive_int,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
vol.Optional(CONF_LAST_RESET_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_LAST_RESET_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Optional(CONF_SUGGESTED_DISPLAY_PRECISION): cv.positive_int, vol.Optional(CONF_SUGGESTED_DISPLAY_PRECISION): cv.positive_int,
vol.Optional(CONF_STATE_CLASS): vol.Any(STATE_CLASSES_SCHEMA, None), vol.Optional(CONF_STATE_CLASS): vol.Any(STATE_CLASSES_SCHEMA, None),
vol.Optional(CONF_UNIT_OF_MEASUREMENT): vol.Any(cv.string, 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): class MqttSensor(MqttEntity, RestoreSensor):
"""Representation of a sensor that can be updated using MQTT.""" """Representation of a sensor that can be updated using MQTT."""
_default_name = DEFAULT_NAME
_entity_id_format = ENTITY_ID_FORMAT _entity_id_format = ENTITY_ID_FORMAT
_attr_last_reset: datetime | None = None _attr_last_reset: datetime | None = None
_attributes_extra_blocked = MQTT_SENSOR_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_SENSOR_ATTRIBUTES_BLOCKED

View file

@ -79,7 +79,7 @@ PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend(
vol.Optional(CONF_AVAILABLE_TONES): cv.ensure_list, vol.Optional(CONF_AVAILABLE_TONES): cv.ensure_list,
vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template,
vol.Optional(CONF_COMMAND_OFF_TEMPLATE): cv.template, vol.Optional(CONF_COMMAND_OFF_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_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_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_STATE_OFF): cv.string, vol.Optional(CONF_STATE_OFF): cv.string,
@ -138,6 +138,7 @@ async def _async_setup_entity(
class MqttSiren(MqttEntity, SirenEntity): class MqttSiren(MqttEntity, SirenEntity):
"""Representation of a siren that can be controlled using MQTT.""" """Representation of a siren that can be controlled using MQTT."""
_default_name = DEFAULT_NAME
_entity_id_format = ENTITY_ID_FORMAT _entity_id_format = ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_SIREN_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_SIREN_ATTRIBUTES_BLOCKED
_extra_attributes: dict[str, Any] _extra_attributes: dict[str, Any]

View file

@ -49,7 +49,7 @@ CONF_STATE_OFF = "state_off"
PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend( PLATFORM_SCHEMA_MODERN = MQTT_RW_SCHEMA.extend(
{ {
vol.Optional(CONF_NAME, default=DEFAULT_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_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_STATE_OFF): cv.string, vol.Optional(CONF_STATE_OFF): cv.string,
@ -88,6 +88,7 @@ async def _async_setup_entity(
class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity): class MqttSwitch(MqttEntity, SwitchEntity, RestoreEntity):
"""Representation of a switch that can be toggled using MQTT.""" """Representation of a switch that can be toggled using MQTT."""
_default_name = DEFAULT_NAME
_entity_id_format = switch.ENTITY_ID_FORMAT _entity_id_format = switch.ENTITY_ID_FORMAT
_optimistic: bool _optimistic: bool

View file

@ -78,7 +78,7 @@ def valid_text_size_configuration(config: ConfigType) -> ConfigType:
_PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend( _PLATFORM_SCHEMA_BASE = MQTT_RW_SCHEMA.extend(
{ {
vol.Optional(CONF_COMMAND_TEMPLATE): cv.template, vol.Optional(CONF_COMMAND_TEMPLATE): cv.template,
vol.Optional(CONF_NAME, default=DEFAULT_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_MAX, default=MAX_LENGTH_STATE_STATE): cv.positive_int,
vol.Optional(CONF_MIN, default=0): cv.positive_int, vol.Optional(CONF_MIN, default=0): cv.positive_int,
vol.Optional(CONF_MODE, default=text.TextMode.TEXT): vol.In( vol.Optional(CONF_MODE, default=text.TextMode.TEXT): vol.In(
@ -125,6 +125,7 @@ class MqttTextEntity(MqttEntity, TextEntity):
"""Representation of the MQTT text entity.""" """Representation of the MQTT text entity."""
_attributes_extra_blocked = MQTT_TEXT_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_TEXT_ATTRIBUTES_BLOCKED
_default_name = DEFAULT_NAME
_entity_id_format = text.ENTITY_ID_FORMAT _entity_id_format = text.ENTITY_ID_FORMAT
_compiled_pattern: re.Pattern[Any] | None _compiled_pattern: re.Pattern[Any] | None

View file

@ -57,7 +57,7 @@ PLATFORM_SCHEMA_MODERN = MQTT_RO_SCHEMA.extend(
vol.Optional(CONF_ENTITY_PICTURE): cv.string, vol.Optional(CONF_ENTITY_PICTURE): cv.string,
vol.Optional(CONF_LATEST_VERSION_TEMPLATE): cv.template, vol.Optional(CONF_LATEST_VERSION_TEMPLATE): cv.template,
vol.Optional(CONF_LATEST_VERSION_TOPIC): valid_subscribe_topic, vol.Optional(CONF_LATEST_VERSION_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Optional(CONF_PAYLOAD_INSTALL): cv.string, vol.Optional(CONF_PAYLOAD_INSTALL): cv.string,
vol.Optional(CONF_RELEASE_SUMMARY): cv.string, vol.Optional(CONF_RELEASE_SUMMARY): cv.string,
vol.Optional(CONF_RELEASE_URL): cv.string, vol.Optional(CONF_RELEASE_URL): cv.string,
@ -107,6 +107,7 @@ async def _async_setup_entity(
class MqttUpdate(MqttEntity, UpdateEntity, RestoreEntity): class MqttUpdate(MqttEntity, UpdateEntity, RestoreEntity):
"""Representation of the MQTT update entity.""" """Representation of the MQTT update entity."""
_default_name = DEFAULT_NAME
_entity_id_format = update.ENTITY_ID_FORMAT _entity_id_format = update.ENTITY_ID_FORMAT
def __init__( def __init__(

View file

@ -131,7 +131,7 @@ PLATFORM_SCHEMA_LEGACY_MODERN = (
), ),
vol.Inclusive(CONF_FAN_SPEED_TEMPLATE, "fan_speed"): cv.template, vol.Inclusive(CONF_FAN_SPEED_TEMPLATE, "fan_speed"): cv.template,
vol.Inclusive(CONF_FAN_SPEED_TOPIC, "fan_speed"): valid_publish_topic, vol.Inclusive(CONF_FAN_SPEED_TOPIC, "fan_speed"): valid_publish_topic,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Optional( vol.Optional(
CONF_PAYLOAD_CLEAN_SPOT, default=DEFAULT_PAYLOAD_CLEAN_SPOT CONF_PAYLOAD_CLEAN_SPOT, default=DEFAULT_PAYLOAD_CLEAN_SPOT
): cv.string, ): cv.string,
@ -215,6 +215,7 @@ async def async_setup_entity_legacy(
class MqttVacuum(MqttEntity, VacuumEntity): class MqttVacuum(MqttEntity, VacuumEntity):
"""Representation of a MQTT-controlled legacy vacuum.""" """Representation of a MQTT-controlled legacy vacuum."""
_default_name = DEFAULT_NAME
_entity_id_format = ENTITY_ID_FORMAT _entity_id_format = ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_LEGACY_VACUUM_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_LEGACY_VACUUM_ATTRIBUTES_BLOCKED

View file

@ -126,7 +126,7 @@ PLATFORM_SCHEMA_STATE_MODERN = (
vol.Optional(CONF_FAN_SPEED_LIST, default=[]): vol.All( vol.Optional(CONF_FAN_SPEED_LIST, default=[]): vol.All(
cv.ensure_list, [cv.string] cv.ensure_list, [cv.string]
), ),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Optional( vol.Optional(
CONF_PAYLOAD_CLEAN_SPOT, default=DEFAULT_PAYLOAD_CLEAN_SPOT CONF_PAYLOAD_CLEAN_SPOT, default=DEFAULT_PAYLOAD_CLEAN_SPOT
): cv.string, ): cv.string,
@ -170,6 +170,7 @@ async def async_setup_entity_state(
class MqttStateVacuum(MqttEntity, StateVacuumEntity): class MqttStateVacuum(MqttEntity, StateVacuumEntity):
"""Representation of a MQTT-controlled state vacuum.""" """Representation of a MQTT-controlled state vacuum."""
_default_name = DEFAULT_NAME
_entity_id_format = ENTITY_ID_FORMAT _entity_id_format = ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_VACUUM_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_VACUUM_ATTRIBUTES_BLOCKED

View file

@ -123,7 +123,7 @@ _PLATFORM_SCHEMA_BASE = MQTT_BASE_SCHEMA.extend(
): cv.ensure_list, ): cv.ensure_list,
vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template,
vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_MODE_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME): vol.Any(cv.string, None),
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string,
vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string,
@ -180,6 +180,7 @@ async def _async_setup_entity(
class MqttWaterHeater(MqttTemperatureControlEntity, WaterHeaterEntity): class MqttWaterHeater(MqttTemperatureControlEntity, WaterHeaterEntity):
"""Representation of an MQTT water heater device.""" """Representation of an MQTT water heater device."""
_default_name = DEFAULT_NAME
_entity_id_format = water_heater.ENTITY_ID_FORMAT _entity_id_format = water_heater.ENTITY_ID_FORMAT
_attributes_extra_blocked = MQTT_WATER_HEATER_ATTRIBUTES_BLOCKED _attributes_extra_blocked = MQTT_WATER_HEATER_ATTRIBUTES_BLOCKED

View file

@ -55,6 +55,7 @@ from .test_common import (
help_test_entity_device_info_with_identifier, help_test_entity_device_info_with_identifier,
help_test_entity_id_update_discovery_update, help_test_entity_id_update_discovery_update,
help_test_entity_id_update_subscriptions, help_test_entity_id_update_subscriptions,
help_test_entity_name,
help_test_publishing_with_custom_encoding, help_test_publishing_with_custom_encoding,
help_test_reloadable, help_test_reloadable,
help_test_setting_attribute_via_mqtt_json_message, 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( await help_test_unload_config_entry_with_platform(
hass, mqtt_mock_entry, domain, config 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
)

View file

@ -41,6 +41,7 @@ from .test_common import (
help_test_entity_device_info_with_identifier, help_test_entity_device_info_with_identifier,
help_test_entity_id_update_discovery_update, help_test_entity_id_update_discovery_update,
help_test_entity_id_update_subscriptions, help_test_entity_id_update_subscriptions,
help_test_entity_name,
help_test_reload_with_config, help_test_reload_with_config,
help_test_reloadable, help_test_reloadable,
help_test_setting_attribute_via_mqtt_json_message, 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( await help_test_unload_config_entry_with_platform(
hass, mqtt_mock_entry, domain, config 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
)

View file

@ -30,6 +30,7 @@ from .test_common import (
help_test_entity_device_info_with_connection, help_test_entity_device_info_with_connection,
help_test_entity_device_info_with_identifier, help_test_entity_device_info_with_identifier,
help_test_entity_id_update_discovery_update, help_test_entity_id_update_discovery_update,
help_test_entity_name,
help_test_publishing_with_custom_encoding, help_test_publishing_with_custom_encoding,
help_test_reloadable, help_test_reloadable,
help_test_setting_attribute_via_mqtt_json_message, 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( await help_test_unload_config_entry_with_platform(
hass, mqtt_mock_entry, domain, config 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
)

View file

@ -1117,6 +1117,45 @@ async def help_test_entity_device_info_update(
assert device.name == "Milk" 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( async def help_test_entity_id_update_subscriptions(
hass: HomeAssistant, hass: HomeAssistant,
mqtt_mock_entry: MqttMockHAClientGenerator, mqtt_mock_entry: MqttMockHAClientGenerator,
@ -1390,7 +1429,7 @@ async def help_test_entity_debug_info_message(
with patch("homeassistant.util.dt.utcnow") as dt_utcnow: with patch("homeassistant.util.dt.utcnow") as dt_utcnow:
dt_utcnow.return_value = start_dt dt_utcnow.return_value = start_dt
if service: if service:
service_data = {ATTR_ENTITY_ID: f"{domain}.test"} service_data = {ATTR_ENTITY_ID: f"{domain}.beer_test"}
if service_parameters: if service_parameters:
service_data.update(service_parameters) service_data.update(service_parameters)
@ -1458,7 +1497,7 @@ async def help_test_entity_debug_info_remove(
"subscriptions" "subscriptions"
] ]
assert len(debug_info_data["triggers"]) == 0 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"] entity_id = debug_info_data["entities"][0]["entity_id"]
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", "") async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", "")
@ -1503,7 +1542,7 @@ async def help_test_entity_debug_info_update_entity_id(
== f"homeassistant/{domain}/bla/config" == f"homeassistant/{domain}/bla/config"
) )
assert debug_info_data["entities"][0]["discovery_data"]["payload"] == 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 len(debug_info_data["entities"][0]["subscriptions"]) == 1
assert {"topic": "test-topic", "messages": []} in debug_info_data["entities"][0][ assert {"topic": "test-topic", "messages": []} in debug_info_data["entities"][0][
"subscriptions" "subscriptions"
@ -1511,7 +1550,7 @@ async def help_test_entity_debug_info_update_entity_id(
assert len(debug_info_data["triggers"]) == 0 assert len(debug_info_data["triggers"]) == 0
entity_registry.async_update_entity( 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()
await hass.async_block_till_done() await hass.async_block_till_done()
@ -1529,7 +1568,7 @@ async def help_test_entity_debug_info_update_entity_id(
"subscriptions" "subscriptions"
] ]
assert len(debug_info_data["triggers"]) == 0 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( async def help_test_entity_disabled_by_default(

View file

@ -80,7 +80,7 @@ async def test_entry_diagnostics(
expected_debug_info = { expected_debug_info = {
"entities": [ "entities": [
{ {
"entity_id": "sensor.mqtt_sensor", "entity_id": "sensor.none_mqtt_sensor",
"subscriptions": [{"topic": "foobar/sensor", "messages": []}], "subscriptions": [{"topic": "foobar/sensor", "messages": []}],
"discovery_data": { "discovery_data": {
"payload": config_sensor, "payload": config_sensor,
@ -109,13 +109,13 @@ async def test_entry_diagnostics(
"disabled": False, "disabled": False,
"disabled_by": None, "disabled_by": None,
"entity_category": None, "entity_category": None,
"entity_id": "sensor.mqtt_sensor", "entity_id": "sensor.none_mqtt_sensor",
"icon": None, "icon": None,
"original_device_class": None, "original_device_class": None,
"original_icon": None, "original_icon": None,
"state": { "state": {
"attributes": {"friendly_name": "MQTT Sensor"}, "attributes": {"friendly_name": "MQTT Sensor"},
"entity_id": "sensor.mqtt_sensor", "entity_id": "sensor.none_mqtt_sensor",
"last_changed": ANY, "last_changed": ANY,
"last_updated": ANY, "last_updated": ANY,
"state": "unknown", "state": "unknown",

View file

@ -729,10 +729,10 @@ async def test_cleanup_device(
# Verify device and registry entries are created # Verify device and registry entries are created
device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")})
assert device_entry is not None 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 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 assert state is not None
# Remove MQTT from the device # Remove MQTT from the device
@ -753,11 +753,11 @@ async def test_cleanup_device(
# Verify device and registry entries are cleared # Verify device and registry entries are cleared
device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")})
assert device_entry is None 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 assert entity_entry is None
# Verify state is removed # Verify state is removed
state = hass.states.get("sensor.mqtt_sensor") state = hass.states.get("sensor.none_mqtt_sensor")
assert state is None assert state is None
await hass.async_block_till_done() await hass.async_block_till_done()
@ -788,10 +788,10 @@ async def test_cleanup_device_mqtt(
# Verify device and registry entries are created # Verify device and registry entries are created
device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")})
assert device_entry is not None 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 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 assert state is not None
async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", "") 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 # Verify device and registry entries are cleared
device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")}) device_entry = device_registry.async_get_device(identifiers={("mqtt", "0AFFD2")})
assert device_entry is None 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 assert entity_entry is None
# Verify state is removed # Verify state is removed
state = hass.states.get("sensor.mqtt_sensor") state = hass.states.get("sensor.none_mqtt_sensor")
assert state is None assert state is None
await hass.async_block_till_done() await hass.async_block_till_done()
@ -873,10 +873,10 @@ async def test_cleanup_device_multiple_config_entries(
mqtt_config_entry.entry_id, mqtt_config_entry.entry_id,
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 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 assert state is not None
# Remove MQTT from the device # 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")} connections={("mac", "12:34:56:AB:CD:EF")}
) )
assert device_entry is not None 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 device_entry.config_entries == {config_entry.entry_id}
assert entity_entry is None assert entity_entry is None
# Verify state is removed # Verify state is removed
state = hass.states.get("sensor.mqtt_sensor") state = hass.states.get("sensor.none_mqtt_sensor")
assert state is None assert state is None
await hass.async_block_till_done() await hass.async_block_till_done()
@ -973,10 +973,10 @@ async def test_cleanup_device_multiple_config_entries_mqtt(
mqtt_config_entry.entry_id, mqtt_config_entry.entry_id,
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 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 assert state is not None
# Send MQTT messages to remove # 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")} connections={("mac", "12:34:56:AB:CD:EF")}
) )
assert device_entry is not None 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 device_entry.config_entries == {config_entry.entry_id}
assert entity_entry is None assert entity_entry is None
# Verify state is removed # Verify state is removed
state = hass.states.get("sensor.mqtt_sensor") state = hass.states.get("sensor.none_mqtt_sensor")
assert state is None assert state is None
await hass.async_block_till_done() await hass.async_block_till_done()
@ -1474,13 +1474,12 @@ async def test_clear_config_topic_disabled_entity(
mqtt_mock = await mqtt_mock_entry() mqtt_mock = await mqtt_mock_entry()
# discover an entity that is not enabled by default # discover an entity that is not enabled by default
config = { config = {
"name": "sbfspot_12345",
"state_topic": "homeassistant_test/sensor/sbfspot_0/sbfspot_12345/", "state_topic": "homeassistant_test/sensor/sbfspot_0/sbfspot_12345/",
"unique_id": "sbfspot_12345", "unique_id": "sbfspot_12345",
"enabled_by_default": False, "enabled_by_default": False,
"device": { "device": {
"identifiers": ["sbfspot_12345"], "identifiers": ["sbfspot_12345"],
"name": "sbfspot_12345", "name": "abc123",
"sw_version": "1.0", "sw_version": "1.0",
"connections": [["mac", "12:34:56:AB:CD:EF"]], "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() await hass.async_block_till_done()
assert "Platform mqtt does not generate unique IDs" in caplog.text 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.abc123_sbfspot_12345") is None # disabled
assert hass.states.get("sensor.sbfspot_12345_1") is not None # enabled assert hass.states.get("sensor.abc123_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_2") is None # not unique
# Verify device is created # Verify device is created
device_entry = device_registry.async_get_device( 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.""" """Test the unique_id collision detection has priority over registry disabled items."""
await mqtt_mock_entry() await mqtt_mock_entry()
config = { config = {
"name": "sbfspot_12345",
"state_topic": "homeassistant_test/sensor/sbfspot_0/sbfspot_12345/", "state_topic": "homeassistant_test/sensor/sbfspot_0/sbfspot_12345/",
"unique_id": "sbfspot_12345", "unique_id": "sbfspot_12345",
"enabled_by_default": False, "enabled_by_default": False,
"device": { "device": {
"identifiers": ["sbfspot_12345"], "identifiers": ["sbfspot_12345"],
"name": "sbfspot_12345", "name": "abc123",
"sw_version": "1.0", "sw_version": "1.0",
"connections": [["mac", "12:34:56:AB:CD:EF"]], "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() await hass.async_block_till_done()
assert hass.states.get("sensor.sbfspot_12345_1") is None # not enabled assert hass.states.get("sensor.abc123_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_2") is None # not unique
# Verify the first entity is created # 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 # 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]) @patch("homeassistant.components.mqtt.PLATFORMS", [Platform.SENSOR])

View file

@ -2821,7 +2821,7 @@ async def test_mqtt_ws_get_device_debug_info(
expected_result = { expected_result = {
"entities": [ "entities": [
{ {
"entity_id": "sensor.mqtt_sensor", "entity_id": "sensor.none_mqtt_sensor",
"subscriptions": [{"topic": "foobar/sensor", "messages": []}], "subscriptions": [{"topic": "foobar/sensor", "messages": []}],
"discovery_data": { "discovery_data": {
"payload": config_sensor, "payload": config_sensor,
@ -2884,7 +2884,7 @@ async def test_mqtt_ws_get_device_debug_info_binary(
expected_result = { expected_result = {
"entities": [ "entities": [
{ {
"entity_id": "camera.mqtt_camera", "entity_id": "camera.none_mqtt_camera",
"subscriptions": [ "subscriptions": [
{ {
"topic": "foobar/image", "topic": "foobar/image",

View file

@ -5,8 +5,12 @@ from unittest.mock import patch
import pytest import pytest
from homeassistant.components import mqtt, sensor 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.const import EVENT_STATE_CHANGED, Platform
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import (
device_registry as dr,
)
from tests.common import async_fire_mqtt_message from tests.common import async_fire_mqtt_message
from tests.typing import MqttMockHAClientGenerator from tests.typing import MqttMockHAClientGenerator
@ -73,3 +77,179 @@ async def test_availability_with_shared_state_topic(
# The availability is changed but the topic is shared, # The availability is changed but the topic is shared,
# hence there the state will be written when the value is updated # hence there the state will be written when the value is updated
assert len(events) == 1 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.none_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",
"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.none_mysensor",
"MySensor",
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",
"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",
"none_entity_name_with_device_name",
"none_entity_name_without_device_name",
],
)
@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 (
"MQTT device information always needs to include a name" in caplog.text
) is assert_log

View file

@ -48,6 +48,7 @@ from .test_common import (
help_test_entity_device_info_with_identifier, help_test_entity_device_info_with_identifier,
help_test_entity_id_update_discovery_update, help_test_entity_id_update_discovery_update,
help_test_entity_id_update_subscriptions, help_test_entity_id_update_subscriptions,
help_test_entity_name,
help_test_publishing_with_custom_encoding, help_test_publishing_with_custom_encoding,
help_test_reloadable, help_test_reloadable,
help_test_setting_attribute_via_mqtt_json_message, 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( await help_test_unload_config_entry_with_platform(
hass, mqtt_mock_entry, domain, config 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
)

View file

@ -53,6 +53,7 @@ from .test_common import (
help_test_entity_disabled_by_default, help_test_entity_disabled_by_default,
help_test_entity_id_update_discovery_update, help_test_entity_id_update_discovery_update,
help_test_entity_id_update_subscriptions, help_test_entity_id_update_subscriptions,
help_test_entity_name,
help_test_reload_with_config, help_test_reload_with_config,
help_test_reloadable, help_test_reloadable,
help_test_setting_attribute_via_mqtt_json_message, 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( await help_test_unload_config_entry_with_platform(
hass, mqtt_mock_entry, domain, config 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
)