-
Notifications
You must be signed in to change notification settings - Fork 0
Add light.adjust service/action #1
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
base: dev
Are you sure you want to change the base?
Changes from all commits
8b1b38f
7a82bdd
cbbc2b1
f013d82
bededb8
549a7ae
326d0d3
55898c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,10 @@ | |
| from homeassistant.components import light | ||
| from homeassistant.components.light import ( | ||
| ATTR_BRIGHTNESS, | ||
| ATTR_BRIGHTNESS_STEP, | ||
| ATTR_BRIGHTNESS_STEP_PCT, | ||
| ATTR_COLOR_MODE, | ||
| ATTR_COLOR_NAME, | ||
| ATTR_COLOR_TEMP_KELVIN, | ||
| ATTR_EFFECT, | ||
| ATTR_EFFECT_LIST, | ||
|
|
@@ -129,6 +132,9 @@ def async_create_preview_light( | |
| FORWARDED_ATTRIBUTES = frozenset( | ||
| { | ||
| ATTR_BRIGHTNESS, | ||
| ATTR_BRIGHTNESS_STEP, | ||
| ATTR_BRIGHTNESS_STEP_PCT, | ||
| ATTR_COLOR_NAME, | ||
| ATTR_COLOR_TEMP_KELVIN, | ||
| ATTR_EFFECT, | ||
| ATTR_FLASH, | ||
|
|
@@ -185,6 +191,34 @@ async def async_turn_on(self, **kwargs: Any) -> None: | |
| context=self._context, | ||
| ) | ||
|
|
||
| async def async_adjust(self, **kwargs: Any) -> None: | ||
| """Forward the adjust command to on lights in the light group.""" | ||
| data = { | ||
| key: value for key, value in kwargs.items() if key in FORWARDED_ATTRIBUTES | ||
| } | ||
| entity_ids = [ | ||
| state.entity_id | ||
| for entity_id in self._entity_ids | ||
| if (state := self.hass.states.get(entity_id)) is not None | ||
| and ( | ||
| state.state == STATE_ON | ||
| or isinstance(state.attributes.get(ATTR_ENTITY_ID), list) | ||
| ) | ||
| ] | ||
|
Comment on lines
+199
to
+207
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a light group contains another HA light group configured with Useful? React with 👍 / 👎. |
||
| if not entity_ids: | ||
| return | ||
| data[ATTR_ENTITY_ID] = entity_ids | ||
|
|
||
| _LOGGER.debug("Forwarded adjust command: %s", data) | ||
|
|
||
| await self.hass.services.async_call( | ||
| light.DOMAIN, | ||
| light.SERVICE_ADJUST, | ||
| data, | ||
| blocking=True, | ||
| context=self._context, | ||
| ) | ||
|
|
||
| async def async_turn_off(self, **kwargs: Any) -> None: | ||
| """Forward the turn_off command to all lights in the light group.""" | ||
| data = {ATTR_ENTITY_ID: self._entity_ids} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,6 +44,7 @@ | |
| PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA | ||
| PLATFORM_SCHEMA_BASE = cv.PLATFORM_SCHEMA_BASE | ||
|
|
||
| SERVICE_ADJUST = "adjust" | ||
|
|
||
| # Color mode of the light | ||
| ATTR_COLOR_MODE = "color_mode" | ||
|
|
@@ -174,6 +175,8 @@ def get_supported_color_modes(hass: HomeAssistant, entity_id: str) -> set[str] | | |
| VALID_BRIGHTNESS_PCT = vol.All(vol.Coerce(float), vol.Range(min=0, max=100)) | ||
| VALID_BRIGHTNESS_STEP = vol.All(vol.Coerce(int), vol.Clamp(min=-255, max=255)) | ||
| VALID_BRIGHTNESS_STEP_PCT = vol.All(vol.Coerce(float), vol.Clamp(min=-100, max=100)) | ||
| VALID_BRIGHTNESS_ADJUST = vol.All(vol.Coerce(int), vol.Range(min=1, max=255)) | ||
| VALID_BRIGHTNESS_PCT_ADJUST = vol.All(vol.Coerce(float), vol.Range(min=0.001, max=100)) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This validator accepts very small positive percentages such as Useful? React with 👍 / 👎. |
||
| VALID_FLASH = vol.In([FLASH_SHORT, FLASH_LONG]) | ||
|
|
||
| LIGHT_TURN_ON_SCHEMA: VolDictType = { | ||
|
|
@@ -182,7 +185,9 @@ def get_supported_color_modes(hass: HomeAssistant, entity_id: str) -> set[str] | | |
| vol.Exclusive(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS): VALID_BRIGHTNESS, | ||
| vol.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_PCT, | ||
| vol.Exclusive(ATTR_BRIGHTNESS_STEP, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP, | ||
| vol.Exclusive(ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP_PCT, | ||
| vol.Exclusive( | ||
| ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS | ||
| ): VALID_BRIGHTNESS_STEP_PCT, | ||
| vol.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string, | ||
| vol.Exclusive(ATTR_COLOR_TEMP_KELVIN, COLOR_GROUP): cv.positive_int, | ||
| vol.Exclusive(ATTR_HS_COLOR, COLOR_GROUP): vol.All( | ||
|
|
@@ -216,6 +221,39 @@ def get_supported_color_modes(hass: HomeAssistant, entity_id: str) -> set[str] | | |
| ATTR_FLASH: VALID_FLASH, | ||
| } | ||
|
|
||
| LIGHT_ADJUST_SCHEMA: VolDictType = { | ||
| ATTR_TRANSITION: VALID_TRANSITION, | ||
| vol.Exclusive(ATTR_BRIGHTNESS, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_ADJUST, | ||
| vol.Exclusive(ATTR_BRIGHTNESS_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_PCT_ADJUST, | ||
| vol.Exclusive(ATTR_BRIGHTNESS_STEP, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP, | ||
| vol.Exclusive(ATTR_BRIGHTNESS_STEP_PCT, ATTR_BRIGHTNESS): VALID_BRIGHTNESS_STEP_PCT, | ||
| vol.Exclusive(ATTR_COLOR_NAME, COLOR_GROUP): cv.string, | ||
| vol.Exclusive(ATTR_COLOR_TEMP_KELVIN, COLOR_GROUP): cv.positive_int, | ||
| vol.Exclusive(ATTR_HS_COLOR, COLOR_GROUP): vol.All( | ||
| vol.Coerce(tuple), | ||
| vol.ExactSequence( | ||
| ( | ||
| vol.All(vol.Coerce(float), vol.Range(min=0, max=360)), | ||
| vol.All(vol.Coerce(float), vol.Range(min=0, max=100)), | ||
| ) | ||
| ), | ||
| ), | ||
| vol.Exclusive(ATTR_RGB_COLOR, COLOR_GROUP): vol.All( | ||
| vol.Coerce(tuple), vol.ExactSequence((cv.byte,) * 3) | ||
| ), | ||
| vol.Exclusive(ATTR_RGBW_COLOR, COLOR_GROUP): vol.All( | ||
| vol.Coerce(tuple), vol.ExactSequence((cv.byte,) * 4) | ||
| ), | ||
| vol.Exclusive(ATTR_RGBWW_COLOR, COLOR_GROUP): vol.All( | ||
| vol.Coerce(tuple), vol.ExactSequence((cv.byte,) * 5) | ||
| ), | ||
| vol.Exclusive(ATTR_XY_COLOR, COLOR_GROUP): vol.All( | ||
| vol.Coerce(tuple), vol.ExactSequence((cv.small_float, cv.small_float)) | ||
| ), | ||
| vol.Exclusive(ATTR_WHITE, COLOR_GROUP): vol.Any(True, VALID_BRIGHTNESS_ADJUST), | ||
| ATTR_EFFECT: cv.string, | ||
| } | ||
|
|
||
|
|
||
| _LOGGER = logging.getLogger(__name__) | ||
|
|
||
|
|
@@ -296,6 +334,8 @@ def filter_turn_on_params(light: LightEntity, params: dict[str, Any]) -> dict[st | |
| ) | ||
| if not brightness_supported(supported_color_modes): | ||
| params.pop(ATTR_BRIGHTNESS, None) | ||
| params.pop(ATTR_BRIGHTNESS_STEP, None) | ||
| params.pop(ATTR_BRIGHTNESS_STEP_PCT, None) | ||
| if ColorMode.COLOR_TEMP not in supported_color_modes: | ||
| params.pop(ATTR_COLOR_TEMP_KELVIN, None) | ||
| if ColorMode.HS not in supported_color_modes: | ||
|
|
@@ -524,6 +564,48 @@ async def async_handle_light_off_service( | |
|
|
||
| await light.async_turn_off(**filter_turn_off_params(light, params)) | ||
|
|
||
| async def async_handle_light_adjust_service( | ||
| light: LightEntity, call: ServiceCall | ||
| ) -> None: | ||
| """Handle adjusting a light without turning it on.""" | ||
| if not call.data["params"]: | ||
| return | ||
|
|
||
| raw_params = dict(call.data["params"]) | ||
|
|
||
| if light.__class__.async_adjust is not LightEntity.async_adjust: | ||
| params = filter_turn_on_params(light, raw_params) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Useful? React with 👍 / 👎. |
||
| if not params: | ||
| return | ||
| await light.async_adjust(**params) | ||
| return | ||
|
|
||
| has_step_adjust = ( | ||
| ATTR_BRIGHTNESS_STEP in raw_params | ||
| or ATTR_BRIGHTNESS_STEP_PCT in raw_params | ||
| ) | ||
|
|
||
| if ( | ||
| not light.is_on | ||
| and has_step_adjust | ||
| ): | ||
| return | ||
|
|
||
| params = filter_turn_on_params( | ||
| light, | ||
| process_turn_on_params(hass, light, raw_params.copy()), | ||
| ) | ||
|
|
||
| if not params: | ||
| return | ||
|
|
||
| if params.get(ATTR_BRIGHTNESS) == 0 or params.get(ATTR_WHITE) == 0: | ||
| raise HomeAssistantError( | ||
| "light.adjust does not accept zero brightness or white values" | ||
| ) | ||
|
Comment on lines
+602
to
+605
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For an on light that does not support brightness, a negative Useful? React with 👍 / 👎. |
||
|
|
||
| await light.async_adjust(**params) | ||
|
|
||
| async def async_handle_toggle_service( | ||
| light: LightEntity, call: ServiceCall | ||
| ) -> None: | ||
|
|
@@ -538,6 +620,12 @@ async def async_handle_toggle_service( | |
| async_handle_light_on_service, | ||
| ) | ||
|
|
||
| component.async_register_entity_service( | ||
| SERVICE_ADJUST, | ||
| vol.All(cv.make_entity_service_schema(LIGHT_ADJUST_SCHEMA), preprocess_data), | ||
| async_handle_light_adjust_service, | ||
| ) | ||
|
|
||
| component.async_register_entity_service( | ||
| SERVICE_TURN_OFF, | ||
| vol.All(cv.make_entity_service_schema(LIGHT_TURN_OFF_SCHEMA), preprocess_data), | ||
|
|
@@ -1062,3 +1150,9 @@ async def async_toggle(self, **kwargs: Any) -> None: | |
|
|
||
| params = process_turn_off_params(self.hass, self, kwargs) | ||
| await self.async_turn_off(**filter_turn_off_params(self, params)) | ||
|
|
||
| async def async_adjust(self, **kwargs: Any) -> None: | ||
| """Adjust the entity without turning it on.""" | ||
| if not self.is_on: | ||
| return | ||
| await self.async_turn_on(**kwargs) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
light.adjustis called on a Home Assistant light group with bothcolor_nameandbrightness_step/brightness_step_pct, the service handler deliberately skipsprocess_turn_on_paramsso each member can resolve its own step, then this filter dropscolor_namebecause it is not inFORWARDED_ATTRIBUTES. The nestedlight.adjustcall therefore receives only the brightness step and silently ignores the requested color, even though the service schema accepts that combination.Useful? React with 👍 / 👎.