forked from home-assistant/core
Compare commits
33 Commits
0.115.0b7
...
0.115.0b10
| Author | SHA1 | Date | |
|---|---|---|---|
| e095120023 | |||
| 3ef3d848f7 | |||
| 610a327b52 | |||
| 81436fb688 | |||
| 24fe9cdd5a | |||
| e5c499c22e | |||
| 99a8604601 | |||
| 3ef821d62f | |||
| a38e047e83 | |||
| e0fcf9b648 | |||
| 0e823b566b | |||
| a9d24c2cd5 | |||
| 7a7cad39eb | |||
| 1a76a953c7 | |||
| db27079fa8 | |||
| ef1649383c | |||
| afde5a7ece | |||
| 30b8565548 | |||
| a971b92899 | |||
| 4ee7cdc8a0 | |||
| 4c2788a13c | |||
| 8b4e193614 | |||
| f0ce65af7d | |||
| b81c61dd99 | |||
| 30ef7a5e88 | |||
| 5a6492b76d | |||
| b19fe17e76 | |||
| 47326b2295 | |||
| 951c373110 | |||
| b9b76b3519 | |||
| da6885af6c | |||
| bc2173747c | |||
| d0e6b3e268 |
@@ -656,11 +656,6 @@ omit =
|
||||
homeassistant/components/plaato/*
|
||||
homeassistant/components/plex/media_player.py
|
||||
homeassistant/components/plex/sensor.py
|
||||
homeassistant/components/plugwise/__init__.py
|
||||
homeassistant/components/plugwise/binary_sensor.py
|
||||
homeassistant/components/plugwise/climate.py
|
||||
homeassistant/components/plugwise/sensor.py
|
||||
homeassistant/components/plugwise/switch.py
|
||||
homeassistant/components/plum_lightpad/light.py
|
||||
homeassistant/components/pocketcasts/sensor.py
|
||||
homeassistant/components/point/*
|
||||
|
||||
+1
-1
@@ -466,7 +466,7 @@ homeassistant/components/velbus/* @Cereal2nd @brefra
|
||||
homeassistant/components/velux/* @Julius2342
|
||||
homeassistant/components/vera/* @vangorra
|
||||
homeassistant/components/versasense/* @flamm3blemuff1n
|
||||
homeassistant/components/version/* @fabaff
|
||||
homeassistant/components/version/* @fabaff @ludeeus
|
||||
homeassistant/components/vesync/* @markperdue @webdjoe @thegardenmonkey
|
||||
homeassistant/components/vicare/* @oischinger
|
||||
homeassistant/components/vilfo/* @ManneW
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
- template: templates/azp-job-wheels.yaml@azure
|
||||
parameters:
|
||||
builderVersion: '$(versionWheels)'
|
||||
builderApk: 'build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev'
|
||||
builderApk: 'build-base;cmake;git;linux-headers;libexecinfo-dev;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev'
|
||||
builderPip: 'Cython;numpy;scikit-build'
|
||||
builderEnvFile: true
|
||||
skipBinary: 'aiohttp'
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
|
||||
# Write env for build settings
|
||||
(
|
||||
echo "GRPC_BUILD_WITH_BORING_SSL_ASM=0"
|
||||
echo "GRPC_BUILD_WITH_BORING_SSL_ASM="
|
||||
echo "GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=1"
|
||||
) > .env_file
|
||||
displayName: 'Prepare requirements files for Home Assistant wheels'
|
||||
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"image": "homeassistant/{arch}-homeassistant",
|
||||
"build_from": {
|
||||
"aarch64": "homeassistant/aarch64-homeassistant-base:8.3.0",
|
||||
"armhf": "homeassistant/armhf-homeassistant-base:8.3.0",
|
||||
"armv7": "homeassistant/armv7-homeassistant-base:8.3.0",
|
||||
"amd64": "homeassistant/amd64-homeassistant-base:8.3.0",
|
||||
"i386": "homeassistant/i386-homeassistant-base:8.3.0"
|
||||
"aarch64": "homeassistant/aarch64-homeassistant-base:8.4.0",
|
||||
"armhf": "homeassistant/armhf-homeassistant-base:8.4.0",
|
||||
"armv7": "homeassistant/armv7-homeassistant-base:8.4.0",
|
||||
"amd64": "homeassistant/amd64-homeassistant-base:8.4.0",
|
||||
"i386": "homeassistant/i386-homeassistant-base:8.4.0"
|
||||
},
|
||||
"labels": {
|
||||
"io.hass.type": "core"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/androidtv",
|
||||
"requirements": [
|
||||
"adb-shell[async]==0.2.1",
|
||||
"androidtv[async]==0.0.49",
|
||||
"androidtv[async]==0.0.50",
|
||||
"pure-python-adb[async]==0.3.0.dev0"
|
||||
],
|
||||
"codeowners": ["@JeffLIrion"]
|
||||
|
||||
@@ -380,7 +380,7 @@ def adb_decorator(override_available=False):
|
||||
# An unforeseen exception occurred. Close the ADB connection so that
|
||||
# it doesn't happen over and over again, then raise the exception.
|
||||
await self.aftv.adb_close()
|
||||
self._available = False # pylint: disable=protected-access
|
||||
self._available = False
|
||||
raise
|
||||
|
||||
return _adb_exception_catcher
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "deCONZ",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/deconz",
|
||||
"requirements": ["pydeconz==72"],
|
||||
"requirements": ["pydeconz==73"],
|
||||
"ssdp": [
|
||||
{
|
||||
"manufacturer": "Royal Philips Electronics"
|
||||
|
||||
@@ -27,7 +27,7 @@ async def async_setup_entry(
|
||||
|
||||
for device in hass.data[DOMAIN]["homecontrol"].multi_level_switch_devices:
|
||||
for multi_level_switch in device.multi_level_switch_property:
|
||||
if device.deviceModelUID in [
|
||||
if device.device_model_uid in [
|
||||
"devolo.model.Thermostat:Valve",
|
||||
"devolo.model.Room:Thermostat",
|
||||
]:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Support for displaying weather info from Ecobee API."""
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from pyecobee.const import ECOBEE_STATE_UNKNOWN
|
||||
|
||||
@@ -13,6 +13,7 @@ from homeassistant.components.weather import (
|
||||
WeatherEntity,
|
||||
)
|
||||
from homeassistant.const import TEMP_FAHRENHEIT
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .const import (
|
||||
_LOGGER,
|
||||
@@ -165,10 +166,13 @@ class EcobeeWeather(WeatherEntity):
|
||||
return None
|
||||
|
||||
forecasts = []
|
||||
for day in range(1, 5):
|
||||
date = dt_util.utcnow()
|
||||
for day in range(0, 5):
|
||||
forecast = _process_forecast(self.weather["forecasts"][day])
|
||||
if forecast is None:
|
||||
continue
|
||||
forecast[ATTR_FORECAST_TIME] = date.isoformat()
|
||||
date += timedelta(days=1)
|
||||
forecasts.append(forecast)
|
||||
|
||||
if forecasts:
|
||||
@@ -186,9 +190,6 @@ def _process_forecast(json):
|
||||
"""Process a single ecobee API forecast to return expected values."""
|
||||
forecast = {}
|
||||
try:
|
||||
forecast[ATTR_FORECAST_TIME] = datetime.strptime(
|
||||
json["dateTime"], "%Y-%m-%d %H:%M:%S"
|
||||
).isoformat()
|
||||
forecast[ATTR_FORECAST_CONDITION] = ECOBEE_WEATHER_SYMBOL_TO_HASS[
|
||||
json["weatherSymbol"]
|
||||
]
|
||||
|
||||
@@ -146,11 +146,12 @@ class FreeboxCallSensor(FreeboxSensor):
|
||||
def async_update_state(self) -> None:
|
||||
"""Update the Freebox call sensor."""
|
||||
self._call_list_for_type = []
|
||||
for call in self._router.call_list:
|
||||
if not call["new"]:
|
||||
continue
|
||||
if call["type"] == self._sensor_type:
|
||||
self._call_list_for_type.append(call)
|
||||
if self._router.call_list:
|
||||
for call in self._router.call_list:
|
||||
if not call["new"]:
|
||||
continue
|
||||
if call["type"] == self._sensor_type:
|
||||
self._call_list_for_type.append(call)
|
||||
|
||||
self._state = len(self._call_list_for_type)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"domain": "frontend",
|
||||
"name": "Home Assistant Frontend",
|
||||
"documentation": "https://www.home-assistant.io/integrations/frontend",
|
||||
"requirements": ["home-assistant-frontend==20200909.0"],
|
||||
"requirements": ["home-assistant-frontend==20200915.0"],
|
||||
"dependencies": [
|
||||
"api",
|
||||
"auth",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Gogogate2 and iSmartGate",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/gogogate2",
|
||||
"requirements": ["gogogate2-api==2.0.1"],
|
||||
"requirements": ["gogogate2-api==2.0.2"],
|
||||
"codeowners": ["@vangorra"],
|
||||
"homekit": {
|
||||
"models": [
|
||||
|
||||
@@ -24,6 +24,10 @@ from homeassistant.const import (
|
||||
STATE_ON,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.util.color import (
|
||||
color_temperature_mired_to_kelvin,
|
||||
color_temperature_to_hs,
|
||||
)
|
||||
|
||||
from .accessories import TYPES, HomeAccessory
|
||||
from .const import (
|
||||
@@ -64,8 +68,6 @@ class Light(HomeAccessory):
|
||||
if self._features & SUPPORT_COLOR:
|
||||
self.chars.append(CHAR_HUE)
|
||||
self.chars.append(CHAR_SATURATION)
|
||||
self._hue = None
|
||||
self._saturation = None
|
||||
elif self._features & SUPPORT_COLOR_TEMP:
|
||||
# ColorTemperature and Hue characteristic should not be
|
||||
# exposed both. Both states are tracked separately in HomeKit,
|
||||
@@ -179,7 +181,16 @@ class Light(HomeAccessory):
|
||||
|
||||
# Handle Color
|
||||
if CHAR_SATURATION in self.chars and CHAR_HUE in self.chars:
|
||||
hue, saturation = new_state.attributes.get(ATTR_HS_COLOR, (None, None))
|
||||
if ATTR_HS_COLOR in new_state.attributes:
|
||||
hue, saturation = new_state.attributes[ATTR_HS_COLOR]
|
||||
elif ATTR_COLOR_TEMP in new_state.attributes:
|
||||
hue, saturation = color_temperature_to_hs(
|
||||
color_temperature_mired_to_kelvin(
|
||||
new_state.attributes[ATTR_COLOR_TEMP]
|
||||
)
|
||||
)
|
||||
else:
|
||||
hue, saturation = None, None
|
||||
if isinstance(hue, (int, float)) and isinstance(saturation, (int, float)):
|
||||
hue = round(hue, 0)
|
||||
saturation = round(saturation, 0)
|
||||
|
||||
@@ -8,12 +8,19 @@ import voluptuous as vol
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components import zeroconf
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import (
|
||||
CONNECTION_NETWORK_MAC,
|
||||
async_get_registry as async_get_device_registry,
|
||||
)
|
||||
|
||||
from .connection import get_accessory_name, get_bridge_information
|
||||
from .const import DOMAIN, KNOWN_DEVICES
|
||||
|
||||
HOMEKIT_IGNORE = ["Home Assistant Bridge"]
|
||||
HOMEKIT_DIR = ".homekit"
|
||||
HOMEKIT_BRIDGE_DOMAIN = "homekit"
|
||||
HOMEKIT_BRIDGE_SERIAL_NUMBER = "homekit.bridge"
|
||||
HOMEKIT_BRIDGE_MODEL = "Home Assistant HomeKit Bridge"
|
||||
|
||||
PAIRING_FILE = "pairing.json"
|
||||
|
||||
PIN_FORMAT = re.compile(r"^(\d{3})-{0,1}(\d{2})-{0,1}(\d{3})$")
|
||||
@@ -141,6 +148,17 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow):
|
||||
|
||||
return self.async_abort(reason="no_devices")
|
||||
|
||||
async def _hkid_is_homekit_bridge(self, hkid):
|
||||
"""Determine if the device is a homekit bridge."""
|
||||
dev_reg = await async_get_device_registry(self.hass)
|
||||
device = dev_reg.async_get_device(
|
||||
identifiers=set(), connections={(CONNECTION_NETWORK_MAC, hkid)}
|
||||
)
|
||||
|
||||
if device is None:
|
||||
return False
|
||||
return device.model == HOMEKIT_BRIDGE_MODEL
|
||||
|
||||
async def async_step_zeroconf(self, discovery_info):
|
||||
"""Handle a discovered HomeKit accessory.
|
||||
|
||||
@@ -153,6 +171,12 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow):
|
||||
key.lower(): value for (key, value) in discovery_info["properties"].items()
|
||||
}
|
||||
|
||||
if "id" not in properties:
|
||||
_LOGGER.warning(
|
||||
"HomeKit device %s: id not exposed, in violation of spec", properties
|
||||
)
|
||||
return self.async_abort(reason="invalid_properties")
|
||||
|
||||
# The hkid is a unique random number that looks like a pairing code.
|
||||
# It changes if a device is factory reset.
|
||||
hkid = properties["id"]
|
||||
@@ -208,7 +232,7 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow):
|
||||
# Devices in HOMEKIT_IGNORE have native local integrations - users
|
||||
# should be encouraged to use native integration and not confused
|
||||
# by alternative HK API.
|
||||
if model in HOMEKIT_IGNORE:
|
||||
if await self._hkid_is_homekit_bridge(hkid):
|
||||
return self.async_abort(reason="ignored_model")
|
||||
|
||||
self.model = model
|
||||
@@ -280,9 +304,8 @@ class HomekitControllerFlowHandler(config_entries.ConfigFlow):
|
||||
# Its possible that the first try may have been busy so
|
||||
# we always check to see if self.finish_paring has been
|
||||
# set.
|
||||
discovery = await self.controller.find_ip_by_device_id(self.hkid)
|
||||
|
||||
try:
|
||||
discovery = await self.controller.find_ip_by_device_id(self.hkid)
|
||||
self.finish_pairing = await discovery.start_pairing(self.hkid)
|
||||
|
||||
except aiohomekit.BusyError:
|
||||
|
||||
@@ -3,8 +3,16 @@
|
||||
"name": "HomeKit Controller",
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
|
||||
"requirements": ["aiohomekit[IP]==0.2.49"],
|
||||
"zeroconf": ["_hap._tcp.local."],
|
||||
"after_dependencies": ["zeroconf"],
|
||||
"codeowners": ["@Jc2k"]
|
||||
"requirements": [
|
||||
"aiohomekit==0.2.53"
|
||||
],
|
||||
"zeroconf": [
|
||||
"_hap._tcp.local."
|
||||
],
|
||||
"after_dependencies": [
|
||||
"zeroconf"
|
||||
],
|
||||
"codeowners": [
|
||||
"@Jc2k"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"already_configured": "Accessory is already configured with this controller.",
|
||||
"invalid_config_entry": "This device is showing as ready to pair but there is already a conflicting configuration entry for it in Home Assistant that must first be removed.",
|
||||
"accessory_not_found_error": "Cannot add pairing as device can no longer be found.",
|
||||
"invalid_properties": "Invalid properties announced by device.",
|
||||
"already_in_progress": "Config flow for device is already in progress."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ CONNECTION_FAILED = "connection_failed"
|
||||
CONNECTION_FAILED_RECOVERABLE = "connection_failed_recoverable"
|
||||
|
||||
DISCOVERY_COOLDOWN = 2
|
||||
TIMEOUT_ACK = 1
|
||||
TIMEOUT_ACK = 2
|
||||
|
||||
PLATFORMS = [
|
||||
"alarm_control_panel",
|
||||
|
||||
@@ -284,9 +284,9 @@ class NetatmoCamera(NetatmoBase, Camera):
|
||||
self._data.events.get(self._id, {})
|
||||
)
|
||||
elif self._model == "NOC": # Smart Outdoor Camera
|
||||
self.hass.data[DOMAIN][DATA_EVENTS][
|
||||
self._id
|
||||
] = self._data.outdoor_events.get(self._id, {})
|
||||
self.hass.data[DOMAIN][DATA_EVENTS][self._id] = self.process_events(
|
||||
self._data.outdoor_events.get(self._id, {})
|
||||
)
|
||||
|
||||
def process_events(self, events):
|
||||
"""Add meta data to events."""
|
||||
|
||||
@@ -80,8 +80,20 @@ class NetatmoSource(MediaSource):
|
||||
) -> BrowseMediaSource:
|
||||
if event_id and event_id in self.events[camera_id]:
|
||||
created = dt.datetime.fromtimestamp(event_id)
|
||||
thumbnail = self.events[camera_id][event_id].get("snapshot", {}).get("url")
|
||||
message = remove_html_tags(self.events[camera_id][event_id]["message"])
|
||||
if self.events[camera_id][event_id]["type"] == "outdoor":
|
||||
thumbnail = (
|
||||
self.events[camera_id][event_id]["event_list"][0]
|
||||
.get("snapshot", {})
|
||||
.get("url")
|
||||
)
|
||||
message = remove_html_tags(
|
||||
self.events[camera_id][event_id]["event_list"][0]["message"]
|
||||
)
|
||||
else:
|
||||
thumbnail = (
|
||||
self.events[camera_id][event_id].get("snapshot", {}).get("url")
|
||||
)
|
||||
message = remove_html_tags(self.events[camera_id][event_id]["message"])
|
||||
title = f"{created} - {message}"
|
||||
else:
|
||||
title = self.hass.data[DOMAIN][DATA_CAMERAS].get(camera_id, MANUFACTURER)
|
||||
|
||||
@@ -38,8 +38,8 @@ def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]:
|
||||
"""
|
||||
nzbget_api = NZBGetAPI(
|
||||
data[CONF_HOST],
|
||||
data[CONF_USERNAME] if data[CONF_USERNAME] != "" else None,
|
||||
data[CONF_PASSWORD] if data[CONF_PASSWORD] != "" else None,
|
||||
data.get(CONF_USERNAME),
|
||||
data.get(CONF_PASSWORD),
|
||||
data[CONF_SSL],
|
||||
data[CONF_VERIFY_SSL],
|
||||
data[CONF_PORT],
|
||||
|
||||
@@ -29,8 +29,8 @@ class NZBGetDataUpdateCoordinator(DataUpdateCoordinator):
|
||||
"""Initialize global NZBGet data updater."""
|
||||
self.nzbget = NZBGetAPI(
|
||||
config[CONF_HOST],
|
||||
config[CONF_USERNAME] if config[CONF_USERNAME] != "" else None,
|
||||
config[CONF_PASSWORD] if config[CONF_PASSWORD] != "" else None,
|
||||
config.get(CONF_USERNAME),
|
||||
config.get(CONF_PASSWORD),
|
||||
config[CONF_SSL],
|
||||
config[CONF_VERIFY_SSL],
|
||||
config[CONF_PORT],
|
||||
|
||||
@@ -91,7 +91,7 @@ class OpenWeatherMapConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if CONF_LONGITUDE not in config:
|
||||
config[CONF_LONGITUDE] = self.hass.config.longitude
|
||||
if CONF_MODE not in config:
|
||||
config[CONF_MODE] = DEFAULT_LANGUAGE
|
||||
config[CONF_MODE] = DEFAULT_FORECAST_MODE
|
||||
if CONF_LANGUAGE not in config:
|
||||
config[CONF_LANGUAGE] = DEFAULT_LANGUAGE
|
||||
return await self.async_step_user(config)
|
||||
|
||||
@@ -210,7 +210,8 @@ class PingDataSubProcess(PingData):
|
||||
out_error,
|
||||
)
|
||||
|
||||
if pinger.returncode != 0:
|
||||
if pinger.returncode > 1:
|
||||
# returncode of 1 means the host is unreachable
|
||||
_LOGGER.exception(
|
||||
"Error running command: `%s`, return code: %s",
|
||||
" ".join(self._ping_cmd),
|
||||
|
||||
@@ -205,6 +205,7 @@ def special_library_payload(parent_payload, special_type):
|
||||
media_content_type=parent_payload.media_content_type,
|
||||
can_play=False,
|
||||
can_expand=True,
|
||||
children_media_class=parent_payload.children_media_class,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -94,6 +94,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
|
||||
api.get_all_devices()
|
||||
|
||||
if entry.unique_id is None:
|
||||
if api.smile_version[0] != "1.8.0":
|
||||
hass.config_entries.async_update_entry(entry, unique_id=api.smile_hostname)
|
||||
|
||||
undo_listener = entry.add_update_listener(_update_listener)
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
|
||||
|
||||
@@ -96,6 +96,10 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
if self.discovery_info:
|
||||
user_input[CONF_HOST] = self.discovery_info[CONF_HOST]
|
||||
|
||||
for entry in self._async_current_entries():
|
||||
if entry.data.get(CONF_HOST) == user_input[CONF_HOST]:
|
||||
return self.async_abort(reason="already_configured")
|
||||
|
||||
try:
|
||||
api = await validate_input(self.hass, user_input)
|
||||
|
||||
@@ -106,9 +110,10 @@ class PlugwiseConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
|
||||
if not errors:
|
||||
await self.async_set_unique_id(api.gateway_id)
|
||||
await self.async_set_unique_id(
|
||||
api.smile_hostname or api.gateway_id, raise_on_progress=False
|
||||
)
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(title=api.smile_name, data=user_input)
|
||||
|
||||
@@ -202,22 +202,23 @@ class SlackNotificationService(BaseNotificationService):
|
||||
self, targets, message, title, blocks, username, icon
|
||||
):
|
||||
"""Send a text-only message."""
|
||||
if self._icon.lower().startswith(("http://", "https://")):
|
||||
icon_type = "url"
|
||||
else:
|
||||
icon_type = "emoji"
|
||||
message_dict = {
|
||||
"blocks": blocks,
|
||||
"link_names": True,
|
||||
"text": message,
|
||||
"username": username,
|
||||
}
|
||||
|
||||
if self._icon:
|
||||
if self._icon.lower().startswith(("http://", "https://")):
|
||||
icon_type = "url"
|
||||
else:
|
||||
icon_type = "emoji"
|
||||
|
||||
message_dict[f"icon_{icon_type}"] = icon
|
||||
|
||||
tasks = {
|
||||
target: self._client.chat_postMessage(
|
||||
**{
|
||||
"blocks": blocks,
|
||||
"channel": target,
|
||||
"link_names": True,
|
||||
"text": message,
|
||||
"username": username,
|
||||
f"icon_{icon_type}": icon,
|
||||
}
|
||||
)
|
||||
target: self._client.chat_postMessage(**message_dict, channel=target)
|
||||
for target in targets
|
||||
}
|
||||
|
||||
|
||||
@@ -1547,6 +1547,13 @@ def library_payload(media_library):
|
||||
|
||||
Used by async_browse_media.
|
||||
"""
|
||||
if not media_library.browse_by_idstring(
|
||||
"tracks",
|
||||
"",
|
||||
max_items=1,
|
||||
):
|
||||
raise BrowseError("Local library not found")
|
||||
|
||||
children = []
|
||||
for item in media_library.browse():
|
||||
try:
|
||||
|
||||
@@ -143,9 +143,12 @@ class SpeedTestDataCoordinator(DataUpdateCoordinator):
|
||||
|
||||
self.servers[DEFAULT_SERVER] = {}
|
||||
for server in sorted(
|
||||
server_list.values(), key=lambda server: server[0]["country"]
|
||||
server_list.values(),
|
||||
key=lambda server: server[0]["country"] + server[0]["sponsor"],
|
||||
):
|
||||
self.servers[f"{server[0]['country']} - {server[0]['sponsor']}"] = server[0]
|
||||
self.servers[
|
||||
f"{server[0]['country']} - {server[0]['sponsor']} - {server[0]['name']}"
|
||||
] = server[0]
|
||||
|
||||
def update_data(self):
|
||||
"""Get the latest data from speedtest.net."""
|
||||
|
||||
@@ -148,7 +148,8 @@ class HlsStreamOutput(StreamOutput):
|
||||
def container_options(self) -> Callable[[int], dict]:
|
||||
"""Return Callable which takes a sequence number and returns container options."""
|
||||
return lambda sequence: {
|
||||
"movflags": "frag_custom+empty_moov+default_base_moof+skip_sidx+frag_discont",
|
||||
# Removed skip_sidx - see https://github.com/home-assistant/core/pull/39970
|
||||
"movflags": "frag_custom+empty_moov+default_base_moof+frag_discont",
|
||||
"avoid_negative_ts": "make_non_negative",
|
||||
"fragment_index": str(sequence),
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class TagStorageCollection(collection.StorageCollection):
|
||||
data[TAG_ID] = str(uuid.uuid4())
|
||||
# make last_scanned JSON serializeable
|
||||
if LAST_SCANNED in data:
|
||||
data[LAST_SCANNED] = str(data[LAST_SCANNED])
|
||||
data[LAST_SCANNED] = data[LAST_SCANNED].isoformat()
|
||||
return data
|
||||
|
||||
@callback
|
||||
@@ -83,7 +83,7 @@ class TagStorageCollection(collection.StorageCollection):
|
||||
data = {**data, **self.UPDATE_SCHEMA(update_data)}
|
||||
# make last_scanned JSON serializeable
|
||||
if LAST_SCANNED in data:
|
||||
data[LAST_SCANNED] = str(data[LAST_SCANNED])
|
||||
data[LAST_SCANNED] = data[LAST_SCANNED].isoformat()
|
||||
return data
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"domain": "version",
|
||||
"name": "Version",
|
||||
"documentation": "https://www.home-assistant.io/integrations/version",
|
||||
"requirements": ["pyhaversion==3.3.0"],
|
||||
"codeowners": ["@fabaff"],
|
||||
"requirements": ["pyhaversion==3.4.0"],
|
||||
"codeowners": ["@fabaff", "@ludeeus"],
|
||||
"quality_scale": "internal"
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ ALL_IMAGES = [
|
||||
"raspberrypi4-64",
|
||||
"tinker",
|
||||
"odroid-c2",
|
||||
"odroid-n2",
|
||||
"odroid-xu",
|
||||
]
|
||||
ALL_SOURCES = ["local", "pypi", "hassio", "docker", "haio"]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/zha",
|
||||
"requirements": [
|
||||
"bellows==0.20.1",
|
||||
"bellows==0.20.2",
|
||||
"pyserial==3.4",
|
||||
"zha-quirks==0.0.44",
|
||||
"zigpy-cc==0.5.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Constants used by Home Assistant components."""
|
||||
MAJOR_VERSION = 0
|
||||
MINOR_VERSION = 115
|
||||
PATCH_VERSION = "0b7"
|
||||
PATCH_VERSION = "0b10"
|
||||
__short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||
__version__ = f"{__short_version__}.{PATCH_VERSION}"
|
||||
REQUIRED_PYTHON_VER = (3, 7, 1)
|
||||
|
||||
@@ -649,13 +649,16 @@ async def async_validate_condition_config(
|
||||
|
||||
|
||||
@callback
|
||||
def async_extract_entities(config: ConfigType) -> Set[str]:
|
||||
def async_extract_entities(config: Union[ConfigType, Template]) -> Set[str]:
|
||||
"""Extract entities from a condition."""
|
||||
referenced: Set[str] = set()
|
||||
to_process = deque([config])
|
||||
|
||||
while to_process:
|
||||
config = to_process.popleft()
|
||||
if isinstance(config, Template):
|
||||
continue
|
||||
|
||||
condition = config[CONF_CONDITION]
|
||||
|
||||
if condition in ("and", "not", "or"):
|
||||
@@ -674,13 +677,16 @@ def async_extract_entities(config: ConfigType) -> Set[str]:
|
||||
|
||||
|
||||
@callback
|
||||
def async_extract_devices(config: ConfigType) -> Set[str]:
|
||||
def async_extract_devices(config: Union[ConfigType, Template]) -> Set[str]:
|
||||
"""Extract devices from a condition."""
|
||||
referenced = set()
|
||||
to_process = deque([config])
|
||||
|
||||
while to_process:
|
||||
config = to_process.popleft()
|
||||
if isinstance(config, Template):
|
||||
continue
|
||||
|
||||
condition = config[CONF_CONDITION]
|
||||
|
||||
if condition in ("and", "not", "or"):
|
||||
|
||||
@@ -13,7 +13,7 @@ defusedxml==0.6.0
|
||||
distro==1.5.0
|
||||
emoji==0.5.4
|
||||
hass-nabucasa==0.37.0
|
||||
home-assistant-frontend==20200909.0
|
||||
home-assistant-frontend==20200915.0
|
||||
importlib-metadata==1.6.0;python_version<'3.8'
|
||||
jinja2>=2.11.2
|
||||
netdisco==2.8.2
|
||||
|
||||
@@ -178,7 +178,7 @@ aioguardian==1.0.1
|
||||
aioharmony==0.2.6
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit[IP]==0.2.49
|
||||
aiohomekit==0.2.53
|
||||
|
||||
# homeassistant.components.emulated_hue
|
||||
# homeassistant.components.http
|
||||
@@ -248,7 +248,7 @@ ambiclimate==0.2.1
|
||||
amcrest==1.7.0
|
||||
|
||||
# homeassistant.components.androidtv
|
||||
androidtv[async]==0.0.49
|
||||
androidtv[async]==0.0.50
|
||||
|
||||
# homeassistant.components.anel_pwrctrl
|
||||
anel_pwrctrl-homeassistant==0.0.1.dev2
|
||||
@@ -339,7 +339,7 @@ beautifulsoup4==4.9.1
|
||||
# beewi_smartclim==0.0.7
|
||||
|
||||
# homeassistant.components.zha
|
||||
bellows==0.20.1
|
||||
bellows==0.20.2
|
||||
|
||||
# homeassistant.components.bmw_connected_drive
|
||||
bimmer_connected==0.7.7
|
||||
@@ -669,7 +669,7 @@ glances_api==0.2.0
|
||||
gntp==1.0.3
|
||||
|
||||
# homeassistant.components.gogogate2
|
||||
gogogate2-api==2.0.1
|
||||
gogogate2-api==2.0.2
|
||||
|
||||
# homeassistant.components.google
|
||||
google-api-python-client==1.6.4
|
||||
@@ -747,7 +747,7 @@ hole==0.5.1
|
||||
holidays==0.10.3
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20200909.0
|
||||
home-assistant-frontend==20200915.0
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
@@ -1295,7 +1295,7 @@ pydaikin==2.3.1
|
||||
pydanfossair==0.1.0
|
||||
|
||||
# homeassistant.components.deconz
|
||||
pydeconz==72
|
||||
pydeconz==73
|
||||
|
||||
# homeassistant.components.delijn
|
||||
pydelijn==0.6.1
|
||||
@@ -1377,7 +1377,7 @@ pygtfs==0.1.5
|
||||
pygti==0.6.0
|
||||
|
||||
# homeassistant.components.version
|
||||
pyhaversion==3.3.0
|
||||
pyhaversion==3.4.0
|
||||
|
||||
# homeassistant.components.heos
|
||||
pyheos==0.6.0
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
asynctest==0.13.0
|
||||
codecov==2.1.0
|
||||
coverage==5.2.1
|
||||
jsonpickle==1.4.1
|
||||
mock-open==1.4.0
|
||||
mypy==0.780
|
||||
pre-commit==2.7.1
|
||||
|
||||
@@ -103,7 +103,7 @@ aioguardian==1.0.1
|
||||
aioharmony==0.2.6
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
aiohomekit[IP]==0.2.49
|
||||
aiohomekit==0.2.53
|
||||
|
||||
# homeassistant.components.emulated_hue
|
||||
# homeassistant.components.http
|
||||
@@ -149,7 +149,7 @@ airly==0.0.2
|
||||
ambiclimate==0.2.1
|
||||
|
||||
# homeassistant.components.androidtv
|
||||
androidtv[async]==0.0.49
|
||||
androidtv[async]==0.0.50
|
||||
|
||||
# homeassistant.components.apns
|
||||
apns2==0.3.0
|
||||
@@ -183,7 +183,7 @@ azure-eventhub==5.1.0
|
||||
base36==0.1.1
|
||||
|
||||
# homeassistant.components.zha
|
||||
bellows==0.20.1
|
||||
bellows==0.20.2
|
||||
|
||||
# homeassistant.components.blebox
|
||||
blebox_uniapi==1.3.2
|
||||
@@ -334,7 +334,7 @@ gios==0.1.4
|
||||
glances_api==0.2.0
|
||||
|
||||
# homeassistant.components.gogogate2
|
||||
gogogate2-api==2.0.1
|
||||
gogogate2-api==2.0.2
|
||||
|
||||
# homeassistant.components.google
|
||||
google-api-python-client==1.6.4
|
||||
@@ -370,7 +370,7 @@ hole==0.5.1
|
||||
holidays==0.10.3
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20200909.0
|
||||
home-assistant-frontend==20200915.0
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.10
|
||||
@@ -625,7 +625,7 @@ pycountry==19.8.18
|
||||
pydaikin==2.3.1
|
||||
|
||||
# homeassistant.components.deconz
|
||||
pydeconz==72
|
||||
pydeconz==73
|
||||
|
||||
# homeassistant.components.dexcom
|
||||
pydexcom==0.2.0
|
||||
@@ -662,7 +662,7 @@ pygatt[GATTTOOL]==4.0.5
|
||||
pygti==0.6.0
|
||||
|
||||
# homeassistant.components.version
|
||||
pyhaversion==3.3.0
|
||||
pyhaversion==3.4.0
|
||||
|
||||
# homeassistant.components.heos
|
||||
pyheos==0.6.0
|
||||
|
||||
@@ -67,6 +67,15 @@ LIGHTS = {
|
||||
"type": "On and Off light",
|
||||
"uniqueid": "00:00:00:00:00:00:00:03-00",
|
||||
},
|
||||
"5": {
|
||||
"ctmax": 1000,
|
||||
"ctmin": 0,
|
||||
"id": "Tunable white light with bad maxmin values id",
|
||||
"name": "Tunable white light with bad maxmin values",
|
||||
"state": {"on": True, "colormode": "ct", "ct": 2500, "reachable": True},
|
||||
"type": "Tunable white light",
|
||||
"uniqueid": "00:00:00:00:00:00:00:04-00",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +110,7 @@ async def test_lights_and_groups(hass):
|
||||
assert "light.on_off_switch" not in gateway.deconz_ids
|
||||
assert "light.on_off_light" in gateway.deconz_ids
|
||||
|
||||
assert len(hass.states.async_all()) == 5
|
||||
assert len(hass.states.async_all()) == 6
|
||||
|
||||
rgb_light = hass.states.get("light.rgb_light")
|
||||
assert rgb_light.state == "on"
|
||||
@@ -117,6 +126,15 @@ async def test_lights_and_groups(hass):
|
||||
assert tunable_white_light.attributes["min_mireds"] == 155
|
||||
assert tunable_white_light.attributes["supported_features"] == 2
|
||||
|
||||
tunable_white_light_bad_maxmin = hass.states.get(
|
||||
"light.tunable_white_light_with_bad_maxmin_values"
|
||||
)
|
||||
assert tunable_white_light_bad_maxmin.state == "on"
|
||||
assert tunable_white_light_bad_maxmin.attributes["color_temp"] == 2500
|
||||
assert tunable_white_light_bad_maxmin.attributes["max_mireds"] == 650
|
||||
assert tunable_white_light_bad_maxmin.attributes["min_mireds"] == 140
|
||||
assert tunable_white_light_bad_maxmin.attributes["supported_features"] == 2
|
||||
|
||||
on_off_light = hass.states.get("light.on_off_light")
|
||||
assert on_off_light.state == "on"
|
||||
assert on_off_light.attributes["supported_features"] == 0
|
||||
@@ -256,7 +274,7 @@ async def test_disable_light_groups(hass):
|
||||
assert "light.empty_group" not in gateway.deconz_ids
|
||||
assert "light.on_off_switch" not in gateway.deconz_ids
|
||||
# 3 entities
|
||||
assert len(hass.states.async_all()) == 4
|
||||
assert len(hass.states.async_all()) == 5
|
||||
|
||||
rgb_light = hass.states.get("light.rgb_light")
|
||||
assert rgb_light is not None
|
||||
@@ -281,7 +299,7 @@ async def test_disable_light_groups(hass):
|
||||
assert "light.empty_group" not in gateway.deconz_ids
|
||||
assert "light.on_off_switch" not in gateway.deconz_ids
|
||||
# 3 entities
|
||||
assert len(hass.states.async_all()) == 5
|
||||
assert len(hass.states.async_all()) == 6
|
||||
|
||||
hass.config_entries.async_update_entry(
|
||||
gateway.config_entry, options={deconz.gateway.CONF_ALLOW_DECONZ_GROUPS: False}
|
||||
@@ -294,4 +312,4 @@ async def test_disable_light_groups(hass):
|
||||
assert "light.empty_group" not in gateway.deconz_ids
|
||||
assert "light.on_off_switch" not in gateway.deconz_ids
|
||||
# 3 entities
|
||||
assert len(hass.states.async_all()) == 4
|
||||
assert len(hass.states.async_all()) == 5
|
||||
|
||||
@@ -110,6 +110,7 @@ async def test_import(
|
||||
camera=False,
|
||||
events=2,
|
||||
temperature=None,
|
||||
voltage=40,
|
||||
),
|
||||
door2=GogoGate2Door(
|
||||
door_id=2,
|
||||
@@ -123,6 +124,7 @@ async def test_import(
|
||||
camera=False,
|
||||
events=0,
|
||||
temperature=None,
|
||||
voltage=40,
|
||||
),
|
||||
door3=GogoGate2Door(
|
||||
door_id=3,
|
||||
@@ -136,6 +138,7 @@ async def test_import(
|
||||
camera=False,
|
||||
events=0,
|
||||
temperature=None,
|
||||
voltage=40,
|
||||
),
|
||||
outputs=Outputs(output1=True, output2=False, output3=True),
|
||||
network=Network(ip=""),
|
||||
@@ -170,6 +173,7 @@ async def test_import(
|
||||
enabled=True,
|
||||
apicode="apicode0",
|
||||
customimage=False,
|
||||
voltage=40,
|
||||
),
|
||||
door2=ISmartGateDoor(
|
||||
door_id=1,
|
||||
@@ -186,6 +190,7 @@ async def test_import(
|
||||
enabled=True,
|
||||
apicode="apicode0",
|
||||
customimage=False,
|
||||
voltage=40,
|
||||
),
|
||||
door3=ISmartGateDoor(
|
||||
door_id=1,
|
||||
@@ -202,6 +207,7 @@ async def test_import(
|
||||
enabled=True,
|
||||
apicode="apicode0",
|
||||
customimage=False,
|
||||
voltage=40,
|
||||
),
|
||||
network=Network(ip=""),
|
||||
wifi=Wifi(SSID="", linkquality="", signal=""),
|
||||
@@ -268,6 +274,7 @@ async def test_open_close_update(gogogat2api_mock, hass: HomeAssistant) -> None:
|
||||
camera=False,
|
||||
events=2,
|
||||
temperature=None,
|
||||
voltage=40,
|
||||
),
|
||||
door2=GogoGate2Door(
|
||||
door_id=2,
|
||||
@@ -281,6 +288,7 @@ async def test_open_close_update(gogogat2api_mock, hass: HomeAssistant) -> None:
|
||||
camera=False,
|
||||
events=0,
|
||||
temperature=None,
|
||||
voltage=40,
|
||||
),
|
||||
door3=GogoGate2Door(
|
||||
door_id=3,
|
||||
@@ -294,6 +302,7 @@ async def test_open_close_update(gogogat2api_mock, hass: HomeAssistant) -> None:
|
||||
camera=False,
|
||||
events=0,
|
||||
temperature=None,
|
||||
voltage=40,
|
||||
),
|
||||
outputs=Outputs(output1=True, output2=False, output3=True),
|
||||
network=Network(ip=""),
|
||||
@@ -381,6 +390,7 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None:
|
||||
enabled=True,
|
||||
apicode="apicode0",
|
||||
customimage=False,
|
||||
voltage=40,
|
||||
),
|
||||
door2=ISmartGateDoor(
|
||||
door_id=2,
|
||||
@@ -397,6 +407,7 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None:
|
||||
enabled=True,
|
||||
apicode="apicode0",
|
||||
customimage=False,
|
||||
voltage=40,
|
||||
),
|
||||
door3=ISmartGateDoor(
|
||||
door_id=3,
|
||||
@@ -413,6 +424,7 @@ async def test_availability(ismartgateapi_mock, hass: HomeAssistant) -> None:
|
||||
enabled=True,
|
||||
apicode="apicode0",
|
||||
customimage=False,
|
||||
voltage=40,
|
||||
),
|
||||
network=Network(ip=""),
|
||||
wifi=Wifi(SSID="", linkquality="", signal=""),
|
||||
|
||||
@@ -293,9 +293,25 @@ async def test_light_color_temperature_and_rgb_color(hass, hk_driver, cls, event
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 2, None)
|
||||
assert acc.char_hue.value == 260
|
||||
assert acc.char_saturation.value == 90
|
||||
|
||||
assert not hasattr(acc, "char_color_temperature")
|
||||
|
||||
hass.states.async_set(entity_id, STATE_ON, {ATTR_COLOR_TEMP: 224})
|
||||
await hass.async_block_till_done()
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_hue.value == 27
|
||||
assert acc.char_saturation.value == 27
|
||||
|
||||
hass.states.async_set(entity_id, STATE_ON, {ATTR_COLOR_TEMP: 352})
|
||||
await hass.async_block_till_done()
|
||||
await acc.run_handler()
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_hue.value == 28
|
||||
assert acc.char_saturation.value == 61
|
||||
|
||||
|
||||
async def test_light_rgb_color(hass, hk_driver, cls, events):
|
||||
"""Test light with rgb_color."""
|
||||
|
||||
@@ -8,10 +8,11 @@ from aiohomekit.model.services import ServicesTypes
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.homekit_controller import config_flow
|
||||
from homeassistant.helpers import device_registry
|
||||
|
||||
import tests.async_mock
|
||||
from tests.async_mock import patch
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, mock_device_registry
|
||||
|
||||
PAIRING_START_FORM_ERRORS = [
|
||||
(KeyError, "pairing_failed"),
|
||||
@@ -233,11 +234,45 @@ async def test_pair_already_paired_1(hass, controller):
|
||||
assert result["reason"] == "already_paired"
|
||||
|
||||
|
||||
async def test_id_missing(hass, controller):
|
||||
"""Test id is missing."""
|
||||
device = setup_mock_accessory(controller)
|
||||
discovery_info = get_device_discovery_info(device)
|
||||
|
||||
# Remove id from device
|
||||
del discovery_info["properties"]["id"]
|
||||
|
||||
# Device is discovered
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
"homekit_controller", context={"source": "zeroconf"}, data=discovery_info
|
||||
)
|
||||
assert result["type"] == "abort"
|
||||
assert result["reason"] == "invalid_properties"
|
||||
|
||||
|
||||
async def test_discovery_ignored_model(hass, controller):
|
||||
"""Already paired."""
|
||||
device = setup_mock_accessory(controller)
|
||||
discovery_info = get_device_discovery_info(device)
|
||||
discovery_info["properties"]["md"] = config_flow.HOMEKIT_IGNORE[0]
|
||||
|
||||
config_entry = MockConfigEntry(domain=config_flow.HOMEKIT_BRIDGE_DOMAIN, data={})
|
||||
formatted_mac = device_registry.format_mac("AA:BB:CC:DD:EE:FF")
|
||||
|
||||
dev_reg = mock_device_registry(hass)
|
||||
dev_reg.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
identifiers={
|
||||
(
|
||||
config_flow.HOMEKIT_BRIDGE_DOMAIN,
|
||||
config_entry.entry_id,
|
||||
config_flow.HOMEKIT_BRIDGE_SERIAL_NUMBER,
|
||||
)
|
||||
},
|
||||
connections={(device_registry.CONNECTION_NETWORK_MAC, formatted_mac)},
|
||||
model=config_flow.HOMEKIT_BRIDGE_MODEL,
|
||||
)
|
||||
|
||||
discovery_info["properties"]["id"] = "AA:BB:CC:DD:EE:FF"
|
||||
|
||||
# Device is discovered
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
||||
@@ -18,6 +18,7 @@ async def test_async_browse_media(hass):
|
||||
"12:34:56:78:90:ab": {
|
||||
1599152672: {
|
||||
"id": "12345",
|
||||
"type": "person",
|
||||
"time": 1599152672,
|
||||
"camera_id": "12:34:56:78:90:ab",
|
||||
"snapshot": {
|
||||
@@ -30,6 +31,7 @@ async def test_async_browse_media(hass):
|
||||
},
|
||||
1599152673: {
|
||||
"id": "12346",
|
||||
"type": "person",
|
||||
"time": 1599152673,
|
||||
"camera_id": "12:34:56:78:90:ab",
|
||||
"snapshot": {
|
||||
@@ -37,9 +39,47 @@ async def test_async_browse_media(hass):
|
||||
},
|
||||
"message": "<b>Tobias</b> seen",
|
||||
},
|
||||
1599152674: {
|
||||
"id": "12347",
|
||||
"type": "outdoor",
|
||||
"time": 1599152674,
|
||||
"camera_id": "12:34:56:78:90:ac",
|
||||
"snapshot": {
|
||||
"url": "https://netatmocameraimage",
|
||||
},
|
||||
"video_id": "98766",
|
||||
"video_status": "available",
|
||||
"event_list": [
|
||||
{
|
||||
"type": "vehicle",
|
||||
"time": 1599152674,
|
||||
"id": "12347-0",
|
||||
"offset": 0,
|
||||
"message": "Vehicle detected",
|
||||
"snapshot": {
|
||||
"url": "https://netatmocameraimage",
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "human",
|
||||
"time": 1599152674,
|
||||
"id": "12347-1",
|
||||
"offset": 8,
|
||||
"message": "Person detected",
|
||||
"snapshot": {
|
||||
"url": "https://netatmocameraimage",
|
||||
},
|
||||
},
|
||||
],
|
||||
"media_url": "http:///files/high/index.m3u8",
|
||||
},
|
||||
}
|
||||
}
|
||||
hass.data[DOMAIN][DATA_CAMERAS] = {"12:34:56:78:90:ab": "MyCamera"}
|
||||
|
||||
hass.data[DOMAIN][DATA_CAMERAS] = {
|
||||
"12:34:56:78:90:ab": "MyCamera",
|
||||
"12:34:56:78:90:ac": "MyOutdoorCamera",
|
||||
}
|
||||
|
||||
assert await async_setup_component(hass, const.DOMAIN, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Common initialisation for the Plugwise integration."""
|
||||
|
||||
from homeassistant.components.plugwise import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
||||
async def async_init_integration(
|
||||
hass: HomeAssistant,
|
||||
aioclient_mock: AiohttpClientMocker,
|
||||
skip_setup: bool = False,
|
||||
):
|
||||
"""Initialize the Smile integration."""
|
||||
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN, data={"host": "1.1.1.1", "password": "test-password"}
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
if not skip_setup:
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return entry
|
||||
@@ -0,0 +1,170 @@
|
||||
"""Setup mocks for the Plugwise integration tests."""
|
||||
|
||||
from functools import partial
|
||||
import re
|
||||
|
||||
from Plugwise_Smile.Smile import Smile
|
||||
import jsonpickle
|
||||
import pytest
|
||||
|
||||
from tests.async_mock import AsyncMock, patch
|
||||
from tests.common import load_fixture
|
||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
|
||||
|
||||
def _read_json(environment, call):
|
||||
"""Undecode the json data."""
|
||||
fixture = load_fixture(f"plugwise/{environment}/{call}.json")
|
||||
return jsonpickle.decode(fixture)
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_smile")
|
||||
def mock_smile():
|
||||
"""Create a Mock Smile for testing exceptions."""
|
||||
with patch(
|
||||
"homeassistant.components.plugwise.config_flow.Smile",
|
||||
) as smile_mock:
|
||||
smile_mock.InvalidAuthentication = Smile.InvalidAuthentication
|
||||
smile_mock.ConnectionFailedError = Smile.ConnectionFailedError
|
||||
smile_mock.return_value.connect.return_value = True
|
||||
yield smile_mock.return_value
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_smile_unauth")
|
||||
def mock_smile_unauth(aioclient_mock: AiohttpClientMocker) -> None:
|
||||
"""Mock the Plugwise Smile unauthorized for Home Assistant."""
|
||||
aioclient_mock.get(re.compile(".*"), status=401)
|
||||
aioclient_mock.put(re.compile(".*"), status=401)
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_smile_error")
|
||||
def mock_smile_error(aioclient_mock: AiohttpClientMocker) -> None:
|
||||
"""Mock the Plugwise Smile server failure for Home Assistant."""
|
||||
aioclient_mock.get(re.compile(".*"), status=500)
|
||||
aioclient_mock.put(re.compile(".*"), status=500)
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_smile_notconnect")
|
||||
def mock_smile_notconnect():
|
||||
"""Mock the Plugwise Smile general connection failure for Home Assistant."""
|
||||
with patch("homeassistant.components.plugwise.Smile") as smile_mock:
|
||||
smile_mock.InvalidAuthentication = Smile.InvalidAuthentication
|
||||
smile_mock.ConnectionFailedError = Smile.ConnectionFailedError
|
||||
smile_mock.PlugwiseError = Smile.PlugwiseError
|
||||
smile_mock.return_value.connect.side_effect = AsyncMock(return_value=False)
|
||||
yield smile_mock.return_value
|
||||
|
||||
|
||||
def _get_device_data(chosen_env, device_id):
|
||||
"""Mock return data for specific devices."""
|
||||
return _read_json(chosen_env, "get_device_data/" + device_id)
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_smile_adam")
|
||||
def mock_smile_adam():
|
||||
"""Create a Mock Adam environment for testing exceptions."""
|
||||
chosen_env = "adam_multiple_devices_per_zone"
|
||||
with patch("homeassistant.components.plugwise.Smile") as smile_mock:
|
||||
smile_mock.InvalidAuthentication = Smile.InvalidAuthentication
|
||||
smile_mock.ConnectionFailedError = Smile.ConnectionFailedError
|
||||
smile_mock.XMLDataMissingError = Smile.XMLDataMissingError
|
||||
|
||||
smile_mock.return_value.gateway_id = "fe799307f1624099878210aa0b9f1475"
|
||||
smile_mock.return_value.heater_id = "90986d591dcd426cae3ec3e8111ff730"
|
||||
smile_mock.return_value.smile_version = "3.0.15"
|
||||
smile_mock.return_value.smile_type = "thermostat"
|
||||
smile_mock.return_value.smile_hostname = "smile98765"
|
||||
|
||||
smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True)
|
||||
smile_mock.return_value.full_update_device.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
smile_mock.return_value.set_schedule_state.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
smile_mock.return_value.set_preset.side_effect = AsyncMock(return_value=True)
|
||||
smile_mock.return_value.set_temperature.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
smile_mock.return_value.set_relay_state.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
|
||||
smile_mock.return_value.get_all_devices.return_value = _read_json(
|
||||
chosen_env, "get_all_devices"
|
||||
)
|
||||
smile_mock.return_value.get_device_data.side_effect = partial(
|
||||
_get_device_data, chosen_env
|
||||
)
|
||||
|
||||
yield smile_mock.return_value
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_smile_anna")
|
||||
def mock_smile_anna():
|
||||
"""Create a Mock Anna environment for testing exceptions."""
|
||||
chosen_env = "anna_heatpump"
|
||||
with patch("homeassistant.components.plugwise.Smile") as smile_mock:
|
||||
smile_mock.InvalidAuthentication = Smile.InvalidAuthentication
|
||||
smile_mock.ConnectionFailedError = Smile.ConnectionFailedError
|
||||
smile_mock.XMLDataMissingError = Smile.XMLDataMissingError
|
||||
|
||||
smile_mock.return_value.gateway_id = "015ae9ea3f964e668e490fa39da3870b"
|
||||
smile_mock.return_value.heater_id = "1cbf783bb11e4a7c8a6843dee3a86927"
|
||||
smile_mock.return_value.smile_version = "4.0.15"
|
||||
smile_mock.return_value.smile_type = "thermostat"
|
||||
smile_mock.return_value.smile_hostname = "smile98765"
|
||||
|
||||
smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True)
|
||||
smile_mock.return_value.full_update_device.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
smile_mock.return_value.set_schedule_state.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
smile_mock.return_value.set_preset.side_effect = AsyncMock(return_value=True)
|
||||
smile_mock.return_value.set_temperature.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
smile_mock.return_value.set_relay_state.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
|
||||
smile_mock.return_value.get_all_devices.return_value = _read_json(
|
||||
chosen_env, "get_all_devices"
|
||||
)
|
||||
smile_mock.return_value.get_device_data.side_effect = partial(
|
||||
_get_device_data, chosen_env
|
||||
)
|
||||
|
||||
yield smile_mock.return_value
|
||||
|
||||
|
||||
@pytest.fixture(name="mock_smile_p1")
|
||||
def mock_smile_p1():
|
||||
"""Create a Mock P1 DSMR environment for testing exceptions."""
|
||||
chosen_env = "p1v3_full_option"
|
||||
with patch("homeassistant.components.plugwise.Smile") as smile_mock:
|
||||
smile_mock.InvalidAuthentication = Smile.InvalidAuthentication
|
||||
smile_mock.ConnectionFailedError = Smile.ConnectionFailedError
|
||||
smile_mock.XMLDataMissingError = Smile.XMLDataMissingError
|
||||
|
||||
smile_mock.return_value.gateway_id = "e950c7d5e1ee407a858e2a8b5016c8b3"
|
||||
smile_mock.return_value.heater_id = None
|
||||
smile_mock.return_value.smile_version = "3.3.9"
|
||||
smile_mock.return_value.smile_type = "power"
|
||||
smile_mock.return_value.smile_hostname = "smile98765"
|
||||
|
||||
smile_mock.return_value.connect.side_effect = AsyncMock(return_value=True)
|
||||
smile_mock.return_value.full_update_device.side_effect = AsyncMock(
|
||||
return_value=True
|
||||
)
|
||||
|
||||
smile_mock.return_value.get_all_devices.return_value = _read_json(
|
||||
chosen_env, "get_all_devices"
|
||||
)
|
||||
smile_mock.return_value.get_device_data.side_effect = partial(
|
||||
_get_device_data, chosen_env
|
||||
)
|
||||
|
||||
yield smile_mock.return_value
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Tests for the Plugwise binary_sensor integration."""
|
||||
|
||||
from homeassistant.config_entries import ENTRY_STATE_LOADED
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
|
||||
from tests.components.plugwise.common import async_init_integration
|
||||
|
||||
|
||||
async def test_anna_climate_binary_sensor_entities(hass, mock_smile_anna):
|
||||
"""Test creation of climate related binary_sensor entities."""
|
||||
entry = await async_init_integration(hass, mock_smile_anna)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
state = hass.states.get("binary_sensor.auxiliary_slave_boiler_state")
|
||||
assert str(state.state) == STATE_OFF
|
||||
|
||||
state = hass.states.get("binary_sensor.auxiliary_dhw_state")
|
||||
assert str(state.state) == STATE_OFF
|
||||
|
||||
|
||||
async def test_anna_climate_binary_sensor_change(hass, mock_smile_anna):
|
||||
"""Test change of climate related binary_sensor entities."""
|
||||
entry = await async_init_integration(hass, mock_smile_anna)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
hass.states.async_set("binary_sensor.auxiliary_dhw_state", STATE_ON, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.auxiliary_dhw_state")
|
||||
assert str(state.state) == STATE_ON
|
||||
|
||||
await hass.helpers.entity_component.async_update_entity(
|
||||
"binary_sensor.auxiliary_dhw_state"
|
||||
)
|
||||
|
||||
state = hass.states.get("binary_sensor.auxiliary_dhw_state")
|
||||
assert str(state.state) == STATE_OFF
|
||||
@@ -0,0 +1,161 @@
|
||||
"""Tests for the Plugwise Climate integration."""
|
||||
|
||||
from homeassistant.config_entries import ENTRY_STATE_LOADED
|
||||
|
||||
from tests.components.plugwise.common import async_init_integration
|
||||
|
||||
|
||||
async def test_adam_climate_entity_attributes(hass, mock_smile_adam):
|
||||
"""Test creation of adam climate device environment."""
|
||||
entry = await async_init_integration(hass, mock_smile_adam)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
state = hass.states.get("climate.zone_lisa_wk")
|
||||
attrs = state.attributes
|
||||
|
||||
assert attrs["hvac_modes"] is None
|
||||
|
||||
assert "preset_modes" in attrs
|
||||
assert "no_frost" in attrs["preset_modes"]
|
||||
assert "home" in attrs["preset_modes"]
|
||||
|
||||
assert attrs["current_temperature"] == 20.9
|
||||
assert attrs["temperature"] == 21.5
|
||||
|
||||
assert attrs["preset_mode"] == "home"
|
||||
|
||||
assert attrs["supported_features"] == 17
|
||||
|
||||
state = hass.states.get("climate.zone_thermostat_jessie")
|
||||
attrs = state.attributes
|
||||
|
||||
assert attrs["hvac_modes"] is None
|
||||
|
||||
assert "preset_modes" in attrs
|
||||
assert "no_frost" in attrs["preset_modes"]
|
||||
assert "home" in attrs["preset_modes"]
|
||||
|
||||
assert attrs["current_temperature"] == 17.2
|
||||
assert attrs["temperature"] == 15.0
|
||||
|
||||
assert attrs["preset_mode"] == "asleep"
|
||||
|
||||
|
||||
async def test_adam_climate_entity_climate_changes(hass, mock_smile_adam):
|
||||
"""Test handling of user requests in adam climate device environment."""
|
||||
entry = await async_init_integration(hass, mock_smile_adam)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
await hass.services.async_call(
|
||||
"climate",
|
||||
"set_temperature",
|
||||
{"entity_id": "climate.zone_lisa_wk", "temperature": 25},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get("climate.zone_lisa_wk")
|
||||
attrs = state.attributes
|
||||
|
||||
assert attrs["temperature"] == 25.0
|
||||
|
||||
await hass.services.async_call(
|
||||
"climate",
|
||||
"set_preset_mode",
|
||||
{"entity_id": "climate.zone_lisa_wk", "preset_mode": "away"},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get("climate.zone_lisa_wk")
|
||||
attrs = state.attributes
|
||||
|
||||
assert attrs["preset_mode"] == "away"
|
||||
|
||||
assert attrs["supported_features"] == 17
|
||||
|
||||
await hass.services.async_call(
|
||||
"climate",
|
||||
"set_temperature",
|
||||
{"entity_id": "climate.zone_thermostat_jessie", "temperature": 25},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get("climate.zone_thermostat_jessie")
|
||||
attrs = state.attributes
|
||||
|
||||
assert attrs["temperature"] == 25.0
|
||||
|
||||
await hass.services.async_call(
|
||||
"climate",
|
||||
"set_preset_mode",
|
||||
{"entity_id": "climate.zone_thermostat_jessie", "preset_mode": "home"},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get("climate.zone_thermostat_jessie")
|
||||
attrs = state.attributes
|
||||
|
||||
assert attrs["preset_mode"] == "home"
|
||||
|
||||
|
||||
async def test_anna_climate_entity_attributes(hass, mock_smile_anna):
|
||||
"""Test creation of anna climate device environment."""
|
||||
entry = await async_init_integration(hass, mock_smile_anna)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
state = hass.states.get("climate.anna")
|
||||
attrs = state.attributes
|
||||
|
||||
assert "hvac_modes" in attrs
|
||||
assert "heat_cool" in attrs["hvac_modes"]
|
||||
|
||||
assert "preset_modes" in attrs
|
||||
assert "no_frost" in attrs["preset_modes"]
|
||||
assert "home" in attrs["preset_modes"]
|
||||
|
||||
assert attrs["current_temperature"] == 23.3
|
||||
assert attrs["temperature"] == 21.0
|
||||
|
||||
assert state.state == "auto"
|
||||
assert attrs["hvac_action"] == "idle"
|
||||
assert attrs["preset_mode"] == "home"
|
||||
|
||||
assert attrs["supported_features"] == 17
|
||||
|
||||
|
||||
async def test_anna_climate_entity_climate_changes(hass, mock_smile_anna):
|
||||
"""Test handling of user requests in anna climate device environment."""
|
||||
entry = await async_init_integration(hass, mock_smile_anna)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
await hass.services.async_call(
|
||||
"climate",
|
||||
"set_temperature",
|
||||
{"entity_id": "climate.anna", "temperature": 25},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get("climate.anna")
|
||||
attrs = state.attributes
|
||||
|
||||
assert attrs["temperature"] == 25.0
|
||||
|
||||
await hass.services.async_call(
|
||||
"climate",
|
||||
"set_preset_mode",
|
||||
{"entity_id": "climate.anna", "preset_mode": "away"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get("climate.anna")
|
||||
attrs = state.attributes
|
||||
|
||||
assert attrs["preset_mode"] == "away"
|
||||
|
||||
await hass.services.async_call(
|
||||
"climate",
|
||||
"set_hvac_mode",
|
||||
{"entity_id": "climate.anna", "hvac_mode": "heat_cool"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
state = hass.states.get("climate.anna")
|
||||
attrs = state.attributes
|
||||
|
||||
assert state.state == "heat_cool"
|
||||
@@ -62,14 +62,14 @@ async def test_form(hass):
|
||||
{"host": TEST_HOST, "password": TEST_PASSWORD},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result2["data"] == {
|
||||
"host": TEST_HOST,
|
||||
"password": TEST_PASSWORD,
|
||||
}
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["errors"] == {}
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
@@ -108,10 +108,37 @@ async def test_zeroconf_form(hass):
|
||||
"password": TEST_PASSWORD,
|
||||
}
|
||||
|
||||
assert result["errors"] == {}
|
||||
assert len(mock_setup.mock_calls) == 1
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
result3 = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={"source": SOURCE_ZEROCONF},
|
||||
data=TEST_DISCOVERY,
|
||||
)
|
||||
assert result3["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||
assert result3["errors"] == {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.plugwise.config_flow.Smile.connect",
|
||||
return_value=True,
|
||||
), patch(
|
||||
"homeassistant.components.plugwise.async_setup",
|
||||
return_value=True,
|
||||
) as mock_setup, patch(
|
||||
"homeassistant.components.plugwise.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result4 = await hass.config_entries.flow.async_configure(
|
||||
result3["flow_id"],
|
||||
{"password": TEST_PASSWORD},
|
||||
)
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result4["type"] == "abort"
|
||||
assert result4["reason"] == "already_configured"
|
||||
|
||||
|
||||
async def test_form_invalid_auth(hass, mock_smile):
|
||||
"""Test we handle invalid auth."""
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Tests for the Plugwise Climate integration."""
|
||||
|
||||
import asyncio
|
||||
|
||||
from Plugwise_Smile.Smile import Smile
|
||||
|
||||
from homeassistant.config_entries import (
|
||||
ENTRY_STATE_SETUP_ERROR,
|
||||
ENTRY_STATE_SETUP_RETRY,
|
||||
)
|
||||
|
||||
from tests.components.plugwise.common import async_init_integration
|
||||
|
||||
|
||||
async def test_smile_unauthorized(hass, mock_smile_unauth):
|
||||
"""Test failing unauthorization by Smile."""
|
||||
entry = await async_init_integration(hass, mock_smile_unauth)
|
||||
assert entry.state == ENTRY_STATE_SETUP_ERROR
|
||||
|
||||
|
||||
async def test_smile_error(hass, mock_smile_error):
|
||||
"""Test server error handling by Smile."""
|
||||
entry = await async_init_integration(hass, mock_smile_error)
|
||||
assert entry.state == ENTRY_STATE_SETUP_RETRY
|
||||
|
||||
|
||||
async def test_smile_notconnect(hass, mock_smile_notconnect):
|
||||
"""Connection failure error handling by Smile."""
|
||||
mock_smile_notconnect.connect.return_value = False
|
||||
entry = await async_init_integration(hass, mock_smile_notconnect)
|
||||
assert entry.state == ENTRY_STATE_SETUP_RETRY
|
||||
|
||||
|
||||
async def test_smile_timeout(hass, mock_smile_notconnect):
|
||||
"""Timeout error handling by Smile."""
|
||||
mock_smile_notconnect.connect.side_effect = asyncio.TimeoutError
|
||||
entry = await async_init_integration(hass, mock_smile_notconnect)
|
||||
assert entry.state == ENTRY_STATE_SETUP_RETRY
|
||||
|
||||
|
||||
async def test_smile_adam_xmlerror(hass, mock_smile_adam):
|
||||
"""Detect malformed XML by Smile in Adam environment."""
|
||||
mock_smile_adam.full_update_device.side_effect = Smile.XMLDataMissingError
|
||||
entry = await async_init_integration(hass, mock_smile_adam)
|
||||
assert entry.state == ENTRY_STATE_SETUP_RETRY
|
||||
@@ -0,0 +1,66 @@
|
||||
"""Tests for the Plugwise Sensor integration."""
|
||||
|
||||
from homeassistant.config_entries import ENTRY_STATE_LOADED
|
||||
|
||||
from tests.components.plugwise.common import async_init_integration
|
||||
|
||||
|
||||
async def test_adam_climate_sensor_entities(hass, mock_smile_adam):
|
||||
"""Test creation of climate related sensor entities."""
|
||||
entry = await async_init_integration(hass, mock_smile_adam)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
state = hass.states.get("sensor.adam_outdoor_temperature")
|
||||
assert float(state.state) == 7.81
|
||||
|
||||
state = hass.states.get("sensor.cv_pomp_electricity_consumed")
|
||||
assert float(state.state) == 35.6
|
||||
|
||||
state = hass.states.get("sensor.auxiliary_water_temperature")
|
||||
assert float(state.state) == 70.0
|
||||
|
||||
state = hass.states.get("sensor.cv_pomp_electricity_consumed_interval")
|
||||
assert float(state.state) == 7.37
|
||||
|
||||
await hass.helpers.entity_component.async_update_entity(
|
||||
"sensor.zone_lisa_wk_battery"
|
||||
)
|
||||
|
||||
state = hass.states.get("sensor.zone_lisa_wk_battery")
|
||||
assert float(state.state) == 34
|
||||
|
||||
|
||||
async def test_anna_climate_sensor_entities(hass, mock_smile_anna):
|
||||
"""Test creation of climate related sensor entities."""
|
||||
entry = await async_init_integration(hass, mock_smile_anna)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
state = hass.states.get("sensor.auxiliary_outdoor_temperature")
|
||||
assert float(state.state) == 18.0
|
||||
|
||||
state = hass.states.get("sensor.auxiliary_water_temperature")
|
||||
assert float(state.state) == 29.1
|
||||
|
||||
state = hass.states.get("sensor.anna_illuminance")
|
||||
assert float(state.state) == 86.0
|
||||
|
||||
|
||||
async def test_p1_dsmr_sensor_entities(hass, mock_smile_p1):
|
||||
"""Test creation of power related sensor entities."""
|
||||
entry = await async_init_integration(hass, mock_smile_p1)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
state = hass.states.get("sensor.p1_net_electricity_point")
|
||||
assert float(state.state) == -2761.0
|
||||
|
||||
state = hass.states.get("sensor.p1_electricity_consumed_off_peak_cumulative")
|
||||
assert int(state.state) == 551
|
||||
|
||||
state = hass.states.get("sensor.p1_electricity_produced_peak_point")
|
||||
assert float(state.state) == 2761.0
|
||||
|
||||
state = hass.states.get("sensor.p1_electricity_consumed_peak_cumulative")
|
||||
assert int(state.state) == 442
|
||||
|
||||
state = hass.states.get("sensor.p1_gas_consumed_cumulative")
|
||||
assert float(state.state) == 584.9
|
||||
@@ -0,0 +1,50 @@
|
||||
"""Tests for the Plugwise switch integration."""
|
||||
|
||||
from homeassistant.config_entries import ENTRY_STATE_LOADED
|
||||
|
||||
from tests.components.plugwise.common import async_init_integration
|
||||
|
||||
|
||||
async def test_adam_climate_switch_entities(hass, mock_smile_adam):
|
||||
"""Test creation of climate related switch entities."""
|
||||
entry = await async_init_integration(hass, mock_smile_adam)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
state = hass.states.get("switch.cv_pomp")
|
||||
assert str(state.state) == "on"
|
||||
|
||||
state = hass.states.get("switch.fibaro_hc2")
|
||||
assert str(state.state) == "on"
|
||||
|
||||
|
||||
async def test_adam_climate_switch_changes(hass, mock_smile_adam):
|
||||
"""Test changing of climate related switch entities."""
|
||||
entry = await async_init_integration(hass, mock_smile_adam)
|
||||
assert entry.state == ENTRY_STATE_LOADED
|
||||
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"turn_off",
|
||||
{"entity_id": "switch.cv_pomp"},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get("switch.cv_pomp")
|
||||
assert str(state.state) == "off"
|
||||
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"toggle",
|
||||
{"entity_id": "switch.fibaro_hc2"},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get("switch.fibaro_hc2")
|
||||
assert str(state.state) == "off"
|
||||
|
||||
await hass.services.async_call(
|
||||
"switch",
|
||||
"toggle",
|
||||
{"entity_id": "switch.fibaro_hc2"},
|
||||
blocking=True,
|
||||
)
|
||||
state = hass.states.get("switch.fibaro_hc2")
|
||||
assert str(state.state) == "on"
|
||||
@@ -9,7 +9,7 @@ MOCK_SERVERS = {
|
||||
"name": "Server1",
|
||||
"country": "Country1",
|
||||
"cc": "LL1",
|
||||
"sponsor": "Server1",
|
||||
"sponsor": "Sponsor1",
|
||||
"id": "1",
|
||||
"host": "server1:8080",
|
||||
"d": 1,
|
||||
@@ -23,7 +23,7 @@ MOCK_SERVERS = {
|
||||
"name": "Server2",
|
||||
"country": "Country2",
|
||||
"cc": "LL2",
|
||||
"sponsor": "server2",
|
||||
"sponsor": "Sponsor2",
|
||||
"id": "2",
|
||||
"host": "server2:8080",
|
||||
"d": 2,
|
||||
|
||||
@@ -108,7 +108,7 @@ async def test_options(hass):
|
||||
result = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={
|
||||
CONF_SERVER_NAME: "Country1 - Server1",
|
||||
CONF_SERVER_NAME: "Country1 - Sponsor1 - Server1",
|
||||
CONF_SCAN_INTERVAL: 30,
|
||||
CONF_MANUAL: False,
|
||||
},
|
||||
@@ -116,7 +116,7 @@ async def test_options(hass):
|
||||
|
||||
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||
assert result["data"] == {
|
||||
CONF_SERVER_NAME: "Country1 - Server1",
|
||||
CONF_SERVER_NAME: "Country1 - Sponsor1 - Server1",
|
||||
CONF_SERVER_ID: "1",
|
||||
CONF_SCAN_INTERVAL: 30,
|
||||
CONF_MANUAL: False,
|
||||
|
||||
@@ -6,6 +6,9 @@ import pytest
|
||||
from homeassistant.components.tag import DOMAIN, TAGS, async_scan_tag
|
||||
from homeassistant.helpers import collection
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from tests.async_mock import patch
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -60,7 +63,10 @@ async def test_tag_scanned(hass, hass_ws_client, storage_setup):
|
||||
assert len(result) == 1
|
||||
assert "test tag" in result
|
||||
|
||||
await async_scan_tag(hass, "new tag", "some_scanner")
|
||||
now = dt_util.utcnow()
|
||||
with patch("homeassistant.util.dt.utcnow", return_value=now):
|
||||
await async_scan_tag(hass, "new tag", "some_scanner")
|
||||
|
||||
await client.send_json({"id": 7, "type": f"{DOMAIN}/list"})
|
||||
resp = await client.receive_json()
|
||||
assert resp["success"]
|
||||
@@ -70,7 +76,7 @@ async def test_tag_scanned(hass, hass_ws_client, storage_setup):
|
||||
assert len(result) == 2
|
||||
assert "test tag" in result
|
||||
assert "new tag" in result
|
||||
assert result["new tag"]["last_scanned"] is not None
|
||||
assert result["new tag"]["last_scanned"] == now.isoformat()
|
||||
|
||||
|
||||
def track_changes(coll: collection.ObservableCollection):
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"df4a4a8169904cdb9c03d61a21f42140": {"name": "Zone Lisa Bios", "types": {"py/set": ["thermostat"]}, "class": "zone_thermostat", "location": "12493538af164a409c6a1c79e38afe1c"}, "b310b72a0e354bfab43089919b9a88bf": {"name": "Floor kraan", "types": {"py/set": ["thermostat"]}, "class": "thermo_sensor", "location": "c50f167537524366a5af7aa3942feb1e"}, "a2c3583e0a6349358998b760cea82d2a": {"name": "Bios Cv Thermostatic Radiator ", "types": {"py/set": ["thermostat"]}, "class": "thermo_sensor", "location": "12493538af164a409c6a1c79e38afe1c"}, "b59bcebaf94b499ea7d46e4a66fb62d8": {"name": "Zone Lisa WK", "types": {"py/set": ["thermostat"]}, "class": "zone_thermostat", "location": "c50f167537524366a5af7aa3942feb1e"}, "fe799307f1624099878210aa0b9f1475": {"name": "Adam", "types": {"py/set": ["temperature", "thermostat", "home"]}, "class": "gateway", "location": "1f9dcf83fd4e4b66b72ff787957bfe5d"}, "d3da73bde12a47d5a6b8f9dad971f2ec": {"name": "Thermostatic Radiator Jessie", "types": {"py/set": ["thermostat"]}, "class": "thermo_sensor", "location": "82fa13f017d240daa0d0ea1775420f24"}, "21f2b542c49845e6bb416884c55778d6": {"name": "Playstation Smart Plug", "types": {"py/set": ["plug", "power"]}, "class": "game_console", "location": "cd143c07248f491493cea0533bc3d669"}, "78d1126fc4c743db81b61c20e88342a7": {"name": "CV Pomp", "types": {"py/set": ["plug", "power"]}, "class": "central_heating_pump", "location": "c50f167537524366a5af7aa3942feb1e"}, "90986d591dcd426cae3ec3e8111ff730": {"name": "Adam", "types": {"py/set": ["temperature", "thermostat", "home"]}, "class": "heater_central", "location": "1f9dcf83fd4e4b66b72ff787957bfe5d"}, "cd0ddb54ef694e11ac18ed1cbce5dbbd": {"name": "NAS", "types": {"py/set": ["plug", "power"]}, "class": "vcr", "location": "cd143c07248f491493cea0533bc3d669"}, "4a810418d5394b3f82727340b91ba740": {"name": "USG Smart Plug", "types": {"py/set": ["plug", "power"]}, "class": "router", "location": "cd143c07248f491493cea0533bc3d669"}, "02cf28bfec924855854c544690a609ef": {"name": "NVR", "types": {"py/set": ["plug", "power"]}, "class": "vcr", "location": "cd143c07248f491493cea0533bc3d669"}, "a28f588dc4a049a483fd03a30361ad3a": {"name": "Fibaro HC2", "types": {"py/set": ["plug", "power"]}, "class": "settop", "location": "cd143c07248f491493cea0533bc3d669"}, "6a3bf693d05e48e0b460c815a4fdd09d": {"name": "Zone Thermostat Jessie", "types": {"py/set": ["thermostat"]}, "class": "zone_thermostat", "location": "82fa13f017d240daa0d0ea1775420f24"}, "680423ff840043738f42cc7f1ff97a36": {"name": "Thermostatic Radiator Badkamer", "types": {"py/set": ["thermostat"]}, "class": "thermo_sensor", "location": "08963fec7c53423ca5680aa4cb502c63"}, "f1fee6043d3642a9b0a65297455f008e": {"name": "Zone Thermostat Badkamer", "types": {"py/set": ["thermostat"]}, "class": "zone_thermostat", "location": "08963fec7c53423ca5680aa4cb502c63"}, "675416a629f343c495449970e2ca37b5": {"name": "Ziggo Modem", "types": {"py/set": ["plug", "power"]}, "class": "router", "location": "cd143c07248f491493cea0533bc3d669"}, "e7693eb9582644e5b865dba8d4447cf1": {"name": "CV Kraan Garage", "types": {"py/set": ["thermostat"]}, "class": "thermostatic_radiator_valve", "location": "446ac08dd04d4eff8ac57489757b7314"}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"electricity_consumed": 34.0, "electricity_consumed_interval": 9.15, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"electricity_consumed": 82.6, "electricity_consumed_interval": 8.6, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"electricity_consumed": 8.5, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"electricity_consumed": 12.2, "electricity_consumed_interval": 2.97, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"setpoint": 14.0, "temperature": 19.1, "battery": 0.51, "valve_position": 0.0, "temperature_difference": -0.4}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"setpoint": 15.0, "temperature": 17.2, "battery": 0.37, "active_preset": "asleep", "presets": {"home": [20.0, 22.0], "no_frost": [10.0, 30.0], "away": [12.0, 25.0], "vacation": [11.0, 28.0], "asleep": [16.0, 24.0]}, "schedule_temperature": 15.0, "available_schedules": ["CV Jessie"], "selected_schedule": "CV Jessie", "last_used": "CV Jessie"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"electricity_consumed": 35.6, "electricity_consumed_interval": 7.37, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"water_temperature": 70.0, "intended_boiler_temperature": 70.0, "modulation_level": 0.01}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"electricity_consumed": 12.5, "electricity_consumed_interval": 3.8, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"setpoint": 13.0, "temperature": 17.2, "battery": 0.62, "valve_position": 0.0, "temperature_difference": -0.2}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"setpoint": 21.5, "temperature": 26.0, "valve_position": 1.0, "temperature_difference": 3.5}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"setpoint": 21.5, "temperature": 20.9, "battery": 0.34, "active_preset": "home", "presets": {"vacation": [15.0, 28.0], "asleep": [18.0, 24.0], "no_frost": [12.0, 30.0], "away": [17.0, 25.0], "home": [21.5, 22.0]}, "schedule_temperature": 21.5, "available_schedules": ["GF7 Woonkamer"], "selected_schedule": "GF7 Woonkamer", "last_used": "GF7 Woonkamer"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"electricity_consumed": 16.5, "electricity_consumed_interval": 0.5, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"setpoint": 15.0, "temperature": 17.1, "battery": 0.62, "valve_position": 0.0, "temperature_difference": 0.1}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"setpoint": 13.0, "temperature": 16.5, "battery": 0.67, "active_preset": "away", "presets": {"home": [20.0, 22.0], "away": [12.0, 25.0], "vacation": [12.0, 28.0], "no_frost": [8.0, 30.0], "asleep": [15.0, 24.0]}, "schedule_temperature": null, "available_schedules": [], "selected_schedule": null, "last_used": null}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"setpoint": 5.5, "temperature": 15.6, "battery": 0.68, "valve_position": 0.0, "temperature_difference": 0.0, "active_preset": "no_frost", "presets": {"home": [20.0, 22.0], "asleep": [17.0, 24.0], "away": [15.0, 25.0], "vacation": [15.0, 28.0], "no_frost": [10.0, 30.0]}, "schedule_temperature": null, "available_schedules": [], "selected_schedule": null, "last_used": null}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"setpoint": 14.0, "temperature": 18.9, "battery": 0.92, "active_preset": "away", "presets": {"asleep": [17.0, 24.0], "no_frost": [10.0, 30.0], "away": [14.0, 25.0], "home": [21.0, 22.0], "vacation": [12.0, 28.0]}, "schedule_temperature": 14.0, "available_schedules": ["Badkamer Schema"], "selected_schedule": "Badkamer Schema", "last_used": "Badkamer Schema"}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"outdoor_temperature": 7.81}
|
||||
@@ -0,0 +1 @@
|
||||
{"1cbf783bb11e4a7c8a6843dee3a86927": {"name": "Anna", "types": {"py/set": ["temperature", "thermostat", "home"]}, "class": "heater_central", "location": "a57efe5f145f498c9be62a9b63626fbf"}, "015ae9ea3f964e668e490fa39da3870b": {"name": "Anna", "types": {"py/set": ["temperature", "thermostat", "home"]}, "class": "gateway", "location": "a57efe5f145f498c9be62a9b63626fbf"}, "3cb70739631c4d17a86b8b12e8a5161b": {"name": "Anna", "types": {"py/set": ["thermostat"]}, "class": "thermostat", "location": "c784ee9fdab44e1395b8dee7d7a497d5"}}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"outdoor_temperature": 20.2}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"outdoor_temperature": 18.0, "heating_state": false, "dhw_state": false, "water_temperature": 29.1, "return_temperature": 25.1, "water_pressure": 1.57, "intended_boiler_temperature": 0.0, "modulation_level": 0.52, "cooling_state": false, "slave_boiler_state": false, "compressor_state": true, "flame_state": false}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"setpoint": 21.0, "temperature": 23.3, "active_preset": "home", "presets": {"no_frost": [10.0, 30.0], "home": [21.0, 22.0], "away": [20.0, 25.0], "asleep": [20.5, 24.0], "vacation": [17.0, 28.0]}, "schedule_temperature": null, "available_schedules": ["standaard"], "selected_schedule": "standaard", "last_used": "standaard", "illuminance": 86.0}
|
||||
@@ -0,0 +1 @@
|
||||
{"e950c7d5e1ee407a858e2a8b5016c8b3": {"name": "P1", "types": {"py/set": ["power", "home"]}, "class": "gateway", "location": "cd3e822288064775a7c4afcdd70bdda2"}}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"net_electricity_point": -2761.0, "electricity_consumed_peak_point": 0.0, "electricity_consumed_off_peak_point": 0.0, "net_electricity_cumulative": 442972.0, "electricity_consumed_peak_cumulative": 442932.0, "electricity_consumed_off_peak_cumulative": 551090.0, "net_electricity_interval": 0.0, "electricity_consumed_peak_interval": 0.0, "electricity_consumed_off_peak_interval": 0.0, "electricity_produced_peak_point": 2761.0, "electricity_produced_off_peak_point": 0.0, "electricity_produced_peak_cumulative": 396559.0, "electricity_produced_off_peak_cumulative": 154491.0, "electricity_produced_peak_interval": 0.0, "electricity_produced_off_peak_interval": 0.0, "gas_consumed_cumulative": 584.9, "gas_consumed_interval": 0.0}
|
||||
@@ -5,6 +5,7 @@ import pytest
|
||||
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import condition
|
||||
from homeassistant.helpers.template import Template
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.util import dt
|
||||
|
||||
@@ -807,6 +808,7 @@ async def test_extract_entities():
|
||||
"entity_id": ["sensor.temperature_9", "sensor.temperature_10"],
|
||||
"below": 110,
|
||||
},
|
||||
Template("{{ is_state('light.example', 'on') }}"),
|
||||
],
|
||||
}
|
||||
) == {
|
||||
@@ -867,6 +869,7 @@ async def test_extract_devices():
|
||||
},
|
||||
],
|
||||
},
|
||||
Template("{{ is_state('light.example', 'on') }}"),
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user