Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
139 commits
Select commit Hold shift + click to select a range
7c29e32
start implementing profile settings, as suggested in https://github.c…
basnijholt Sep 6, 2020
ae64db7
initial implementation of profiles
basnijholt Sep 6, 2020
7cbe6a4
another attempt
basnijholt Sep 7, 2020
ad57fb0
add option to add multiple profiles
basnijholt Sep 7, 2020
cd31b5d
profiles -> profile
basnijholt Sep 7, 2020
7e41621
setup sensor for default profile
basnijholt Sep 7, 2020
18561e5
fix import
basnijholt Sep 7, 2020
24f422c
make switch use profile
basnijholt Sep 7, 2020
c18cb47
fix doc-string
basnijholt Sep 7, 2020
6a0f12a
setup a sensor per profile
basnijholt Sep 7, 2020
b16d69b
switch doesn't need to have the profile as attribute
basnijholt Sep 7, 2020
4aff61d
move to single switch
basnijholt Sep 11, 2020
79944c9
use astral location from HA
basnijholt Sep 11, 2020
8cd6a25
add transition=self._initial_transition
basnijholt Sep 11, 2020
99490b4
switch to a simpler setup
basnijholt Sep 12, 2020
b0e8900
rename ct -> mired
basnijholt Sep 12, 2020
8dc0b59
remove elevation
basnijholt Sep 12, 2020
7f99426
define CONF_TRANSITION
basnijholt Sep 12, 2020
b7245ec
no need to cast to a different type
basnijholt Sep 12, 2020
36f82e8
comments and style
basnijholt Sep 12, 2020
fe6ed9f
get times from astral in UTC
basnijholt Sep 12, 2020
2b891c5
simplify calculation
basnijholt Sep 12, 2020
47a4db2
divide percent by 100 and fix updating of attrs
basnijholt Sep 12, 2020
4f0b064
use self.unsub_tracker instead of _state
basnijholt Sep 12, 2020
cf41492
change default interval to 90
basnijholt Sep 12, 2020
667c645
BRIGHT -> BRIGHTNESS
basnijholt Sep 12, 2020
6964d08
add config flow basics
basnijholt Sep 12, 2020
1331b79
small changes
basnijholt Sep 13, 2020
f444a8a
need to change the name of folder to get strings.json working
basnijholt Sep 13, 2020
6e5668e
remove lights_brightness, lights_mired, lights_rgb, lights_xy with "l…
basnijholt Sep 15, 2020
4898cbc
split _adjust_lights
basnijholt Sep 15, 2020
d4fcaa4
colortemp -> color_temp
basnijholt Sep 15, 2020
276d991
unpack light groups
basnijholt Sep 15, 2020
0b1be00
use events
basnijholt Sep 15, 2020
b0b11d0
Revert "use events"
basnijholt Sep 15, 2020
4870b7c
add missing await
basnijholt Sep 15, 2020
7b79096
simplification and don't add trackers when lights is []
basnijholt Sep 15, 2020
c4bb689
update config_flow.py
basnijholt Sep 15, 2020
48c16ff
unify configs
basnijholt Sep 15, 2020
954e022
unify config options flow and YAML schema
basnijholt Sep 15, 2020
909584a
small fixes
basnijholt Sep 16, 2020
9d06be0
WIP
basnijholt Sep 19, 2020
aea2e83
cleanup unused imports
basnijholt Sep 19, 2020
9593bca
simplify _calc_percent
basnijholt Sep 19, 2020
edfaedb
use globals
basnijholt Sep 19, 2020
8d8a409
add _ALLOWED_ORDERS check
basnijholt Sep 19, 2020
d63abb4
commit working version but not with all options
basnijholt Sep 20, 2020
a18a312
styling
basnijholt Sep 20, 2020
f778f45
remove backticks from strings.json
basnijholt Sep 20, 2020
b37e006
add semi working version with positive_time_dict
basnijholt Sep 20, 2020
f8c1a50
convert strings to timedeltas and datetimes
basnijholt Sep 20, 2020
0ed8ab4
fix most of the settings by hardcoding 'none'
basnijholt Sep 20, 2020
71cd8f7
style
basnijholt Sep 20, 2020
355f88b
add comment
basnijholt Sep 20, 2020
ee607bc
use FAKE_NONE
basnijholt Sep 20, 2020
575b81c
rename FAKE_NONE to None
basnijholt Sep 20, 2020
2b0eaff
validate options
basnijholt Sep 23, 2020
5a4da95
use VALIDATION in switch.py
basnijholt Sep 23, 2020
d33a96f
simplify validation
basnijholt Sep 23, 2020
5ff1da3
simplify schema creation
basnijholt Sep 23, 2020
cadd827
add comment
basnijholt Sep 23, 2020
3b275e1
start with YAML config validation
basnijholt Sep 23, 2020
d4123ee
use defaults
basnijholt Sep 23, 2020
b67b25d
coerce to serializable type
basnijholt Sep 24, 2020
5168fe9
fix title
basnijholt Sep 24, 2020
e9ee147
fix data in switch
basnijholt Sep 24, 2020
28389eb
abort if already setup
basnijholt Sep 24, 2020
2c0ab9d
do not show options if managed via YAML
basnijholt Sep 24, 2020
59f8f74
simplify setting up DOMAIN_SCHEMA
basnijholt Sep 24, 2020
8618cd8
create validate function
basnijholt Sep 24, 2020
be6dd97
don't pass name into AdaptiveSwitch
basnijholt Sep 24, 2020
049fb8b
rename FAKE_NONE -> NONE_STR
basnijholt Sep 24, 2020
1aa6f8f
set name correctly
basnijholt Sep 24, 2020
bd7edb5
simplify validate_options
basnijholt Sep 24, 2020
5b11893
add apply service
basnijholt Sep 25, 2020
21cc22d
call even if off
basnijholt Sep 25, 2020
ab069de
simplify
basnijholt Sep 25, 2020
ab61cda
add more options to apply service
basnijholt Sep 25, 2020
34acd03
rename replace_none -> replace_none_str
basnijholt Sep 26, 2020
8f4bd6b
rename variable
basnijholt Sep 26, 2020
685e4cd
rephase some text
basnijholt Sep 26, 2020
2109921
rename function
basnijholt Sep 26, 2020
5be0462
fix ha/core pre-commit issues and submit this code as PR
basnijholt Sep 26, 2020
52fa727
sync with HA/core PR
basnijholt Sep 26, 2020
ef04036
fix bug and use vol.In for entity selection
basnijholt Sep 26, 2020
478200b
fix pylint
basnijholt Sep 26, 2020
db63c5a
fix strings
basnijholt Sep 26, 2020
5ee3575
fix the 'off' -> 'on' -> 'off' switches
basnijholt Sep 26, 2020
199d31b
add disable_color option
basnijholt Sep 26, 2020
869ff91
rename disable_color -> disable_color_adjust
basnijholt Sep 26, 2020
a4b4ef3
fix problem with turn_off transition
basnijholt Sep 27, 2020
7575d4d
remove indentation level in _maybe_cancel
basnijholt Sep 27, 2020
2f1531c
use light.turn_on
basnijholt Sep 27, 2020
5f7e8af
log fixes
basnijholt Sep 27, 2020
6a5ded6
update to latest PR
basnijholt Sep 28, 2020
555d405
simplify turn on and off listening
basnijholt Sep 28, 2020
d198528
add hacs.json
basnijholt Sep 28, 2020
6bb5e42
update yaml settings
basnijholt Sep 28, 2020
e6e51da
always use color_temp over rgb
basnijholt Sep 28, 2020
0b1ff11
fixup
basnijholt Sep 28, 2020
0c76334
update to latest PR https://github.com/home-assistant/core/pull/40626
basnijholt Sep 28, 2020
5167dc6
fix toggle bug
basnijholt Sep 28, 2020
02923a3
do not raise but call self._abort_if_unique_id_configured
basnijholt Sep 28, 2020
9582357
pylint fix
basnijholt Sep 28, 2020
d18b2a9
fix unsub sub
basnijholt Sep 28, 2020
ee71742
fix turning on and off, delay setup listeners until HA start
basnijholt Sep 29, 2020
58b4c63
define and use switch._unsub_trackers
basnijholt Sep 29, 2020
d06d4e7
fix bug
basnijholt Sep 29, 2020
297539a
fixes
basnijholt Sep 29, 2020
37f110f
use locks
basnijholt Sep 29, 2020
9bb16f1
sync with PR
basnijholt Sep 29, 2020
53bf687
rename to adapt_brightness, adapt_color_temp, and adapt_rgb_color
basnijholt Sep 29, 2020
eda8c97
rename again
basnijholt Sep 29, 2020
1c88ecc
renames
basnijholt Sep 29, 2020
f07d128
expand
basnijholt Sep 29, 2020
09b835c
fix unsubs
basnijholt Sep 29, 2020
f2fcd1a
better strings
basnijholt Sep 29, 2020
d304b49
fix setting state before turn_on
basnijholt Sep 30, 2020
efcc268
comments and strings
basnijholt Sep 30, 2020
d57ef3f
sync PR
basnijholt Sep 30, 2020
8d4e0d1
synx
basnijholt Sep 30, 2020
8ec6230
exception
basnijholt Oct 1, 2020
303e2fe
add sleep_mode switch and deprecate disable_state/entity
basnijholt Oct 2, 2020
d29649b
fix temp issue
basnijholt Oct 2, 2020
3eb593e
add take_over_control feat
basnijholt Oct 3, 2020
7f6deaa
bug fixes
basnijholt Oct 3, 2020
60cc02a
detect significant changes
basnijholt Oct 4, 2020
1684844
add detect_non_ha_changes option
basnijholt Oct 4, 2020
e81d259
change opt order
basnijholt Oct 4, 2020
c245298
sync PR
basnijholt Oct 4, 2020
51340c6
add basic instructions
basnijholt Oct 4, 2020
773a955
bug fix
basnijholt Oct 5, 2020
91735eb
register service after async_add_entities
basnijholt Oct 5, 2020
6c6fe59
change order
basnijholt Oct 5, 2020
f1c2101
add 'manually_controlled' attribute to switch
basnijholt Oct 5, 2020
313c1ad
track state_changed
basnijholt Oct 5, 2020
ba44113
fixes
basnijholt Oct 6, 2020
8ba4603
keep contexts around that identify where they come from
basnijholt Oct 7, 2020
c4db73d
fixes
basnijholt Oct 7, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 6 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,20 @@
# Circadian Lighting [[Home Assistant](https://www.home-assistant.io/) Component]
## Stay healthier and sleep better by syncing your lights with natural daylight to maintain your circadian rhythm!
# Adaptive Lighting component

