Add set_music_mode service to flux_led for detailed music mode control (#62429)
parent
eb897c6f48
commit
4b30c9631f
|
@ -76,6 +76,11 @@ class FluxEntity(CoordinatorEntity):
|
||||||
unique_id, self._device, coordinator.entry
|
unique_id, self._device, coordinator.entry
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _async_ensure_device_on(self) -> None:
|
||||||
|
"""Turn the device on if it needs to be turned on before a command."""
|
||||||
|
if self._device.requires_turn_on and not self._device.is_on:
|
||||||
|
await self._device.async_turn_on()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def extra_state_attributes(self) -> dict[str, str]:
|
def extra_state_attributes(self) -> dict[str, str]:
|
||||||
"""Return the attributes."""
|
"""Return the attributes."""
|
||||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
||||||
from typing import Any, Final
|
from typing import Any, Final
|
||||||
|
|
||||||
from flux_led.const import MultiColorEffects
|
from flux_led.const import MultiColorEffects
|
||||||
|
from flux_led.protocol import MusicMode
|
||||||
from flux_led.utils import (
|
from flux_led.utils import (
|
||||||
color_temp_to_white_levels,
|
color_temp_to_white_levels,
|
||||||
rgbcw_brightness,
|
rgbcw_brightness,
|
||||||
|
@ -73,6 +74,11 @@ MODE_ATTRS = {
|
||||||
ATTR_WHITE,
|
ATTR_WHITE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ATTR_FOREGROUND_COLOR: Final = "foreground_color"
|
||||||
|
ATTR_BACKGROUND_COLOR: Final = "background_color"
|
||||||
|
ATTR_SENSITIVITY: Final = "sensitivity"
|
||||||
|
ATTR_LIGHT_SCREEN: Final = "light_screen"
|
||||||
|
|
||||||
# Constant color temp values for 2 flux_led special modes
|
# Constant color temp values for 2 flux_led special modes
|
||||||
# Warm-white and Cool-white modes
|
# Warm-white and Cool-white modes
|
||||||
COLOR_TEMP_WARM_VS_COLD_WHITE_CUT_OFF: Final = 285
|
COLOR_TEMP_WARM_VS_COLD_WHITE_CUT_OFF: Final = 285
|
||||||
|
@ -81,6 +87,7 @@ EFFECT_CUSTOM: Final = "custom"
|
||||||
|
|
||||||
SERVICE_CUSTOM_EFFECT: Final = "set_custom_effect"
|
SERVICE_CUSTOM_EFFECT: Final = "set_custom_effect"
|
||||||
SERVICE_SET_ZONES: Final = "set_zones"
|
SERVICE_SET_ZONES: Final = "set_zones"
|
||||||
|
SERVICE_SET_MUSIC_MODE: Final = "set_music_mode"
|
||||||
|
|
||||||
CUSTOM_EFFECT_DICT: Final = {
|
CUSTOM_EFFECT_DICT: Final = {
|
||||||
vol.Required(CONF_COLORS): vol.All(
|
vol.Required(CONF_COLORS): vol.All(
|
||||||
|
@ -96,6 +103,25 @@ CUSTOM_EFFECT_DICT: Final = {
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SET_MUSIC_MODE_DICT: Final = {
|
||||||
|
vol.Optional(ATTR_SENSITIVITY, default=100): vol.All(
|
||||||
|
vol.Range(min=0, max=100), vol.Coerce(int)
|
||||||
|
),
|
||||||
|
vol.Optional(ATTR_BRIGHTNESS, default=100): vol.All(
|
||||||
|
vol.Range(min=0, max=100), vol.Coerce(int)
|
||||||
|
),
|
||||||
|
vol.Optional(ATTR_EFFECT, default=1): vol.All(
|
||||||
|
vol.Range(min=1, max=16), vol.Coerce(int)
|
||||||
|
),
|
||||||
|
vol.Optional(ATTR_LIGHT_SCREEN, default=False): bool,
|
||||||
|
vol.Optional(ATTR_FOREGROUND_COLOR): vol.All(
|
||||||
|
vol.Coerce(tuple), vol.ExactSequence((cv.byte,) * 3)
|
||||||
|
),
|
||||||
|
vol.Optional(ATTR_BACKGROUND_COLOR): vol.All(
|
||||||
|
vol.Coerce(tuple), vol.ExactSequence((cv.byte,) * 3)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
SET_ZONES_DICT: Final = {
|
SET_ZONES_DICT: Final = {
|
||||||
vol.Required(CONF_COLORS): vol.All(
|
vol.Required(CONF_COLORS): vol.All(
|
||||||
cv.ensure_list,
|
cv.ensure_list,
|
||||||
|
@ -130,6 +156,11 @@ async def async_setup_entry(
|
||||||
SET_ZONES_DICT,
|
SET_ZONES_DICT,
|
||||||
"async_set_zones",
|
"async_set_zones",
|
||||||
)
|
)
|
||||||
|
platform.async_register_entity_service(
|
||||||
|
SERVICE_SET_MUSIC_MODE,
|
||||||
|
SET_MUSIC_MODE_DICT,
|
||||||
|
"async_set_music_mode",
|
||||||
|
)
|
||||||
options = entry.options
|
options = entry.options
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -330,3 +361,23 @@ class FluxLight(FluxOnOffEntity, CoordinatorEntity, LightEntity):
|
||||||
speed_pct,
|
speed_pct,
|
||||||
_str_to_multi_color_effect(effect),
|
_str_to_multi_color_effect(effect),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def async_set_music_mode(
|
||||||
|
self,
|
||||||
|
sensitivity: int,
|
||||||
|
brightness: int,
|
||||||
|
effect: int,
|
||||||
|
light_screen: bool,
|
||||||
|
foreground_color: tuple[int, int, int] | None = None,
|
||||||
|
background_color: tuple[int, int, int] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Configure music mode."""
|
||||||
|
await self._async_ensure_device_on()
|
||||||
|
await self._device.async_set_music_mode(
|
||||||
|
sensitivity=sensitivity,
|
||||||
|
brightness=brightness,
|
||||||
|
mode=MusicMode.LIGHT_SCREEN.value if light_screen else None,
|
||||||
|
effect=effect,
|
||||||
|
foreground_color=foreground_color,
|
||||||
|
background_color=background_color,
|
||||||
|
)
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": ["network"],
|
"dependencies": ["network"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
"documentation": "https://www.home-assistant.io/integrations/flux_led",
|
||||||
"requirements": ["flux_led==0.27.8"],
|
"requirements": ["flux_led==0.27.10"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"codeowners": ["@icemanch"],
|
"codeowners": ["@icemanch"],
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
|
|
|
@ -77,3 +77,60 @@ set_zones:
|
||||||
- "strobe"
|
- "strobe"
|
||||||
- "jump"
|
- "jump"
|
||||||
- "breathing"
|
- "breathing"
|
||||||
|
set_music_mode:
|
||||||
|
description: Configure music mode on Controller RGB with MIC (0x08), Addressable v2 (0xA2), and Addressable v3 (0xA3) devices that have a built-in microphone.
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
integration: flux_led
|
||||||
|
domain: light
|
||||||
|
fields:
|
||||||
|
sensitivity:
|
||||||
|
description: Microphone sensitivity (0-100)
|
||||||
|
example: 80
|
||||||
|
default: 100
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 1
|
||||||
|
step: 1
|
||||||
|
max: 100
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
brightness:
|
||||||
|
description: Light brightness (0-100)
|
||||||
|
example: 80
|
||||||
|
default: 100
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 1
|
||||||
|
step: 1
|
||||||
|
max: 100
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
light_screen:
|
||||||
|
description: Light screen mode for 2 dimensional pixels (Addressable models only)
|
||||||
|
default: false
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
boolean:
|
||||||
|
effect:
|
||||||
|
description: Effect (1-16 on Addressable models, 0-3 on RGB with MIC models)
|
||||||
|
example: 1
|
||||||
|
default: 1
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
number:
|
||||||
|
min: 0
|
||||||
|
step: 1
|
||||||
|
max: 16
|
||||||
|
foreground_color:
|
||||||
|
description: The foreground RGB color
|
||||||
|
example: "[255, 100, 100]"
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
background_color:
|
||||||
|
description: The background RGB color (Addressable models only)
|
||||||
|
example: "[255, 100, 100]"
|
||||||
|
required: false
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
|
|
|
@ -126,8 +126,7 @@ class FluxMusicSwitch(FluxEntity, SwitchEntity):
|
||||||
|
|
||||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the microphone on."""
|
"""Turn the microphone on."""
|
||||||
if self._device.requires_turn_on and not self._device.is_on:
|
await self._async_ensure_device_on()
|
||||||
await self._device.async_turn_on()
|
|
||||||
await self._device.async_set_music_mode()
|
await self._device.async_set_music_mode()
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
await self.coordinator.async_request_refresh()
|
await self.coordinator.async_request_refresh()
|
||||||
|
|
|
@ -664,7 +664,7 @@ fjaraskupan==1.0.2
|
||||||
flipr-api==1.4.1
|
flipr-api==1.4.1
|
||||||
|
|
||||||
# homeassistant.components.flux_led
|
# homeassistant.components.flux_led
|
||||||
flux_led==0.27.8
|
flux_led==0.27.10
|
||||||
|
|
||||||
# homeassistant.components.homekit
|
# homeassistant.components.homekit
|
||||||
fnvhash==0.1.0
|
fnvhash==0.1.0
|
||||||
|
|
|
@ -405,7 +405,7 @@ fjaraskupan==1.0.2
|
||||||
flipr-api==1.4.1
|
flipr-api==1.4.1
|
||||||
|
|
||||||
# homeassistant.components.flux_led
|
# homeassistant.components.flux_led
|
||||||
flux_led==0.27.8
|
flux_led==0.27.10
|
||||||
|
|
||||||
# homeassistant.components.homekit
|
# homeassistant.components.homekit
|
||||||
fnvhash==0.1.0
|
fnvhash==0.1.0
|
||||||
|
|
|
@ -10,8 +10,10 @@ from flux_led.const import (
|
||||||
COLOR_MODE_RGBW as FLUX_COLOR_MODE_RGBW,
|
COLOR_MODE_RGBW as FLUX_COLOR_MODE_RGBW,
|
||||||
COLOR_MODE_RGBWW as FLUX_COLOR_MODE_RGBWW,
|
COLOR_MODE_RGBWW as FLUX_COLOR_MODE_RGBWW,
|
||||||
COLOR_MODES_RGB_W as FLUX_COLOR_MODES_RGB_W,
|
COLOR_MODES_RGB_W as FLUX_COLOR_MODES_RGB_W,
|
||||||
|
MODE_MUSIC,
|
||||||
MultiColorEffects,
|
MultiColorEffects,
|
||||||
)
|
)
|
||||||
|
from flux_led.protocol import MusicMode
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components import flux_led
|
from homeassistant.components import flux_led
|
||||||
|
@ -26,6 +28,12 @@ from homeassistant.components.flux_led.const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
TRANSITION_JUMP,
|
TRANSITION_JUMP,
|
||||||
)
|
)
|
||||||
|
from homeassistant.components.flux_led.light import (
|
||||||
|
ATTR_BACKGROUND_COLOR,
|
||||||
|
ATTR_FOREGROUND_COLOR,
|
||||||
|
ATTR_LIGHT_SCREEN,
|
||||||
|
ATTR_SENSITIVITY,
|
||||||
|
)
|
||||||
from homeassistant.components.light import (
|
from homeassistant.components.light import (
|
||||||
ATTR_BRIGHTNESS,
|
ATTR_BRIGHTNESS,
|
||||||
ATTR_COLOR_MODE,
|
ATTR_COLOR_MODE,
|
||||||
|
@ -1191,3 +1199,47 @@ async def test_addressable_light(hass: HomeAssistant) -> None:
|
||||||
bulb.async_turn_on.assert_called_once()
|
bulb.async_turn_on.assert_called_once()
|
||||||
bulb.async_turn_on.reset_mock()
|
bulb.async_turn_on.reset_mock()
|
||||||
await async_mock_device_turn_on(hass, bulb)
|
await async_mock_device_turn_on(hass, bulb)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_music_mode_service(hass: HomeAssistant) -> None:
|
||||||
|
"""Test music mode service."""
|
||||||
|
config_entry = MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={CONF_HOST: IP_ADDRESS, CONF_NAME: DEFAULT_ENTRY_TITLE},
|
||||||
|
unique_id=MAC_ADDRESS,
|
||||||
|
)
|
||||||
|
config_entry.add_to_hass(hass)
|
||||||
|
bulb = _mocked_bulb()
|
||||||
|
bulb.raw_state = bulb.raw_state._replace(model_num=0xA3) # has music mode
|
||||||
|
bulb.microphone = True
|
||||||
|
with _patch_discovery(), _patch_wifibulb(device=bulb):
|
||||||
|
await async_setup_component(hass, flux_led.DOMAIN, {flux_led.DOMAIN: {}})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
entity_id = "light.bulb_rgbcw_ddeeff"
|
||||||
|
assert hass.states.get(entity_id)
|
||||||
|
|
||||||
|
bulb.effect = MODE_MUSIC
|
||||||
|
bulb.is_on = False
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
"set_music_mode",
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: entity_id,
|
||||||
|
ATTR_EFFECT: 12,
|
||||||
|
ATTR_LIGHT_SCREEN: True,
|
||||||
|
ATTR_SENSITIVITY: 50,
|
||||||
|
ATTR_BRIGHTNESS: 50,
|
||||||
|
ATTR_FOREGROUND_COLOR: [255, 0, 0],
|
||||||
|
ATTR_BACKGROUND_COLOR: [0, 255, 0],
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
bulb.async_set_music_mode.assert_called_once_with(
|
||||||
|
sensitivity=50,
|
||||||
|
brightness=50,
|
||||||
|
mode=MusicMode.LIGHT_SCREEN.value,
|
||||||
|
effect=12,
|
||||||
|
foreground_color=(255, 0, 0),
|
||||||
|
background_color=(0, 255, 0),
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in New Issue