mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Merge pull request #37836 from home-assistant/rc
This commit is contained in:
commit
ec1df9b427
@ -2,7 +2,7 @@
|
|||||||
"domain": "blink",
|
"domain": "blink",
|
||||||
"name": "Blink",
|
"name": "Blink",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/blink",
|
"documentation": "https://www.home-assistant.io/integrations/blink",
|
||||||
"requirements": ["blinkpy==0.15.0"],
|
"requirements": ["blinkpy==0.15.1"],
|
||||||
"codeowners": ["@fronzbot"],
|
"codeowners": ["@fronzbot"],
|
||||||
"config_flow": true
|
"config_flow": true
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "deCONZ",
|
"name": "deCONZ",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/deconz",
|
"documentation": "https://www.home-assistant.io/integrations/deconz",
|
||||||
"requirements": ["pydeconz==71"],
|
"requirements": ["pydeconz==72"],
|
||||||
"ssdp": [
|
"ssdp": [
|
||||||
{
|
{
|
||||||
"manufacturer": "Royal Philips Electronics"
|
"manufacturer": "Royal Philips Electronics"
|
||||||
|
@ -88,6 +88,8 @@ CONF_TLS_INSECURE = "tls_insecure"
|
|||||||
CONF_TLS_VERSION = "tls_version"
|
CONF_TLS_VERSION = "tls_version"
|
||||||
|
|
||||||
CONF_COMMAND_TOPIC = "command_topic"
|
CONF_COMMAND_TOPIC = "command_topic"
|
||||||
|
CONF_TOPIC = "topic"
|
||||||
|
CONF_AVAILABILITY = "availability"
|
||||||
CONF_AVAILABILITY_TOPIC = "availability_topic"
|
CONF_AVAILABILITY_TOPIC = "availability_topic"
|
||||||
CONF_PAYLOAD_AVAILABLE = "payload_available"
|
CONF_PAYLOAD_AVAILABLE = "payload_available"
|
||||||
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
|
CONF_PAYLOAD_NOT_AVAILABLE = "payload_not_available"
|
||||||
@ -203,9 +205,9 @@ 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_SCHEMA = vol.Schema(
|
MQTT_AVAILABILITY_SINGLE_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_AVAILABILITY_TOPIC): valid_subscribe_topic,
|
vol.Exclusive(CONF_AVAILABILITY_TOPIC, "availability"): valid_subscribe_topic,
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CONF_PAYLOAD_AVAILABLE, default=DEFAULT_PAYLOAD_AVAILABLE
|
CONF_PAYLOAD_AVAILABLE, default=DEFAULT_PAYLOAD_AVAILABLE
|
||||||
): cv.string,
|
): cv.string,
|
||||||
@ -215,6 +217,30 @@ MQTT_AVAILABILITY_SCHEMA = vol.Schema(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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(
|
MQTT_ENTITY_DEVICE_INFO_SCHEMA = vol.All(
|
||||||
cv.deprecated(CONF_DEPRECATED_VIA_HUB, CONF_VIA_DEVICE),
|
cv.deprecated(CONF_DEPRECATED_VIA_HUB, CONF_VIA_DEVICE),
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
@ -989,24 +1015,44 @@ class MqttAvailability(Entity):
|
|||||||
"""Initialize the availability mixin."""
|
"""Initialize the availability mixin."""
|
||||||
self._availability_sub_state = None
|
self._availability_sub_state = None
|
||||||
self._available = False
|
self._available = False
|
||||||
|
self._availability_setup_from_config(config)
|
||||||
self._avail_config = config
|
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Subscribe MQTT events."""
|
"""Subscribe MQTT events."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
await self._availability_subscribe_topics()
|
await self._availability_subscribe_topics()
|
||||||
async_dispatcher_connect(self.hass, MQTT_CONNECTED, self.async_mqtt_connect)
|
|
||||||
async_dispatcher_connect(self.hass, MQTT_DISCONNECTED, self.async_mqtt_connect)
|
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
async_dispatcher_connect(self.hass, MQTT_CONNECTED, self.async_mqtt_connect)
|
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):
|
async def availability_discovery_update(self, config: dict):
|
||||||
"""Handle updated discovery message."""
|
"""Handle updated discovery message."""
|
||||||
self._avail_config = config
|
self._availability_setup_from_config(config)
|
||||||
await self._availability_subscribe_topics()
|
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):
|
async def _availability_subscribe_topics(self):
|
||||||
"""(Re)Subscribe to topics."""
|
"""(Re)Subscribe to topics."""
|
||||||
|
|
||||||
@ -1014,29 +1060,30 @@ class MqttAvailability(Entity):
|
|||||||
@log_messages(self.hass, self.entity_id)
|
@log_messages(self.hass, self.entity_id)
|
||||||
def availability_message_received(msg: Message) -> None:
|
def availability_message_received(msg: Message) -> None:
|
||||||
"""Handle a new received MQTT availability message."""
|
"""Handle a new received MQTT availability message."""
|
||||||
if msg.payload == self._avail_config[CONF_PAYLOAD_AVAILABLE]:
|
topic = msg.topic
|
||||||
|
if msg.payload == self._avail_topics[topic][CONF_PAYLOAD_AVAILABLE]:
|
||||||
self._available = True
|
self._available = True
|
||||||
elif msg.payload == self._avail_config[CONF_PAYLOAD_NOT_AVAILABLE]:
|
elif msg.payload == self._avail_topics[topic][CONF_PAYLOAD_NOT_AVAILABLE]:
|
||||||
self._available = False
|
self._available = False
|
||||||
|
|
||||||
self.async_write_ha_state()
|
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._availability_sub_state = await async_subscribe_topics(
|
||||||
self.hass,
|
self.hass, self._availability_sub_state, topics,
|
||||||
self._availability_sub_state,
|
|
||||||
{
|
|
||||||
"availability_topic": {
|
|
||||||
"topic": self._avail_config.get(CONF_AVAILABILITY_TOPIC),
|
|
||||||
"msg_callback": availability_message_received,
|
|
||||||
"qos": self._avail_config[CONF_QOS],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_mqtt_connect(self):
|
def async_mqtt_connect(self):
|
||||||
"""Update state on connection/disconnection to MQTT broker."""
|
"""Update state on connection/disconnection to MQTT broker."""
|
||||||
if self.hass.is_running:
|
if not self.hass.is_stopping:
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_will_remove_from_hass(self):
|
async def async_will_remove_from_hass(self):
|
||||||
@ -1048,10 +1095,9 @@ class MqttAvailability(Entity):
|
|||||||
@property
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
"""Return if the device is available."""
|
"""Return if the device is available."""
|
||||||
availability_topic = self._avail_config.get(CONF_AVAILABILITY_TOPIC)
|
if not self.hass.data[DATA_MQTT].connected and not self.hass.is_stopping:
|
||||||
if not self.hass.data[DATA_MQTT].connected:
|
|
||||||
return False
|
return False
|
||||||
return availability_topic is None or self._available
|
return not self._avail_topics or self._available
|
||||||
|
|
||||||
|
|
||||||
async def cleanup_device_registry(hass, device_id):
|
async def cleanup_device_registry(hass, device_id):
|
||||||
|
@ -7,6 +7,7 @@ ABBREVIATIONS = {
|
|||||||
"aux_cmd_t": "aux_command_topic",
|
"aux_cmd_t": "aux_command_topic",
|
||||||
"aux_stat_tpl": "aux_state_template",
|
"aux_stat_tpl": "aux_state_template",
|
||||||
"aux_stat_t": "aux_state_topic",
|
"aux_stat_t": "aux_state_topic",
|
||||||
|
"avty": "availability",
|
||||||
"avty_t": "availability_topic",
|
"avty_t": "availability_topic",
|
||||||
"away_mode_cmd_t": "away_mode_command_topic",
|
"away_mode_cmd_t": "away_mode_command_topic",
|
||||||
"away_mode_stat_tpl": "away_mode_state_template",
|
"away_mode_stat_tpl": "away_mode_state_template",
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/smappee",
|
"documentation": "https://www.home-assistant.io/integrations/smappee",
|
||||||
"dependencies": ["http"],
|
"dependencies": ["http"],
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"pysmappee==0.1.4"
|
"pysmappee==0.1.5"
|
||||||
],
|
],
|
||||||
"codeowners": [
|
"codeowners": [
|
||||||
"@bsmappee"
|
"@bsmappee"
|
||||||
|
@ -105,16 +105,14 @@ class SpeedTestDataCoordinator(DataUpdateCoordinator):
|
|||||||
self.api = None
|
self.api = None
|
||||||
self.servers = {}
|
self.servers = {}
|
||||||
super().__init__(
|
super().__init__(
|
||||||
self.hass,
|
self.hass, _LOGGER, name=DOMAIN, update_method=self.async_update,
|
||||||
_LOGGER,
|
)
|
||||||
name=DOMAIN,
|
if not self.config_entry.options.get(CONF_MANUAL):
|
||||||
update_method=self.async_update,
|
self.update_interval = timedelta(
|
||||||
update_interval=timedelta(
|
|
||||||
minutes=self.config_entry.options.get(
|
minutes=self.config_entry.options.get(
|
||||||
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
|
||||||
)
|
)
|
||||||
),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
def update_servers(self):
|
def update_servers(self):
|
||||||
"""Update list of test servers."""
|
"""Update list of test servers."""
|
||||||
@ -189,12 +187,11 @@ class SpeedTestDataCoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
async def options_updated_listener(hass, entry):
|
async def options_updated_listener(hass, entry):
|
||||||
"""Handle options update."""
|
"""Handle options update."""
|
||||||
if not entry.options[CONF_MANUAL]:
|
if entry.options[CONF_MANUAL]:
|
||||||
hass.data[DOMAIN].update_interval = timedelta(
|
hass.data[DOMAIN].update_interval = None
|
||||||
minutes=entry.options[CONF_SCAN_INTERVAL]
|
|
||||||
)
|
|
||||||
await hass.data[DOMAIN].async_request_refresh()
|
|
||||||
return
|
return
|
||||||
# set the update interval to a very long time
|
|
||||||
# if the user wants to disable auto update
|
hass.data[DOMAIN].update_interval = timedelta(
|
||||||
hass.data[DOMAIN].update_interval = timedelta(days=7)
|
minutes=entry.options[CONF_SCAN_INTERVAL]
|
||||||
|
)
|
||||||
|
await hass.data[DOMAIN].async_request_refresh()
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"name": "Ubiquiti UniFi",
|
"name": "Ubiquiti UniFi",
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/unifi",
|
"documentation": "https://www.home-assistant.io/integrations/unifi",
|
||||||
"requirements": ["aiounifi==22"],
|
"requirements": ["aiounifi==23"],
|
||||||
"codeowners": ["@Kane610"],
|
"codeowners": ["@Kane610"],
|
||||||
"quality_scale": "platinum"
|
"quality_scale": "platinum"
|
||||||
}
|
}
|
||||||
|
@ -200,7 +200,7 @@ def setup(hass, config):
|
|||||||
|
|
||||||
# If we can handle it as a HomeKit discovery, we do that here.
|
# If we can handle it as a HomeKit discovery, we do that here.
|
||||||
if service_type == HOMEKIT_TYPE:
|
if service_type == HOMEKIT_TYPE:
|
||||||
handle_homekit(hass, info)
|
discovery_was_forwarded = handle_homekit(hass, info)
|
||||||
# Continue on here as homekit_controller
|
# Continue on here as homekit_controller
|
||||||
# still needs to get updates on devices
|
# still needs to get updates on devices
|
||||||
# so it can see when the 'c#' field is updated.
|
# so it can see when the 'c#' field is updated.
|
||||||
@ -209,7 +209,8 @@ def setup(hass, config):
|
|||||||
# if the device is already paired in order to avoid
|
# if the device is already paired in order to avoid
|
||||||
# offering a second discovery for the same device
|
# offering a second discovery for the same device
|
||||||
if (
|
if (
|
||||||
HOMEKIT_PROPERTIES in info
|
discovery_was_forwarded
|
||||||
|
and HOMEKIT_PROPERTIES in info
|
||||||
and HOMEKIT_PAIRED_STATUS_FLAG in info[HOMEKIT_PROPERTIES]
|
and HOMEKIT_PAIRED_STATUS_FLAG in info[HOMEKIT_PROPERTIES]
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Constants used by Home Assistant components."""
|
"""Constants used by Home Assistant components."""
|
||||||
MAJOR_VERSION = 0
|
MAJOR_VERSION = 0
|
||||||
MINOR_VERSION = 112
|
MINOR_VERSION = 112
|
||||||
PATCH_VERSION = "4"
|
PATCH_VERSION = "5"
|
||||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER = (3, 7, 0)
|
REQUIRED_PYTHON_VER = (3, 7, 0)
|
||||||
|
@ -30,7 +30,7 @@ class DataUpdateCoordinator:
|
|||||||
logger: logging.Logger,
|
logger: logging.Logger,
|
||||||
*,
|
*,
|
||||||
name: str,
|
name: str,
|
||||||
update_interval: timedelta,
|
update_interval: Optional[timedelta] = None,
|
||||||
update_method: Optional[Callable[[], Awaitable]] = None,
|
update_method: Optional[Callable[[], Awaitable]] = None,
|
||||||
request_refresh_debouncer: Optional[Debouncer] = None,
|
request_refresh_debouncer: Optional[Debouncer] = None,
|
||||||
):
|
):
|
||||||
@ -91,6 +91,9 @@ class DataUpdateCoordinator:
|
|||||||
@callback
|
@callback
|
||||||
def _schedule_refresh(self) -> None:
|
def _schedule_refresh(self) -> None:
|
||||||
"""Schedule a refresh."""
|
"""Schedule a refresh."""
|
||||||
|
if self.update_interval is None:
|
||||||
|
return
|
||||||
|
|
||||||
if self._unsub_refresh:
|
if self._unsub_refresh:
|
||||||
self._unsub_refresh()
|
self._unsub_refresh()
|
||||||
self._unsub_refresh = None
|
self._unsub_refresh = None
|
||||||
|
@ -230,7 +230,7 @@ aiopylgtv==0.3.3
|
|||||||
aioswitcher==1.2.0
|
aioswitcher==1.2.0
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==22
|
aiounifi==23
|
||||||
|
|
||||||
# homeassistant.components.airly
|
# homeassistant.components.airly
|
||||||
airly==0.0.2
|
airly==0.0.2
|
||||||
@ -348,7 +348,7 @@ bizkaibus==0.1.1
|
|||||||
blebox_uniapi==1.3.2
|
blebox_uniapi==1.3.2
|
||||||
|
|
||||||
# homeassistant.components.blink
|
# homeassistant.components.blink
|
||||||
blinkpy==0.15.0
|
blinkpy==0.15.1
|
||||||
|
|
||||||
# homeassistant.components.blinksticklight
|
# homeassistant.components.blinksticklight
|
||||||
blinkstick==1.1.8
|
blinkstick==1.1.8
|
||||||
@ -1282,7 +1282,7 @@ pydaikin==2.2.0
|
|||||||
pydanfossair==0.1.0
|
pydanfossair==0.1.0
|
||||||
|
|
||||||
# homeassistant.components.deconz
|
# homeassistant.components.deconz
|
||||||
pydeconz==71
|
pydeconz==72
|
||||||
|
|
||||||
# homeassistant.components.delijn
|
# homeassistant.components.delijn
|
||||||
pydelijn==0.6.0
|
pydelijn==0.6.0
|
||||||
@ -1610,7 +1610,7 @@ pysignalclirestapi==0.3.4
|
|||||||
pysma==0.3.5
|
pysma==0.3.5
|
||||||
|
|
||||||
# homeassistant.components.smappee
|
# homeassistant.components.smappee
|
||||||
pysmappee==0.1.4
|
pysmappee==0.1.5
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartapp==0.3.2
|
pysmartapp==0.3.2
|
||||||
|
@ -122,7 +122,7 @@ aiopylgtv==0.3.3
|
|||||||
aioswitcher==1.2.0
|
aioswitcher==1.2.0
|
||||||
|
|
||||||
# homeassistant.components.unifi
|
# homeassistant.components.unifi
|
||||||
aiounifi==22
|
aiounifi==23
|
||||||
|
|
||||||
# homeassistant.components.airly
|
# homeassistant.components.airly
|
||||||
airly==0.0.2
|
airly==0.0.2
|
||||||
@ -171,7 +171,7 @@ bellows==0.17.0
|
|||||||
blebox_uniapi==1.3.2
|
blebox_uniapi==1.3.2
|
||||||
|
|
||||||
# homeassistant.components.blink
|
# homeassistant.components.blink
|
||||||
blinkpy==0.15.0
|
blinkpy==0.15.1
|
||||||
|
|
||||||
# homeassistant.components.bom
|
# homeassistant.components.bom
|
||||||
bomradarloop==0.1.4
|
bomradarloop==0.1.4
|
||||||
@ -576,7 +576,7 @@ pycountry==19.8.18
|
|||||||
pydaikin==2.2.0
|
pydaikin==2.2.0
|
||||||
|
|
||||||
# homeassistant.components.deconz
|
# homeassistant.components.deconz
|
||||||
pydeconz==71
|
pydeconz==72
|
||||||
|
|
||||||
# homeassistant.components.zwave
|
# homeassistant.components.zwave
|
||||||
pydispatcher==2.0.5
|
pydispatcher==2.0.5
|
||||||
@ -715,7 +715,7 @@ pysignalclirestapi==0.3.4
|
|||||||
pysma==0.3.5
|
pysma==0.3.5
|
||||||
|
|
||||||
# homeassistant.components.smappee
|
# homeassistant.components.smappee
|
||||||
pysmappee==0.1.4
|
pysmappee==0.1.5
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartapp==0.3.2
|
pysmartapp==0.3.2
|
||||||
|
@ -104,6 +104,98 @@ async def help_test_default_availability_payload(
|
|||||||
assert state.state != STATE_UNAVAILABLE
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def help_test_default_availability_list_payload(
|
||||||
|
hass,
|
||||||
|
mqtt_mock,
|
||||||
|
domain,
|
||||||
|
config,
|
||||||
|
no_assumed_state=False,
|
||||||
|
state_topic=None,
|
||||||
|
state_message=None,
|
||||||
|
):
|
||||||
|
"""Test availability by default payload with defined topic.
|
||||||
|
|
||||||
|
This is a test helper for the MqttAvailability mixin.
|
||||||
|
"""
|
||||||
|
# Add availability settings to config
|
||||||
|
config = copy.deepcopy(config)
|
||||||
|
config[domain]["availability"] = [
|
||||||
|
{"topic": "availability-topic1"},
|
||||||
|
{"topic": "availability-topic2"},
|
||||||
|
]
|
||||||
|
assert await async_setup_component(hass, domain, config,)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic1", "online")
|
||||||
|
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
if no_assumed_state:
|
||||||
|
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic1", "offline")
|
||||||
|
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic2", "online")
|
||||||
|
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
if no_assumed_state:
|
||||||
|
assert not state.attributes.get(ATTR_ASSUMED_STATE)
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic2", "offline")
|
||||||
|
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
if state_topic:
|
||||||
|
async_fire_mqtt_message(hass, state_topic, state_message)
|
||||||
|
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic1", "online")
|
||||||
|
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def help_test_default_availability_list_single(
|
||||||
|
hass,
|
||||||
|
mqtt_mock,
|
||||||
|
caplog,
|
||||||
|
domain,
|
||||||
|
config,
|
||||||
|
no_assumed_state=False,
|
||||||
|
state_topic=None,
|
||||||
|
state_message=None,
|
||||||
|
):
|
||||||
|
"""Test availability list and availability_topic are mutually exclusive.
|
||||||
|
|
||||||
|
This is a test helper for the MqttAvailability mixin.
|
||||||
|
"""
|
||||||
|
# Add availability settings to config
|
||||||
|
config = copy.deepcopy(config)
|
||||||
|
config[domain]["availability"] = [
|
||||||
|
{"topic": "availability-topic1"},
|
||||||
|
]
|
||||||
|
config[domain]["availability_topic"] = "availability-topic"
|
||||||
|
assert await async_setup_component(hass, domain, config,)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state is None
|
||||||
|
assert (
|
||||||
|
"Invalid config for [sensor.mqtt]: two or more values in the same group of exclusion 'availability'"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def help_test_custom_availability_payload(
|
async def help_test_custom_availability_payload(
|
||||||
hass,
|
hass,
|
||||||
mqtt_mock,
|
mqtt_mock,
|
||||||
@ -152,6 +244,88 @@ async def help_test_custom_availability_payload(
|
|||||||
assert state.state != STATE_UNAVAILABLE
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def help_test_discovery_update_availability(
|
||||||
|
hass,
|
||||||
|
mqtt_mock,
|
||||||
|
domain,
|
||||||
|
config,
|
||||||
|
no_assumed_state=False,
|
||||||
|
state_topic=None,
|
||||||
|
state_message=None,
|
||||||
|
):
|
||||||
|
"""Test update of discovered MQTTAvailability.
|
||||||
|
|
||||||
|
This is a test helper for the MQTTAvailability mixin.
|
||||||
|
"""
|
||||||
|
# Add availability settings to config
|
||||||
|
config1 = copy.deepcopy(config)
|
||||||
|
config1[domain]["availability_topic"] = "availability-topic1"
|
||||||
|
config2 = copy.deepcopy(config)
|
||||||
|
config2[domain]["availability"] = [
|
||||||
|
{"topic": "availability-topic2"},
|
||||||
|
{"topic": "availability-topic3"},
|
||||||
|
]
|
||||||
|
config3 = copy.deepcopy(config)
|
||||||
|
config3[domain]["availability_topic"] = "availability-topic4"
|
||||||
|
data1 = json.dumps(config1[domain])
|
||||||
|
data2 = json.dumps(config2[domain])
|
||||||
|
data3 = json.dumps(config3[domain])
|
||||||
|
|
||||||
|
entry = hass.config_entries.async_entries(mqtt.DOMAIN)[0]
|
||||||
|
await async_start(hass, "homeassistant", entry)
|
||||||
|
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data1)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic1", "online")
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic1", "offline")
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# Change availability_topic
|
||||||
|
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data2)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Verify we are no longer subscribing to the old topic
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic1", "online")
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# Verify we are subscribing to the new topic
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic2", "online")
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# Verify we are subscribing to the new topic
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic3", "offline")
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# Change availability_topic
|
||||||
|
async_fire_mqtt_message(hass, f"homeassistant/{domain}/bla/config", data3)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Verify we are no longer subscribing to the old topic
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic2", "online")
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# Verify we are no longer subscribing to the old topic
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic3", "online")
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
# Verify we are subscribing to the new topic
|
||||||
|
async_fire_mqtt_message(hass, "availability-topic4", "online")
|
||||||
|
state = hass.states.get(f"{domain}.test")
|
||||||
|
assert state.state != STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
async def help_test_setting_attribute_via_mqtt_json_message(
|
async def help_test_setting_attribute_via_mqtt_json_message(
|
||||||
hass, mqtt_mock, domain, config
|
hass, mqtt_mock, domain, config
|
||||||
):
|
):
|
||||||
|
@ -16,11 +16,14 @@ from .test_common import (
|
|||||||
help_test_availability_when_connection_lost,
|
help_test_availability_when_connection_lost,
|
||||||
help_test_availability_without_topic,
|
help_test_availability_without_topic,
|
||||||
help_test_custom_availability_payload,
|
help_test_custom_availability_payload,
|
||||||
|
help_test_default_availability_list_payload,
|
||||||
|
help_test_default_availability_list_single,
|
||||||
help_test_default_availability_payload,
|
help_test_default_availability_payload,
|
||||||
help_test_discovery_broken,
|
help_test_discovery_broken,
|
||||||
help_test_discovery_removal,
|
help_test_discovery_removal,
|
||||||
help_test_discovery_update,
|
help_test_discovery_update,
|
||||||
help_test_discovery_update_attr,
|
help_test_discovery_update_attr,
|
||||||
|
help_test_discovery_update_availability,
|
||||||
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,
|
||||||
@ -250,6 +253,20 @@ async def test_default_availability_payload(hass, mqtt_mock):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_default_availability_list_payload(hass, mqtt_mock):
|
||||||
|
"""Test availability by default payload with defined topic."""
|
||||||
|
await help_test_default_availability_list_payload(
|
||||||
|
hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_default_availability_list_single(hass, mqtt_mock, caplog):
|
||||||
|
"""Test availability list and availability_topic are mutually exclusive."""
|
||||||
|
await help_test_default_availability_list_single(
|
||||||
|
hass, mqtt_mock, caplog, sensor.DOMAIN, DEFAULT_CONFIG
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_custom_availability_payload(hass, mqtt_mock):
|
async def test_custom_availability_payload(hass, mqtt_mock):
|
||||||
"""Test availability by custom payload with defined topic."""
|
"""Test availability by custom payload with defined topic."""
|
||||||
await help_test_custom_availability_payload(
|
await help_test_custom_availability_payload(
|
||||||
@ -257,6 +274,13 @@ async def test_custom_availability_payload(hass, mqtt_mock):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_discovery_update_availability(hass, mqtt_mock):
|
||||||
|
"""Test availability discovery update."""
|
||||||
|
await help_test_discovery_update_availability(
|
||||||
|
hass, mqtt_mock, sensor.DOMAIN, DEFAULT_CONFIG
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_invalid_device_class(hass, mqtt_mock):
|
async def test_invalid_device_class(hass, mqtt_mock):
|
||||||
"""Test device_class option with invalid value."""
|
"""Test device_class option with invalid value."""
|
||||||
assert await async_setup_component(
|
assert await async_setup_component(
|
||||||
|
@ -215,6 +215,25 @@ async def test_homekit_invalid_paring_status(hass, mock_zeroconf):
|
|||||||
assert mock_config_flow.mock_calls[0][1][0] == "tado"
|
assert mock_config_flow.mock_calls[0][1][0] == "tado"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_homekit_not_paired(hass, mock_zeroconf):
|
||||||
|
"""Test that an not paired device is sent to homekit_controller."""
|
||||||
|
with patch.dict(
|
||||||
|
zc_gen.ZEROCONF, {zeroconf.HOMEKIT_TYPE: ["homekit_controller"]}, clear=True
|
||||||
|
), patch.object(
|
||||||
|
hass.config_entries.flow, "async_init"
|
||||||
|
) as mock_config_flow, patch.object(
|
||||||
|
zeroconf, "HaServiceBrowser", side_effect=service_update_mock
|
||||||
|
) as mock_service_browser:
|
||||||
|
mock_zeroconf.get_service_info.side_effect = get_homekit_info_mock(
|
||||||
|
"this_will_not_match_any_integration", HOMEKIT_STATUS_UNPAIRED
|
||||||
|
)
|
||||||
|
assert await async_setup_component(hass, zeroconf.DOMAIN, {zeroconf.DOMAIN: {}})
|
||||||
|
|
||||||
|
assert len(mock_service_browser.mock_calls) == 1
|
||||||
|
assert len(mock_config_flow.mock_calls) == 1
|
||||||
|
assert mock_config_flow.mock_calls[0][1][0] == "homekit_controller"
|
||||||
|
|
||||||
|
|
||||||
async def test_info_from_service_non_utf8(hass):
|
async def test_info_from_service_non_utf8(hass):
|
||||||
"""Test info_from_service handles non UTF-8 property keys and values correctly."""
|
"""Test info_from_service handles non UTF-8 property keys and values correctly."""
|
||||||
service_type = "_test._tcp.local."
|
service_type = "_test._tcp.local."
|
||||||
|
@ -15,9 +15,8 @@ from tests.common import async_fire_time_changed
|
|||||||
LOGGER = logging.getLogger(__name__)
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
def get_crd(hass, update_interval):
|
||||||
def crd(hass):
|
"""Make coordinator mocks."""
|
||||||
"""Coordinator mock."""
|
|
||||||
calls = 0
|
calls = 0
|
||||||
|
|
||||||
async def refresh():
|
async def refresh():
|
||||||
@ -30,11 +29,26 @@ def crd(hass):
|
|||||||
LOGGER,
|
LOGGER,
|
||||||
name="test",
|
name="test",
|
||||||
update_method=refresh,
|
update_method=refresh,
|
||||||
update_interval=timedelta(seconds=10),
|
update_interval=update_interval,
|
||||||
)
|
)
|
||||||
return crd
|
return crd
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_UPDATE_INTERVAL = timedelta(seconds=10)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def crd(hass):
|
||||||
|
"""Coordinator mock with default update interval."""
|
||||||
|
return get_crd(hass, DEFAULT_UPDATE_INTERVAL)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def crd_without_update_interval(hass):
|
||||||
|
"""Coordinator mock that never automatically updates."""
|
||||||
|
return get_crd(hass, None)
|
||||||
|
|
||||||
|
|
||||||
async def test_async_refresh(crd):
|
async def test_async_refresh(crd):
|
||||||
"""Test async_refresh for update coordinator."""
|
"""Test async_refresh for update coordinator."""
|
||||||
assert crd.data is None
|
assert crd.data is None
|
||||||
@ -79,6 +93,20 @@ async def test_request_refresh(crd):
|
|||||||
assert crd.last_update_success is True
|
assert crd.last_update_success is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_request_refresh_no_auto_update(crd_without_update_interval):
|
||||||
|
"""Test request refresh for update coordinator without automatic update."""
|
||||||
|
crd = crd_without_update_interval
|
||||||
|
assert crd.data is None
|
||||||
|
await crd.async_request_refresh()
|
||||||
|
assert crd.data == 1
|
||||||
|
assert crd.last_update_success is True
|
||||||
|
|
||||||
|
# Second time we hit the debonuce
|
||||||
|
await crd.async_request_refresh()
|
||||||
|
assert crd.data == 1
|
||||||
|
assert crd.last_update_success is True
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"err_msg",
|
"err_msg",
|
||||||
[
|
[
|
||||||
@ -151,6 +179,37 @@ async def test_update_interval(hass, crd):
|
|||||||
assert crd.data == 2
|
assert crd.data == 2
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_interval_not_present(hass, crd_without_update_interval):
|
||||||
|
"""Test update never happens with no update interval."""
|
||||||
|
crd = crd_without_update_interval
|
||||||
|
# Test we don't update without subscriber with no update interval
|
||||||
|
async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert crd.data is None
|
||||||
|
|
||||||
|
# Add subscriber
|
||||||
|
update_callback = Mock()
|
||||||
|
crd.async_add_listener(update_callback)
|
||||||
|
|
||||||
|
# Test twice we don't update with subscriber with no update interval
|
||||||
|
async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert crd.data is None
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert crd.data is None
|
||||||
|
|
||||||
|
# Test removing listener
|
||||||
|
crd.async_remove_listener(update_callback)
|
||||||
|
|
||||||
|
async_fire_time_changed(hass, utcnow() + DEFAULT_UPDATE_INTERVAL)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Test we stop don't update after we lose last subscriber
|
||||||
|
assert crd.data is None
|
||||||
|
|
||||||
|
|
||||||
async def test_refresh_recover(crd, caplog):
|
async def test_refresh_recover(crd, caplog):
|
||||||
"""Test recovery of freshing data."""
|
"""Test recovery of freshing data."""
|
||||||
crd.last_update_success = False
|
crd.last_update_success = False
|
||||||
|
Loading…
x
Reference in New Issue
Block a user