diff --git a/homeassistant/components/mobile_app/entity.py b/homeassistant/components/mobile_app/entity.py index 84527a528c07..a2a60d3d2393 100644 --- a/homeassistant/components/mobile_app/entity.py +++ b/homeassistant/components/mobile_app/entity.py @@ -26,11 +26,13 @@ from .const import ( ATTR_SENSOR_STATE, ATTR_SENSOR_STATE_CLASS, ATTR_SENSOR_TYPE, + ATTR_SENSOR_UNIQUE_ID, DATA_PENDING_UPDATES, DOMAIN, SIGNAL_SENSOR_UPDATE, ) from .helpers import device_info +from .known_sensors import get_translation_key _LOGGER = logging.getLogger(__name__) @@ -49,7 +51,14 @@ class MobileAppEntity(RestoreEntity): self._attr_entity_registry_enabled_default = not config.get( ATTR_SENSOR_DISABLED ) - self._attr_name = config[CONF_NAME] + translation_key = get_translation_key( + config[ATTR_SENSOR_TYPE], config[ATTR_SENSOR_UNIQUE_ID] + ) + if translation_key is not None: + self._attr_has_entity_name = True + self._attr_translation_key = translation_key + else: + self._attr_name = config[CONF_NAME] self._async_update_attr_from_config() @callback @@ -59,7 +68,8 @@ class MobileAppEntity(RestoreEntity): self._attr_device_class = config.get(ATTR_SENSOR_DEVICE_CLASS) self._attr_state_class = config.get(ATTR_SENSOR_STATE_CLASS) self._attr_extra_state_attributes = config[ATTR_SENSOR_ATTRIBUTES] - self._attr_icon = config[ATTR_SENSOR_ICON] + if self._attr_translation_key is None: + self._attr_icon = config[ATTR_SENSOR_ICON] self._attr_entity_category = config.get(ATTR_SENSOR_ENTITY_CATEGORY) self._attr_available = config.get(ATTR_SENSOR_STATE) != STATE_UNAVAILABLE diff --git a/homeassistant/components/mobile_app/icons.json b/homeassistant/components/mobile_app/icons.json index e4a00bd8427d..df65c7d4d0f3 100644 --- a/homeassistant/components/mobile_app/icons.json +++ b/homeassistant/components/mobile_app/icons.json @@ -4,6 +4,20 @@ "notify": { "default": "mdi:cellphone-message" } + }, + "sensor": { + "audio_mode": { + "default": "mdi:volume-high" + }, + "battery_state": { + "default": "mdi:battery" + }, + "charger_type": { + "default": "mdi:power-plug" + }, + "ringer_mode": { + "default": "mdi:bell-ring" + } } } } diff --git a/homeassistant/components/mobile_app/known_sensors.py b/homeassistant/components/mobile_app/known_sensors.py new file mode 100644 index 000000000000..6382a8b2e54c --- /dev/null +++ b/homeassistant/components/mobile_app/known_sensors.py @@ -0,0 +1,27 @@ +"""Known sensor unique IDs that have translation support. + +Mobile app sensors are dynamically registered by the companion apps. For a +curated set of sensor IDs that the iOS and Android Companion apps use +consistently we provide HA-side translations for the entity name, icon and +(when applicable) state. For all other sensor IDs the integration keeps using +the name/icon supplied by the app. +""" + +from __future__ import annotations + +from .const import ATTR_SENSOR_TYPE_SENSOR + +# Mapping of (entity_type, sensor_unique_id) to translation_key. +# The translation_key must have a matching entry in strings.json under +# entity... +KNOWN_SENSORS: dict[tuple[str, str], str] = { + (ATTR_SENSOR_TYPE_SENSOR, "battery_state"): "battery_state", + (ATTR_SENSOR_TYPE_SENSOR, "charger_type"): "charger_type", + (ATTR_SENSOR_TYPE_SENSOR, "ringer_mode"): "ringer_mode", + (ATTR_SENSOR_TYPE_SENSOR, "audio_mode"): "audio_mode", +} + + +def get_translation_key(entity_type: str, sensor_unique_id: str) -> str | None: + """Return the translation key for a known sensor, or None.""" + return KNOWN_SENSORS.get((entity_type, sensor_unique_id)) diff --git a/homeassistant/components/mobile_app/strings.json b/homeassistant/components/mobile_app/strings.json index 60ee8750c023..734ef373da2f 100644 --- a/homeassistant/components/mobile_app/strings.json +++ b/homeassistant/components/mobile_app/strings.json @@ -18,6 +18,47 @@ "title": "Title" } }, + "entity": { + "sensor": { + "audio_mode": { + "name": "Audio mode", + "state": { + "normal": "Normal", + "ringing": "Ringing", + "in_call": "In call", + "in_communication": "In communication", + "call_screening": "Call screening" + } + }, + "battery_state": { + "name": "Battery state", + "state": { + "charging": "Charging", + "discharging": "Discharging", + "full": "Full", + "not_charging": "Not charging", + "unknown": "Unknown" + } + }, + "charger_type": { + "name": "Charger type", + "state": { + "ac": "AC", + "usb": "USB", + "wireless": "Wireless", + "none": "None" + } + }, + "ringer_mode": { + "name": "Ringer mode", + "state": { + "normal": "Normal", + "silent": "Silent", + "vibrate": "Vibrate" + } + } + } + }, "exceptions": { "device_not_connected_for_local_push_notifications": { "message": "Device {device_name} is not connected for local push notifications" diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 232c4c50c6c3..d1fcd4ff9f88 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -120,6 +120,7 @@ from .helpers import ( safe_registration, webhook_response, ) +from .known_sensors import get_translation_key _LOGGER = logging.getLogger(__name__) @@ -567,6 +568,8 @@ async def webhook_register_sensor( data[CONF_WEBHOOK_ID] = config_entry.data[CONF_WEBHOOK_ID] + translation_key = get_translation_key(entity_type, unique_id) + # If sensor already is registered, update current state instead if existing_sensor: _LOGGER.debug( @@ -577,10 +580,25 @@ async def webhook_register_sensor( assert entry is not None changes: dict[str, Any] = {} - if ( - new_name := f"{device_name} {data[ATTR_SENSOR_NAME]}" - ) != entry.original_name: - changes["original_name"] = new_name + if translation_key is not None: + # For known sensors HA owns the name and icon via translations, + # so we ignore the values supplied by the mobile app. + if entry.translation_key != translation_key: + changes["translation_key"] = translation_key + if entry.original_name is not None: + changes["original_name"] = None + if entry.original_icon is not None: + changes["original_icon"] = None + else: + if ( + new_name := f"{device_name} {data[ATTR_SENSOR_NAME]}" + ) != entry.original_name: + changes["original_name"] = new_name + if ( + ATTR_SENSOR_ICON in data + and entry.original_icon != data[ATTR_SENSOR_ICON] + ): + changes["original_icon"] = data[ATTR_SENSOR_ICON] if ( should_be_disabled := data.get(ATTR_SENSOR_DISABLED) @@ -595,7 +613,6 @@ async def webhook_register_sensor( ("device_class", ATTR_SENSOR_DEVICE_CLASS), ("unit_of_measurement", ATTR_SENSOR_UOM), ("entity_category", ATTR_SENSOR_ENTITY_CATEGORY), - ("original_icon", ATTR_SENSOR_ICON), ): if data_key in data and getattr(entry, ent_reg_key) != data[data_key]: changes[ent_reg_key] = data[data_key]