![Circadian Light Rhythm|690x287](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/5/f/5fe7a780e9f8905fea4d1cbb66cdbe35858a6e36.jpg)
Try out this code by adding https://github.com/basnijholt/adaptive-lighting to your custom repos in HACS and install it!

Circadian Lighting slowly synchronizes your color changing lights with the regular naturally occurring color temperature of the sky throughout the day. This gives your environment a more natural feel, with cooler hues during the midday and warmer tints near twilight and dawn.
I have not written any docs yet, so I recommend to use the UI to add this integration.

In addition, Circadian Lighting can set your lights to a nice cool white at 1% in “Sleep” mode, which is far brighter than starlight but won’t reset your circadian rhythm or break down too much rhodopsin in your eyes.
See [this video on Reddit](https://www.reddit.com/r/homeassistant/comments/j09219/any_circadian_lighting_users_good_news_i_just/) to see how to add the integration and set the options.


<details><summary>Expand for articles explaining the benefits of maintaining a natural Circadian rhythm</summary>

* [Circadian Rhythms - National Institute of General Medical Sciences](https://www.nigms.nih.gov/Education/Pages/Factsheet_CircadianRhythms.aspx)
* [Circadian Rhythms Linked to Aging and Well-Being | Psychology Today](https://www.psychologytoday.com/us/blog/the-athletes-way/201306/circadian-rhythms-linked-aging-and-well-being)
* [Maintaining a daily rhythm is important for mental health, study suggests - CNN](https://www.cnn.com/2018/05/15/health/circadian-rhythm-mood-disorder-study/index.html)
* [How Nobel Winning Circadian Rhythm Research Benefits Pregnancy](https://www.healthypregnancy.com/how-nobel-prize-winning-circadian-rhythms-research-benefits-a-healthy-pregnancy/)
* [Body Clock & Sleep - National Sleep Foundation](https://sleepfoundation.org/sleep-topics/sleep-drive-and-your-body-clock)
* [How our body’s circadian clocks affect our health beyond sleep](https://www.theverge.com/2018/6/12/17453398/sleep-circadian-code-satchin-panda-clock-health-science)

</details>

### Visit the [Wiki](https://github.com/claytonjn/hass-circadian_lighting/wiki) for more information.
<hr>

## Basic Installation/Configuration Instructions:

#### Installation:
Install `custom_component` files automatically using [HACS](https://github.com/claytonjn/hass-circadian_lighting/wiki/Installation-Instructions#hacs) or [Custom Updater](https://github.com/claytonjn/hass-circadian_lighting/wiki/Installation-Instructions#custom-updater), or install [Manually](https://github.com/claytonjn/hass-circadian_lighting/wiki/Installation-Instructions#manual-installation).

[![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg?style=for-the-badge)](https://github.com/custom-components/hacs)

#### Component Configuration:
```yaml
# Example configuration.yaml entry
circadian_lighting:
```
[_Advanced Configuration_](https://github.com/claytonjn/hass-circadian_lighting/wiki/Advanced-Configuration#component-configuration-variables)

#### Switch Configuration:
```yaml
# Example configuration.yaml entry
switch:
- platform: circadian_lighting
lights_ct:
- light.desk
- light.lamp
```
Switch configuration variables:
* **name** (_Optional_): The name to use when displaying this switch.
* **lights_ct** (_Optional_): array: List of light entities which should be set in mireds.
* **lights_rgb** (_Optional_): array: List of light entities which should be set in RGB.
* **lights_xy** (_Optional_): array: List of light entities which should be set in XY.
* **lights_brightness** (_Optional_): array: List of light entities which should only have brightness adjusted.

[_Advanced Configuration_](https://github.com/claytonjn/hass-circadian_lighting/wiki/Advanced-Configuration#switch-configuration-variables)

<hr>

### Graphs!
These graphs were generated using the values calculated by the Circadian Lighting sensor/switch(es).
These graphs were generated using the values calculated by the Adaptive Lighting sensor/switch(es).

##### Sun Position:
![cl_percent|690x131](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/6/5/657ff98beb65a94598edeb4bdfd939095db1a22c.PNG)

##### Color Temperature:
![cl_colortemp|690x129](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/5/9/59e84263cbecd8e428cb08777a0413672c48dfcd.PNG)
![cl_color_temp|690x129](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/5/9/59e84263cbecd8e428cb08777a0413672c48dfcd.PNG)

##### Brightness:
![cl_brightness|690x130](https://community-home-assistant-assets.s3.dualstack.us-west-2.amazonaws.com/original/3X/5/8/58ebd994b62a8b1abfb3497a5288d923ff4e2330.PNG)
111 changes: 111 additions & 0 deletions custom_components/adaptive_lighting/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""Adaptive Lighting integration in Home-Assistant.

This integration calculates color temperature and brightness to synchronize
your color-changing lights with the perceived color temperature of the sky
throughout the day. This gives your environment a more natural feel, with
cooler whites during the midday and warmer tints near twilight and dawn.

Additionally, the integration sets your lights to a nice warm white at 1% in
"Sleep mode", which is far brighter than starlight but won't reset your
circadian rhythm or break down too much rhodopsin in your eyes.

Human circadian rhythms are heavily influenced by ambient light levels and
hues. Hormone production, brainwave activity, mood, and wakefulness are
just some of the cognitive functions tied to cyclical natural light.

Resources:
- http://en.wikipedia.org/wiki/Zeitgeber
- http://www.cambridgeincolour.com/tutorials/sunrise-sunset-calculator.htm
- http://en.wikipedia.org/wiki/Color_temperature

## Notes
* Only your location is taken into account to calculate the the sun's position.
* Weather is not considered.
* The integration does not calculate a true "Blue Hour" -- it just sets the
lights to 2700K (warm white) until your hub goes into "Sleep mode".
"""
import logging
from typing import Any, Dict

import voluptuous as vol

from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_SOURCE
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv

from .const import (
_DOMAIN_SCHEMA,
ATTR_TURN_ON_OFF_LISTENER,
CONF_NAME,
DOMAIN,
UNDO_UPDATE_LISTENER,
)

_LOGGER = logging.getLogger(__name__)

PLATFORMS = ["switch"]


def _all_unique_names(value):
"""Validate that all entities have a unique profile name."""
hosts = [device[CONF_NAME] for device in value]
schema = vol.Schema(vol.Unique())
schema(hosts)
return value


CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.All(cv.ensure_list, [_DOMAIN_SCHEMA], _all_unique_names)},
extra=vol.ALLOW_EXTRA,
)


async def async_setup(hass: HomeAssistant, config: Dict[str, Any]):
"""Import integration from config."""

if DOMAIN in config:
for entry in config[DOMAIN]:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={CONF_SOURCE: SOURCE_IMPORT}, data=entry
)
)
return True


async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Set up the component."""
data = hass.data.setdefault(DOMAIN, {})

undo_listener = config_entry.add_update_listener(async_update_options)
data[config_entry.entry_id] = {UNDO_UPDATE_LISTENER: undo_listener}
for platform in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, platform)
)

return True


async def async_update_options(hass, config_entry: ConfigEntry):
"""Update options."""
await hass.config_entries.async_reload(config_entry.entry_id)


async def async_unload_entry(hass, config_entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_forward_entry_unload(
config_entry, "switch"
)
data = hass.data[DOMAIN]
data[config_entry.entry_id][UNDO_UPDATE_LISTENER]()
if len(data) == 1: # no more config_entries
turn_on_off_listener = data.pop(ATTR_TURN_ON_OFF_LISTENER)
turn_on_off_listener.remove_listener()
turn_on_off_listener.remove_listener2()

if unload_ok:
data.pop(config_entry.entry_id)

return unload_ok
109 changes: 109 additions & 0 deletions custom_components/adaptive_lighting/config_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""Config flow for Adaptive Lighting integration."""
import logging

import voluptuous as vol

from homeassistant import config_entries
from homeassistant.const import CONF_NAME
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv

from .const import ( # pylint: disable=unused-import
CONF_LIGHTS,
DOMAIN,
EXTRA_VALIDATION,
NONE_STR,
VALIDATION_TUPLES,
)
from .switch import _supported_features

_LOGGER = logging.getLogger(__name__)


class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Adaptive Lighting."""

VERSION = 1

async def async_step_user(self, user_input=None):
"""Handle the initial step."""
errors = {}

if user_input is not None:
await self.async_set_unique_id(user_input[CONF_NAME])
self._abort_if_unique_id_configured()
return self.async_create_entry(title=user_input[CONF_NAME], data=user_input)

return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required(CONF_NAME): str}),
errors=errors,
)

async def async_step_import(self, user_input=None):
"""Handle configuration by yaml file."""
await self.async_set_unique_id(user_input[CONF_NAME])
for entry in self._async_current_entries():
if entry.unique_id == self.unique_id:
self.hass.config_entries.async_update_entry(entry, data=user_input)
self._abort_if_unique_id_configured()
return self.async_create_entry(title=user_input[CONF_NAME], data=user_input)

@staticmethod
@callback
def async_get_options_flow(config_entry):
"""Get the options flow for this handler."""
return OptionsFlowHandler(config_entry)


def validate_options(user_input, errors):
"""Validate the options in the OptionsFlow.

This is an extra validation step because the validators
in `EXTRA_VALIDATION` cannot be serialized to json.
"""
for key, (validate, _) in EXTRA_VALIDATION.items():
# these are unserializable validators
value = user_input.get(key)
try:
if value is not None and value != NONE_STR:
validate(value)
except vol.Invalid:
_LOGGER.exception("Configuration option %s=%s is incorrect", key, value)
errors["base"] = "option_error"


class OptionsFlowHandler(config_entries.OptionsFlow):
"""Handle a option flow for Adaptive Lighting."""

def __init__(self, config_entry: config_entries.ConfigEntry):
"""Initialize options flow."""
self.config_entry = config_entry

async def async_step_init(self, user_input=None):
"""Handle options flow."""
conf = self.config_entry
if conf.source == config_entries.SOURCE_IMPORT:
return self.async_show_form(step_id="init", data_schema={})
errors = {}
if user_input is not None:
validate_options(user_input, errors)
if not errors:
return self.async_create_entry(title="", data=user_input)

all_lights = [
light
for light in self.hass.states.async_entity_ids("light")
if _supported_features(self.hass, light)
]
to_replace = {CONF_LIGHTS: cv.multi_select(sorted(all_lights))}

options_schema = {}
for name, default, validation in VALIDATION_TUPLES:
key = vol.Optional(name, default=conf.options.get(name, default))
value = to_replace.get(name, validation)
options_schema[key] = value

return self.async_show_form(
step_id="init", data_schema=vol.Schema(options_schema), errors=errors
)
Loading