Retroactive review changes for #443 (#485)

* Retroactive review changes for #443

* Move up __init__

* Add test for test_change_switch_settings_service

* Add docs

* Add test for defaults

* Remove unused function

* Add skip var

* move pylint marker

* Refactor validate

* Update README.md

---------

Co-authored-by: Benjamin Auquite <halomastar@gmail.com>
This commit is contained in:
Bas Nijholt 2023-03-27 18:56:54 -07:00 • committed by GitHub
commit 1ca3bf0f6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 191 additions and 136 deletions

View file

@ -41,9 +41,6 @@ async def async_setup(hass: HomeAssistant, config: dict[str, Any]):
# This will reload any changes the user made to any YAML configurations.
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
# quickly populate data for change_switch_settings before actually loading integration.
# update_services_yaml()
if DOMAIN in config:
for entry in config[DOMAIN]:
hass.async_create_task(

View file

@ -8,5 +8,5 @@
"iot_class": "calculated",
"issue_tracker": "https://github.com/basnijholt/adaptive-lighting/issues",
"requirements": [],
"version": "1.6.0"
"version": "1.7.0"
}

View file

@ -189,11 +189,6 @@ def _int_to_bytes(i: int, signed: bool = False) -> bytes:
return i.to_bytes((bits + 7) // 8, "little", signed=signed)
def int_between(min_int, max_int):
"""Return an integer between 'min_int' and 'max_int'."""
return vol.All(vol.Coerce(int), vol.Range(min=min_int, max=max_int))
def _short_hash(string: str, length: int = 4) -> str:
"""Create a hash of 'string' with length 'length'."""
str_hash_bytes = _int_to_bytes(hash(string), signed=True)
@ -365,12 +360,10 @@ async def handle_change_switch_settings(
which = data.get(CONF_USE_DEFAULTS, "current")
if which == "current": # use whatever we're already using.
defaults = switch._current_settings # pylint: disable=protected-access
# not needed since validate() does this part for us
elif which == "factory": # use actual defaults listed in the documentation
defaults = {key: default for key, default, _ in VALIDATION_TUPLES}
elif (
which == "configuration"
): # use whatever's in the config flow or configuration.yaml
elif which == "configuration":
# use whatever's in the config flow or configuration.yaml
defaults = switch._config_backup # pylint: disable=protected-access
else:
defaults = None
@ -387,15 +380,13 @@ async def handle_change_switch_settings(
all_lights = switch._lights # pylint: disable=protected-access
switch.turn_on_off_listener.reset(*all_lights, reset_manual_control=False)
# pylint: disable=protected-access
if switch.is_on:
await switch._update_attrs_and_maybe_adapt_lights(
await switch._update_attrs_and_maybe_adapt_lights( # pylint: disable=protected-access
all_lights,
transition=switch._initial_transition,
force=True,
context=switch.create_context("service", parent=service_call.context),
)
# pylint: enable=protected-access
@callback
@ -509,8 +500,8 @@ async def async_setup_entry(
_fire_manual_control_event(this_switch, light, service_call.context)
else:
this_switch.turn_on_off_listener.reset(*all_lights)
# pylint: disable=protected-access
if this_switch.is_on:
# pylint: disable=protected-access
await this_switch._update_attrs_and_maybe_adapt_lights(
all_lights,
transition=this_switch._initial_transition,
@ -556,9 +547,10 @@ async def async_setup_entry(
)
args = {vol.Optional(CONF_USE_DEFAULTS, default="current"): cv.string}
# Modifying these after init isn't possible
skip = (CONF_INTERVAL, CONF_NAME, CONF_LIGHTS)
for k, _, valid in VALIDATION_TUPLES:
# Modifying these after initialization isn't possible (yet)
if k != CONF_INTERVAL and k != CONF_NAME and k != CONF_LIGHTS:
if k not in skip:
args[vol.Optional(k)] = valid
platform = entity_platform.current_platform.get()
platform.async_register_entity_service(
@ -568,24 +560,24 @@ async def async_setup_entry(
)
def validate(config_entry: ConfigEntry, **kwargs):
def validate(
config_entry: ConfigEntry,
service_data: dict[str, Any] | None = None,
defaults: dict[str, Any] | None = None,
):
"""Get the options and data from the config_entry and add defaults."""
# defaults and data will exist only if this is called from change_switch_settings
defaults = kwargs.get("defaults")
service_data = kwargs.get("data")
if defaults is None:
defaults = {key: default for key, default, _ in VALIDATION_TUPLES}
data = {key: default for key, default, _ in VALIDATION_TUPLES}
else:
data = defaults
if config_entry is not None:
# Is this deepcopy necessary?
# We're already creating a new array for the defaults variable...
# afterwards defaults is never used again.
data = deepcopy(defaults)
assert service_data is None
assert defaults is None
data.update(config_entry.options) # come from options flow
data.update(config_entry.data) # all yaml settings come from data
else:
# no idea how to clear original data from memory
# hopefully it does it automatically, otherwise TODO.
data = deepcopy(defaults)
assert service_data is not None
data.update(service_data)
data = {key: replace_none_str(value) for key, value in data.items()}
for key, (validate_value, _) in EXTRA_VALIDATION.items():
@ -756,67 +748,6 @@ def _attributes_have_changed(
class AdaptiveSwitch(SwitchEntity, RestoreEntity):
"""Representation of a Adaptive Lighting switch."""
def _set_changeable_settings(
self,
data: dict,
defaults: dict,
):
# Should only contain the settings we want the users to be able to change during runtime.
data = validate(
config_entry=None,
data=data,
defaults=defaults,
)
# backup data for use in change_switch_settings "current" CONF_USE_DEFAULTS
self._current_settings = data
self._detect_non_ha_changes = data[CONF_DETECT_NON_HA_CHANGES]
self._include_config_in_attributes = data[CONF_INCLUDE_CONFIG_IN_ATTRIBUTES]
self._initial_transition = data[CONF_INITIAL_TRANSITION]
self._sleep_transition = data[CONF_SLEEP_TRANSITION]
self._only_once = data[CONF_ONLY_ONCE]
self._prefer_rgb_color = data[CONF_PREFER_RGB_COLOR]
self._separate_turn_on_commands = data[CONF_SEPARATE_TURN_ON_COMMANDS]
self._take_over_control = data[CONF_TAKE_OVER_CONTROL]
self._transition = data[CONF_TRANSITION]
self._adapt_delay = data[CONF_ADAPT_DELAY]
self._send_split_delay = data[CONF_SEND_SPLIT_DELAY]
_loc = get_astral_location(self.hass)
if isinstance(_loc, tuple):
# Astral v2.2
location, _ = _loc
else:
# Astral v1
location = _loc
self._sun_light_settings = SunLightSettings(
name=self._name,
astral_location=location,
max_brightness=data[CONF_MAX_BRIGHTNESS],
max_color_temp=data[CONF_MAX_COLOR_TEMP],
min_brightness=data[CONF_MIN_BRIGHTNESS],
min_color_temp=data[CONF_MIN_COLOR_TEMP],
sleep_brightness=data[CONF_SLEEP_BRIGHTNESS],
sleep_color_temp=data[CONF_SLEEP_COLOR_TEMP],
sleep_rgb_color=data[CONF_SLEEP_RGB_COLOR],
sleep_rgb_or_color_temp=data[CONF_SLEEP_RGB_OR_COLOR_TEMP],
sunrise_offset=data[CONF_SUNRISE_OFFSET],
sunrise_time=data[CONF_SUNRISE_TIME],
max_sunrise_time=data[CONF_MAX_SUNRISE_TIME],
sunset_offset=data[CONF_SUNSET_OFFSET],
sunset_time=data[CONF_SUNSET_TIME],
min_sunset_time=data[CONF_MIN_SUNSET_TIME],
time_zone=self.hass.config.time_zone,
transition=data[CONF_TRANSITION],
)
_LOGGER.debug(
"%s: Set switch settings for lights '%s'. now using data: '%s'",
self._name,
self._lights,
data,
)
def __init__(
self,
hass,
@ -827,8 +758,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
adapt_brightness_switch: SimpleSwitch,
):
"""Initialize the Adaptive Lighting switch."""
# Set attributes we DON'T want users modifying
# during runtime here.
# Set attributes that can't be modified during runtime
self.hass = hass
self.turn_on_off_listener = turn_on_off_listener
self.sleep_mode_switch = sleep_mode_switch
@ -887,6 +817,67 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
data,
)
def _set_changeable_settings(
self,
data: dict,
defaults: dict,
):
# Only pass settings users can change during runtime
data = validate(
config_entry=None,
service_data=data,
defaults=defaults,
)
# backup data for use in change_switch_settings "current" CONF_USE_DEFAULTS
self._current_settings = data
self._detect_non_ha_changes = data[CONF_DETECT_NON_HA_CHANGES]
self._include_config_in_attributes = data[CONF_INCLUDE_CONFIG_IN_ATTRIBUTES]
self._initial_transition = data[CONF_INITIAL_TRANSITION]
self._sleep_transition = data[CONF_SLEEP_TRANSITION]
self._only_once = data[CONF_ONLY_ONCE]
self._prefer_rgb_color = data[CONF_PREFER_RGB_COLOR]
self._separate_turn_on_commands = data[CONF_SEPARATE_TURN_ON_COMMANDS]
self._take_over_control = data[CONF_TAKE_OVER_CONTROL]
self._transition = data[CONF_TRANSITION]
self._adapt_delay = data[CONF_ADAPT_DELAY]
self._send_split_delay = data[CONF_SEND_SPLIT_DELAY]
_loc = get_astral_location(self.hass)
if isinstance(_loc, tuple):
# Astral v2.2
location, _ = _loc
else:
# Astral v1
location = _loc
self._sun_light_settings = SunLightSettings(
name=self._name,
astral_location=location,
max_brightness=data[CONF_MAX_BRIGHTNESS],
max_color_temp=data[CONF_MAX_COLOR_TEMP],
min_brightness=data[CONF_MIN_BRIGHTNESS],
min_color_temp=data[CONF_MIN_COLOR_TEMP],
sleep_brightness=data[CONF_SLEEP_BRIGHTNESS],
sleep_color_temp=data[CONF_SLEEP_COLOR_TEMP],
sleep_rgb_color=data[CONF_SLEEP_RGB_COLOR],
sleep_rgb_or_color_temp=data[CONF_SLEEP_RGB_OR_COLOR_TEMP],
sunrise_offset=data[CONF_SUNRISE_OFFSET],
sunrise_time=data[CONF_SUNRISE_TIME],
max_sunrise_time=data[CONF_MAX_SUNRISE_TIME],
sunset_offset=data[CONF_SUNSET_OFFSET],
sunset_time=data[CONF_SUNSET_TIME],
min_sunset_time=data[CONF_MIN_SUNSET_TIME],
time_zone=self.hass.config.time_zone,
transition=data[CONF_TRANSITION],
)
_LOGGER.debug(
"%s: Set switch settings for lights '%s'. now using data: '%s'",
self._name,
self._lights,
data,
)
@property
def name(self):
"""Return the name of the device if any."""