Merge pull request #51595 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2021-06-07 14:47:50 -07:00 committed by GitHub
commit e5028d285c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 353 additions and 63 deletions

View File

@ -115,7 +115,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image - name: Build base image
uses: home-assistant/builder@2021.05.0 uses: home-assistant/builder@2021.06.2
with: with:
args: | args: |
$BUILD_ARGS \ $BUILD_ARGS \
@ -167,7 +167,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build base image - name: Build base image
uses: home-assistant/builder@2021.05.0 uses: home-assistant/builder@2021.06.2
with: with:
args: | args: |
$BUILD_ARGS \ $BUILD_ARGS \

View File

@ -228,10 +228,10 @@ class AsusWrtRouter:
# System # System
model = await _get_nvram_info(self._api, "MODEL") model = await _get_nvram_info(self._api, "MODEL")
if model: if model and "model" in model:
self._model = model["model"] self._model = model["model"]
firmware = await _get_nvram_info(self._api, "FIRMWARE") firmware = await _get_nvram_info(self._api, "FIRMWARE")
if firmware: if firmware and "firmver" in firmware and "buildno" in firmware:
self._sw_v = f"{firmware['firmver']} (build {firmware['buildno']})" self._sw_v = f"{firmware['firmver']} (build {firmware['buildno']})"
# Load tracked entities from registry # Load tracked entities from registry

View File

@ -44,11 +44,15 @@ class BroadlinkHeartbeat:
"""Send packets to feed watchdog timers.""" """Send packets to feed watchdog timers."""
hass = self._hass hass = self._hass
config_entries = hass.config_entries.async_entries(DOMAIN) config_entries = hass.config_entries.async_entries(DOMAIN)
hosts = {entry.data[CONF_HOST] for entry in config_entries}
await hass.async_add_executor_job(self.heartbeat, hosts)
for entry in config_entries: @staticmethod
host = entry.data[CONF_HOST] def heartbeat(hosts):
"""Send packets to feed watchdog timers."""
for host in hosts:
try: try:
await hass.async_add_executor_job(blk.ping, host) blk.ping(host)
except OSError as err: except OSError as err:
_LOGGER.debug("Failed to send heartbeat to %s: %s", host, err) _LOGGER.debug("Failed to send heartbeat to %s: %s", host, err)
else: else:

View File

@ -3,6 +3,6 @@
"name": "Deutscher Wetterdienst (DWD) Weather Warnings", "name": "Deutscher Wetterdienst (DWD) Weather Warnings",
"documentation": "https://www.home-assistant.io/integrations/dwd_weather_warnings", "documentation": "https://www.home-assistant.io/integrations/dwd_weather_warnings",
"codeowners": ["@runningman84", "@stephan192", "@Hummel95"], "codeowners": ["@runningman84", "@stephan192", "@Hummel95"],
"requirements": ["dwdwfsapi==1.0.3"], "requirements": ["dwdwfsapi==1.0.4"],
"iot_class": "cloud_polling" "iot_class": "cloud_polling"
} }

View File

@ -39,14 +39,14 @@ class GarminConnectConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
return await self._show_setup_form() return await self._show_setup_form()
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
username = user_input[CONF_USERNAME]
password = user_input[CONF_PASSWORD]
garmin_client = Garmin( garmin_client = Garmin(websession, username, password)
websession, user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
)
errors = {} errors = {}
try: try:
username = await garmin_client.login() await garmin_client.login()
except GarminConnectConnectionError: except GarminConnectConnectionError:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
return await self._show_setup_form(errors) return await self._show_setup_form(errors)
@ -68,7 +68,7 @@ class GarminConnectConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
title=username, title=username,
data={ data={
CONF_ID: username, CONF_ID: username,
CONF_USERNAME: user_input[CONF_USERNAME], CONF_USERNAME: username,
CONF_PASSWORD: user_input[CONF_PASSWORD], CONF_PASSWORD: password,
}, },
) )

View File

