diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 00000000..c5bcb3a4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,17 @@ +--- +name: 'Bug Report' +about: 'Report a bug in adaptive-lighting.' +labels: kind/bug, need/triage +--- + +#### Version information: + + +#### Description: + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..c4eeda14 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false +contact_links: + - name: Getting Help on adaptive-lighting + url: https://github.com/basnijholt/adaptive-lighting/discussions/categories/q-a + about: Q&A section of the discussion tab + - name: Share your idea + url: https://github.com/basnijholt/adaptive-lighting/discussions/categories/ideas + about: And discuss it with the community + - name: General discussions about this component + url: https://github.com/basnijholt/adaptive-lighting/discussions/categories/general + about: General discussions about this component + - name: Share your setup with adaptive-lighting + url: https://github.com/basnijholt/adaptive-lighting/discussions/categories/show-and-tell + about: Or see what other people do with this component diff --git a/.github/ISSUE_TEMPLATE/doc.md b/.github/ISSUE_TEMPLATE/doc.md new file mode 100644 index 00000000..98c9a008 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/doc.md @@ -0,0 +1,13 @@ +--- +name: 'Documentation Issue' +about: 'Report missing, erroneous docs, broken links or propose new docs' +labels: kind/docs_issue, need/triage +--- + +#### Location + + + +#### Description + + diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md new file mode 100644 index 00000000..fcd16fc6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -0,0 +1,6 @@ +--- +name: 'Enhancement' +about: 'Suggest an improvement to an existing feature.' +labels: kind/enhancement, need/triage +--- + diff --git a/.github/ISSUE_TEMPLATE/feature.md b/.github/ISSUE_TEMPLATE/feature.md new file mode 100644 index 00000000..c4b787df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.md @@ -0,0 +1,5 @@ +--- +name: 'Feature' +about: 'Suggest a new feature' +labels: kind/feature, need/triage +--- diff --git a/.github/auto-comment.yml b/.github/auto-comment.yml new file mode 100644 index 00000000..c1272240 --- /dev/null +++ b/.github/auto-comment.yml @@ -0,0 +1,6 @@ +# Comment to a new issue. +# Disabled +# issueOpened: "" + +# Disabled +# pullRequestOpened: "" diff --git a/.github/config.yml b/.github/config.yml new file mode 100644 index 00000000..915efced --- /dev/null +++ b/.github/config.yml @@ -0,0 +1,64 @@ +# Configuration for welcome - https://github.com/behaviorbot/welcome + +# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome +# Comment to be posted to on first time issues +newIssueWelcomeComment: > + Thank you for submitting your first issue to this repository! A maintainer + will be here shortly to triage and review. + + In the meantime, please double-check that you have provided all the + necessary information to make this process easy! Any information that can + help save additional round trips is useful! We currently aim to give + initial feedback within **two business days**. If this does not happen, feel + free to leave a comment. + + Please keep an eye on how this issue will be labeled, as labels give an + overview of priorities, assignments and additional actions requested by the + maintainers: + + - "Priority" labels will show how urgent this is for the team. + - "Status" labels will show if this is ready to be worked on, blocked, or in progress. + - "Need" labels will indicate if additional input or analysis is required. + + Finally, remember to use [the discussion tab](https://github.com/basnijholt/adaptive-lighting/discussions) if you just need general + support. + +# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome +# Comment to be posted to on PRs from first time contributors in your repository +newPRWelcomeComment: > + Thank you for submitting this PR! + + A maintainer will be here shortly to review it. + + We are super grateful! Help us by making sure that: + + * The context for this PR is clear, with relevant discussion, decisions + and stakeholders linked/mentioned. + + * Your contribution itself is clear (code comments, self-review for the + rest) and in its best form. + + Getting other community members to do a review would be great help too on + complex PRs. If you are unsure about something, just leave us a comment. + + Next steps: + + * A maintainer will triage and assign priority to this PR, commenting on + any missing things and potentially assigning a reviewer for high + priority items. + + * The PR gets reviews, discussed and approvals as needed. + + * The PR is merged by maintainers when it has been approved and comments addressed. + + We currently aim to provide initial feedback/triaging within **two business + days**. Please keep an eye on any labelling actions, as these will indicate + priorities and status of your contribution. + + We are very grateful for your contribution! + + +# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge +# Comment to be posted to on pull requests merged by a first time user +# Currently disabled +#firstPRMergeComment: "" diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/hassfest.yaml new file mode 100644 index 00000000..18c7d193 --- /dev/null +++ b/.github/workflows/hassfest.yaml @@ -0,0 +1,14 @@ +name: Validate with hassfest + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + +jobs: + validate: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v2" + - uses: home-assistant/actions/hassfest@master diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 00000000..fc1b5f91 --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,17 @@ +name: Validate + +on: + push: + pull_request: + schedule: + - cron: "0 0 * * *" + +jobs: + validate: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@v2" + - name: HACS validation + uses: "hacs/action@main" + with: + category: "integration" diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..b6e47617 --- /dev/null +++ b/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/README.md b/README.md index 4df681b1..d0dadacc 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ +[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs) +![Version](https://img.shields.io/github/v/release/basnijholt/adaptive-lighting) + # Adaptive Lighting component for Home Assistant ![](https://github.com/home-assistant/brands/raw/b4a168b9af282ef916e120d31091ecd5e3c35e66/core_integrations/adaptive_lighting/icon.png) _Try out this code by adding https://github.com/basnijholt/adaptive-lighting to your custom repos in [HACS (Home Assistant Community Store)](https://hacs.xyz/) and install it!_ -*This `custom_component` is also being added to `core`, see [this PR](https://github.com/home-assistant/core/pull/40626), although it might take months before it makes it in.* + The `adaptive_lighting` platform changes the settings of your lights throughout the day. It uses the position of the sun to calculate the color temperature and brightness that is most fitting for that time of the day. @@ -49,7 +52,8 @@ adaptive_lighting: | name | The name to use when displaying this switch. | False | default | string | | lights | List of light entities for Adaptive Lighting to control (may be empty). | False | list | [] | | prefer_rgb_color | Whether to use RGB color adjustment instead of native light color temperature. | False | False | boolean | -| initial_transition | How long the first transition is when the lights go from `off` to `on` (or when "sleep mode" is toggled). | False | 1 | time | +| initial_transition | How long the first transition is when the lights go from `off` to `on`. | False | 1 | time | +| sleep_transition | How long the transition is when when "sleep mode" is toggled | False | 1 | time | | transition | How long the transition is when the lights change, in seconds. | False | 45 | integer | | interval | How often to adapt the lights, in seconds. | False | 90 | integer | | min_brightness | The minimum percent of brightness to set the lights to. | False | 1 | integer | @@ -113,8 +117,8 @@ adaptive_lighting: | Service data attribute | Optional | Description | |------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------| | `entity_id` | no | The `entity_id` of the switch in which to (un)mark the light as being "manually controlled". | -| `lights` | no | A light (or list of lights) to apply the settings to. | -| `manual_control` | yes | Whether to mark (true) or unmark (false) the light as "manually controlled", when not specified it selects all lights in the switch. | +| `lights` | yes | entity_id(s) of lights, if not specified, all lights in the switch are selected. | +| `manual_control` | yes | Whether to add ('true') or remove ('false') the light from the 'manual_control' list, default: true | ## Automation examples @@ -186,3 +190,8 @@ These graphs were generated using the values calculated by the Adaptive Lighting ##### Brightness: ![cl_brightness|690x130](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/5/8/58ebd994b62a8b1abfb3497a5288d923ff4e2330.PNG) + +# Maintainers + +- @basnijholt +- @RubenKelevra diff --git a/custom_components/adaptive_lighting/const.py b/custom_components/adaptive_lighting/const.py index 03f3bf0e..2f53377d 100644 --- a/custom_components/adaptive_lighting/const.py +++ b/custom_components/adaptive_lighting/const.py @@ -17,6 +17,7 @@ CONF_DETECT_NON_HA_CHANGES, DEFAULT_DETECT_NON_HA_CHANGES = ( False, ) CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION = "initial_transition", 1 +CONF_SLEEP_TRANSITION, DEFAULT_SLEEP_TRANSITION = "sleep_transition", 1 CONF_INTERVAL, DEFAULT_INTERVAL = "interval", 90 CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS = "max_brightness", 100 CONF_MAX_COLOR_TEMP, DEFAULT_MAX_COLOR_TEMP = "max_color_temp", 5500 @@ -77,6 +78,7 @@ VALIDATION_TUPLES = [ (CONF_LIGHTS, DEFAULT_LIGHTS, cv.entity_ids), (CONF_PREFER_RGB_COLOR, DEFAULT_PREFER_RGB_COLOR, bool), (CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION, VALID_TRANSITION), + (CONF_SLEEP_TRANSITION, DEFAULT_SLEEP_TRANSITION, VALID_TRANSITION), (CONF_TRANSITION, DEFAULT_TRANSITION, VALID_TRANSITION), (CONF_INTERVAL, DEFAULT_INTERVAL, cv.positive_int), (CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS, int_between(1, 100)), diff --git a/custom_components/adaptive_lighting/manifest.json b/custom_components/adaptive_lighting/manifest.json index ee4c828b..d4f9a091 100644 --- a/custom_components/adaptive_lighting/manifest.json +++ b/custom_components/adaptive_lighting/manifest.json @@ -1,10 +1,12 @@ { "domain": "adaptive_lighting", "name": "Adaptive Lighting", - "documentation": "https://www.home-assistant.io/integrations/adaptive_lighting", + "documentation": "https://github.com/basnijholt/adaptive-lighting#readme", + "issue_tracker": "https://github.com/basnijholt/adaptive-lighting/issues", "config_flow": true, "dependencies": [], - "codeowners": ["@basnijholt"], - "version": "1.0.0", - "requirements": [] + "codeowners": ["@basnijholt", "@RubenKelevra"], + "version": "1.0.14", + "requirements": [], + "iot_class": "calculated" } diff --git a/custom_components/adaptive_lighting/services.yaml b/custom_components/adaptive_lighting/services.yaml index 10bfd2a8..8f449a77 100755 --- a/custom_components/adaptive_lighting/services.yaml +++ b/custom_components/adaptive_lighting/services.yaml @@ -5,7 +5,7 @@ apply: description: entity_id of the Adaptive Lighting switch. example: switch.adaptive_lighting_default lights: - description: entity_id(s) of lights. + description: "entity_id(s) of lights, default: lights of the switch" example: light.bedroom_ceiling transition: description: Transition of the lights. diff --git a/custom_components/adaptive_lighting/strings.json b/custom_components/adaptive_lighting/strings.json index 274569da..72fdcb3b 100644 --- a/custom_components/adaptive_lighting/strings.json +++ b/custom_components/adaptive_lighting/strings.json @@ -21,7 +21,8 @@ "description": "All settings for a Adaptive Lighting component. The option names correspond with the YAML settings. No options are shown if you have this entry defined in YAML.", "data": { "lights": "lights", - "initial_transition": "initial_transition, when lights go 'off' to 'on' or when 'sleep_state' changes", + "initial_transition": "initial_transition, when lights go 'off' to 'on'", + "sleep_transition": "sleep_transition, when 'sleep_state' changes", "interval": "interval, time between switch updates in seconds", "max_brightness": "max_brightness, in %", "max_color_temp": "max_color_temp, in Kelvin", diff --git a/custom_components/adaptive_lighting/switch.py b/custom_components/adaptive_lighting/switch.py index e0470b3c..3822803b 100755 --- a/custom_components/adaptive_lighting/switch.py +++ b/custom_components/adaptive_lighting/switch.py @@ -114,6 +114,7 @@ from .const import ( CONF_ACCENT_XY, CONF_DETECT_NON_HA_CHANGES, CONF_INITIAL_TRANSITION, + CONF_SLEEP_TRANSITION, CONF_INTERVAL, CONF_LIGHTS, CONF_MANUAL_CONTROL, @@ -194,12 +195,17 @@ def _short_hash(string: str, length: int = 4) -> str: return hashlib.sha1(string.encode("UTF-8")).hexdigest()[:length] -def create_context(name: str, which: str, index: int) -> Context: +def create_context( + name: str, which: str, index: int, parent: Optional[Context] = None +) -> Context: """Create a context that can identify this integration.""" # Use a hash for the name because otherwise the context might become # too long (max len == 36) to fit in the database. name_hash = _short_hash(name) - return Context(id=f"{_DOMAIN_SHORT}_{name_hash}_{which}_{index}") + parent_id = parent.id if parent else None + return Context( + id=f"{_DOMAIN_SHORT}_{name_hash}_{which}_{index}", parent_id=parent_id + ) def is_our_context(context: Optional[Context]) -> bool: @@ -233,9 +239,15 @@ async def handle_apply(switch: AdaptiveSwitch, service_call: ServiceCall): """Handle the entity service apply.""" hass = switch.hass data = service_call.data - all_lights = _expand_light_groups(hass, data[CONF_LIGHTS]) + all_lights = data[CONF_LIGHTS] + if not all_lights: + all_lights = switch._lights + all_lights = _expand_light_groups(hass, all_lights) switch.turn_on_off_listener.lights.update(all_lights) - + _LOGGER.debug( + "Called 'adaptive_lighting.apply' service with '%s'", + data, + ) for light in all_lights: if data[CONF_TURN_ON_LIGHTS] or is_on(hass, light): await switch._adapt_light( # pylint: disable=protected-access @@ -246,6 +258,7 @@ async def handle_apply(switch: AdaptiveSwitch, service_call: ServiceCall): data[ATTR_USE_ACCENT_COLOR], data[CONF_PREFER_RGB_COLOR], force=True, + context=switch.create_context("service", parent=service_call.context), ) async def handle_set_accent_color(switch: AdaptiveSwitch, service_call: ServiceCall): @@ -290,7 +303,7 @@ async def handle_set_manual_control(switch: AdaptiveSwitch, service_call: Servic all_lights, transition=switch._initial_transition, force=True, - context=switch.create_context("service"), + context=switch.create_context("service", parent=service_call.context), ) @@ -354,7 +367,7 @@ async def async_setup_entry( platform.async_register_entity_service( SERVICE_APPLY, { - vol.Required(CONF_LIGHTS): cv.entity_ids, + vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids, # pylint: disable=protected-access vol.Optional( CONF_TRANSITION, default=switch._initial_transition, # pylint: disable=protected-access @@ -626,14 +639,13 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): self._detect_non_ha_changes = data[CONF_DETECT_NON_HA_CHANGES] self._initial_transition = data[CONF_INITIAL_TRANSITION] + self._sleep_transition = data[CONF_SLEEP_TRANSITION] self._interval = data[CONF_INTERVAL] self._only_once = data[CONF_ONLY_ONCE] self._prefer_rgb_color = data[CONF_PREFER_RGB_COLOR] self._separate_turn_on_commands = data[CONF_SEPARATE_TURN_ON_COMMANDS] self._take_over_control = data[CONF_TAKE_OVER_CONTROL] - self._transition = min( - data[CONF_TRANSITION], self._interval.total_seconds() // 2 - ) + self._transition = data[CONF_TRANSITION] _loc = get_astral_location(self.hass) if isinstance(_loc, tuple): # Astral v2.2 @@ -659,6 +671,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): accent_color=data[CONF_ACCENT_COLOR], accent_color_lower_bound=float(data[CONF_ACCENT_COLOR_MIX_MIN])/100.0, accent_color_upper_bound=float(data[CONF_ACCENT_COLOR_MIX_MAX])/100.0, + transition=data[CONF_TRANSITION], ) self._sun_light_settings.set_accent_color(data[CONF_ACCENT_COLOR]) @@ -787,7 +800,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): return self._icon @property - def device_state_attributes(self) -> Dict[str, Any]: + def extra_state_attributes(self) -> Dict[str, Any]: """Return the attributes of the switch.""" if not self.is_on: return {key: None for key in self._settings} @@ -798,7 +811,9 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): ] return dict(self._settings, manual_control=manual_control) - def create_context(self, which: str = "default") -> Context: + def create_context( + self, which: str = "default", parent: Optional[Context] = None + ) -> Context: """Create a context that identifies this Adaptive Lighting instance.""" # Right now the highest number of each context_id it can create is # 'adapt_lgt_XXXX_turn_on_9999999999999' @@ -808,7 +823,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): # 'adapt_lgt_XXXX_light_event_999999999' # 'adapt_lgt_XXXX_service_9999999999999' # So 100 million calls before we run into the 36 chars limit. - context = create_context(self._name, which, self._context_cnt) + context = create_context(self._name, which, self._context_cnt, parent=parent) self._context_cnt += 1 return context @@ -841,7 +856,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): async def _async_update_at_interval(self, now=None) -> None: await self._update_attrs_and_maybe_adapt_lights( - force=False, context=self.create_context("interval") + transition=self._transition, force=False, context=self.create_context("interval") ) async def _adapt_light( @@ -957,7 +972,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): ) assert self.is_on self._settings = self._sun_light_settings.get_settings( - self.sleep_mode_switch.is_on, self.accent_color_switch.is_on + self.sleep_mode_switch.is_on, self.accent_color_switch.is_on, transition ) self.async_write_ha_state() if lights is None: @@ -1013,9 +1028,9 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): # Reset the manually controlled status when the "sleep mode" changes self.turn_on_off_listener.reset(*self._lights) await self._update_attrs_and_maybe_adapt_lights( - transition=self._initial_transition, + transition=self._sleep_transition, force=True, - context=self.create_context("sleep"), + context=self.create_context("sleep", parent=event.context), ) async def _accent_color_switch_state_event(self, event: Event) -> None: @@ -1070,7 +1085,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity): lights=[entity_id], transition=self._initial_transition, force=True, - context=self.create_context("light_event"), + context=self.create_context("light_event", parent=event.context), ) elif ( old_state is not None @@ -1157,6 +1172,7 @@ class SunLightSettings: sunset_offset: Optional[datetime.timedelta] sunset_time: Optional[datetime.time] time_zone: datetime.tzinfo + transition: int accent_color: Optional[Tuple[float, float, float]] accent_color_lower_bound: Optional[float] accent_color_upper_bound: Optional[float] @@ -1189,6 +1205,18 @@ class SunLightSettings: utc_time = date_time.replace(tzinfo=dt_util.DEFAULT_TIME_ZONE).astimezone(dt_util.UTC) return utc_time + def calculate_noon_and_midnight( + sunset: datetime.datetime, sunrise: datetime.datetime + ) -> Tuple[datetime.datetime, datetime.datetime]: + middle = abs(sunset - sunrise) / 2 + if sunset > sunrise: + noon = sunrise + middle + midnight = noon + timedelta(hours=12) * (1 if noon.hour < 12 else -1) + else: + midnight = sunset + middle + noon = midnight + timedelta(hours=12) * (1 if midnight.hour < 12 else -1) + return noon, midnight + location = self.astral_location sunrise = ( @@ -1212,8 +1240,7 @@ class SunLightSettings: solar_noon = location.noon(date, local=False) solar_midnight = location.midnight(date, local=False) else: - solar_noon = sunrise + (sunset - sunrise) / 2 - solar_midnight = sunset + ((sunrise + timedelta(days=1)) - sunset) / 2 + (solar_noon, solar_midnight) = calculate_noon_and_midnight(sunset, sunrise) events = [ (SUN_EVENT_SUNRISE, sunrise.timestamp()), @@ -1246,11 +1273,13 @@ class SunLightSettings: i_now = bisect.bisect([ts for _, ts in events], now.timestamp()) return events[i_now - 1 : i_now + 1] - def calc_percent(self) -> float: + def calc_percent(self, transition: int) -> float: """Calculate the position of the sun in %.""" now = dt_util.utcnow() - now_ts = now.timestamp() - today = self.relevant_events(now) + + target_time = now + timedelta(seconds=transition) + target_ts = target_time.timestamp() + today = self.relevant_events(target_time) (_, prev_ts), (next_event, next_ts) = today h, x = ( # pylint: disable=invalid-name (prev_ts, next_ts) @@ -1258,7 +1287,7 @@ class SunLightSettings: else (next_ts, prev_ts) ) k = 1 if next_event in (SUN_EVENT_SUNSET, SUN_EVENT_NOON) else -1 - percentage = (0 - k) * ((now_ts - h) / (h - x)) ** 2 + k + percentage = (0 - k) * ((target_ts - h) / (h - x)) ** 2 + k return percentage def calc_brightness_pct(self, percent: float, is_sleep: bool) -> float: @@ -1315,13 +1344,13 @@ class SunLightSettings: return self.accent_color def get_settings( - self, is_sleep, use_accent + self, is_sleep, use_accent, transition ) -> Dict[str, Union[float, Tuple[float, float], Tuple[float, float, float]]]: """Get all light settings. Calculating all values takes <0.5ms. """ - percent = self.calc_percent() + percent = self.calc_percent(transition) if transition is not None else self.calc_percent(0) brightness_pct = self.calc_brightness_pct(percent, is_sleep) color_temp_kelvin = self.calc_color_temp_kelvin(percent, is_sleep) color_temp_mired: float = color_temperature_kelvin_to_mired(color_temp_kelvin) diff --git a/custom_components/adaptive_lighting/translations/en.json b/custom_components/adaptive_lighting/translations/en.json index e66b6769..cc32a722 100644 --- a/custom_components/adaptive_lighting/translations/en.json +++ b/custom_components/adaptive_lighting/translations/en.json @@ -21,12 +21,13 @@ "description": "All settings for a Adaptive Lighting component. The option names correspond with the YAML settings. No options are shown if you have the adaptive_lighting entry defined in your YAML configuration.", "data": { "lights": "lights", - "initial_transition": "initial_transition: When lights turn 'off' to 'on' or when 'sleep_state' changes. (seconds)", + "initial_transition": "initial_transition: When lights turn 'off' to 'on'. (seconds)", + "sleep_transition": "sleep_transition: When 'sleep_state' changes. (seconds)", "interval": "interval: Time between switch updates. (seconds)", "max_brightness": "max_brightness: Highest brightness of lights during a cycle. (%)", "max_color_temp": "max_color_temp: Coldest hue of the color temperature cycle. (Kelvin)", "min_brightness": "min_brightness: Lowest brightness of lights during a cycle. (%)", - "min_color_temp": "min_color_temp, Warmest hue of the color temperature cycle. (%)", + "min_color_temp": "min_color_temp, Warmest hue of the color temperature cycle. (Kelvin)", "only_once": "only_once: Only adapt the lights when turning them on.", "prefer_rgb_color": "prefer_rgb_color: Use 'rgb_color' rather than 'color_temp' when possible.", "separate_turn_on_commands": "separate_turn_on_commands: Separate the commands for each attribute (color, brightness, etc.) in 'light.turn_on' (required for some lights).", diff --git a/custom_components/adaptive_lighting/translations/nb.json b/custom_components/adaptive_lighting/translations/nb.json new file mode 100644 index 00000000..2abeae9b --- /dev/null +++ b/custom_components/adaptive_lighting/translations/nb.json @@ -0,0 +1,49 @@ +{ + "title":"Adaptiv Belysning", + "config":{ + "step":{ + "user":{ + "title":"Velg et navn", + "description":"Velg et navn for denne konfigurasjonen for adaptiv belysning - hver konfigurasjon kan inneholde flere lyskilder!", + "data":{ + "name":"Navn" + } + } + }, + "abort":{ + "already_configured":"Denne enheten er allerede konfigurert!" + } + }, + "options":{ + "step":{ + "init":{ + "title":"Adaptiv Belysning Innstillinger", + "description":"Alle innstillinger for en adaptiv belysning konfigurasjon. Innstillingene er identiske med innstillingene for YAML konfigurasjon. Ingen innstillinger vises dersom du har definert adaptive_lighting i din YAML konfigurasjon.", + "data":{ + "lights":"Lys / Lyskilder", + "initial_transition":"'initial_transition': overgangen (i sekunder) når lysene skrus av eller på - eller når 'sleep_state' endres", + "interval":"'interval': tiden mellom oppdateringer (i sekunder)", + "max_brightness":"'max_brightness': den høyeste lysstyrken (i prosent) på lysene i løpet av en syklus", + "max_color_temp":"'max_color_temp': den høyeste fargetemperaturen (i kelvin) på lysene i løpet av en syklus", + "min_brightness":"'min_brightness': den laveste lysstyrken (i prosent) på lysene i løpet av en syklus", + "min_color_temp":"'min_color_temp': den laveste fargetemperaturen (i kelvin) på lysene i løpet av en syklus", + "only_once":"'only_once': anvend innstillingene for adaptiv belysning kun når lysene skrus av eller på", + "prefer_rgb_color":"'prefer_rgb_color': benytt rgb i stedet for fargetemperatur dersom det er mulig", + "separate_turn_on_commands":"'separate_turn_on_commands': separer kommandone i 'light.turn_on' for hver attributt (farge, lysstyrke, osv.). Dette kan være nødvendig for enkelte typer lys / lyskilder", + "sleep_brightness":"'sleep_brightness': lysstyrken på lysene (i prosent) når 'sleep_mode' (søvnmodus) er aktiv", + "sleep_color_temp":"'sleep_color_temp': fargetemperaturen på lysene (i kelvin) når 'sleep_mode' (søvnmodus) er aktiv", + "sunrise_offset":"'sunrise_offset': hvor lenge før (-) eller etter (+) tidspunktet solen står opp (lokalt) skal defineres som soloppgang (i sekunder)", + "sunrise_time":"'sunrise_time': definer tidspunktet for soloppgang manuelt (i følgende format: TT:MM:SS)", + "sunset_offset":"'sunset_offset': hvor lenge før (-) eller etter (+) tidspunktet solen går ned (lokalt) skal defineres som solnedgang (i sekunder)", + "sunset_time":"'sunset_time': definer tidspunktet for solnedgang manuelt (i følgende format: TT:MM:SS - f. eks: '20:30:00' vil definere tidspunktet for solnegang som halv-ni på kvelden)", + "take_over_control":"'take_over_control': dersom en annen tjeneste enn adaptiv belysning skrur lysene av eller på, vil automatisk adaptering av lyset stoppes inntil lyset (eller den tilhørende bryteren for adaptiv belysning) blir slått av - og på igjen", + "detect_non_ha_changes":"'detect_non_ha_changes': registrerer alle endringer i lysstyrke over 10% med opprinnelse utenfor Home Assistant - krever at 'take_over_control' er aktivert (OBS: tilkaller 'homeassistant.update_entity' ved hvert 'interval'!)", + "transition":"'transition': varigheten (i sekunder) på overgangen når lysene oppdateres " + } + } + }, + "error":{ + "option_error":"En eller flere valgte innstillinger er ugyldige" + } + } +} diff --git a/hacs.json b/hacs.json index 1de0dd51..500d0ebd 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,5 @@ { - "name": "adaptive_lighting", + "name": "Adaptive Lighting", "render_readme": true, "domains": ["switch"] }