diff --git a/.strict-typing b/.strict-typing index 4c9e626afa6..66a965b3221 100644 --- a/.strict-typing +++ b/.strict-typing @@ -137,6 +137,7 @@ homeassistant.components.uptime.* homeassistant.components.uptimerobot.* homeassistant.components.vacuum.* homeassistant.components.vallox.* +homeassistant.components.velbus.* homeassistant.components.vlc_telnet.* homeassistant.components.water_heater.* homeassistant.components.watttime.* diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index d907557bb01..be5ce04051c 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -3,16 +3,18 @@ from __future__ import annotations import logging +from velbusaio.channels import Channel as VelbusChannel from velbusaio.controller import Velbus import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PORT -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, ServiceCall from homeassistant.helpers import device_registry import homeassistant.helpers.config_validation as cv from homeassistant.helpers.device_registry import DeviceEntry from homeassistant.helpers.entity import DeviceInfo, Entity +from homeassistant.helpers.typing import ConfigType from .const import ( CONF_INTERFACE, @@ -32,7 +34,7 @@ CONFIG_SCHEMA = vol.Schema( PLATFORMS = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] -async def async_setup(hass, config): +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Velbus platform.""" # Import from the configuration file if needed if DOMAIN not in config: @@ -97,7 +99,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if hass.services.has_service(DOMAIN, SERVICE_SCAN): return True - def check_entry_id(interface: str): + def check_entry_id(interface: str) -> str: for entry in hass.config_entries.async_entries(DOMAIN): if "port" in entry.data and entry.data["port"] == interface: return entry.entry_id @@ -105,7 +107,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: "The interface provided is not defined as a port in a Velbus integration" ) - async def scan(call): + async def scan(call: ServiceCall) -> None: await hass.data[DOMAIN][call.data[CONF_INTERFACE]]["cntrl"].scan() hass.services.async_register( @@ -115,7 +117,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: vol.Schema({vol.Required(CONF_INTERFACE): vol.All(cv.string, check_entry_id)}), ) - async def syn_clock(call): + async def syn_clock(call: ServiceCall) -> None: await hass.data[DOMAIN][call.data[CONF_INTERFACE]]["cntrl"].sync_clock() hass.services.async_register( @@ -125,7 +127,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: vol.Schema({vol.Required(CONF_INTERFACE): vol.All(cv.string, check_entry_id)}), ) - async def set_memo_text(call): + async def set_memo_text(call: ServiceCall) -> None: """Handle Memo Text service call.""" memo_text = call.data[CONF_MEMO_TEXT] memo_text.hass = hass @@ -167,32 +169,32 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: class VelbusEntity(Entity): """Representation of a Velbus entity.""" - def __init__(self, channel): + def __init__(self, channel: VelbusChannel) -> None: """Initialize a Velbus entity.""" self._channel = channel @property - def unique_id(self): + def unique_id(self) -> str: """Get unique ID.""" - if (serial := self._channel.get_module_serial()) == 0: - serial = self._channel.get_module_address() + if (serial := self._channel.get_module_serial()) == "": + serial = str(self._channel.get_module_address()) return f"{serial}-{self._channel.get_channel_number()}" @property - def name(self): + def name(self) -> str: """Return the display name of this entity.""" return self._channel.get_name() @property - def should_poll(self): + def should_poll(self) -> bool: """Disable polling.""" return False - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Add listener for state changes.""" self._channel.on_status_update(self._on_update) - async def _on_update(self): + async def _on_update(self) -> None: self.async_write_ha_state() @property @@ -200,7 +202,7 @@ class VelbusEntity(Entity): """Return the device info.""" return DeviceInfo( identifiers={ - (DOMAIN, self._channel.get_module_address()), + (DOMAIN, str(self._channel.get_module_address())), }, manufacturer="Velleman", model=self._channel.get_module_type_name(), diff --git a/homeassistant/components/velbus/binary_sensor.py b/homeassistant/components/velbus/binary_sensor.py index 014b1410fdc..8c67520dd9a 100644 --- a/homeassistant/components/velbus/binary_sensor.py +++ b/homeassistant/components/velbus/binary_sensor.py @@ -1,4 +1,6 @@ """Support for Velbus Binary Sensors.""" +from velbusaio.channels import Button as VelbusButton + from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -25,6 +27,8 @@ async def async_setup_entry( class VelbusBinarySensor(VelbusEntity, BinarySensorEntity): """Representation of a Velbus Binary Sensor.""" + _channel: VelbusButton + @property def is_on(self) -> bool: """Return true if the sensor is on.""" diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py index a065679c4e5..c11698b1358 100644 --- a/homeassistant/components/velbus/climate.py +++ b/homeassistant/components/velbus/climate.py @@ -1,6 +1,10 @@ """Support for Velbus thermostat.""" from __future__ import annotations +from typing import Any + +from velbusaio.channels import Temperature as VelbusTemp + from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, @@ -33,6 +37,7 @@ async def async_setup_entry( class VelbusClimate(VelbusEntity, ClimateEntity): """Representation of a Velbus thermostat.""" + _channel: VelbusTemp _attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE _attr_temperature_unit = TEMP_CELSIUS _attr_hvac_mode = HVAC_MODE_HEAT @@ -40,7 +45,7 @@ class VelbusClimate(VelbusEntity, ClimateEntity): _attr_preset_modes = list(PRESET_MODES) @property - def target_temperature(self) -> int | None: + def target_temperature(self) -> float | None: """Return the temperature we try to reach.""" return self._channel.get_climate_target() @@ -56,7 +61,7 @@ class VelbusClimate(VelbusEntity, ClimateEntity): None, ) - async def async_set_temperature(self, **kwargs) -> None: + async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperatures.""" if (temp := kwargs.get(ATTR_TEMPERATURE)) is None: return diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 3ec5af14397..3facd8c6a33 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -1,6 +1,8 @@ """Config flow for the Velbus platform.""" from __future__ import annotations +from typing import Any + import velbusaio from velbusaio.exceptions import VelbusConnectionFailed import voluptuous as vol @@ -8,16 +10,17 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import CONF_NAME, CONF_PORT from homeassistant.core import HomeAssistant, callback +from homeassistant.data_entry_flow import FlowResult from homeassistant.util import slugify from .const import DOMAIN @callback -def velbus_entries(hass: HomeAssistant): +def velbus_entries(hass: HomeAssistant) -> set[str]: """Return connections for Velbus domain.""" return { - (entry.data[CONF_PORT]) for entry in hass.config_entries.async_entries(DOMAIN) + entry.data[CONF_PORT] for entry in hass.config_entries.async_entries(DOMAIN) } @@ -30,11 +33,11 @@ class VelbusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Initialize the velbus config flow.""" self._errors: dict[str, str] = {} - def _create_device(self, name: str, prt: str): + def _create_device(self, name: str, prt: str) -> FlowResult: """Create an entry async.""" return self.async_create_entry(title=name, data={CONF_PORT: prt}) - async def _test_connection(self, prt): + async def _test_connection(self, prt: str) -> bool: """Try to connect to the velbus with the port specified.""" try: controller = velbusaio.controller.Velbus(prt) @@ -51,7 +54,9 @@ class VelbusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): return True return False - async def async_step_user(self, user_input=None): + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: """Step when user initializes a integration.""" self._errors = {} if user_input is not None: @@ -78,7 +83,7 @@ class VelbusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): errors=self._errors, ) - async def async_step_import(self, user_input=None): + async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult: """Import a config entry.""" user_input[CONF_NAME] = "Velbus Import" prt = user_input[CONF_PORT] diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index 43cec0adb24..2e2ceb761a9 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -3,7 +3,7 @@ from __future__ import annotations from typing import Any -from velbusaio.channels import Channel as VelbusChannel +from velbusaio.channels import Blind as VelbusBlind from homeassistant.components.cover import ( ATTR_POSITION, @@ -38,7 +38,9 @@ async def async_setup_entry( class VelbusCover(VelbusEntity, CoverEntity): """Representation a Velbus cover.""" - def __init__(self, channel: VelbusChannel) -> None: + _channel: VelbusBlind + + def __init__(self, channel: VelbusBlind) -> None: """Initialize the dimmer.""" super().__init__(channel) if self._channel.support_position(): @@ -60,8 +62,7 @@ class VelbusCover(VelbusEntity, CoverEntity): None is unknown, 0 is closed, 100 is fully open Velbus: 100 = closed, 0 = open """ - pos = self._channel.get_position() - return 100 - pos + return 100 - self._channel.get_position() async def async_open_cover(self, **kwargs: Any) -> None: """Open the cover.""" diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index 2c6cd8d8776..ad0b150bf22 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -1,4 +1,14 @@ """Support for Velbus light.""" +from __future__ import annotations + +from typing import Any + +from velbusaio.channels import ( + Button as VelbusButton, + Channel as VelbusChannel, + Dimmer as VelbusDimmer, +) + from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_FLASH, @@ -10,16 +20,24 @@ from homeassistant.components.light import ( SUPPORT_TRANSITION, LightEntity, ) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import VelbusEntity from .const import DOMAIN -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up Velbus switch based on config_entry.""" await hass.data[DOMAIN][entry.entry_id]["tsk"] cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - entities = [] + entities: list[Entity] = [] for channel in cntrl.get_all("light"): entities.append(VelbusLight(channel)) for channel in cntrl.get_all("led"): @@ -30,24 +48,25 @@ async def async_setup_entry(hass, entry, async_add_entities): class VelbusLight(VelbusEntity, LightEntity): """Representation of a Velbus light.""" + _channel: VelbusDimmer _attr_supported_feature = SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION - def __init__(self, channel): + def __init__(self, channel: VelbusDimmer) -> None: """Initialize the dimmer.""" super().__init__(channel) self._attr_name = self._channel.get_name() @property - def is_on(self): + def is_on(self) -> bool: """Return true if the light is on.""" return self._channel.is_on() @property - def brightness(self): + def brightness(self) -> int: """Return the brightness of the light.""" return int((self._channel.get_dimmer_state() * 255) / 100) - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Instruct the Velbus light to turn on.""" if ATTR_BRIGHTNESS in kwargs: # Make sure a low but non-zero value is not rounded down to zero @@ -67,7 +86,7 @@ class VelbusLight(VelbusEntity, LightEntity): ) await getattr(self._channel, attr)(*args) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Instruct the velbus light to turn off.""" attr, *args = ( "set_dimmer_state", @@ -80,25 +99,21 @@ class VelbusLight(VelbusEntity, LightEntity): class VelbusButtonLight(VelbusEntity, LightEntity): """Representation of a Velbus light.""" + _channel: VelbusButton _attr_entity_registry_enabled_default = False _attr_supported_feature = SUPPORT_FLASH - def __init__(self, channel): + def __init__(self, channel: VelbusChannel) -> None: """Initialize the button light (led).""" super().__init__(channel) self._attr_name = f"LED {self._channel.get_name()}" @property - def is_on(self): + def is_on(self) -> Any: """Return true if the light is on.""" return self._channel.is_on() - @property - def brightness(self): - """Return the brightness of the light.""" - return int((self._channel.get_dimmer_state() * 255) / 100) - - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Instruct the Velbus light to turn on.""" if ATTR_FLASH in kwargs: if kwargs[ATTR_FLASH] == FLASH_LONG: @@ -111,7 +126,7 @@ class VelbusButtonLight(VelbusEntity, LightEntity): attr, *args = "set_led_state", "on" await getattr(self._channel, attr)(*args) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: Any) -> None: """Instruct the velbus light to turn off.""" attr, *args = "set_led_state", "off" await getattr(self._channel, attr)(*args) diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 32f016b8ce3..8534a32080e 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -1,22 +1,31 @@ """Support for Velbus sensors.""" from __future__ import annotations +from velbusaio.channels import ButtonCounter, LightSensor, SensorNumber, Temperature + from homeassistant.components.sensor import ( STATE_CLASS_MEASUREMENT, STATE_CLASS_TOTAL_INCREASING, SensorEntity, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, ) +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import VelbusEntity from .const import DOMAIN -async def async_setup_entry(hass, entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: """Set up Velbus switch based on config_entry.""" await hass.data[DOMAIN][entry.entry_id]["tsk"] cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] @@ -31,13 +40,19 @@ async def async_setup_entry(hass, entry, async_add_entities): class VelbusSensor(VelbusEntity, SensorEntity): """Representation of a sensor.""" - def __init__(self, channel, counter=False): + _channel: ButtonCounter | Temperature | LightSensor | SensorNumber + + def __init__( + self, + channel: ButtonCounter | Temperature | LightSensor | SensorNumber, + counter: bool = False, + ) -> None: """Initialize a sensor Velbus entity.""" super().__init__(channel) - self._is_counter = counter + self._is_counter: bool = counter @property - def unique_id(self): + def unique_id(self) -> str: """Return unique ID for counter sensors.""" unique_id = super().unique_id if self._is_counter: @@ -45,7 +60,7 @@ class VelbusSensor(VelbusEntity, SensorEntity): return unique_id @property - def name(self): + def name(self) -> str: """Return the name for the sensor.""" name = super().name if self._is_counter: @@ -53,7 +68,7 @@ class VelbusSensor(VelbusEntity, SensorEntity): return name @property - def device_class(self): + def device_class(self) -> str | None: """Return the device class of the sensor.""" if self._is_counter: return DEVICE_CLASS_ENERGY @@ -64,28 +79,28 @@ class VelbusSensor(VelbusEntity, SensorEntity): return None @property - def native_value(self): + def native_value(self) -> float | int | None: """Return the state of the sensor.""" if self._is_counter: - return self._channel.get_counter_state() - return self._channel.get_state() + return float(self._channel.get_counter_state()) + return float(self._channel.get_state()) @property - def native_unit_of_measurement(self): + def native_unit_of_measurement(self) -> str: """Return the unit this state is expressed in.""" if self._is_counter: - return self._channel.get_counter_unit() - return self._channel.get_unit() + return str(self._channel.get_counter_unit()) + return str(self._channel.get_unit()) @property - def icon(self): + def icon(self) -> str | None: """Icon to use in the frontend.""" if self._is_counter: return "mdi:counter" return None @property - def state_class(self): + def state_class(self) -> str: """Return the state class of this device.""" if self._is_counter: return STATE_CLASS_TOTAL_INCREASING diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py index c7b4a80f196..c3c4c8a5863 100644 --- a/homeassistant/components/velbus/switch.py +++ b/homeassistant/components/velbus/switch.py @@ -1,6 +1,8 @@ """Support for Velbus switches.""" from typing import Any +from velbusaio.channels import Relay as VelbusRelay + from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant @@ -27,6 +29,8 @@ async def async_setup_entry( class VelbusSwitch(VelbusEntity, SwitchEntity): """Representation of a switch.""" + _channel: VelbusRelay + @property def is_on(self) -> bool: """Return true if the switch is on.""" diff --git a/mypy.ini b/mypy.ini index 6859b8b2d48..91bca58ba6b 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1518,6 +1518,17 @@ no_implicit_optional = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.velbus.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +no_implicit_optional = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.vlc_telnet.*] check_untyped_defs = true disallow_incomplete_defs = true