@ -2,7 +2,7 @@
"domain": "garmin_connect", "domain": "garmin_connect",
"name": "Garmin Connect", "name": "Garmin Connect",
"documentation": "https://www.home-assistant.io/integrations/garmin_connect", "documentation": "https://www.home-assistant.io/integrations/garmin_connect",
"requirements": ["garminconnect_aio==0.1.1"], "requirements": ["garminconnect_aio==0.1.4"],
"codeowners": ["@cyberjunky"], "codeowners": ["@cyberjunky"],
"config_flow": true, "config_flow": true,
"iot_class": "cloud_polling" "iot_class": "cloud_polling"

View File

@ -1,4 +1,6 @@
"""Offer geolocation automation rules.""" """Offer geolocation automation rules."""
import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.geo_location import DOMAIN from homeassistant.components.geo_location import DOMAIN
@ -10,6 +12,8 @@ from homeassistant.helpers.event import TrackStates, async_track_state_change_fi
# mypy: allow-untyped-defs, no-check-untyped-defs # mypy: allow-untyped-defs, no-check-untyped-defs
_LOGGER = logging.getLogger(__name__)
EVENT_ENTER = "enter" EVENT_ENTER = "enter"
EVENT_LEAVE = "leave" EVENT_LEAVE = "leave"
DEFAULT_EVENT = EVENT_ENTER DEFAULT_EVENT = EVENT_ENTER
@ -49,6 +53,13 @@ async def async_attach_trigger(hass, config, action, automation_info):
return return
zone_state = hass.states.get(zone_entity_id) zone_state = hass.states.get(zone_entity_id)
if zone_state is None:
_LOGGER.warning(
"Unable to execute automation %s: Zone %s not found",
automation_info["name"],
zone_entity_id,
)
return
from_match = ( from_match = (
condition.zone(hass, zone_state, from_state) if from_state else False condition.zone(hass, zone_state, from_state) if from_state else False

View File

@ -2,7 +2,7 @@
"domain": "ialarm", "domain": "ialarm",
"name": "Antifurto365 iAlarm", "name": "Antifurto365 iAlarm",
"documentation": "https://www.home-assistant.io/integrations/ialarm", "documentation": "https://www.home-assistant.io/integrations/ialarm",
"requirements": ["pyialarm==1.7"], "requirements": ["pyialarm==1.8.1"],
"codeowners": ["@RyuzakiKK"], "codeowners": ["@RyuzakiKK"],
"config_flow": true, "config_flow": true,
"iot_class": "local_polling" "iot_class": "local_polling"

View File

@ -172,14 +172,12 @@ async def async_setup_entry(
) )
try: try:
async with async_timeout.timeout(30): async with async_timeout.timeout(60):
await isy.initialize() await isy.initialize()
except asyncio.TimeoutError as err: except asyncio.TimeoutError as err:
_LOGGER.error( raise ConfigEntryNotReady(
"Timed out initializing the ISY; device may be busy, trying again later: %s", f"Timed out initializing the ISY; device may be busy, trying again later: {err}"
err, ) from err
)
raise ConfigEntryNotReady from err
except ISYInvalidAuthError as err: except ISYInvalidAuthError as err:
_LOGGER.error( _LOGGER.error(
"Invalid credentials for the ISY, please adjust settings and try again: %s", "Invalid credentials for the ISY, please adjust settings and try again: %s",
@ -187,16 +185,13 @@ async def async_setup_entry(
) )
return False return False
except ISYConnectionError as err: except ISYConnectionError as err:
_LOGGER.error( raise ConfigEntryNotReady(
"Failed to connect to the ISY, please adjust settings and try again: %s", f"Failed to connect to the ISY, please adjust settings and try again: {err}"
err, ) from err
)
raise ConfigEntryNotReady from err
except ISYResponseParseError as err: except ISYResponseParseError as err:
_LOGGER.warning( raise ConfigEntryNotReady(
"Error processing responses from the ISY; device may be busy, trying again later" f"Invalid XML response from ISY; Ensure the ISY is running the latest firmware: {err}"
) ) from err
raise ConfigEntryNotReady from err
_categorize_nodes(hass_isy_data, isy.nodes, ignore_identifier, sensor_identifier) _categorize_nodes(hass_isy_data, isy.nodes, ignore_identifier, sensor_identifier)
_categorize_programs(hass_isy_data, isy.programs) _categorize_programs(hass_isy_data, isy.programs)

View File

@ -186,9 +186,6 @@ async def async_setup_entity_basic(
hass, config, async_add_entities, config_entry, discovery_data=None hass, config, async_add_entities, config_entry, discovery_data=None
): ):
"""Set up a MQTT Light.""" """Set up a MQTT Light."""
if CONF_STATE_VALUE_TEMPLATE not in config and CONF_VALUE_TEMPLATE in config:
config[CONF_STATE_VALUE_TEMPLATE] = config[CONF_VALUE_TEMPLATE]
async_add_entities([MqttLight(hass, config, config_entry, discovery_data)]) async_add_entities([MqttLight(hass, config, config_entry, discovery_data)])
@ -236,6 +233,9 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity):
def _setup_from_config(self, config): def _setup_from_config(self, config):
"""(Re)Setup the entity.""" """(Re)Setup the entity."""
if CONF_STATE_VALUE_TEMPLATE not in config and CONF_VALUE_TEMPLATE in config:
config[CONF_STATE_VALUE_TEMPLATE] = config[CONF_VALUE_TEMPLATE]
topic = { topic = {
key: config.get(key) key: config.get(key)
for key in ( for key in (

View File

@ -3,7 +3,7 @@
"name": "ReCollect Waste", "name": "ReCollect Waste",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/recollect_waste", "documentation": "https://www.home-assistant.io/integrations/recollect_waste",
"requirements": ["aiorecollect==1.0.4"], "requirements": ["aiorecollect==1.0.5"],
"codeowners": ["@bachya"], "codeowners": ["@bachya"],
"iot_class": "cloud_polling" "iot_class": "cloud_polling"
} }

View File

@ -280,10 +280,10 @@ def _update_states_table_with_foreign_key_options(connection, engine):
for foreign_key in inspector.get_foreign_keys(TABLE_STATES): for foreign_key in inspector.get_foreign_keys(TABLE_STATES):
if foreign_key["name"] and ( if foreign_key["name"] and (
# MySQL/MariaDB will have empty options # MySQL/MariaDB will have empty options
not foreign_key["options"] not foreign_key.get("options")
or or
# Postgres will have ondelete set to None # Postgres will have ondelete set to None
foreign_key["options"].get("ondelete") is None foreign_key.get("options", {}).get("ondelete") is None
): ):
alters.append( alters.append(
{ {
@ -319,7 +319,7 @@ def _drop_foreign_key_constraints(connection, engine, table, columns):
for foreign_key in inspector.get_foreign_keys(table): for foreign_key in inspector.get_foreign_keys(table):
if ( if (
foreign_key["name"] foreign_key["name"]
and foreign_key["options"].get("ondelete") and foreign_key.get("options", {}).get("ondelete")
and foreign_key["constrained_columns"] == columns and foreign_key["constrained_columns"] == columns
): ):
drops.append(ForeignKeyConstraint((), (), name=foreign_key["name"])) drops.append(ForeignKeyConstraint((), (), name=foreign_key["name"]))

View File

@ -112,6 +112,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def _async_set_unique_id_from_udn(self, raise_on_progress=True): async def _async_set_unique_id_from_udn(self, raise_on_progress=True):
"""Set the unique id from the udn.""" """Set the unique id from the udn."""
assert self._host is not None
await self.async_set_unique_id(self._udn, raise_on_progress=raise_on_progress) await self.async_set_unique_id(self._udn, raise_on_progress=raise_on_progress)
self._async_update_existing_host_entry(self._host) self._async_update_existing_host_entry(self._host)
updates = {CONF_HOST: self._host} updates = {CONF_HOST: self._host}
@ -206,30 +207,28 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return entry return entry
return None return None
async def _async_start_discovery_for_host(self, host): async def _async_start_discovery(self):
"""Start discovery for a host.""" """Start discovery."""
if entry := self._async_update_existing_host_entry(host): assert self._host is not None
if entry := self._async_update_existing_host_entry(self._host):
if entry.unique_id: if entry.unique_id:
# Let the flow continue to fill the missing # Let the flow continue to fill the missing
# unique id as we may be able to obtain it # unique id as we may be able to obtain it
# in the next step # in the next step
raise data_entry_flow.AbortFlow("already_configured") raise data_entry_flow.AbortFlow("already_configured")
self.context[CONF_HOST] = host self.context[CONF_HOST] = self._host
for progress in self._async_in_progress(): for progress in self._async_in_progress():
if progress.get("context", {}).get(CONF_HOST) == host: if progress.get("context", {}).get(CONF_HOST) == self._host:
raise data_entry_flow.AbortFlow("already_in_progress") raise data_entry_flow.AbortFlow("already_in_progress")
self._host = host
async def async_step_ssdp(self, discovery_info: DiscoveryInfoType): async def async_step_ssdp(self, discovery_info: DiscoveryInfoType):
"""Handle a flow initialized by ssdp discovery.""" """Handle a flow initialized by ssdp discovery."""
LOGGER.debug("Samsung device found via SSDP: %s", discovery_info) LOGGER.debug("Samsung device found via SSDP: %s", discovery_info)
self._udn = _strip_uuid(discovery_info[ATTR_UPNP_UDN]) self._udn = _strip_uuid(discovery_info[ATTR_UPNP_UDN])
self._host = urlparse(discovery_info[ATTR_SSDP_LOCATION]).hostname
await self._async_set_unique_id_from_udn() await self._async_set_unique_id_from_udn()
await self._async_start_discovery_for_host( await self._async_start_discovery()
urlparse(discovery_info[ATTR_SSDP_LOCATION]).hostname
)
self._manufacturer = discovery_info[ATTR_UPNP_MANUFACTURER] self._manufacturer = discovery_info[ATTR_UPNP_MANUFACTURER]
if not self._manufacturer or not self._manufacturer.lower().startswith( if not self._manufacturer or not self._manufacturer.lower().startswith(
"samsung" "samsung"
@ -245,7 +244,8 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a flow initialized by dhcp discovery.""" """Handle a flow initialized by dhcp discovery."""
LOGGER.debug("Samsung device found via DHCP: %s", discovery_info) LOGGER.debug("Samsung device found via DHCP: %s", discovery_info)
self._mac = discovery_info[MAC_ADDRESS] self._mac = discovery_info[MAC_ADDRESS]
await self._async_start_discovery_for_host(discovery_info[IP_ADDRESS]) self._host = discovery_info[IP_ADDRESS]
await self._async_start_discovery()
await self._async_set_device_unique_id() await self._async_set_device_unique_id()
self.context["title_placeholders"] = {"device": self._title} self.context["title_placeholders"] = {"device": self._title}
return await self.async_step_confirm() return await self.async_step_confirm()
@ -254,7 +254,8 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a flow initialized by zeroconf discovery.""" """Handle a flow initialized by zeroconf discovery."""
LOGGER.debug("Samsung device found via ZEROCONF: %s", discovery_info) LOGGER.debug("Samsung device found via ZEROCONF: %s", discovery_info)
self._mac = format_mac(discovery_info[ATTR_PROPERTIES]["deviceid"]) self._mac = format_mac(discovery_info[ATTR_PROPERTIES]["deviceid"])
await self._async_start_discovery_for_host(discovery_info[CONF_HOST]) self._host = discovery_info[CONF_HOST]
await self._async_start_discovery()
await self._async_set_device_unique_id() await self._async_set_device_unique_id()
self.context["title_placeholders"] = {"device": self._title} self.context["title_placeholders"] = {"device": self._title}
return await self.async_step_confirm() return await self.async_step_confirm()

View File

@ -764,7 +764,7 @@ class SonosSpeaker:
"""Pause all current coordinators and restore groups.""" """Pause all current coordinators and restore groups."""
for speaker in (s for s in speakers if s.is_coordinator): for speaker in (s for s in speakers if s.is_coordinator):
if speaker.media.playback_status == SONOS_STATE_PLAYING: if speaker.media.playback_status == SONOS_STATE_PLAYING:
hass.async_create_task(speaker.soco.pause()) speaker.soco.pause()
groups = [] groups = []

View File

@ -45,6 +45,7 @@ SIGNAL_UPDATE_ENTITY = "tibber_rt_update_{}"
RT_SENSOR_MAP = { RT_SENSOR_MAP = {
"averagePower": ["average power", DEVICE_CLASS_POWER, POWER_WATT, None], "averagePower": ["average power", DEVICE_CLASS_POWER, POWER_WATT, None],
"power": ["power", DEVICE_CLASS_POWER, POWER_WATT, None], "power": ["power", DEVICE_CLASS_POWER, POWER_WATT, None],
"powerProduction": ["power production", DEVICE_CLASS_POWER, POWER_WATT, None],
"minPower": ["min power", DEVICE_CLASS_POWER, POWER_WATT, None], "minPower": ["min power", DEVICE_CLASS_POWER, POWER_WATT, None],
"maxPower": ["max power", DEVICE_CLASS_POWER, POWER_WATT, None], "maxPower": ["max power", DEVICE_CLASS_POWER, POWER_WATT, None],
"accumulatedConsumption": [ "accumulatedConsumption": [

View File

@ -156,6 +156,26 @@ class TodSensor(BinarySensorEntity):
self._time_after += self._after_offset self._time_after += self._after_offset
self._time_before += self._before_offset self._time_before += self._before_offset
def _turn_to_next_day(self):
"""Turn to to the next day."""
if is_sun_event(self._after):
self._time_after = get_astral_event_next(
self.hass, self._after, self._time_after - self._after_offset
)
self._time_after += self._after_offset
else:
# Offset is already there
self._time_after += timedelta(days=1)
if is_sun_event(self._before):
self._time_before = get_astral_event_next(
self.hass, self._before, self._time_before - self._before_offset
)
self._time_before += self._before_offset
else:
# Offset is already there
self._time_before += timedelta(days=1)
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Call when entity about to be added to Home Assistant.""" """Call when entity about to be added to Home Assistant."""
self._calculate_boudary_time() self._calculate_boudary_time()
@ -182,7 +202,7 @@ class TodSensor(BinarySensorEntity):
if now < self._time_before: if now < self._time_before:
self._next_update = self._time_before self._next_update = self._time_before
return return
self._calculate_boudary_time() self._turn_to_next_day()
self._next_update = self._time_after self._next_update = self._time_after
@callback @callback

View File

@ -5,7 +5,7 @@ from typing import Final
MAJOR_VERSION: Final = 2021 MAJOR_VERSION: Final = 2021
MINOR_VERSION: Final = 6 MINOR_VERSION: Final = 6
PATCH_VERSION: Final = "2" PATCH_VERSION: Final = "3"
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
__version__: Final = f"{__short_version__}.{PATCH_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}"
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0) REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0)

View File

@ -224,7 +224,7 @@ aiopvpc==2.1.2
aiopylgtv==0.4.0 aiopylgtv==0.4.0
# homeassistant.components.recollect_waste # homeassistant.components.recollect_waste
aiorecollect==1.0.4 aiorecollect==1.0.5
# homeassistant.components.shelly # homeassistant.components.shelly
aioshelly==0.6.4 aioshelly==0.6.4
@ -515,7 +515,7 @@ dovado==0.4.1
dsmr_parser==0.29 dsmr_parser==0.29
# homeassistant.components.dwd_weather_warnings # homeassistant.components.dwd_weather_warnings
dwdwfsapi==1.0.3 dwdwfsapi==1.0.4
# homeassistant.components.dweet # homeassistant.components.dweet
dweepy==0.3.0 dweepy==0.3.0
@ -635,7 +635,7 @@ gTTS==2.2.2
garages-amsterdam==2.1.1 garages-amsterdam==2.1.1
# homeassistant.components.garmin_connect # homeassistant.components.garmin_connect
garminconnect_aio==0.1.1 garminconnect_aio==0.1.4
# homeassistant.components.geniushub # homeassistant.components.geniushub
geniushub-client==0.6.30 geniushub-client==0.6.30
@ -1464,7 +1464,7 @@ pyhomematic==0.1.72
pyhomeworks==0.0.6 pyhomeworks==0.0.6
# homeassistant.components.ialarm # homeassistant.components.ialarm
pyialarm==1.7 pyialarm==1.8.1
# homeassistant.components.icloud # homeassistant.components.icloud
pyicloud==0.10.2 pyicloud==0.10.2

View File

@ -146,7 +146,7 @@ aiopvpc==2.1.2
aiopylgtv==0.4.0 aiopylgtv==0.4.0
# homeassistant.components.recollect_waste # homeassistant.components.recollect_waste
aiorecollect==1.0.4 aiorecollect==1.0.5
# homeassistant.components.shelly # homeassistant.components.shelly
aioshelly==0.6.4 aioshelly==0.6.4
@ -341,7 +341,7 @@ gTTS==2.2.2
garages-amsterdam==2.1.1 garages-amsterdam==2.1.1
# homeassistant.components.garmin_connect # homeassistant.components.garmin_connect
garminconnect_aio==0.1.1 garminconnect_aio==0.1.4
# homeassistant.components.geo_json_events # homeassistant.components.geo_json_events
# homeassistant.components.usgs_earthquakes_feed # homeassistant.components.usgs_earthquakes_feed
@ -808,7 +808,7 @@ pyhiveapi==0.4.2
pyhomematic==0.1.72 pyhomematic==0.1.72
# homeassistant.components.ialarm # homeassistant.components.ialarm
pyialarm==1.7 pyialarm==1.8.1
# homeassistant.components.icloud # homeassistant.components.icloud
pyicloud==0.10.2 pyicloud==0.10.2

View File

@ -1,4 +1,6 @@
"""The tests for the geolocation trigger.""" """The tests for the geolocation trigger."""
import logging
import pytest import pytest
from homeassistant.components import automation, zone from homeassistant.components import automation, zone
@ -318,3 +320,46 @@ async def test_if_fires_on_zone_disappear(hass, calls):
assert ( assert (
calls[0].data["some"] == "geo_location - geo_location.entity - hello - - test" calls[0].data["some"] == "geo_location - geo_location.entity - hello - - test"
) )
async def test_zone_undefined(hass, calls, caplog):
"""Test for undefined zone."""
hass.states.async_set(
"geo_location.entity",
"hello",
{"latitude": 32.880586, "longitude": -117.237564, "source": "test_source"},
)
await hass.async_block_till_done()
caplog.set_level(logging.WARNING)
zone_does_not_exist = "zone.does_not_exist"
assert await async_setup_component(
hass,
automation.DOMAIN,
{
automation.DOMAIN: {
"trigger": {
"platform": "geo_location",
"source": "test_source",
"zone": zone_does_not_exist,
"event": "leave",
},
"action": {"service": "test.automation"},
}
},
)
hass.states.async_set(
"geo_location.entity",
"hello",
{"latitude": 32.881011, "longitude": -117.234758, "source": "test_source"},
)
await hass.async_block_till_done()
assert len(calls) == 0
assert (
f"Unable to execute automation automation 0: Zone {zone_does_not_exist} not found"
in caplog.text
)

View File

@ -12,6 +12,8 @@ import homeassistant.util.dt as dt_util
from tests.common import assert_setup_component from tests.common import assert_setup_component
ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def mock_legacy_time(legacy_patchable_time): def mock_legacy_time(legacy_patchable_time):
@ -26,6 +28,13 @@ def setup_fixture(hass):
hass.config.longitude = 18.98583 hass.config.longitude = 18.98583
@pytest.fixture(autouse=True)
def restore_timezone(hass):
"""Make sure we change timezone."""
yield
dt_util.set_default_time_zone(ORIG_TIMEZONE)
async def test_setup(hass): async def test_setup(hass):
"""Test the setup.""" """Test the setup."""
config = { config = {
@ -863,6 +872,7 @@ async def test_sun_offset(hass):
async def test_dst(hass): async def test_dst(hass):
"""Test sun event with offset.""" """Test sun event with offset."""
hass.config.time_zone = "CET" hass.config.time_zone = "CET"
dt_util.set_default_time_zone(dt_util.get_time_zone("CET"))
test_time = datetime(2019, 3, 30, 3, 0, 0, tzinfo=dt_util.UTC) test_time = datetime(2019, 3, 30, 3, 0, 0, tzinfo=dt_util.UTC)
config = { config = {
"binary_sensor": [ "binary_sensor": [
@ -882,7 +892,210 @@ async def test_dst(hass):
await hass.async_block_till_done() await hass.async_block_till_done()
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
assert state.attributes["after"] == "2019-03-30T03:30:00+01:00" assert state.attributes["after"] == "2019-03-31T03:30:00+02:00"
assert state.attributes["before"] == "2019-03-30T03:40:00+01:00" assert state.attributes["before"] == "2019-03-31T03:40:00+02:00"
assert state.attributes["next_update"] == "2019-03-30T03:30:00+01:00" assert state.attributes["next_update"] == "2019-03-31T03:30:00+02:00"
assert state.state == STATE_OFF assert state.state == STATE_OFF
async def test_simple_before_after_does_not_loop_utc_not_in_range(hass):
"""Test simple before after."""
hass.config.time_zone = "UTC"
dt_util.set_default_time_zone(dt_util.UTC)
test_time = datetime(2019, 1, 10, 18, 43, 0, tzinfo=dt_util.UTC)
config = {
"binary_sensor": [
{
"platform": "tod",
"name": "Night",
"before": "06:00",
"after": "22:00",
}
]
}
with patch(
"homeassistant.components.tod.binary_sensor.dt_util.utcnow",
return_value=test_time,
):
await async_setup_component(hass, "binary_sensor", config)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.night")
assert state.state == STATE_OFF
assert state.attributes["after"] == "2019-01-10T22:00:00+00:00"
assert state.attributes["before"] == "2019-01-11T06:00:00+00:00"
assert state.attributes["next_update"] == "2019-01-10T22:00:00+00:00"
async def test_simple_before_after_does_not_loop_utc_in_range(hass):
"""Test simple before after."""
hass.config.time_zone = "UTC"
dt_util.set_default_time_zone(dt_util.UTC)
test_time = datetime(2019, 1, 10, 22, 43, 0, tzinfo=dt_util.UTC)
config = {
"binary_sensor": [
{
"platform": "tod",
"name": "Night",
"before": "06:00",
"after": "22:00",
}
]
}
with patch(
"homeassistant.components.tod.binary_sensor.dt_util.utcnow",
return_value=test_time,
):
await async_setup_component(hass, "binary_sensor", config)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.night")
assert state.state == STATE_ON
assert state.attributes["after"] == "2019-01-10T22:00:00+00:00"
assert state.attributes["before"] == "2019-01-11T06:00:00+00:00"
assert state.attributes["next_update"] == "2019-01-11T06:00:00+00:00"
async def test_simple_before_after_does_not_loop_utc_fire_at_before(hass):
"""Test simple before after."""
hass.config.time_zone = "UTC"
dt_util.set_default_time_zone(dt_util.UTC)
test_time = datetime(2019, 1, 11, 6, 0, 0, tzinfo=dt_util.UTC)
config = {
"binary_sensor": [
{
"platform": "tod",
"name": "Night",
"before": "06:00",
"after": "22:00",
}
]
}
with patch(
"homeassistant.components.tod.binary_sensor.dt_util.utcnow",
return_value=test_time,
):
await async_setup_component(hass, "binary_sensor", config)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.night")
assert state.state == STATE_OFF
assert state.attributes["after"] == "2019-01-11T22:00:00+00:00"
assert state.attributes["before"] == "2019-01-12T06:00:00+00:00"
assert state.attributes["next_update"] == "2019-01-11T22:00:00+00:00"
async def test_simple_before_after_does_not_loop_utc_fire_at_after(hass):
"""Test simple before after."""
hass.config.time_zone = "UTC"
dt_util.set_default_time_zone(dt_util.UTC)
test_time = datetime(2019, 1, 10, 22, 0, 0, tzinfo=dt_util.UTC)
config = {
"binary_sensor": [
{
"platform": "tod",
"name": "Night",
"before": "06:00",
"after": "22:00",
}
]
}
with patch(
"homeassistant.components.tod.binary_sensor.dt_util.utcnow",
return_value=test_time,
):
await async_setup_component(hass, "binary_sensor", config)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.night")
assert state.state == STATE_ON
assert state.attributes["after"] == "2019-01-10T22:00:00+00:00"
assert state.attributes["before"] == "2019-01-11T06:00:00+00:00"
assert state.attributes["next_update"] == "2019-01-11T06:00:00+00:00"
async def test_simple_before_after_does_not_loop_utc_both_before_now(hass):
"""Test simple before after."""
hass.config.time_zone = "UTC"
dt_util.set_default_time_zone(dt_util.UTC)
test_time = datetime(2019, 1, 10, 22, 0, 0, tzinfo=dt_util.UTC)
config = {
"binary_sensor": [
{
"platform": "tod",
"name": "Morning",
"before": "08:00",
"after": "00:00",
}
]
}
with patch(
"homeassistant.components.tod.binary_sensor.dt_util.utcnow",
return_value=test_time,
):
await async_setup_component(hass, "binary_sensor", config)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.morning")
assert state.state == STATE_OFF
assert state.attributes["after"] == "2019-01-11T00:00:00+00:00"
assert state.attributes["before"] == "2019-01-11T08:00:00+00:00"
assert state.attributes["next_update"] == "2019-01-11T00:00:00+00:00"
async def test_simple_before_after_does_not_loop_berlin_not_in_range(hass):
"""Test simple before after."""
hass.config.time_zone = "Europe/Berlin"
dt_util.set_default_time_zone(dt_util.get_time_zone("Europe/Berlin"))
test_time = datetime(2019, 1, 10, 18, 43, 0, tzinfo=dt_util.UTC)
config = {
"binary_sensor": [
{
"platform": "tod",
"name": "Dark",
"before": "06:00",
"after": "00:00",
}
]
}
with patch(
"homeassistant.components.tod.binary_sensor.dt_util.utcnow",
return_value=test_time,
):
await async_setup_component(hass, "binary_sensor", config)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.dark")
assert state.state == STATE_OFF
assert state.attributes["after"] == "2019-01-11T00:00:00+01:00"
assert state.attributes["before"] == "2019-01-11T06:00:00+01:00"
assert state.attributes["next_update"] == "2019-01-11T00:00:00+01:00"
async def test_simple_before_after_does_not_loop_berlin_in_range(hass):
"""Test simple before after."""
hass.config.time_zone = "Europe/Berlin"
dt_util.set_default_time_zone(dt_util.get_time_zone("Europe/Berlin"))
test_time = datetime(2019, 1, 10, 23, 43, 0, tzinfo=dt_util.UTC)
config = {
"binary_sensor": [
{
"platform": "tod",
"name": "Dark",
"before": "06:00",
"after": "00:00",
}
]
}
with patch(
"homeassistant.components.tod.binary_sensor.dt_util.utcnow",
return_value=test_time,
):
await async_setup_component(hass, "binary_sensor", config)
await hass.async_block_till_done()
state = hass.states.get("binary_sensor.dark")
assert state.state == STATE_ON
assert state.attributes["after"] == "2019-01-11T00:00:00+01:00"
assert state.attributes["before"] == "2019-01-11T06:00:00+01:00"
assert state.attributes["next_update"] == "2019-01-11T06:00:00+01:00"