Compare commits

...

38 Commits

Author SHA1 Message Date
Franck Nijhof
77f14b63f5 Merge pull request #49302 from home-assistant/rc 2021-04-16 16:04:26 +02:00
Franck Nijhof
a08df4e18f Bumped version to 2021.4.5 2021-04-16 15:35:06 +02:00
Joakim Sørensen
58b743eec1 Mark camera as a base platform (#49297) 2021-04-16 15:34:17 +02:00
Franck Nijhof
41736c93a1 Fix Coronavirus integration robustness (#49287)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2021-04-16 15:34:14 +02:00
Martin Hjelmare
733a394c55 Fix mysensors sensor protocol version check (#49257) 2021-04-16 15:34:10 +02:00
Erik Montnemery
ba0d3aad1c Fix race when restarting script (#49247) 2021-04-16 15:34:07 +02:00
Franck Nijhof
5c3cb044d0 Upgrade spotipy to 2.18.0 (#49220) 2021-04-16 15:34:03 +02:00
Erik Montnemery
a3ca48c1bd Set deprecated supported_features for MQTT JSON light (#49167)
* Set deprecated supported_features for MQTT json light

* Update homeassistant/components/light/__init__.py

Co-authored-by: Franck Nijhof <git@frenck.dev>

Co-authored-by: Franck Nijhof <git@frenck.dev>
2021-04-16 15:30:39 +02:00
Erik Montnemery
5356ce2b6d Don't receive homeassistant_* events from MQTT eventstream (#49158) 2021-04-16 15:30:36 +02:00
J. Nick Koston
02def46991 Fix setting up remotes that lack a supported features list in homekit (#49152) 2021-04-16 15:30:33 +02:00
J. Nick Koston
3ae82c3cac Bump aiodiscover to 1.3.4 (#49142)
- Changelog: https://github.com/bdraco/aiodiscover/compare/v1.3.3...v1.3.4 (bumps pyroute2>=0.5.18 to fix https://github.com/svinota/pyroute2/issues/717)
2021-04-16 15:30:29 +02:00
Paulus Schoutsen
b5548c57fb Merge pull request #49139 from home-assistant/rc 2021-04-12 17:45:38 -07:00
Paulus Schoutsen
e5281051a3 Bumped version to 2021.4.4 2021-04-13 00:10:42 +00:00
jjlawren
346ae78a8e Check all endpoints for zwave_js.climate hvac_action (#49115) 2021-04-13 00:09:51 +00:00
Unai
b5650bdd52 Upgrade maxcube-api to 0.4.2 (#49106)
Upgrade to maxcube-api 0.4.2 to fix pending issues in HA 2021.4.x:
 - Interpret correctly S command error responses (https://github.com/home-assistant/core/issues/49075)
 - Support application timezone configuration (https://github.com/home-assistant/core/issues/49076)
2021-04-13 00:09:50 +00:00
J. Nick Koston
bf28268732 Downgrade logger message about homekit id missing (#49079)
This can happen if the TXT record is received after the PTR record and
should not generate a warning since it will get processed later
2021-04-13 00:09:49 +00:00
Kevin Worrel
4eb794ae84 Catch unknown equipment values (#49073)
* Catch unknown equipment values

* Catch unknown equipment values

* Remove warning spam.
2021-04-13 00:09:48 +00:00
Jan Bouwhuis
21b5551506 mqtt fan percentage to speed_range and received speed_state fix (#49060)
* percentage to speed_range and get speed state fix

* Update homeassistant/components/mqtt/fan.py

* Update homeassistant/components/mqtt/fan.py

* Update homeassistant/components/mqtt/fan.py

* Update homeassistant/components/mqtt/fan.py

Co-authored-by: J. Nick Koston <nick@koston.org>
2021-04-13 00:07:27 +00:00
Erik Montnemery
e0131f726f Quote media_source paths (#49054)
* Quote path in async_sign_path

* Address review comments, add tests

* Update tests/testing_config/media/Epic Sax Guy 10 Hours.mp4

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-04-13 00:07:27 +00:00
Erik Montnemery
e685b1a1e3 Fix cast options flow overwriting data (#49051) 2021-04-13 00:07:26 +00:00
J. Nick Koston
0d00e49dfc Bump aiohomekit to 0.2.61 (#49044) 2021-04-13 00:07:25 +00:00
Chris Talkington
36e08e770b Resolve potential roku setup memory leaks (#49025)
* resolve potential roku setup memory leaks

* Update __init__.py
2021-04-13 00:07:24 +00:00
Franck Nijhof
e3b3d136d8 Fix use search instead of match to filter logs (#49017) 2021-04-13 00:07:23 +00:00
Joakim Plate
0a5a5ff053 Bump ha-philipsjs to 2.7.0 (#49008)
This has some improvements to not consider the TV
off due to some exceptions that is related to API
being buggy rather than off.
2021-04-13 00:07:22 +00:00
Shay Levy
0bb7592fab Fix Shelly brightness offset (#49007) 2021-04-13 00:07:21 +00:00
Aidan Timson
d081ac8d4a Set Lyric hold time to use local time instead of utc (#48994) 2021-04-13 00:07:20 +00:00
J. Nick Koston
b96e0e69f2 Bump nexia to 0.9.6 (#48982)
- Now returns None when a humidity sensor cannot be read instead of throwing an exception
2021-04-13 00:07:20 +00:00
Erik Montnemery
82cca8fb1c Move cast config flow tests to test_config_flow (#48362) 2021-04-13 00:07:19 +00:00
Matt Zimmerman
a9602e7a08 Update python-smarttub to 0.0.23 (#48978) 2021-04-10 03:38:13 +00:00
Matt Zimmerman
c08ae64085 Update python-smarttub to 0.0.23 (#48978) 2021-04-10 03:37:10 +00:00
Paulus Schoutsen
01e558430a Merge pull request #48977 from home-assistant/rc 2021-04-09 20:35:34 -07:00
Paulus Schoutsen
31b061e8f1 Bumped version to 2021.4.3 2021-04-10 00:30:34 +00:00
Shay Levy
4ca40367d1 Fix Shelly button device triggers (#48974) 2021-04-10 00:30:24 +00:00
J. Nick Koston
12da88cae9 Prevent ping id allocation conflict with device_tracker (#48969)
* Prevent ping id allocation conflict with device_tracker

- Solves id conflict resulting unexpected home state

* Update homeassistant/components/ping/device_tracker.py

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2021-04-10 00:30:23 +00:00
Paulus Schoutsen
0520ce5ed3 Fix config forwarding (#48967) 2021-04-10 00:30:22 +00:00
Guido Schmitz
995e22d3bb Bump devolo Home Control to support old websocket-client versions again (#48960) 2021-04-10 00:30:21 +00:00
Jan Bouwhuis
6296d78e58 Implement percentage_step and preset_mode is not not speed fix for MQTT fan (#48951) 2021-04-10 00:30:20 +00:00
Joakim Sørensen
2c7fd30029 Add TTS engines in config.components (#48939)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2021-04-10 00:30:20 +00:00
63 changed files with 909 additions and 696 deletions

View File

@@ -133,7 +133,7 @@ class CastOptionsFlowHandler(config_entries.OptionsFlow):
)
if not bad_cec and not bad_hosts and not bad_uuid:
updated_config = {}
updated_config = dict(current_config)
updated_config[CONF_IGNORE_CEC] = ignore_cec
updated_config[CONF_KNOWN_HOSTS] = known_hosts
updated_config[CONF_UUID] = wanted_uuid

View File

@@ -7,6 +7,7 @@ from datetime import timedelta
import functools as ft
import json
import logging
from urllib.parse import quote
import pychromecast
from pychromecast.controllers.homeassistant import HomeAssistantController
@@ -472,7 +473,7 @@ class CastDevice(MediaPlayerEntity):
media_id = async_sign_path(
self.hass,
refresh_token.id,
media_id,
quote(media_id),
timedelta(seconds=media_source.DEFAULT_EXPIRY_TIME),
)

View File

@@ -15,14 +15,14 @@ from .const import DOMAIN
PLATFORMS = ["sensor"]
async def async_setup(hass: HomeAssistant, config: dict):
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
"""Set up the Coronavirus component."""
# Make sure coordinator is initialized.
await get_coordinator(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Coronavirus from a config entry."""
if isinstance(entry.data["country"], int):
hass.config_entries.async_update_entry(
@@ -44,6 +44,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
if not entry.unique_id:
hass.config_entries.async_update_entry(entry, unique_id=entry.data["country"])
coordinator = await get_coordinator(hass)
if not coordinator.last_update_success:
await coordinator.async_config_entry_first_refresh()
for platform in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform)
@@ -52,9 +56,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = all(
return all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, platform)
@@ -63,10 +67,10 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
)
)
return unload_ok
async def get_coordinator(hass):
async def get_coordinator(
hass: HomeAssistant,
) -> update_coordinator.DataUpdateCoordinator:
"""Get the data update coordinator."""
if DOMAIN in hass.data:
return hass.data[DOMAIN]

View File

@@ -1,4 +1,8 @@
"""Config flow for Coronavirus integration."""
from __future__ import annotations
from typing import Any
import voluptuous as vol
from homeassistant import config_entries
@@ -15,13 +19,18 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
_options = None
async def async_step_user(self, user_input=None):
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> dict[str, Any]:
"""Handle the initial step."""
errors = {}
if self._options is None:
self._options = {OPTION_WORLDWIDE: "Worldwide"}
coordinator = await get_coordinator(self.hass)
if not coordinator.last_update_success:
return self.async_abort(reason="cannot_connect")
self._options = {OPTION_WORLDWIDE: "Worldwide"}
for case in sorted(
coordinator.data.values(), key=lambda case: case.country
):

View File

@@ -7,6 +7,7 @@
}
},
"abort": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
}
}

View File

@@ -1,7 +1,8 @@
{
"config": {
"abort": {
"already_configured": "Service is already configured"
"already_configured": "Service is already configured",
"cannot_connect": "Failed to connect"
},
"step": {
"user": {

View File

@@ -2,7 +2,7 @@
"domain": "devolo_home_control",
"name": "devolo Home Control",
"documentation": "https://www.home-assistant.io/integrations/devolo_home_control",
"requirements": ["devolo-home-control-api==0.17.1"],
"requirements": ["devolo-home-control-api==0.17.3"],
"after_dependencies": ["zeroconf"],
"config_flow": true,
"codeowners": ["@2Fake", "@Shutgun"],

View File

@@ -3,7 +3,7 @@
"name": "DHCP Discovery",
"documentation": "https://www.home-assistant.io/integrations/dhcp",
"requirements": [
"scapy==2.4.4", "aiodiscover==1.3.3"
"scapy==2.4.4", "aiodiscover==1.3.4"
],
"codeowners": [
"@bdraco"

View File

@@ -507,5 +507,5 @@ def state_needs_accessory_mode(state):
or state.domain == MEDIA_PLAYER_DOMAIN
and state.attributes.get(ATTR_DEVICE_CLASS) == DEVICE_CLASS_TV
or state.domain == REMOTE_DOMAIN
and state.attributes.get(ATTR_SUPPORTED_FEATURES) & SUPPORT_ACTIVITY
and state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) & SUPPORT_ACTIVITY
)

View File

@@ -209,8 +209,11 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow):
}
if "id" not in properties:
_LOGGER.warning(
"HomeKit device %s: id not exposed, in violation of spec", properties
# This can happen if the TXT record is received after the PTR record
# we will wait for the next update in this case
_LOGGER.debug(
"HomeKit device %s: id not exposed; TXT record may have not yet been received",
properties,
)
return self.async_abort(reason="invalid_properties")

View File

@@ -4,7 +4,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
"requirements": [
"aiohomekit==0.2.60"
"aiohomekit==0.2.61"
],
"zeroconf": [
"_hap._tcp.local."

View File

@@ -1,6 +1,7 @@
"""Authentication for HTTP component."""
import logging
import secrets
from urllib.parse import unquote
from aiohttp import hdrs
from aiohttp.web import middleware
@@ -30,11 +31,16 @@ def async_sign_path(hass, refresh_token_id, path, expiration):
now = dt_util.utcnow()
encoded = jwt.encode(
{"iss": refresh_token_id, "path": path, "iat": now, "exp": now + expiration},
{
"iss": refresh_token_id,
"path": unquote(path),
"iat": now,
"exp": now + expiration,
},
secret,
algorithm="HS256",
)
return f"{path}?{SIGN_QUERY_PARAM}=" f"{encoded.decode()}"
return f"{path}?{SIGN_QUERY_PARAM}={encoded.decode()}"
@callback

View File

@@ -751,3 +751,20 @@ class Light(LightEntity):
"Light is deprecated, modify %s to extend LightEntity",
cls.__name__,
)
def legacy_supported_features(
supported_features: int, supported_color_modes: list[str] | None
) -> int:
"""Calculate supported features with backwards compatibility."""
# Backwards compatibility for supported_color_modes added in 2021.4
if supported_color_modes is None:
return supported_features
if any(mode in supported_color_modes for mode in COLOR_MODES_COLOR):
supported_features |= SUPPORT_COLOR
if any(mode in supported_color_modes for mode in COLOR_MODES_BRIGHTNESS):
supported_features |= SUPPORT_BRIGHTNESS
if COLOR_MODE_COLOR_TEMP in supported_color_modes:
supported_features |= SUPPORT_COLOR_TEMP
return supported_features

View File

@@ -114,7 +114,7 @@ def _add_log_filter(logger, patterns):
"""Add a Filter to the logger based on a regexp of the filter_str."""
def filter_func(logrecord):
return not any(p.match(logrecord.getMessage()) for p in patterns)
return not any(p.search(logrecord.getMessage()) for p in patterns)
logger.addFilter(filter_func)

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
import logging
from time import gmtime, strftime, time
from time import localtime, strftime, time
from aiolyric.objects.device import LyricDevice
from aiolyric.objects.location import LyricLocation
@@ -82,7 +82,7 @@ SCHEMA_HOLD_TIME = {
vol.Required(ATTR_TIME_PERIOD, default="01:00:00"): vol.All(
cv.time_period,
cv.positive_timedelta,
lambda td: strftime("%H:%M:%S", gmtime(time() + td.total_seconds())),
lambda td: strftime("%H:%M:%S", localtime(time() + td.total_seconds())),
)
}

View File

@@ -10,6 +10,7 @@ import voluptuous as vol
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SCAN_INTERVAL
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.util.dt import now
_LOGGER = logging.getLogger(__name__)
@@ -59,7 +60,7 @@ def setup(hass, config):
scan_interval = gateway[CONF_SCAN_INTERVAL].total_seconds()
try:
cube = MaxCube(host, port)
cube = MaxCube(host, port, now=now)
hass.data[DATA_KEY][host] = MaxCubeHandle(cube, scan_interval)
except timeout as ex:
_LOGGER.error("Unable to connect to Max!Cube gateway: %s", str(ex))

View File

@@ -2,6 +2,6 @@
"domain": "maxcube",
"name": "eQ-3 MAX!",
"documentation": "https://www.home-assistant.io/integrations/maxcube",
"requirements": ["maxcube-api==0.4.1"],
"requirements": ["maxcube-api==0.4.2"],
"codeowners": []
}

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
from datetime import timedelta
from urllib.parse import quote
import voluptuous as vol
@@ -123,7 +124,7 @@ async def websocket_resolve_media(hass, connection, msg):
url = async_sign_path(
hass,
connection.refresh_token_id,
url,
quote(url),
timedelta(seconds=msg["expires"]),
)

View File

@@ -1,6 +1,7 @@
"""Support for MQTT fans."""
import functools
import logging
import math
import voluptuous as vol
@@ -32,6 +33,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.typing import ConfigType, HomeAssistantType
from homeassistant.util.percentage import (
int_states_in_range,
ordered_list_item_to_percentage,
percentage_to_ordered_list_item,
percentage_to_ranged_value,
@@ -224,6 +226,9 @@ class MqttFan(MqttEntity, FanEntity):
self._optimistic_preset_mode = None
self._optimistic_speed = None
self._legacy_speeds_list = []
self._legacy_speeds_list_no_off = []
MqttEntity.__init__(self, hass, config, config_entry, discovery_data)
@staticmethod
@@ -284,28 +289,18 @@ class MqttFan(MqttEntity, FanEntity):
self._legacy_speeds_list_no_off = speed_list_without_preset_modes(
self._legacy_speeds_list
)
else:
self._legacy_speeds_list = []
self._feature_percentage = CONF_PERCENTAGE_COMMAND_TOPIC in config
self._feature_preset_mode = CONF_PRESET_MODE_COMMAND_TOPIC in config
if self._feature_preset_mode:
self._speeds_list = speed_list_without_preset_modes(
self._legacy_speeds_list + config[CONF_PRESET_MODES_LIST]
)
self._preset_modes = (
self._legacy_speeds_list + config[CONF_PRESET_MODES_LIST]
)
self._preset_modes = config[CONF_PRESET_MODES_LIST]
else:
self._speeds_list = speed_list_without_preset_modes(
self._legacy_speeds_list
)
self._preset_modes = []
if not self._speeds_list or self._feature_percentage:
self._speed_count = 100
if self._feature_percentage:
self._speed_count = min(int_states_in_range(self._speed_range), 100)
else:
self._speed_count = len(self._speeds_list)
self._speed_count = len(self._legacy_speeds_list_no_off) or 100
optimistic = config[CONF_OPTIMISTIC]
self._optimistic = optimistic or self._topic[CONF_STATE_TOPIC] is None
@@ -327,11 +322,7 @@ class MqttFan(MqttEntity, FanEntity):
self._topic[CONF_OSCILLATION_COMMAND_TOPIC] is not None
and SUPPORT_OSCILLATE
)
if self._feature_preset_mode and self._speeds_list:
self._supported_features |= SUPPORT_SET_SPEED
if self._feature_percentage:
self._supported_features |= SUPPORT_SET_SPEED
if self._feature_legacy_speeds:
if self._feature_percentage or self._feature_legacy_speeds:
self._supported_features |= SUPPORT_SET_SPEED
if self._feature_preset_mode:
self._supported_features |= SUPPORT_PRESET_MODE
@@ -414,10 +405,6 @@ class MqttFan(MqttEntity, FanEntity):
return
self._preset_mode = preset_mode
if not self._implemented_percentage and (preset_mode in self.speed_list):
self._percentage = ordered_list_item_to_percentage(
self.speed_list, preset_mode
)
self.async_write_ha_state()
if self._topic[CONF_PRESET_MODE_STATE_TOPIC] is not None:
@@ -455,13 +442,12 @@ class MqttFan(MqttEntity, FanEntity):
)
return
if not self._implemented_percentage:
if speed in self._speeds_list:
self._percentage = ordered_list_item_to_percentage(
self._speeds_list, speed
)
elif speed == SPEED_OFF:
self._percentage = 0
if speed in self._legacy_speeds_list_no_off:
self._percentage = ordered_list_item_to_percentage(
self._legacy_speeds_list_no_off, speed
)
elif speed == SPEED_OFF:
self._percentage = 0
self.async_write_ha_state()
@@ -506,19 +492,9 @@ class MqttFan(MqttEntity, FanEntity):
"""Return true if device is on."""
return self._state
@property
def _implemented_percentage(self):
"""Return true if percentage has been implemented."""
return self._feature_percentage
@property
def _implemented_preset_mode(self):
"""Return true if preset_mode has been implemented."""
return self._feature_preset_mode
# The use of legacy speeds is deprecated in the schema, support will be removed after a quarter (2021.7)
@property
def _implemented_speed(self):
def _implemented_speed(self) -> bool:
"""Return true if speed has been implemented."""
return self._feature_legacy_speeds
@@ -541,7 +517,7 @@ class MqttFan(MqttEntity, FanEntity):
@property
def speed_list(self) -> list:
"""Get the list of available speeds."""
return self._speeds_list
return self._legacy_speeds_list_no_off
@property
def supported_features(self) -> int:
@@ -555,7 +531,7 @@ class MqttFan(MqttEntity, FanEntity):
@property
def speed_count(self) -> int:
"""Return the number of speeds the fan supports or 100 if percentage is supported."""
"""Return the number of speeds the fan supports."""
return self._speed_count
@property
@@ -616,24 +592,12 @@ class MqttFan(MqttEntity, FanEntity):
This method is a coroutine.
"""
percentage_payload = int(
percentage_payload = math.ceil(
percentage_to_ranged_value(self._speed_range, percentage)
)
mqtt_payload = self._command_templates[ATTR_PERCENTAGE](percentage_payload)
if self._implemented_preset_mode:
if percentage:
await self.async_set_preset_mode(
preset_mode=percentage_to_ordered_list_item(
self.speed_list, percentage
)
)
# Legacy are deprecated in the schema, support will be removed after a quarter (2021.7)
elif self._feature_legacy_speeds and (
SPEED_OFF in self._legacy_speeds_list
):
await self.async_set_preset_mode(SPEED_OFF)
# Legacy are deprecated in the schema, support will be removed after a quarter (2021.7)
elif self._feature_legacy_speeds:
if self._feature_legacy_speeds:
if percentage:
await self.async_set_speed(
percentage_to_ordered_list_item(
@@ -644,7 +608,7 @@ class MqttFan(MqttEntity, FanEntity):
elif SPEED_OFF in self._legacy_speeds_list:
await self.async_set_speed(SPEED_OFF)
if self._implemented_percentage:
if self._feature_percentage:
mqtt.async_publish(
self.hass,
self._topic[CONF_PERCENTAGE_COMMAND_TOPIC],
@@ -665,13 +629,7 @@ class MqttFan(MqttEntity, FanEntity):
if preset_mode not in self.preset_modes:
_LOGGER.warning("'%s'is not a valid preset mode", preset_mode)
return
# Legacy are deprecated in the schema, support will be removed after a quarter (2021.7)
if preset_mode in self._legacy_speeds_list:
await self.async_set_speed(speed=preset_mode)
if not self._implemented_percentage and preset_mode in self.speed_list:
self._percentage = ordered_list_item_to_percentage(
self.speed_list, preset_mode
)
mqtt_payload = self._command_templates[ATTR_PRESET_MODE](preset_mode)
mqtt.async_publish(
@@ -693,18 +651,18 @@ class MqttFan(MqttEntity, FanEntity):
This method is a coroutine.
"""
speed_payload = None
if self._feature_legacy_speeds:
if speed in self._legacy_speeds_list:
if speed == SPEED_LOW:
speed_payload = self._payload["SPEED_LOW"]
elif speed == SPEED_MEDIUM:
speed_payload = self._payload["SPEED_MEDIUM"]
elif speed == SPEED_HIGH:
speed_payload = self._payload["SPEED_HIGH"]
elif speed == SPEED_OFF:
speed_payload = self._payload["SPEED_OFF"]
else:
_LOGGER.warning("'%s'is not a valid speed", speed)
return
speed_payload = self._payload["SPEED_OFF"]
else:
_LOGGER.warning("'%s' is not a valid speed", speed)
return
if speed_payload:
mqtt.async_publish(

View File

@@ -35,6 +35,7 @@ from homeassistant.components.light import (
SUPPORT_WHITE_VALUE,
VALID_COLOR_MODES,
LightEntity,
legacy_supported_features,
valid_supported_color_modes,
)
from homeassistant.const import (
@@ -458,7 +459,9 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
@property
def supported_features(self):
"""Flag supported features."""
return self._supported_features
return legacy_supported_features(
self._supported_features, self._config.get(CONF_SUPPORTED_COLOR_MODES)
)
def _set_flash_and_transition(self, message, **kwargs):
if ATTR_TRANSITION in kwargs:

View File

@@ -7,6 +7,11 @@ from homeassistant.components.mqtt import valid_publish_topic, valid_subscribe_t
from homeassistant.const import (
ATTR_SERVICE_DATA,
EVENT_CALL_SERVICE,
EVENT_HOMEASSISTANT_CLOSE,
EVENT_HOMEASSISTANT_FINAL_WRITE,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STARTED,
EVENT_HOMEASSISTANT_STOP,
EVENT_STATE_CHANGED,
EVENT_TIME_CHANGED,
MATCH_ALL,
@@ -37,6 +42,14 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
BLOCKED_EVENTS = [
EVENT_HOMEASSISTANT_CLOSE,
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STARTED,
EVENT_HOMEASSISTANT_STOP,
EVENT_HOMEASSISTANT_FINAL_WRITE,
]
async def async_setup(hass, config):
"""Set up the MQTT eventstream component."""
@@ -45,16 +58,15 @@ async def async_setup(hass, config):
pub_topic = conf.get(CONF_PUBLISH_TOPIC)
sub_topic = conf.get(CONF_SUBSCRIBE_TOPIC)
ignore_event = conf.get(CONF_IGNORE_EVENT)
ignore_event.append(EVENT_TIME_CHANGED)
@callback
def _event_publisher(event):
"""Handle events by publishing them on the MQTT queue."""
if event.origin != EventOrigin.local:
return
if event.event_type == EVENT_TIME_CHANGED:
return
# User-defined events to ignore
# Events to ignore
if event.event_type in ignore_event:
return
@@ -84,6 +96,10 @@ async def async_setup(hass, config):
event_type = event.get("event_type")
event_data = event.get("event_data")
# Don't fire HOMEASSISTANT_* events on this instance
if event_type in BLOCKED_EVENTS:
return
# Special case handling for event STATE_CHANGED
# We will try to convert state dicts back to State objects
# Copied over from the _handle_api_post_events_event method

View File

@@ -1,6 +1,8 @@
"""Support for MySensors sensors."""
from typing import Callable
from awesomeversion import AwesomeVersion
from homeassistant.components import mysensors
from homeassistant.components.mysensors import on_unload
from homeassistant.components.mysensors.const import MYSENSORS_DISCOVERY
@@ -115,7 +117,7 @@ class MySensorsSensor(mysensors.device.MySensorsEntity, SensorEntity):
"""Return the unit of measurement of this entity."""
set_req = self.gateway.const.SetReq
if (
float(self.gateway.protocol_version) >= 1.5
AwesomeVersion(self.gateway.protocol_version) >= AwesomeVersion("1.5")
and set_req.V_UNIT_PREFIX in self._values
):
return self._values[set_req.V_UNIT_PREFIX]

View File

@@ -1,7 +1,7 @@
{
"domain": "nexia",
"name": "Nexia",
"requirements": ["nexia==0.9.5"],
"requirements": ["nexia==0.9.6"],
"codeowners": ["@bdraco"],
"documentation": "https://www.home-assistant.io/integrations/nexia",
"config_flow": true,

View File

@@ -13,4 +13,6 @@ def is_invalid_auth_code(http_status_code):
def percent_conv(val):
"""Convert an actual percentage (0.0-1.0) to 0-100 scale."""
if val is None:
return None
return round(val * 100.0, 1)

View File

@@ -134,8 +134,12 @@ class PhilipsTVDataUpdateCoordinator(DataUpdateCoordinator[None]):
async def _notify_task(self):
while self.api.on and self.api.notify_change_supported:
if await self.api.notifyChange(130):
res = await self.api.notifyChange(130)
if res:
self.async_set_updated_data(None)
elif res is None:
LOGGER.debug("Aborting notify due to unexpected return")
break
@callback
def _async_notify_stop(self):

View File

@@ -3,7 +3,7 @@
"name": "Philips TV",
"documentation": "https://www.home-assistant.io/integrations/philips_js",
"requirements": [
"ha-philipsjs==2.3.2"
"ha-philipsjs==2.7.0"
],
"codeowners": [
"@elupus"

View File

@@ -24,20 +24,22 @@ async def async_setup(hass, config):
@callback
def async_get_next_ping_id(hass):
def async_get_next_ping_id(hass, count=1):
"""Find the next id to use in the outbound ping.
When using multiping, we increment the id
by the number of ids that multiping
will use.
Must be called in async
"""
current_id = hass.data[DOMAIN][PING_ID]
if current_id == MAX_PING_ID:
next_id = DEFAULT_START_ID
else:
next_id = current_id + 1
hass.data[DOMAIN][PING_ID] = next_id
return next_id
allocated_id = hass.data[DOMAIN][PING_ID] + 1
if allocated_id > MAX_PING_ID:
allocated_id -= MAX_PING_ID - DEFAULT_START_ID
hass.data[DOMAIN][PING_ID] += count
if hass.data[DOMAIN][PING_ID] > MAX_PING_ID:
hass.data[DOMAIN][PING_ID] -= MAX_PING_ID - DEFAULT_START_ID
return allocated_id
def _can_use_icmp_lib_with_privilege() -> None | bool:

View File

@@ -125,7 +125,7 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
count=PING_ATTEMPTS_COUNT,
timeout=ICMP_TIMEOUT,
privileged=privileged,
id=async_get_next_ping_id(hass),
id=async_get_next_ping_id(hass, len(ip_to_dev_id)),
)
)
_LOGGER.debug("Multiping responses: %s", responses)

View File

@@ -47,10 +47,12 @@ async def async_setup(hass: HomeAssistantType, config: dict) -> bool:
async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
"""Set up Roku from a config entry."""
coordinator = RokuDataUpdateCoordinator(hass, host=entry.data[CONF_HOST])
await coordinator.async_config_entry_first_refresh()
coordinator = hass.data[DOMAIN].get(entry.entry_id)
if not coordinator:
coordinator = RokuDataUpdateCoordinator(hass, host=entry.data[CONF_HOST])
hass.data[DOMAIN][entry.entry_id] = coordinator
hass.data[DOMAIN][entry.entry_id] = coordinator
await coordinator.async_config_entry_first_refresh()
for platform in PLATFORMS:
hass.async_create_task(

View File

@@ -195,9 +195,15 @@ class ScreenlogicEntity(CoordinatorEntity):
"""Return device information for the controller."""
controller_type = self.config_data["controller_type"]
hardware_type = self.config_data["hardware_type"]
try:
equipment_model = EQUIPMENT.CONTROLLER_HARDWARE[controller_type][
hardware_type
]
except KeyError:
equipment_model = f"Unknown Model C:{controller_type} H:{hardware_type}"
return {
"connections": {(dr.CONNECTION_NETWORK_MAC, self.mac)},
"name": self.gateway_name,
"manufacturer": "Pentair",
"model": EQUIPMENT.CONTROLLER_HARDWARE[controller_type][hardware_type],
"model": equipment_model,
}

View File

@@ -27,6 +27,7 @@ from .const import (
DOMAIN,
EVENT_SHELLY_CLICK,
INPUTS_EVENTS_SUBTYPES,
SHBTN_1_INPUTS_EVENTS_TYPES,
SUPPORTED_INPUTS_EVENTS_TYPES,
)
from .utils import get_device_wrapper, get_input_triggers
@@ -45,7 +46,7 @@ async def async_validate_trigger_config(hass, config):
# if device is available verify parameters against device capabilities
wrapper = get_device_wrapper(hass, config[CONF_DEVICE_ID])
if not wrapper:
if not wrapper or not wrapper.device.initialized:
return config
trigger = (config[CONF_TYPE], config[CONF_SUBTYPE])
@@ -68,6 +69,19 @@ async def async_get_triggers(hass: HomeAssistant, device_id: str) -> list[dict]:
if not wrapper:
raise InvalidDeviceAutomationConfig(f"Device not found: {device_id}")
if wrapper.model in ("SHBTN-1", "SHBTN-2"):
for trigger in SHBTN_1_INPUTS_EVENTS_TYPES:
triggers.append(
{
CONF_PLATFORM: "device",
CONF_DEVICE_ID: device_id,
CONF_DOMAIN: DOMAIN,
CONF_TYPE: trigger,
CONF_SUBTYPE: "button",
}
)
return triggers
for block in wrapper.device.blocks:
input_triggers = get_input_triggers(wrapper.device, block)

View File

@@ -118,15 +118,16 @@ class ShellyLight(ShellyBlockEntity, LightEntity):
"""Brightness of light."""
if self.mode == "color":
if self.control_result:
brightness = self.control_result["gain"]
brightness_pct = self.control_result["gain"]
else:
brightness = self.block.gain
brightness_pct = self.block.gain
else:
if self.control_result:
brightness = self.control_result["brightness"]
brightness_pct = self.control_result["brightness"]
else:
brightness = self.block.brightness
return int(brightness / 100 * 255)
brightness_pct = self.block.brightness
return round(255 * brightness_pct / 100)
@property
def white_value(self) -> int:
@@ -188,11 +189,11 @@ class ShellyLight(ShellyBlockEntity, LightEntity):
set_mode = None
params = {"turn": "on"}
if ATTR_BRIGHTNESS in kwargs:
tmp_brightness = int(kwargs[ATTR_BRIGHTNESS] / 255 * 100)
brightness_pct = int(100 * (kwargs[ATTR_BRIGHTNESS] + 1) / 255)
if hasattr(self.block, "gain"):
params["gain"] = tmp_brightness
params["gain"] = brightness_pct
if hasattr(self.block, "brightness"):
params["brightness"] = tmp_brightness
params["brightness"] = brightness_pct
if ATTR_COLOR_TEMP in kwargs:
color_temp = color_temperature_mired_to_kelvin(kwargs[ATTR_COLOR_TEMP])
color_temp = min(self._max_kelvin, max(self._min_kelvin, color_temp))

View File

@@ -6,7 +6,7 @@
"dependencies": [],
"codeowners": ["@mdz"],
"requirements": [
"python-smarttub==0.0.19"
"python-smarttub==0.0.23"
],
"quality_scale": "platinum"
}

View File

@@ -2,7 +2,7 @@
"domain": "spotify",
"name": "Spotify",
"documentation": "https://www.home-assistant.io/integrations/spotify",
"requirements": ["spotipy==2.17.1"],
"requirements": ["spotipy==2.18.0"],
"zeroconf": ["_spotify-connect._tcp.local."],
"dependencies": ["http"],
"codeowners": ["@frenck"],

View File

@@ -68,7 +68,7 @@ async def _process_config(hass, config):
async def init_coordinator(hass, conf):
coordinator = TriggerUpdateCoordinator(hass, conf)
await coordinator.async_setup(conf)
await coordinator.async_setup(config)
return coordinator
hass.data[DOMAIN] = await asyncio.gather(

View File

@@ -31,6 +31,7 @@ from homeassistant.const import (
CONF_PLATFORM,
HTTP_BAD_REQUEST,
HTTP_NOT_FOUND,
PLATFORM_FORMAT,
)
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
@@ -316,6 +317,10 @@ class SpeechManager:
provider.name = engine
self.providers[engine] = provider
self.hass.config.components.add(
PLATFORM_FORMAT.format(domain=engine, platform=DOMAIN)
)
async def async_get_url_path(
self, engine, message, cache=None, language=None, options=None
):

View File

@@ -5,5 +5,6 @@
"requirements": ["mutagen==1.45.1"],
"dependencies": ["http"],
"after_dependencies": ["media_player"],
"quality_scale": "internal",
"codeowners": ["@pvizeli"]
}

View File

@@ -150,6 +150,7 @@ class ZWaveClimate(ZWaveBaseEntity, ClimateEntity):
THERMOSTAT_OPERATING_STATE_PROPERTY,
command_class=CommandClass.THERMOSTAT_OPERATING_STATE,
add_to_watched_value_ids=True,
check_all_endpoints=True,
)
self._current_temp = self.get_zwave_value(
THERMOSTAT_CURRENT_TEMP_PROPERTY,

View File

@@ -1,7 +1,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 2021
MINOR_VERSION = 4
PATCH_VERSION = "2"
PATCH_VERSION = "5"
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__ = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER = (3, 8, 0)

View File

@@ -1144,10 +1144,7 @@ class Script:
self._log("Already running", level=LOGSEVERITY[self._max_exceeded])
script_execution_set("failed_single")
return
if self.script_mode == SCRIPT_MODE_RESTART:
self._log("Restarting")
await self.async_stop(update_state=False)
elif len(self._runs) == self.max_runs:
if self.script_mode != SCRIPT_MODE_RESTART and self.runs == self.max_runs:
if self._max_exceeded != "SILENT":
self._log(
"Maximum number of runs exceeded",
@@ -1186,6 +1183,14 @@ class Script:
self._hass, self, cast(dict, variables), context, self._log_exceptions
)
self._runs.append(run)
if self.script_mode == SCRIPT_MODE_RESTART:
# When script mode is SCRIPT_MODE_RESTART, first add the new run and then
# stop any other runs. If we stop other runs first, self.is_running will
# return false after the other script runs were stopped until our task
# resumes running.
self._log("Restarting")
await self.async_stop(update_state=False, spare=run)
if started_action:
self._hass.async_run_job(started_action)
self.last_triggered = utcnow()
@@ -1198,17 +1203,21 @@ class Script:
self._changed()
raise
async def _async_stop(self, update_state):
aws = [asyncio.create_task(run.async_stop()) for run in self._runs]
async def _async_stop(self, update_state, spare=None):
aws = [
asyncio.create_task(run.async_stop()) for run in self._runs if run != spare
]
if not aws:
return
await asyncio.wait(aws)
if update_state:
self._changed()
async def async_stop(self, update_state: bool = True) -> None:
async def async_stop(
self, update_state: bool = True, spare: _ScriptRun | None = None
) -> None:
"""Stop running script."""
await asyncio.shield(self._async_stop(update_state))
await asyncio.shield(self._async_stop(update_state, spare))
async def _async_get_condition(self, config):
if isinstance(config, template.Template):

View File

@@ -1,6 +1,6 @@
PyJWT==1.7.1
PyNaCl==1.3.0
aiodiscover==1.3.3
aiodiscover==1.3.4
aiohttp==3.7.4.post0
aiohttp_cors==0.7.0
astral==1.10.1

View File

@@ -22,6 +22,7 @@ BASE_PLATFORMS = {
"air_quality",
"alarm_control_panel",
"binary_sensor",
"camera",
"climate",
"cover",
"device_tracker",
@@ -36,6 +37,7 @@ BASE_PLATFORMS = {
"scene",
"sensor",
"switch",
"tts",
"vacuum",
"water_heater",
}

View File

@@ -147,7 +147,7 @@ aioazuredevops==1.3.5
aiobotocore==0.11.1
# homeassistant.components.dhcp
aiodiscover==1.3.3
aiodiscover==1.3.4
# homeassistant.components.dnsip
# homeassistant.components.minecraft_server
@@ -172,7 +172,7 @@ aioguardian==1.0.4
aioharmony==0.2.7
# homeassistant.components.homekit_controller
aiohomekit==0.2.60
aiohomekit==0.2.61
# homeassistant.components.emulated_hue
# homeassistant.components.http
@@ -479,7 +479,7 @@ deluge-client==1.7.1
denonavr==0.9.10
# homeassistant.components.devolo_home_control
devolo-home-control-api==0.17.1
devolo-home-control-api==0.17.3
# homeassistant.components.directv
directv==0.4.0
@@ -721,7 +721,7 @@ guppy3==3.1.0
ha-ffmpeg==3.0.2
# homeassistant.components.philips_js
ha-philipsjs==2.3.2
ha-philipsjs==2.7.0
# homeassistant.components.habitica
habitipy==0.2.0
@@ -916,7 +916,7 @@ magicseaweed==1.0.3
matrix-client==0.3.2
# homeassistant.components.maxcube
maxcube-api==0.4.1
maxcube-api==0.4.2
# homeassistant.components.mythicbeastsdns
mbddns==0.1.2
@@ -986,7 +986,7 @@ netdisco==2.8.2
neurio==0.3.1
# homeassistant.components.nexia
nexia==0.9.5
nexia==0.9.6
# homeassistant.components.nextcloud
nextcloudmonitor==1.1.0
@@ -1822,7 +1822,7 @@ python-qbittorrent==0.4.2
python-ripple-api==0.0.3
# homeassistant.components.smarttub
python-smarttub==0.0.19
python-smarttub==0.0.23
# homeassistant.components.sochain
python-sochain-api==0.0.2
@@ -2117,7 +2117,7 @@ spiderpy==1.4.2
spotcrime==1.0.4
# homeassistant.components.spotify
spotipy==2.17.1
spotipy==2.18.0
# homeassistant.components.recorder
# homeassistant.components.sql

View File

@@ -84,7 +84,7 @@ aioazuredevops==1.3.5
aiobotocore==0.11.1
# homeassistant.components.dhcp
aiodiscover==1.3.3
aiodiscover==1.3.4
# homeassistant.components.dnsip
# homeassistant.components.minecraft_server
@@ -106,7 +106,7 @@ aioguardian==1.0.4
aioharmony==0.2.7
# homeassistant.components.homekit_controller
aiohomekit==0.2.60
aiohomekit==0.2.61
# homeassistant.components.emulated_hue
# homeassistant.components.http
@@ -261,7 +261,7 @@ defusedxml==0.6.0
denonavr==0.9.10
# homeassistant.components.devolo_home_control
devolo-home-control-api==0.17.1
devolo-home-control-api==0.17.3
# homeassistant.components.directv
directv==0.4.0
@@ -382,7 +382,7 @@ guppy3==3.1.0
ha-ffmpeg==3.0.2
# homeassistant.components.philips_js
ha-philipsjs==2.3.2
ha-philipsjs==2.7.0
# homeassistant.components.habitica
habitipy==0.2.0
@@ -476,7 +476,7 @@ logi_circle==0.2.2
luftdaten==0.6.4
# homeassistant.components.maxcube
maxcube-api==0.4.1
maxcube-api==0.4.2
# homeassistant.components.mythicbeastsdns
mbddns==0.1.2
@@ -516,7 +516,7 @@ nessclient==0.9.15
netdisco==2.8.2
# homeassistant.components.nexia
nexia==0.9.5
nexia==0.9.6
# homeassistant.components.notify_events
notify-events==1.0.4
@@ -959,7 +959,7 @@ python-nest==4.1.0
python-openzwave-mqtt[mqtt-client]==1.4.0
# homeassistant.components.smarttub
python-smarttub==0.0.19
python-smarttub==0.0.23
# homeassistant.components.songpal
python-songpal==0.12
@@ -1104,7 +1104,7 @@ speedtest-cli==2.1.3
spiderpy==1.4.2
# homeassistant.components.spotify
spotipy==2.17.1
spotipy==2.18.0
# homeassistant.components.recorder
# homeassistant.components.sql

View File

@@ -156,8 +156,8 @@ async def test_motion_light(hass):
# Turn on motion
hass.states.async_set("binary_sensor.kitchen", "on")
# Can't block till done because delay is active
# So wait 5 event loop iterations to process script
for _ in range(5):
# So wait 10 event loop iterations to process script
for _ in range(10):
await asyncio.sleep(0)
assert len(turn_on_calls) == 1
@@ -165,7 +165,7 @@ async def test_motion_light(hass):
# Test light doesn't turn off if motion stays
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=200))
for _ in range(5):
for _ in range(10):
await asyncio.sleep(0)
assert len(turn_off_calls) == 0
@@ -173,7 +173,7 @@ async def test_motion_light(hass):
# Test light turns off off 120s after last motion
hass.states.async_set("binary_sensor.kitchen", "off")
for _ in range(5):
for _ in range(10):
await asyncio.sleep(0)
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=120))
@@ -184,7 +184,7 @@ async def test_motion_light(hass):
# Test restarting the script
hass.states.async_set("binary_sensor.kitchen", "on")
for _ in range(5):
for _ in range(10):
await asyncio.sleep(0)
assert len(turn_on_calls) == 2
@@ -192,7 +192,7 @@ async def test_motion_light(hass):
hass.states.async_set("binary_sensor.kitchen", "off")
for _ in range(5):
for _ in range(10):
await asyncio.sleep(0)
hass.states.async_set("binary_sensor.kitchen", "on")

View File

@@ -0,0 +1,244 @@
"""Tests for the Cast config flow."""
from unittest.mock import ANY, patch
import pytest
from homeassistant import config_entries, data_entry_flow
from homeassistant.components import cast
from tests.common import MockConfigEntry
async def test_creating_entry_sets_up_media_player(hass):
"""Test setting up Cast loads the media player."""
with patch(
"homeassistant.components.cast.media_player.async_setup_entry",
return_value=True,
) as mock_setup, patch(
"pychromecast.discovery.discover_chromecasts", return_value=(True, None)
), patch(
"pychromecast.discovery.stop_discovery"
):
result = await hass.config_entries.flow.async_init(
cast.DOMAIN, context={"source": config_entries.SOURCE_USER}
)
# Confirmation form
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
@pytest.mark.parametrize("source", ["import", "user", "zeroconf"])
async def test_single_instance(hass, source):
"""Test we only allow a single config flow."""
MockConfigEntry(domain="cast").add_to_hass(hass)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_init(
"cast", context={"source": source}
)
assert result["type"] == "abort"
assert result["reason"] == "single_instance_allowed"
async def test_user_setup(hass):
"""Test we can finish a config flow."""
result = await hass.config_entries.flow.async_init(
"cast", context={"source": "user"}
)
assert result["type"] == "form"
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
users = await hass.auth.async_get_users()
assert len(users) == 1
assert result["type"] == "create_entry"
assert result["result"].data == {
"ignore_cec": [],
"known_hosts": [],
"uuid": [],
"user_id": users[0].id, # Home Assistant cast user
}
async def test_user_setup_options(hass):
"""Test we can finish a config flow."""
result = await hass.config_entries.flow.async_init(
"cast", context={"source": "user"}
)
assert result["type"] == "form"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"known_hosts": "192.168.0.1, , 192.168.0.2 "}
)
users = await hass.auth.async_get_users()
assert len(users) == 1
assert result["type"] == "create_entry"
assert result["result"].data == {
"ignore_cec": [],
"known_hosts": ["192.168.0.1", "192.168.0.2"],
"uuid": [],
"user_id": users[0].id, # Home Assistant cast user
}
async def test_zeroconf_setup(hass):
"""Test we can finish a config flow through zeroconf."""
result = await hass.config_entries.flow.async_init(
"cast", context={"source": "zeroconf"}
)
assert result["type"] == "form"
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
users = await hass.auth.async_get_users()
assert len(users) == 1
assert result["type"] == "create_entry"
assert result["result"].data == {
"ignore_cec": [],
"known_hosts": [],
"uuid": [],
"user_id": users[0].id, # Home Assistant cast user
}
def get_suggested(schema, key):
"""Get suggested value for key in voluptuous schema."""
for k in schema.keys():
if k == key:
if k.description is None or "suggested_value" not in k.description:
return None
return k.description["suggested_value"]
@pytest.mark.parametrize(
"parameter_data",
[
(
"known_hosts",
["192.168.0.10", "192.168.0.11"],
"192.168.0.10,192.168.0.11",
"192.168.0.1, , 192.168.0.2 ",
["192.168.0.1", "192.168.0.2"],
),
(
"uuid",
["bla", "blu"],
"bla,blu",
"foo, , bar ",
["foo", "bar"],
),
(
"ignore_cec",
["cast1", "cast2"],
"cast1,cast2",
"other_cast, , some_cast ",
["other_cast", "some_cast"],
),
],
)
async def test_option_flow(hass, parameter_data):
"""Test config flow options."""
all_parameters = ["ignore_cec", "known_hosts", "uuid"]
parameter, initial, suggested, user_input, updated = parameter_data
data = {
"ignore_cec": [],
"known_hosts": [],
"uuid": [],
}
data[parameter] = initial
config_entry = MockConfigEntry(domain="cast", data=data)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Test ignore_cec and uuid options are hidden if advanced options are disabled
result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "options"
data_schema = result["data_schema"].schema
assert set(data_schema) == {"known_hosts"}
orig_data = dict(config_entry.data)
# Reconfigure ignore_cec, known_hosts, uuid
context = {"source": "user", "show_advanced_options": True}
result = await hass.config_entries.options.async_init(
config_entry.entry_id, context=context
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "options"
data_schema = result["data_schema"].schema
for other_param in all_parameters:
if other_param == parameter:
continue
assert get_suggested(data_schema, other_param) == ""
assert get_suggested(data_schema, parameter) == suggested
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={parameter: user_input},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["data"] is None
for other_param in all_parameters:
if other_param == parameter:
continue
assert config_entry.data[other_param] == []
assert config_entry.data[parameter] == updated
# Clear known_hosts
result = await hass.config_entries.options.async_init(config_entry.entry_id)
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={"known_hosts": ""},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["data"] is None
assert config_entry.data == {
**orig_data,
"ignore_cec": [],
"known_hosts": [],
"uuid": [],
}
async def test_known_hosts(hass, castbrowser_mock, castbrowser_constructor_mock):
"""Test known hosts is passed to pychromecasts."""
result = await hass.config_entries.flow.async_init(
"cast", context={"source": "user"}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"known_hosts": "192.168.0.1, 192.168.0.2"}
)
assert result["type"] == "create_entry"
await hass.async_block_till_done()
config_entry = hass.config_entries.async_entries("cast")[0]
assert castbrowser_mock.start_discovery.call_count == 1
castbrowser_constructor_mock.assert_called_once_with(
ANY, ANY, ["192.168.0.1", "192.168.0.2"]
)
castbrowser_mock.reset_mock()
castbrowser_constructor_mock.reset_mock()
result = await hass.config_entries.options.async_init(config_entry.entry_id)
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={"known_hosts": "192.168.0.11, 192.168.0.12"},
)
await hass.async_block_till_done()
castbrowser_mock.start_discovery.assert_not_called()
castbrowser_constructor_mock.assert_not_called()
castbrowser_mock.host_browser.update_hosts.assert_called_once_with(
["192.168.0.11", "192.168.0.12"]
)

View File

@@ -1,39 +1,9 @@
"""Tests for the Cast config flow."""
from unittest.mock import ANY, patch
"""Tests for the Cast integration."""
from unittest.mock import patch
import pytest
from homeassistant import config_entries, data_entry_flow
from homeassistant.components import cast
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
async def test_creating_entry_sets_up_media_player(hass):
"""Test setting up Cast loads the media player."""
with patch(
"homeassistant.components.cast.media_player.async_setup_entry",
return_value=True,
) as mock_setup, patch(
"pychromecast.discovery.discover_chromecasts", return_value=(True, None)
), patch(
"pychromecast.discovery.stop_discovery"
):
result = await hass.config_entries.flow.async_init(
cast.DOMAIN, context={"source": config_entries.SOURCE_USER}
)
# Confirmation form
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 1
async def test_import(hass, caplog):
"""Test that specifying config will create an entry."""
@@ -67,7 +37,7 @@ async def test_import(hass, caplog):
async def test_not_configuring_cast_not_creates_entry(hass):
"""Test that no config will not create an entry."""
"""Test that an empty config does not create an entry."""
with patch(
"homeassistant.components.cast.async_setup_entry", return_value=True
) as mock_setup:
@@ -75,207 +45,3 @@ async def test_not_configuring_cast_not_creates_entry(hass):
await hass.async_block_till_done()
assert len(mock_setup.mock_calls) == 0
@pytest.mark.parametrize("source", ["import", "user", "zeroconf"])
async def test_single_instance(hass, source):
"""Test we only allow a single config flow."""
MockConfigEntry(domain="cast").add_to_hass(hass)
await hass.async_block_till_done()
result = await hass.config_entries.flow.async_init(
"cast", context={"source": source}
)
assert result["type"] == "abort"
assert result["reason"] == "single_instance_allowed"
async def test_user_setup(hass):
"""Test we can finish a config flow."""
result = await hass.config_entries.flow.async_init(
"cast", context={"source": "user"}
)
assert result["type"] == "form"
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
users = await hass.auth.async_get_users()
assert len(users) == 1
assert result["type"] == "create_entry"
assert result["result"].data == {
"ignore_cec": [],
"known_hosts": [],
"uuid": [],
"user_id": users[0].id, # Home Assistant cast user
}
async def test_user_setup_options(hass):
"""Test we can finish a config flow."""
result = await hass.config_entries.flow.async_init(
"cast", context={"source": "user"}
)
assert result["type"] == "form"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"known_hosts": "192.168.0.1, , 192.168.0.2 "}
)
users = await hass.auth.async_get_users()
assert len(users) == 1
assert result["type"] == "create_entry"
assert result["result"].data == {
"ignore_cec": [],
"known_hosts": ["192.168.0.1", "192.168.0.2"],
"uuid": [],
"user_id": users[0].id, # Home Assistant cast user
}
async def test_zeroconf_setup(hass):
"""Test we can finish a config flow through zeroconf."""
result = await hass.config_entries.flow.async_init(
"cast", context={"source": "zeroconf"}
)
assert result["type"] == "form"
result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
users = await hass.auth.async_get_users()
assert len(users) == 1
assert result["type"] == "create_entry"
assert result["result"].data == {
"ignore_cec": [],
"known_hosts": [],
"uuid": [],
"user_id": users[0].id, # Home Assistant cast user
}
def get_suggested(schema, key):
"""Get suggested value for key in voluptuous schema."""
for k in schema.keys():
if k == key:
if k.description is None or "suggested_value" not in k.description:
return None
return k.description["suggested_value"]
@pytest.mark.parametrize(
"parameter_data",
[
(
"known_hosts",
["192.168.0.10", "192.168.0.11"],
"192.168.0.10,192.168.0.11",
"192.168.0.1, , 192.168.0.2 ",
["192.168.0.1", "192.168.0.2"],
),
(
"uuid",
["bla", "blu"],
"bla,blu",
"foo, , bar ",
["foo", "bar"],
),
(
"ignore_cec",
["cast1", "cast2"],
"cast1,cast2",
"other_cast, , some_cast ",
["other_cast", "some_cast"],
),
],
)
async def test_option_flow(hass, parameter_data):
"""Test config flow options."""
all_parameters = ["ignore_cec", "known_hosts", "uuid"]
parameter, initial, suggested, user_input, updated = parameter_data
data = {
"ignore_cec": [],
"known_hosts": [],
"uuid": [],
}
data[parameter] = initial
config_entry = MockConfigEntry(domain="cast", data=data)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Test ignore_cec and uuid options are hidden if advanced options are disabled
result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "options"
data_schema = result["data_schema"].schema
assert set(data_schema) == {"known_hosts"}
# Reconfigure ignore_cec, known_hosts, uuid
context = {"source": "user", "show_advanced_options": True}
result = await hass.config_entries.options.async_init(
config_entry.entry_id, context=context
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "options"
data_schema = result["data_schema"].schema
for other_param in all_parameters:
if other_param == parameter:
continue
assert get_suggested(data_schema, other_param) == ""
assert get_suggested(data_schema, parameter) == suggested
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={parameter: user_input},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["data"] is None
for other_param in all_parameters:
if other_param == parameter:
continue
assert config_entry.data[other_param] == []
assert config_entry.data[parameter] == updated
# Clear known_hosts
result = await hass.config_entries.options.async_init(config_entry.entry_id)
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={"known_hosts": ""},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["data"] is None
assert config_entry.data == {"ignore_cec": [], "known_hosts": [], "uuid": []}
async def test_known_hosts(hass, castbrowser_mock, castbrowser_constructor_mock):
"""Test known hosts is passed to pychromecasts."""
result = await hass.config_entries.flow.async_init(
"cast", context={"source": "user"}
)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {"known_hosts": "192.168.0.1, 192.168.0.2"}
)
assert result["type"] == "create_entry"
await hass.async_block_till_done()
config_entry = hass.config_entries.async_entries("cast")[0]
assert castbrowser_mock.start_discovery.call_count == 1
castbrowser_constructor_mock.assert_called_once_with(
ANY, ANY, ["192.168.0.1", "192.168.0.2"]
)
castbrowser_mock.reset_mock()
castbrowser_constructor_mock.reset_mock()
result = await hass.config_entries.options.async_init(config_entry.entry_id)
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={"known_hosts": "192.168.0.11, 192.168.0.12"},
)
await hass.async_block_till_done()
castbrowser_mock.start_discovery.assert_not_called()
castbrowser_constructor_mock.assert_not_called()
castbrowser_mock.host_browser.update_hosts.assert_called_once_with(
["192.168.0.11", "192.168.0.12"]
)

View File

@@ -1,9 +1,14 @@
"""Test the Coronavirus config flow."""
from unittest.mock import MagicMock, patch
from aiohttp import ClientError
from homeassistant import config_entries, setup
from homeassistant.components.coronavirus.const import DOMAIN, OPTION_WORLDWIDE
from homeassistant.core import HomeAssistant
async def test_form(hass):
async def test_form(hass: HomeAssistant) -> None:
"""Test we get the form."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
@@ -24,3 +29,22 @@ async def test_form(hass):
}
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4
@patch(
"coronavirus.get_cases",
side_effect=ClientError,
)
async def test_abort_on_connection_error(
mock_get_cases: MagicMock, hass: HomeAssistant
) -> None:
"""Test we abort on connection error."""
await setup.async_setup_component(hass, "persistent_notification", {})
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert "type" in result
assert result["type"] == "abort"
assert "reason" in result
assert result["reason"] == "cannot_connect"

View File

@@ -1,12 +1,18 @@
"""Test init of Coronavirus integration."""
from unittest.mock import MagicMock, patch
from aiohttp import ClientError
from homeassistant.components.coronavirus.const import DOMAIN, OPTION_WORLDWIDE
from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry, mock_registry
async def test_migration(hass):
async def test_migration(hass: HomeAssistant) -> None:
"""Test that we can migrate coronavirus to stable unique ID."""
nl_entry = MockConfigEntry(domain=DOMAIN, title="Netherlands", data={"country": 34})
nl_entry.add_to_hass(hass)
@@ -47,3 +53,20 @@ async def test_migration(hass):
assert nl_entry.unique_id == "Netherlands"
assert worldwide_entry.unique_id == OPTION_WORLDWIDE
@patch(
"coronavirus.get_cases",
side_effect=ClientError,
)
async def test_config_entry_not_ready(
mock_get_cases: MagicMock, hass: HomeAssistant
) -> None:
"""Test the configuration entry not ready."""
entry = MockConfigEntry(domain=DOMAIN, title="Netherlands", data={"country": 34})
entry.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
assert entry.state == ENTRY_STATE_SETUP_RETRY

View File

@@ -145,6 +145,8 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass):
hass.states.async_set("camera.one", "on")
hass.states.async_set("camera.existing", "on")
hass.states.async_set("media_player.two", "on", {"device_class": "tv"})
hass.states.async_set("remote.standard", "on")
hass.states.async_set("remote.activity", "on", {"supported_features": 4})
bridge_mode_entry = MockConfigEntry(
domain=DOMAIN,
@@ -178,7 +180,7 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"include_domains": ["camera", "media_player", "light"]},
{"include_domains": ["camera", "media_player", "light", "remote"]},
)
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result2["step_id"] == "pairing"
@@ -205,7 +207,7 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass):
"filter": {
"exclude_domains": [],
"exclude_entities": [],
"include_domains": ["media_player", "light"],
"include_domains": ["media_player", "light", "remote"],
"include_entities": [],
},
"exclude_accessory_mode": True,
@@ -222,7 +224,8 @@ async def test_setup_creates_entries_for_accessory_mode_devices(hass):
# 3 - new bridge
# 4 - camera.one in accessory mode
# 5 - media_player.two in accessory mode
assert len(mock_setup_entry.mock_calls) == 5
# 6 - remote.activity in accessory mode
assert len(mock_setup_entry.mock_calls) == 6
async def test_import(hass):

View File

@@ -42,6 +42,7 @@ async def test_log_filtering(hass, caplog):
"doesntmatchanything",
".*shouldfilterall.*",
"^filterthis:.*",
"in the middle",
],
"test.other_filter": [".*otherfilterer"],
},
@@ -62,6 +63,7 @@ async def test_log_filtering(hass, caplog):
filter_logger, False, "this line containing shouldfilterall should be filtered"
)
msg_test(filter_logger, True, "this line should not be filtered filterthis:")
msg_test(filter_logger, False, "this in the middle should be filtered")
msg_test(filter_logger, False, "filterthis: should be filtered")
msg_test(filter_logger, False, "format string shouldfilter%s", "all")
msg_test(filter_logger, True, "format string shouldfilter%s", "not")

View File

@@ -10,6 +10,7 @@ import pytest
from homeassistant.components.maxcube import DOMAIN
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import now
@pytest.fixture
@@ -105,5 +106,5 @@ async def cube(hass, hass_config, room, thermostat, wallthermostat, windowshutte
assert await async_setup_component(hass, DOMAIN, hass_config)
await hass.async_block_till_done()
gateway = hass_config[DOMAIN]["gateways"][0]
mock.assert_called_with(gateway["host"], gateway.get("port", 62910))
mock.assert_called_with(gateway["host"], gateway.get("port", 62910), now=now)
return cube

View File

@@ -1,5 +1,6 @@
"""Test Media Source initialization."""
from unittest.mock import patch
from urllib.parse import quote
import pytest
@@ -45,7 +46,7 @@ async def test_async_browse_media(hass):
media = await media_source.async_browse_media(hass, "")
assert isinstance(media, media_source.models.BrowseMediaSource)
assert media.title == "media/"
assert len(media.children) == 1
assert len(media.children) == 2
# Test invalid media content
with pytest.raises(ValueError):
@@ -133,14 +134,15 @@ async def test_websocket_browse_media(hass, hass_ws_client):
assert msg["error"]["message"] == "test"
async def test_websocket_resolve_media(hass, hass_ws_client):
@pytest.mark.parametrize("filename", ["test.mp3", "Epic Sax Guy 10 Hours.mp4"])
async def test_websocket_resolve_media(hass, hass_ws_client, filename):
"""Test browse media websocket."""
assert await async_setup_component(hass, const.DOMAIN, {})
await hass.async_block_till_done()
client = await hass_ws_client(hass)
media = media_source.models.PlayMedia("/media/local/test.mp3", "audio/mpeg")
media = media_source.models.PlayMedia(f"/media/local/{filename}", "audio/mpeg")
with patch(
"homeassistant.components.media_source.async_resolve_media",
@@ -150,7 +152,7 @@ async def test_websocket_resolve_media(hass, hass_ws_client):
{
"id": 1,
"type": "media_source/resolve_media",
"media_content_id": f"{const.URI_SCHEME}{const.DOMAIN}/local/test.mp3",
"media_content_id": f"{const.URI_SCHEME}{const.DOMAIN}/local/{filename}",
}
)
@@ -158,7 +160,7 @@ async def test_websocket_resolve_media(hass, hass_ws_client):
assert msg["success"]
assert msg["id"] == 1
assert msg["result"]["url"].startswith(media.url)
assert msg["result"]["url"].startswith(quote(media.url))
assert msg["result"]["mime_type"] == media.mime_type
with patch(

View File

@@ -95,5 +95,8 @@ async def test_media_view(hass, hass_client):
resp = await client.get("/media/local/test.mp3")
assert resp.status == 200
resp = await client.get("/media/local/Epic Sax Guy 10 Hours.mp4")
assert resp.status == 200
resp = await client.get("/media/recordings/test.mp3")
assert resp.status == 200

View File

@@ -85,11 +85,11 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog):
"preset_mode_state_topic": "preset-mode-state-topic",
"preset_mode_command_topic": "preset-mode-command-topic",
"preset_modes": [
"medium",
"medium-high",
"high",
"very-high",
"freaking-high",
"auto",
"smart",
"whoosh",
"eco",
"breeze",
"silent",
],
"speed_range_min": 1,
@@ -126,6 +126,8 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog):
state = hass.states.get("fan.test")
assert state.attributes.get("oscillating") is False
assert state.attributes.get("percentage_step") == 1.0
async_fire_mqtt_message(hass, "percentage-state-topic", "0")
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0
@@ -151,16 +153,16 @@ async def test_controlling_state_via_topic(hass, mqtt_mock, caplog):
caplog.clear()
async_fire_mqtt_message(hass, "preset-mode-state-topic", "low")
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "low"
assert "not a valid preset mode" in caplog.text
caplog.clear()
async_fire_mqtt_message(hass, "preset-mode-state-topic", "medium")
async_fire_mqtt_message(hass, "preset-mode-state-topic", "auto")
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "medium"
assert state.attributes.get("preset_mode") == "auto"
async_fire_mqtt_message(hass, "preset-mode-state-topic", "very-high")
async_fire_mqtt_message(hass, "preset-mode-state-topic", "eco")
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "very-high"
assert state.attributes.get("preset_mode") == "eco"
async_fire_mqtt_message(hass, "preset-mode-state-topic", "silent")
state = hass.states.get("fan.test")
@@ -256,7 +258,9 @@ async def test_controlling_state_via_topic_with_different_speed_range(
caplog.clear()
async def test_controlling_state_via_topic_no_percentage_topics(hass, mqtt_mock):
async def test_controlling_state_via_topic_no_percentage_topics(
hass, mqtt_mock, caplog
):
"""Test the controlling state via topic without percentage topics."""
assert await async_setup_component(
hass,
@@ -273,9 +277,11 @@ async def test_controlling_state_via_topic_no_percentage_topics(hass, mqtt_mock)
"preset_mode_state_topic": "preset-mode-state-topic",
"preset_mode_command_topic": "preset-mode-command-topic",
"preset_modes": [
"high",
"freaking-high",
"silent",
"auto",
"smart",
"whoosh",
"eco",
"breeze",
],
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
"speeds": ["off", "low", "medium"],
@@ -288,57 +294,51 @@ async def test_controlling_state_via_topic_no_percentage_topics(hass, mqtt_mock)
assert state.state == STATE_OFF
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "preset-mode-state-topic", "freaking-high")
async_fire_mqtt_message(hass, "preset-mode-state-topic", "smart")
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "freaking-high"
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100
assert state.attributes.get("preset_mode") == "smart"
assert state.attributes.get(fan.ATTR_PERCENTAGE) is None
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
assert state.attributes.get("speed") == fan.SPEED_OFF
async_fire_mqtt_message(hass, "preset-mode-state-topic", "high")
async_fire_mqtt_message(hass, "preset-mode-state-topic", "auto")
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "high"
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 75
assert state.attributes.get("preset_mode") == "auto"
assert state.attributes.get(fan.ATTR_PERCENTAGE) is None
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
assert state.attributes.get("speed") == fan.SPEED_OFF
async_fire_mqtt_message(hass, "preset-mode-state-topic", "silent")
async_fire_mqtt_message(hass, "preset-mode-state-topic", "whoosh")
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "silent"
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 75
assert state.attributes.get("preset_mode") == "whoosh"
assert state.attributes.get(fan.ATTR_PERCENTAGE) is None
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
assert state.attributes.get("speed") == fan.SPEED_OFF
async_fire_mqtt_message(hass, "preset-mode-state-topic", "medium")
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "medium"
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
assert state.attributes.get("speed") == fan.SPEED_OFF
assert "not a valid preset mode" in caplog.text
caplog.clear()
async_fire_mqtt_message(hass, "preset-mode-state-topic", "low")
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "low"
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 25
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
assert state.attributes.get("speed") == fan.SPEED_OFF
assert "not a valid preset mode" in caplog.text
caplog.clear()
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
async_fire_mqtt_message(hass, "speed-state-topic", "medium")
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "low"
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50
assert state.attributes.get("preset_mode") == "whoosh"
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100
assert state.attributes.get("speed") == fan.SPEED_MEDIUM
async_fire_mqtt_message(hass, "speed-state-topic", "low")
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "low"
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 25
assert state.attributes.get("preset_mode") == "whoosh"
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 50
assert state.attributes.get("speed") == fan.SPEED_LOW
async_fire_mqtt_message(hass, "speed-state-topic", "off")
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "low"
assert state.attributes.get("preset_mode") == "whoosh"
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0
assert state.attributes.get("speed") == fan.SPEED_OFF
@@ -361,11 +361,11 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap
"preset_mode_state_topic": "preset-mode-state-topic",
"preset_mode_command_topic": "preset-mode-command-topic",
"preset_modes": [
"medium",
"medium-high",
"high",
"very-high",
"freaking-high",
"auto",
"smart",
"whoosh",
"eco",
"breeze",
"silent",
],
"state_value_template": "{{ value_json.val }}",
@@ -412,20 +412,20 @@ async def test_controlling_state_via_topic_and_json_message(hass, mqtt_mock, cap
assert "not a valid preset mode" in caplog.text
caplog.clear()
async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "medium"}')
async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "auto"}')
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "medium"
assert state.attributes.get("preset_mode") == "auto"
async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "freaking-high"}')
async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "breeze"}')
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "freaking-high"
assert state.attributes.get("preset_mode") == "breeze"
async_fire_mqtt_message(hass, "preset-mode-state-topic", '{"val": "silent"}')
state = hass.states.get("fan.test")
assert state.attributes.get("preset_mode") == "silent"
async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock, caplog):
"""Test optimistic mode without state topic."""
assert await async_setup_component(
hass,
@@ -447,8 +447,8 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
"speeds": ["off", "low", "medium"],
"preset_modes": [
"high",
"freaking-high",
"whoosh",
"breeze",
"silent",
],
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
@@ -510,7 +510,7 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "100", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "freaking-high", 0, False
"speed-command-topic", "speed_mEdium", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
@@ -518,11 +518,8 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_percentage(hass, "fan.test", 0)
assert mqtt_mock.async_publish.call_count == 3
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "0", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "off", 0, False
)
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call(
"speed-command-topic", "speed_OfF", 0, False
@@ -534,54 +531,32 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
assert state.attributes.get(fan.ATTR_SPEED) == fan.SPEED_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
await common.async_set_preset_mode(hass, "fan.test", "low")
assert mqtt_mock.async_publish.call_count == 2
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call(
"speed-command-topic", "speed_lOw", 0, False
)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "low", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "low"
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
assert state.attributes.get(fan.ATTR_SPEED) == fan.SPEED_LOW
assert state.attributes.get(ATTR_ASSUMED_STATE)
assert "not a valid preset mode" in caplog.text
caplog.clear()
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
await common.async_set_preset_mode(hass, "fan.test", "medium")
assert mqtt_mock.async_publish.call_count == 2
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call(
"speed-command-topic", "speed_mEdium", 0, False
)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "medium", 0, False
assert "not a valid preset mode" in caplog.text
caplog.clear()
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
mqtt_mock.async_publish.assert_called_once_with(
"preset-mode-command-topic", "whoosh", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "medium"
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
assert state.attributes.get(fan.ATTR_SPEED) == fan.SPEED_MEDIUM
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "whoosh"
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "high")
await common.async_set_preset_mode(hass, "fan.test", "breeze")
mqtt_mock.async_publish.assert_called_once_with(
"preset-mode-command-topic", "high", 0, False
"preset-mode-command-topic", "breeze", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "high"
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "freaking-high")
mqtt_mock.async_publish.assert_called_once_with(
"preset-mode-command-topic", "freaking-high", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "freaking-high"
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "breeze"
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "silent")
@@ -615,13 +590,8 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH)
mqtt_mock.async_publish.assert_called_once_with(
"speed-command-topic", "speed_High", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
assert "not a valid speed" in caplog.text
caplog.clear()
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF)
@@ -648,7 +618,7 @@ async def test_sending_mqtt_commands_with_alternate_speed_range(hass, mqtt_mock)
"percentage_state_topic": "percentage-state-topic1",
"percentage_command_topic": "percentage-command-topic1",
"speed_range_min": 1,
"speed_range_max": 100,
"speed_range_max": 3,
},
{
"platform": "mqtt",
@@ -681,9 +651,25 @@ async def test_sending_mqtt_commands_with_alternate_speed_range(hass, mqtt_mock)
state = hass.states.get("fan.test1")
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_percentage(hass, "fan.test1", 33)
mqtt_mock.async_publish.assert_called_once_with(
"percentage-command-topic1", "1", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test1")
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_percentage(hass, "fan.test1", 66)
mqtt_mock.async_publish.assert_called_once_with(
"percentage-command-topic1", "2", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test1")
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_percentage(hass, "fan.test1", 100)
mqtt_mock.async_publish.assert_called_once_with(
"percentage-command-topic1", "100", 0, False
"percentage-command-topic1", "3", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test1")
@@ -735,8 +721,8 @@ async def test_sending_mqtt_commands_and_optimistic_no_legacy(hass, mqtt_mock, c
"percentage_command_topic": "percentage-command-topic",
"preset_mode_command_topic": "preset-mode-command-topic",
"preset_modes": [
"high",
"freaking-high",
"whoosh",
"breeze",
"silent",
],
}
@@ -769,14 +755,12 @@ async def test_sending_mqtt_commands_and_optimistic_no_legacy(hass, mqtt_mock, c
await common.async_set_percentage(hass, "fan.test", 101)
await common.async_set_percentage(hass, "fan.test", 100)
mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "100", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "freaking-high", 0, False
mqtt_mock.async_publish.assert_called_once_with(
"percentage-command-topic", "100", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "freaking-high"
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_percentage(hass, "fan.test", 0)
@@ -793,26 +777,26 @@ async def test_sending_mqtt_commands_and_optimistic_no_legacy(hass, mqtt_mock, c
assert "not a valid preset mode" in caplog.text
caplog.clear()
await common.async_set_preset_mode(hass, "fan.test", "medium")
await common.async_set_preset_mode(hass, "fan.test", "auto")
assert "not a valid preset mode" in caplog.text
caplog.clear()
await common.async_set_preset_mode(hass, "fan.test", "high")
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
mqtt_mock.async_publish.assert_called_once_with(
"preset-mode-command-topic", "high", 0, False
"preset-mode-command-topic", "whoosh", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "high"
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "whoosh"
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "freaking-high")
await common.async_set_preset_mode(hass, "fan.test", "breeze")
mqtt_mock.async_publish.assert_called_once_with(
"preset-mode-command-topic", "freaking-high", 0, False
"preset-mode-command-topic", "breeze", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "freaking-high"
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "breeze"
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "silent")
@@ -825,12 +809,9 @@ async def test_sending_mqtt_commands_and_optimistic_no_legacy(hass, mqtt_mock, c
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_turn_on(hass, "fan.test", percentage=25)
assert mqtt_mock.async_publish.call_count == 3
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False)
mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "25", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "high", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_ON
@@ -843,11 +824,11 @@ async def test_sending_mqtt_commands_and_optimistic_no_legacy(hass, mqtt_mock, c
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_turn_on(hass, "fan.test", preset_mode="high")
await common.async_turn_on(hass, "fan.test", preset_mode="whoosh")
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "high", 0, False
"preset-mode-command-topic", "whoosh", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
@@ -855,7 +836,7 @@ async def test_sending_mqtt_commands_and_optimistic_no_legacy(hass, mqtt_mock, c
assert state.attributes.get(ATTR_ASSUMED_STATE)
with pytest.raises(NotValidPresetModeError):
await common.async_turn_on(hass, "fan.test", preset_mode="low")
await common.async_turn_on(hass, "fan.test", preset_mode="freaking-high")
async def test_sending_mqtt_command_templates_(hass, mqtt_mock, caplog):
@@ -876,8 +857,8 @@ async def test_sending_mqtt_command_templates_(hass, mqtt_mock, caplog):
"preset_mode_command_topic": "preset-mode-command-topic",
"preset_mode_command_template": "preset_mode: {{ value }}",
"preset_modes": [
"high",
"freaking-high",
"whoosh",
"breeze",
"silent",
],
}
@@ -914,16 +895,12 @@ async def test_sending_mqtt_command_templates_(hass, mqtt_mock, caplog):
await common.async_set_percentage(hass, "fan.test", 101)
await common.async_set_percentage(hass, "fan.test", 100)
mqtt_mock.async_publish.assert_any_call(
mqtt_mock.async_publish.assert_called_once_with(
"percentage-command-topic", "percentage: 100", 0, False
)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "preset_mode: freaking-high", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "freaking-high"
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_percentage(hass, "fan.test", 0)
@@ -944,22 +921,22 @@ async def test_sending_mqtt_command_templates_(hass, mqtt_mock, caplog):
assert "not a valid preset mode" in caplog.text
caplog.clear()
await common.async_set_preset_mode(hass, "fan.test", "high")
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
mqtt_mock.async_publish.assert_called_once_with(
"preset-mode-command-topic", "preset_mode: high", 0, False
"preset-mode-command-topic", "preset_mode: whoosh", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "high"
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "whoosh"
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "freaking-high")
await common.async_set_preset_mode(hass, "fan.test", "breeze")
mqtt_mock.async_publish.assert_called_once_with(
"preset-mode-command-topic", "preset_mode: freaking-high", 0, False
"preset-mode-command-topic", "preset_mode: breeze", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "freaking-high"
assert state.attributes.get(fan.ATTR_PRESET_MODE) == "breeze"
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "silent")
@@ -972,14 +949,11 @@ async def test_sending_mqtt_command_templates_(hass, mqtt_mock, caplog):
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_turn_on(hass, "fan.test", percentage=25)
assert mqtt_mock.async_publish.call_count == 3
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("command-topic", "state: ON", 0, False)
mqtt_mock.async_publish.assert_any_call(
"percentage-command-topic", "percentage: 25", 0, False
)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "preset_mode: high", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_ON
@@ -992,11 +966,11 @@ async def test_sending_mqtt_command_templates_(hass, mqtt_mock, caplog):
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_turn_on(hass, "fan.test", preset_mode="high")
await common.async_turn_on(hass, "fan.test", preset_mode="whoosh")
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("command-topic", "state: ON", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "preset_mode: high", 0, False
"preset-mode-command-topic", "preset_mode: whoosh", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
@@ -1008,7 +982,7 @@ async def test_sending_mqtt_command_templates_(hass, mqtt_mock, caplog):
async def test_sending_mqtt_commands_and_optimistic_no_percentage_topic(
hass, mqtt_mock
hass, mqtt_mock, caplog
):
"""Test optimistic mode without state topic without percentage command topic."""
assert await async_setup_component(
@@ -1027,9 +1001,10 @@ async def test_sending_mqtt_commands_and_optimistic_no_percentage_topic(
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
"speeds": ["off", "low", "medium"],
"preset_modes": [
"high",
"freaking-high",
"whoosh",
"breeze",
"silent",
"high",
],
}
},
@@ -1047,9 +1022,7 @@ async def test_sending_mqtt_commands_and_optimistic_no_percentage_topic(
await common.async_set_percentage(hass, "fan.test", 101)
await common.async_set_percentage(hass, "fan.test", 100)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "freaking-high", 0, False
)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 100
@@ -1063,41 +1036,27 @@ async def test_sending_mqtt_commands_and_optimistic_no_percentage_topic(
assert state.attributes.get(fan.ATTR_PERCENTAGE) == 0
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "low")
assert mqtt_mock.async_publish.call_count == 2
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "low", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PRESET_MODE) is None
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "low")
assert "not a valid preset mode" in caplog.text
caplog.clear()
await common.async_set_preset_mode(hass, "fan.test", "medium")
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "medium", 0, False
assert "not a valid preset mode" in caplog.text
caplog.clear()
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
mqtt_mock.async_publish.assert_called_once_with(
"preset-mode-command-topic", "whoosh", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PRESET_MODE) is None
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "high")
await common.async_set_preset_mode(hass, "fan.test", "breeze")
mqtt_mock.async_publish.assert_called_once_with(
"preset-mode-command-topic", "high", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.attributes.get(fan.ATTR_PRESET_MODE) is None
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "freaking-high")
mqtt_mock.async_publish.assert_called_once_with(
"preset-mode-command-topic", "freaking-high", 0, False
"preset-mode-command-topic", "breeze", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
@@ -1133,14 +1092,8 @@ async def test_sending_mqtt_commands_and_optimistic_no_percentage_topic(
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH)
mqtt_mock.async_publish.assert_called_once_with(
"speed-command-topic", "high", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
assert "not a valid speed" in caplog.text
caplog.clear()
await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False)
@@ -1150,13 +1103,10 @@ async def test_sending_mqtt_commands_and_optimistic_no_percentage_topic(
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_turn_on(hass, "fan.test", speed="medium")
assert mqtt_mock.async_publish.call_count == 3
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False)
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "medium", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_ON
@@ -1325,8 +1275,8 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
"speeds": ["off", "low", "medium"],
"preset_modes": [
"high",
"freaking-high",
"whoosh",
"breeze",
"silent",
],
"optimistic": True,
@@ -1358,9 +1308,7 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca
mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False)
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "medium", 0, False
)
mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "100", 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_ON
@@ -1374,11 +1322,8 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_turn_on(hass, "fan.test", percentage=25)
assert mqtt_mock.async_publish.call_count == 4
assert mqtt_mock.async_publish.call_count == 3
mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "low", 0, False
)
mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "25", 0, False)
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False)
@@ -1394,24 +1339,15 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_turn_on(hass, "fan.test", preset_mode="medium")
assert mqtt_mock.async_publish.call_count == 3
mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "medium", 0, False
)
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_ON
assert state.attributes.get(ATTR_ASSUMED_STATE)
with pytest.raises(NotValidPresetModeError):
await common.async_turn_on(hass, "fan.test", preset_mode="auto")
await common.async_turn_on(hass, "fan.test", preset_mode="high")
await common.async_turn_on(hass, "fan.test", preset_mode="whoosh")
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "high", 0, False
"preset-mode-command-topic", "whoosh", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
@@ -1471,14 +1407,11 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_turn_on(hass, "fan.test", percentage=50)
assert mqtt_mock.async_publish.call_count == 4
assert mqtt_mock.async_publish.call_count == 3
mqtt_mock.async_publish.assert_any_call("command-topic", "ON", 0, False)
mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "50", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "medium", 0, False
)
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_ON
@@ -1501,26 +1434,20 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_percentage(hass, "fan.test", 33)
assert mqtt_mock.async_publish.call_count == 3
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "33", 0, False)
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "medium", 0, False
)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_percentage(hass, "fan.test", 50)
assert mqtt_mock.async_publish.call_count == 3
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "50", 0, False)
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "medium", 0, False
)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_OFF
@@ -1529,22 +1456,18 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca
await common.async_set_percentage(hass, "fan.test", 100)
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "100", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "freaking-high", 0, False
)
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_percentage(hass, "fan.test", 0)
assert mqtt_mock.async_publish.call_count == 3
assert mqtt_mock.async_publish.call_count == 2
mqtt_mock.async_publish.assert_any_call("percentage-command-topic", "0", 0, False)
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "off", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "off", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_OFF
@@ -1554,32 +1477,16 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca
await common.async_set_percentage(hass, "fan.test", 101)
await common.async_set_preset_mode(hass, "fan.test", "low")
assert mqtt_mock.async_publish.call_count == 2
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "low", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "low", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
assert "not a valid preset mode" in caplog.text
caplog.clear()
await common.async_set_preset_mode(hass, "fan.test", "medium")
assert mqtt_mock.async_publish.call_count == 2
# use of speeds is deprecated, support will be removed after a quarter (2021.7)
mqtt_mock.async_publish.assert_any_call("speed-command-topic", "medium", 0, False)
mqtt_mock.async_publish.assert_any_call(
"preset-mode-command-topic", "medium", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
assert "not a valid preset mode" in caplog.text
caplog.clear()
await common.async_set_preset_mode(hass, "fan.test", "high")
await common.async_set_preset_mode(hass, "fan.test", "whoosh")
mqtt_mock.async_publish.assert_called_once_with(
"preset-mode-command-topic", "high", 0, False
"preset-mode-command-topic", "whoosh", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
@@ -1595,7 +1502,7 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_preset_mode(hass, "fan.test", "ModeX")
await common.async_set_preset_mode(hass, "fan.test", "freaking-high")
assert "not a valid preset mode" in caplog.text
caplog.clear()
@@ -1615,13 +1522,8 @@ async def test_sending_mqtt_commands_and_explicit_optimistic(hass, mqtt_mock, ca
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_set_speed(hass, "fan.test", fan.SPEED_HIGH)
mqtt_mock.async_publish.assert_called_once_with(
"speed-command-topic", "high", 0, False
)
mqtt_mock.async_publish.reset_mock()
state = hass.states.get("fan.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_ASSUMED_STATE)
assert "not a valid speed" in caplog.text
caplog.clear()
await common.async_set_speed(hass, "fan.test", fan.SPEED_OFF)
mqtt_mock.async_publish.assert_called_once_with(
@@ -1653,7 +1555,7 @@ async def test_attributes(hass, mqtt_mock, caplog):
"preset_mode_command_topic": "preset-mode-command-topic",
"percentage_command_topic": "percentage-command-topic",
"preset_modes": [
"freaking-high",
"breeze",
"silent",
],
}
@@ -1667,7 +1569,6 @@ async def test_attributes(hass, mqtt_mock, caplog):
"low",
"medium",
"high",
"freaking-high",
]
await common.async_turn_on(hass, "fan.test")
@@ -1821,14 +1722,14 @@ async def test_supported_features(hass, mqtt_mock):
"name": "test3c2",
"command_topic": "command-topic",
"preset_mode_command_topic": "preset-mode-command-topic",
"preset_modes": ["very-fast", "auto"],
"preset_modes": ["eco", "auto"],
},
{
"platform": "mqtt",
"name": "test3c3",
"command_topic": "command-topic",
"preset_mode_command_topic": "preset-mode-command-topic",
"preset_modes": ["off", "on", "auto"],
"preset_modes": ["eco", "smart", "auto"],
},
{
"platform": "mqtt",
@@ -1863,7 +1764,7 @@ async def test_supported_features(hass, mqtt_mock):
"name": "test5pr_mb",
"command_topic": "command-topic",
"preset_mode_command_topic": "preset-mode-command-topic",
"preset_modes": ["off", "on", "auto"],
"preset_modes": ["whoosh", "silent", "auto"],
},
{
"platform": "mqtt",
@@ -1927,10 +1828,7 @@ async def test_supported_features(hass, mqtt_mock):
assert state is None
state = hass.states.get("fan.test3c2")
assert (
state.attributes.get(ATTR_SUPPORTED_FEATURES)
== fan.SUPPORT_PRESET_MODE | fan.SUPPORT_SET_SPEED
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_PRESET_MODE
state = hass.states.get("fan.test3c3")
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_PRESET_MODE
@@ -1949,21 +1847,19 @@ async def test_supported_features(hass, mqtt_mock):
)
state = hass.states.get("fan.test5pr_ma")
assert (
state.attributes.get(ATTR_SUPPORTED_FEATURES)
== fan.SUPPORT_SET_SPEED | fan.SUPPORT_PRESET_MODE
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_PRESET_MODE
state = hass.states.get("fan.test5pr_mb")
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_PRESET_MODE
state = hass.states.get("fan.test5pr_mc")
assert (
state.attributes.get(ATTR_SUPPORTED_FEATURES)
== fan.SUPPORT_OSCILLATE | fan.SUPPORT_SET_SPEED | fan.SUPPORT_PRESET_MODE
== fan.SUPPORT_OSCILLATE | fan.SUPPORT_PRESET_MODE
)
state = hass.states.get("fan.test6spd_range_a")
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == fan.SUPPORT_SET_SPEED
assert state.attributes.get("percentage_step") == 2.5
state = hass.states.get("fan.test6spd_range_b")
assert state is None
state = hass.states.get("fan.test6spd_range_c")

View File

@@ -234,10 +234,10 @@ async def test_rgb_light(hass, mqtt_mock):
state = hass.states.get("light.test")
expected_features = (
light.SUPPORT_TRANSITION
light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR
| light.SUPPORT_FLASH
| light.SUPPORT_BRIGHTNESS
| light.SUPPORT_TRANSITION
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
@@ -261,7 +261,8 @@ async def test_no_color_brightness_color_temp_white_val_if_no_topics(hass, mqtt_
state = hass.states.get("light.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 40
expected_features = light.SUPPORT_FLASH | light.SUPPORT_TRANSITION
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get("rgb_color") is None
assert state.attributes.get("brightness") is None
assert state.attributes.get("color_temp") is None
@@ -310,7 +311,16 @@ async def test_controlling_state_via_topic(hass, mqtt_mock):
state = hass.states.get("light.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 191
expected_features = (
light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR
| light.SUPPORT_COLOR_TEMP
| light.SUPPORT_EFFECT
| light.SUPPORT_FLASH
| light.SUPPORT_TRANSITION
| light.SUPPORT_WHITE_VALUE
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get("rgb_color") is None
assert state.attributes.get("brightness") is None
assert state.attributes.get("color_temp") is None
@@ -429,7 +439,15 @@ async def test_controlling_state_via_topic2(hass, mqtt_mock, caplog):
state = hass.states.get("light.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 44
expected_features = (
light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR
| light.SUPPORT_COLOR_TEMP
| light.SUPPORT_EFFECT
| light.SUPPORT_FLASH
| light.SUPPORT_TRANSITION
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get("brightness") is None
assert state.attributes.get("color_mode") is None
assert state.attributes.get("color_temp") is None
@@ -610,7 +628,16 @@ async def test_sending_mqtt_commands_and_optimistic(hass, mqtt_mock):
assert state.attributes.get("effect") == "random"
assert state.attributes.get("color_temp") == 100
assert state.attributes.get("white_value") == 50
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 191
expected_features = (
light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR
| light.SUPPORT_COLOR_TEMP
| light.SUPPORT_EFFECT
| light.SUPPORT_FLASH
| light.SUPPORT_TRANSITION
| light.SUPPORT_WHITE_VALUE
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get(ATTR_ASSUMED_STATE)
await common.async_turn_on(hass, "light.test")
@@ -738,7 +765,15 @@ async def test_sending_mqtt_commands_and_optimistic2(hass, mqtt_mock):
state = hass.states.get("light.test")
assert state.state == STATE_ON
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 44
expected_features = (
light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR
| light.SUPPORT_COLOR_TEMP
| light.SUPPORT_EFFECT
| light.SUPPORT_FLASH
| light.SUPPORT_TRANSITION
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get("brightness") == 95
assert state.attributes.get("color_mode") == "rgb"
assert state.attributes.get("color_temp") is None
@@ -1313,7 +1348,10 @@ async def test_effect(hass, mqtt_mock):
state = hass.states.get("light.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 44
expected_features = (
light.SUPPORT_EFFECT | light.SUPPORT_FLASH | light.SUPPORT_TRANSITION
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
await common.async_turn_on(hass, "light.test")
@@ -1373,7 +1411,8 @@ async def test_flash_short_and_long(hass, mqtt_mock):
state = hass.states.get("light.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 40
expected_features = light.SUPPORT_FLASH | light.SUPPORT_TRANSITION
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
await common.async_turn_on(hass, "light.test", flash="short")
@@ -1431,8 +1470,8 @@ async def test_transition(hass, mqtt_mock):
state = hass.states.get("light.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 40
expected_features = light.SUPPORT_FLASH | light.SUPPORT_TRANSITION
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
await common.async_turn_on(hass, "light.test", transition=15)
mqtt_mock.async_publish.assert_called_once_with(
@@ -1523,7 +1562,15 @@ async def test_invalid_values(hass, mqtt_mock):
state = hass.states.get("light.test")
assert state.state == STATE_OFF
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 187
expected_features = (
light.SUPPORT_BRIGHTNESS
| light.SUPPORT_COLOR
| light.SUPPORT_COLOR_TEMP
| light.SUPPORT_FLASH
| light.SUPPORT_TRANSITION
| light.SUPPORT_WHITE_VALUE
)
assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == expected_features
assert state.attributes.get("rgb_color") is None
assert state.attributes.get("brightness") is None
assert state.attributes.get("white_value") is None

View File

@@ -3,7 +3,7 @@ import json
from unittest.mock import ANY, patch
import homeassistant.components.mqtt_eventstream as eventstream
from homeassistant.const import EVENT_STATE_CHANGED
from homeassistant.const import EVENT_STATE_CHANGED, MATCH_ALL
from homeassistant.core import State, callback
from homeassistant.helpers.json import JSONEncoder
from homeassistant.setup import async_setup_component
@@ -114,6 +114,7 @@ async def test_time_event_does_not_send_message(hass, mqtt_mock):
mqtt_mock.async_publish.reset_mock()
async_fire_time_changed(hass, dt_util.utcnow())
await hass.async_block_till_done()
assert not mqtt_mock.async_publish.called
@@ -140,6 +141,33 @@ async def test_receiving_remote_event_fires_hass_event(hass, mqtt_mock):
assert len(calls) == 1
await hass.async_block_till_done()
async def test_receiving_blocked_event_fires_hass_event(hass, mqtt_mock):
"""Test the receiving of blocked event does not fire."""
sub_topic = "foo"
assert await add_eventstream(hass, sub_topic=sub_topic)
await hass.async_block_till_done()
calls = []
@callback
def listener(_):
calls.append(1)
hass.bus.async_listen(MATCH_ALL, listener)
await hass.async_block_till_done()
for event in eventstream.BLOCKED_EVENTS:
payload = json.dumps({"event_type": event, "event_data": {}}, cls=JSONEncoder)
async_fire_mqtt_message(hass, sub_topic, payload)
await hass.async_block_till_done()
assert len(calls) == 0
await hass.async_block_till_done()
async def test_ignored_event_doesnt_send_over_stream(hass, mqtt_mock):
"""Test the ignoring of sending events if defined."""
@@ -159,6 +187,7 @@ async def test_ignored_event_doesnt_send_over_stream(hass, mqtt_mock):
# Set a state of an entity
mock_state_change_event(hass, State(e_id, "on"))
await hass.async_block_till_done()
await hass.async_block_till_done()
assert not mqtt_mock.async_publish.called

View File

@@ -0,0 +1,27 @@
"""Test ping id allocation."""
from homeassistant.components.ping import async_get_next_ping_id
from homeassistant.components.ping.const import (
DEFAULT_START_ID,
DOMAIN,
MAX_PING_ID,
PING_ID,
)
async def test_async_get_next_ping_id(hass):
"""Verify we allocate ping ids as expected."""
hass.data[DOMAIN] = {PING_ID: DEFAULT_START_ID}
assert async_get_next_ping_id(hass) == DEFAULT_START_ID + 1
assert async_get_next_ping_id(hass) == DEFAULT_START_ID + 2
assert async_get_next_ping_id(hass, 2) == DEFAULT_START_ID + 3
assert async_get_next_ping_id(hass) == DEFAULT_START_ID + 5
hass.data[DOMAIN][PING_ID] = MAX_PING_ID
assert async_get_next_ping_id(hass) == DEFAULT_START_ID + 1
assert async_get_next_ping_id(hass) == DEFAULT_START_ID + 2
hass.data[DOMAIN][PING_ID] = MAX_PING_ID
assert async_get_next_ping_id(hass, 2) == DEFAULT_START_ID + 1
assert async_get_next_ping_id(hass) == DEFAULT_START_ID + 3

View File

@@ -1,4 +1,6 @@
"""The tests for Shelly device triggers."""
from unittest.mock import AsyncMock, Mock
import pytest
from homeassistant import setup
@@ -6,10 +8,13 @@ from homeassistant.components import automation
from homeassistant.components.device_automation.exceptions import (
InvalidDeviceAutomationConfig,
)
from homeassistant.components.shelly import ShellyDeviceWrapper
from homeassistant.components.shelly.const import (
ATTR_CHANNEL,
ATTR_CLICK_TYPE,
COAP,
CONF_SUBTYPE,
DATA_CONFIG_ENTRY,
DOMAIN,
EVENT_SHELLY_CLICK,
)
@@ -52,6 +57,71 @@ async def test_get_triggers(hass, coap_wrapper):
assert_lists_same(triggers, expected_triggers)
async def test_get_triggers_button(hass):
"""Test we get the expected triggers from a shelly button."""
await async_setup_component(hass, "shelly", {})
config_entry = MockConfigEntry(
domain=DOMAIN,
data={"sleep_period": 43200, "model": "SHBTN-1"},
unique_id="12345678",
)
config_entry.add_to_hass(hass)
device = Mock(
blocks=None,
settings=None,
shelly=None,
update=AsyncMock(),
initialized=False,
)
hass.data[DOMAIN] = {DATA_CONFIG_ENTRY: {}}
hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id] = {}
coap_wrapper = hass.data[DOMAIN][DATA_CONFIG_ENTRY][config_entry.entry_id][
COAP
] = ShellyDeviceWrapper(hass, config_entry, device)
await coap_wrapper.async_setup()
expected_triggers = [
{
CONF_PLATFORM: "device",
CONF_DEVICE_ID: coap_wrapper.device_id,
CONF_DOMAIN: DOMAIN,
CONF_TYPE: "single",
CONF_SUBTYPE: "button",
},
{
CONF_PLATFORM: "device",
CONF_DEVICE_ID: coap_wrapper.device_id,
CONF_DOMAIN: DOMAIN,
CONF_TYPE: "double",
CONF_SUBTYPE: "button",
},
{
CONF_PLATFORM: "device",
CONF_DEVICE_ID: coap_wrapper.device_id,
CONF_DOMAIN: DOMAIN,
CONF_TYPE: "triple",
CONF_SUBTYPE: "button",
},
{
CONF_PLATFORM: "device",
CONF_DEVICE_ID: coap_wrapper.device_id,
CONF_DOMAIN: DOMAIN,
CONF_TYPE: "long",
CONF_SUBTYPE: "button",
},
]
triggers = await async_get_device_automations(
hass, "trigger", coap_wrapper.device_id
)
assert_lists_same(triggers, expected_triggers)
async def test_get_triggers_for_invalid_device_id(hass, device_reg, coap_wrapper):
"""Test error raised for invalid shelly device_id."""
assert coap_wrapper

View File

@@ -102,6 +102,7 @@ async def test_setup_component_demo(hass):
assert hass.services.has_service(tts.DOMAIN, "demo_say")
assert hass.services.has_service(tts.DOMAIN, "clear_cache")
assert f"{tts.DOMAIN}.demo" in hass.config.components
async def test_setup_component_demo_no_access_cache_folder(hass, mock_init_cache_dir):

View File

@@ -12,6 +12,7 @@ from homeassistant.components.climate.const import (
ATTR_PRESET_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
CURRENT_HVAC_COOL,
CURRENT_HVAC_IDLE,
DOMAIN as CLIMATE_DOMAIN,
HVAC_MODE_COOL,
@@ -351,6 +352,7 @@ async def test_thermostat_different_endpoints(
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 22.8
assert state.attributes[ATTR_FAN_MODE] == "Auto low"
assert state.attributes[ATTR_FAN_STATE] == "Idle / off"
assert state.attributes[ATTR_HVAC_ACTION] == CURRENT_HVAC_COOL
async def test_setpoint_thermostat(hass, client, climate_danfoss_lc_13, integration):

View File

@@ -528,7 +528,7 @@ async def test_poll_value(
},
blocking=True,
)
assert len(client.async_send_command.call_args_list) == 7
assert len(client.async_send_command.call_args_list) == 8
# Test polling against an invalid entity raises ValueError
with pytest.raises(ValueError):

View File

@@ -0,0 +1 @@
I play the sax