mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-16 00:34:04 +02:00
sync with PR
This commit is contained in:
parent
d1714d800a
commit
86d1cad588
3 changed files with 72 additions and 2 deletions
|
|
@ -40,6 +40,7 @@ ATTR_TURN_ON_OFF_LISTENER = "turn_on_off_listener"
|
|||
UNDO_UPDATE_LISTENER = "undo_update_listener"
|
||||
NONE_STR = "None"
|
||||
|
||||
SERVICE_NOT_MANUALLY_CONTROLLED = "not_manually_controlled"
|
||||
SERVICE_APPLY = "apply"
|
||||
CONF_TURN_ON_LIGHTS = "turn_on_lights"
|
||||
|
||||
|
|
|
|||
|
|
@ -22,3 +22,12 @@ apply:
|
|||
turn_on_lights:
|
||||
description: "Turn on the lights that are off, default: false"
|
||||
example: false
|
||||
not_manually_controlled:
|
||||
description: Mark a light as not being 'manually_controlled'.
|
||||
fields:
|
||||
entity_id:
|
||||
description: entity_id of the Adaptive Lighting switch.
|
||||
example: switch.adaptive_lighting_default
|
||||
lights:
|
||||
description: entity_id(s) of lights.
|
||||
example: light.bedroom_ceiling
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
import bisect
|
||||
from collections import defaultdict
|
||||
from copy import deepcopy
|
||||
from dataclasses import dataclass
|
||||
import datetime
|
||||
|
|
@ -51,7 +52,14 @@ from homeassistant.const import (
|
|||
SUN_EVENT_SUNRISE,
|
||||
SUN_EVENT_SUNSET,
|
||||
)
|
||||
from homeassistant.core import Context, Event, HomeAssistant, ServiceCall, State
|
||||
from homeassistant.core import (
|
||||
Context,
|
||||
Event,
|
||||
HomeAssistant,
|
||||
ServiceCall,
|
||||
State,
|
||||
callback,
|
||||
)
|
||||
from homeassistant.helpers import entity_platform
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.event import (
|
||||
|
|
@ -96,6 +104,7 @@ from .const import (
|
|||
EXTRA_VALIDATION,
|
||||
ICON,
|
||||
SERVICE_APPLY,
|
||||
SERVICE_NOT_MANUALLY_CONTROLLED,
|
||||
SUN_EVENT_MIDNIGHT,
|
||||
SUN_EVENT_NOON,
|
||||
TURNING_OFF_DELAY,
|
||||
|
|
@ -167,6 +176,23 @@ async def handle_apply(switch: AdaptiveSwitch, service_call: ServiceCall):
|
|||
)
|
||||
|
||||
|
||||
async def handle_not_manually_controlled(
|
||||
switch: AdaptiveSwitch, service_call: ServiceCall
|
||||
):
|
||||
"""Remove lights from the 'manually_controlled' list."""
|
||||
all_lights = _expand_light_groups(switch.hass, service_call.data[CONF_LIGHTS])
|
||||
switch.turn_on_off_listener.reset(*all_lights)
|
||||
|
||||
|
||||
@callback
|
||||
def _fire_manually_controlled_event(
|
||||
hass: HomeAssistant, light: str, context: Context, is_async=True
|
||||
):
|
||||
"""Fire an event that 'light' is marked as manually_controlled."""
|
||||
fire = hass.bus.async_fire if is_async else hass.bus.fire
|
||||
fire(f"{DOMAIN}.manually_controlled", {ATTR_ENTITY_ID: light}, context=context)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: bool
|
||||
):
|
||||
|
|
@ -203,6 +229,12 @@ async def async_setup_entry(
|
|||
handle_apply,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_NOT_MANUALLY_CONTROLLED,
|
||||
{vol.Required(CONF_LIGHTS): cv.entity_ids},
|
||||
handle_not_manually_controlled,
|
||||
)
|
||||
|
||||
|
||||
def validate(config_entry: ConfigEntry):
|
||||
"""Get the options and data from the config_entry and add defaults."""
|
||||
|
|
@ -961,11 +993,17 @@ class TurnOnOffListener:
|
|||
self.sleep_tasks: Dict[str, asyncio.Task] = {}
|
||||
# Tracks which lights are manually controlled
|
||||
self.manually_controlled: 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
|
||||
self.last_service_data: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
# When a state is different `max_cnt_significant_changes` times in a row,
|
||||
# mark it as manually_controlled.
|
||||
self.max_cnt_significant_changes = 1
|
||||
|
||||
self.remove_listener = self.hass.bus.async_listen(
|
||||
EVENT_CALL_SERVICE, self.turn_on_off_event_listener
|
||||
)
|
||||
|
|
@ -979,6 +1017,7 @@ class TurnOnOffListener:
|
|||
self.manually_controlled[light] = False
|
||||
self.last_state_change.pop(light, None)
|
||||
self.last_service_data.pop(light, None)
|
||||
self.cnt_significant_changes[light] = 0
|
||||
|
||||
async def turn_on_off_event_listener(self, event: Event) -> None:
|
||||
"""Track 'light.turn_off' and 'light.turn_on' service calls."""
|
||||
|
|
@ -1081,6 +1120,7 @@ class TurnOnOffListener:
|
|||
# Light was already on and 'light.turn_on' was not called by
|
||||
# the adaptive_lighting integration.
|
||||
manually_controlled = self.manually_controlled[light] = True
|
||||
_fire_manually_controlled_event(self.hass, light, turn_on_event.context)
|
||||
_LOGGER.debug(
|
||||
"'%s' was already on and 'light.turn_on' was not called by the"
|
||||
" adaptive_lighting integration (context.id='%s'), the Adaptive"
|
||||
|
|
@ -1151,7 +1191,27 @@ class TurnOnOffListener:
|
|||
context.id,
|
||||
)
|
||||
|
||||
self.manually_controlled[light] = changed
|
||||
n_changes = self.cnt_significant_changes[light]
|
||||
if changed:
|
||||
self.cnt_significant_changes[light] += 1
|
||||
if n_changes >= self.max_cnt_significant_changes:
|
||||
# Only mark a light as significantly changing, changed==True `x`
|
||||
# times in a row. We do this because sometimes a state changes
|
||||
# happens only *after* a new update interval has already started.
|
||||
self.manually_controlled[light] = True
|
||||
_fire_manually_controlled_event(
|
||||
self.hass, light, context, is_async=False
|
||||
)
|
||||
else:
|
||||
if n_changes > 1:
|
||||
_LOGGER.debug(
|
||||
"State of '%s' had 'cnt_significant_changes=%s' but the state"
|
||||
" changed to the expected settings now",
|
||||
light,
|
||||
n_changes,
|
||||
)
|
||||
self.cnt_significant_changes[light] = 0
|
||||
|
||||
return changed
|
||||
|
||||
async def maybe_cancel_adjusting(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue