Move MQTT entity helpers to separate file (#44838)

* Move MQTT entity helpers to separate file

* Fix imports

* Update MQTT number

* Review comments

* Fix formatting
This commit is contained in:
Erik Montnemery 2021-01-09 00:47:17 +01:00 committed by GitHub
parent 3a88a4120e
commit b85efd343f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 716 additions and 660 deletions

View file

@ -3,7 +3,6 @@ import asyncio
from functools import lru_cache, partial, wraps from functools import lru_cache, partial, wraps
import inspect import inspect
from itertools import groupby from itertools import groupby
import json
import logging import logging
from operator import attrgetter from operator import attrgetter
import os import os
@ -20,8 +19,6 @@ from homeassistant import config_entries
from homeassistant.components import websocket_api from homeassistant.components import websocket_api
from homeassistant.const import ( from homeassistant.const import (
CONF_CLIENT_ID, CONF_CLIENT_ID,
CONF_DEVICE,
CONF_NAME,
CONF_PASSWORD, CONF_PASSWORD,
CONF_PAYLOAD, CONF_PAYLOAD,
CONF_PORT, CONF_PORT,
@ -35,12 +32,7 @@ from homeassistant.const import CONF_UNIQUE_ID # noqa: F401
from homeassistant.core import CoreState, Event, HassJob, ServiceCall, callback from homeassistant.core import CoreState, Event, HassJob, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError, Unauthorized from homeassistant.exceptions import HomeAssistantError, Unauthorized
from homeassistant.helpers import config_validation as cv, event, template from homeassistant.helpers import config_validation as cv, event, template
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import async_dispatcher_connect, dispatcher_send
async_dispatcher_connect,
async_dispatcher_send,
dispatcher_send,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType, HomeAssistantType, ServiceDataType from homeassistant.helpers.typing import ConfigType, HomeAssistantType, ServiceDataType
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
@ -51,9 +43,6 @@ from homeassistant.util.logging import catch_log_exception
from . import config_flow # noqa: F401 pylint: disable=unused-import from . import config_flow # noqa: F401 pylint: disable=unused-import
from . import debug_info, discovery from . import debug_info, discovery
from .const import ( from .const import (
ATTR_DISCOVERY_HASH,
ATTR_DISCOVERY_PAYLOAD,
ATTR_DISCOVERY_TOPIC,
ATTR_PAYLOAD, ATTR_PAYLOAD,
ATTR_QOS, ATTR_QOS,
ATTR_RETAIN, ATTR_RETAIN,
@ -68,8 +57,6 @@ from .const import (
DATA_MQTT_CONFIG, DATA_MQTT_CONFIG,
DEFAULT_BIRTH, DEFAULT_BIRTH,
DEFAULT_DISCOVERY, DEFAULT_DISCOVERY,
DEFAULT_PAYLOAD_AVAILABLE,
DEFAULT_PAYLOAD_NOT_AVAILABLE,
DEFAULT_PREFIX, DEFAULT_PREFIX,
DEFAULT_QOS, DEFAULT_QOS,
DEFAULT_RETAIN, DEFAULT_RETAIN,
@ -79,16 +66,8 @@ from .const import (
MQTT_DISCONNECTED, MQTT_DISCONNECTED,
PROTOCOL_311, PROTOCOL_311,
) )
from .debug_info import log_messages from .discovery import LAST_DISCOVERY
from .discovery import (
LAST_DISCOVERY,
MQTT_DISCOVERY_DONE,
MQTT_DISCOVERY_UPDATED,
clear_discovery_hash,
set_discovery_hash,
)
from .models import Message, MessageCallbackType, PublishPayloadType from .models import Message, MessageCallbackType, PublishPayloadType
from .subscription import async_subscribe_topics, async_unsubscribe_topics
from .util import _VALID_QOS_SCHEMA, valid_publish_topic, valid_subscribe_topic from .util import _VALID_QOS_SCHEMA, valid_publish_topic, valid_subscribe_topic
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -108,20 +87,6 @@ CONF_TLS_VERSION = "tls_version"
CONF_COMMAND_TOPIC = "command_topic" CONF_COMMAND_TOPIC = "command_topic"
CONF_TOPIC = "topic" CONF_TOPIC = "topic"
CONF_AVAILABILITY = "availability"
CONF_AVAILABILITY_TOPIC = "availability_topic"
CONF_PAYLOAD_AVAILABLE = "payload_available"
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
CONF_JSON_ATTRS_TOPIC = "json_attributes_topic"
CONF_JSON_ATTRS_TEMPLATE = "json_attributes_template"
CONF_IDENTIFIERS = "identifiers"
CONF_CONNECTIONS = "connections"
CONF_MANUFACTURER = "manufacturer"
CONF_MODEL = "model"
CONF_SW_VERSION = "sw_version"
CONF_VIA_DEVICE = "via_device"
CONF_DEPRECATED_VIA_HUB = "via_hub"
PROTOCOL_31 = "3.1" PROTOCOL_31 = "3.1"
@ -158,16 +123,6 @@ PLATFORMS = [
] ]
def validate_device_has_at_least_one_identifier(value: ConfigType) -> ConfigType:
"""Validate that a device info entry has at least one identifying value."""
if not value.get(CONF_IDENTIFIERS) and not value.get(CONF_CONNECTIONS):
raise vol.Invalid(
"Device must have at least one identifying value in "
"'identifiers' and/or 'connections'"
)
return value
CLIENT_KEY_AUTH_MSG = ( CLIENT_KEY_AUTH_MSG = (
"client_key and client_cert must both be present in " "client_key and client_cert must both be present in "
"the MQTT broker configuration" "the MQTT broker configuration"
@ -243,69 +198,6 @@ CONFIG_SCHEMA = vol.Schema(
SCHEMA_BASE = {vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA} SCHEMA_BASE = {vol.Optional(CONF_QOS, default=DEFAULT_QOS): _VALID_QOS_SCHEMA}
MQTT_AVAILABILITY_SINGLE_SCHEMA = vol.Schema(
{
vol.Exclusive(CONF_AVAILABILITY_TOPIC, "availability"): valid_subscribe_topic,
vol.Optional(
CONF_PAYLOAD_AVAILABLE, default=DEFAULT_PAYLOAD_AVAILABLE
): cv.string,
vol.Optional(
CONF_PAYLOAD_NOT_AVAILABLE, default=DEFAULT_PAYLOAD_NOT_AVAILABLE
): cv.string,
}
)
MQTT_AVAILABILITY_LIST_SCHEMA = vol.Schema(
{
vol.Exclusive(CONF_AVAILABILITY, "availability"): vol.All(
cv.ensure_list,
[
{
vol.Optional(CONF_TOPIC): valid_subscribe_topic,
vol.Optional(
CONF_PAYLOAD_AVAILABLE, default=DEFAULT_PAYLOAD_AVAILABLE
): cv.string,
vol.Optional(
CONF_PAYLOAD_NOT_AVAILABLE,
default=DEFAULT_PAYLOAD_NOT_AVAILABLE,
): cv.string,
}
],
),
}
)
MQTT_AVAILABILITY_SCHEMA = MQTT_AVAILABILITY_SINGLE_SCHEMA.extend(
MQTT_AVAILABILITY_LIST_SCHEMA.schema
)
MQTT_ENTITY_DEVICE_INFO_SCHEMA = vol.All(
cv.deprecated(CONF_DEPRECATED_VIA_HUB, CONF_VIA_DEVICE),
vol.Schema(
{
vol.Optional(CONF_IDENTIFIERS, default=list): vol.All(
cv.ensure_list, [cv.string]
),
vol.Optional(CONF_CONNECTIONS, default=list): vol.All(
cv.ensure_list, [vol.All(vol.Length(2), [cv.string])]
),
vol.Optional(CONF_MANUFACTURER): cv.string,
vol.Optional(CONF_MODEL): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_SW_VERSION): cv.string,
vol.Optional(CONF_VIA_DEVICE): cv.string,
}
),
validate_device_has_at_least_one_identifier,
)
MQTT_JSON_ATTRS_SCHEMA = vol.Schema(
{
vol.Optional(CONF_JSON_ATTRS_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_JSON_ATTRS_TEMPLATE): cv.template,
}
)
MQTT_BASE_PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(SCHEMA_BASE) MQTT_BASE_PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend(SCHEMA_BASE)
# Sensor type platforms subscribe to MQTT events # Sensor type platforms subscribe to MQTT events
@ -1085,347 +977,6 @@ def _matcher_for_topic(subscription: str) -> Any:
return lambda topic: next(matcher.iter_match(topic), False) return lambda topic: next(matcher.iter_match(topic), False)
class MqttAttributes(Entity):
"""Mixin used for platforms that support JSON attributes."""
def __init__(self, config: dict) -> None:
"""Initialize the JSON attributes mixin."""
self._attributes = None
self._attributes_sub_state = None
self._attributes_config = config
async def async_added_to_hass(self) -> None:
"""Subscribe MQTT events."""
await super().async_added_to_hass()
await self._attributes_subscribe_topics()
async def attributes_discovery_update(self, config: dict):
"""Handle updated discovery message."""
self._attributes_config = config
await self._attributes_subscribe_topics()
async def _attributes_subscribe_topics(self):
"""(Re)Subscribe to topics."""
attr_tpl = self._attributes_config.get(CONF_JSON_ATTRS_TEMPLATE)
if attr_tpl is not None:
attr_tpl.hass = self.hass
@callback
@log_messages(self.hass, self.entity_id)
def attributes_message_received(msg: Message) -> None:
try:
payload = msg.payload
if attr_tpl is not None:
payload = attr_tpl.async_render_with_possible_json_value(payload)
json_dict = json.loads(payload)
if isinstance(json_dict, dict):
self._attributes = json_dict
self.async_write_ha_state()
else:
_LOGGER.warning("JSON result was not a dictionary")
self._attributes = None
except ValueError:
_LOGGER.warning("Erroneous JSON: %s", payload)
self._attributes = None
self._attributes_sub_state = await async_subscribe_topics(
self.hass,
self._attributes_sub_state,
{
CONF_JSON_ATTRS_TOPIC: {
"topic": self._attributes_config.get(CONF_JSON_ATTRS_TOPIC),
"msg_callback": attributes_message_received,
"qos": self._attributes_config.get(CONF_QOS),
}
},
)
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
self._attributes_sub_state = await async_unsubscribe_topics(
self.hass, self._attributes_sub_state
)
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self._attributes
class MqttAvailability(Entity):
"""Mixin used for platforms that report availability."""
def __init__(self, config: dict) -> None:
"""Initialize the availability mixin."""
self._availability_sub_state = None
self._available = False
self._availability_setup_from_config(config)
async def async_added_to_hass(self) -> None:
"""Subscribe MQTT events."""
await super().async_added_to_hass()
await self._availability_subscribe_topics()
self.async_on_remove(
async_dispatcher_connect(self.hass, MQTT_CONNECTED, self.async_mqtt_connect)
)
self.async_on_remove(
async_dispatcher_connect(
self.hass, MQTT_DISCONNECTED, self.async_mqtt_connect
)
)
async def availability_discovery_update(self, config: dict):
"""Handle updated discovery message."""
self._availability_setup_from_config(config)
await self._availability_subscribe_topics()
def _availability_setup_from_config(self, config):
"""(Re)Setup."""
self._avail_topics = {}
if CONF_AVAILABILITY_TOPIC in config:
self._avail_topics[config[CONF_AVAILABILITY_TOPIC]] = {
CONF_PAYLOAD_AVAILABLE: config[CONF_PAYLOAD_AVAILABLE],
CONF_PAYLOAD_NOT_AVAILABLE: config[CONF_PAYLOAD_NOT_AVAILABLE],
}
if CONF_AVAILABILITY in config:
for avail in config[CONF_AVAILABILITY]:
self._avail_topics[avail[CONF_TOPIC]] = {
CONF_PAYLOAD_AVAILABLE: avail[CONF_PAYLOAD_AVAILABLE],
CONF_PAYLOAD_NOT_AVAILABLE: avail[CONF_PAYLOAD_NOT_AVAILABLE],
}
self._avail_config = config
async def _availability_subscribe_topics(self):
"""(Re)Subscribe to topics."""
@callback
@log_messages(self.hass, self.entity_id)
def availability_message_received(msg: Message) -> None:
"""Handle a new received MQTT availability message."""
topic = msg.topic
if msg.payload == self._avail_topics[topic][CONF_PAYLOAD_AVAILABLE]:
self._available = True
elif msg.payload == self._avail_topics[topic][CONF_PAYLOAD_NOT_AVAILABLE]:
self._available = False
self.async_write_ha_state()
topics = {}
for topic in self._avail_topics:
topics[f"availability_{topic}"] = {
"topic": topic,
"msg_callback": availability_message_received,
"qos": self._avail_config[CONF_QOS],
}
self._availability_sub_state = await async_subscribe_topics(
self.hass,
self._availability_sub_state,
topics,
)
@callback
def async_mqtt_connect(self):
"""Update state on connection/disconnection to MQTT broker."""
if not self.hass.is_stopping:
self.async_write_ha_state()
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
self._availability_sub_state = await async_unsubscribe_topics(
self.hass, self._availability_sub_state
)
@property
def available(self) -> bool:
"""Return if the device is available."""
if not self.hass.data[DATA_MQTT].connected and not self.hass.is_stopping:
return False
return not self._avail_topics or self._available
async def cleanup_device_registry(hass, device_id):
"""Remove device registry entry if there are no remaining entities or triggers."""
# Local import to avoid circular dependencies
# pylint: disable=import-outside-toplevel
from . import device_trigger, tag
device_registry = await hass.helpers.device_registry.async_get_registry()
entity_registry = await hass.helpers.entity_registry.async_get_registry()
if (
device_id
and not hass.helpers.entity_registry.async_entries_for_device(
entity_registry, device_id, include_disabled_entities=True
)
and not await device_trigger.async_get_triggers(hass, device_id)
and not tag.async_has_tags(hass, device_id)
):
device_registry.async_remove_device(device_id)
class MqttDiscoveryUpdate(Entity):
"""Mixin used to handle updated discovery message."""
def __init__(self, discovery_data, discovery_update=None) -> None:
"""Initialize the discovery update mixin."""
self._discovery_data = discovery_data
self._discovery_update = discovery_update
self._remove_signal = None
self._removed_from_hass = False
async def async_added_to_hass(self) -> None:
"""Subscribe to discovery updates."""
await super().async_added_to_hass()
self._removed_from_hass = False
discovery_hash = (
self._discovery_data[ATTR_DISCOVERY_HASH] if self._discovery_data else None
)
async def _async_remove_state_and_registry_entry(self) -> None:
"""Remove entity's state and entity registry entry.
Remove entity from entity registry if it is registered, this also removes the state.
If the entity is not in the entity registry, just remove the state.
"""
entity_registry = (
await self.hass.helpers.entity_registry.async_get_registry()
)
if entity_registry.async_is_registered(self.entity_id):
entity_entry = entity_registry.async_get(self.entity_id)
entity_registry.async_remove(self.entity_id)
await cleanup_device_registry(self.hass, entity_entry.device_id)
else:
await self.async_remove()
async def discovery_callback(payload):
"""Handle discovery update."""
_LOGGER.info(
"Got update for entity with hash: %s '%s'",
discovery_hash,
payload,
)
old_payload = self._discovery_data[ATTR_DISCOVERY_PAYLOAD]
debug_info.update_entity_discovery_data(self.hass, payload, self.entity_id)
if not payload:
# Empty payload: Remove component
_LOGGER.info("Removing component: %s", self.entity_id)
self._cleanup_discovery_on_remove()
await _async_remove_state_and_registry_entry(self)
elif self._discovery_update:
if old_payload != self._discovery_data[ATTR_DISCOVERY_PAYLOAD]:
# Non-empty, changed payload: Notify component
_LOGGER.info("Updating component: %s", self.entity_id)
await self._discovery_update(payload)
else:
# Non-empty, unchanged payload: Ignore to avoid changing states
_LOGGER.info("Ignoring unchanged update for: %s", self.entity_id)
async_dispatcher_send(
self.hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None
)
if discovery_hash:
debug_info.add_entity_discovery_data(
self.hass, self._discovery_data, self.entity_id
)
# Set in case the entity has been removed and is re-added, for example when changing entity_id
set_discovery_hash(self.hass, discovery_hash)
self._remove_signal = async_dispatcher_connect(
self.hass,
MQTT_DISCOVERY_UPDATED.format(discovery_hash),
discovery_callback,
)
async_dispatcher_send(
self.hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None
)
async def async_removed_from_registry(self) -> None:
"""Clear retained discovery topic in broker."""
if not self._removed_from_hass:
discovery_topic = self._discovery_data[ATTR_DISCOVERY_TOPIC]
publish(self.hass, discovery_topic, "", retain=True)
@callback
def add_to_platform_abort(self) -> None:
"""Abort adding an entity to a platform."""
if self._discovery_data:
discovery_hash = self._discovery_data[ATTR_DISCOVERY_HASH]
clear_discovery_hash(self.hass, discovery_hash)
async_dispatcher_send(
self.hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None
)
super().add_to_platform_abort()
async def async_will_remove_from_hass(self) -> None:
"""Stop listening to signal and cleanup discovery data.."""
self._cleanup_discovery_on_remove()
def _cleanup_discovery_on_remove(self) -> None:
"""Stop listening to signal and cleanup discovery data."""
if self._discovery_data and not self._removed_from_hass:
debug_info.remove_entity_data(self.hass, self.entity_id)
clear_discovery_hash(self.hass, self._discovery_data[ATTR_DISCOVERY_HASH])
self._removed_from_hass = True
if self._remove_signal:
self._remove_signal()
self._remove_signal = None
def device_info_from_config(config):
"""Return a device description for device registry."""
if not config:
return None
info = {
"identifiers": {(DOMAIN, id_) for id_ in config[CONF_IDENTIFIERS]},
"connections": {tuple(x) for x in config[CONF_CONNECTIONS]},
}
if CONF_MANUFACTURER in config:
info["manufacturer"] = config[CONF_MANUFACTURER]
if CONF_MODEL in config:
info["model"] = config[CONF_MODEL]
if CONF_NAME in config:
info["name"] = config[CONF_NAME]
if CONF_SW_VERSION in config:
info["sw_version"] = config[CONF_SW_VERSION]
if CONF_VIA_DEVICE in config:
info["via_device"] = (DOMAIN, config[CONF_VIA_DEVICE])
return info
class MqttEntityDeviceInfo(Entity):
"""Mixin used for mqtt platforms that support the device registry."""
def __init__(self, device_config: Optional[ConfigType], config_entry=None) -> None:
"""Initialize the device mixin."""
self._device_config = device_config
self._config_entry = config_entry
async def device_info_discovery_update(self, config: dict):
"""Handle updated discovery message."""
self._device_config = config.get(CONF_DEVICE)
device_registry = await self.hass.helpers.device_registry.async_get_registry()
config_entry_id = self._config_entry.entry_id
device_info = self.device_info
if config_entry_id is not None and device_info is not None:
device_info["config_entry_id"] = config_entry_id
device_registry.async_get_or_create(**device_info)
@property
def device_info(self):
"""Return a device description for device registry."""
return device_info_from_config(self._device_config)
@websocket_api.websocket_command( @websocket_api.websocket_command(
{vol.Required("type"): "mqtt/device/debug_info", vol.Required("device_id"): str} {vol.Required("type"): "mqtt/device/debug_info", vol.Required("device_id"): str}
) )

View file

@ -37,22 +37,27 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import ( from . import (
ATTR_DISCOVERY_HASH,
CONF_COMMAND_TOPIC, CONF_COMMAND_TOPIC,
CONF_QOS, CONF_QOS,
CONF_RETAIN, CONF_RETAIN,
CONF_STATE_TOPIC, CONF_STATE_TOPIC,
DOMAIN, DOMAIN,
PLATFORMS, PLATFORMS,
subscription,
)
from .. import mqtt
from .const import ATTR_DISCOVERY_HASH
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from .mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from .. import mqtt
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -82,7 +87,7 @@ PLATFORM_SCHEMA = (
CONF_COMMAND_TEMPLATE, default=DEFAULT_COMMAND_TEMPLATE CONF_COMMAND_TEMPLATE, default=DEFAULT_COMMAND_TEMPLATE
): cv.template, ): cv.template,
vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Required(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
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,
@ -97,8 +102,8 @@ PLATFORM_SCHEMA = (
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
) )

View file

@ -31,21 +31,20 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from . import ( from . import CONF_QOS, CONF_STATE_TOPIC, DOMAIN, PLATFORMS, subscription
ATTR_DISCOVERY_HASH, from .. import mqtt
CONF_QOS, from .const import ATTR_DISCOVERY_HASH
CONF_STATE_TOPIC, from .debug_info import log_messages
DOMAIN, from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
PLATFORMS, from .mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from .. import mqtt
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -59,7 +58,7 @@ CONF_EXPIRE_AFTER = "expire_after"
PLATFORM_SCHEMA = ( PLATFORM_SCHEMA = (
mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( mqtt.MQTT_RO_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
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,
@ -70,8 +69,8 @@ PLATFORM_SCHEMA = (
vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
) )

View file

@ -15,20 +15,20 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import ( from . import CONF_QOS, DOMAIN, PLATFORMS, subscription
ATTR_DISCOVERY_HASH, from .. import mqtt
CONF_QOS, from .const import ATTR_DISCOVERY_HASH
DOMAIN, from .debug_info import log_messages
PLATFORMS, from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from .mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from .. import mqtt
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -38,14 +38,14 @@ DEFAULT_NAME = "MQTT Camera"
PLATFORM_SCHEMA = ( PLATFORM_SCHEMA = (
mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
) )

View file

@ -55,21 +55,26 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import ( from . import (
ATTR_DISCOVERY_HASH,
CONF_QOS, CONF_QOS,
CONF_RETAIN, CONF_RETAIN,
DOMAIN, DOMAIN,
MQTT_BASE_PLATFORM_SCHEMA, MQTT_BASE_PLATFORM_SCHEMA,
PLATFORMS, PLATFORMS,
subscription,
)
from .. import mqtt
from .const import ATTR_DISCOVERY_HASH
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from .mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from .. import mqtt
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -174,7 +179,7 @@ PLATFORM_SCHEMA = (
vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_CURRENT_TEMP_TEMPLATE): cv.template, vol.Optional(CONF_CURRENT_TEMP_TEMPLATE): cv.template,
vol.Optional(CONF_CURRENT_TEMP_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_CURRENT_TEMP_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional( vol.Optional(
CONF_FAN_MODE_LIST, CONF_FAN_MODE_LIST,
@ -237,8 +242,8 @@ PLATFORM_SCHEMA = (
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
) )

View file

@ -41,22 +41,27 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import ( from . import (
ATTR_DISCOVERY_HASH,
CONF_COMMAND_TOPIC, CONF_COMMAND_TOPIC,
CONF_QOS, CONF_QOS,
CONF_RETAIN, CONF_RETAIN,
CONF_STATE_TOPIC, CONF_STATE_TOPIC,
DOMAIN, DOMAIN,
PLATFORMS, PLATFORMS,
subscription,
)
from .. import mqtt
from .const import ATTR_DISCOVERY_HASH
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from .mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from .. import mqtt
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -126,7 +131,7 @@ PLATFORM_SCHEMA = vol.All(
mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_GET_POSITION_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_GET_POSITION_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@ -167,8 +172,8 @@ PLATFORM_SCHEMA = vol.All(
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema), .extend(MQTT_JSON_ATTRS_SCHEMA.schema),
validate_options, validate_options,
) )

View file

@ -9,8 +9,9 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_send,
) )
from . import ATTR_DISCOVERY_HASH, device_trigger from . import device_trigger
from .. import mqtt from .. import mqtt
from .const import ATTR_DISCOVERY_HASH
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View file

@ -25,17 +25,20 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_send,
) )
from .. import ( from .. import subscription
MqttAttributes,
MqttAvailability,
MqttDiscoveryUpdate,
MqttEntityDeviceInfo,
subscription,
)
from ... import mqtt from ... import mqtt
from ..const import ATTR_DISCOVERY_HASH, CONF_QOS, CONF_STATE_TOPIC from ..const import ATTR_DISCOVERY_HASH, CONF_QOS, CONF_STATE_TOPIC
from ..debug_info import log_messages from ..debug_info import log_messages
from ..discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash from ..discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from ..mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes,
MqttAvailability,
MqttDiscoveryUpdate,
MqttEntityDeviceInfo,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -46,7 +49,7 @@ CONF_SOURCE_TYPE = "source_type"
PLATFORM_SCHEMA_DISCOVERY = ( PLATFORM_SCHEMA_DISCOVERY = (
mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( mqtt.MQTT_RO_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_ICON): cv.icon, vol.Optional(CONF_ICON): cv.icon,
vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_PAYLOAD_HOME, default=STATE_HOME): cv.string, vol.Optional(CONF_PAYLOAD_HOME, default=STATE_HOME): cv.string,
@ -55,8 +58,8 @@ PLATFORM_SCHEMA_DISCOVERY = (
vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
) )

View file

@ -7,7 +7,13 @@ import voluptuous as vol
from homeassistant.components.automation import AutomationActionType from homeassistant.components.automation import AutomationActionType
from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA
from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE from homeassistant.const import (
CONF_DEVICE,
CONF_DEVICE_ID,
CONF_DOMAIN,
CONF_PLATFORM,
CONF_TYPE,
)
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
@ -17,21 +23,18 @@ from homeassistant.helpers.dispatcher import (
) )
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import ( from . import CONF_PAYLOAD, CONF_QOS, DOMAIN, debug_info, trigger as mqtt_trigger
ATTR_DISCOVERY_HASH,
ATTR_DISCOVERY_TOPIC,
CONF_CONNECTIONS,
CONF_DEVICE,
CONF_IDENTIFIERS,
CONF_PAYLOAD,
CONF_QOS,
DOMAIN,
cleanup_device_registry,
debug_info,
trigger as mqtt_trigger,
)
from .. import mqtt from .. import mqtt
from .const import ATTR_DISCOVERY_HASH, ATTR_DISCOVERY_TOPIC
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_UPDATED, clear_discovery_hash from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_UPDATED, clear_discovery_hash
from .mixins import (
CONF_CONNECTIONS,
CONF_IDENTIFIERS,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
cleanup_device_registry,
device_info_from_config,
validate_device_has_at_least_one_identifier,
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -62,13 +65,13 @@ TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend(
TRIGGER_DISCOVERY_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( TRIGGER_DISCOVERY_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend(
{ {
vol.Required(CONF_AUTOMATION_TYPE): str, vol.Required(CONF_AUTOMATION_TYPE): str,
vol.Required(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Required(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_PAYLOAD, default=None): vol.Any(None, cv.string), vol.Optional(CONF_PAYLOAD, default=None): vol.Any(None, cv.string),
vol.Required(CONF_TYPE): cv.string, vol.Required(CONF_TYPE): cv.string,
vol.Required(CONF_SUBTYPE): cv.string, vol.Required(CONF_SUBTYPE): cv.string,
}, },
mqtt.validate_device_has_at_least_one_identifier, validate_device_has_at_least_one_identifier,
) )
DEVICE_TRIGGERS = "mqtt_device_triggers" DEVICE_TRIGGERS = "mqtt_device_triggers"
@ -172,7 +175,7 @@ async def _update_device(hass, config_entry, config):
"""Update device registry.""" """Update device registry."""
device_registry = await hass.helpers.device_registry.async_get_registry() device_registry = await hass.helpers.device_registry.async_get_registry()
config_entry_id = config_entry.entry_id config_entry_id = config_entry.entry_id
device_info = mqtt.device_info_from_config(config[CONF_DEVICE]) device_info = device_info_from_config(config[CONF_DEVICE])
if config_entry_id is not None and device_info is not None: if config_entry_id is not None and device_info is not None:
device_info["config_entry_id"] = config_entry_id device_info["config_entry_id"] = config_entry_id

View file

@ -33,22 +33,27 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import ( from . import (
ATTR_DISCOVERY_HASH,
CONF_COMMAND_TOPIC, CONF_COMMAND_TOPIC,
CONF_QOS, CONF_QOS,
CONF_RETAIN, CONF_RETAIN,
CONF_STATE_TOPIC, CONF_STATE_TOPIC,
DOMAIN, DOMAIN,
PLATFORMS, PLATFORMS,
subscription,
)
from .. import mqtt
from .const import ATTR_DISCOVERY_HASH
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from .mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from .. import mqtt
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -80,7 +85,7 @@ OSCILLATION = "oscillation"
PLATFORM_SCHEMA = ( PLATFORM_SCHEMA = (
mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( mqtt.MQTT_RW_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_OSCILLATION_COMMAND_TOPIC): mqtt.valid_publish_topic,
@ -109,8 +114,8 @@ PLATFORM_SCHEMA = (
vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
) )

View file

@ -11,7 +11,8 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from .. import ATTR_DISCOVERY_HASH, DOMAIN, PLATFORMS from .. import DOMAIN, PLATFORMS
from ..const import ATTR_DISCOVERY_HASH
from ..discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash from ..discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from .schema import CONF_SCHEMA, MQTT_LIGHT_SCHEMA_SCHEMA from .schema import CONF_SCHEMA, MQTT_LIGHT_SCHEMA_SCHEMA
from .schema_basic import PLATFORM_SCHEMA_BASIC, async_setup_entity_basic from .schema_basic import PLATFORM_SCHEMA_BASIC, async_setup_entity_basic

View file

@ -31,19 +31,18 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
from .. import ( from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription
CONF_COMMAND_TOPIC, from ... import mqtt
CONF_QOS, from ..debug_info import log_messages
CONF_RETAIN, from ..mixins import (
CONF_STATE_TOPIC, MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from ... import mqtt
from ..debug_info import log_messages
from .schema import MQTT_LIGHT_SCHEMA_SCHEMA from .schema import MQTT_LIGHT_SCHEMA_SCHEMA
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -114,7 +113,7 @@ PLATFORM_SCHEMA_BASIC = (
vol.Optional(CONF_COLOR_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_COLOR_TEMP_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_COLOR_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_COLOR_TEMP_STATE_TOPIC): mqtt.valid_subscribe_topic,
vol.Optional(CONF_COLOR_TEMP_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_COLOR_TEMP_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_EFFECT_COMMAND_TOPIC): mqtt.valid_publish_topic, vol.Optional(CONF_EFFECT_COMMAND_TOPIC): mqtt.valid_publish_topic,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_EFFECT_STATE_TOPIC): mqtt.valid_subscribe_topic, vol.Optional(CONF_EFFECT_STATE_TOPIC): mqtt.valid_subscribe_topic,
@ -148,8 +147,8 @@ PLATFORM_SCHEMA_BASIC = (
vol.Optional(CONF_XY_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_XY_VALUE_TEMPLATE): cv.template,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
.extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema)
) )

View file

@ -42,19 +42,18 @@ from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
from .. import ( from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription
CONF_COMMAND_TOPIC, from ... import mqtt
CONF_QOS, from ..debug_info import log_messages
CONF_RETAIN, from ..mixins import (
CONF_STATE_TOPIC, MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from ... import mqtt
from ..debug_info import log_messages
from .schema import MQTT_LIGHT_SCHEMA_SCHEMA from .schema import MQTT_LIGHT_SCHEMA_SCHEMA
from .schema_basic import CONF_BRIGHTNESS_SCALE from .schema_basic import CONF_BRIGHTNESS_SCALE
@ -93,7 +92,7 @@ PLATFORM_SCHEMA_JSON = (
CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE CONF_BRIGHTNESS_SCALE, default=DEFAULT_BRIGHTNESS_SCALE
): vol.All(vol.Coerce(int), vol.Range(min=1)), ): vol.All(vol.Coerce(int), vol.Range(min=1)),
vol.Optional(CONF_COLOR_TEMP, default=DEFAULT_COLOR_TEMP): cv.boolean, vol.Optional(CONF_COLOR_TEMP, default=DEFAULT_COLOR_TEMP): cv.boolean,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_EFFECT, default=DEFAULT_EFFECT): cv.boolean, vol.Optional(CONF_EFFECT, default=DEFAULT_EFFECT): cv.boolean,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional( vol.Optional(
@ -118,8 +117,8 @@ PLATFORM_SCHEMA_JSON = (
vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean, vol.Optional(CONF_XY, default=DEFAULT_XY): cv.boolean,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
.extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema)
) )

View file

@ -33,19 +33,18 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.restore_state import RestoreEntity
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
from .. import ( from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription
CONF_COMMAND_TOPIC, from ... import mqtt
CONF_QOS, from ..debug_info import log_messages
CONF_RETAIN, from ..mixins import (
CONF_STATE_TOPIC, MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from ... import mqtt
from ..debug_info import log_messages
from .schema import MQTT_LIGHT_SCHEMA_SCHEMA from .schema import MQTT_LIGHT_SCHEMA_SCHEMA
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -77,7 +76,7 @@ PLATFORM_SCHEMA_TEMPLATE = (
vol.Optional(CONF_COLOR_TEMP_TEMPLATE): cv.template, vol.Optional(CONF_COLOR_TEMP_TEMPLATE): cv.template,
vol.Required(CONF_COMMAND_OFF_TEMPLATE): cv.template, vol.Required(CONF_COMMAND_OFF_TEMPLATE): cv.template,
vol.Required(CONF_COMMAND_ON_TEMPLATE): cv.template, vol.Required(CONF_COMMAND_ON_TEMPLATE): cv.template,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]), vol.Optional(CONF_EFFECT_LIST): vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_EFFECT_TEMPLATE): cv.template, vol.Optional(CONF_EFFECT_TEMPLATE): cv.template,
vol.Optional(CONF_GREEN_TEMPLATE): cv.template, vol.Optional(CONF_GREEN_TEMPLATE): cv.template,
@ -91,8 +90,8 @@ PLATFORM_SCHEMA_TEMPLATE = (
vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_WHITE_VALUE_TEMPLATE): cv.template,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
.extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema) .extend(MQTT_LIGHT_SCHEMA_SCHEMA.schema)
) )

View file

@ -22,22 +22,27 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import ( from . import (
ATTR_DISCOVERY_HASH,
CONF_COMMAND_TOPIC, CONF_COMMAND_TOPIC,
CONF_QOS, CONF_QOS,
CONF_RETAIN, CONF_RETAIN,
CONF_STATE_TOPIC, CONF_STATE_TOPIC,
DOMAIN, DOMAIN,
PLATFORMS, PLATFORMS,
subscription,
)
from .. import mqtt
from .const import ATTR_DISCOVERY_HASH
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from .mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from .. import mqtt
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -57,7 +62,7 @@ DEFAULT_STATE_UNLOCKED = "UNLOCKED"
PLATFORM_SCHEMA = ( PLATFORM_SCHEMA = (
mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( mqtt.MQTT_RW_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK): cv.string, vol.Optional(CONF_PAYLOAD_LOCK, default=DEFAULT_PAYLOAD_LOCK): cv.string,
@ -71,8 +76,8 @@ PLATFORM_SCHEMA = (
vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
) )

View file

@ -0,0 +1,472 @@
"""MQTT component mixins and helpers."""
import json
import logging
from typing import Optional
import voluptuous as vol
from homeassistant.const import CONF_DEVICE, CONF_NAME
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
async_dispatcher_send,
)
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType
from . import CONF_TOPIC, DATA_MQTT, debug_info, publish
from .const import (
ATTR_DISCOVERY_HASH,
ATTR_DISCOVERY_PAYLOAD,
ATTR_DISCOVERY_TOPIC,
CONF_QOS,
DEFAULT_PAYLOAD_AVAILABLE,
DEFAULT_PAYLOAD_NOT_AVAILABLE,
DOMAIN,
MQTT_CONNECTED,
MQTT_DISCONNECTED,
)
from .debug_info import log_messages
from .discovery import (
MQTT_DISCOVERY_DONE,
MQTT_DISCOVERY_UPDATED,
clear_discovery_hash,
set_discovery_hash,
)
from .models import Message
from .subscription import async_subscribe_topics, async_unsubscribe_topics
from .util import valid_subscribe_topic
_LOGGER = logging.getLogger(__name__)
CONF_AVAILABILITY = "availability"
CONF_AVAILABILITY_TOPIC = "availability_topic"
CONF_PAYLOAD_AVAILABLE = "payload_available"
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
CONF_JSON_ATTRS_TOPIC = "json_attributes_topic"
CONF_JSON_ATTRS_TEMPLATE = "json_attributes_template"
CONF_IDENTIFIERS = "identifiers"
CONF_CONNECTIONS = "connections"
CONF_MANUFACTURER = "manufacturer"
CONF_MODEL = "model"
CONF_SW_VERSION = "sw_version"
CONF_VIA_DEVICE = "via_device"
CONF_DEPRECATED_VIA_HUB = "via_hub"
MQTT_AVAILABILITY_SINGLE_SCHEMA = vol.Schema(
{
vol.Exclusive(CONF_AVAILABILITY_TOPIC, "availability"): valid_subscribe_topic,
vol.Optional(
CONF_PAYLOAD_AVAILABLE, default=DEFAULT_PAYLOAD_AVAILABLE
): cv.string,
vol.Optional(
CONF_PAYLOAD_NOT_AVAILABLE, default=DEFAULT_PAYLOAD_NOT_AVAILABLE
): cv.string,
}
)
MQTT_AVAILABILITY_LIST_SCHEMA = vol.Schema(
{
vol.Exclusive(CONF_AVAILABILITY, "availability"): vol.All(
cv.ensure_list,
[
{
vol.Optional(CONF_TOPIC): valid_subscribe_topic,
vol.Optional(
CONF_PAYLOAD_AVAILABLE, default=DEFAULT_PAYLOAD_AVAILABLE
): cv.string,
vol.Optional(
CONF_PAYLOAD_NOT_AVAILABLE,
default=DEFAULT_PAYLOAD_NOT_AVAILABLE,
): cv.string,
}
],
),
}
)
MQTT_AVAILABILITY_SCHEMA = MQTT_AVAILABILITY_SINGLE_SCHEMA.extend(
MQTT_AVAILABILITY_LIST_SCHEMA.schema
)
def validate_device_has_at_least_one_identifier(value: ConfigType) -> ConfigType:
"""Validate that a device info entry has at least one identifying value."""
if value.get(CONF_IDENTIFIERS) or value.get(CONF_CONNECTIONS):
return value
raise vol.Invalid(
"Device must have at least one identifying value in "
"'identifiers' and/or 'connections'"
)
MQTT_ENTITY_DEVICE_INFO_SCHEMA = vol.All(
cv.deprecated(CONF_DEPRECATED_VIA_HUB, CONF_VIA_DEVICE),
vol.Schema(
{
vol.Optional(CONF_IDENTIFIERS, default=list): vol.All(
cv.ensure_list, [cv.string]
),
vol.Optional(CONF_CONNECTIONS, default=list): vol.All(
cv.ensure_list, [vol.All(vol.Length(2), [cv.string])]
),
vol.Optional(CONF_MANUFACTURER): cv.string,
vol.Optional(CONF_MODEL): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_SW_VERSION): cv.string,
vol.Optional(CONF_VIA_DEVICE): cv.string,
}
),
validate_device_has_at_least_one_identifier,
)
MQTT_JSON_ATTRS_SCHEMA = vol.Schema(
{
vol.Optional(CONF_JSON_ATTRS_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_JSON_ATTRS_TEMPLATE): cv.template,
}
)
class MqttAttributes(Entity):
"""Mixin used for platforms that support JSON attributes."""
def __init__(self, config: dict) -> None:
"""Initialize the JSON attributes mixin."""
self._attributes = None
self._attributes_sub_state = None
self._attributes_config = config
async def async_added_to_hass(self) -> None:
"""Subscribe MQTT events."""
await super().async_added_to_hass()
await self._attributes_subscribe_topics()
async def attributes_discovery_update(self, config: dict):
"""Handle updated discovery message."""
self._attributes_config = config
await self._attributes_subscribe_topics()
async def _attributes_subscribe_topics(self):
"""(Re)Subscribe to topics."""
attr_tpl = self._attributes_config.get(CONF_JSON_ATTRS_TEMPLATE)
if attr_tpl is not None:
attr_tpl.hass = self.hass
@callback
@log_messages(self.hass, self.entity_id)
def attributes_message_received(msg: Message) -> None:
try:
payload = msg.payload
if attr_tpl is not None:
payload = attr_tpl.async_render_with_possible_json_value(payload)
json_dict = json.loads(payload)
if isinstance(json_dict, dict):
self._attributes = json_dict
self.async_write_ha_state()
else:
_LOGGER.warning("JSON result was not a dictionary")
self._attributes = None
except ValueError:
_LOGGER.warning("Erroneous JSON: %s", payload)
self._attributes = None
self._attributes_sub_state = await async_subscribe_topics(
self.hass,
self._attributes_sub_state,
{
CONF_JSON_ATTRS_TOPIC: {
"topic": self._attributes_config.get(CONF_JSON_ATTRS_TOPIC),
"msg_callback": attributes_message_received,
"qos": self._attributes_config.get(CONF_QOS),
}
},
)
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
self._attributes_sub_state = await async_unsubscribe_topics(
self.hass, self._attributes_sub_state
)
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self._attributes
class MqttAvailability(Entity):
"""Mixin used for platforms that report availability."""
def __init__(self, config: dict) -> None:
"""Initialize the availability mixin."""
self._availability_sub_state = None
self._available = False
self._availability_setup_from_config(config)
async def async_added_to_hass(self) -> None:
"""Subscribe MQTT events."""
await super().async_added_to_hass()
await self._availability_subscribe_topics()
self.async_on_remove(
async_dispatcher_connect(self.hass, MQTT_CONNECTED, self.async_mqtt_connect)
)
self.async_on_remove(
async_dispatcher_connect(
self.hass, MQTT_DISCONNECTED, self.async_mqtt_connect
)
)
async def availability_discovery_update(self, config: dict):
"""Handle updated discovery message."""
self._availability_setup_from_config(config)
await self._availability_subscribe_topics()
def _availability_setup_from_config(self, config):
"""(Re)Setup."""
self._avail_topics = {}
if CONF_AVAILABILITY_TOPIC in config:
self._avail_topics[config[CONF_AVAILABILITY_TOPIC]] = {
CONF_PAYLOAD_AVAILABLE: config[CONF_PAYLOAD_AVAILABLE],
CONF_PAYLOAD_NOT_AVAILABLE: config[CONF_PAYLOAD_NOT_AVAILABLE],
}
if CONF_AVAILABILITY in config:
for avail in config[CONF_AVAILABILITY]:
self._avail_topics[avail[CONF_TOPIC]] = {
CONF_PAYLOAD_AVAILABLE: avail[CONF_PAYLOAD_AVAILABLE],
CONF_PAYLOAD_NOT_AVAILABLE: avail[CONF_PAYLOAD_NOT_AVAILABLE],
}
self._avail_config = config
async def _availability_subscribe_topics(self):
"""(Re)Subscribe to topics."""
@callback
@log_messages(self.hass, self.entity_id)
def availability_message_received(msg: Message) -> None:
"""Handle a new received MQTT availability message."""
topic = msg.topic
if msg.payload == self._avail_topics[topic][CONF_PAYLOAD_AVAILABLE]:
self._available = True
elif msg.payload == self._avail_topics[topic][CONF_PAYLOAD_NOT_AVAILABLE]:
self._available = False
self.async_write_ha_state()
topics = {
f"availability_{topic}": {
"topic": topic,
"msg_callback": availability_message_received,
"qos": self._avail_config[CONF_QOS],
}
for topic in self._avail_topics
}
self._availability_sub_state = await async_subscribe_topics(
self.hass,
self._availability_sub_state,
topics,
)
@callback
def async_mqtt_connect(self):
"""Update state on connection/disconnection to MQTT broker."""
if not self.hass.is_stopping:
self.async_write_ha_state()
async def async_will_remove_from_hass(self):
"""Unsubscribe when removed."""
self._availability_sub_state = await async_unsubscribe_topics(
self.hass, self._availability_sub_state
)
@property
def available(self) -> bool:
"""Return if the device is available."""
if not self.hass.data[DATA_MQTT].connected and not self.hass.is_stopping:
return False
return not self._avail_topics or self._available
async def cleanup_device_registry(hass, device_id):
"""Remove device registry entry if there are no remaining entities or triggers."""
# Local import to avoid circular dependencies
# pylint: disable=import-outside-toplevel
from . import device_trigger, tag
device_registry = await hass.helpers.device_registry.async_get_registry()
entity_registry = await hass.helpers.entity_registry.async_get_registry()
if (
device_id
and not hass.helpers.entity_registry.async_entries_for_device(
entity_registry, device_id, include_disabled_entities=True
)
and not await device_trigger.async_get_triggers(hass, device_id)
and not tag.async_has_tags(hass, device_id)
):
device_registry.async_remove_device(device_id)
class MqttDiscoveryUpdate(Entity):
"""Mixin used to handle updated discovery message."""
def __init__(self, discovery_data, discovery_update=None) -> None:
"""Initialize the discovery update mixin."""
self._discovery_data = discovery_data
self._discovery_update = discovery_update
self._remove_signal = None
self._removed_from_hass = False
async def async_added_to_hass(self) -> None:
"""Subscribe to discovery updates."""
await super().async_added_to_hass()
self._removed_from_hass = False
discovery_hash = (
self._discovery_data[ATTR_DISCOVERY_HASH] if self._discovery_data else None
)
async def _async_remove_state_and_registry_entry(self) -> None:
"""Remove entity's state and entity registry entry.
Remove entity from entity registry if it is registered, this also removes the state.
If the entity is not in the entity registry, just remove the state.
"""
entity_registry = (
await self.hass.helpers.entity_registry.async_get_registry()
)
if entity_registry.async_is_registered(self.entity_id):
entity_entry = entity_registry.async_get(self.entity_id)
entity_registry.async_remove(self.entity_id)
await cleanup_device_registry(self.hass, entity_entry.device_id)
else:
await self.async_remove()
async def discovery_callback(payload):
"""Handle discovery update."""
_LOGGER.info(
"Got update for entity with hash: %s '%s'",
discovery_hash,
payload,
)
old_payload = self._discovery_data[ATTR_DISCOVERY_PAYLOAD]
debug_info.update_entity_discovery_data(self.hass, payload, self.entity_id)
if not payload:
# Empty payload: Remove component
_LOGGER.info("Removing component: %s", self.entity_id)
self._cleanup_discovery_on_remove()
await _async_remove_state_and_registry_entry(self)
elif self._discovery_update:
if old_payload != self._discovery_data[ATTR_DISCOVERY_PAYLOAD]:
# Non-empty, changed payload: Notify component
_LOGGER.info("Updating component: %s", self.entity_id)
await self._discovery_update(payload)
else:
# Non-empty, unchanged payload: Ignore to avoid changing states
_LOGGER.info("Ignoring unchanged update for: %s", self.entity_id)
async_dispatcher_send(
self.hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None
)
if discovery_hash:
debug_info.add_entity_discovery_data(
self.hass, self._discovery_data, self.entity_id
)
# Set in case the entity has been removed and is re-added, for example when changing entity_id
set_discovery_hash(self.hass, discovery_hash)
self._remove_signal = async_dispatcher_connect(
self.hass,
MQTT_DISCOVERY_UPDATED.format(discovery_hash),
discovery_callback,
)
async_dispatcher_send(
self.hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None
)
async def async_removed_from_registry(self) -> None:
"""Clear retained discovery topic in broker."""
if not self._removed_from_hass:
discovery_topic = self._discovery_data[ATTR_DISCOVERY_TOPIC]
publish(self.hass, discovery_topic, "", retain=True)
@callback
def add_to_platform_abort(self) -> None:
"""Abort adding an entity to a platform."""
if self._discovery_data:
discovery_hash = self._discovery_data[ATTR_DISCOVERY_HASH]
clear_discovery_hash(self.hass, discovery_hash)
async_dispatcher_send(
self.hass, MQTT_DISCOVERY_DONE.format(discovery_hash), None
)
super().add_to_platform_abort()
async def async_will_remove_from_hass(self) -> None:
"""Stop listening to signal and cleanup discovery data.."""
self._cleanup_discovery_on_remove()
def _cleanup_discovery_on_remove(self) -> None:
"""Stop listening to signal and cleanup discovery data."""
if self._discovery_data and not self._removed_from_hass:
debug_info.remove_entity_data(self.hass, self.entity_id)
clear_discovery_hash(self.hass, self._discovery_data[ATTR_DISCOVERY_HASH])
self._removed_from_hass = True
if self._remove_signal:
self._remove_signal()
self._remove_signal = None
def device_info_from_config(config):
"""Return a device description for device registry."""
if not config:
return None
info = {
"identifiers": {(DOMAIN, id_) for id_ in config[CONF_IDENTIFIERS]},
"connections": {tuple(x) for x in config[CONF_CONNECTIONS]},
}
if CONF_MANUFACTURER in config:
info["manufacturer"] = config[CONF_MANUFACTURER]
if CONF_MODEL in config:
info["model"] = config[CONF_MODEL]
if CONF_NAME in config:
info["name"] = config[CONF_NAME]
if CONF_SW_VERSION in config:
info["sw_version"] = config[CONF_SW_VERSION]
if CONF_VIA_DEVICE in config:
info["via_device"] = (DOMAIN, config[CONF_VIA_DEVICE])
return info
class MqttEntityDeviceInfo(Entity):
"""Mixin used for mqtt platforms that support the device registry."""
def __init__(self, device_config: Optional[ConfigType], config_entry=None) -> None:
"""Initialize the device mixin."""
self._device_config = device_config
self._config_entry = config_entry
async def device_info_discovery_update(self, config: dict):
"""Handle updated discovery message."""
self._device_config = config.get(CONF_DEVICE)
device_registry = await self.hass.helpers.device_registry.async_get_registry()
config_entry_id = self._config_entry.entry_id
device_info = self.device_info
if config_entry_id is not None and device_info is not None:
device_info["config_entry_id"] = config_entry_id
device_registry.async_get_or_create(**device_info)
@property
def device_info(self):
"""Return a device description for device registry."""
return device_info_from_config(self._device_config)

View file

@ -23,21 +23,26 @@ from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import ( from . import (
ATTR_DISCOVERY_HASH,
CONF_COMMAND_TOPIC, CONF_COMMAND_TOPIC,
CONF_QOS, CONF_QOS,
CONF_STATE_TOPIC, CONF_STATE_TOPIC,
DOMAIN, DOMAIN,
PLATFORMS, PLATFORMS,
subscription,
)
from .. import mqtt
from .const import ATTR_DISCOVERY_HASH
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from .mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from .. import mqtt
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -47,15 +52,15 @@ DEFAULT_OPTIMISTIC = False
PLATFORM_SCHEMA = ( PLATFORM_SCHEMA = (
mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( mqtt.MQTT_RW_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
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, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
) )

View file

@ -14,18 +14,11 @@ from homeassistant.helpers.dispatcher import (
from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import ( from . import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, DOMAIN, PLATFORMS
ATTR_DISCOVERY_HASH,
CONF_COMMAND_TOPIC,
CONF_QOS,
CONF_RETAIN,
DOMAIN,
PLATFORMS,
MqttAvailability,
MqttDiscoveryUpdate,
)
from .. import mqtt from .. import mqtt
from .const import ATTR_DISCOVERY_HASH
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from .mixins import MQTT_AVAILABILITY_SCHEMA, MqttAvailability, MqttDiscoveryUpdate
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -41,7 +34,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend(
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,
} }
).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) ).extend(MQTT_AVAILABILITY_SCHEMA.schema)
async def async_setup_platform( async def async_setup_platform(

View file

@ -29,21 +29,20 @@ from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from . import ( from . import CONF_QOS, CONF_STATE_TOPIC, DOMAIN, PLATFORMS, subscription
ATTR_DISCOVERY_HASH, from .. import mqtt
CONF_QOS, from .const import ATTR_DISCOVERY_HASH
CONF_STATE_TOPIC, from .debug_info import log_messages
DOMAIN, from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
PLATFORMS, from .mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from .. import mqtt
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -54,7 +53,7 @@ DEFAULT_FORCE_UPDATE = False
PLATFORM_SCHEMA = ( PLATFORM_SCHEMA = (
mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( mqtt.MQTT_RO_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
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,
@ -64,8 +63,8 @@ PLATFORM_SCHEMA = (
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string, vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
) )

View file

@ -27,22 +27,27 @@ from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.typing import ConfigType, HomeAssistantType from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from . import ( from . import (
ATTR_DISCOVERY_HASH,
CONF_COMMAND_TOPIC, CONF_COMMAND_TOPIC,
CONF_QOS, CONF_QOS,
CONF_RETAIN, CONF_RETAIN,
CONF_STATE_TOPIC, CONF_STATE_TOPIC,
DOMAIN, DOMAIN,
PLATFORMS, PLATFORMS,
subscription,
)
from .. import mqtt
from .const import ATTR_DISCOVERY_HASH
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from .mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from .. import mqtt
from .debug_info import log_messages
from .discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -56,7 +61,7 @@ CONF_STATE_OFF = "state_off"
PLATFORM_SCHEMA = ( PLATFORM_SCHEMA = (
mqtt.MQTT_RW_PLATFORM_SCHEMA.extend( mqtt.MQTT_RW_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
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, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
@ -67,8 +72,8 @@ PLATFORM_SCHEMA = (
vol.Optional(CONF_UNIQUE_ID): cv.string, vol.Optional(CONF_UNIQUE_ID): cv.string,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
) )

View file

@ -3,7 +3,7 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.const import CONF_PLATFORM, CONF_VALUE_TEMPLATE from homeassistant.const import CONF_DEVICE, CONF_PLATFORM, CONF_VALUE_TEMPLATE
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
@ -11,25 +11,23 @@ from homeassistant.helpers.dispatcher import (
async_dispatcher_send, async_dispatcher_send,
) )
from . import ( from . import CONF_QOS, CONF_TOPIC, DOMAIN, subscription
ATTR_DISCOVERY_HASH,
ATTR_DISCOVERY_TOPIC,
CONF_CONNECTIONS,
CONF_DEVICE,
CONF_IDENTIFIERS,
CONF_QOS,
CONF_TOPIC,
DOMAIN,
cleanup_device_registry,
subscription,
)
from .. import mqtt from .. import mqtt
from .const import ATTR_DISCOVERY_HASH, ATTR_DISCOVERY_TOPIC
from .discovery import ( from .discovery import (
MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_DONE,
MQTT_DISCOVERY_NEW, MQTT_DISCOVERY_NEW,
MQTT_DISCOVERY_UPDATED, MQTT_DISCOVERY_UPDATED,
clear_discovery_hash, clear_discovery_hash,
) )
from .mixins import (
CONF_CONNECTIONS,
CONF_IDENTIFIERS,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
cleanup_device_registry,
device_info_from_config,
validate_device_has_at_least_one_identifier,
)
from .util import valid_subscribe_topic from .util import valid_subscribe_topic
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -39,12 +37,12 @@ TAGS = "mqtt_tags"
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Optional(CONF_PLATFORM): "mqtt", vol.Optional(CONF_PLATFORM): "mqtt",
vol.Required(CONF_TOPIC): valid_subscribe_topic, vol.Required(CONF_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
}, },
mqtt.validate_device_has_at_least_one_identifier, validate_device_has_at_least_one_identifier,
) )
@ -236,7 +234,7 @@ async def _update_device(hass, config_entry, config):
"""Update device registry.""" """Update device registry."""
device_registry = await hass.helpers.device_registry.async_get_registry() device_registry = await hass.helpers.device_registry.async_get_registry()
config_entry_id = config_entry.entry_id config_entry_id = config_entry.entry_id
device_info = mqtt.device_info_from_config(config[CONF_DEVICE]) device_info = device_info_from_config(config[CONF_DEVICE])
if config_entry_id is not None and device_info is not None: if config_entry_id is not None and device_info is not None:
device_info["config_entry_id"] = config_entry_id device_info["config_entry_id"] = config_entry_id

View file

@ -10,7 +10,8 @@ from homeassistant.helpers.dispatcher import (
) )
from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.reload import async_setup_reload_service
from .. import ATTR_DISCOVERY_HASH, DOMAIN as MQTT_DOMAIN, PLATFORMS from .. import DOMAIN as MQTT_DOMAIN, PLATFORMS
from ..const import ATTR_DISCOVERY_HASH
from ..discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash from ..discovery import MQTT_DISCOVERY_DONE, MQTT_DISCOVERY_NEW, clear_discovery_hash
from .schema import CONF_SCHEMA, LEGACY, MQTT_VACUUM_SCHEMA, STATE from .schema import CONF_SCHEMA, LEGACY, MQTT_VACUUM_SCHEMA, STATE
from .schema_legacy import PLATFORM_SCHEMA_LEGACY, async_setup_entity_legacy from .schema_legacy import PLATFORM_SCHEMA_LEGACY, async_setup_entity_legacy

View file

@ -28,15 +28,18 @@ from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.icon import icon_for_battery_level from homeassistant.helpers.icon import icon_for_battery_level
from .. import ( from .. import subscription
from ... import mqtt
from ..debug_info import log_messages
from ..mixins import (
MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from ... import mqtt
from ..debug_info import log_messages
from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -120,7 +123,7 @@ PLATFORM_SCHEMA_LEGACY = (
vol.Inclusive(CONF_CHARGING_TOPIC, "charging"): mqtt.valid_publish_topic, vol.Inclusive(CONF_CHARGING_TOPIC, "charging"): mqtt.valid_publish_topic,
vol.Inclusive(CONF_CLEANING_TEMPLATE, "cleaning"): cv.template, vol.Inclusive(CONF_CLEANING_TEMPLATE, "cleaning"): cv.template,
vol.Inclusive(CONF_CLEANING_TOPIC, "cleaning"): mqtt.valid_publish_topic, vol.Inclusive(CONF_CLEANING_TOPIC, "cleaning"): mqtt.valid_publish_topic,
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
vol.Inclusive(CONF_DOCKED_TEMPLATE, "docked"): cv.template, vol.Inclusive(CONF_DOCKED_TEMPLATE, "docked"): cv.template,
vol.Inclusive(CONF_DOCKED_TOPIC, "docked"): mqtt.valid_publish_topic, vol.Inclusive(CONF_DOCKED_TOPIC, "docked"): mqtt.valid_publish_topic,
vol.Inclusive(CONF_ERROR_TEMPLATE, "error"): cv.template, vol.Inclusive(CONF_ERROR_TEMPLATE, "error"): cv.template,
@ -160,8 +163,8 @@ PLATFORM_SCHEMA_LEGACY = (
vol.Optional(mqtt.CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, vol.Optional(mqtt.CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
.extend(MQTT_VACUUM_SCHEMA.schema) .extend(MQTT_VACUUM_SCHEMA.schema)
) )

View file

@ -32,19 +32,18 @@ from homeassistant.const import (
from homeassistant.core import callback from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from .. import ( from .. import CONF_COMMAND_TOPIC, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC, subscription
CONF_COMMAND_TOPIC, from ... import mqtt
CONF_QOS, from ..debug_info import log_messages
CONF_RETAIN, from ..mixins import (
CONF_STATE_TOPIC, MQTT_AVAILABILITY_SCHEMA,
MQTT_ENTITY_DEVICE_INFO_SCHEMA,
MQTT_JSON_ATTRS_SCHEMA,
MqttAttributes, MqttAttributes,
MqttAvailability, MqttAvailability,
MqttDiscoveryUpdate, MqttDiscoveryUpdate,
MqttEntityDeviceInfo, MqttEntityDeviceInfo,
subscription,
) )
from ... import mqtt
from ..debug_info import log_messages
from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services from .schema import MQTT_VACUUM_SCHEMA, services_to_strings, strings_to_services
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -120,7 +119,7 @@ DEFAULT_PAYLOAD_PAUSE = "pause"
PLATFORM_SCHEMA_STATE = ( PLATFORM_SCHEMA_STATE = (
mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend(
{ {
vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, vol.Optional(CONF_DEVICE): MQTT_ENTITY_DEVICE_INFO_SCHEMA,
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]
), ),
@ -148,8 +147,8 @@ PLATFORM_SCHEMA_STATE = (
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
} }
) )
.extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) .extend(MQTT_AVAILABILITY_SCHEMA.schema)
.extend(mqtt.MQTT_JSON_ATTRS_SCHEMA.schema) .extend(MQTT_JSON_ATTRS_SCHEMA.schema)
.extend(MQTT_VACUUM_SCHEMA.schema) .extend(MQTT_VACUUM_SCHEMA.schema)
) )

View file

@ -10,6 +10,7 @@ import voluptuous as vol
from homeassistant.components import mqtt, websocket_api from homeassistant.components import mqtt, websocket_api
from homeassistant.components.mqtt import debug_info from homeassistant.components.mqtt import debug_info
from homeassistant.components.mqtt.mixins import MQTT_ENTITY_DEVICE_INFO_SCHEMA
from homeassistant.const import ( from homeassistant.const import (
ATTR_DOMAIN, ATTR_DOMAIN,
ATTR_SERVICE, ATTR_SERVICE,
@ -241,12 +242,12 @@ def test_validate_publish_topic():
def test_entity_device_info_schema(): def test_entity_device_info_schema():
"""Test MQTT entity device info validation.""" """Test MQTT entity device info validation."""
# just identifier # just identifier
mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA({"identifiers": ["abcd"]}) MQTT_ENTITY_DEVICE_INFO_SCHEMA({"identifiers": ["abcd"]})
mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA({"identifiers": "abcd"}) MQTT_ENTITY_DEVICE_INFO_SCHEMA({"identifiers": "abcd"})
# just connection # just connection
mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA({"connections": [["mac", "02:5b:26:a8:dc:12"]]}) MQTT_ENTITY_DEVICE_INFO_SCHEMA({"connections": [["mac", "02:5b:26:a8:dc:12"]]})
# full device info # full device info
mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA( MQTT_ENTITY_DEVICE_INFO_SCHEMA(
{ {
"identifiers": ["helloworld", "hello"], "identifiers": ["helloworld", "hello"],
"connections": [["mac", "02:5b:26:a8:dc:12"], ["zigbee", "zigbee_id"]], "connections": [["mac", "02:5b:26:a8:dc:12"], ["zigbee", "zigbee_id"]],
@ -257,7 +258,7 @@ def test_entity_device_info_schema():
} }
) )
# full device info with via_device # full device info with via_device
mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA( MQTT_ENTITY_DEVICE_INFO_SCHEMA(
{ {
"identifiers": ["helloworld", "hello"], "identifiers": ["helloworld", "hello"],
"connections": [["mac", "02:5b:26:a8:dc:12"], ["zigbee", "zigbee_id"]], "connections": [["mac", "02:5b:26:a8:dc:12"], ["zigbee", "zigbee_id"]],
@ -270,7 +271,7 @@ def test_entity_device_info_schema():
) )
# no identifiers # no identifiers
with pytest.raises(vol.Invalid): with pytest.raises(vol.Invalid):
mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA( MQTT_ENTITY_DEVICE_INFO_SCHEMA(
{ {
"manufacturer": "Whatever", "manufacturer": "Whatever",
"name": "Beer", "name": "Beer",
@ -280,7 +281,7 @@ def test_entity_device_info_schema():
) )
# empty identifiers # empty identifiers
with pytest.raises(vol.Invalid): with pytest.raises(vol.Invalid):
mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA( MQTT_ENTITY_DEVICE_INFO_SCHEMA(
{"identifiers": [], "connections": [], "name": "Beer"} {"identifiers": [], "connections": [], "name": "Beer"}
) )