diff --git a/homeassistant/components/wled/__init__.py b/homeassistant/components/wled/__init__.py index b875f65bf425..f7b9a69d1dec 100644 --- a/homeassistant/components/wled/__init__.py +++ b/homeassistant/components/wled/__init__.py @@ -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.switch import DOMAIN as SWITCH_DOMAIN 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.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.entity import DeviceInfo @@ -20,13 +27,7 @@ from homeassistant.helpers.update_coordinator import ( UpdateFailed, ) -from .const import ( - ATTR_IDENTIFIERS, - ATTR_MANUFACTURER, - ATTR_MODEL, - ATTR_SOFTWARE_VERSION, - DOMAIN, -) +from .const import DOMAIN SCAN_INTERVAL = timedelta(seconds=5) PLATFORMS = (LIGHT_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN) @@ -128,6 +129,8 @@ class WLEDDataUpdateCoordinator(DataUpdateCoordinator[WLEDDevice]): class WLEDEntity(CoordinatorEntity): """Defines a base WLED entity.""" + coordinator: WLEDDataUpdateCoordinator + def __init__( self, *, @@ -172,5 +175,5 @@ class WLEDDeviceEntity(WLEDEntity): ATTR_NAME: self.coordinator.data.info.name, ATTR_MANUFACTURER: self.coordinator.data.info.brand, ATTR_MODEL: self.coordinator.data.info.product, - ATTR_SOFTWARE_VERSION: self.coordinator.data.info.version, + ATTR_SW_VERSION: self.coordinator.data.info.version, } diff --git a/homeassistant/components/wled/config_flow.py b/homeassistant/components/wled/config_flow.py index a06ab3d37abb..bb9d4c0cfe51 100644 --- a/homeassistant/components/wled/config_flow.py +++ b/homeassistant/components/wled/config_flow.py @@ -1,6 +1,8 @@ """Config flow to configure the WLED integration.""" from __future__ import annotations +from typing import Any + import voluptuous as vol 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.data_entry_flow import FlowResult 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 @@ -18,16 +20,16 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): 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.""" return await self._handle_config_flow(user_input) async def async_step_zeroconf( - self, discovery_info: ConfigType | None = None + self, discovery_info: DiscoveryInfoType ) -> FlowResult: """Handle zeroconf discovery.""" - if discovery_info is None: - return self.async_abort(reason="cannot_connect") # Hostname is format: wled-livingroom.local. host = discovery_info["hostname"].rstrip(".") @@ -46,13 +48,13 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): return await self._handle_config_flow(discovery_info, True) async def async_step_zeroconf_confirm( - self, user_input: ConfigType = None + self, user_input: dict[str, Any] | None = None ) -> FlowResult: """Handle a flow initiated by zeroconf.""" return await self._handle_config_flow(user_input) 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: """Config flow handler for WLED.""" source = self.context.get("source") @@ -63,6 +65,9 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN): return self._show_confirm_dialog() 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: user_input[CONF_HOST] = self.context.get(CONF_HOST) user_input[CONF_MAC] = self.context.get(CONF_MAC) diff --git a/homeassistant/components/wled/const.py b/homeassistant/components/wled/const.py index e0880dd40fd3..7cc52601d792 100644 --- a/homeassistant/components/wled/const.py +++ b/homeassistant/components/wled/const.py @@ -7,12 +7,9 @@ DOMAIN = "wled" ATTR_COLOR_PRIMARY = "color_primary" ATTR_DURATION = "duration" ATTR_FADE = "fade" -ATTR_IDENTIFIERS = "identifiers" ATTR_INTENSITY = "intensity" ATTR_LED_COUNT = "led_count" -ATTR_MANUFACTURER = "manufacturer" ATTR_MAX_POWER = "max_power" -ATTR_MODEL = "model" ATTR_ON = "on" ATTR_PALETTE = "palette" ATTR_PLAYLIST = "playlist" diff --git a/homeassistant/components/wled/light.py b/homeassistant/components/wled/light.py index 0151737ece97..f6a51e6159ae 100644 --- a/homeassistant/components/wled/light.py +++ b/homeassistant/components/wled/light.py @@ -58,6 +58,7 @@ async def async_setup_entry( coordinator: WLEDDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] platform = entity_platform.async_get_current_platform() + platform.async_register_entity_service( SERVICE_EFFECT, { @@ -127,7 +128,7 @@ class WLEDMasterLight(LightEntity, WLEDDeviceEntity): @wled_exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" - data = {ATTR_ON: False} + data: dict[str, bool | int] = {ATTR_ON: False} if ATTR_TRANSITION in kwargs: # WLED uses 100ms per unit, so 10 = 1 second. @@ -138,7 +139,7 @@ class WLEDMasterLight(LightEntity, WLEDDeviceEntity): @wled_exception_handler async def async_turn_on(self, **kwargs: Any) -> None: """Turn on the light.""" - data = {ATTR_ON: True} + data: dict[str, bool | int] = {ATTR_ON: True} if ATTR_TRANSITION in kwargs: # WLED uses 100ms per unit, so 10 = 1 second. @@ -230,7 +231,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): } @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].""" color = self.coordinator.data.state.segments[self._segment].color_primary return color_util.color_RGB_to_hs(*color[:3]) @@ -295,7 +296,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): @wled_exception_handler async def async_turn_off(self, **kwargs: Any) -> None: """Turn off the light.""" - data = {ATTR_ON: False} + data: dict[str, bool | int] = {ATTR_ON: False} if ATTR_TRANSITION in kwargs: # WLED uses 100ms per unit, so 10 = 1 second. @@ -312,7 +313,10 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): @wled_exception_handler async def async_turn_on(self, **kwargs: Any) -> None: """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: mireds = color_util.color_temperature_kelvin_to_mired( @@ -385,7 +389,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): speed: int | None = None, ) -> None: """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: data[ATTR_EFFECT] = effect @@ -419,7 +423,7 @@ class WLEDSegmentLight(LightEntity, WLEDDeviceEntity): def async_update_segments( entry: ConfigEntry, coordinator: WLEDDataUpdateCoordinator, - current: dict[int, WLEDSegmentLight], + current: dict[int, WLEDSegmentLight | WLEDMasterLight], async_add_entities, ) -> None: """Update segments.""" @@ -459,7 +463,7 @@ def async_update_segments( async def async_remove_entity( index: int, coordinator: WLEDDataUpdateCoordinator, - current: dict[int, WLEDSegmentLight], + current: dict[int, WLEDSegmentLight | WLEDMasterLight], ) -> None: """Remove WLED segment light from Home Assistant.""" entity = current[index] diff --git a/homeassistant/components/wled/sensor.py b/homeassistant/components/wled/sensor.py index eff40caea22b..4c104e1c9364 100644 --- a/homeassistant/components/wled/sensor.py +++ b/homeassistant/components/wled/sensor.py @@ -74,7 +74,7 @@ class WLEDSensor(WLEDDeviceEntity, SensorEntity): return f"{self.coordinator.data.info.mac_address}_{self._key}" @property - def unit_of_measurement(self) -> str: + def unit_of_measurement(self) -> str | None: """Return the unit this state is expressed in.""" return self._unit_of_measurement diff --git a/mypy.ini b/mypy.ini index ff5971ed9434..3f57c90f2a81 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1325,9 +1325,6 @@ ignore_errors = true [mypy-homeassistant.components.withings.*] ignore_errors = true -[mypy-homeassistant.components.wled.*] -ignore_errors = true - [mypy-homeassistant.components.wunderground.*] ignore_errors = true diff --git a/script/hassfest/mypy_config.py b/script/hassfest/mypy_config.py index 5c375a403daf..b806fcdbf469 100644 --- a/script/hassfest/mypy_config.py +++ b/script/hassfest/mypy_config.py @@ -236,7 +236,6 @@ IGNORED_MODULES: Final[list[str]] = [ "homeassistant.components.wemo.*", "homeassistant.components.wink.*", "homeassistant.components.withings.*", - "homeassistant.components.wled.*", "homeassistant.components.wunderground.*", "homeassistant.components.xbox.*", "homeassistant.components.xiaomi_aqara.*", diff --git a/tests/components/wled/test_config_flow.py b/tests/components/wled/test_config_flow.py index 4ed1723be77f..60f2c59ecbac 100644 --- a/tests/components/wled/test_config_flow.py +++ b/tests/components/wled/test_config_flow.py @@ -119,19 +119,6 @@ async def test_zeroconf_confirm_connection_error( 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( hass: HomeAssistant, aioclient_mock: AiohttpClientMocker ) -> None: