mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-16 16:54:04 +02:00
merge upstream\main
This commit is contained in:
commit
46d5563697
16 changed files with 657 additions and 456 deletions
|
|
@ -429,6 +429,15 @@
|
|||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "igiannakas",
|
||||
"name": "igiannakas",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/59056762?v=4",
|
||||
"profile": "https://github.com/igiannakas",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
|
|
|||
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
* @basnijholt
|
||||
25
.github/update-services.py
vendored
Normal file
25
.github/update-services.py
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
import yaml
|
||||
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
|
||||
from custom_components.adaptive_lighting import const # noqa: E402
|
||||
|
||||
services_filename = "custom_components/adaptive_lighting/services.yaml"
|
||||
with open(services_filename) as f:
|
||||
services = yaml.safe_load(f)
|
||||
|
||||
for service_name, dct in services.items():
|
||||
_docs = {"set_manual_control": const.DOCS_MANUAL_CONTROL, "apply": const.DOCS_APPLY}
|
||||
alternative_docs = _docs.get(service_name, const.DOCS)
|
||||
for field_name, field in dct["fields"].items():
|
||||
description = alternative_docs.get(field_name, const.DOCS[field_name])
|
||||
field["description"] = description
|
||||
|
||||
comment = "# This file is auto-generated by .github/update-services.py."
|
||||
|
||||
with open(services_filename, "w") as f:
|
||||
f.write(comment + "\n")
|
||||
yaml.dump(services, f, sort_keys=False, width=1000, allow_unicode=True)
|
||||
31
.github/update-strings.py
vendored
Normal file
31
.github/update-strings.py
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
sys.path.append(str(Path(__file__).parent.parent))
|
||||
|
||||
from custom_components.adaptive_lighting import const # noqa: E402
|
||||
|
||||
strings_fname = "custom_components/adaptive_lighting/strings.json"
|
||||
en_fname = "custom_components/adaptive_lighting/translations/en.json"
|
||||
with open(strings_fname) as f:
|
||||
strings = json.load(f)
|
||||
|
||||
data = {k: f"{k}: {const.DOCS[k]}" for k, _, _ in const.VALIDATION_TUPLES}
|
||||
strings["options"]["step"]["init"]["data"] = data
|
||||
|
||||
with open(strings_fname, "w") as f:
|
||||
json.dump(strings, f, indent=2, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
|
||||
|
||||
# Sync changes from strings.json to en.json
|
||||
with open(en_fname) as f:
|
||||
en = json.load(f)
|
||||
|
||||
en["config"]["step"]["user"] = strings["config"]["step"]["user"]
|
||||
en["options"]["step"]["init"]["data"] = data
|
||||
|
||||
with open(en_fname, "w") as f:
|
||||
json.dump(en, f, indent=2, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
8
.github/workflows/pytest.yaml
vendored
8
.github/workflows/pytest.yaml
vendored
|
|
@ -31,8 +31,8 @@ jobs:
|
|||
echo "::notice::### 4. ERROR:homeassistant.setup:Setup failed for 'component': Unable to import component: No module named ''module'' ###"
|
||||
echo "::notice::### 5. add 'component'.'module' (without the '') from the above log into the 'required' list inside of 'test_dependencies.py' ###"
|
||||
echo "::notice::### 6. Try again! If more issues persist they should be easily solvable by reading the verbose logs now. ###"
|
||||
- name: Run pytest
|
||||
timeout-minutes: 60
|
||||
|
||||
- name: Link custom_components/adaptive_lighting
|
||||
run: |
|
||||
cd core
|
||||
|
||||
|
|
@ -46,6 +46,10 @@ jobs:
|
|||
ln -fs ../../../tests adaptive_lighting
|
||||
cd -
|
||||
|
||||
- name: Run pytest
|
||||
timeout-minutes: 60
|
||||
run: |
|
||||
cd core
|
||||
python3 -X dev -m pytest \
|
||||
-qq \
|
||||
--timeout=9 \
|
||||
|
|
|
|||
21
.github/workflows/update-readme.yml
vendored
21
.github/workflows/update-readme.yml
vendored
|
|
@ -22,24 +22,35 @@ jobs:
|
|||
with:
|
||||
python_version: "3.10"
|
||||
|
||||
- name: Install pandas and tabulate
|
||||
- name: Install markdown-code-runner and README code dependencies
|
||||
run: |
|
||||
pip install markdown-code-runner pandas tabulate
|
||||
|
||||
- name: Link custom_components/adaptive_lighting
|
||||
run: |
|
||||
cd core/homeassistant/components
|
||||
ln -fs ../../../custom_components/adaptive_lighting adaptive_lighting
|
||||
|
||||
- name: Run markdown-code-runner
|
||||
run: markdown-code-runner --debug README.md
|
||||
|
||||
- name: Commit updated README.md
|
||||
- name: Run update strings.json
|
||||
run: python .github/update-strings.py
|
||||
|
||||
- name: Run update services.yaml
|
||||
run: python .github/update-services.py
|
||||
|
||||
- name: Commit updated README.md, strings.json, and services.yaml
|
||||
id: commit
|
||||
run: |
|
||||
git add README.md
|
||||
git add -u .
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
if git diff --quiet && git diff --staged --quiet; then
|
||||
echo "No changes in README.md, skipping commit."
|
||||
echo "No changes in README.md, strings.json, and services.yaml, skipping commit."
|
||||
echo "commit_status=skipped" >> $GITHUB_ENV
|
||||
else
|
||||
git commit -m "Update README.md"
|
||||
git commit -m "Update README.md, strings.json, and services.yaml"
|
||||
echo "commit_status=committed" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ repos:
|
|||
hooks:
|
||||
- id: flake8
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
|
|
|
|||
89
README.md
89
README.md
|
|
@ -1,7 +1,7 @@
|
|||
[](https://github.com/hacs/integration)
|
||||

|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
# 🌞 Adaptive Lighting: Enhance Your Home's Atmosphere with Smart, Sun-Synchronized Lighting 🌙
|
||||
|
|
@ -58,6 +58,7 @@ The `adaptive_lighting.manual_control` event is fired when a light is marked as
|
|||
- [:sunny: Sun Position](#sunny-sun-position)
|
||||
- [:thermometer: Color Temperature](#thermometer-color-temperature)
|
||||
- [:high_brightness: Brightness](#high_brightness-brightness)
|
||||
- [While using `adapt_until_sleep: true`](#while-using-adapt_until_sleep-true)
|
||||
- [:busts_in_silhouette: Contributors](#busts_in_silhouette-contributors)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
|
@ -81,45 +82,12 @@ All of the configuration options are listed below, along with their default valu
|
|||
The YAML and frontend configuration methods support all of the options listed below.
|
||||
|
||||
<!-- START_CODE -->
|
||||
<!-- import os, sys -->
|
||||
<!-- sys.path.append(os.path.abspath(".")) -->
|
||||
<!-- from custom_components.adaptive_lighting import const -->
|
||||
<!-- markdown_table = const.generate_markdown_table() -->
|
||||
<!-- print(markdown_table) -->
|
||||
<!-- from homeassistant.components.adaptive_lighting import _docs_helpers -->
|
||||
<!-- print(_docs_helpers.generate_config_markdown_table()) -->
|
||||
<!-- END_CODE -->
|
||||
|
||||
<!-- START_OUTPUT -->
|
||||
<!-- THIS CONTENT IS AUTOMATICALLY GENERATED -->
|
||||
| Variable name | Description | Default | Type |
|
||||
|:-------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------|:-------------------------------------|
|
||||
| `lights` | List of light entities to be controlled by Adaptive Lighting (may be empty). 🌟 | `[]` | list of `entity_id`s |
|
||||
| `prefer_rgb_color` | Use RGB color adjustment instead of native light color temperature. 🌈 | `False` | `bool` |
|
||||
| `include_config_in_attributes` | Show all options as attributes on the switch in Home Assistant when set to `true`. 📝 | `False` | `bool` |
|
||||
| `initial_transition` | Duration of the first transition when lights turn from `off` to `on`. ⏲️ | `1` | `float` 0-6553 |
|
||||
| `sleep_transition` | Duration of transition when 'sleep mode' is toggled. 😴 | `1` | `float` 0-6553 |
|
||||
| `transition` | Duration of transition when lights change, in seconds. 🕑 | `45` | `float` 0-6553 |
|
||||
| `interval` | Frequency to adapt the lights, in seconds. 🔄 | `90` | `int > 0` |
|
||||
| `min_brightness` | Minimum brightness percentage. 💡 | `1` | `int` 1-100 |
|
||||
| `max_brightness` | Maximum brightness percentage. 💡 | `100` | `int` 1-100 |
|
||||
| `min_color_temp` | Warmest color temperature in Kelvin. 🔥 | `2000` | `int` 1000-10000 |
|
||||
| `max_color_temp` | Coldest color temperature in Kelvin. ❄️ | `5500` | `int` 1000-10000 |
|
||||
| `sleep_brightness` | Brightness of lights in sleep mode. 😴 | `1` | `int` 1-100 |
|
||||
| `sleep_rgb_or_color_temp` | Use either `'rgb_color'` or `'color_temp'` in sleep mode. 🌙 | `color_temp` | one of `['color_temp', 'rgb_color']` |
|
||||
| `sleep_color_temp` | Color temperature in sleep mode (used when `sleep_rgb_or_color_temp` is `color_temp`). 😴 | `1000` | `int` 1000-10000 |
|
||||
| `sleep_rgb_color` | RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is 'rgb_color'). 🌈 | `[255, 56, 0]` | RGB color |
|
||||
| `sunrise_time` | Set a fixed time for sunrise. 🌅 | `None` | `str` |
|
||||
| `max_sunrise_time` | Set the latest virtual sunrise time, allowing for earlier real sunrises. 🌅 | `None` | `str` |
|
||||
| `sunrise_offset` | Adjust sunrise time with a positive or negative offset. ⏰ | `0` | `int` |
|
||||
| `sunset_time` | Set a fixed time for sunset. 🌇 | `None` | `str` |
|
||||
| `min_sunset_time` | Set the earliest virtual sunset time, allowing for later real sunsets. 🌇 | `None` | `str` |
|
||||
| `sunset_offset` | Adjust sunset time with a positive or negative offset. ⏰ | `0` | `int` |
|
||||
| `only_once` | Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄 | `False` | `bool` |
|
||||
| `take_over_control` | Disable Adaptive Lighting if another source calls `light.turn_on` while lights are on and being adapted. Note that this calls `homeassistant.update_entity` every `interval`! 🔒 | `True` | `bool` |
|
||||
| `detect_non_ha_changes` | Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️ | `False` | `bool` |
|
||||
| `separate_turn_on_commands` | Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀 | `False` | `bool` |
|
||||
| `send_split_delay` | Wait time (milliseconds) between commands when using `separate_turn_on_commands`. Helps ensure correct handling. ⏲️ | `0` | `int` 0-10000 |
|
||||
| `adapt_delay` | Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Helps avoid flickering. ⏲️ | `0` | `float > 0` |
|
||||
| `autoreset_control_seconds` | Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️ | `0` | `int` 0-604800 |
|
||||
|
||||
<!-- END_OUTPUT -->
|
||||
|
||||
|
|
@ -156,26 +124,42 @@ adaptive_lighting:
|
|||
|
||||
`adaptive_lighting.apply` applies Adaptive Lighting settings to lights on demand.
|
||||
|
||||
| Service data attribute | Required | Description |
|
||||
| ---------------------- | -------- | -------------------------------------------------------------------------------------------- |
|
||||
| `entity_id` | ✅ | The `entity_id` of the switch with the settings to apply. |
|
||||
| `lights` | ❌ | A light (or list of lights) to apply the settings to. |
|
||||
| `transition` | ❌ | The number of seconds for the transition. |
|
||||
| `adapt_brightness` | ❌ | Whether to change the brightness of the light or not. |
|
||||
| `adapt_color` | ❌ | Whether to adapt the color on supporting lights. |
|
||||
| `prefer_rgb_color` | ❌ | Whether to prefer RGB color adjustment over of native light color temperature when possible. |
|
||||
| `turn_on_lights` | ❌ | Whether to turn on lights that are currently off. |
|
||||
<!-- START_CODE -->
|
||||
<!-- from homeassistant.components.adaptive_lighting import _docs_helpers -->
|
||||
<!-- print(_docs_helpers.generate_apply_markdown_table()) -->
|
||||
<!-- END_CODE -->
|
||||
|
||||
<!-- START_OUTPUT -->
|
||||
<!-- THIS CONTENT IS AUTOMATICALLY GENERATED -->
|
||||
| Service data attribute | Description | Required | Type |
|
||||
|:-------------------------|:-------------------------------------------------------------------------------------|:-----------|:---------------------|
|
||||
| `entity_id` | The `entity_id` of the switch with the settings to apply. 📝 | ✅ | list of `entity_id`s |
|
||||
| `lights` | A light (or list of lights) to apply the settings to. 💡 | ❌ | list of `entity_id`s |
|
||||
| `transition` | Duration of transition when lights change, in seconds. 🕑 | ❌ | `float` 0-6553 |
|
||||
| `adapt_brightness` | Whether to adapt the brightness of the light. 🌞 | ❌ | bool |
|
||||
| `adapt_color` | Whether to adapt the color on supporting lights. 🌈 | ❌ | bool |
|
||||
| `prefer_rgb_color` | Whether to prefer RGB color adjustment over light color temperature when possible. 🌈 | ❌ | bool |
|
||||
| `turn_on_lights` | Whether to turn on lights that are currently off. 🔆 | ❌ | bool |
|
||||
|
||||
<!-- END_OUTPUT -->
|
||||
#### `adaptive_lighting.set_manual_control`
|
||||
|
||||
`adaptive_lighting.set_manual_control` can mark (or unmark) whether a light is "manually controlled", meaning that when a light has `manual_control`, the light is not adapted.
|
||||
|
||||
| Service data attribute | Required | Description |
|
||||
| ---------------------- | -------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `entity_id` | ✅ | The `entity_id` of the switch in which to (un)mark the light as being "manually controlled". |
|
||||
| `lights` | ❌ | entity_id(s) of lights, if not specified, all lights in the switch are selected. |
|
||||
| `manual_control` | ❌ | Whether to add ('true') or remove ('false') the light from the 'manual_control' list, default: true |
|
||||
<!-- START_CODE -->
|
||||
<!-- from homeassistant.components.adaptive_lighting import _docs_helpers -->
|
||||
<!-- print(_docs_helpers.generate_set_manual_control_markdown_table()) -->
|
||||
<!-- END_CODE -->
|
||||
|
||||
<!-- START_OUTPUT -->
|
||||
<!-- THIS CONTENT IS AUTOMATICALLY GENERATED -->
|
||||
| Service data attribute | Description | Required | Type |
|
||||
|:-------------------------|:-----------------------------------------------------------------------------------------------|:-----------|:---------------------|
|
||||
| `entity_id` | The `entity_id` of the switch in which to (un)mark the light as being `manually controlled`. 📝 | ✅ | list of `entity_id`s |
|
||||
| `lights` | entity_id(s) of lights, if not specified, all lights in the switch are selected. 💡 | ❌ | list of `entity_id`s |
|
||||
| `manual_control` | Whether to add ("true") or remove ("false") the light from the "manual_control" list. 🔒 | ❌ | bool |
|
||||
|
||||
<!-- END_OUTPUT -->
|
||||
#### `adaptive_lighting.change_switch_settings`
|
||||
|
||||
`adaptive_lighting.change_switch_settings` (new in 1.7.0) Change any of the above configuration options of Adaptive Lighting (such as `sunrise_time` or `prefer_rgb_color`) with a service call directly from your script/automation.
|
||||
|
|
@ -364,6 +348,10 @@ These graphs were generated using the values calculated by the Adaptive Lighting
|
|||
#### :high_brightness: Brightness
|
||||

|
||||
|
||||
#### While using `adapt_until_sleep: true`
|
||||

|
||||
|
||||
|
||||
## :busts_in_silhouette: Contributors
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
||||
|
|
@ -430,6 +418,7 @@ These graphs were generated using the values calculated by the Adaptive Lighting
|
|||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/skycarl"><img src="https://avatars.githubusercontent.com/u/43375685?v=4?s=100" width="100px;" alt="Skyler Carlson"/><br /><sub><b>Skyler Carlson</b></sub></a><br /><a href="https://github.com/basnijholt/adaptive-lighting/commits?author=skycarl" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/firstof9"><img src="https://avatars.githubusercontent.com/u/1105672?v=4?s=100" width="100px;" alt="Chris"/><br /><sub><b>Chris</b></sub></a><br /><a href="https://github.com/basnijholt/adaptive-lighting/commits?author=firstof9" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/raman325"><img src="https://avatars.githubusercontent.com/u/7243222?v=4?s=100" width="100px;" alt="Raman Gupta"/><br /><sub><b>Raman Gupta</b></sub></a><br /><a href="https://github.com/basnijholt/adaptive-lighting/commits?author=raman325" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/igiannakas"><img src="https://avatars.githubusercontent.com/u/59056762?v=4?s=100" width="100px;" alt="igiannakas"/><br /><sub><b>igiannakas</b></sub></a><br /><a href="https://github.com/basnijholt/adaptive-lighting/commits?author=igiannakas" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
|
|
|
|||
116
custom_components/adaptive_lighting/_docs_helpers.py
Normal file
116
custom_components/adaptive_lighting/_docs_helpers.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
from typing import Any
|
||||
|
||||
from homeassistant.helpers import selector
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import pandas as pd
|
||||
import voluptuous as vol
|
||||
|
||||
from .const import (
|
||||
DOCS,
|
||||
DOCS_APPLY,
|
||||
DOCS_MANUAL_CONTROL,
|
||||
SET_MANUAL_CONTROL_SCHEMA,
|
||||
VALIDATION_TUPLES,
|
||||
apply_service_schema,
|
||||
)
|
||||
|
||||
|
||||
def _format_voluptuous_instance(instance):
|
||||
coerce_type = None
|
||||
min_val = None
|
||||
max_val = None
|
||||
|
||||
for validator in instance.validators:
|
||||
if isinstance(validator, vol.Coerce):
|
||||
coerce_type = validator.type.__name__
|
||||
elif isinstance(validator, (vol.Clamp, vol.Range)):
|
||||
min_val = validator.min
|
||||
max_val = validator.max
|
||||
|
||||
if min_val is not None and max_val is not None:
|
||||
return f"`{coerce_type}` {min_val}-{max_val}"
|
||||
elif min_val is not None:
|
||||
return f"`{coerce_type} > {min_val}`"
|
||||
elif max_val is not None:
|
||||
return f"`{coerce_type} < {max_val}`"
|
||||
else:
|
||||
return f"`{coerce_type}`"
|
||||
|
||||
|
||||
def _type_to_str(type_: Any) -> str:
|
||||
"""Convert a (voluptuous) type to a string."""
|
||||
if type_ == cv.entity_ids:
|
||||
return "list of `entity_id`s"
|
||||
elif type_ in (bool, int, float, str):
|
||||
return f"`{type_.__name__}`"
|
||||
elif type_ == cv.boolean:
|
||||
return "bool"
|
||||
elif isinstance(type_, vol.All):
|
||||
return _format_voluptuous_instance(type_)
|
||||
elif isinstance(type_, vol.In):
|
||||
return f"one of `{type_.container}`"
|
||||
elif isinstance(type_, selector.SelectSelector):
|
||||
return f"one of `{type_.config['options']}`"
|
||||
elif isinstance(type_, selector.ColorRGBSelector):
|
||||
return "RGB color"
|
||||
else:
|
||||
raise ValueError(f"Unknown type: {type_}")
|
||||
|
||||
|
||||
def generate_config_markdown_table():
|
||||
import pandas as pd
|
||||
|
||||
rows = []
|
||||
for k, default, type_ in VALIDATION_TUPLES:
|
||||
description = DOCS[k]
|
||||
row = {
|
||||
"Variable name": f"`{k}`",
|
||||
"Description": description,
|
||||
"Default": f"`{default}`",
|
||||
"Type": _type_to_str(type_),
|
||||
}
|
||||
rows.append(row)
|
||||
|
||||
df = pd.DataFrame(rows)
|
||||
return df.to_markdown(index=False)
|
||||
|
||||
|
||||
def _schema_to_dict(schema: vol.Schema) -> dict[str, tuple[Any, Any]]:
|
||||
result = {}
|
||||
for key, value in schema.schema.items():
|
||||
if isinstance(key, vol.Optional):
|
||||
default_value = key.default
|
||||
result[key.schema] = (default_value, value)
|
||||
return result
|
||||
|
||||
|
||||
def _generate_service_markdown_table(
|
||||
schema: dict[str, tuple[Any, Any]], alternative_docs: dict[str, str] = None
|
||||
):
|
||||
schema = _schema_to_dict(schema)
|
||||
rows = []
|
||||
for k, (default, type_) in schema.items():
|
||||
if alternative_docs is not None and k in alternative_docs:
|
||||
description = alternative_docs[k]
|
||||
else:
|
||||
description = DOCS[k]
|
||||
row = {
|
||||
"Service data attribute": f"`{k}`",
|
||||
"Description": description,
|
||||
"Required": "✅" if default == vol.UNDEFINED else "❌",
|
||||
"Type": _type_to_str(type_),
|
||||
}
|
||||
rows.append(row)
|
||||
|
||||
df = pd.DataFrame(rows)
|
||||
return df.to_markdown(index=False)
|
||||
|
||||
|
||||
def generate_apply_markdown_table():
|
||||
return _generate_service_markdown_table(apply_service_schema(), DOCS_APPLY)
|
||||
|
||||
|
||||
def generate_set_manual_control_markdown_table():
|
||||
return _generate_service_markdown_table(
|
||||
SET_MANUAL_CONTROL_SCHEMA, DOCS_MANUAL_CONTROL
|
||||
)
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
"""Constants for the Adaptive Lighting integration."""
|
||||
|
||||
from homeassistant.components.light import VALID_TRANSITION
|
||||
from homeassistant.const import CONF_ENTITY_ID
|
||||
from homeassistant.helpers import selector
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
|
|
@ -14,16 +15,14 @@ DOMAIN = "adaptive_lighting"
|
|||
SUN_EVENT_NOON = "solar_noon"
|
||||
SUN_EVENT_MIDNIGHT = "solar_midnight"
|
||||
|
||||
DOCS = {}
|
||||
DOCS = {CONF_ENTITY_ID: "Entity ID of the switch. 📝"}
|
||||
|
||||
|
||||
CONF_NAME, DEFAULT_NAME = "name", "default"
|
||||
DOCS[CONF_NAME] = "Display name for this switch. 📝"
|
||||
|
||||
CONF_LIGHTS, DEFAULT_LIGHTS = "lights", []
|
||||
DOCS[CONF_LIGHTS] = (
|
||||
"List of light entities to be controlled by Adaptive " "Lighting (may be empty). 🌟"
|
||||
)
|
||||
DOCS[CONF_LIGHTS] = "List of light entity_ids to be controlled (may be empty). 🌟"
|
||||
|
||||
CONF_DETECT_NON_HA_CHANGES, DEFAULT_DETECT_NON_HA_CHANGES = (
|
||||
"detect_non_ha_changes",
|
||||
|
|
@ -45,11 +44,14 @@ DOCS[CONF_INCLUDE_CONFIG_IN_ATTRIBUTES] = (
|
|||
|
||||
CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION = "initial_transition", 1
|
||||
DOCS[CONF_INITIAL_TRANSITION] = (
|
||||
"Duration of the first transition when lights turn " "from `off` to `on`. ⏲️"
|
||||
"Duration of the first transition when lights turn "
|
||||
"from `off` to `on` in seconds. ⏲️"
|
||||
)
|
||||
|
||||
CONF_SLEEP_TRANSITION, DEFAULT_SLEEP_TRANSITION = "sleep_transition", 1
|
||||
DOCS[CONF_SLEEP_TRANSITION] = "Duration of transition when 'sleep mode' is toggled. 😴"
|
||||
DOCS[CONF_SLEEP_TRANSITION] = (
|
||||
'Duration of transition when "sleep mode" is toggled ' "in seconds. 😴"
|
||||
)
|
||||
|
||||
CONF_INTERVAL, DEFAULT_INTERVAL = "interval", 90
|
||||
DOCS[CONF_INTERVAL] = "Frequency to adapt the lights, in seconds. 🔄"
|
||||
|
|
@ -73,9 +75,10 @@ DOCS[CONF_ONLY_ONCE] = (
|
|||
)
|
||||
|
||||
CONF_PREFER_RGB_COLOR, DEFAULT_PREFER_RGB_COLOR = "prefer_rgb_color", False
|
||||
DOCS[
|
||||
CONF_PREFER_RGB_COLOR
|
||||
] = "Use RGB color adjustment instead of native light color temperature. 🌈"
|
||||
DOCS[CONF_PREFER_RGB_COLOR] = (
|
||||
"Whether to prefer RGB color adjustment over "
|
||||
"light color temperature when possible. 🌈"
|
||||
)
|
||||
|
||||
CONF_SEPARATE_TURN_ON_COMMANDS, DEFAULT_SEPARATE_TURN_ON_COMMANDS = (
|
||||
"separate_turn_on_commands",
|
||||
|
|
@ -87,47 +90,53 @@ DOCS[CONF_SEPARATE_TURN_ON_COMMANDS] = (
|
|||
)
|
||||
|
||||
CONF_SLEEP_BRIGHTNESS, DEFAULT_SLEEP_BRIGHTNESS = "sleep_brightness", 1
|
||||
DOCS[CONF_SLEEP_BRIGHTNESS] = "Brightness of lights in sleep mode. 😴"
|
||||
DOCS[CONF_SLEEP_BRIGHTNESS] = "Brightness percentage of lights in sleep mode. 😴"
|
||||
|
||||
CONF_SLEEP_COLOR_TEMP, DEFAULT_SLEEP_COLOR_TEMP = "sleep_color_temp", 1000
|
||||
DOCS[CONF_SLEEP_COLOR_TEMP] = (
|
||||
"Color temperature in sleep mode (used when `sleep_rgb_or_color_temp` is "
|
||||
"`color_temp`). 😴"
|
||||
"`color_temp`) in Kelvin. 😴"
|
||||
)
|
||||
|
||||
CONF_SLEEP_RGB_COLOR, DEFAULT_SLEEP_RGB_COLOR = "sleep_rgb_color", [255, 56, 0]
|
||||
DOCS[CONF_SLEEP_RGB_COLOR] = (
|
||||
"RGB color in sleep mode (used when " "`sleep_rgb_or_color_temp` is 'rgb_color'). 🌈"
|
||||
"RGB color in sleep mode (used when " '`sleep_rgb_or_color_temp` is "rgb_color"). 🌈'
|
||||
)
|
||||
|
||||
CONF_SLEEP_RGB_OR_COLOR_TEMP, DEFAULT_SLEEP_RGB_OR_COLOR_TEMP = (
|
||||
"sleep_rgb_or_color_temp",
|
||||
"color_temp",
|
||||
)
|
||||
DOCS[
|
||||
CONF_SLEEP_RGB_OR_COLOR_TEMP
|
||||
] = "Use either `'rgb_color'` or `'color_temp'` in sleep mode. 🌙"
|
||||
DOCS[CONF_SLEEP_RGB_OR_COLOR_TEMP] = (
|
||||
'Use either `"rgb_color"` or `"color_temp"` ' "in sleep mode. 🌙"
|
||||
)
|
||||
|
||||
CONF_SUNRISE_OFFSET, DEFAULT_SUNRISE_OFFSET = "sunrise_offset", 0
|
||||
DOCS[CONF_SUNRISE_OFFSET] = "Adjust sunrise time with a positive or negative offset. ⏰"
|
||||
DOCS[CONF_SUNRISE_OFFSET] = (
|
||||
"Adjust sunrise time with a positive or negative offset " "in seconds. ⏰"
|
||||
)
|
||||
|
||||
CONF_SUNRISE_TIME = "sunrise_time"
|
||||
DOCS[CONF_SUNRISE_TIME] = "Set a fixed time for sunrise. 🌅"
|
||||
DOCS[CONF_SUNRISE_TIME] = "Set a fixed time (HH:MM:SS) for sunrise. 🌅"
|
||||
|
||||
CONF_MAX_SUNRISE_TIME = "max_sunrise_time"
|
||||
DOCS[CONF_MAX_SUNRISE_TIME] = (
|
||||
"Set the latest virtual sunrise time, allowing" " for earlier real sunrises. 🌅"
|
||||
"Set the latest virtual sunrise time (HH:MM:SS), allowing"
|
||||
" for earlier real sunrises. 🌅"
|
||||
)
|
||||
|
||||
CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET = "sunset_offset", 0
|
||||
DOCS[CONF_SUNSET_OFFSET] = "Adjust sunset time with a positive or negative offset. ⏰"
|
||||
DOCS[
|
||||
CONF_SUNSET_OFFSET
|
||||
] = "Adjust sunset time with a positive or negative offset in seconds. ⏰"
|
||||
|
||||
CONF_SUNSET_TIME = "sunset_time"
|
||||
DOCS[CONF_SUNSET_TIME] = "Set a fixed time for sunset. 🌇"
|
||||
DOCS[CONF_SUNSET_TIME] = "Set a fixed time (HH:MM:SS) for sunset. 🌇"
|
||||
|
||||
CONF_MIN_SUNSET_TIME = "min_sunset_time"
|
||||
DOCS[CONF_MIN_SUNSET_TIME] = (
|
||||
"Set the earliest virtual sunset time, allowing" " for later real sunsets. 🌇"
|
||||
"Set the earliest virtual sunset time (HH:MM:SS), allowing"
|
||||
" for later real sunsets. 🌇"
|
||||
)
|
||||
|
||||
CONF_TAKE_OVER_CONTROL, DEFAULT_TAKE_OVER_CONTROL = "take_over_control", True
|
||||
|
|
@ -140,16 +149,25 @@ DOCS[CONF_TAKE_OVER_CONTROL] = (
|
|||
CONF_TRANSITION, DEFAULT_TRANSITION = "transition", 45
|
||||
DOCS[CONF_TRANSITION] = "Duration of transition when lights change, in seconds. 🕑"
|
||||
|
||||
CONF_ADAPT_UNTIL_SLEEP, DEFAULT_ADAPT_UNTIL_SLEEP = (
|
||||
"transition_until_sleep",
|
||||
False,
|
||||
)
|
||||
DOCS[CONF_ADAPT_UNTIL_SLEEP] = (
|
||||
"When enabled, Adaptive Lighting will treat sleep settings as the minimum, "
|
||||
"transitioning to these values after sunset. 🌙"
|
||||
)
|
||||
|
||||
CONF_ADAPT_DELAY, DEFAULT_ADAPT_DELAY = "adapt_delay", 0
|
||||
DOCS[CONF_ADAPT_DELAY] = (
|
||||
"Wait time (seconds) between light turn on and Adaptive Lighting applying "
|
||||
"changes. Helps avoid flickering. ⏲️"
|
||||
"changes. Might help to avoid flickering. ⏲️"
|
||||
)
|
||||
|
||||
CONF_SEND_SPLIT_DELAY, DEFAULT_SEND_SPLIT_DELAY = "send_split_delay", 0
|
||||
DOCS[CONF_SEND_SPLIT_DELAY] = (
|
||||
"Wait time (milliseconds) between commands when using `separate_turn_on_commands`. "
|
||||
"Helps ensure correct handling. ⏲️"
|
||||
"Delay (ms) between `separate_turn_on_commands` for lights that don't support "
|
||||
"simultaneous brightness and color setting. ⏲️"
|
||||
)
|
||||
|
||||
CONF_AUTORESET_CONTROL, DEFAULT_AUTORESET_CONTROL = "autoreset_control_seconds", 0
|
||||
|
|
@ -165,18 +183,40 @@ ATTR_TURN_ON_OFF_LISTENER = "turn_on_off_listener"
|
|||
UNDO_UPDATE_LISTENER = "undo_update_listener"
|
||||
NONE_STR = "None"
|
||||
ATTR_ADAPT_COLOR = "adapt_color"
|
||||
DOCS[ATTR_ADAPT_COLOR] = "Whether to adapt the color on supporting lights. 🌈"
|
||||
ATTR_ADAPT_BRIGHTNESS = "adapt_brightness"
|
||||
DOCS[ATTR_ADAPT_BRIGHTNESS] = "Whether to adapt the brightness of the light. 🌞"
|
||||
|
||||
SERVICE_SET_MANUAL_CONTROL = "set_manual_control"
|
||||
CONF_MANUAL_CONTROL = "manual_control"
|
||||
DOCS[CONF_MANUAL_CONTROL] = "Whether to manually control the lights. 🔒"
|
||||
SERVICE_APPLY = "apply"
|
||||
CONF_TURN_ON_LIGHTS = "turn_on_lights"
|
||||
DOCS[CONF_TURN_ON_LIGHTS] = "Whether to turn on lights that are currently off. 🔆"
|
||||
SERVICE_CHANGE_SWITCH_SETTINGS = "change_switch_settings"
|
||||
CONF_USE_DEFAULTS = "use_defaults"
|
||||
|
||||
DOCS[CONF_USE_DEFAULTS] = (
|
||||
"Sets the default values not specified in this service call. Options: "
|
||||
'"current" (default, retains current values), "factory" (resets to '
|
||||
'documented defaults), or "configuration" (reverts to switch config defaults). ⚙️'
|
||||
)
|
||||
|
||||
TURNING_OFF_DELAY = 5
|
||||
|
||||
DOCS_MANUAL_CONTROL = {
|
||||
CONF_ENTITY_ID: "The `entity_id` of the switch in which to (un)mark the "
|
||||
"light as being `manually controlled`. 📝",
|
||||
CONF_LIGHTS: "entity_id(s) of lights, if not specified, all lights in the "
|
||||
"switch are selected. 💡",
|
||||
CONF_MANUAL_CONTROL: 'Whether to add ("true") or remove ("false") the '
|
||||
'light from the "manual_control" list. 🔒',
|
||||
}
|
||||
|
||||
DOCS_APPLY = {
|
||||
CONF_ENTITY_ID: "The `entity_id` of the switch with the settings to apply. 📝",
|
||||
CONF_LIGHTS: "A light (or list of lights) to apply the settings to. 💡",
|
||||
}
|
||||
|
||||
|
||||
def int_between(min_int, max_int):
|
||||
"""Return an integer between 'min_int' and 'max_int'."""
|
||||
|
|
@ -190,6 +230,7 @@ VALIDATION_TUPLES = [
|
|||
(CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION, VALID_TRANSITION),
|
||||
(CONF_SLEEP_TRANSITION, DEFAULT_SLEEP_TRANSITION, VALID_TRANSITION),
|
||||
(CONF_TRANSITION, DEFAULT_TRANSITION, VALID_TRANSITION),
|
||||
(CONF_ADAPT_UNTIL_SLEEP, DEFAULT_ADAPT_UNTIL_SLEEP, bool),
|
||||
(CONF_INTERVAL, DEFAULT_INTERVAL, cv.positive_int),
|
||||
(CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS, int_between(1, 100)),
|
||||
(CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS, int_between(1, 100)),
|
||||
|
|
@ -228,7 +269,7 @@ VALIDATION_TUPLES = [
|
|||
(
|
||||
CONF_AUTORESET_CONTROL,
|
||||
DEFAULT_AUTORESET_CONTROL,
|
||||
int_between(0, 7 * 24 * 60 * 60), # 7 days max
|
||||
int_between(0, 365 * 24 * 60 * 60), # 1 year max
|
||||
),
|
||||
]
|
||||
|
||||
|
|
@ -280,55 +321,28 @@ _DOMAIN_SCHEMA = vol.Schema(
|
|||
)
|
||||
|
||||
|
||||
def _format_voluptuous_instance(instance):
|
||||
coerce_type = None
|
||||
min_val = None
|
||||
max_val = None
|
||||
|
||||
for validator in instance.validators:
|
||||
if isinstance(validator, vol.Coerce):
|
||||
coerce_type = validator.type.__name__
|
||||
elif isinstance(validator, (vol.Clamp, vol.Range)):
|
||||
min_val = validator.min
|
||||
max_val = validator.max
|
||||
|
||||
if min_val is not None and max_val is not None:
|
||||
return f"`{coerce_type}` {min_val}-{max_val}"
|
||||
elif min_val is not None:
|
||||
return f"`{coerce_type} > {min_val}`"
|
||||
elif max_val is not None:
|
||||
return f"`{coerce_type} < {max_val}`"
|
||||
else:
|
||||
return f"`{coerce_type}`"
|
||||
|
||||
|
||||
def generate_markdown_table():
|
||||
import pandas as pd
|
||||
|
||||
rows = []
|
||||
for k, default, type_ in VALIDATION_TUPLES:
|
||||
description = DOCS[k]
|
||||
if type_ == cv.entity_ids:
|
||||
type_ = "list of `entity_id`s"
|
||||
elif type_ in (bool, int, float, str):
|
||||
type_ = f"`{type_.__name__}`"
|
||||
elif isinstance(type_, vol.All):
|
||||
type_ = _format_voluptuous_instance(type_)
|
||||
elif isinstance(type_, vol.In):
|
||||
type_ = f"one of `{type_.container}`"
|
||||
elif isinstance(type_, selector.SelectSelector):
|
||||
type_ = f"one of `{type_.config['options']}`"
|
||||
elif isinstance(type_, selector.ColorRGBSelector):
|
||||
type_ = "RGB color"
|
||||
else:
|
||||
raise ValueError(f"Unknown type: {type_}")
|
||||
row = {
|
||||
"Variable name": f"`{k}`",
|
||||
"Description": description,
|
||||
"Default": f"`{default}`",
|
||||
"Type": type_,
|
||||
def apply_service_schema(initial_transition: int = 1):
|
||||
"""Return the schema for the apply service."""
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids,
|
||||
vol.Optional(
|
||||
CONF_TRANSITION,
|
||||
default=initial_transition,
|
||||
): VALID_TRANSITION,
|
||||
vol.Optional(ATTR_ADAPT_BRIGHTNESS, default=True): cv.boolean,
|
||||
vol.Optional(ATTR_ADAPT_COLOR, default=True): cv.boolean,
|
||||
vol.Optional(CONF_PREFER_RGB_COLOR, default=False): cv.boolean,
|
||||
vol.Optional(CONF_TURN_ON_LIGHTS, default=False): cv.boolean,
|
||||
}
|
||||
rows.append(row)
|
||||
)
|
||||
|
||||
df = pd.DataFrame(rows)
|
||||
return df.to_markdown(index=False)
|
||||
|
||||
SET_MANUAL_CONTROL_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids,
|
||||
vol.Optional(CONF_MANUAL_CONTROL, default=True): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@
|
|||
"iot_class": "calculated",
|
||||
"issue_tracker": "https://github.com/basnijholt/adaptive-lighting/issues",
|
||||
"requirements": [],
|
||||
"version": "1.8.0"
|
||||
"version": "1.10.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,191 +1,186 @@
|
|||
# This file is auto-generated by .github/update-services.py.
|
||||
apply:
|
||||
description: Applies the current Adaptive Lighting settings to lights.
|
||||
fields:
|
||||
entity_id:
|
||||
description: entity_id of the Adaptive Lighting switch. If not specified, the current Adaptive Lighting switch will be used.
|
||||
example: switch.adaptive_lighting_default
|
||||
description: The `entity_id` of the switch with the settings to apply. 📝
|
||||
selector:
|
||||
entity:
|
||||
integration: adaptive_lighting
|
||||
domain: switch
|
||||
multiple: false
|
||||
lights:
|
||||
description: entity_id(s) of lights, if not specified, all lights in the switch are selected.
|
||||
example: light.bedroom_ceiling
|
||||
description: A light (or list of lights) to apply the settings to. 💡
|
||||
selector:
|
||||
entity:
|
||||
domain: light
|
||||
multiple: true
|
||||
transition:
|
||||
description: Transition of the lights.
|
||||
description: Duration of transition when lights change, in seconds. 🕑
|
||||
example: 10
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
adapt_brightness:
|
||||
description: "Adapt the 'brightness', default: true"
|
||||
description: Whether to adapt the brightness of the light. 🌞
|
||||
example: true
|
||||
selector:
|
||||
boolean:
|
||||
boolean: null
|
||||
adapt_color:
|
||||
description: "Adapt the color_temp/color_rgb, default: true"
|
||||
description: Whether to adapt the color on supporting lights. 🌈
|
||||
example: true
|
||||
selector:
|
||||
boolean:
|
||||
boolean: null
|
||||
prefer_rgb_color:
|
||||
description: "Prefer to use color_rgb over color_temp if possible, default: false"
|
||||
description: Whether to prefer RGB color adjustment over light color temperature when possible. 🌈
|
||||
example: false
|
||||
selector:
|
||||
boolean:
|
||||
boolean: null
|
||||
turn_on_lights:
|
||||
description: "Turn on the lights that are off, default: false"
|
||||
description: Whether to turn on lights that are currently off. 🔆
|
||||
example: false
|
||||
selector:
|
||||
boolean:
|
||||
|
||||
boolean: null
|
||||
set_manual_control:
|
||||
description: Mark whether a light is 'manually controlled'.
|
||||
fields:
|
||||
entity_id:
|
||||
description: entity_id of the Adaptive Lighting switch. If not specified, the current Adaptive Lighting switch will be used.
|
||||
example: switch.adaptive_lighting_default
|
||||
description: The `entity_id` of the switch in which to (un)mark the light as being `manually controlled`. 📝
|
||||
selector:
|
||||
entity:
|
||||
integration: adaptive_lighting
|
||||
domain: switch
|
||||
multiple: false
|
||||
lights:
|
||||
description: entity_id(s) of lights, if not specified, all lights in the switch are selected.
|
||||
example: light.bedroom_ceiling
|
||||
description: entity_id(s) of lights, if not specified, all lights in the switch are selected. 💡
|
||||
selector:
|
||||
entity:
|
||||
domain: light
|
||||
multiple: true
|
||||
manual_control:
|
||||
description: "Whether to add ('true') or remove ('false') the light from the 'manual_control' list, default: true"
|
||||
description: Whether to add ("true") or remove ("false") the light from the "manual_control" list. 🔒
|
||||
example: true
|
||||
default: true
|
||||
selector:
|
||||
boolean:
|
||||
|
||||
boolean: null
|
||||
change_switch_settings:
|
||||
description: "Change any settings you'd like in the switch. All options here are the same as in the config flow."
|
||||
description: Change any settings you'd like in the switch. All options here are the same as in the config flow.
|
||||
fields:
|
||||
entity_id:
|
||||
description: "entity_id of the Adaptive Lighting switch."
|
||||
description: Entity ID of the switch. 📝
|
||||
required: true
|
||||
selector:
|
||||
entity:
|
||||
domain: switch
|
||||
use_defaults:
|
||||
description: "(default: 'current' for current settings) You can set this to 'factory', 'configuration', or 'current' to reset the variables not being set with this service call. 'current' leaves them as is, 'configuration' resets to whatever already initializes at startup, 'factory' resets to the default values listed in the documentation."
|
||||
example: "current"
|
||||
description: 'Sets the default values not specified in this service call. Options: "current" (default, retains current values), "factory" (resets to documented defaults), or "configuration" (reverts to switch config defaults). ⚙️'
|
||||
example: current
|
||||
required: false
|
||||
default: "current"
|
||||
default: current
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- "current"
|
||||
- "configuration"
|
||||
- "factory"
|
||||
- current
|
||||
- configuration
|
||||
- factory
|
||||
include_config_in_attributes:
|
||||
description: "include_config_in_attributes: All config options will be listed as attributes under the adaptive-lighting switch this integration creates. (default: false)"
|
||||
description: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
boolean: null
|
||||
turn_on_lights:
|
||||
description: "Turn on the lights that are off, default: false"
|
||||
description: Whether to turn on lights that are currently off. 🔆
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
boolean: null
|
||||
initial_transition:
|
||||
description: "initial_transition: When lights turn 'off' to 'on'. (seconds)"
|
||||
description: Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️
|
||||
example: 1
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
sleep_transition:
|
||||
description: "sleep_transition: When 'sleep_state' changes. (seconds)"
|
||||
description: Duration of transition when "sleep mode" is toggled in seconds. 😴
|
||||
example: 1
|
||||
required: false
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
max_brightness:
|
||||
description: "max_brightness: Highest brightness of lights during a cycle. (%)"
|
||||
description: Maximum brightness percentage. 💡
|
||||
required: false
|
||||
example: 100
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
max_color_temp:
|
||||
description: "max_color_temp: Coldest hue of the color temperature cycle. (Kelvin)"
|
||||
description: Coldest color temperature in Kelvin. ❄️
|
||||
required: false
|
||||
example: 5500
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
min_brightness:
|
||||
description: "min_brightness: Lowest brightness of lights during a cycle. (%)"
|
||||
description: Minimum brightness percentage. 💡
|
||||
required: false
|
||||
example: 1
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
min_color_temp:
|
||||
description: "min_color_temp, Warmest hue of the color temperature cycle. (Kelvin)"
|
||||
description: Warmest color temperature in Kelvin. 🔥
|
||||
required: false
|
||||
example: 2000
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
only_once:
|
||||
description: "only_once: Only adapt the lights when turning them on."
|
||||
description: Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄
|
||||
example: false
|
||||
required: false
|
||||
selector:
|
||||
boolean:
|
||||
boolean: null
|
||||
prefer_rgb_color:
|
||||
description: "prefer_rgb_color: Use 'rgb_color' rather than 'color_temp' when possible."
|
||||
description: Whether to prefer RGB color adjustment over light color temperature when possible. 🌈
|
||||
required: false
|
||||
example: false
|
||||
selector:
|
||||
boolean:
|
||||
boolean: null
|
||||
separate_turn_on_commands:
|
||||
description: "separate_turn_on_commands: Separate the commands for each attribute (color, brightness, etc.) in 'light.turn_on' (required for some lights)."
|
||||
description: Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀
|
||||
required: false
|
||||
example: false
|
||||
selector:
|
||||
boolean:
|
||||
boolean: null
|
||||
send_split_delay:
|
||||
description: "send_split_delay: wait between commands (milliseconds), when separate_turn_on_commands is used. May ensure that both commands are handled by the bulb correctly."
|
||||
description: Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️
|
||||
required: false
|
||||
example: 0
|
||||
selector:
|
||||
boolean:
|
||||
boolean: null
|
||||
sleep_brightness:
|
||||
description: "sleep_brightness, Brightness setting for Sleep Mode. (%)"
|
||||
description: Brightness percentage of lights in sleep mode. 😴
|
||||
required: false
|
||||
example: 1
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
sleep_rgb_or_color_temp:
|
||||
description: "sleep_rgb_or_color_temp, use 'rgb_color' or 'color_temp'"
|
||||
description: Use either `"rgb_color"` or `"color_temp"` in sleep mode. 🌙
|
||||
required: false
|
||||
example: "color_temp"
|
||||
example: color_temp
|
||||
selector:
|
||||
select:
|
||||
options:
|
||||
- "rgb_color"
|
||||
- "color_temp"
|
||||
- rgb_color
|
||||
- color_temp
|
||||
sleep_rgb_color:
|
||||
description: "sleep_rgb_color, in RGB"
|
||||
description: RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is "rgb_color"). 🌈
|
||||
required: false
|
||||
selector:
|
||||
color_rgb:
|
||||
color_rgb: null
|
||||
sleep_color_temp:
|
||||
description: "sleep_color_temp: Color temperature setting for Sleep Mode. (Kelvin)"
|
||||
description: Color temperature in sleep mode (used when `sleep_rgb_or_color_temp` is `color_temp`) in Kelvin. 😴
|
||||
required: false
|
||||
example: 1000
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
sunrise_offset:
|
||||
description: sunrise_offset, in +/- seconds (integer)
|
||||
description: Adjust sunrise time with a positive or negative offset in seconds. ⏰
|
||||
required: false
|
||||
example: 0
|
||||
selector:
|
||||
|
|
@ -193,64 +188,64 @@ change_switch_settings:
|
|||
min: 0
|
||||
max: 86300
|
||||
sunrise_time:
|
||||
description: sunrise_time, in 'HH:MM:SS' format (if 'None', it uses the actual sunrise time at your location)
|
||||
description: Set a fixed time (HH:MM:SS) for sunrise. 🌅
|
||||
required: false
|
||||
example: ""
|
||||
example: ''
|
||||
selector:
|
||||
time:
|
||||
time: null
|
||||
sunset_offset:
|
||||
description: sunset_offset, in +/- seconds (integer)
|
||||
description: Adjust sunset time with a positive or negative offset in seconds. ⏰
|
||||
required: false
|
||||
example: ""
|
||||
example: ''
|
||||
selector:
|
||||
number:
|
||||
min: 0
|
||||
max: 86300
|
||||
sunset_time:
|
||||
description: sunset_time, in 'HH:MM:SS' format (if 'None', it uses the actual sunset time at your location)
|
||||
example: ""
|
||||
description: Set a fixed time (HH:MM:SS) for sunset. 🌇
|
||||
example: ''
|
||||
required: false
|
||||
selector:
|
||||
time:
|
||||
time: null
|
||||
max_sunrise_time:
|
||||
description: "max_sunrise_time: Manual override of the maximum sunrise time, if 'None', it uses the actual sunrise time at your location (HH:MM:SS)"
|
||||
example: ""
|
||||
description: Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier real sunrises. 🌅
|
||||
example: ''
|
||||
required: false
|
||||
selector:
|
||||
time:
|
||||
time: null
|
||||
min_sunset_time:
|
||||
description: "min_sunset_time: Manual override of the minimum sunset time, if 'None', it uses the actual sunset time at your location (HH:MM:SS)"
|
||||
example: ""
|
||||
description: Set the earliest virtual sunset time (HH:MM:SS), allowing for later real sunsets. 🌇
|
||||
example: ''
|
||||
required: false
|
||||
selector:
|
||||
time:
|
||||
time: null
|
||||
take_over_control:
|
||||
description: "take_over_control: If anything but Adaptive Lighting calls 'light.turn_on' when a light is already on, stop adapting that light until it (or the switch) toggles off -> on."
|
||||
description: Disable Adaptive Lighting if another source calls `light.turn_on` while lights are on and being adapted. Note that this calls `homeassistant.update_entity` every `interval`! 🔒
|
||||
required: false
|
||||
example: true
|
||||
selector:
|
||||
boolean:
|
||||
boolean: null
|
||||
detect_non_ha_changes:
|
||||
description: "detect_non_ha_changes: detects all >10% changes made to the lights (also outside of HA), requires 'take_over_control' to be enabled (calls 'homeassistant.update_entity' every 'interval'!)"
|
||||
description: Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️
|
||||
required: false
|
||||
example: false
|
||||
selector:
|
||||
boolean:
|
||||
boolean: null
|
||||
transition:
|
||||
description: "Transition time when applying a change to the lights (seconds)"
|
||||
description: Duration of transition when lights change, in seconds. 🕑
|
||||
required: false
|
||||
example: 45
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
adapt_delay:
|
||||
description: "adapt_delay: wait time between light turn on (seconds), and Adaptive Lights applying changes to the light state. May avoid flickering."
|
||||
description: Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️
|
||||
required: false
|
||||
example: 0
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
autoreset_control_seconds:
|
||||
description: "autoreset_control_seconds: wait time (seconds) before Adaptive Lighting resets `manual_control` status of any light (default: 0)"
|
||||
description: Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️
|
||||
required: false
|
||||
example: 0
|
||||
selector:
|
||||
text:
|
||||
text: null
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Choose a name for the Adaptive Lighting",
|
||||
"title": "Choose a name for the Adaptive Lighting instance",
|
||||
"description": "Every instance can contain multiple lights!",
|
||||
"data": {
|
||||
"name": "Name"
|
||||
|
|
@ -19,34 +19,35 @@
|
|||
"title": "Adaptive Lighting options",
|
||||
"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 turn 'off' to 'on'. (seconds)",
|
||||
"include_config_in_attributes": "include_config_in_attributes: All config options will be listed as attributes under the adaptive-lighting switch this integration creates. (default: false)",
|
||||
"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. (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).",
|
||||
"send_split_delay": "send_split_delay: wait between commands (milliseconds), when separate_turn_on_commands is used. May ensure that both commands are handled by the bulb correctly.",
|
||||
"sleep_brightness": "sleep_brightness, Brightness setting for Sleep Mode. (%)",
|
||||
"sleep_rgb_or_color_temp": "sleep_rgb_or_color_temp, use 'rgb_color' or 'color_temp'",
|
||||
"sleep_rgb_color": "sleep_rgb_color, in RGB",
|
||||
"sleep_color_temp": "sleep_color_temp: Color temperature setting for Sleep Mode. (Kelvin)",
|
||||
"sunrise_offset": "sunrise_offset: How long before(-) or after(+) to define the sunrise point of the cycle (+/- seconds)",
|
||||
"sunrise_time": "sunrise_time: Manual override of the sunrise time, if 'None', it uses the actual sunrise time at your location (HH:MM:SS)",
|
||||
"max_sunrise_time": "max_sunrise_time: Manual override of the maximum sunrise time, if 'None', it uses the actual sunrise time at your location (HH:MM:SS)",
|
||||
"sunset_offset": "sunset_offset: How long before(-) or after(+) to define the sunset point of the cycle (+/- seconds)",
|
||||
"sunset_time": "sunset_time: Manual override of the sunset time, if 'None', it uses the actual sunset time at your location (HH:MM:SS)",
|
||||
"min_sunset_time": "min_sunset_time: Manual override of the minimum sunset time, if 'None', it uses the actual sunset time at your location (HH:MM:SS)",
|
||||
"take_over_control": "take_over_control: If anything but Adaptive Lighting calls 'light.turn_on' when a light is already on, stop adapting that light until it (or the switch) toggles off -> on.",
|
||||
"detect_non_ha_changes": "detect_non_ha_changes: detects all >10% changes made to the lights (also outside of HA), requires 'take_over_control' to be enabled (calls 'homeassistant.update_entity' every 'interval'!)",
|
||||
"transition": "Transition time when applying a change to the lights (seconds)",
|
||||
"adapt_delay": "adapt_delay: wait time between light turn on (seconds), and Adaptive Lights applying changes to the light state. May avoid flickering.",
|
||||
"autoreset_control_seconds": "autoreset_control_seconds: wait time (seconds) before Adaptive Lighting resets `manual_control` status of any light (default: 0)"
|
||||
"lights": "lights: List of light entity_ids to be controlled (may be empty). 🌟",
|
||||
"prefer_rgb_color": "prefer_rgb_color: Whether to prefer RGB color adjustment over light color temperature when possible. 🌈",
|
||||
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝",
|
||||
"initial_transition": "initial_transition: Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️",
|
||||
"sleep_transition": "sleep_transition: Duration of transition when \"sleep mode\" is toggled in seconds. 😴",
|
||||
"transition": "transition: Duration of transition when lights change, in seconds. 🕑",
|
||||
"transition_until_sleep": "transition_until_sleep: When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning to these values after sunset. 🌙",
|
||||
"interval": "interval: Frequency to adapt the lights, in seconds. 🔄",
|
||||
"min_brightness": "min_brightness: Minimum brightness percentage. 💡",
|
||||
"max_brightness": "max_brightness: Maximum brightness percentage. 💡",
|
||||
"min_color_temp": "min_color_temp: Warmest color temperature in Kelvin. 🔥",
|
||||
"max_color_temp": "max_color_temp: Coldest color temperature in Kelvin. ❄️",
|
||||
"sleep_brightness": "sleep_brightness: Brightness percentage of lights in sleep mode. 😴",
|
||||
"sleep_rgb_or_color_temp": "sleep_rgb_or_color_temp: Use either `\"rgb_color\"` or `\"color_temp\"` in sleep mode. 🌙",
|
||||
"sleep_color_temp": "sleep_color_temp: Color temperature in sleep mode (used when `sleep_rgb_or_color_temp` is `color_temp`) in Kelvin. 😴",
|
||||
"sleep_rgb_color": "sleep_rgb_color: RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is \"rgb_color\"). 🌈",
|
||||
"sunrise_time": "sunrise_time: Set a fixed time (HH:MM:SS) for sunrise. 🌅",
|
||||
"max_sunrise_time": "max_sunrise_time: Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier real sunrises. 🌅",
|
||||
"sunrise_offset": "sunrise_offset: Adjust sunrise time with a positive or negative offset in seconds. ⏰",
|
||||
"sunset_time": "sunset_time: Set a fixed time (HH:MM:SS) for sunset. 🌇",
|
||||
"min_sunset_time": "min_sunset_time: Set the earliest virtual sunset time (HH:MM:SS), allowing for later real sunsets. 🌇",
|
||||
"sunset_offset": "sunset_offset: Adjust sunset time with a positive or negative offset in seconds. ⏰",
|
||||
"only_once": "only_once: Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄",
|
||||
"take_over_control": "take_over_control: Disable Adaptive Lighting if another source calls `light.turn_on` while lights are on and being adapted. Note that this calls `homeassistant.update_entity` every `interval`! 🔒",
|
||||
"detect_non_ha_changes": "detect_non_ha_changes: Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️",
|
||||
"separate_turn_on_commands": "separate_turn_on_commands: Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀",
|
||||
"send_split_delay": "send_split_delay: Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️",
|
||||
"adapt_delay": "adapt_delay: Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️",
|
||||
"autoreset_control_seconds": "autoreset_control_seconds: Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from __future__ import annotations
|
|||
import asyncio
|
||||
import base64
|
||||
import bisect
|
||||
from collections import defaultdict
|
||||
from collections.abc import Callable, Coroutine
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
|
|
@ -39,7 +39,6 @@ from homeassistant.components.light import (
|
|||
SUPPORT_COLOR,
|
||||
SUPPORT_COLOR_TEMP,
|
||||
SUPPORT_TRANSITION,
|
||||
VALID_TRANSITION,
|
||||
is_on,
|
||||
)
|
||||
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
|
|
@ -97,6 +96,7 @@ from .const import (
|
|||
ATTR_ADAPT_COLOR,
|
||||
ATTR_TURN_ON_OFF_LISTENER,
|
||||
CONF_ADAPT_DELAY,
|
||||
CONF_ADAPT_UNTIL_SLEEP,
|
||||
CONF_AUTORESET_CONTROL,
|
||||
CONF_DETECT_NON_HA_CHANGES,
|
||||
CONF_INCLUDE_CONFIG_IN_ATTRIBUTES,
|
||||
|
|
@ -136,11 +136,13 @@ from .const import (
|
|||
SERVICE_APPLY,
|
||||
SERVICE_CHANGE_SWITCH_SETTINGS,
|
||||
SERVICE_SET_MANUAL_CONTROL,
|
||||
SET_MANUAL_CONTROL_SCHEMA,
|
||||
SLEEP_MODE_SWITCH,
|
||||
SUN_EVENT_MIDNIGHT,
|
||||
SUN_EVENT_NOON,
|
||||
TURNING_OFF_DELAY,
|
||||
VALIDATION_TUPLES,
|
||||
apply_service_schema,
|
||||
replace_none_str,
|
||||
)
|
||||
|
||||
|
|
@ -494,20 +496,9 @@ async def async_setup_entry(
|
|||
domain=DOMAIN,
|
||||
service=SERVICE_APPLY,
|
||||
service_func=handle_apply,
|
||||
schema=vol.Schema(
|
||||
{
|
||||
vol.Optional("entity_id"): cv.entity_ids,
|
||||
vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids,
|
||||
vol.Optional(
|
||||
CONF_TRANSITION,
|
||||
default=switch._initial_transition, # pylint: disable=protected-access
|
||||
): VALID_TRANSITION,
|
||||
vol.Optional(ATTR_ADAPT_BRIGHTNESS, default=True): cv.boolean,
|
||||
vol.Optional(ATTR_ADAPT_COLOR, default=True): cv.boolean,
|
||||
vol.Optional(CONF_PREFER_RGB_COLOR, default=False): cv.boolean,
|
||||
vol.Optional(CONF_TURN_ON_LIGHTS, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
schema=apply_service_schema(
|
||||
switch._initial_transition
|
||||
), # pylint: disable=protected-access
|
||||
)
|
||||
|
||||
# Register `set_manual_control` service
|
||||
|
|
@ -515,13 +506,7 @@ async def async_setup_entry(
|
|||
domain=DOMAIN,
|
||||
service=SERVICE_SET_MANUAL_CONTROL,
|
||||
service_func=handle_set_manual_control,
|
||||
schema=vol.Schema(
|
||||
{
|
||||
vol.Optional("entity_id"): cv.entity_ids,
|
||||
vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids,
|
||||
vol.Optional(CONF_MANUAL_CONTROL, default=True): cv.boolean,
|
||||
}
|
||||
),
|
||||
schema=SET_MANUAL_CONTROL_SCHEMA,
|
||||
)
|
||||
|
||||
args = {vol.Optional(CONF_USE_DEFAULTS, default="current"): cv.string}
|
||||
|
|
@ -597,7 +582,7 @@ def _expand_light_groups(hass: HomeAssistant, lights: list[str]) -> list[str]:
|
|||
|
||||
def _supported_features(hass: HomeAssistant, light: str):
|
||||
state = hass.states.get(light)
|
||||
supported_features = state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
supported_features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
supported = {
|
||||
key for key, value in _SUPPORT_OPTS.items() if supported_features & value
|
||||
}
|
||||
|
|
@ -822,18 +807,16 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
|||
self._send_split_delay = data[CONF_SEND_SPLIT_DELAY]
|
||||
self._take_over_control = data[CONF_TAKE_OVER_CONTROL]
|
||||
self._detect_non_ha_changes = data[CONF_DETECT_NON_HA_CHANGES]
|
||||
if not data[CONF_TAKE_OVER_CONTROL] and (data[CONF_DETECT_NON_HA_CHANGES]):
|
||||
_LOGGER.warn(
|
||||
if not data[CONF_TAKE_OVER_CONTROL] and data[CONF_DETECT_NON_HA_CHANGES]:
|
||||
_LOGGER.warning(
|
||||
"%s: Config mismatch: 'detect_non_ha_changes: true' "
|
||||
" are set in config, however required"
|
||||
" variable 'take_over_control' is turned off. Please check your"
|
||||
" configuration to ensure desired functionality. We will now"
|
||||
" enable 'take_over_control' and continue setting up the"
|
||||
" adaptive-lighting integration normally.",
|
||||
"requires 'take_over_control' to be enabled. Adjusting config "
|
||||
"and continuing setup with `take_over_control: true`.",
|
||||
self._name,
|
||||
)
|
||||
self._take_over_control = True
|
||||
self._auto_reset_manual_control_time = data[CONF_AUTORESET_CONTROL]
|
||||
self._expand_light_groups() # updates manual control timers
|
||||
_loc = get_astral_location(self.hass)
|
||||
if isinstance(_loc, tuple):
|
||||
# Astral v2.2
|
||||
|
|
@ -845,6 +828,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
|||
self._sun_light_settings = SunLightSettings(
|
||||
name=self._name,
|
||||
astral_location=location,
|
||||
adapt_until_sleep=data[CONF_ADAPT_UNTIL_SLEEP],
|
||||
max_brightness=data[CONF_MAX_BRIGHTNESS],
|
||||
max_color_temp=data[CONF_MAX_COLOR_TEMP],
|
||||
min_brightness=data[CONF_MIN_BRIGHTNESS],
|
||||
|
|
@ -1048,7 +1032,8 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
|||
service_data = {ATTR_ENTITY_ID: light}
|
||||
features = _supported_features(self.hass, light)
|
||||
|
||||
if "transition" in features:
|
||||
# Check transition == 0 to fix #378
|
||||
if "transition" in features and transition > 0:
|
||||
service_data[ATTR_TRANSITION] = transition
|
||||
if "brightness" in features and adapt_brightness:
|
||||
brightness = round(255 * self._settings["brightness_pct"] / 100)
|
||||
|
|
@ -1076,7 +1061,19 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
|||
service_data[ATTR_RGB_COLOR] = self._settings["rgb_color"]
|
||||
|
||||
context = context or self.create_context("adapt_lights")
|
||||
self.turn_on_off_listener.last_service_data[light] = service_data
|
||||
|
||||
# See #80. Doesn't check if transitions differ but it does the job.
|
||||
last_service_data = self.turn_on_off_listener.last_service_data
|
||||
if last_service_data.get(light) == service_data:
|
||||
_LOGGER.debug(
|
||||
"%s: Cancelling adapt to light %s, there's no new values to set (context.id='%s')",
|
||||
self._name,
|
||||
light,
|
||||
context.id,
|
||||
)
|
||||
return
|
||||
else:
|
||||
self.turn_on_off_listener.last_service_data[light] = service_data
|
||||
|
||||
async def turn_on(service_data):
|
||||
_LOGGER.debug(
|
||||
|
|
@ -1131,6 +1128,19 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
|
|||
|
||||
if lights is None:
|
||||
lights = self._lights
|
||||
if not force and self._only_once:
|
||||
return
|
||||
|
||||
filtered_lights = []
|
||||
for light in lights:
|
||||
# Don't adapt lights that haven't finished prior transitions.
|
||||
if force or not self.turn_on_off_listener.transition_timers.get(light):
|
||||
filtered_lights.append(light)
|
||||
|
||||
if not filtered_lights:
|
||||
return
|
||||
|
||||
await self._adapt_lights(filtered_lights, transition, force, context)
|
||||
|
||||
if not force:
|
||||
if self._only_once:
|
||||
|
|
@ -1354,6 +1364,7 @@ class SunLightSettings:
|
|||
|
||||
name: str
|
||||
astral_location: astral.Location
|
||||
adapt_until_sleep: bool
|
||||
max_brightness: int
|
||||
max_color_temp: int
|
||||
min_brightness: int
|
||||
|
|
@ -1503,7 +1514,12 @@ class SunLightSettings:
|
|||
delta = self.max_color_temp - self.min_color_temp
|
||||
ct = (delta * percent) + self.min_color_temp
|
||||
return 5 * round(ct / 5) # round to nearest 5
|
||||
return self.min_color_temp
|
||||
if percent == 0 or not self.adapt_until_sleep:
|
||||
return self.min_color_temp
|
||||
if self.adapt_until_sleep and percent < 0:
|
||||
delta = abs(self.min_color_temp - self.sleep_color_temp)
|
||||
ct = (delta * abs(1 + percent)) + self.sleep_color_temp
|
||||
return 5 * round(ct / 5) # round to nearest 5
|
||||
|
||||
def get_settings(
|
||||
self, is_sleep, transition
|
||||
|
|
@ -1526,11 +1542,14 @@ class SunLightSettings:
|
|||
rgb_color: tuple[float, float, float] = color_temperature_to_rgb(
|
||||
color_temp_kelvin
|
||||
)
|
||||
# backwards compatibility for versions < 1.3.1 - see #403
|
||||
color_temp_mired: float = math.floor(1000000 / color_temp_kelvin)
|
||||
xy_color: tuple[float, float] = color_RGB_to_xy(*rgb_color)
|
||||
hs_color: tuple[float, float] = color_xy_to_hs(*xy_color)
|
||||
return {
|
||||
"brightness_pct": brightness_pct,
|
||||
"color_temp_kelvin": color_temp_kelvin,
|
||||
"color_temp_mired": color_temp_mired,
|
||||
"rgb_color": rgb_color,
|
||||
"xy_color": xy_color,
|
||||
"hs_color": hs_color,
|
||||
|
|
@ -1554,8 +1573,6 @@ class TurnOnOffListener:
|
|||
self.sleep_tasks: dict[str, asyncio.Task] = {}
|
||||
# Tracks which lights are manually controlled
|
||||
self.manual_control: dict[str, bool] = {}
|
||||
# Counts the number of times (in a row) a light had a changed state.
|
||||
self.cnt_significant_changes: dict[str, int] = defaultdict(int)
|
||||
# Track 'state_changed' events of self.lights resulting from this integration
|
||||
self.last_state_change: dict[str, list[State]] = {}
|
||||
# Track last 'service_data' to 'light.turn_on' resulting from this integration
|
||||
|
|
@ -1579,6 +1596,26 @@ class TurnOnOffListener:
|
|||
EVENT_STATE_CHANGED, self.state_changed_event_listener
|
||||
)
|
||||
|
||||
def _handle_timer(
|
||||
self,
|
||||
light: str,
|
||||
timers_dict: dict[str, _AsyncSingleShotTimer],
|
||||
delay: float | None,
|
||||
reset_coroutine: Callable[[], Coroutine[Any, Any, None]],
|
||||
) -> None:
|
||||
timer = timers_dict.get(light)
|
||||
if timer is not None:
|
||||
if delay is None: # Timer object exists, but should not anymore
|
||||
timer.cancel()
|
||||
timers_dict.pop(light)
|
||||
else: # Timer object already exists, just update the delay and restart it
|
||||
timer.delay = delay
|
||||
timer.start()
|
||||
elif delay is not None: # Timer object does not exist, create it
|
||||
timer = _AsyncSingleShotTimer(delay, reset_coroutine)
|
||||
timers_dict[light] = timer
|
||||
timer.start()
|
||||
|
||||
def start_transition_timer(self, light: str) -> None:
|
||||
"""Mark a light as manually controlled."""
|
||||
_LOGGER.debug("Start transition timer for %s", light)
|
||||
|
|
@ -1588,39 +1625,26 @@ class TurnOnOffListener:
|
|||
or light not in last_service_data
|
||||
or ATTR_TRANSITION not in last_service_data[light]
|
||||
):
|
||||
return False
|
||||
return
|
||||
|
||||
delay = last_service_data[light][ATTR_TRANSITION]
|
||||
timer = self.transition_timers.get(light)
|
||||
if timer is not None:
|
||||
if delay is None: # Timer object exists, but should not anymore
|
||||
timer.cancel()
|
||||
self.transition_timers.pop(light)
|
||||
else: # Timer object already exists, just update the delay and restart it
|
||||
timer.delay = delay
|
||||
timer.start()
|
||||
elif delay is not None: # Timer object does not exist, create it
|
||||
|
||||
async def reset():
|
||||
_LOGGER.debug(
|
||||
"Transition finished for light %s",
|
||||
light,
|
||||
async def reset():
|
||||
_LOGGER.debug(
|
||||
"Transition finished for light %s",
|
||||
light,
|
||||
)
|
||||
switches = _get_switches_with_lights(self.hass, [light])
|
||||
for switch in switches:
|
||||
if not switch.is_on:
|
||||
continue
|
||||
await switch._update_attrs_and_maybe_adapt_lights(
|
||||
[light],
|
||||
force=False,
|
||||
context=switch.create_context("transit"),
|
||||
)
|
||||
# This part is optional, we could just wait for the next interval.
|
||||
switches = _get_switches_with_lights(self.hass, [light])
|
||||
for switch in switches:
|
||||
if not switch.is_on:
|
||||
continue
|
||||
# pylint: disable=protected-access
|
||||
await switch._update_attrs_and_maybe_adapt_lights(
|
||||
[light],
|
||||
force=False,
|
||||
context=switch.create_context("transit"),
|
||||
)
|
||||
|
||||
timer = _AsyncSingleShotTimer(delay, reset)
|
||||
self.transition_timers[light] = timer
|
||||
timer.start()
|
||||
self._handle_timer(light, self.transition_timers, delay, reset)
|
||||
|
||||
def set_auto_reset_manual_control_times(self, lights: list[str], time: float):
|
||||
"""Set the time after which the lights are automatically reset."""
|
||||
|
|
@ -1644,40 +1668,28 @@ class TurnOnOffListener:
|
|||
_LOGGER.debug("Marking '%s' as manually controlled.", light)
|
||||
self.manual_control[light] = True
|
||||
delay = self.auto_reset_manual_control_times.get(light)
|
||||
timer = self.auto_reset_manual_control_timers.get(light)
|
||||
if timer is not None:
|
||||
if delay is None: # Timer object exists, but should not anymore
|
||||
timer.cancel()
|
||||
self.auto_reset_manual_control_timers.pop(light)
|
||||
else: # Timer object already exists, just update the delay and restart it
|
||||
timer.delay = delay
|
||||
timer.start()
|
||||
elif delay is not None: # Timer object does not exist, create it
|
||||
|
||||
async def reset():
|
||||
self.reset(light)
|
||||
switches = _get_switches_with_lights(self.hass, [light])
|
||||
for switch in switches:
|
||||
if not switch.is_on:
|
||||
continue
|
||||
# pylint: disable=protected-access
|
||||
await switch._update_attrs_and_maybe_adapt_lights(
|
||||
[light],
|
||||
transition=switch._initial_transition,
|
||||
force=True,
|
||||
context=switch.create_context("autoreset"),
|
||||
)
|
||||
_LOGGER.debug(
|
||||
"Auto resetting 'manual_control' status of '%s' because"
|
||||
" it was not manually controlled for %s seconds.",
|
||||
light,
|
||||
delay,
|
||||
async def reset():
|
||||
self.reset(light)
|
||||
switches = _get_switches_with_lights(self.hass, [light])
|
||||
for switch in switches:
|
||||
if not switch.is_on:
|
||||
continue
|
||||
await switch._update_attrs_and_maybe_adapt_lights(
|
||||
[light],
|
||||
transition=switch._initial_transition,
|
||||
force=True,
|
||||
context=switch.create_context("autoreset"),
|
||||
)
|
||||
assert not self.manual_control[light]
|
||||
_LOGGER.debug(
|
||||
"Auto resetting 'manual_control' status of '%s' because"
|
||||
" it was not manually controlled for %s seconds.",
|
||||
light,
|
||||
delay,
|
||||
)
|
||||
assert not self.manual_control[light]
|
||||
|
||||
timer = _AsyncSingleShotTimer(delay, reset)
|
||||
self.auto_reset_manual_control_timers[light] = timer
|
||||
timer.start()
|
||||
self._handle_timer(light, self.auto_reset_manual_control_timers, delay, reset)
|
||||
|
||||
def reset(self, *lights, reset_manual_control=True) -> None:
|
||||
"""Reset the 'manual_control' status of the lights."""
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"step": {
|
||||
"user": {
|
||||
"title": "Choose a name for the Adaptive Lighting instance",
|
||||
"description": "Pick a name for this instance. You can run several instances of Adaptive lighting, each of these can contain multiple lights!",
|
||||
"description": "Every instance can contain multiple lights!",
|
||||
"data": {
|
||||
"name": "Name"
|
||||
}
|
||||
|
|
@ -20,33 +20,35 @@
|
|||
"title": "Adaptive Lighting options",
|
||||
"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'. (seconds)",
|
||||
"include_config_in_attributes": "include_config_in_attributes: All config options will be listed as attributes under the adaptive-lighting switch this integration creates. (default: false)",
|
||||
"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. (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).",
|
||||
"send_split_delay": "send_split_delay: wait between commands (milliseconds), when separate_turn_on_commands is used. May ensure that both commands are handled by the bulb correctly.",
|
||||
"sleep_brightness": "sleep_brightness, Brightness setting for Sleep Mode. (%)",
|
||||
"sleep_rgb_or_color_temp": "sleep_rgb_or_color_temp, use 'rgb_color' or 'color_temp'",
|
||||
"sleep_rgb_color": "sleep_rgb_color, in RGB",
|
||||
"sleep_color_temp": "sleep_color_temp: Color temperature setting for Sleep Mode. (Kelvin)",
|
||||
"sunrise_offset": "sunrise_offset: How long before(-) or after(+) to define the sunrise point of the cycle (+/- seconds)",
|
||||
"sunrise_time": "sunrise_time: Manual override of the sunrise time, if 'None', it uses the actual sunrise time at your location (HH:MM:SS)",
|
||||
"max_sunrise_time": "max_sunrise_time: Manual override of the maximum sunrise time, if 'None', it uses the actual sunrise time at your location (HH:MM:SS)",
|
||||
"sunset_offset": "sunset_offset: How long before(-) or after(+) to define the sunset point of the cycle (+/- seconds)",
|
||||
"sunset_time": "sunset_time: Manual override of the sunset time, if 'None', it uses the actual sunset time at your location (HH:MM:SS)",
|
||||
"min_sunset_time": "min_sunset_time: Manual override of the minimum sunset time, if 'None', it uses the actual sunset time at your location (HH:MM:SS)",
|
||||
"take_over_control": "take_over_control: If anything but Adaptive Lighting calls 'light.turn_on' when a light is already on, stop adapting that light until it (or the switch) toggles off -> on.",
|
||||
"detect_non_ha_changes": "detect_non_ha_changes: detects all >10% changes made to the lights (also outside of HA), requires 'take_over_control' to be enabled (calls 'homeassistant.update_entity' every 'interval'!)",
|
||||
"transition": "Transition time when applying a change to the lights (seconds)",
|
||||
"adapt_delay": "adapt_delay: wait time between light turn on (seconds), and Adaptive Lights applying changes to the light state. May avoid flickering."
|
||||
"lights": "lights: List of light entity_ids to be controlled (may be empty). 🌟",
|
||||
"prefer_rgb_color": "prefer_rgb_color: Whether to prefer RGB color adjustment over light color temperature when possible. 🌈",
|
||||
"include_config_in_attributes": "include_config_in_attributes: Show all options as attributes on the switch in Home Assistant when set to `true`. 📝",
|
||||
"initial_transition": "initial_transition: Duration of the first transition when lights turn from `off` to `on` in seconds. ⏲️",
|
||||
"sleep_transition": "sleep_transition: Duration of transition when \"sleep mode\" is toggled in seconds. 😴",
|
||||
"transition": "transition: Duration of transition when lights change, in seconds. 🕑",
|
||||
"transition_until_sleep": "transition_until_sleep: When enabled, Adaptive Lighting will treat sleep settings as the minimum, transitioning to these values after sunset. 🌙",
|
||||
"interval": "interval: Frequency to adapt the lights, in seconds. 🔄",
|
||||
"min_brightness": "min_brightness: Minimum brightness percentage. 💡",
|
||||
"max_brightness": "max_brightness: Maximum brightness percentage. 💡",
|
||||
"min_color_temp": "min_color_temp: Warmest color temperature in Kelvin. 🔥",
|
||||
"max_color_temp": "max_color_temp: Coldest color temperature in Kelvin. ❄️",
|
||||
"sleep_brightness": "sleep_brightness: Brightness percentage of lights in sleep mode. 😴",
|
||||
"sleep_rgb_or_color_temp": "sleep_rgb_or_color_temp: Use either `\"rgb_color\"` or `\"color_temp\"` in sleep mode. 🌙",
|
||||
"sleep_color_temp": "sleep_color_temp: Color temperature in sleep mode (used when `sleep_rgb_or_color_temp` is `color_temp`) in Kelvin. 😴",
|
||||
"sleep_rgb_color": "sleep_rgb_color: RGB color in sleep mode (used when `sleep_rgb_or_color_temp` is \"rgb_color\"). 🌈",
|
||||
"sunrise_time": "sunrise_time: Set a fixed time (HH:MM:SS) for sunrise. 🌅",
|
||||
"max_sunrise_time": "max_sunrise_time: Set the latest virtual sunrise time (HH:MM:SS), allowing for earlier real sunrises. 🌅",
|
||||
"sunrise_offset": "sunrise_offset: Adjust sunrise time with a positive or negative offset in seconds. ⏰",
|
||||
"sunset_time": "sunset_time: Set a fixed time (HH:MM:SS) for sunset. 🌇",
|
||||
"min_sunset_time": "min_sunset_time: Set the earliest virtual sunset time (HH:MM:SS), allowing for later real sunsets. 🌇",
|
||||
"sunset_offset": "sunset_offset: Adjust sunset time with a positive or negative offset in seconds. ⏰",
|
||||
"only_once": "only_once: Adapt lights only when they are turned on (`true`) or keep adapting them (`false`). 🔄",
|
||||
"take_over_control": "take_over_control: Disable Adaptive Lighting if another source calls `light.turn_on` while lights are on and being adapted. Note that this calls `homeassistant.update_entity` every `interval`! 🔒",
|
||||
"detect_non_ha_changes": "detect_non_ha_changes: Detect non-`light.turn_on` state changes and stop adapting lights. Requires `take_over_control`. 🕵️",
|
||||
"separate_turn_on_commands": "separate_turn_on_commands: Use separate `light.turn_on` calls for color and brightness, needed for some light types. 🔀",
|
||||
"send_split_delay": "send_split_delay: Delay (ms) between `separate_turn_on_commands` for lights that don't support simultaneous brightness and color setting. ⏲️",
|
||||
"adapt_delay": "adapt_delay: Wait time (seconds) between light turn on and Adaptive Lighting applying changes. Might help to avoid flickering. ⏲️",
|
||||
"autoreset_control_seconds": "autoreset_control_seconds: Automatically reset the manual control after a number of seconds. Set to 0 to disable. ⏲️"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -530,11 +530,18 @@ async def test_manual_control(hass):
|
|||
await turn_switch(True, entity_id)
|
||||
assert not manual_control[ENTITY_LIGHT]
|
||||
|
||||
# Check that manual control is still enabled if set while bulb is off.
|
||||
# Test issue #37
|
||||
await turn_light(False)
|
||||
await change_manual_control(True)
|
||||
await turn_light(True)
|
||||
assert manual_control[ENTITY_LIGHT]
|
||||
|
||||
# Check that when 'adapt_brightness' is off, changing the brightness
|
||||
# doesn't mark it as manually controlled but changing color_temp
|
||||
# does
|
||||
await turn_light(False) # reset manually controlled status
|
||||
await turn_light(True)
|
||||
await turn_light(False)
|
||||
await turn_light(True) # reset manually controlled status
|
||||
assert not manual_control[ENTITY_LIGHT]
|
||||
await switch.adapt_brightness_switch.async_turn_off()
|
||||
await turn_light(True, brightness=increased_brightness())
|
||||
|
|
@ -695,101 +702,6 @@ async def test_apply_service(hass):
|
|||
assert old_state[ATTR_COLOR_TEMP_KELVIN] == new_state[ATTR_COLOR_TEMP_KELVIN]
|
||||
|
||||
|
||||
async def test_significant_change(hass):
|
||||
"""Test significant change."""
|
||||
|
||||
async def turn_light(state, **kwargs):
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON if state else SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: ENTITY_LIGHT, **kwargs},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async def update(force):
|
||||
await switch._update_attrs_and_maybe_adapt_lights(
|
||||
transition=0,
|
||||
context=switch.create_context("test"),
|
||||
force=force,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async def change_switch_settings(**kwargs):
|
||||
await hass.services.async_call(
|
||||
DOMAIN,
|
||||
SERVICE_CHANGE_SWITCH_SETTINGS,
|
||||
{
|
||||
ATTR_ENTITY_ID: ENTITY_SWITCH,
|
||||
**kwargs,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async def set_brightness(val: int):
|
||||
hass.states.async_set(
|
||||
ENTITY_LIGHT, "on", {ATTR_BRIGHTNESS: val, ATTR_SUPPORTED_FEATURES: 1}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async def do_nothing(entity_id):
|
||||
_LOGGER.debug("update entity successfully replaced for %s", entity_id)
|
||||
return None
|
||||
|
||||
switch, (bed_light_instance, *_) = await setup_lights_and_switch(hass)
|
||||
_LOGGER.debug("Test detect_non_ha_changes:")
|
||||
switch._take_over_control = True
|
||||
assert switch._take_over_control
|
||||
switch._detect_non_ha_changes = True
|
||||
assert switch._detect_non_ha_changes
|
||||
switch._alt_detect_method = False
|
||||
assert not switch._alt_detect_method
|
||||
|
||||
# build last service data
|
||||
await update(force=False)
|
||||
|
||||
# force=True should not reset manual control.
|
||||
await turn_light(True, brightness=40)
|
||||
await turn_light(True, brightness=20)
|
||||
await update(force=False)
|
||||
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||
await update(force=True)
|
||||
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||
|
||||
# turn light off then on should reset manual control.
|
||||
await turn_light(False)
|
||||
await turn_light(True)
|
||||
assert not switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||
|
||||
# Assert last_service_data got filled from update()
|
||||
# Assert last_state_change got filled from update()
|
||||
await update(force=True)
|
||||
assert switch.turn_on_off_listener.last_service_data.get(ENTITY_LIGHT) is not None
|
||||
assert switch.turn_on_off_listener.last_state_change.get(ENTITY_LIGHT) is not None
|
||||
|
||||
# Simulate a transition to 255 where the update() is already using brightness 255.
|
||||
await set_brightness(240)
|
||||
await set_brightness(244)
|
||||
await set_brightness(247)
|
||||
await set_brightness(250)
|
||||
|
||||
# last_state_change should have our state changes.
|
||||
# Change brightness by async_set (not using 'light.turn_on')
|
||||
new_brightness = 50
|
||||
await set_brightness(new_brightness)
|
||||
_LOGGER.debug("Test: Brightness set to %s", new_brightness)
|
||||
|
||||
# Override update_entity() to do nothing. Otherwise what happens is
|
||||
# update_entity() refreshes the state to the last call of
|
||||
# light.turn_on().
|
||||
switch.hass.helpers.entity_component.async_update_entity = do_nothing
|
||||
# On next update ENTITY_LIGHT should be marked as manually controlled
|
||||
await update(force=False)
|
||||
assert ENTITY_LIGHT in switch.turn_on_off_listener.last_state_change
|
||||
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||
|
||||
|
||||
async def test_switch_off_on_off(hass):
|
||||
"""Test switch rapid off_on_off."""
|
||||
|
||||
|
|
@ -840,6 +752,85 @@ async def test_switch_off_on_off(hass):
|
|||
assert state == STATE_OFF
|
||||
|
||||
|
||||
async def test_significant_change(hass):
|
||||
"""Test significant change."""
|
||||
|
||||
async def turn_light(state, **kwargs):
|
||||
await hass.services.async_call(
|
||||
LIGHT_DOMAIN,
|
||||
SERVICE_TURN_ON if state else SERVICE_TURN_OFF,
|
||||
{ATTR_ENTITY_ID: ENTITY_LIGHT, **kwargs},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async def update(force):
|
||||
await switch._update_attrs_and_maybe_adapt_lights(
|
||||
transition=0,
|
||||
context=switch.create_context("test"),
|
||||
force=force,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async def set_brightness(val: int):
|
||||
hass.states.async_set(
|
||||
ENTITY_LIGHT, "on", {ATTR_BRIGHTNESS: val, ATTR_SUPPORTED_FEATURES: 1}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
switch, _ = await setup_lights_and_switch(hass)
|
||||
_LOGGER.debug("Test detect_non_ha_changes:")
|
||||
switch._take_over_control = True
|
||||
assert switch._take_over_control
|
||||
switch._detect_non_ha_changes = True
|
||||
assert switch._detect_non_ha_changes
|
||||
|
||||
# build last service data
|
||||
await update(force=False)
|
||||
|
||||
# force=True should not reset manual control.
|
||||
await turn_light(True, brightness=40)
|
||||
await turn_light(True, brightness=20)
|
||||
await update(force=False)
|
||||
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||
await update(force=True)
|
||||
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||
|
||||
# turn light off then on should reset manual control.
|
||||
await turn_light(False)
|
||||
await turn_light(True)
|
||||
assert not switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||
|
||||
# Assert last_service_data got filled from update()
|
||||
await update(force=True)
|
||||
assert switch.turn_on_off_listener.last_service_data.get(ENTITY_LIGHT) is not None
|
||||
|
||||
# Simulate a transition to 255 where the update() is already using brightness 255.
|
||||
await set_brightness(240)
|
||||
await set_brightness(244)
|
||||
await set_brightness(247)
|
||||
await set_brightness(250)
|
||||
|
||||
# last_state_change should have our state changes.
|
||||
# Change brightness by async_set (not using 'light.turn_on')
|
||||
new_brightness = 50
|
||||
await set_brightness(new_brightness)
|
||||
_LOGGER.debug("Test: Brightness set to %s", new_brightness)
|
||||
|
||||
# mock homeassistant.core.HomeAssistant.helpers.entity_component.async_update_entity
|
||||
# Otherwise what happens is update_entity() refreshes the state to the last call of
|
||||
# light.turn_on(). This is because we are not using hass.states.async_set() to
|
||||
# set the brightness of the light. We mock `async_update_ha_state` because
|
||||
# `async_update_entity` calls it.
|
||||
with patch("homeassistant.helpers.entity.Entity.async_update_ha_state"):
|
||||
# On next update ENTITY_LIGHT should be marked as manually controlled
|
||||
await update(force=False)
|
||||
assert (
|
||||
switch.turn_on_off_listener.last_service_data.get(ENTITY_LIGHT) is not None
|
||||
)
|
||||
assert switch.turn_on_off_listener.manual_control[ENTITY_LIGHT]
|
||||
|
||||
|
||||
def test_color_difference_redmean():
|
||||
"""Test color_difference_redmean function."""
|
||||
for _ in range(10):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue