From 9bec45b63c95d63796c7ca223ec0fee3145b55c6 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Tue, 11 Apr 2023 19:12:20 -0500 Subject: [PATCH] Squashed commit of the following: commit 1edaf1d7ed2a47fd326ef3163428e3c035322231 Merge: 0e7e06e a888afd Author: Benjamin Auquite Date: Mon Apr 10 12:57:30 2023 -0500 Merge branch 'main' into unconventional_sunrise_sunset_times commit 0e7e06e8a46158d078e2b15742af1de9dec2be3e Merge: 8fa554a fe7bdd3 Author: Benjamin Auquite Date: Sat Apr 8 20:23:20 2023 -0500 Merge branch 'main' into unconventional_sunrise_sunset_times commit 8fa554a4e7610fb462a49ade1a1f5a642ae5e090 Merge: ec64eda 39e9d0e Author: Benjamin Auquite Date: Sat Apr 8 00:50:16 2023 -0500 merge commit ec64eda6bff4c1887bc79c8cdb46d78685a45f90 Merge: 8353db5 744e43f Author: Benjamin Auquite Date: Tue Apr 4 21:08:54 2023 -0500 Merge branch 'main' into unconventional_sunrise_sunset_times commit 8353db54cc1f0a64c481bbc6430b5cd3185dece4 Author: Benjamin Auquite Date: Tue Apr 4 00:56:24 2023 -0500 tests are unfinished commit 6cb24116c01a3ede391168d1c3e1aa3782667c88 Author: Benjamin Auquite Date: Mon Apr 3 21:17:01 2023 -0500 Update switch.py --- .../workflows/install_dependencies/action.yml | 3 +- custom_components/adaptive_lighting/switch.py | 20 ++- tests/test_switch.py | 154 ++++++++++++++++++ 3 files changed, 172 insertions(+), 5 deletions(-) diff --git a/.github/workflows/install_dependencies/action.yml b/.github/workflows/install_dependencies/action.yml index df0bee8a..a092a8a0 100644 --- a/.github/workflows/install_dependencies/action.yml +++ b/.github/workflows/install_dependencies/action.yml @@ -34,9 +34,10 @@ runs: - name: Install dependencies shell: bash run: | - echo "::warning::### WARNING! Deprecation warnings muted with option '--use-pep517' please address this at some point in pytest.yaml. ###" + echo "::warning::### WARNING! Deprecation warnings muted with option '--use-pep517' please address this at some point in '\workflows\install_dependencies\action.yml'. ###" pip install -r core/requirements.txt --use-pep517 pip install -r core/requirements_test.txt --use-pep517 pip install -e core/ --use-pep517 pip install ulid-transform # this is in Adaptive-lighting's manifest.json pip install $(python test_dependencies.py) --use-pep517 + pip install jaraco --use-pep517 diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index 749dd7e0..d952b3c3 100644 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -1682,15 +1682,27 @@ class SunLightSettings: def calculate_noon_and_midnight( sunset: datetime.datetime, sunrise: datetime.datetime ) -> tuple[datetime.datetime, datetime.datetime]: - middle = abs(sunset - sunrise) / 2 + total = abs(sunset - sunrise) + middle = total / 2 + total = ( + total.total_seconds() / 60 / 60 * (2 / 3) + ) # about 12 hours normally. + _LOGGER.debug( + "Calculate noon/midnight. Total diff: %s, middle: %s", total, middle + ) if sunset > sunrise: noon = sunrise + middle - midnight = noon + timedelta(hours=12) * (1 if noon.hour < 12 else -1) + midnight = noon + timedelta(hours=total) * ( + 1 if noon.hour < total else -1 + ) else: midnight = sunset + middle - noon = midnight + timedelta(hours=12) * ( - 1 if midnight.hour < 12 else -1 + noon = midnight + timedelta(hours=total) * ( + 1 if midnight.hour < total else -1 ) + _LOGGER.debug( + "Calculate noon/midnight. Noon: %s Midnight: %s", noon, midnight + ) return noon, midnight location = self.astral_location diff --git a/tests/test_switch.py b/tests/test_switch.py index f6083bc3..5303f9cb 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -501,6 +501,160 @@ async def test_light_settings(hass): assert_expected_color_temp(state) +async def test_unconventional_sun_events(hass): + """Test unconventional sunrise/noon/sunset/midnight times.""" + + async def change_switch_settings(switch_entity_id, service_data): + await hass.services.async_call( + DOMAIN, + SERVICE_CHANGE_SWITCH_SETTINGS, + { + ATTR_ENTITY_ID: switch_entity_id, + **service_data, + }, + blocking=True, + ) + await hass.async_block_till_done() + + switch, _ = await setup_lights_and_switch(hass) + + # Set config options for the test. + # change_switch_settings( + # switch.unique_id, + # { + # "sunrise_time": "03:00:00", + # "sunset_time": "05:00:00", + # "transition_until_sleep": True, + # }, + # ) + + lights = switch._lights + + # Turn on "sleep mode" + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: ENTITY_SLEEP_MODE_SWITCH}, + blocking=True, + ) + await hass.async_block_till_done() + light_states = [hass.states.get(light) for light in lights] + for state in light_states: + assert state.attributes[ATTR_BRIGHTNESS] == round( + 255 * switch._settings[ATTR_BRIGHTNESS_PCT] / 100 + ) + last_service_data = switch.turn_on_off_listener.last_service_data[ + state.entity_id + ] + assert state.attributes[ATTR_BRIGHTNESS] == last_service_data[ATTR_BRIGHTNESS] + assert ( + state.attributes[ATTR_COLOR_TEMP_KELVIN] + == last_service_data[ATTR_COLOR_TEMP_KELVIN] + ) + + # Turn off "sleep mode" + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: ENTITY_SLEEP_MODE_SWITCH}, + blocking=True, + ) + await hass.async_block_till_done() + + # Test with different times + + context = switch.create_context("test") # needs to be passed to update method + + # Test with different times + sun_events = ( + switch._sun_light_settings.get_sun_events( # pylint: disable=protected-access + dt_util.utcnow() + ) + ) + + # get our custom sun events. + sunrise = sun_events[0][1] + noon = sun_events[1][1] + sunset = sun_events[2][1] + midnight = sun_events[3][1] + assert sunrise < noon + assert noon < sunset + assert sunset < midnight + + total = sunset - sunrise + # quarter = total / 4 + + before_sunset = datetime.datetime.fromtimestamp(sunset - (total / 12)) + after_sunset = datetime.datetime.fromtimestamp(sunset + (total / 12)) + before_sunrise = datetime.datetime.fromtimestamp(sunrise - (total / 12)) + after_sunrise = datetime.datetime.fromtimestamp(sunrise + (total / 12)) + + test_date = datetime.datetime.strptime("10/17/2020", "%m/%d/%Y").date() + sunrise_time = ( + switch._sun_light_settings.sunrise_time + ) # pylint: disable=protected-access + sunset_time = ( + switch._sun_light_settings.sunrise_time + ) # pylint: disable=protected-access + sunrise_dt = datetime.datetime.combine(test_date, sunrise_time) + sunset_dt = datetime.datetime.combine(test_date, sunset_time) + + async def patch_time_and_get_updated_states(time): + with patch("homeassistant.util.dt.utcnow", return_value=time): + await switch._update_attrs_and_maybe_adapt_lights( + transition=0, context=context, force=True + ) + await hass.async_block_till_done() + return [hass.states.get(light) for light in lights] + + def assert_expected_color_temp(state): + last_service_data = switch.turn_on_off_listener.last_service_data[ + state.entity_id + ] + assert ( + state.attributes[ATTR_COLOR_TEMP_KELVIN] + == last_service_data[ATTR_COLOR_TEMP_KELVIN] + ) + + # At sunset the brightness should be max and color_temp at the smallest value + light_states = await patch_time_and_get_updated_states(sunset_dt) + for state in light_states: + assert state.attributes[ATTR_BRIGHTNESS] == 255 + assert_expected_color_temp(state) + + # One hour before sunset the brightness should be max and color_temp + # not at the smallest value yet. + light_states = await patch_time_and_get_updated_states(before_sunset) + for state in light_states: + assert state.attributes[ATTR_BRIGHTNESS] == 255 + assert_expected_color_temp(state) + + # One hour after sunset the brightness should be down + light_states = await patch_time_and_get_updated_states(after_sunset) + for state in light_states: + assert state.attributes[ATTR_BRIGHTNESS] < 255 + assert_expected_color_temp(state) + + # At sunrise the brightness should be max and color_temp at the smallest value + light_states = await patch_time_and_get_updated_states(sunrise_dt) + for state in light_states: + assert state.attributes[ATTR_BRIGHTNESS] == 255 + assert_expected_color_temp(state) + + # One hour before sunrise the brightness should smaller than max + # and color_temp at the min value. + light_states = await patch_time_and_get_updated_states(before_sunrise) + for state in light_states: + assert state.attributes[ATTR_BRIGHTNESS] < 255 + assert_expected_color_temp(state) + + # One hour after sunrise the brightness should be up + light_states = await patch_time_and_get_updated_states(after_sunrise) + for state in light_states: + assert state.attributes[ATTR_BRIGHTNESS] == 255 + assert_expected_color_temp(state) + + @pytest.mark.dependency(depends=GLOBAL_TEST_DEPENDENCIES) async def test_turn_on_off_listener_not_tracking_untracked_lights(hass): """Test that lights that are not in a Adaptive Lighting switch aren't tracked."""