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

Update wled to 0.19.2 #122101

Merged
merged 4 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
],
}
37 changes: 34 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,40 @@
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:
frenck marked this conversation as resolved.
Show resolved Hide resolved
"""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

Check warning on line 148 in homeassistant/components/wled/coordinator.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/wled/coordinator.py#L148

Added line #L148 was not covered by tests
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 @@
) -> 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 @@
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

Check warning on line 159 in homeassistant/components/wled/light.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/wled/light.py#L159

Added line #L159 was not covered by tests
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

Check warning on line 166 in homeassistant/components/wled/light.py

View check run for this annotation

Codecov / codecov/patch

homeassistant/components/wled/light.py#L166

Added line #L166 was not covered by tests
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 @@
@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 @@
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