From cd6d98f7344c946ebd8f155d90aa6bc8cef15333 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Mon, 3 Apr 2023 21:17:01 -0500 Subject: [PATCH 1/5] Update switch.py --- .../adaptive_lighting/color_and_brightness.py | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/custom_components/adaptive_lighting/color_and_brightness.py b/custom_components/adaptive_lighting/color_and_brightness.py index 9441e9e6..e9b95b38 100644 --- a/custom_components/adaptive_lighting/color_and_brightness.py +++ b/custom_components/adaptive_lighting/color_and_brightness.py @@ -121,13 +121,27 @@ def noon_and_midnight( if sunrise is None: sunrise = self.sunrise(dt) - 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 def sun_events(self, dt: datetime.datetime) -> list[tuple[str, float]]: From f859de559c49aabe868a3d1d8f7afdec792e2490 Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Tue, 4 Apr 2023 00:56:24 -0500 Subject: [PATCH 2/5] tests are unfinished --- .../workflows/install_dependencies/action.yml | 3 +- tests/test_switch.py | 309 ++++++++++++++++++ 2 files changed, 311 insertions(+), 1 deletion(-) diff --git a/.github/workflows/install_dependencies/action.yml b/.github/workflows/install_dependencies/action.yml index 3041698b..019e499f 100644 --- a/.github/workflows/install_dependencies/action.yml +++ b/.github/workflows/install_dependencies/action.yml @@ -34,7 +34,7 @@ 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 # because they decided to pull codecov the package from PyPI... sed -i '/codecov/d' core/requirements_test.txt @@ -42,3 +42,4 @@ runs: 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/tests/test_switch.py b/tests/test_switch.py index 018c8965..9238b29c 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -550,6 +550,315 @@ def assert_expected_color_temp(state): 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( + "custom_components.adaptive_lighting.color_and_brightness.utcnow", + return_value=time, + ): + await switch._update_attrs_and_maybe_adapt_lights( + context=context, transition=0, 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.manager.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) + + +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) + + async def test_manager_not_tracking_untracked_lights(hass): """Test that lights that are not in a Adaptive Lighting switch aren't tracked.""" switch, _ = await setup_lights_and_switch(hass) From da5c6470a003881de627fdacf9e80e1e73dc862c Mon Sep 17 00:00:00 2001 From: Benjamin Auquite Date: Tue, 11 Apr 2023 19:37:36 -0500 Subject: [PATCH 3/5] didn't mean to include this --- .github/workflows/install_dependencies/action.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/install_dependencies/action.yml b/.github/workflows/install_dependencies/action.yml index 019e499f..c87c69b2 100644 --- a/.github/workflows/install_dependencies/action.yml +++ b/.github/workflows/install_dependencies/action.yml @@ -42,4 +42,3 @@ runs: 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 From 8f0cef3abd19a45008b804bbc3ff2cabfe0f7b16 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 3 Nov 2023 08:44:13 +0000 Subject: [PATCH 4/5] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../adaptive_lighting/color_and_brightness.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/custom_components/adaptive_lighting/color_and_brightness.py b/custom_components/adaptive_lighting/color_and_brightness.py index e9b95b38..a9293f70 100644 --- a/custom_components/adaptive_lighting/color_and_brightness.py +++ b/custom_components/adaptive_lighting/color_and_brightness.py @@ -123,24 +123,24 @@ def noon_and_midnight( total = abs(sunset - sunrise) middle = total / 2 - total = ( - total.total_seconds() / 60 / 60 * (2 / 3) - ) # about 12 hours normally. + total = total.total_seconds() / 60 / 60 * (2 / 3) # about 12 hours normally. _LOGGER.debug( - "Calculate noon/midnight. Total diff: %s, middle: %s", total, middle + "Calculate noon/midnight. Total diff: %s, middle: %s", + total, + middle, ) if sunset > sunrise: noon = sunrise + middle - midnight = noon + timedelta(hours=total) * ( - 1 if noon.hour < total else -1 - ) + midnight = noon + timedelta(hours=total) * (1 if noon.hour < total else -1) else: midnight = sunset + middle noon = midnight + timedelta(hours=total) * ( 1 if midnight.hour < total else -1 ) _LOGGER.debug( - "Calculate noon/midnight. Noon: %s Midnight: %s", noon, midnight + "Calculate noon/midnight. Noon: %s Midnight: %s", + noon, + midnight, ) return noon, midnight From 8ca8af6d0c239d0f402b53a5aca62a0bf4813834 Mon Sep 17 00:00:00 2001 From: Bas Nijholt Date: Fri, 3 Nov 2023 16:02:36 -0700 Subject: [PATCH 5/5] Rename _lights to lights --- tests/test_switch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_switch.py b/tests/test_switch.py index 9238b29c..15ff27a1 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -577,7 +577,7 @@ async def change_switch_settings(switch_entity_id, service_data): # }, # ) - lights = switch._lights + lights = switch.lights # Turn on "sleep mode" await hass.services.async_call( @@ -732,7 +732,7 @@ async def change_switch_settings(switch_entity_id, service_data): # }, # ) - lights = switch._lights + lights = switch.lights # Turn on "sleep mode" await hass.services.async_call(