Compare commits

...

33 Commits

Author SHA1 Message Date
Bram Kragten e095120023 Bumped version to 0.115.0b10 2020-09-15 16:21:32 +02:00
Bram Kragten 3ef3d848f7 Update frontend to 20200915.0 (#40101) 2020-09-15 16:19:42 +02:00
J. Nick Koston 610a327b52 Convert color temperature to hue and saturation for HomeKit (#40089)
The HomeKit spec does not permit the color temp characteristic
being exposed when color (hue, sat) is present.  Since
Home Assistant will still send color temp values, we need to
convert them to hue, sat values for HomeKit
2020-09-15 16:19:41 +02:00
cgtobi 81436fb688 Check Sonos for local library before browsing (#40085) 2020-09-15 16:19:40 +02:00
Alexei Chetroi 24fe9cdd5a Update ZHA dependency (#40083) 2020-09-15 16:19:39 +02:00
Erik Montnemery e5c499c22e Increase TIMEOUT_ACK to 2s (#40080) 2020-09-15 16:19:39 +02:00
cgtobi 99a8604601 Fix netatmo media browser of outdoor events (#40079)
* Fix outdoor events

* Fix test data

* Increase coverage
2020-09-15 16:19:38 +02:00
Paulus Schoutsen 3ef821d62f Fix tag last scanned serialization (#40067) 2020-09-15 16:19:37 +02:00
Pascal Vizeli a38e047e83 Update docker base image to 8.4.0 (#40066) 2020-09-15 16:19:36 +02:00
Jc2k e0fcf9b648 Bump aiohomekit version (regression fix) (#40064) 2020-09-15 16:18:08 +02:00
Evgeny 0e823b566b Fix default forecast mode OpenWeatherMap (#40062) 2020-09-15 16:00:28 +02:00
Markus Bong a9d24c2cd5 Correct devolo climate devices (#40061) 2020-09-15 16:00:27 +02:00
b3nj1 7a7cad39eb Fix ecobee weather forcast off by 1 bug (#40048) 2020-09-15 16:00:26 +02:00
J. Nick Koston 1a76a953c7 Update gogogate2-api to 2.0.2 (#40010)
* Update gogogate2-api to 2.0.2

Resolves a timeout issue: https://github.com/vangorra/python_gogogate2_api/pull/11

* mock voltage
2020-09-15 16:00:25 +02:00
r4nd0mbr1ck db27079fa8 Speedtestdotnet - use server name to generate server list (#39775) 2020-09-15 16:00:24 +02:00
Paulus Schoutsen ef1649383c Bumped version to 0.115.0b9 2020-09-13 20:22:47 +00:00
Franck Nijhof afde5a7ece Fix entity extraction from Template conditions (#40034) 2020-09-13 20:22:38 +00:00
J. Nick Koston 30b8565548 Ensure homekit_controller traps exceptions from find_ip_by_device_id (#40030) 2020-09-13 20:22:38 +00:00
springstan a971b92899 Fix slack notifications requiring an icon (#40027) 2020-09-13 20:22:37 +00:00
J. Nick Koston 4ee7cdc8a0 Do not log an error when a host is unreachable while pinging (#40024) 2020-09-13 20:22:36 +00:00
Robert Svensson 4c2788a13c Improve handling of mireds being far out of spec (#40018) 2020-09-13 20:22:36 +00:00
Bouwe Westerdijk 8b4e193614 Ensure Plugwise unique_id is correctly set (#40014)
* Ensure unique_id is correctly set

* Removed unnec. line

Co-authored-by: Tom Scholten <git@scholten.nu>
2020-09-13 20:22:35 +00:00
Tom f0ce65af7d Add tests for Plugwise integration (#36371) 2020-09-13 20:22:34 +00:00
springstan b81c61dd99 Fix requiring username or password for nzbget yaml config (#40003) 2020-09-13 20:18:57 +00:00
J. Nick Koston 30ef7a5e88 Suppress homekit bridge discovery by homekit controller (#39990) 2020-09-13 20:18:56 +00:00
Pascal Vizeli 5a6492b76d Update azure-pipelines-wheels.yml 2020-09-13 15:38:31 +02:00
Bram Kragten b19fe17e76 Bumped version to 0.115.0b8 2020-09-13 11:41:46 +02:00
Joakim Sørensen 47326b2295 Bump pyhaversion to 3.4.0 (#40016) 2020-09-13 11:40:19 +02:00
Quentame 951c373110 Fix Freebox call sensor when no call in history (#40001) 2020-09-13 11:40:19 +02:00
Jeff Irion b9b76b3519 Bump androidtv to 0.0.50 (#39998) 2020-09-13 11:40:17 +02:00
Joakim Sørensen da6885af6c Bump frontend to 20200912.0 (#39997) 2020-09-13 11:40:16 +02:00
jjlawren bc2173747c Fix children_media_class for special folders (#39974) 2020-09-13 11:40:15 +02:00
uvjustin d0e6b3e268 Remove skip_sidx container option in stream (#39970)
* Remove skip_sidx container option

* Add comment
2020-09-13 11:40:14 +02:00
82 changed files with 930 additions and 110 deletions
-5
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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
View File
@@ -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",
]:
+6 -5
View File
@@ -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"]
]
+6 -5
View File
@@ -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."
}
}
+1 -1
View File
@@ -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",
+3 -3
View File
@@ -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)
+15 -14
View File
@@ -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."""
+2 -1
View File
@@ -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),
}
+2 -2
View File
@@ -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"]
+1 -1
View File
@@ -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 -1
View File
@@ -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)
+8 -2
View File
@@ -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"):
+1 -1
View File
@@ -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
+7 -7
View File
@@ -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
+1
View File
@@ -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
+7 -7
View File
@@ -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
+22 -4
View File
@@ -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
+12
View File
@@ -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(
+41 -1
View File
@@ -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()
+26
View File
@@ -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
+170
View File
@@ -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
+161
View File
@@ -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"
+30 -3
View File
@@ -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."""
+45
View File
@@ -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
+66
View File
@@ -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
+50
View File
@@ -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"
+2 -2
View File
@@ -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,
+8 -2
View File
@@ -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"}}
@@ -0,0 +1 @@
{"electricity_consumed": 34.0, "electricity_consumed_interval": 9.15, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
@@ -0,0 +1 @@
{"electricity_consumed": 82.6, "electricity_consumed_interval": 8.6, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
@@ -0,0 +1 @@
{"electricity_consumed": 8.5, "electricity_consumed_interval": 0.0, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
@@ -0,0 +1 @@
{"electricity_consumed": 12.2, "electricity_consumed_interval": 2.97, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
@@ -0,0 +1 @@
{"setpoint": 14.0, "temperature": 19.1, "battery": 0.51, "valve_position": 0.0, "temperature_difference": -0.4}
@@ -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"}
@@ -0,0 +1 @@
{"electricity_consumed": 35.6, "electricity_consumed_interval": 7.37, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
@@ -0,0 +1 @@
{"water_temperature": 70.0, "intended_boiler_temperature": 70.0, "modulation_level": 0.01}
@@ -0,0 +1 @@
{"electricity_consumed": 12.5, "electricity_consumed_interval": 3.8, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
@@ -0,0 +1 @@
{"setpoint": 13.0, "temperature": 17.2, "battery": 0.62, "valve_position": 0.0, "temperature_difference": -0.2}
@@ -0,0 +1 @@
{"setpoint": 21.5, "temperature": 26.0, "valve_position": 1.0, "temperature_difference": 3.5}
@@ -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"}
@@ -0,0 +1 @@
{"electricity_consumed": 16.5, "electricity_consumed_interval": 0.5, "electricity_produced": 0.0, "electricity_produced_interval": 0.0, "relay": true}
@@ -0,0 +1 @@
{"setpoint": 15.0, "temperature": 17.1, "battery": 0.62, "valve_position": 0.0, "temperature_difference": 0.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}
@@ -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}
@@ -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"}
@@ -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"}}
@@ -0,0 +1 @@
{"outdoor_temperature": 20.2}
@@ -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}
@@ -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"}}
@@ -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}
+3
View File
@@ -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') }}"),
],
}
)