add take_over_control feat

This commit is contained in:
Bas Nijholt 2020-10-03 14:55:02 +02:00
commit 3eb593ec6d
4 changed files with 59 additions and 16 deletions

View file

@ -29,7 +29,7 @@ CONF_SUNRISE_OFFSET, DEFAULT_SUNRISE_OFFSET = "sunrise_offset", 0
CONF_SUNRISE_TIME = "sunrise_time"
CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET = "sunset_offset", 0
CONF_SUNSET_TIME = "sunset_time"
CONF_TAKE_OVER_CONTROL, DEFAULT_TAKE_OVER_CONTROL = "take_over_control", True
CONF_TAKE_OVER_CONTROL, DEFAULT_TAKE_OVER_CONTROL = "take_over_control", False
CONF_TRANSITION, DEFAULT_TRANSITION = "transition", 60
ATTR_TURN_ON_OFF_LISTENER = "turn_on_off_listener"

View file

@ -38,7 +38,7 @@
"sunrise_time": "sunrise_time, in 'HH:MM:SS' format",
"sunset_offset": "sunset_offset, in +/- seconds",
"sunset_time": "sunset_time, in 'HH:MM:SS' format",
"take_over_control": "take_over_control, (NOT YET IMPLEMENTED!) if manually adjusting the lights when they are already on",
"take_over_control": "take_over_control, (beta feature) if manually adjusting the lights when they are already on",
"transition": "transition, in seconds"
}
}

View file

@ -269,7 +269,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
# Locks that prevent light adjusting when waiting for a light to 'turn_off'
self._locks: Dict[str, asyncio.Lock] = {}
# To identify that this integration made a change
self._context = Context()
self.__context = Context() # self._context will be overwritten
# Set in self._update_attrs_and_maybe_adapt_lights
self._light_settings = {}
@ -370,6 +370,10 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
return {key: None for key in self._light_settings}
return self._light_settings
def _reset_take_over_control(self):
for light in self._lights:
self.turn_on_off_listener.manually_controlled[light] = False
async def async_turn_on( # pylint: disable=arguments-differ
self, adapt_lights: bool = True
) -> None:
@ -380,6 +384,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
if self.is_on:
return
self._state = True
self._reset_take_over_control()
await self._setup_listeners()
if adapt_lights:
await self._update_attrs_and_maybe_adapt_lights(
@ -392,6 +397,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
return
self._state = False
self._remove_listeners()
self._reset_take_over_control()
async def _async_update_at_interval(self, now=None) -> None:
await self._update_attrs_and_maybe_adapt_lights(force=False)
@ -450,7 +456,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
LIGHT_DOMAIN,
SERVICE_TURN_ON,
service_data,
context=self._context,
context=self.__context,
)
async def _update_attrs_and_maybe_adapt_lights(
@ -469,23 +475,32 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
lights = self._lights
if (self._only_once and not force) or not lights:
return
await self._adapt_lights(lights, transition)
await self._adapt_lights(lights, transition, force)
async def _adapt_lights(
self, lights: List[str], transition: Optional[int]
self, lights: List[str], transition: Optional[int], force: bool
):
_LOGGER.debug(
"%s: '_adapt_lights(%s, %s)' called", self.name, lights, transition
"%s: '_adapt_lights(%s, %s, %s)' called",
self.name,
lights,
transition,
force,
)
for light in lights:
if not is_on(self.hass, light):
continue
if self._take_over_control:
if await self.turn_on_off_listener.is_manually_adjusted(
if self.turn_on_off_listener.is_manually_controlled(
light,
off_to_on_event=self._off_to_on_event.get(light),
adaptive_lighting_context=self._context,
force,
adaptive_lighting_context=self.__context,
):
_LOGGER.debug(
"%s: '%s' is being manually controlled, stop adapting.",
self._name,
light,
)
continue
await self._adapt_light(light, transition)
@ -493,6 +508,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
if not match_state_event(event, ("on", "off")):
return
_LOGGER.debug("%s: _sleep_state_event, event: '%s'", self._name, event)
self._reset_take_over_control()
await self._update_attrs_and_maybe_adapt_lights(
transition=self._initial_transition, force=True
)
@ -540,6 +556,7 @@ class AdaptiveSwitch(SwitchEntity, RestoreEntity):
):
# Tracks 'off' → 'on' state changes
self._on_to_off_event[entity_id] = event
self.turn_on_off_listener.manually_controlled[entity_id] = False
class AdaptiveSleepModeSwitch(SwitchEntity, RestoreEntity):
@ -752,6 +769,8 @@ class TurnOnOffListener:
self.turn_on_event: Dict[str, Event] = {}
# Keeps 'asyncio.sleep` tasks that can be cancelled by 'light.turn_on' events
self.sleep_tasks: Dict[str, asyncio.Task] = {}
# Tracks which lights are manually controlled
self.manually_controlled: Dict[str, bool] = {}
self.remove_listener = self.hass.bus.async_listen(
EVENT_CALL_SERVICE, self.turn_on_off_event_listener
@ -782,6 +801,7 @@ class TurnOnOffListener:
)
for eid in entity_ids:
self.turn_off_event[eid] = event
self.manually_controlled[eid] = False
elif service == SERVICE_TURN_ON:
_LOGGER.debug("Detected an 'light.turn_on('%s')' event", entity_ids)
@ -791,12 +811,35 @@ class TurnOnOffListener:
task.cancel()
self.turn_on_event[eid] = event
async def is_manually_adjusted(self, light: str, off_to_on_event: Optional[Event], adaptive_lighting_context: Context):
def is_manually_controlled(
self,
light: str,
force: bool,
adaptive_lighting_context: Context,
):
"""Check if the light has been 'on' and is now manually being adjusted."""
if off_to_on_event is None:
# No state change has been registered before, so we can't tell.
return False
return False
manually_controlled = self.manually_controlled[light]
if manually_controlled:
# Manually controlled until light is turned on and off
return True
turn_on_event = self.turn_on_event.get(light)
if (
turn_on_event is not None
and adaptive_lighting_context.id != turn_on_event.context.id
and not force
):
# Light was already on and 'light.turn_on' was not called by
# the adaptive_lighting integration.
manually_controlled = self.manually_controlled[light] = True
_LOGGER.debug(
"'%s' was already on and 'light.turn_on' was not called by the"
" adaptive_lighting integration, the Adaptive Lighting will stop"
" adapting the light until the switch or the light turns off and"
" then on again.",
light,
)
return manually_controlled
async def maybe_cancel_adjusting(
self, entity_id: str, off_to_on_event: Event, on_to_off_event: Optional[Event]

View file

@ -38,7 +38,7 @@
"sunrise_time": "sunrise_time, in 'HH:MM:SS' format",
"sunset_offset": "sunset_offset, in +/- seconds",
"sunset_time": "sunset_time, in 'HH:MM:SS' format",
"take_over_control": "take_over_control, (NOT YET IMPLEMENTED!) if manually adjusting the lights when they are already on",
"take_over_control": "take_over_control, (beta feature) if manually adjusting the lights when they are already on",
"transition": "transition, in seconds"
}
}