diff --git a/.github/workflows/builder.yml b/.github/workflows/builder.yml index 190c449cf3c..607af99fb51 100644 --- a/.github/workflows/builder.yml +++ b/.github/workflows/builder.yml @@ -115,7 +115,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build base image - uses: home-assistant/builder@2021.05.0 + uses: home-assistant/builder@2021.06.2 with: args: | $BUILD_ARGS \ @@ -167,7 +167,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build base image - uses: home-assistant/builder@2021.05.0 + uses: home-assistant/builder@2021.06.2 with: args: | $BUILD_ARGS \ diff --git a/homeassistant/components/asuswrt/router.py b/homeassistant/components/asuswrt/router.py index 0912869abb7..134cf960aae 100644 --- a/homeassistant/components/asuswrt/router.py +++ b/homeassistant/components/asuswrt/router.py @@ -228,10 +228,10 @@ class AsusWrtRouter: # System model = await _get_nvram_info(self._api, "MODEL") - if model: + if model and "model" in model: self._model = model["model"] 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']})" # Load tracked entities from registry diff --git a/homeassistant/components/broadlink/heartbeat.py b/homeassistant/components/broadlink/heartbeat.py index 282df3ae6a8..b4deffa5b81 100644 --- a/homeassistant/components/broadlink/heartbeat.py +++ b/homeassistant/components/broadlink/heartbeat.py @@ -44,11 +44,15 @@ class BroadlinkHeartbeat: """Send packets to feed watchdog timers.""" hass = self._hass 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: - host = entry.data[CONF_HOST] + @staticmethod + def heartbeat(hosts): + """Send packets to feed watchdog timers.""" + for host in hosts: try: - await hass.async_add_executor_job(blk.ping, host) + blk.ping(host) except OSError as err: _LOGGER.debug("Failed to send heartbeat to %s: %s", host, err) else: diff --git a/homeassistant/components/dwd_weather_warnings/manifest.json b/homeassistant/components/dwd_weather_warnings/manifest.json index 1550d9262a4..55c848ea219 100644 --- a/homeassistant/components/dwd_weather_warnings/manifest.json +++ b/homeassistant/components/dwd_weather_warnings/manifest.json @@ -3,6 +3,6 @@ "name": "Deutscher Wetterdienst (DWD) Weather Warnings", "documentation": "https://www.home-assistant.io/integrations/dwd_weather_warnings", "codeowners": ["@runningman84", "@stephan192", "@Hummel95"], - "requirements": ["dwdwfsapi==1.0.3"], + "requirements": ["dwdwfsapi==1.0.4"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/garmin_connect/config_flow.py b/homeassistant/components/garmin_connect/config_flow.py index 8e26e2bf608..8f83a9e1071 100644 --- a/homeassistant/components/garmin_connect/config_flow.py +++ b/homeassistant/components/garmin_connect/config_flow.py @@ -39,14 +39,14 @@ class GarminConnectConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): return await self._show_setup_form() websession = async_get_clientsession(self.hass) + username = user_input[CONF_USERNAME] + password = user_input[CONF_PASSWORD] - garmin_client = Garmin( - websession, user_input[CONF_USERNAME], user_input[CONF_PASSWORD] - ) + garmin_client = Garmin(websession, username, password) errors = {} try: - username = await garmin_client.login() + await garmin_client.login() except GarminConnectConnectionError: errors["base"] = "cannot_connect" return await self._show_setup_form(errors) @@ -68,7 +68,7 @@ class GarminConnectConfigFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): title=username, data={ CONF_ID: username, - CONF_USERNAME: user_input[CONF_USERNAME], - CONF_PASSWORD: user_input[CONF_PASSWORD], + CONF_USERNAME: username, + CONF_PASSWORD: password, }, ) diff --git a/homeassistant/components/garmin_connect/manifest.json b/homeassistant/components/garmin_connect/manifest.json index 2495249e4a4..22e115d0e06 100644 --- a/homeassistant/components/garmin_connect/manifest.json +++ b/homeassistant/components/garmin_connect/manifest.json @@ -2,7 +2,7 @@ "domain": "garmin_connect", "name": "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"], "config_flow": true, "iot_class": "cloud_polling" diff --git a/homeassistant/components/geo_location/trigger.py b/homeassistant/components/geo_location/trigger.py index 3cdefccfeab..4410d39c0a6 100644 --- a/homeassistant/components/geo_location/trigger.py +++ b/homeassistant/components/geo_location/trigger.py @@ -1,4 +1,6 @@ """Offer geolocation automation rules.""" +import logging + import voluptuous as vol 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 +_LOGGER = logging.getLogger(__name__) + EVENT_ENTER = "enter" EVENT_LEAVE = "leave" DEFAULT_EVENT = EVENT_ENTER @@ -49,6 +53,13 @@ async def async_attach_trigger(hass, config, action, automation_info): return 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 = ( condition.zone(hass, zone_state, from_state) if from_state else False diff --git a/homeassistant/components/ialarm/manifest.json b/homeassistant/components/ialarm/manifest.json index e112a26003e..08666129fd9 100644 --- a/homeassistant/components/ialarm/manifest.json +++ b/homeassistant/components/ialarm/manifest.json @@ -2,7 +2,7 @@ "domain": "ialarm", "name": "Antifurto365 iAlarm", "documentation": "https://www.home-assistant.io/integrations/ialarm", - "requirements": ["pyialarm==1.7"], + "requirements": ["pyialarm==1.8.1"], "codeowners": ["@RyuzakiKK"], "config_flow": true, "iot_class": "local_polling" diff --git a/homeassistant/components/isy994/__init__.py b/homeassistant/components/isy994/__init__.py index 99905e4d946..51c34aeb0a7 100644 --- a/homeassistant/components/isy994/__init__.py +++ b/homeassistant/components/isy994/__init__.py @@ -172,14 +172,12 @@ async def async_setup_entry( ) try: - async with async_timeout.timeout(30): + async with async_timeout.timeout(60): await isy.initialize() except asyncio.TimeoutError as err: - _LOGGER.error( - "Timed out initializing the ISY; device may be busy, trying again later: %s", - err, - ) - raise ConfigEntryNotReady from err + raise ConfigEntryNotReady( + f"Timed out initializing the ISY; device may be busy, trying again later: {err}" + ) from err except ISYInvalidAuthError as err: _LOGGER.error( "Invalid credentials for the ISY, please adjust settings and try again: %s", @@ -187,16 +185,13 @@ async def async_setup_entry( ) return False except ISYConnectionError as err: - _LOGGER.error( - "Failed to connect to the ISY, please adjust settings and try again: %s", - err, - ) - raise ConfigEntryNotReady from err + raise ConfigEntryNotReady( + f"Failed to connect to the ISY, please adjust settings and try again: {err}" + ) from err except ISYResponseParseError as err: - _LOGGER.warning( - "Error processing responses from the ISY; device may be busy, trying again later" - ) - raise ConfigEntryNotReady from err + raise ConfigEntryNotReady( + f"Invalid XML response from ISY; Ensure the ISY is running the latest firmware: {err}" + ) from err _categorize_nodes(hass_isy_data, isy.nodes, ignore_identifier, sensor_identifier) _categorize_programs(hass_isy_data, isy.programs) diff --git a/homeassistant/components/mqtt/light/schema_basic.py b/homeassistant/components/mqtt/light/schema_basic.py index 3e347363428..684dcf337aa 100644 --- a/homeassistant/components/mqtt/light/schema_basic.py +++ b/homeassistant/components/mqtt/light/schema_basic.py @@ -186,9 +186,6 @@ async def async_setup_entity_basic( hass, config, async_add_entities, config_entry, discovery_data=None ): """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)]) @@ -236,6 +233,9 @@ class MqttLight(MqttEntity, LightEntity, RestoreEntity): def _setup_from_config(self, config): """(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 = { key: config.get(key) for key in ( diff --git a/homeassistant/components/recollect_waste/manifest.json b/homeassistant/components/recollect_waste/manifest.json index e33edcc2ab5..550612fbea2 100644 --- a/homeassistant/components/recollect_waste/manifest.json +++ b/homeassistant/components/recollect_waste/manifest.json @@ -3,7 +3,7 @@ "name": "ReCollect Waste", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/recollect_waste", - "requirements": ["aiorecollect==1.0.4"], + "requirements": ["aiorecollect==1.0.5"], "codeowners": ["@bachya"], "iot_class": "cloud_polling" } diff --git a/homeassistant/components/recorder/migration.py b/homeassistant/components/recorder/migration.py index 8e6c4861739..02c74635f03 100644 --- a/homeassistant/components/recorder/migration.py +++ b/homeassistant/components/recorder/migration.py @@ -280,10 +280,10 @@ def _update_states_table_with_foreign_key_options(connection, engine): for foreign_key in inspector.get_foreign_keys(TABLE_STATES): if foreign_key["name"] and ( # MySQL/MariaDB will have empty options - not foreign_key["options"] + not foreign_key.get("options") or # Postgres will have ondelete set to None - foreign_key["options"].get("ondelete") is None + foreign_key.get("options", {}).get("ondelete") is None ): alters.append( { @@ -319,7 +319,7 @@ def _drop_foreign_key_constraints(connection, engine, table, columns): for foreign_key in inspector.get_foreign_keys(table): if ( foreign_key["name"] - and foreign_key["options"].get("ondelete") + and foreign_key.get("options", {}).get("ondelete") and foreign_key["constrained_columns"] == columns ): drops.append(ForeignKeyConstraint((), (), name=foreign_key["name"])) diff --git a/homeassistant/components/samsungtv/config_flow.py b/homeassistant/components/samsungtv/config_flow.py index 7c6dea56b96..a69a456df40 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -112,6 +112,7 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def _async_set_unique_id_from_udn(self, raise_on_progress=True): """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) self._async_update_existing_host_entry(self._host) updates = {CONF_HOST: self._host} @@ -206,30 +207,28 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return entry return None - async def _async_start_discovery_for_host(self, host): - """Start discovery for a host.""" - if entry := self._async_update_existing_host_entry(host): + async def _async_start_discovery(self): + """Start discovery.""" + assert self._host is not None + if entry := self._async_update_existing_host_entry(self._host): if entry.unique_id: # Let the flow continue to fill the missing # unique id as we may be able to obtain it # in the next step 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(): - 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") - self._host = host - async def async_step_ssdp(self, discovery_info: DiscoveryInfoType): """Handle a flow initialized by ssdp discovery.""" LOGGER.debug("Samsung device found via SSDP: %s", discovery_info) 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_start_discovery_for_host( - urlparse(discovery_info[ATTR_SSDP_LOCATION]).hostname - ) + await self._async_start_discovery() self._manufacturer = discovery_info[ATTR_UPNP_MANUFACTURER] if not self._manufacturer or not self._manufacturer.lower().startswith( "samsung" @@ -245,7 +244,8 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a flow initialized by dhcp discovery.""" LOGGER.debug("Samsung device found via DHCP: %s", discovery_info) 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() self.context["title_placeholders"] = {"device": self._title} return await self.async_step_confirm() @@ -254,7 +254,8 @@ class SamsungTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a flow initialized by zeroconf discovery.""" LOGGER.debug("Samsung device found via ZEROCONF: %s", discovery_info) 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() self.context["title_placeholders"] = {"device": self._title} return await self.async_step_confirm() diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index 957851dfbee..58598648992 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -764,7 +764,7 @@ class SonosSpeaker: """Pause all current coordinators and restore groups.""" for speaker in (s for s in speakers if s.is_coordinator): if speaker.media.playback_status == SONOS_STATE_PLAYING: - hass.async_create_task(speaker.soco.pause()) + speaker.soco.pause() groups = [] diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index b60d88e9814..660bbb741b0 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -45,6 +45,7 @@ SIGNAL_UPDATE_ENTITY = "tibber_rt_update_{}" RT_SENSOR_MAP = { "averagePower": ["average 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], "maxPower": ["max power", DEVICE_CLASS_POWER, POWER_WATT, None], "accumulatedConsumption": [ diff --git a/homeassistant/components/tod/binary_sensor.py b/homeassistant/components/tod/binary_sensor.py index 4fd9a3b8bf9..8264468e2e7 100644 --- a/homeassistant/components/tod/binary_sensor.py +++ b/homeassistant/components/tod/binary_sensor.py @@ -156,6 +156,26 @@ class TodSensor(BinarySensorEntity): self._time_after += self._after_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): """Call when entity about to be added to Home Assistant.""" self._calculate_boudary_time() @@ -182,7 +202,7 @@ class TodSensor(BinarySensorEntity): if now < self._time_before: self._next_update = self._time_before return - self._calculate_boudary_time() + self._turn_to_next_day() self._next_update = self._time_after @callback diff --git a/homeassistant/const.py b/homeassistant/const.py index 87e53643240..3b1fc4705a8 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -5,7 +5,7 @@ from typing import Final MAJOR_VERSION: Final = 2021 MINOR_VERSION: Final = 6 -PATCH_VERSION: Final = "2" +PATCH_VERSION: Final = "3" __short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__: Final = f"{__short_version__}.{PATCH_VERSION}" REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 8, 0) diff --git a/requirements_all.txt b/requirements_all.txt index 43f477f9682..48e5632b730 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -224,7 +224,7 @@ aiopvpc==2.1.2 aiopylgtv==0.4.0 # homeassistant.components.recollect_waste -aiorecollect==1.0.4 +aiorecollect==1.0.5 # homeassistant.components.shelly aioshelly==0.6.4 @@ -515,7 +515,7 @@ dovado==0.4.1 dsmr_parser==0.29 # homeassistant.components.dwd_weather_warnings -dwdwfsapi==1.0.3 +dwdwfsapi==1.0.4 # homeassistant.components.dweet dweepy==0.3.0 @@ -635,7 +635,7 @@ gTTS==2.2.2 garages-amsterdam==2.1.1 # homeassistant.components.garmin_connect -garminconnect_aio==0.1.1 +garminconnect_aio==0.1.4 # homeassistant.components.geniushub geniushub-client==0.6.30 @@ -1464,7 +1464,7 @@ pyhomematic==0.1.72 pyhomeworks==0.0.6 # homeassistant.components.ialarm -pyialarm==1.7 +pyialarm==1.8.1 # homeassistant.components.icloud pyicloud==0.10.2 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 183124f95f0..b2d64664395 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -146,7 +146,7 @@ aiopvpc==2.1.2 aiopylgtv==0.4.0 # homeassistant.components.recollect_waste -aiorecollect==1.0.4 +aiorecollect==1.0.5 # homeassistant.components.shelly aioshelly==0.6.4 @@ -341,7 +341,7 @@ gTTS==2.2.2 garages-amsterdam==2.1.1 # homeassistant.components.garmin_connect -garminconnect_aio==0.1.1 +garminconnect_aio==0.1.4 # homeassistant.components.geo_json_events # homeassistant.components.usgs_earthquakes_feed @@ -808,7 +808,7 @@ pyhiveapi==0.4.2 pyhomematic==0.1.72 # homeassistant.components.ialarm -pyialarm==1.7 +pyialarm==1.8.1 # homeassistant.components.icloud pyicloud==0.10.2 diff --git a/tests/components/geo_location/test_trigger.py b/tests/components/geo_location/test_trigger.py index e40b134e657..bc74f01f6f1 100644 --- a/tests/components/geo_location/test_trigger.py +++ b/tests/components/geo_location/test_trigger.py @@ -1,4 +1,6 @@ """The tests for the geolocation trigger.""" +import logging + import pytest from homeassistant.components import automation, zone @@ -318,3 +320,46 @@ async def test_if_fires_on_zone_disappear(hass, calls): assert ( 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 + ) diff --git a/tests/components/tod/test_binary_sensor.py b/tests/components/tod/test_binary_sensor.py index 8b63082c36c..ef8088d6aab 100644 --- a/tests/components/tod/test_binary_sensor.py +++ b/tests/components/tod/test_binary_sensor.py @@ -12,6 +12,8 @@ import homeassistant.util.dt as dt_util from tests.common import assert_setup_component +ORIG_TIMEZONE = dt_util.DEFAULT_TIME_ZONE + @pytest.fixture(autouse=True) def mock_legacy_time(legacy_patchable_time): @@ -26,6 +28,13 @@ def setup_fixture(hass): 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): """Test the setup.""" config = { @@ -863,6 +872,7 @@ async def test_sun_offset(hass): async def test_dst(hass): """Test sun event with offset.""" 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) config = { "binary_sensor": [ @@ -882,7 +892,210 @@ async def test_dst(hass): await hass.async_block_till_done() state = hass.states.get(entity_id) - assert state.attributes["after"] == "2019-03-30T03:30:00+01:00" - assert state.attributes["before"] == "2019-03-30T03:40:00+01:00" - assert state.attributes["next_update"] == "2019-03-30T03:30:00+01:00" + assert state.attributes["after"] == "2019-03-31T03:30:00+02:00" + assert state.attributes["before"] == "2019-03-31T03:40:00+02:00" + assert state.attributes["next_update"] == "2019-03-31T03:30:00+02:00" 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"