1
mirror of https://github.com/home-assistant/core synced 2024-08-02 23:40:32 +02:00

Add support for entity categories to MQTT entities (#57656)

* Add support for entity categories to MQTT entities

* Improve test

* Apply suggestions from code review

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Update homeassistant/components/mqtt/mixins.py

Co-authored-by: Paul Monigatti <paulmonigatti@users.noreply.github.com>

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Paul Monigatti <paulmonigatti@users.noreply.github.com>
This commit is contained in:
Erik Montnemery 2021-10-15 14:28:30 +02:00 committed by GitHub
parent f8dbcb953c
commit 1eebe45154
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 77 additions and 3 deletions

View File

@ -8,14 +8,20 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import CONF_DEVICE, CONF_ICON, CONF_NAME, CONF_UNIQUE_ID from homeassistant.const import (
CONF_DEVICE,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_NAME,
CONF_UNIQUE_ID,
)
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_connect,
async_dispatcher_send, async_dispatcher_send,
) )
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import ENTITY_CATEGORIES_SCHEMA, Entity
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
from . import DATA_MQTT, debug_info, publish, subscription from . import DATA_MQTT, debug_info, publish, subscription
@ -76,6 +82,7 @@ MQTT_ATTRIBUTES_BLOCKED = {
"context_recent_time", "context_recent_time",
"device_class", "device_class",
"device_info", "device_info",
"entity_category",
"entity_picture", "entity_picture",
"entity_registry_enabled_default", "entity_registry_enabled_default",
"extra_state_attributes", "extra_state_attributes",
@ -165,6 +172,7 @@ MQTT_ENTITY_COMMON_SCHEMA = MQTT_AVAILABILITY_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_ENABLED_BY_DEFAULT, default=True): cv.boolean, vol.Optional(CONF_ENABLED_BY_DEFAULT, default=True): cv.boolean,
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_JSON_ATTRS_TOPIC): valid_subscribe_topic, vol.Optional(CONF_JSON_ATTRS_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_JSON_ATTRS_TEMPLATE): cv.template, vol.Optional(CONF_JSON_ATTRS_TEMPLATE): cv.template,
@ -630,6 +638,11 @@ class MqttEntity(
"""Return if the entity should be enabled when first added to the entity registry.""" """Return if the entity should be enabled when first added to the entity registry."""
return self._config[CONF_ENABLED_BY_DEFAULT] return self._config[CONF_ENABLED_BY_DEFAULT]
@property
def entity_category(self) -> str | None:
"""Return the entity category if any."""
return self._config.get(CONF_ENTITY_CATEGORY)
@property @property
def icon(self): def icon(self):
"""Return icon of the entity if any.""" """Return icon of the entity if any."""

View File

@ -102,6 +102,7 @@ CONF_EFFECT: Final = "effect"
CONF_ELEVATION: Final = "elevation" CONF_ELEVATION: Final = "elevation"
CONF_EMAIL: Final = "email" CONF_EMAIL: Final = "email"
CONF_ENTITIES: Final = "entities" CONF_ENTITIES: Final = "entities"
CONF_ENTITY_CATEGORY: Final = "entity_category"
CONF_ENTITY_ID: Final = "entity_id" CONF_ENTITY_ID: Final = "entity_id"
CONF_ENTITY_NAMESPACE: Final = "entity_namespace" CONF_ENTITY_NAMESPACE: Final = "entity_namespace"
CONF_ENTITY_PICTURE_TEMPLATE: Final = "entity_picture_template" CONF_ENTITY_PICTURE_TEMPLATE: Final = "entity_picture_template"

View File

@ -11,7 +11,9 @@ import logging
import math import math
import sys import sys
from timeit import default_timer as timer from timeit import default_timer as timer
from typing import Any, Literal, TypedDict, final from typing import Any, Final, Literal, TypedDict, final
import voluptuous as vol
from homeassistant.config import DATA_CUSTOMIZE from homeassistant.config import DATA_CUSTOMIZE
from homeassistant.const import ( from homeassistant.const import (
@ -24,6 +26,8 @@ from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, ATTR_SUPPORTED_FEATURES,
ATTR_UNIT_OF_MEASUREMENT, ATTR_UNIT_OF_MEASUREMENT,
DEVICE_DEFAULT_NAME, DEVICE_DEFAULT_NAME,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
STATE_OFF, STATE_OFF,
STATE_ON, STATE_ON,
STATE_UNAVAILABLE, STATE_UNAVAILABLE,
@ -52,6 +56,14 @@ SOURCE_PLATFORM_CONFIG = "platform_config"
FLOAT_PRECISION = abs(int(math.floor(math.log10(abs(sys.float_info.epsilon))))) - 1 FLOAT_PRECISION = abs(int(math.floor(math.log10(abs(sys.float_info.epsilon))))) - 1
ENTITY_CATEGORIES: Final[list[str]] = [
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
]
ENTITY_CATEGORIES_SCHEMA: Final = vol.In(ENTITY_CATEGORIES)
@callback @callback
@bind_hass @bind_hass
def entity_sources(hass: HomeAssistant) -> dict[str, dict[str, str]]: def entity_sources(hass: HomeAssistant) -> dict[str, dict[str, str]]:

View File

@ -1217,3 +1217,44 @@ async def help_test_entity_disabled_by_default(hass, mqtt_mock, domain, config):
assert not ent_registry.async_get_entity_id(domain, mqtt.DOMAIN, "veryunique1") assert not ent_registry.async_get_entity_id(domain, mqtt.DOMAIN, "veryunique1")
assert not ent_registry.async_get_entity_id(domain, mqtt.DOMAIN, "veryunique2") assert not ent_registry.async_get_entity_id(domain, mqtt.DOMAIN, "veryunique2")
assert not dev_registry.async_get_device({("mqtt", "helloworld")}) assert not dev_registry.async_get_device({("mqtt", "helloworld")})
async def help_test_entity_category(hass, mqtt_mock, domain, config):
"""Test device registry remove."""
# Add device settings to config
config = copy.deepcopy(config[domain])
config["device"] = copy.deepcopy(DEFAULT_CONFIG_DEVICE_INFO_ID)
ent_registry = er.async_get(hass)
# Discover an entity without entity category
unique_id = "veryunique1"
config["unique_id"] = unique_id
data = json.dumps(config)
async_fire_mqtt_message(hass, f"homeassistant/{domain}/{unique_id}/config", data)
await hass.async_block_till_done()
entity_id = ent_registry.async_get_entity_id(domain, mqtt.DOMAIN, unique_id)
assert hass.states.get(entity_id)
entry = ent_registry.async_get(entity_id)
assert entry.entity_category is None
# Discover an entity with entity category set to "config"
unique_id = "veryunique2"
config["entity_category"] = "config"
config["unique_id"] = unique_id
data = json.dumps(config)
async_fire_mqtt_message(hass, f"homeassistant/{domain}/{unique_id}/config", data)
await hass.async_block_till_done()
entity_id = ent_registry.async_get_entity_id(domain, mqtt.DOMAIN, unique_id)
assert hass.states.get(entity_id)
entry = ent_registry.async_get(entity_id)
assert entry.entity_category == "config"
# Discover an entity with entity category set to "no_such_category"
unique_id = "veryunique3"
config["entity_category"] = "no_such_category"
config["unique_id"] = unique_id
data = json.dumps(config)
async_fire_mqtt_message(hass, f"homeassistant/{domain}/{unique_id}/config", data)
await hass.async_block_till_done()
assert not ent_registry.async_get_entity_id(domain, mqtt.DOMAIN, unique_id)

View File

@ -29,6 +29,7 @@ from .test_common import (
help_test_discovery_update_attr, help_test_discovery_update_attr,
help_test_discovery_update_availability, help_test_discovery_update_availability,
help_test_discovery_update_unchanged, help_test_discovery_update_unchanged,
help_test_entity_category,
help_test_entity_debug_info, help_test_entity_debug_info,
help_test_entity_debug_info_max_messages, help_test_entity_debug_info_max_messages,
help_test_entity_debug_info_message, help_test_entity_debug_info_message,
@ -838,6 +839,12 @@ async def test_entity_disabled_by_default(hass, mqtt_mock):
) )
@pytest.mark.no_fail_on_log_exception
async def test_entity_category(hass, mqtt_mock):
"""Test entity category."""
await help_test_entity_category(hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG)
async def test_value_template_with_entity_id(hass, mqtt_mock): async def test_value_template_with_entity_id(hass, mqtt_mock):
"""Test the access to attributes in value_template via the entity_id.""" """Test the access to attributes in value_template via the entity_id."""
assert await async_setup_component( assert await async_setup_component(