mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-17 17:24:03 +02:00
Merge branch 'master' into accent_colors
This commit is contained in:
commit
105a378e0f
19 changed files with 416 additions and 38 deletions
17
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
name: 'Bug Report'
|
||||
about: 'Report a bug in adaptive-lighting.'
|
||||
labels: kind/bug, need/triage
|
||||
---
|
||||
|
||||
#### Version information:
|
||||
|
||||
|
||||
#### Description:
|
||||
<!-- This is where you get to tell us what went wrong. When doing so, please make sure to include *all* relevant information.
|
||||
|
||||
Please try to include:
|
||||
* What you were doing when you experienced the bug.
|
||||
* Any error messages you saw, *where* you saw them, and what you believe may have caused them (if you have any ideas).
|
||||
* When possible, steps to reliably produce the bug.
|
||||
-->
|
||||
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
14
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -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
|
||||
13
.github/ISSUE_TEMPLATE/doc.md
vendored
Normal file
13
.github/ISSUE_TEMPLATE/doc.md
vendored
Normal file
|
|
@ -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
|
||||
|
||||
<!-- In the case of missing/erroneous documentation, where is the error? If possible, a link/URL would be great! -->
|
||||
|
||||
#### Description
|
||||
|
||||
<!-- Describe the documentation issue. -->
|
||||
6
.github/ISSUE_TEMPLATE/enhancement.md
vendored
Normal file
6
.github/ISSUE_TEMPLATE/enhancement.md
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
name: 'Enhancement'
|
||||
about: 'Suggest an improvement to an existing feature.'
|
||||
labels: kind/enhancement, need/triage
|
||||
---
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/feature.md
vendored
Normal file
5
.github/ISSUE_TEMPLATE/feature.md
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
name: 'Feature'
|
||||
about: 'Suggest a new feature'
|
||||
labels: kind/feature, need/triage
|
||||
---
|
||||
6
.github/auto-comment.yml
vendored
Normal file
6
.github/auto-comment.yml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Comment to a new issue.
|
||||
# Disabled
|
||||
# issueOpened: ""
|
||||
|
||||
# Disabled
|
||||
# pullRequestOpened: ""
|
||||
64
.github/config.yml
vendored
Normal file
64
.github/config.yml
vendored
Normal file
|
|
@ -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: ""
|
||||
14
.github/workflows/hassfest.yaml
vendored
Normal file
14
.github/workflows/hassfest.yaml
vendored
Normal file
|
|
@ -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
|
||||
17
.github/workflows/validate.yml
vendored
Normal file
17
.github/workflows/validate.yml
vendored
Normal file
|
|
@ -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"
|
||||
129
.gitignore
vendored
Normal file
129
.gitignore
vendored
Normal file
|
|
@ -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/
|
||||
17
README.md
17
README.md
|
|
@ -1,9 +1,12 @@
|
|||
[](https://github.com/custom-components/hacs)
|
||||

|
||||
|
||||
# Adaptive Lighting component for Home Assistant
|
||||
|
||||

|
||||
|
||||
_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:
|
||||

|
||||
|
||||
# Maintainers
|
||||
|
||||
- @basnijholt
|
||||
- @RubenKelevra
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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).",
|
||||
|
|
|
|||
49
custom_components/adaptive_lighting/translations/nb.json
Normal file
49
custom_components/adaptive_lighting/translations/nb.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "adaptive_lighting",
|
||||
"name": "Adaptive Lighting",
|
||||
"render_readme": true,
|
||||
"domains": ["switch"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue