Fix types for WLED (#50001)

This commit is contained in:
Dermot Duffy 2021-05-09 10:34:21 -07:00 committed by GitHub
parent 2ca0eb61dc
commit 4e4042a869
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 37 additions and 45 deletions

View file

@ -10,7 +10,14 @@ from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_NAME, CONF_HOST from homeassistant.const import (
ATTR_IDENTIFIERS,
ATTR_MANUFACTURER,
ATTR_MODEL,
ATTR_NAME,
ATTR_SW_VERSION,
CONF_HOST,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
@ -20,13 +27,7 @@ from homeassistant.helpers.update_coordinator import (
UpdateFailed, UpdateFailed,
) )
from .const import ( from .const import DOMAIN
ATTR_IDENTIFIERS,
ATTR_MANUFACTURER,
ATTR_MODEL,
ATTR_SOFTWARE_VERSION,
DOMAIN,
)
SCAN_INTERVAL = timedelta(seconds=5) SCAN_INTERVAL = timedelta(seconds=5)
PLATFORMS = (LIGHT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN) PLATFORMS = (LIGHT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN)
@ -128,6 +129,8 @@ class WLEDDataUpdateCoordinator(DataUpdateCoordinator[WLEDDevice]):
class WLEDEntity(CoordinatorEntity): class WLEDEntity(CoordinatorEntity):
"""Defines a base WLED entity.""" """Defines a base WLED entity."""
coordinator: WLEDDataUpdateCoordinator
def __init__( def __init__(
self, self,
*, *,
@ -172,5 +175,5 @@ class WLEDDeviceEntity(WLEDEntity):
ATTR_NAME: self.coordinator.data.info.name, ATTR_NAME: self.coordinator.data.info.name,
ATTR_MANUFACTURER: self.coordinator.data.info.brand, ATTR_MANUFACTURER: self.coordinator.data.info.brand,
ATTR_MODEL: self.coordinator.data.info.product, ATTR_MODEL: self.coordinator.data.info.product,
ATTR_SOFTWARE_VERSION: self.coordinator.data.info.version, ATTR_SW_VERSION: self.coordinator.data.info.version,
} }

View file

@ -1,6 +1,8 @@
"""Config flow to configure the WLED integration.""" """Config flow to configure the WLED integration."""
from __future__ import annotations from __future__ import annotations
from typing import Any
import voluptuous as vol import voluptuous as vol
from wled import WLED, WLEDConnectionError from wled import WLED, WLEDConnectionError
@ -8,7 +10,7 @@ from homeassistant.config_entries import SOURCE_ZEROCONF, ConfigFlow
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import DiscoveryInfoType
from .const import DOMAIN from .const import DOMAIN
@ -18,16 +20,16 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
VERSION = 1 VERSION = 1
async def async_step_user(self, user_input: ConfigType | None = None) -> FlowResult: async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle a flow initiated by the user.""" """Handle a flow initiated by the user."""
return await self._handle_config_flow(user_input) return await self._handle_config_flow(user_input)
async def async_step_zeroconf( async def async_step_zeroconf(
self, discovery_info: ConfigType | None = None self, discovery_info: DiscoveryInfoType
) -> FlowResult: ) -> FlowResult:
"""Handle zeroconf discovery.""" """Handle zeroconf discovery."""
if discovery_info is None:
return self.async_abort(reason="cannot_connect")
# Hostname is format: wled-livingroom.local. # Hostname is format: wled-livingroom.local.
host = discovery_info["hostname"].rstrip(".") host = discovery_info["hostname"].rstrip(".")
@ -46,13 +48,13 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
return await self._handle_config_flow(discovery_info, True) return await self._handle_config_flow(discovery_info, True)
async def async_step_zeroconf_confirm( async def async_step_zeroconf_confirm(
self, user_input: ConfigType = None self, user_input: dict[str, Any] | None = None
) -> FlowResult: ) -> FlowResult:
"""Handle a flow initiated by zeroconf.""" """Handle a flow initiated by zeroconf."""
return await self._handle_config_flow(user_input) return await self._handle_config_flow(user_input)
async def _handle_config_flow( async def _handle_config_flow(
self, user_input: ConfigType | None = None, prepare: bool = False self, user_input: dict[str, Any] | None = None, prepare: bool = False
) -> FlowResult: ) -> FlowResult:
"""Config flow handler for WLED.""" """Config flow handler for WLED."""
source = self.context.get("source") source = self.context.get("source")
@ -63,6 +65,9 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
return self._show_confirm_dialog() return self._show_confirm_dialog()
return self._show_setup_form() return self._show_setup_form()
# if prepare is True, user_input can not be None.
assert user_input is not None
if source == SOURCE_ZEROCONF: if source == SOURCE_ZEROCONF:
user_input[CONF_HOST] = self.context.get(CONF_HOST) user_input[CONF_HOST] = self.context.get(CONF_HOST)
user_input[CONF_MAC] = self.context.get(CONF_MAC) user_input[CONF_MAC] = self.context.get(CONF_MAC)

View file

@ -7,12 +7,9 @@ DOMAIN = "wled"
ATTR_COLOR_PRIMARY = "color_primary" ATTR_COLOR_PRIMARY = "color_primary"
ATTR_DURATION = "duration" ATTR_DURATION = "duration"
ATTR_FADE = "fade" ATTR_FADE = "fade"
ATTR_IDENTIFIERS = "identifiers"
ATTR_INTENSITY = "intensity" ATTR_INTENSITY = "intensity"
ATTR_LED_COUNT = "led_count" ATTR_LED_COUNT = "led_count"
ATTR_MANUFACTURER = "manufacturer"
ATTR_MAX_POWER = "max_power" ATTR_MAX_POWER = "max_power"
ATTR_MODEL = "model"
ATTR_ON = "on" ATTR_ON = "on"
ATTR_PALETTE = "palette" ATTR_PALETTE = "palette"
ATTR_PLAYLIST = "playlist" ATTR_PLAYLIST = "playlist"

View file

@ -58,6 +58,7 @@ async def async_setup_entry(
coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
platform = entity_platform.async_get_current_platform() platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service( platform.async_register_entity_service(
SERVICE_EFFECT, SERVICE_EFFECT,
{ {
@ -127,7 +128,7 @@ class WLEDMasterLight(LightEntity, WLEDDeviceEntity):
@wled_exception_handler @wled_exception_handler
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the light.""" """Turn off the light."""
data = {ATTR_ON: False} data: dict[str, bool | int] = {ATTR_ON: False}
if ATTR_TRANSITION in kwargs: if ATTR_TRANSITION in kwargs:
# WLED uses 100ms per unit, so 10 = 1 second. # WLED uses 100ms per unit, so 10 = 1 second.
@ -138,7 +139,7 @@ class WLEDMasterLight(LightEntity, WLEDDeviceEntity):
@wled_exception_handler @wled_exception_handler
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the light.""" """Turn on the light."""
data = {ATTR_ON: True} data: dict[str, bool | int] = {ATTR_ON: True}
if ATTR_TRANSITION in kwargs: if ATTR_TRANSITION in kwargs:
# WLED uses 100ms per unit, so 10 = 1 second. # WLED uses 100ms per unit, so 10 = 1 second.
@ -230,7 +231,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity):
} }
@property @property
def hs_color(self) -> tuple[float, float] | None: def hs_color(self) -> tuple[float, float]:
"""Return the hue and saturation color value [float, float].""" """Return the hue and saturation color value [float, float]."""
color = self.coordinator.data.state.segments[self._segment].color_primary color = self.coordinator.data.state.segments[self._segment].color_primary
return color_util.color_RGB_to_hs(*color[:3]) return color_util.color_RGB_to_hs(*color[:3])
@ -295,7 +296,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity):
@wled_exception_handler @wled_exception_handler
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the light.""" """Turn off the light."""
data = {ATTR_ON: False} data: dict[str, bool | int] = {ATTR_ON: False}
if ATTR_TRANSITION in kwargs: if ATTR_TRANSITION in kwargs:
# WLED uses 100ms per unit, so 10 = 1 second. # WLED uses 100ms per unit, so 10 = 1 second.
@ -312,7 +313,10 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity):
@wled_exception_handler @wled_exception_handler
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the light.""" """Turn on the light."""
data = {ATTR_ON: True, ATTR_SEGMENT_ID: self._segment} data: dict[str, Any] = {
ATTR_ON: True,
ATTR_SEGMENT_ID: self._segment,
}
if ATTR_COLOR_TEMP in kwargs: if ATTR_COLOR_TEMP in kwargs:
mireds = color_util.color_temperature_kelvin_to_mired( mireds = color_util.color_temperature_kelvin_to_mired(
@ -385,7 +389,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity):
speed: int | None = None, speed: int | None = None,
) -> None: ) -> None:
"""Set the effect of a WLED light.""" """Set the effect of a WLED light."""
data = {ATTR_SEGMENT_ID: self._segment} data: dict[str, bool | int | str | None] = {ATTR_SEGMENT_ID: self._segment}
if effect is not None: if effect is not None:
data[ATTR_EFFECT] = effect data[ATTR_EFFECT] = effect
@ -419,7 +423,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity):
def async_update_segments( def async_update_segments(
entry: ConfigEntry, entry: ConfigEntry,
coordinator: WLEDDataUpdateCoordinator, coordinator: WLEDDataUpdateCoordinator,
current: dict[int, WLEDSegmentLight], current: dict[int, WLEDSegmentLight | WLEDMasterLight],
async_add_entities, async_add_entities,
) -> None: ) -> None:
"""Update segments.""" """Update segments."""
@ -459,7 +463,7 @@ def async_update_segments(
async def async_remove_entity( async def async_remove_entity(
index: int, index: int,
coordinator: WLEDDataUpdateCoordinator, coordinator: WLEDDataUpdateCoordinator,
current: dict[int, WLEDSegmentLight], current: dict[int, WLEDSegmentLight | WLEDMasterLight],
) -> None: ) -> None:
"""Remove WLED segment light from Home Assistant.""" """Remove WLED segment light from Home Assistant."""
entity = current[index] entity = current[index]

View file

@ -74,7 +74,7 @@ class WLEDSensor(WLEDDeviceEntity, SensorEntity):
return f"{self.coordinator.data.info.mac_address}_{self._key}" return f"{self.coordinator.data.info.mac_address}_{self._key}"
@property @property
def unit_of_measurement(self) -> str: def unit_of_measurement(self) -> str | None:
"""Return the unit this state is expressed in.""" """Return the unit this state is expressed in."""
return self._unit_of_measurement return self._unit_of_measurement

View file

@ -1325,9 +1325,6 @@ ignore_errors = true
[mypy-homeassistant.components.withings.*] [mypy-homeassistant.components.withings.*]
ignore_errors = true ignore_errors = true
[mypy-homeassistant.components.wled.*]
ignore_errors = true
[mypy-homeassistant.components.wunderground.*] [mypy-homeassistant.components.wunderground.*]
ignore_errors = true ignore_errors = true

View file

@ -236,7 +236,6 @@ IGNORED_MODULES: Final[list[str]] = [
"homeassistant.components.wemo.*", "homeassistant.components.wemo.*",
"homeassistant.components.wink.*", "homeassistant.components.wink.*",
"homeassistant.components.withings.*", "homeassistant.components.withings.*",
"homeassistant.components.wled.*",
"homeassistant.components.wunderground.*", "homeassistant.components.wunderground.*",
"homeassistant.components.xbox.*", "homeassistant.components.xbox.*",
"homeassistant.components.xiaomi_aqara.*", "homeassistant.components.xiaomi_aqara.*",

View file

@ -119,19 +119,6 @@ async def test_zeroconf_confirm_connection_error(
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
@patch("homeassistant.components.wled.WLED.update", side_effect=WLEDConnectionError)
async def test_zeroconf_no_data(
update_mock: MagicMock, hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test we abort if zeroconf provides no data."""
flow = config_flow.WLEDFlowHandler()
flow.hass = hass
result = await flow.async_step_zeroconf()
assert result["reason"] == "cannot_connect"
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
async def test_user_device_exists_abort( async def test_user_device_exists_abort(
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
) -> None: ) -> None: