Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New services adaptive_lighting.turn_on turn_off toggle with optional switch/lights args #559

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ The `adaptive_lighting.manual_control` event is fired when a light is marked as
- [:memo: Options](#memo-options)
- [:hammer_and_wrench: Services](#hammer_and_wrench-services)
- [`adaptive_lighting.apply`](#adaptive_lightingapply)
- [`adaptive_lighting.turn_on`, `adaptive_lighting.turn_off`, `adaptive_lighting.toggle`](#adaptive_lightingturn_on-adaptive_lightingturn_off-adaptive_lightingtoggle)
- [`adaptive_lighting.set_manual_control`](#adaptive_lightingset_manual_control)
- [`adaptive_lighting.change_switch_settings`](#adaptive_lightingchange_switch_settings)
- [:robot: Automation examples](#robot-automation-examples)
Expand Down Expand Up @@ -176,6 +177,25 @@ adaptive_lighting:
| `turn_on_lights` | Whether to turn on lights that are currently off. 🔆 | ❌ | bool |

<!-- END_OUTPUT -->


#### `adaptive_lighting.turn_on`, `adaptive_lighting.turn_off`, `adaptive_lighting.toggle`

`adaptive_lighting.turn_on`, `adaptive_lighting.turn_off`, `adaptive_lighting.toggle` turn Adaptive Lighting on or off.

TODO: ...
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@th3w1zard1 could you add an explaination about what these methods can be used for?

Even I do not understand at the moment why adaptive_lighting.apply couldn't do most of these things 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure no worries. The main problem with AL's switches are the fact that they need to be referenced by name and not the light

With this PR we use the new format you've introduced in 1.10.1 to allow even more flexibility

I have each individual light on their own individual switch. My main use case is turning off the sleep switches when My alarm goes off. With this I can pass the light ids.

Before this I had to use a naming scheme where each switch is light.adaptive_lighting_al_my_light_entity. Then i had to learn Jinja, and substring the adaptive_lighting_al to get my switch. I have a complete tutorial on my fork's README.md if you'd like more info

This pr saves about 5 actions per script in my env

I look forward to hearing your alternatives to this!


<!-- START_CODE -->
<!-- from homeassistant.components.adaptive_lighting import _docs_helpers -->
<!-- print(_docs_helpers.generate_turn_on_markdown_table()) -->
<!-- END_CODE -->

<!-- START_OUTPUT -->
<!-- THIS CONTENT IS AUTOMATICALLY GENERATED -->

<!-- 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.
Expand Down
5 changes: 5 additions & 0 deletions custom_components/adaptive_lighting/_docs_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
DOCS,
DOCS_APPLY,
DOCS_MANUAL_CONTROL,
SERVICE_TOGGLE_SCHEMA,
SET_MANUAL_CONTROL_SCHEMA,
VALIDATION_TUPLES,
apply_service_schema,
Expand Down Expand Up @@ -110,6 +111,10 @@ def generate_apply_markdown_table():
return _generate_service_markdown_table(apply_service_schema(), DOCS_APPLY)


def generate_turn_on_markdown_table():
return _generate_service_markdown_table(SERVICE_TOGGLE_SCHEMA, DOCS_APPLY)


def generate_set_manual_control_markdown_table():
return _generate_service_markdown_table(
SET_MANUAL_CONTROL_SCHEMA, DOCS_MANUAL_CONTROL
Expand Down
28 changes: 27 additions & 1 deletion custom_components/adaptive_lighting/const.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"""Constants for the Adaptive Lighting integration."""

from homeassistant.components.light import VALID_TRANSITION
from homeassistant.const import CONF_ENTITY_ID
from homeassistant.const import (
CONF_ENTITY_ID,
SERVICE_TOGGLE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.helpers import selector
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
Expand Down Expand Up @@ -201,6 +206,19 @@
'documented defaults), or "configuration" (reverts to switch config defaults). ⚙️'
)

CONF_WHICH_SWITCH, DEFAULT_WHICH_SWITCH = "switch_type", "main"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine that "main" is not available in 99% of the installations. Should we have no default?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that sounds like a good idea

DOCS[CONF_WHICH_SWITCH] = (
"Which switch to target in this service call. Options: "
'"main" (default, targets the main switch), "sleep", "brightness", "color"'
)
DOCS[SERVICE_TURN_ON] = (
"Turn on an Adaptive Lighting" " main/sleep/brightness/color switch"
)
DOCS[SERVICE_TURN_OFF] = (
"Turn off an Adaptive Lighting" " main/sleep/brightness/color switch"
)
DOCS[SERVICE_TOGGLE] = "Toggle an Adaptive Lighting main/sleep/brightness/color switch"

TURNING_OFF_DELAY = 5

DOCS_MANUAL_CONTROL = {
Expand Down Expand Up @@ -341,6 +359,14 @@ def apply_service_schema(initial_transition: int = 1):
)


SERVICE_TOGGLE_SCHEMA = vol.Schema(
{
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_LIGHTS, default=[]): cv.entity_ids,
vol.Optional(CONF_WHICH_SWITCH): cv.string,
}
)

SET_MANUAL_CONTROL_SCHEMA = vol.Schema(
{
vol.Optional(CONF_ENTITY_ID): cv.entity_ids,
Expand Down
222 changes: 161 additions & 61 deletions custom_components/adaptive_lighting/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
EVENT_CALL_SERVICE,
EVENT_HOMEASSISTANT_STARTED,
EVENT_STATE_CHANGED,
SERVICE_TOGGLE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
Expand All @@ -74,6 +75,7 @@
HomeAssistant,
ServiceCall,
State,
async_get_hass,
callback,
)
from homeassistant.helpers import entity_platform, entity_registry
Expand Down Expand Up @@ -134,6 +136,7 @@
CONF_TRANSITION,
CONF_TURN_ON_LIGHTS,
CONF_USE_DEFAULTS,
CONF_WHICH_SWITCH,
CONST_COLOR,
DOMAIN,
EXTRA_VALIDATION,
Expand All @@ -144,6 +147,7 @@
SERVICE_APPLY,
SERVICE_CHANGE_SWITCH_SETTINGS,
SERVICE_SET_MANUAL_CONTROL,
SERVICE_TOGGLE_SCHEMA,
SET_MANUAL_CONTROL_SCHEMA,
SLEEP_MODE_SWITCH,
SUN_EVENT_MIDNIGHT,
Expand Down Expand Up @@ -429,6 +433,139 @@ async def handle_change_switch_settings(
)


@callback
async def handle_turn_on(service_call: ServiceCall):
"""Toggles the specified switch."""
hass = async_get_hass()
data = service_call.data
_LOGGER.debug(
"Called 'adaptive_lighting.turn_on' service with '%s'",
data,
)
switches = _get_switches_from_service_call(hass, service_call)
if data[CONF_WHICH_SWITCH] == "sleep":
switches = [s.sleep_mode_switch for s in switches]
elif data[CONF_WHICH_SWITCH] == "brightness":
switches = [s.adapt_brightness_switch for s in switches]
elif data[CONF_WHICH_SWITCH] == "color":
switches = [s.adapt_color_switch for s in switches]

_LOGGER.debug("Turning on switches [%s]", switches)
for switch in switches:
await switch.async_turn_on()


@callback
async def handle_turn_off(service_call: ServiceCall):
"""Toggles the specified switch."""
hass = async_get_hass()
data = service_call.data
_LOGGER.debug(
"Called 'adaptive_lighting.turn_off' service with '%s'",
data,
)
switches = _get_switches_from_service_call(hass, service_call)
if data[CONF_WHICH_SWITCH] == "sleep":
switches = [s.sleep_mode_switch for s in switches]
elif data[CONF_WHICH_SWITCH] == "brightness":
switches = [s.adapt_brightness_switch for s in switches]
elif data[CONF_WHICH_SWITCH] == "color":
switches = [s.adapt_color_switch for s in switches]
_LOGGER.debug("Turning off switches [%s]", switches)
for switch in switches:
await switch.async_turn_off()


@callback
async def handle_toggle(service_call: ServiceCall):
"""Toggles the specified switch."""
hass = async_get_hass()
data = service_call.data
_LOGGER.debug(
"Called 'adaptive_lighting.toggle' service with '%s'",
data,
)
switches = _get_switches_from_service_call(hass, service_call)
if data[CONF_WHICH_SWITCH] == "sleep":
switches = [s.sleep_mode_switch for s in switches]
elif data[CONF_WHICH_SWITCH] == "brightness":
switches = [s.adapt_brightness_switch for s in switches]
elif data[CONF_WHICH_SWITCH] == "color":
switches = [s.adapt_color_switch for s in switches]
_LOGGER.debug("Toggling switches [%s]", switches)
for switch in switches:
if switch.is_on:
await switch.async_turn_off()
else:
await switch.async_turn_on()


@callback
async def handle_apply(service_call: ServiceCall):
"""Handle the entity service apply."""
hass = async_get_hass()
data = service_call.data
_LOGGER.debug(
"Called 'adaptive_lighting.apply' service with '%s'",
data,
)
switches = _get_switches_from_service_call(hass, service_call)
lights = data[CONF_LIGHTS]
for switch in switches:
if not lights:
all_lights = switch._lights # pylint: disable=protected-access
else:
all_lights = _expand_light_groups(switch.hass, lights)
switch.turn_on_off_listener.lights.update(all_lights)
for light in all_lights:
if data[CONF_TURN_ON_LIGHTS] or is_on(hass, light):
await switch._adapt_light( # pylint: disable=protected-access
light,
data[CONF_TRANSITION],
data[ATTR_ADAPT_BRIGHTNESS],
data[ATTR_ADAPT_COLOR],
data[CONF_PREFER_RGB_COLOR],
force=True,
context=switch.create_context(
"service", parent=service_call.context
),
)


@callback
async def handle_set_manual_control(service_call: ServiceCall):
"""Set or unset lights as 'manually controlled'."""
hass = async_get_hass()
data = service_call.data
_LOGGER.debug(
"Called 'adaptive_lighting.set_manual_control' service with '%s'",
data,
)
switches = _get_switches_from_service_call(hass, service_call)
lights = data[CONF_LIGHTS]
for switch in switches:
if not lights:
all_lights = switch._lights # pylint: disable=protected-access
else:
all_lights = _expand_light_groups(switch.hass, lights)
if service_call.data[CONF_MANUAL_CONTROL]:
for light in all_lights:
switch.turn_on_off_listener.mark_as_manual_control(light)
_fire_manual_control_event(switch, light, service_call.context)
else:
switch.turn_on_off_listener.reset(*all_lights)
if switch.is_on:
# pylint: disable=protected-access
await switch._update_attrs_and_maybe_adapt_lights(
all_lights,
transition=switch._initial_transition,
force=True,
context=switch.create_context(
"service", parent=service_call.context
),
)


@callback
def _fire_manual_control_event(
switch: AdaptiveSwitch, light: str, context: Context, is_async=True
Expand Down Expand Up @@ -490,67 +627,6 @@ async def async_setup_entry(
update_before_add=True,
)

@callback
async def handle_apply(service_call: ServiceCall):
"""Handle the entity service apply."""
data = service_call.data
_LOGGER.debug(
"Called 'adaptive_lighting.apply' service with '%s'",
data,
)
switches = _get_switches_from_service_call(hass, service_call)
lights = data[CONF_LIGHTS]
for switch in switches:
if not lights:
all_lights = switch._lights # pylint: disable=protected-access
else:
all_lights = _expand_light_groups(switch.hass, lights)
switch.turn_on_off_listener.lights.update(all_lights)
for light in all_lights:
if data[CONF_TURN_ON_LIGHTS] or is_on(hass, light):
await switch._adapt_light( # pylint: disable=protected-access
light,
data[CONF_TRANSITION],
data[ATTR_ADAPT_BRIGHTNESS],
data[ATTR_ADAPT_COLOR],
data[CONF_PREFER_RGB_COLOR],
force=True,
context=switch.create_context(
"service", parent=service_call.context
),
)

@callback
async def handle_set_manual_control(service_call: ServiceCall):
"""Set or unset lights as 'manually controlled'."""
data = service_call.data
_LOGGER.debug(
"Called 'adaptive_lighting.set_manual_control' service with '%s'",
data,
)
switches = _get_switches_from_service_call(hass, service_call)
lights = data[CONF_LIGHTS]
for switch in switches:
if not lights:
all_lights = switch._lights # pylint: disable=protected-access
else:
all_lights = _expand_light_groups(switch.hass, lights)
if service_call.data[CONF_MANUAL_CONTROL]:
for light in all_lights:
_fire_manual_control_event(switch, light, service_call.context)
else:
switch.turn_on_off_listener.reset(*all_lights)
if switch.is_on:
# pylint: disable=protected-access
await switch._update_attrs_and_maybe_adapt_lights(
all_lights,
transition=switch._initial_transition,
force=True,
context=switch.create_context(
"service", parent=service_call.context
),
)

# Register `apply` service
hass.services.async_register(
domain=DOMAIN,
Expand All @@ -561,6 +637,30 @@ async def handle_set_manual_control(service_call: ServiceCall):
), # pylint: disable=protected-access
)

# Register `turn_on` service
hass.services.async_register(
domain=DOMAIN,
service=SERVICE_TURN_ON,
service_func=handle_turn_on,
schema=SERVICE_TOGGLE_SCHEMA,
)

# Register `turn_off` service
hass.services.async_register(
domain=DOMAIN,
service=SERVICE_TURN_OFF,
service_func=handle_turn_off,
schema=SERVICE_TOGGLE_SCHEMA,
)

# Register `toggle` service
hass.services.async_register(
domain=DOMAIN,
service=SERVICE_TOGGLE,
service_func=handle_toggle,
schema=SERVICE_TOGGLE_SCHEMA,
)

# Register `set_manual_control` service
hass.services.async_register(
domain=DOMAIN,
Expand Down
Loading