Skip to content

Commit

Permalink
Update wled to 0.19.2 (#122101)
Browse files Browse the repository at this point in the history
Co-authored-by: Joost Lekkerkerker <[email protected]>
  • Loading branch information
frenck and joostlek authored Jul 18, 2024
1 parent b06d3fe commit 41d75e1
Show file tree
Hide file tree
Showing 34 changed files with 1,684 additions and 1,085 deletions.
38 changes: 22 additions & 16 deletions homeassistant/components/wled/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey

from .const import LOGGER
from .coordinator import WLEDDataUpdateCoordinator
from .const import DOMAIN
from .coordinator import WLEDDataUpdateCoordinator, WLEDReleasesDataUpdateCoordinator

PLATFORMS = (
Platform.BUTTON,
Expand All @@ -21,23 +24,26 @@

type WLEDConfigEntry = ConfigEntry[WLEDDataUpdateCoordinator]

WLED_KEY: HassKey[WLEDReleasesDataUpdateCoordinator] = HassKey(DOMAIN)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the WLED integration.
We set up a single coordinator for fetching WLED releases, which
is used across all WLED devices (and config entries) to avoid
fetching the same data multiple times for each.
"""
hass.data[WLED_KEY] = WLEDReleasesDataUpdateCoordinator(hass)
await hass.data[WLED_KEY].async_request_refresh()
return True


async def async_setup_entry(hass: HomeAssistant, entry: WLEDConfigEntry) -> bool:
"""Set up WLED from a config entry."""
coordinator = WLEDDataUpdateCoordinator(hass, entry=entry)
await coordinator.async_config_entry_first_refresh()

if coordinator.data.info.leds.cct:
LOGGER.error(
(
"WLED device '%s' has a CCT channel, which is not supported by "
"this integration"
),
entry.title,
)
return False

entry.runtime_data = coordinator
entry.runtime_data = WLEDDataUpdateCoordinator(hass, entry=entry)
await entry.runtime_data.async_config_entry_first_refresh()

# Set up all platforms for this device/entry.
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
Expand Down
5 changes: 0 additions & 5 deletions homeassistant/components/wled/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ async def async_step_user(
except WLEDConnectionError:
errors["base"] = "cannot_connect"
else:
if device.info.leds.cct:
return self.async_abort(reason="cct_unsupported")
await self.async_set_unique_id(device.info.mac_address)
self._abort_if_unique_id_configured(
updates={CONF_HOST: user_input[CONF_HOST]}
Expand Down Expand Up @@ -84,9 +82,6 @@ async def async_step_zeroconf(
except WLEDConnectionError:
return self.async_abort(reason="cannot_connect")

if self.discovered_device.info.leds.cct:
return self.async_abort(reason="cct_unsupported")

await self.async_set_unique_id(self.discovered_device.info.mac_address)
self._abort_if_unique_id_configured(updates={CONF_HOST: discovery_info.host})

Expand Down
74 changes: 74 additions & 0 deletions homeassistant/components/wled/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@
from datetime import timedelta
import logging

from wled import LightCapability

from homeassistant.components.light import ColorMode

# Integration domain
DOMAIN = "wled"

LOGGER = logging.getLogger(__package__)
SCAN_INTERVAL = timedelta(seconds=10)
RELEASES_SCAN_INTERVAL = timedelta(hours=3)

# Options
CONF_KEEP_MAIN_LIGHT = "keep_master_light"
Expand All @@ -24,3 +29,72 @@
ATTR_SPEED = "speed"
ATTR_TARGET_BRIGHTNESS = "target_brightness"
ATTR_UDP_PORT = "udp_port"


LIGHT_CAPABILITIES_COLOR_MODE_MAPPING: dict[LightCapability, list[ColorMode]] = {
LightCapability.NONE: [
ColorMode.ONOFF,
],
LightCapability.RGB_COLOR: [
ColorMode.RGB,
],
LightCapability.WHITE_CHANNEL: [
ColorMode.BRIGHTNESS,
],
LightCapability.RGB_COLOR | LightCapability.WHITE_CHANNEL: [
ColorMode.RGBW,
],
LightCapability.COLOR_TEMPERATURE: [
ColorMode.COLOR_TEMP,
],
LightCapability.RGB_COLOR | LightCapability.COLOR_TEMPERATURE: [
ColorMode.RGBWW,
],
LightCapability.WHITE_CHANNEL | LightCapability.COLOR_TEMPERATURE: [
ColorMode.COLOR_TEMP,
],
LightCapability.RGB_COLOR
| LightCapability.WHITE_CHANNEL
| LightCapability.COLOR_TEMPERATURE: [
ColorMode.RGB,
ColorMode.COLOR_TEMP,
],
LightCapability.MANUAL_WHITE: [
ColorMode.BRIGHTNESS,
],
LightCapability.RGB_COLOR | LightCapability.MANUAL_WHITE: [
ColorMode.RGBW,
],
LightCapability.WHITE_CHANNEL | LightCapability.MANUAL_WHITE: [
ColorMode.BRIGHTNESS,
],
LightCapability.RGB_COLOR
| LightCapability.WHITE_CHANNEL
| LightCapability.MANUAL_WHITE: [
ColorMode.RGBW,
ColorMode.WHITE,
],
LightCapability.COLOR_TEMPERATURE | LightCapability.MANUAL_WHITE: [
ColorMode.COLOR_TEMP,
ColorMode.WHITE,
],
LightCapability.RGB_COLOR
| LightCapability.COLOR_TEMPERATURE
| LightCapability.MANUAL_WHITE: [
ColorMode.RGBW,
ColorMode.COLOR_TEMP,
],
LightCapability.WHITE_CHANNEL
| LightCapability.COLOR_TEMPERATURE
| LightCapability.MANUAL_WHITE: [
ColorMode.COLOR_TEMP,
ColorMode.WHITE,
],
LightCapability.RGB_COLOR
| LightCapability.WHITE_CHANNEL
| LightCapability.COLOR_TEMPERATURE
| LightCapability.MANUAL_WHITE: [
ColorMode.RGBW,
ColorMode.COLOR_TEMP,
],
}
34 changes: 31 additions & 3 deletions homeassistant/components/wled/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@

from __future__ import annotations

from wled import WLED, Device as WLEDDevice, WLEDConnectionClosedError, WLEDError
from wled import (
WLED,
Device as WLEDDevice,
Releases,
WLEDConnectionClosedError,
WLEDError,
WLEDReleases,
)

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP
Expand All @@ -15,6 +22,7 @@
DEFAULT_KEEP_MAIN_LIGHT,
DOMAIN,
LOGGER,
RELEASES_SCAN_INTERVAL,
SCAN_INTERVAL,
)

Expand Down Expand Up @@ -101,17 +109,37 @@ async def close_websocket(_: Event) -> None:
async def _async_update_data(self) -> WLEDDevice:
"""Fetch data from WLED."""
try:
device = await self.wled.update(full_update=not self.last_update_success)
device = await self.wled.update()
except WLEDError as error:
raise UpdateFailed(f"Invalid response from API: {error}") from error

# If the device supports a WebSocket, try activating it.
if (
device.info.websocket is not None
and device.info.leds.cct is not True
and not self.wled.connected
and not self.unsub
):
self._use_websocket()

return device


class WLEDReleasesDataUpdateCoordinator(DataUpdateCoordinator[Releases]):
"""Class to manage fetching WLED releases."""

def __init__(self, hass: HomeAssistant) -> None:
"""Initialize global WLED releases updater."""
self.wled = WLEDReleases(session=async_get_clientsession(hass))
super().__init__(
hass,
LOGGER,
name=DOMAIN,
update_interval=RELEASES_SCAN_INTERVAL,
)

async def _async_update_data(self) -> Releases:
"""Fetch release data from WLED."""
try:
return await self.wled.releases()
except WLEDError as error:
raise UpdateFailed(f"Invalid response from GitHub API: {error}") from error
28 changes: 10 additions & 18 deletions homeassistant/components/wled/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,23 @@ async def async_get_config_entry_diagnostics(
coordinator = entry.runtime_data

data: dict[str, Any] = {
"info": async_redact_data(coordinator.data.info.__dict__, "wifi"),
"state": coordinator.data.state.__dict__,
"info": async_redact_data(coordinator.data.info.to_dict(), "wifi"),
"state": coordinator.data.state.to_dict(),
"effects": {
effect.effect_id: effect.name for effect in coordinator.data.effects
effect.effect_id: effect.name
for effect in coordinator.data.effects.values()
},
"palettes": {
palette.palette_id: palette.name for palette in coordinator.data.palettes
palette.palette_id: palette.name
for palette in coordinator.data.palettes.values()
},
"playlists": {
playlist.playlist_id: {
"name": playlist.name,
"repeat": playlist.repeat,
"shuffle": playlist.shuffle,
"end": playlist.end.preset_id if playlist.end else None,
}
for playlist in coordinator.data.playlists
playlist.playlist_id: playlist.name
for playlist in coordinator.data.playlists.values()
},
"presets": {
preset.preset_id: {
"name": preset.name,
"quick_label": preset.quick_label,
"on": preset.on,
"transition": preset.transition,
}
for preset in coordinator.data.presets
preset.preset_id: preset.name
for preset in coordinator.data.presets.values()
},
}
return data
50 changes: 33 additions & 17 deletions homeassistant/components/wled/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import WLEDConfigEntry
from .const import ATTR_COLOR_PRIMARY, ATTR_ON, ATTR_SEGMENT_ID
from .const import (
ATTR_COLOR_PRIMARY,
ATTR_ON,
ATTR_SEGMENT_ID,
LIGHT_CAPABILITIES_COLOR_MODE_MAPPING,
)
from .coordinator import WLEDDataUpdateCoordinator
from .entity import WLEDEntity
from .helpers import wled_exception_handler
Expand Down Expand Up @@ -112,8 +117,6 @@ def __init__(
) -> None:
"""Initialize WLED segment light."""
super().__init__(coordinator=coordinator)
self._rgbw = coordinator.data.info.leds.rgbw
self._wv = coordinator.data.info.leds.wv
self._segment = segment

# Segment 0 uses a simpler name, which is more natural for when using
Expand All @@ -127,39 +130,48 @@ def __init__(
f"{self.coordinator.data.info.mac_address}_{self._segment}"
)

self._attr_color_mode = ColorMode.RGB
self._attr_supported_color_modes = {ColorMode.RGB}
if self._rgbw and self._wv:
self._attr_color_mode = ColorMode.RGBW
self._attr_supported_color_modes = {ColorMode.RGBW}
if (
coordinator.data.info.leds.segment_light_capabilities is not None
and (
color_modes := LIGHT_CAPABILITIES_COLOR_MODE_MAPPING.get(
coordinator.data.info.leds.segment_light_capabilities[segment]
)
)
is not None
):
self._attr_color_mode = color_modes[0]
self._attr_supported_color_modes = set(color_modes)

@property
def available(self) -> bool:
"""Return True if entity is available."""
try:
self.coordinator.data.state.segments[self._segment]
except IndexError:
except KeyError:
return False

return super().available

@property
def rgb_color(self) -> tuple[int, int, int] | None:
"""Return the color value."""
return self.coordinator.data.state.segments[self._segment].color_primary[:3]
if not (color := self.coordinator.data.state.segments[self._segment].color):
return None
return color.primary[:3]

@property
def rgbw_color(self) -> tuple[int, int, int, int] | None:
"""Return the color value."""
return cast(
tuple[int, int, int, int],
self.coordinator.data.state.segments[self._segment].color_primary,
)
if not (color := self.coordinator.data.state.segments[self._segment].color):
return None
return cast(tuple[int, int, int, int], color.primary)

@property
def effect(self) -> str | None:
"""Return the current effect of the light."""
return self.coordinator.data.state.segments[self._segment].effect.name
return self.coordinator.data.effects[
int(self.coordinator.data.state.segments[self._segment].effect_id)
].name

@property
def brightness(self) -> int | None:
Expand All @@ -178,7 +190,7 @@ def brightness(self) -> int | None:
@property
def effect_list(self) -> list[str]:
"""Return the list of supported effects."""
return [effect.name for effect in self.coordinator.data.effects]
return [effect.name for effect in self.coordinator.data.effects.values()]

@property
def is_on(self) -> bool:
Expand Down Expand Up @@ -258,7 +270,11 @@ def async_update_segments(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Update segments."""
segment_ids = {light.segment_id for light in coordinator.data.state.segments}
segment_ids = {
light.segment_id
for light in coordinator.data.state.segments.values()
if light.segment_id is not None
}
new_entities: list[WLEDMainLight | WLEDSegmentLight] = []

# More than 1 segment now? No main? Add main controls
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/wled/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
"integration_type": "device",
"iot_class": "local_push",
"quality_scale": "platinum",
"requirements": ["wled==0.18.0"],
"requirements": ["wled==0.19.2"],
"zeroconf": ["_wled._tcp.local."]
}
Loading

0 comments on commit 41d75e1

Please sign in to comment.