From 5e525d5d7c8ab29b5df093468ced894babc77e3c Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Wed, 4 Aug 2021 12:47:11 +0200 Subject: [PATCH 01/35] initial commit --- homeassistant/components/velbus/__init__.py | 88 +++++++------------ .../components/velbus/binary_sensor.py | 12 ++- .../components/velbus/config_flow.py | 12 +-- homeassistant/components/velbus/cover.py | 46 ++++------ homeassistant/components/velbus/manifest.json | 5 +- homeassistant/components/velbus/sensor.py | 30 +++---- homeassistant/components/velbus/switch.py | 29 +++--- requirements_all.txt | 6 +- requirements_test_all.txt | 6 +- 9 files changed, 89 insertions(+), 145 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index b798023c465ebf..2e3e7f824c3e09 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -1,13 +1,12 @@ """Support for Velbus devices.""" import logging -import velbus +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.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -15,13 +14,12 @@ _LOGGER = logging.getLogger(__name__) -VELBUS_MESSAGE = "velbus.message" - CONFIG_SCHEMA = vol.Schema( {DOMAIN: vol.Schema({vol.Required(CONF_PORT): cv.string})}, extra=vol.ALLOW_EXTRA ) PLATFORMS = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] +PLATFORMS = ["switch", "binary_sensor", "cover"] async def async_setup(hass, config): @@ -47,35 +45,20 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Establish connection with velbus.""" hass.data.setdefault(DOMAIN, {}) - def callback(): - modules = controller.get_modules() - discovery_info = {"cntrl": controller} - for platform in PLATFORMS: - discovery_info[platform] = [] - for module in modules: - for channel in range(1, module.number_of_channels() + 1): - for platform in PLATFORMS: - if platform in module.get_categories(channel): - discovery_info[platform].append( - (module.get_module_address(), channel) - ) - hass.data[DOMAIN][entry.entry_id] = discovery_info - - for platform in PLATFORMS: - hass.add_job(hass.config_entries.async_forward_entry_setup(entry, platform)) - - try: - controller = velbus.Controller(entry.data[CONF_PORT]) - controller.scan(callback) - except velbus.util.VelbusException as err: - _LOGGER.error("An error occurred: %s", err) - raise ConfigEntryNotReady from err + controller = Velbus(entry.data[CONF_PORT]) + hass.data[DOMAIN][entry.entry_id] = controller + await controller.connect() + + for platform in PLATFORMS: + hass.add_job(hass.config_entries.async_forward_entry_setup(entry, platform)) + + async def scan(self, service=None): + await controller.scan() + + hass.services.async_register(DOMAIN, "scan", scan, schema=vol.Schema({})) def syn_clock(self, service=None): - try: - controller.sync_clock() - except velbus.util.VelbusException as err: - _LOGGER.error("An error occurred: %s", err) + controller.sync_clock() hass.services.async_register(DOMAIN, "sync_clock", syn_clock, schema=vol.Schema({})) @@ -84,12 +67,7 @@ def set_memo_text(service): module_address = service.data[CONF_ADDRESS] memo_text = service.data[CONF_MEMO_TEXT] memo_text.hass = hass - try: - controller.get_module(module_address).set_memo_text( - memo_text.async_render() - ) - except velbus.util.VelbusException as err: - _LOGGER.error("An error occurred while setting memo text: %s", err) + controller.get_module(module_address).set_memo_text(memo_text.async_render()) hass.services.async_register( DOMAIN, @@ -121,25 +99,23 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): class VelbusEntity(Entity): """Representation of a Velbus entity.""" - def __init__(self, module, channel): + def __init__(self, channel): """Initialize a Velbus entity.""" - self._module = module self._channel = channel @property def unique_id(self): """Get unique ID.""" - serial = 0 - if self._module.serial == 0: - serial = self._module.get_module_address() - else: - serial = self._module.serial - return f"{serial}-{self._channel}" + return "{}.{}.{}".format( + self._channel.get_module_type(), + self._channel.get_module_address(), + self._channel.get_channel_number(), + ) @property def name(self): """Return the display name of this entity.""" - return self._module.get_name(self._channel) + return self._channel.get_name() @property def should_poll(self): @@ -148,7 +124,7 @@ def should_poll(self): async def async_added_to_hass(self): """Add listener for state changes.""" - self._module.on_status_update(self._channel, self._on_update) + self._channel.on_status_update(self._on_update) def _on_update(self, state): self.schedule_update_ha_state() @@ -158,16 +134,14 @@ def device_info(self): """Return the device info.""" return { "identifiers": { - (DOMAIN, self._module.get_module_address(), self._module.serial) + ( + DOMAIN, + self._channel.get_module_address(), + self._channel.get_module_serial(), + ) }, - "name": "{} ({})".format( - self._module.get_module_name(), self._module.get_module_address() - ), + "name": self._channel.get_full_name(), "manufacturer": "Velleman", - "model": self._module.get_module_type_name(), - "sw_version": "{}.{}-{}".format( - self._module.memory_map_version, - self._module.build_year, - self._module.build_week, - ), + "model": self._channel.get_module_type_name(), + "sw_version": self._channel.get_module_sw_version(), } diff --git a/homeassistant/components/velbus/binary_sensor.py b/homeassistant/components/velbus/binary_sensor.py index 74263d872340c1..7e6c338a5c1806 100644 --- a/homeassistant/components/velbus/binary_sensor.py +++ b/homeassistant/components/velbus/binary_sensor.py @@ -6,13 +6,11 @@ async def async_setup_entry(hass, entry, async_add_entities): - """Set up Velbus binary sensor based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - modules_data = hass.data[DOMAIN][entry.entry_id]["binary_sensor"] + """Set up Velbus switch based on config_entry.""" + cntrl = hass.data[DOMAIN][entry.entry_id] entities = [] - for address, channel in modules_data: - module = cntrl.get_module(address) - entities.append(VelbusBinarySensor(module, channel)) + for channel in cntrl.get_all("binary_sensor"): + entities.append(VelbusBinarySensor(channel)) async_add_entities(entities) @@ -22,4 +20,4 @@ class VelbusBinarySensor(VelbusEntity, BinarySensorEntity): @property def is_on(self): """Return true if the sensor is on.""" - return self._module.is_closed(self._channel) + return self._channel.is_closed() diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 93dd68c9eea4e2..22fa339c7b9787 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -35,12 +35,12 @@ def _create_device(self, name: str, prt: str): def _test_connection(self, prt): """Try to connect to the velbus with the port specified.""" - try: - controller = velbus.Controller(prt) - except Exception: # pylint: disable=broad-except - self._errors[CONF_PORT] = "cannot_connect" - return False - controller.stop() + # try: + # controller = velbus.Controller(prt) + # except Exception: # pylint: disable=broad-except + # self._errors[CONF_PORT] = "cannot_connect" + # return False + # controller.stop() return True def _prt_in_configuration_exists(self, prt: str) -> bool: diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index efe4fdc964bbf0..757fddf2eac147 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -1,8 +1,6 @@ """Support for Velbus covers.""" import logging -from velbus.util import VelbusException - from homeassistant.components.cover import ( ATTR_POSITION, SUPPORT_CLOSE, @@ -19,13 +17,11 @@ async def async_setup_entry(hass, entry, async_add_entities): - """Set up Velbus cover based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - modules_data = hass.data[DOMAIN][entry.entry_id]["cover"] + """Set up Velbus switch based on config_entry.""" + cntrl = hass.data[DOMAIN][entry.entry_id] entities = [] - for address, channel in modules_data: - module = cntrl.get_module(address) - entities.append(VelbusCover(module, channel)) + for channel in cntrl.get_all("cover"): + entities.append(VelbusCover(channel)) async_add_entities(entities) @@ -35,14 +31,14 @@ class VelbusCover(VelbusEntity, CoverEntity): @property def supported_features(self): """Flag supported features.""" - if self._module.support_position(): + if self._channel.support_position(): return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP | SUPPORT_SET_POSITION return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP @property def is_closed(self): """Return if the cover is closed.""" - if self._module.get_position(self._channel) == 100: + if self._channel.get_position() == 100: return True return False @@ -53,33 +49,21 @@ def current_cover_position(self): None is unknown, 0 is closed, 100 is fully open Velbus: 100 = closed, 0 = open """ - pos = self._module.get_position(self._channel) + pos = self._channel.get_position() return 100 - pos - def open_cover(self, **kwargs): + async def async_open_cover(self, **kwargs): """Open the cover.""" - try: - self._module.open(self._channel) - except VelbusException as err: - _LOGGER.error("A Velbus error occurred: %s", err) + await self._channel.open() - def close_cover(self, **kwargs): + async def async_close_cover(self, **kwargs): """Close the cover.""" - try: - self._module.close(self._channel) - except VelbusException as err: - _LOGGER.error("A Velbus error occurred: %s", err) + await self._channel.close() - def stop_cover(self, **kwargs): + async def async_stop_cover(self, **kwargs): """Stop the cover.""" - try: - self._module.stop(self._channel) - except VelbusException as err: - _LOGGER.error("A Velbus error occurred: %s", err) + await self._channel.stop() - def set_cover_position(self, **kwargs): + async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" - try: - self._module.set(self._channel, (100 - kwargs[ATTR_POSITION])) - except VelbusException as err: - _LOGGER.error("A Velbus error occurred: %s", err) + self._channel.set_position(100 - kwargs[ATTR_POSITION]) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index ba99415944d140..b259713623aed1 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,8 +2,9 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["python-velbus==2.1.2"], + "requirements": ["velbus-aio==2021.8.0"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], - "iot_class": "local_push" + "iot_class": "local_push", + "quality_scale": "platinum" } diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 3a4aa2302f6a16..ac1bf4a6a9ae89 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -7,24 +7,20 @@ async def async_setup_entry(hass, entry, async_add_entities): - """Set up Velbus sensor based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - modules_data = hass.data[DOMAIN][entry.entry_id]["sensor"] + """Set up Velbus switch based on config_entry.""" + cntrl = hass.data[DOMAIN][entry.entry_id] entities = [] - for address, channel in modules_data: - module = cntrl.get_module(address) - entities.append(VelbusSensor(module, channel)) - if module.get_class(channel) == "counter": - entities.append(VelbusSensor(module, channel, True)) + for channel in cntrl.get_all("sensor"): + entities.append(VelbusSensor(channel)) async_add_entities(entities) class VelbusSensor(VelbusEntity, SensorEntity): """Representation of a sensor.""" - def __init__(self, module, channel, counter=False): + def __init__(self, channel, counter=False): """Initialize a sensor Velbus entity.""" - super().__init__(module, channel) + super().__init__(channel) self._is_counter = counter @property @@ -38,25 +34,25 @@ def unique_id(self): @property def device_class(self): """Return the device class of the sensor.""" - if self._module.get_class(self._channel) == "counter" and not self._is_counter: - if self._module.get_counter_unit(self._channel) == ENERGY_KILO_WATT_HOUR: + if self._channel.get_class() == "counter" and not self._is_counter: + if self._channel.get_counter_unit() == ENERGY_KILO_WATT_HOUR: return DEVICE_CLASS_POWER return None - return self._module.get_class(self._channel) + return self._channel.get_class() @property def native_value(self): """Return the state of the sensor.""" if self._is_counter: - return self._module.get_counter_state(self._channel) - return self._module.get_state(self._channel) + return self._channel.get_counter_state() + return self._channel.get_state() @property def native_unit_of_measurement(self): """Return the unit this state is expressed in.""" if self._is_counter: - return self._module.get_counter_unit(self._channel) - return self._module.get_unit(self._channel) + return self._channel.get_counter_unit() + return self._channel.get_unit() @property def icon(self): diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py index 91746b1513ed48..398f0547f0e5c5 100644 --- a/homeassistant/components/velbus/switch.py +++ b/homeassistant/components/velbus/switch.py @@ -1,7 +1,6 @@ """Support for Velbus switches.""" import logging - -from velbus.util import VelbusException +from typing import Any from homeassistant.components.switch import SwitchEntity @@ -13,12 +12,10 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up Velbus switch based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - modules_data = hass.data[DOMAIN][entry.entry_id]["switch"] + cntrl = hass.data[DOMAIN][entry.entry_id] entities = [] - for address, channel in modules_data: - module = cntrl.get_module(address) - entities.append(VelbusSwitch(module, channel)) + for channel in cntrl.get_all("switch"): + entities.append(VelbusSwitch(channel)) async_add_entities(entities) @@ -26,20 +23,14 @@ class VelbusSwitch(VelbusEntity, SwitchEntity): """Representation of a switch.""" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the switch is on.""" - return self._module.is_on(self._channel) + return self._channel.is_on() - def turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs: Any) -> None: """Instruct the switch to turn on.""" - try: - self._module.turn_on(self._channel) - except VelbusException as err: - _LOGGER.error("A Velbus error occurred: %s", err) + await self._channel.turn_on() - def turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs: any) -> None: """Instruct the switch to turn off.""" - try: - self._module.turn_off(self._channel) - except VelbusException as err: - _LOGGER.error("A Velbus error occurred: %s", err) + await self._channel.turn_off() diff --git a/requirements_all.txt b/requirements_all.txt index e8bdb1db15ed4c..238d0ed3fc8404 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1924,9 +1924,6 @@ python-telnet-vlc==2.0.1 # homeassistant.components.twitch python-twitch-client==0.6.0 -# homeassistant.components.velbus -python-velbus==2.1.2 - # homeassistant.components.vlc python-vlc==1.1.2 @@ -2356,6 +2353,9 @@ uvcclient==0.11.0 # homeassistant.components.vallox vallox-websocket-api==2.8.1 +# homeassistant.components.velbus +velbus-aio==2021.8.0 + # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 652eb373c52023..956bb71773d426 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1090,9 +1090,6 @@ python-tado==0.10.0 # homeassistant.components.twitch python-twitch-client==0.6.0 -# homeassistant.components.velbus -python-velbus==2.1.2 - # homeassistant.components.awair python_awair==0.2.1 @@ -1315,6 +1312,9 @@ url-normalize==1.4.1 # homeassistant.components.uvc uvcclient==0.11.0 +# homeassistant.components.velbus +velbus-aio==2021.8.0 + # homeassistant.components.venstar venstarcolortouch==0.14 From 7965e15fa96b665fab9639f5fbb04fd08ae506f2 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Wed, 4 Aug 2021 12:58:42 +0200 Subject: [PATCH 02/35] use new release --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index b259713623aed1..777d0b7ef4e34f 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.0"], + "requirements": ["velbus-aio==2021.8.1"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 238d0ed3fc8404..f82e111518ec30 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.0 +velbus-aio==2021.8.1 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 956bb71773d426..646fa6c403dbb7 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.0 +velbus-aio==2021.8.1 # homeassistant.components.venstar venstarcolortouch==0.14 From b4b189fdb12b1380214eece77c57e7c6655b04c7 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Wed, 4 Aug 2021 20:14:09 +0200 Subject: [PATCH 03/35] Update for sensors --- homeassistant/components/velbus/__init__.py | 2 +- homeassistant/components/velbus/sensor.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 2e3e7f824c3e09..8e5a4bacb71489 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -19,7 +19,7 @@ ) PLATFORMS = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] -PLATFORMS = ["switch", "binary_sensor", "cover"] +PLATFORMS = ["switch", "sensor", "binary_sensor", "cover"] async def async_setup(hass, config): diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index ac1bf4a6a9ae89..ba2915f160bbb2 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -12,6 +12,8 @@ async def async_setup_entry(hass, entry, async_add_entities): entities = [] for channel in cntrl.get_all("sensor"): entities.append(VelbusSensor(channel)) + if channel.get_class() == "counter": + entities.append(VelbusSensor(channel, True)) async_add_entities(entities) @@ -35,7 +37,7 @@ def unique_id(self): def device_class(self): """Return the device class of the sensor.""" if self._channel.get_class() == "counter" and not self._is_counter: - if self._channel.get_counter_unit() == ENERGY_KILO_WATT_HOUR: + if self._channel.get_unit() == ENERGY_KILO_WATT_HOUR: return DEVICE_CLASS_POWER return None return self._channel.get_class() @@ -50,8 +52,6 @@ def native_value(self): @property def native_unit_of_measurement(self): """Return the unit this state is expressed in.""" - if self._is_counter: - return self._channel.get_counter_unit() return self._channel.get_unit() @property From 73ddabe4d15bf74e89607bef26b8bef77982a8ab Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Thu, 5 Aug 2021 08:46:02 +0200 Subject: [PATCH 04/35] big update --- homeassistant/components/velbus/__init__.py | 1 - homeassistant/components/velbus/climate.py | 32 ++++++---------- .../components/velbus/config_flow.py | 14 +++---- homeassistant/components/velbus/light.py | 38 +++++++------------ 4 files changed, 32 insertions(+), 53 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 8e5a4bacb71489..38ec04af5cb55d 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -19,7 +19,6 @@ ) PLATFORMS = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] -PLATFORMS = ["switch", "sensor", "binary_sensor", "cover"] async def async_setup(hass, config): diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py index 6ef91d65c91e8b..a699a3bd0410a3 100644 --- a/homeassistant/components/velbus/climate.py +++ b/homeassistant/components/velbus/climate.py @@ -1,14 +1,12 @@ """Support for Velbus thermostat.""" import logging -from velbus.util import VelbusException - from homeassistant.components.climate import ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_HEAT, SUPPORT_TARGET_TEMPERATURE, ) -from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from . import VelbusEntity from .const import DOMAIN @@ -17,14 +15,12 @@ async def async_setup_entry(hass, entry, async_add_entities): - """Set up Velbus binary sensor based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - modules_data = hass.data[DOMAIN][entry.entry_id]["climate"] + """Set up Velbus switch based on config_entry.""" + cntrl = hass.data[DOMAIN][entry.entry_id] entities = [] - for address, channel in modules_data: - module = cntrl.get_module(address) - entities.append(VelbusClimate(module, channel)) - async_add_entities(entities) + for channel in cntrl.get_all("climate"): + entities.append(VelbusClimate(channel)) + async_add_entities(entities) class VelbusClimate(VelbusEntity, ClimateEntity): @@ -37,15 +33,13 @@ def supported_features(self): @property def temperature_unit(self): - """Return the unit this state is expressed in.""" - if self._module.get_unit(self._channel) == TEMP_CELSIUS: - return TEMP_CELSIUS - return TEMP_FAHRENHEIT + """Return the unit.""" + return TEMP_CELSIUS @property def current_temperature(self): """Return the current temperature.""" - return self._module.get_state(self._channel) + return self._channel.get_state() @property def hvac_mode(self): @@ -66,18 +60,14 @@ def hvac_modes(self): @property def target_temperature(self): """Return the temperature we try to reach.""" - return self._module.get_climate_target() + return self._channel.get_climate_target() def set_temperature(self, **kwargs): """Set new target temperatures.""" temp = kwargs.get(ATTR_TEMPERATURE) if temp is None: return - try: - self._module.set_temp(temp) - except VelbusException as err: - _LOGGER.error("A Velbus error occurred: %s", err) - return + self._channel.set_temp(temp) self.schedule_update_ha_state() def set_hvac_mode(self, hvac_mode): diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 22fa339c7b9787..77e5e733e9d76b 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -1,7 +1,7 @@ """Config flow for the Velbus platform.""" from __future__ import annotations -import velbus +from velbusaio.controller import Velbus import voluptuous as vol from homeassistant import config_entries @@ -35,12 +35,12 @@ def _create_device(self, name: str, prt: str): def _test_connection(self, prt): """Try to connect to the velbus with the port specified.""" - # try: - # controller = velbus.Controller(prt) - # except Exception: # pylint: disable=broad-except - # self._errors[CONF_PORT] = "cannot_connect" - # return False - # controller.stop() + try: + controller = Velbus(prt, True) + controller.stop() + except Exception: # pylint: disable=broad-except + self._errors[CONF_PORT] = "cannot_connect" + return False return True def _prt_in_configuration_exists(self, prt: str) -> bool: diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index 4aebbb27953e50..c35d658ec59f1e 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -1,8 +1,6 @@ """Support for Velbus light.""" import logging -from velbus.util import VelbusException - from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_FLASH, @@ -22,13 +20,11 @@ async def async_setup_entry(hass, entry, async_add_entities): - """Set up Velbus light based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] - modules_data = hass.data[DOMAIN][entry.entry_id]["light"] + """Set up Velbus switch based on config_entry.""" + cntrl = hass.data[DOMAIN][entry.entry_id] entities = [] - for address, channel in modules_data: - module = cntrl.get_module(address) - entities.append(VelbusLight(module, channel)) + for channel in cntrl.get_all("climate"): + entities.append(VelbusLight(channel)) async_add_entities(entities) @@ -38,33 +34,33 @@ class VelbusLight(VelbusEntity, LightEntity): @property def name(self): """Return the display name of this entity.""" - if self._module.light_is_buttonled(self._channel): - return f"LED {self._module.get_name(self._channel)}" - return self._module.get_name(self._channel) + # if self._module.light_is_buttonled(self._channel): + # return f"LED {self._module.get_name(self._channel)}" + return self._channel.get_name() @property def supported_features(self): """Flag supported features.""" - if self._module.light_is_buttonled(self._channel): + if self._channel.light_is_buttonled(): return SUPPORT_FLASH return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION @property def entity_registry_enabled_default(self): """Disable Button LEDs by default.""" - if self._module.light_is_buttonled(self._channel): + if self._channel.light_is_buttonled(): return False return True @property def is_on(self): """Return true if the light is on.""" - return self._module.is_on(self._channel) + return self._channel.is_on() @property def brightness(self): """Return the brightness of the light.""" - return int((self._module.get_dimmer_state(self._channel) * 255) / 100) + return int((self._channel.get_dimmer_state() * 255) / 100) def turn_on(self, **kwargs): """Instruct the Velbus light to turn on.""" @@ -97,14 +93,11 @@ def turn_on(self, **kwargs): self._channel, kwargs.get(ATTR_TRANSITION, 0), ) - try: - getattr(self._module, attr)(*args) - except VelbusException as err: - _LOGGER.error("A Velbus error occurred: %s", err) + getattr(self._channel, attr)(*args) def turn_off(self, **kwargs): """Instruct the velbus light to turn off.""" - if self._module.light_is_buttonled(self._channel): + if self._channel.light_is_buttonled(): attr, *args = "set_led_state", self._channel, "off" else: attr, *args = ( @@ -113,7 +106,4 @@ def turn_off(self, **kwargs): 0, kwargs.get(ATTR_TRANSITION, 0), ) - try: - getattr(self._module, attr)(*args) - except VelbusException as err: - _LOGGER.error("A Velbus error occurred: %s", err) + getattr(self._channel, attr)(*args) From 246e060f43737145c3a352afe5b3ee8d2d62e558 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Thu, 5 Aug 2021 09:13:14 +0200 Subject: [PATCH 05/35] pylint fixes, bump dependancy to 2021.8.2 --- homeassistant/components/velbus/light.py | 6 +++--- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index c35d658ec59f1e..41f3724ad8e6b7 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -34,8 +34,8 @@ class VelbusLight(VelbusEntity, LightEntity): @property def name(self): """Return the display name of this entity.""" - # if self._module.light_is_buttonled(self._channel): - # return f"LED {self._module.get_name(self._channel)}" + if self._channel.light_is_buttonled(): + return f"LED {self._channel.get_name()}" return self._channel.get_name() @property @@ -64,7 +64,7 @@ def brightness(self): def turn_on(self, **kwargs): """Instruct the Velbus light to turn on.""" - if self._module.light_is_buttonled(self._channel): + if self._channel.light_is_buttonled(self._channel): if ATTR_FLASH in kwargs: if kwargs[ATTR_FLASH] == FLASH_LONG: attr, *args = "set_led_state", self._channel, "slow" diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 777d0b7ef4e34f..40a21ba15f0b36 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.1"], + "requirements": ["velbus-aio==2021.8.2"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index f82e111518ec30..1d7124371b677d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.1 +velbus-aio==2021.8.2 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 646fa6c403dbb7..2237dc254e9659 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.1 +velbus-aio==2021.8.2 # homeassistant.components.venstar venstarcolortouch==0.14 From b4f9aec16d1f7adff1d2705f7801582ca0122950 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Thu, 5 Aug 2021 10:49:46 +0200 Subject: [PATCH 06/35] New version to try to fix the tests --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/velbus/test_config_flow.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 40a21ba15f0b36..b0637e6f762bc3 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.2"], + "requirements": ["velbus-aio==2021.8.3"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 1d7124371b677d..1c760abe7bb35d 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.2 +velbus-aio==2021.8.3 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 2237dc254e9659..7d3dc3d15b2b2d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.2 +velbus-aio==2021.8.3 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/tests/components/velbus/test_config_flow.py b/tests/components/velbus/test_config_flow.py index f4a95f0fdf9e42..53855b4beab9c9 100644 --- a/tests/components/velbus/test_config_flow.py +++ b/tests/components/velbus/test_config_flow.py @@ -16,7 +16,7 @@ @pytest.fixture(name="controller_assert") def mock_controller_assert(): """Mock the velbus controller with an assert.""" - with patch("velbus.Controller", side_effect=Exception()): + with patch("velbusaio.Velbus", side_effect=Exception()): yield @@ -24,7 +24,7 @@ def mock_controller_assert(): def mock_controller(): """Mock a successful velbus controller.""" controller = Mock() - with patch("velbus.Controller", return_value=controller): + with patch("velbusaio.Velbus", return_value=controller): yield controller From 61d74e49d9b0b8906a538c157c4e5dad1454ead0 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Fri, 6 Aug 2021 14:23:47 +0200 Subject: [PATCH 07/35] Fix a lot of errors, bump version --- homeassistant/components/velbus/__init__.py | 27 +++---------------- .../components/velbus/config_flow.py | 20 +++++--------- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/velbus/test_config_flow.py | 20 +++----------- 6 files changed, 17 insertions(+), 56 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 38ec04af5cb55d..58a98b65f5c893 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -4,8 +4,8 @@ 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.config_entries import ConfigEntry +from homeassistant.const import CONF_ADDRESS, CONF_PORT from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -21,25 +21,6 @@ PLATFORMS = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] -async def async_setup(hass, config): - """Set up the Velbus platform.""" - # Import from the configuration file if needed - if DOMAIN not in config: - return True - port = config[DOMAIN].get(CONF_PORT) - data = {} - - if port: - data = {CONF_PORT: port, CONF_NAME: "Velbus import"} - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_IMPORT}, data=data - ) - ) - - return True - - async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Establish connection with velbus.""" hass.data.setdefault(DOMAIN, {}) @@ -56,8 +37,8 @@ async def scan(self, service=None): hass.services.async_register(DOMAIN, "scan", scan, schema=vol.Schema({})) - def syn_clock(self, service=None): - controller.sync_clock() + async def syn_clock(self, service=None): + await controller.sync_clock() hass.services.async_register(DOMAIN, "sync_clock", syn_clock, schema=vol.Schema({})) diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 77e5e733e9d76b..5350275aee5cf4 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -33,14 +33,18 @@ def _create_device(self, name: str, prt: str): """Create an entry async.""" return self.async_create_entry(title=name, data={CONF_PORT: prt}) - def _test_connection(self, prt): + async def _test_connection(self, prt): """Try to connect to the velbus with the port specified.""" + print(prt) try: - controller = Velbus(prt, True) + controller = Velbus(prt) + await controller.connect(True) controller.stop() except Exception: # pylint: disable=broad-except self._errors[CONF_PORT] = "cannot_connect" + print("HERE") return False + print("HERE2") return True def _prt_in_configuration_exists(self, prt: str) -> bool: @@ -56,7 +60,7 @@ async def async_step_user(self, user_input=None): name = slugify(user_input[CONF_NAME]) prt = user_input[CONF_PORT] if not self._prt_in_configuration_exists(prt): - if self._test_connection(prt): + if await self._test_connection(prt): return self._create_device(name, prt) else: self._errors[CONF_PORT] = "already_configured" @@ -75,13 +79,3 @@ async def async_step_user(self, user_input=None): ), errors=self._errors, ) - - async def async_step_import(self, user_input=None): - """Import a config entry.""" - user_input[CONF_NAME] = "Velbus Import" - prt = user_input[CONF_PORT] - if self._prt_in_configuration_exists(prt): - # if the velbus import is already in the config - # we should not proceed the import - return self.async_abort(reason="already_configured") - return await self.async_step_user(user_input) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index b0637e6f762bc3..0b765f64d07288 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.3"], + "requirements": ["velbus-aio==2021.8.4"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 1c760abe7bb35d..7d5c16bc224340 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.3 +velbus-aio==2021.8.4 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 7d3dc3d15b2b2d..ef1fac767e3df5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.3 +velbus-aio==2021.8.4 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/tests/components/velbus/test_config_flow.py b/tests/components/velbus/test_config_flow.py index 53855b4beab9c9..a213d0b6a3c7e0 100644 --- a/tests/components/velbus/test_config_flow.py +++ b/tests/components/velbus/test_config_flow.py @@ -16,7 +16,7 @@ @pytest.fixture(name="controller_assert") def mock_controller_assert(): """Mock the velbus controller with an assert.""" - with patch("velbusaio.Velbus", side_effect=Exception()): + with patch("velbusaio.controller.Velbus", side_effect=Exception()): yield @@ -24,7 +24,8 @@ def mock_controller_assert(): def mock_controller(): """Mock a successful velbus controller.""" controller = Mock() - with patch("velbusaio.Velbus", return_value=controller): + with patch("velbusaio.controller.Velbus", return_value=controller): + controller.return_value.connect = Mock(side_effect=Exception) yield controller @@ -75,15 +76,6 @@ async def test_user_fail(hass, controller_assert): assert result["errors"] == {CONF_PORT: "cannot_connect"} -async def test_import(hass, controller): - """Test import step.""" - flow = init_config_flow(hass) - - result = await flow.async_step_import({CONF_PORT: PORT_TCP}) - assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY - assert result["title"] == "velbus_import" - - async def test_abort_if_already_setup(hass): """Test we abort if Daikin is already setup.""" flow = init_config_flow(hass) @@ -91,12 +83,6 @@ async def test_abort_if_already_setup(hass): domain="velbus", data={CONF_PORT: PORT_TCP, CONF_NAME: "velbus home"} ).add_to_hass(hass) - result = await flow.async_step_import( - {CONF_PORT: PORT_TCP, CONF_NAME: "velbus import test"} - ) - assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT - assert result["reason"] == "already_configured" - result = await flow.async_step_user( {CONF_PORT: PORT_TCP, CONF_NAME: "velbus import test"} ) From 928b645a07ed04dc45ee0eafecd0354ab366a451 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Mon, 9 Aug 2021 20:50:12 +0200 Subject: [PATCH 08/35] more work --- homeassistant/components/velbus/__init__.py | 6 +-- homeassistant/components/velbus/climate.py | 2 +- .../components/velbus/config_flow.py | 8 ++-- homeassistant/components/velbus/light.py | 46 +++++++++---------- homeassistant/components/velbus/manifest.json | 2 +- homeassistant/components/velbus/sensor.py | 24 +++++++--- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 8 files changed, 49 insertions(+), 43 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 58a98b65f5c893..f2ef12ca0b137b 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -69,7 +69,7 @@ def set_memo_text(service): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Remove the velbus connection.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - hass.data[DOMAIN][entry.entry_id]["cntrl"].stop() + await hass.data[DOMAIN][entry.entry_id]["cntrl"].stop() hass.data[DOMAIN].pop(entry.entry_id) if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) @@ -106,8 +106,8 @@ async def async_added_to_hass(self): """Add listener for state changes.""" self._channel.on_status_update(self._on_update) - def _on_update(self, state): - self.schedule_update_ha_state() + async def _on_update(self): + self.async_schedule_update_ha_state() @property def device_info(self): diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py index a699a3bd0410a3..79fbbe62979ee8 100644 --- a/homeassistant/components/velbus/climate.py +++ b/homeassistant/components/velbus/climate.py @@ -20,7 +20,7 @@ async def async_setup_entry(hass, entry, async_add_entities): entities = [] for channel in cntrl.get_all("climate"): entities.append(VelbusClimate(channel)) - async_add_entities(entities) + async_add_entities(entities) class VelbusClimate(VelbusEntity, ClimateEntity): diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 5350275aee5cf4..1f48d7a75ba441 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -35,16 +35,14 @@ def _create_device(self, name: str, prt: str): async def _test_connection(self, prt): """Try to connect to the velbus with the port specified.""" - print(prt) try: controller = Velbus(prt) await controller.connect(True) - controller.stop() - except Exception: # pylint: disable=broad-except + await controller.stop() + except Exception as excep: # pylint: disable=broad-except self._errors[CONF_PORT] = "cannot_connect" - print("HERE") + print(excep) return False - print("HERE2") return True def _prt_in_configuration_exists(self, prt: str) -> bool: diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index 41f3724ad8e6b7..205808e4994e1f 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -23,35 +23,35 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up Velbus switch based on config_entry.""" cntrl = hass.data[DOMAIN][entry.entry_id] entities = [] - for channel in cntrl.get_all("climate"): - entities.append(VelbusLight(channel)) + for channel in cntrl.get_all("light"): + entities.append(VelbusLight(channel, False)) + for channel in cntrl.get_all("led"): + entities.append(VelbusLight(channel, True)) async_add_entities(entities) class VelbusLight(VelbusEntity, LightEntity): """Representation of a Velbus light.""" + def __init__(self, channel, led): + """Initialize a light Velbus entity.""" + super().__init__(channel) + self._is_led = led + @property def name(self): """Return the display name of this entity.""" - if self._channel.light_is_buttonled(): + if self._is_led: return f"LED {self._channel.get_name()}" return self._channel.get_name() @property def supported_features(self): """Flag supported features.""" - if self._channel.light_is_buttonled(): + if self._is_led: return SUPPORT_FLASH return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION - @property - def entity_registry_enabled_default(self): - """Disable Button LEDs by default.""" - if self._channel.light_is_buttonled(): - return False - return True - @property def is_on(self): """Return true if the light is on.""" @@ -62,18 +62,18 @@ def brightness(self): """Return the brightness of the light.""" return int((self._channel.get_dimmer_state() * 255) / 100) - def turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs): """Instruct the Velbus light to turn on.""" - if self._channel.light_is_buttonled(self._channel): + if self._is_led: if ATTR_FLASH in kwargs: if kwargs[ATTR_FLASH] == FLASH_LONG: - attr, *args = "set_led_state", self._channel, "slow" + attr, *args = "set_led_state", "slow" elif kwargs[ATTR_FLASH] == FLASH_SHORT: - attr, *args = "set_led_state", self._channel, "fast" + attr, *args = "set_led_state", "fast" else: - attr, *args = "set_led_state", self._channel, "on" + attr, *args = "set_led_state", "on" else: - attr, *args = "set_led_state", self._channel, "on" + attr, *args = "set_led_state", "on" else: if ATTR_BRIGHTNESS in kwargs: # Make sure a low but non-zero value is not rounded down to zero @@ -83,22 +83,20 @@ def turn_on(self, **kwargs): brightness = max(int((kwargs[ATTR_BRIGHTNESS] * 100) / 255), 1) attr, *args = ( "set_dimmer_state", - self._channel, brightness, kwargs.get(ATTR_TRANSITION, 0), ) else: attr, *args = ( "restore_dimmer_state", - self._channel, kwargs.get(ATTR_TRANSITION, 0), ) - getattr(self._channel, attr)(*args) + await getattr(self._channel, attr)(*args) - def turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs): """Instruct the velbus light to turn off.""" - if self._channel.light_is_buttonled(): - attr, *args = "set_led_state", self._channel, "off" + if self._is_led: + attr, *args = "set_led_state", "off" else: attr, *args = ( "set_dimmer_state", @@ -106,4 +104,4 @@ def turn_off(self, **kwargs): 0, kwargs.get(ATTR_TRANSITION, 0), ) - getattr(self._channel, attr)(*args) + await getattr(self._channel, attr)(*args) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 0b765f64d07288..7e80cfa0d6b3f6 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.4"], + "requirements": ["velbus-aio==2021.8.5"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push", diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index ba2915f160bbb2..33c8f3cb7ecf32 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -1,6 +1,6 @@ """Support for Velbus sensors.""" from homeassistant.components.sensor import SensorEntity -from homeassistant.const import DEVICE_CLASS_POWER, ENERGY_KILO_WATT_HOUR +from homeassistant.const import DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER from . import VelbusEntity from .const import DOMAIN @@ -12,7 +12,7 @@ async def async_setup_entry(hass, entry, async_add_entities): entities = [] for channel in cntrl.get_all("sensor"): entities.append(VelbusSensor(channel)) - if channel.get_class() == "counter": + if channel.is_counter_channel(): entities.append(VelbusSensor(channel, True)) async_add_entities(entities) @@ -33,14 +33,22 @@ def unique_id(self): unique_id = f"{unique_id}-counter" return unique_id + @property + def name(self): + """Return the name for the sensor.""" + name = super().name + if self._is_counter: + name = f"{name}-counter" + return name + @property def device_class(self): """Return the device class of the sensor.""" - if self._channel.get_class() == "counter" and not self._is_counter: - if self._channel.get_unit() == ENERGY_KILO_WATT_HOUR: - return DEVICE_CLASS_POWER - return None - return self._channel.get_class() + if self._is_counter: + return DEVICE_CLASS_POWER + elif self._channel.is_counter_channel(): + return DEVICE_CLASS_ENERGY + return None @property def native_value(self): @@ -52,6 +60,8 @@ def native_value(self): @property def native_unit_of_measurement(self): """Return the unit this state is expressed in.""" + if self._is_counter: + return self._channel.get_counter_unit() return self._channel.get_unit() @property diff --git a/requirements_all.txt b/requirements_all.txt index 7d5c16bc224340..db9870d6266985 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.4 +velbus-aio==2021.8.5 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ef1fac767e3df5..6c764332acb93b 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.4 +velbus-aio==2021.8.5 # homeassistant.components.venstar venstarcolortouch==0.14 From dd729a1bce3621c113d93a511e5313c44261958f Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Mon, 9 Aug 2021 21:07:14 +0200 Subject: [PATCH 09/35] Bump version --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 7e80cfa0d6b3f6..744ddf4f2057f3 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.5"], + "requirements": ["velbus-aio==2021.8.6"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index db9870d6266985..5e5ca2865ef08e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.5 +velbus-aio==2021.8.6 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6c764332acb93b..db497c52554d78 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.5 +velbus-aio==2021.8.6 # homeassistant.components.venstar venstarcolortouch==0.14 From 8cd2d2ece733aa904d2c30b0caf64189eb1e0be0 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Tue, 10 Aug 2021 11:33:19 +0200 Subject: [PATCH 10/35] Adde dimmer support --- homeassistant/components/velbus/light.py | 1 - homeassistant/components/velbus/manifest.json | 2 +- homeassistant/components/velbus/sensor.py | 9 +++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index 205808e4994e1f..397b07668338dc 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -100,7 +100,6 @@ async def async_turn_off(self, **kwargs): else: attr, *args = ( "set_dimmer_state", - self._channel, 0, kwargs.get(ATTR_TRANSITION, 0), ) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 744ddf4f2057f3..548b0f750dc722 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -6,5 +6,5 @@ "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push", - "quality_scale": "platinum" + "quality_scale": "gold" } diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 33c8f3cb7ecf32..f081b4241aeb79 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -1,5 +1,5 @@ """Support for Velbus sensors.""" -from homeassistant.components.sensor import SensorEntity +from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity from homeassistant.const import DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER from . import VelbusEntity @@ -46,7 +46,7 @@ def device_class(self): """Return the device class of the sensor.""" if self._is_counter: return DEVICE_CLASS_POWER - elif self._channel.is_counter_channel(): + if self._channel.is_counter_channel(): return DEVICE_CLASS_ENERGY return None @@ -70,3 +70,8 @@ def icon(self): if self._is_counter: return "mdi:counter" return None + + @property + def state_class(self): + """Return the state class of this device.""" + return STATE_CLASS_MEASUREMENT From 8662d688ebf75b034b4b08fc06db7a7f4fef964c Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Tue, 10 Aug 2021 12:18:08 +0200 Subject: [PATCH 11/35] Make sure the counters are useable in the energy dashboard --- homeassistant/components/velbus/sensor.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index f081b4241aeb79..3f6000ad9b9701 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -1,6 +1,11 @@ """Support for Velbus sensors.""" +from __future__ import annotations + +from datetime import datetime + from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity from homeassistant.const import DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER +from homeassistant.util.dt import utc_from_timestamp from . import VelbusEntity from .const import DOMAIN @@ -45,9 +50,9 @@ def name(self): def device_class(self): """Return the device class of the sensor.""" if self._is_counter: - return DEVICE_CLASS_POWER - if self._channel.is_counter_channel(): return DEVICE_CLASS_ENERGY + if self._channel.is_counter_channel(): + return DEVICE_CLASS_POWER return None @property @@ -75,3 +80,10 @@ def icon(self): def state_class(self): """Return the state class of this device.""" return STATE_CLASS_MEASUREMENT + + @property + def last_reset(self) -> datetime | None: + """Return the last reset date of this sensor.""" + if self._is_counter: + return utc_from_timestamp(0) + return None From 2a7456b79f84f68518c036cf2824c79da5ba5b25 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Tue, 10 Aug 2021 16:51:55 +0200 Subject: [PATCH 12/35] bump version --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 548b0f750dc722..ddc34bfb093ef0 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.6"], + "requirements": ["velbus-aio==2021.8.7"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push", diff --git a/requirements_all.txt b/requirements_all.txt index 5e5ca2865ef08e..e55abbc8f6a5b9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.6 +velbus-aio==2021.8.7 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index db497c52554d78..c0401b3727e09d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.6 +velbus-aio==2021.8.7 # homeassistant.components.venstar venstarcolortouch==0.14 From 4e95c4d2f11edf8e4178a0eb68875566997a58c6 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Wed, 11 Aug 2021 10:08:15 +0200 Subject: [PATCH 13/35] Fix testcases --- homeassistant/components/velbus/config_flow.py | 4 ++-- tests/components/velbus/test_config_flow.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 1f48d7a75ba441..041dbe69725ee8 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -1,7 +1,7 @@ """Config flow for the Velbus platform.""" from __future__ import annotations -from velbusaio.controller import Velbus +import velbusaio import voluptuous as vol from homeassistant import config_entries @@ -36,7 +36,7 @@ def _create_device(self, name: str, prt: str): async def _test_connection(self, prt): """Try to connect to the velbus with the port specified.""" try: - controller = Velbus(prt) + controller = velbusaio.controller.Velbus(prt) await controller.connect(True) await controller.stop() except Exception as excep: # pylint: disable=broad-except diff --git a/tests/components/velbus/test_config_flow.py b/tests/components/velbus/test_config_flow.py index a213d0b6a3c7e0..48ba6be1d9d6cd 100644 --- a/tests/components/velbus/test_config_flow.py +++ b/tests/components/velbus/test_config_flow.py @@ -1,5 +1,5 @@ """Tests for the Velbus config flow.""" -from unittest.mock import Mock, patch +from unittest.mock import AsyncMock, patch import pytest @@ -23,9 +23,8 @@ def mock_controller_assert(): @pytest.fixture(name="controller") def mock_controller(): """Mock a successful velbus controller.""" - controller = Mock() + controller = AsyncMock() with patch("velbusaio.controller.Velbus", return_value=controller): - controller.return_value.connect = Mock(side_effect=Exception) yield controller From 4c0922b0395649b8a98f434875fa7d276ce23c98 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Wed, 11 Aug 2021 12:52:01 +0200 Subject: [PATCH 14/35] Update after review --- homeassistant/components/velbus/__init__.py | 9 ++++----- homeassistant/components/velbus/config_flow.py | 3 +-- homeassistant/components/velbus/manifest.json | 3 +-- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index f2ef12ca0b137b..66799a17101e6d 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -29,18 +29,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN][entry.entry_id] = controller await controller.connect() - for platform in PLATFORMS: - hass.add_job(hass.config_entries.async_forward_entry_setup(entry, platform)) + hass.config_entries.async_setup_platforms(entry, PLATFORMS) async def scan(self, service=None): await controller.scan() - hass.services.async_register(DOMAIN, "scan", scan, schema=vol.Schema({})) + hass.services.async_register(DOMAIN, "scan", scan) async def syn_clock(self, service=None): await controller.sync_clock() - hass.services.async_register(DOMAIN, "sync_clock", syn_clock, schema=vol.Schema({})) + hass.services.async_register(DOMAIN, "sync_clock", syn_clock) def set_memo_text(service): """Handle Memo Text service call.""" @@ -107,7 +106,7 @@ async def async_added_to_hass(self): self._channel.on_status_update(self._on_update) async def _on_update(self): - self.async_schedule_update_ha_state() + self.async_write_ha_state() @property def device_info(self): diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 041dbe69725ee8..c0f3f9058316bf 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -39,9 +39,8 @@ async def _test_connection(self, prt): controller = velbusaio.controller.Velbus(prt) await controller.connect(True) await controller.stop() - except Exception as excep: # pylint: disable=broad-except + except Exception: # pylint: disable=broad-except self._errors[CONF_PORT] = "cannot_connect" - print(excep) return False return True diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index ddc34bfb093ef0..3a263b86a56b92 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -5,6 +5,5 @@ "requirements": ["velbus-aio==2021.8.7"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], - "iot_class": "local_push", - "quality_scale": "gold" + "iot_class": "local_push" } From 6561a0fb18c15d7062dcf13a8db0a5d7235cfd12 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Wed, 11 Aug 2021 13:31:28 +0200 Subject: [PATCH 15/35] Bump version to be able to have some decent exception catches, add the temperature device class --- homeassistant/components/velbus/config_flow.py | 3 ++- homeassistant/components/velbus/manifest.json | 2 +- homeassistant/components/velbus/sensor.py | 8 +++++++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 12 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index c0f3f9058316bf..8dbedcf469b604 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -2,6 +2,7 @@ from __future__ import annotations import velbusaio +from velbusaio.exceptions import VelbuConnectionFailed import voluptuous as vol from homeassistant import config_entries @@ -39,7 +40,7 @@ async def _test_connection(self, prt): controller = velbusaio.controller.Velbus(prt) await controller.connect(True) await controller.stop() - except Exception: # pylint: disable=broad-except + except VelbuConnectionFailed: self._errors[CONF_PORT] = "cannot_connect" return False return True diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 3a263b86a56b92..c6f661b23681b8 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.7"], + "requirements": ["velbus-aio==2021.8.8"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push" diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 3f6000ad9b9701..6df6576476103a 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -4,7 +4,11 @@ from datetime import datetime from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity -from homeassistant.const import DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER +from homeassistant.const import ( + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, +) from homeassistant.util.dt import utc_from_timestamp from . import VelbusEntity @@ -53,6 +57,8 @@ def device_class(self): return DEVICE_CLASS_ENERGY if self._channel.is_counter_channel(): return DEVICE_CLASS_POWER + if self._channel.is_temperature(): + return DEVICE_CLASS_TEMPERATURE return None @property diff --git a/requirements_all.txt b/requirements_all.txt index e55abbc8f6a5b9..9f9b8676b4932a 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.7 +velbus-aio==2021.8.8 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index c0401b3727e09d..5552baa5cfdade 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.7 +velbus-aio==2021.8.8 # homeassistant.components.venstar venstarcolortouch==0.14 From 41970fb1dc04c890f88c6befb2d437b76ea3c57c Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Thu, 12 Aug 2021 09:55:23 +0200 Subject: [PATCH 16/35] Readd the import of the platform from config file, but add a deprecation warning --- homeassistant/components/velbus/__init__.py | 25 +++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 66799a17101e6d..e8e52af877ebb8 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -4,8 +4,8 @@ from velbusaio.controller import Velbus import voluptuous as vol -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_ADDRESS, CONF_PORT +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PORT from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -21,6 +21,27 @@ PLATFORMS = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] +async def async_setup(hass, config): + """Set up the Velbus platform.""" + # Import from the configuration file if needed + if DOMAIN not in config: + return True + + _LOGGER.warning("Loading VELBUS via platform config is deprecated") + + port = config[DOMAIN].get(CONF_PORT) + data = {} + + if port: + data = {CONF_PORT: port, CONF_NAME: "Velbus import"} + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_IMPORT}, data=data + ) + ) + return True + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Establish connection with velbus.""" hass.data.setdefault(DOMAIN, {}) From e9b5391c6247a505b2213556c89c69b6f482316e Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Thu, 12 Aug 2021 13:43:40 +0200 Subject: [PATCH 17/35] More comments updated --- homeassistant/components/velbus/__init__.py | 2 +- homeassistant/components/velbus/config_flow.py | 4 ++-- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index e8e52af877ebb8..4537a677087235 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -27,7 +27,7 @@ async def async_setup(hass, config): if DOMAIN not in config: return True - _LOGGER.warning("Loading VELBUS via platform config is deprecated") + _LOGGER.warning("Loading VELBUS via configuration.yaml is deprecated") port = config[DOMAIN].get(CONF_PORT) data = {} diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 8dbedcf469b604..8dfd1e2267dcb4 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -2,7 +2,7 @@ from __future__ import annotations import velbusaio -from velbusaio.exceptions import VelbuConnectionFailed +from velbusaio.exceptions import VelbusConnectionFailed import voluptuous as vol from homeassistant import config_entries @@ -40,7 +40,7 @@ async def _test_connection(self, prt): controller = velbusaio.controller.Velbus(prt) await controller.connect(True) await controller.stop() - except VelbuConnectionFailed: + except VelbusConnectionFailed: self._errors[CONF_PORT] = "cannot_connect" return False return True diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index c6f661b23681b8..9c18c7d813280f 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.8"], + "requirements": ["velbus-aio==2021.8.9"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push" diff --git a/requirements_all.txt b/requirements_all.txt index 9f9b8676b4932a..0e8976fbe651c1 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.8 +velbus-aio==2021.8.9 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 5552baa5cfdade..bae128016835e0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.8 +velbus-aio==2021.8.9 # homeassistant.components.venstar venstarcolortouch==0.14 From 708298d53de7d13f7d6c32b6891c7853b17b2c09 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Mon, 16 Aug 2021 16:43:54 +0200 Subject: [PATCH 18/35] Fix lefover index --- homeassistant/components/velbus/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 4537a677087235..b2878f56de616a 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -89,7 +89,7 @@ def set_memo_text(service): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Remove the velbus connection.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - await hass.data[DOMAIN][entry.entry_id]["cntrl"].stop() + await hass.data[DOMAIN][entry.entry_id].stop() hass.data[DOMAIN].pop(entry.entry_id) if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) From ee580d7c443c8b904323907bde5956d4704bc5b9 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Mon, 16 Aug 2021 16:55:53 +0200 Subject: [PATCH 19/35] Fix unique id to be backwards compatible --- homeassistant/components/velbus/__init__.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index b2878f56de616a..ef1cf725a0ac38 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -106,11 +106,12 @@ def __init__(self, channel): @property def unique_id(self): """Get unique ID.""" - return "{}.{}.{}".format( - self._channel.get_module_type(), - self._channel.get_module_address(), - self._channel.get_channel_number(), - ) + serial = 0 + if self._channel.get_module_serial() == 0: + serial = self._channel.get_module_address() + else: + serial = self._channel.get_module_serial() + return f"{serial}-{self._channel.get_channel_number()}" @property def name(self): From efed4c9e433468963bdee04ca097dba4ff287e61 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Mon, 16 Aug 2021 16:56:18 +0200 Subject: [PATCH 20/35] Fix small bug in covers --- homeassistant/components/velbus/cover.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index 757fddf2eac147..73cb4b5b2ddba2 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -38,9 +38,7 @@ def supported_features(self): @property def is_closed(self): """Return if the cover is closed.""" - if self._channel.get_position() == 100: - return True - return False + return self._channel.is_closed() @property def current_cover_position(self): From c6ea7e129fa49c750d312de9a7c57b5b909900b6 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Mon, 16 Aug 2021 21:00:22 +0200 Subject: [PATCH 21/35] Fix testcases --- tests/components/velbus/test_config_flow.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/components/velbus/test_config_flow.py b/tests/components/velbus/test_config_flow.py index 48ba6be1d9d6cd..e028545a57d030 100644 --- a/tests/components/velbus/test_config_flow.py +++ b/tests/components/velbus/test_config_flow.py @@ -2,6 +2,7 @@ from unittest.mock import AsyncMock, patch import pytest +from velbusaio.exceptions import VelbusConnectionFailed from homeassistant import data_entry_flow from homeassistant.components.velbus import config_flow @@ -16,7 +17,7 @@ @pytest.fixture(name="controller_assert") def mock_controller_assert(): """Mock the velbus controller with an assert.""" - with patch("velbusaio.controller.Velbus", side_effect=Exception()): + with patch("velbusaio.controller.Velbus", side_effect=VelbusConnectionFailed()): yield From 442b16424f81c4c066bb773579134dd5ac85a690 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Mon, 16 Aug 2021 21:03:39 +0200 Subject: [PATCH 22/35] Changes for theenery dashboard --- homeassistant/components/velbus/sensor.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 6df6576476103a..32cfc325f07428 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -1,15 +1,16 @@ """Support for Velbus sensors.""" from __future__ import annotations -from datetime import datetime - -from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity +from homeassistant.components.sensor import ( + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + SensorEntity, +) from homeassistant.const import ( DEVICE_CLASS_ENERGY, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, ) -from homeassistant.util.dt import utc_from_timestamp from . import VelbusEntity from .const import DOMAIN @@ -85,11 +86,6 @@ def icon(self): @property def state_class(self): """Return the state class of this device.""" - return STATE_CLASS_MEASUREMENT - - @property - def last_reset(self) -> datetime | None: - """Return the last reset date of this sensor.""" if self._is_counter: - return utc_from_timestamp(0) - return None + return STATE_CLASS_TOTAL_INCREASING + return STATE_CLASS_MEASUREMENT From 2047e2323d53e97b3be15ba273ae96e1791bb22e Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Wed, 18 Aug 2021 19:54:59 +0200 Subject: [PATCH 23/35] Fixed services --- homeassistant/components/velbus/__init__.py | 75 ++++++++++++++++--- homeassistant/components/velbus/services.yaml | 38 ++++++++-- 2 files changed, 94 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index ef1cf725a0ac38..a5fe21b873d938 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -7,6 +7,7 @@ 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.helpers import device_registry as dr import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -50,21 +51,73 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN][entry.entry_id] = controller await controller.connect() + # add the controller as a device + device_registry = await hass.helpers.device_registry.async_get_registry() + device_registry.async_get_or_create( + name=f"{entry.title}-interface", + config_entry_id=entry.entry_id, + identifiers={(DOMAIN, entry.data[CONF_PORT])}, + connections={(CONF_PORT, entry.data[CONF_PORT])}, + ) + + # setup the platforms hass.config_entries.async_setup_platforms(entry, PLATFORMS) - async def scan(self, service=None): - await controller.scan() + if hass.services.has_service(DOMAIN, "scan"): + return True - hass.services.async_register(DOMAIN, "scan", scan) + async def get_entry_id(interface: str): + device_registry = dr.async_get(hass) + device_entry = device_registry.async_get(interface) + if device_entry is None: + _LOGGER.warning("Missing device: %s", device_id) + return None + for entry in hass.config_entries.async_entries(DOMAIN): + if entry.entry_id in device_entry.config_entries: + return entry.entry_id + _LOGGER.warning("Can not find the entry: %s", entry.entry_id) + return None + + async def scan(call): + entry_id = await get_entry_id(call.data["interface"]) + if not entry_id: + return + await hass.data[DOMAIN][entry_id].scan() + + hass.services.async_register( + DOMAIN, + "scan", + scan, + vol.Schema( + { + vol.Required("interface"): cv.string, + } + ), + ) - async def syn_clock(self, service=None): - await controller.sync_clock() + async def syn_clock(call): + entry_id = await get_entry_id(call.data["interface"]) + if not entry_id: + return + await hass.data[DOMAIN][entry_id].sync_clock() - hass.services.async_register(DOMAIN, "sync_clock", syn_clock) + hass.services.async_register( + DOMAIN, + "sync_clock", + syn_clock, + vol.Schema( + { + vol.Required("interface"): cv.string, + } + ), + ) - def set_memo_text(service): + def set_memo_text(call): """Handle Memo Text service call.""" - module_address = service.data[CONF_ADDRESS] + cntrl_id = await get_entry_id(call.data["interface"]) + if not cntrl_id: + return + # TODO get the module address from call.data[CONF_ADDRESS] memo_text = service.data[CONF_MEMO_TEXT] memo_text.hass = hass controller.get_module(module_address).set_memo_text(memo_text.async_render()) @@ -75,6 +128,7 @@ def set_memo_text(service): set_memo_text, vol.Schema( { + vol.Required("interface"): cv.string, vol.Required(CONF_ADDRESS): vol.All( vol.Coerce(int), vol.Range(min=0, max=255) ), @@ -106,11 +160,8 @@ def __init__(self, channel): @property def unique_id(self): """Get unique ID.""" - serial = 0 - if self._channel.get_module_serial() == 0: + if serial := self._channel.get_module_serial(): serial = self._channel.get_module_address() - else: - serial = self._channel.get_module_serial() return f"{serial}-{self._channel.get_channel_number()}" @property diff --git a/homeassistant/components/velbus/services.yaml b/homeassistant/components/velbus/services.yaml index 9fed172fad4d72..d7b7fe56be4006 100644 --- a/homeassistant/components/velbus/services.yaml +++ b/homeassistant/components/velbus/services.yaml @@ -1,6 +1,26 @@ sync_clock: name: Sync clock description: Sync the velbus modules clock to the Home Assistant clock, this is the same as the 'sync clock' from VelbusLink + fields: + interface: + name: Interface + description: The velbus interface to send the command to + required: true + selector: + device: + integration: velbus + +scan: + name: Scan + description: Scan the velbus modules, this will be need if you see unknown module warnings in the logs, or when you added new modules + fields: + interface: + name: Interface + description: The velbus interface to send the command to + required: true + selector: + device: + integration: velbus set_memo_text: name: Set memo text @@ -8,16 +28,20 @@ set_memo_text: Set the memo text to the display of modules like VMBGPO, VMBGPOD Be sure the page(s) of the module is configured to display the memo text. fields: + interface: + name: Interface + description: The velbus interface to send the command to + required: true + selector: + device: + integration: velbus address: - name: Address - description: > - The module address in decimal format. - The decimal addresses are displayed in front of the modules listed at the integration page. + name: Module + description: The module to send the memotext to required: true selector: - number: - min: 0 - max: 255 + device: + integration: velbus memo_text: name: Memo text description: > From 75e2e31a2e02d70c690517f273da407f57295ed6 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Wed, 18 Aug 2021 20:13:01 +0200 Subject: [PATCH 24/35] Fix memo text --- homeassistant/components/velbus/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index a5fe21b873d938..cf73d5179d0da8 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -112,15 +112,16 @@ async def syn_clock(call): ), ) - def set_memo_text(call): + async def set_memo_text(call): """Handle Memo Text service call.""" cntrl_id = await get_entry_id(call.data["interface"]) if not cntrl_id: return - # TODO get the module address from call.data[CONF_ADDRESS] memo_text = service.data[CONF_MEMO_TEXT] memo_text.hass = hass - controller.get_module(module_address).set_memo_text(memo_text.async_render()) + await hass.data[DOMAIN][entry_id].get_module( + call.data[CONF_ADDRESS] + ).set_memo_text(memo_text.async_render()) hass.services.async_register( DOMAIN, From b0bca85d61185213f5b487a2acda319b14725e64 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Thu, 19 Aug 2021 16:59:16 +0200 Subject: [PATCH 25/35] Make the interface for a service the port string instead of the device selector --- homeassistant/components/velbus/__init__.py | 19 ++--------- homeassistant/components/velbus/services.yaml | 32 +++++++++++-------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index cf73d5179d0da8..4f476edcd94094 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -51,31 +51,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data[DOMAIN][entry.entry_id] = controller await controller.connect() - # add the controller as a device - device_registry = await hass.helpers.device_registry.async_get_registry() - device_registry.async_get_or_create( - name=f"{entry.title}-interface", - config_entry_id=entry.entry_id, - identifiers={(DOMAIN, entry.data[CONF_PORT])}, - connections={(CONF_PORT, entry.data[CONF_PORT])}, - ) - - # setup the platforms hass.config_entries.async_setup_platforms(entry, PLATFORMS) if hass.services.has_service(DOMAIN, "scan"): return True async def get_entry_id(interface: str): - device_registry = dr.async_get(hass) - device_entry = device_registry.async_get(interface) - if device_entry is None: - _LOGGER.warning("Missing device: %s", device_id) - return None for entry in hass.config_entries.async_entries(DOMAIN): - if entry.entry_id in device_entry.config_entries: + if "port" in entry.data and entry.data["port"] == interface: return entry.entry_id - _LOGGER.warning("Can not find the entry: %s", entry.entry_id) + _LOGGER.warning("Can not find the config entry for: %s", interface) return None async def scan(call): diff --git a/homeassistant/components/velbus/services.yaml b/homeassistant/components/velbus/services.yaml index d7b7fe56be4006..83af09409c12aa 100644 --- a/homeassistant/components/velbus/services.yaml +++ b/homeassistant/components/velbus/services.yaml @@ -4,11 +4,12 @@ sync_clock: fields: interface: name: Interface - description: The velbus interface to send the command to + description: The velbus interface to send the command to, this will be the same value as used during configuration required: true + example: "192.168.1.5:27015" + default: '' selector: - device: - integration: velbus + text: scan: name: Scan @@ -16,11 +17,12 @@ scan: fields: interface: name: Interface - description: The velbus interface to send the command to + description: The velbus interface to send the command to, this will be the same value as used during configuration required: true + example: "192.168.1.5:27015" + default: '' selector: - device: - integration: velbus + text: set_memo_text: name: Set memo text @@ -30,18 +32,22 @@ set_memo_text: fields: interface: name: Interface - description: The velbus interface to send the command to + description: The velbus interface to send the command to, this will be the same value as used during configuration required: true + example: "192.168.1.5:27015" + default: '' selector: - device: - integration: velbus + text: address: - name: Module - description: The module to send the memotext to + name: Address + description: > + The module address in decimal format. + The decimal addresses are displayed in front of the modules listed at the integration page. required: true selector: - device: - integration: velbus + number: + min: 1 + max: 254 memo_text: name: Memo text description: > From 0618b0446d2a91f4f6bfca255df22a51f0f0528d Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Thu, 19 Aug 2021 17:08:01 +0200 Subject: [PATCH 26/35] Fix set_memo_text --- homeassistant/components/velbus/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 4f476edcd94094..b82a4195d994ef 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -7,7 +7,6 @@ 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.helpers import device_registry as dr import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -99,10 +98,10 @@ async def syn_clock(call): async def set_memo_text(call): """Handle Memo Text service call.""" - cntrl_id = await get_entry_id(call.data["interface"]) - if not cntrl_id: + entry_id = await get_entry_id(call.data["interface"]) + if not entry_id: return - memo_text = service.data[CONF_MEMO_TEXT] + memo_text = call.data[CONF_MEMO_TEXT] memo_text.hass = hass await hass.data[DOMAIN][entry_id].get_module( call.data[CONF_ADDRESS] From 22d3c84bd8badee3c32eb1d8dbb882083a4d68ce Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Sun, 22 Aug 2021 12:16:08 +0200 Subject: [PATCH 27/35] added an async scan task, more comments --- homeassistant/components/velbus/__init__.py | 84 +++++++++++++------ .../components/velbus/binary_sensor.py | 3 +- homeassistant/components/velbus/climate.py | 3 +- homeassistant/components/velbus/const.py | 3 + homeassistant/components/velbus/cover.py | 3 +- homeassistant/components/velbus/light.py | 3 +- homeassistant/components/velbus/sensor.py | 3 +- homeassistant/components/velbus/switch.py | 3 +- 8 files changed, 74 insertions(+), 31 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index b82a4195d994ef..778183ecd445e7 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -1,16 +1,26 @@ """Support for Velbus devices.""" +from __future__ import annotations + import logging from velbusaio.controller import Velbus import voluptuous as vol +from homeassistant.components import persistent_notification from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PORT from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from .const import CONF_MEMO_TEXT, DOMAIN, SERVICE_SET_MEMO_TEXT +from .const import ( + CONF_INTERFACE, + CONF_MEMO_TEXT, + DOMAIN, + SERVICE_SCAN, + SERVICE_SET_MEMO_TEXT, + SERVICE_SYNC, +) _LOGGER = logging.getLogger(__name__) @@ -19,6 +29,7 @@ ) PLATFORMS = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] +PLATFORMS = ["switch"] async def async_setup(hass, config): @@ -42,68 +53,88 @@ async def async_setup(hass, config): return True +async def velbus_connect_task( + controller: Velbus, hass: HomeAssistant, entry_id: str +) -> None: + """Task to offload the long running connect.""" + # create notification + persistent_notification.async_create( + hass, + "Velbus is scanning the bus, this can take some time.", + "Velbus Starting", + f"{DOMAIN}-{entry_id}", + ) + # connect + await controller.connect() + # clear notification + persistent_notification.async_dismiss(hass, f"{DOMAIN}-{entry_id}") + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Establish connection with velbus.""" hass.data.setdefault(DOMAIN, {}) controller = Velbus(entry.data[CONF_PORT]) - hass.data[DOMAIN][entry.entry_id] = controller - await controller.connect() + hass.data[DOMAIN][entry.entry_id] = {} + hass.data[DOMAIN][entry.entry_id]["cntrl"] = controller + hass.data[DOMAIN][entry.entry_id]["tsk"] = hass.async_create_task( + velbus_connect_task(controller, hass, entry.entry_id) + ) hass.config_entries.async_setup_platforms(entry, PLATFORMS) - if hass.services.has_service(DOMAIN, "scan"): + if hass.services.has_service(DOMAIN, SERVICE_SCAN): return True - async def get_entry_id(interface: str): + def get_entry_id(interface: str) -> str | None: for entry in hass.config_entries.async_entries(DOMAIN): if "port" in entry.data and entry.data["port"] == interface: return entry.entry_id _LOGGER.warning("Can not find the config entry for: %s", interface) return None + def check_entry_id(interface: str): + for entry in hass.config_entries.async_entries(DOMAIN): + if "port" in entry.data and entry.data["port"] == interface: + return interface + raise vol.Invalid( + "The interface provided is not defined as a port in a Velbus integration" + ) + async def scan(call): - entry_id = await get_entry_id(call.data["interface"]) + entry_id = get_entry_id(call.data[CONF_INTERFACE]) if not entry_id: return - await hass.data[DOMAIN][entry_id].scan() + await hass.data[DOMAIN][entry_id]["cntrl"].scan() hass.services.async_register( DOMAIN, - "scan", + SERVICE_SCAN, scan, - vol.Schema( - { - vol.Required("interface"): cv.string, - } - ), + vol.Schema({vol.Required(CONF_INTERFACE): vol.All(check_entry_id, cv.string)}), ) async def syn_clock(call): - entry_id = await get_entry_id(call.data["interface"]) + entry_id = get_entry_id(call.data[CONF_INTERFACE]) if not entry_id: return - await hass.data[DOMAIN][entry_id].sync_clock() + await hass.data[DOMAIN][entry_id]["cntrl"].sync_clock() hass.services.async_register( DOMAIN, - "sync_clock", + SERVICE_SYNC, syn_clock, - vol.Schema( - { - vol.Required("interface"): cv.string, - } - ), + vol.Schema({vol.Required(CONF_INTERFACE): vol.All(check_entry_id, cv.string)}), ) async def set_memo_text(call): """Handle Memo Text service call.""" - entry_id = await get_entry_id(call.data["interface"]) + entry_id = get_entry_id(call.data[CONF_INTERFACE]) if not entry_id: return memo_text = call.data[CONF_MEMO_TEXT] memo_text.hass = hass - await hass.data[DOMAIN][entry_id].get_module( + await hass.data[DOMAIN][entry_id]["cntrl"].get_module( call.data[CONF_ADDRESS] ).set_memo_text(memo_text.async_render()) @@ -113,7 +144,7 @@ async def set_memo_text(call): set_memo_text, vol.Schema( { - vol.Required("interface"): cv.string, + vol.Required(CONF_INTERFACE): vol.All(check_entry_id, cv.string), vol.Required(CONF_ADDRESS): vol.All( vol.Coerce(int), vol.Range(min=0, max=255) ), @@ -128,10 +159,13 @@ async def set_memo_text(call): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): """Remove the velbus connection.""" unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - await hass.data[DOMAIN][entry.entry_id].stop() + await hass.data[DOMAIN][entry.entry_id]["cntrl"].stop() hass.data[DOMAIN].pop(entry.entry_id) if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) + hass.services.async_remove(DOMAIN, SERVICE_SCAN) + hass.services.async_remove(DOMAIN, SERVICE_SYNC) + hass.services.async_remove(DOMAIN, SERVICE_SET_MEMO_TEXT) return unload_ok diff --git a/homeassistant/components/velbus/binary_sensor.py b/homeassistant/components/velbus/binary_sensor.py index 7e6c338a5c1806..25d71f52ca5baf 100644 --- a/homeassistant/components/velbus/binary_sensor.py +++ b/homeassistant/components/velbus/binary_sensor.py @@ -7,7 +7,8 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up Velbus switch based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id] + await hass.data[DOMAIN][entry.entry_id]["tsk"] + cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] entities = [] for channel in cntrl.get_all("binary_sensor"): entities.append(VelbusBinarySensor(channel)) diff --git a/homeassistant/components/velbus/climate.py b/homeassistant/components/velbus/climate.py index 79fbbe62979ee8..68d92bf43d008e 100644 --- a/homeassistant/components/velbus/climate.py +++ b/homeassistant/components/velbus/climate.py @@ -16,7 +16,8 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up Velbus switch based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id] + await hass.data[DOMAIN][entry.entry_id]["tsk"] + cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] entities = [] for channel in cntrl.get_all("climate"): entities.append(VelbusClimate(channel)) diff --git a/homeassistant/components/velbus/const.py b/homeassistant/components/velbus/const.py index d3987295fce308..69c0c9261361d6 100644 --- a/homeassistant/components/velbus/const.py +++ b/homeassistant/components/velbus/const.py @@ -2,6 +2,9 @@ DOMAIN = "velbus" +CONF_INTERFACE = "interface" CONF_MEMO_TEXT = "memo_text" +SERVICE_SCAN = "scan" +SERVICE_SYNC = "sync_clock" SERVICE_SET_MEMO_TEXT = "set_memo_text" diff --git a/homeassistant/components/velbus/cover.py b/homeassistant/components/velbus/cover.py index 73cb4b5b2ddba2..1003d341c93280 100644 --- a/homeassistant/components/velbus/cover.py +++ b/homeassistant/components/velbus/cover.py @@ -18,7 +18,8 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up Velbus switch based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id] + await hass.data[DOMAIN][entry.entry_id]["tsk"] + cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] entities = [] for channel in cntrl.get_all("cover"): entities.append(VelbusCover(channel)) diff --git a/homeassistant/components/velbus/light.py b/homeassistant/components/velbus/light.py index 397b07668338dc..482bdb53e94c1d 100644 --- a/homeassistant/components/velbus/light.py +++ b/homeassistant/components/velbus/light.py @@ -21,7 +21,8 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up Velbus switch based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id] + await hass.data[DOMAIN][entry.entry_id]["tsk"] + cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] entities = [] for channel in cntrl.get_all("light"): entities.append(VelbusLight(channel, False)) diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 32cfc325f07428..32f016b8ce333d 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -18,7 +18,8 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up Velbus switch based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id] + await hass.data[DOMAIN][entry.entry_id]["tsk"] + cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] entities = [] for channel in cntrl.get_all("sensor"): entities.append(VelbusSensor(channel)) diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py index 398f0547f0e5c5..e15f046eb3d7ea 100644 --- a/homeassistant/components/velbus/switch.py +++ b/homeassistant/components/velbus/switch.py @@ -12,7 +12,8 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up Velbus switch based on config_entry.""" - cntrl = hass.data[DOMAIN][entry.entry_id] + await hass.data[DOMAIN][entry.entry_id]["tsk"] + cntrl = hass.data[DOMAIN][entry.entry_id]["cntrl"] entities = [] for channel in cntrl.get_all("switch"): entities.append(VelbusSwitch(channel)) From 61dd343df891d0493747997689409cb58bc786fd Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Sun, 22 Aug 2021 12:58:58 +0200 Subject: [PATCH 28/35] Accidently disabled some paltforms --- homeassistant/components/velbus/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 778183ecd445e7..59d73a53540678 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -29,7 +29,6 @@ ) PLATFORMS = ["switch", "sensor", "binary_sensor", "cover", "climate", "light"] -PLATFORMS = ["switch"] async def async_setup(hass, config): From 1d1cac9b17fde1fe06a1c36139d2080d5f281dca Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Sun, 22 Aug 2021 19:17:02 +0200 Subject: [PATCH 29/35] More comments, bump version --- homeassistant/components/velbus/__init__.py | 17 +++-------------- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 6 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 59d73a53540678..587a0bf4fec02f 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -6,7 +6,6 @@ from velbusaio.controller import Velbus import voluptuous as vol -from homeassistant.components import persistent_notification from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PORT from homeassistant.core import HomeAssistant @@ -56,17 +55,7 @@ async def velbus_connect_task( controller: Velbus, hass: HomeAssistant, entry_id: str ) -> None: """Task to offload the long running connect.""" - # create notification - persistent_notification.async_create( - hass, - "Velbus is scanning the bus, this can take some time.", - "Velbus Starting", - f"{DOMAIN}-{entry_id}", - ) - # connect await controller.connect() - # clear notification - persistent_notification.async_dismiss(hass, f"{DOMAIN}-{entry_id}") async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: @@ -110,7 +99,7 @@ async def scan(call): DOMAIN, SERVICE_SCAN, scan, - vol.Schema({vol.Required(CONF_INTERFACE): vol.All(check_entry_id, cv.string)}), + vol.Schema({vol.Required(CONF_INTERFACE): vol.All(cv.string, check_entry_id)}), ) async def syn_clock(call): @@ -123,7 +112,7 @@ async def syn_clock(call): DOMAIN, SERVICE_SYNC, syn_clock, - vol.Schema({vol.Required(CONF_INTERFACE): vol.All(check_entry_id, cv.string)}), + vol.Schema({vol.Required(CONF_INTERFACE): vol.All(cv.string, check_entry_id)}), ) async def set_memo_text(call): @@ -143,7 +132,7 @@ async def set_memo_text(call): set_memo_text, vol.Schema( { - vol.Required(CONF_INTERFACE): vol.All(check_entry_id, cv.string), + vol.Required(CONF_INTERFACE): vol.All(cv.string, check_entry_id), vol.Required(CONF_ADDRESS): vol.All( vol.Coerce(int), vol.Range(min=0, max=255) ), diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 9c18c7d813280f..e00d7f084a3bfe 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.9"], + "requirements": ["velbus-aio==2021.8.10"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push" diff --git a/requirements_all.txt b/requirements_all.txt index 0e8976fbe651c1..d3ab4469905678 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.9 +velbus-aio==2021.8.10 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index bae128016835e0..e5cde63798c7bf 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.9 +velbus-aio==2021.8.10 # homeassistant.components.venstar venstarcolortouch==0.14 From 8652fff2296aad374232c0d1beeeff55d2ee4e42 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Mon, 23 Aug 2021 13:43:48 +0200 Subject: [PATCH 30/35] Bump version, add extra attributes, enable mypy --- homeassistant/components/velbus/binary_sensor.py | 2 +- homeassistant/components/velbus/manifest.json | 2 +- homeassistant/components/velbus/sensor.py | 10 ++++++++++ homeassistant/components/velbus/switch.py | 11 ++++++++++- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/velbus/binary_sensor.py b/homeassistant/components/velbus/binary_sensor.py index 25d71f52ca5baf..be5d8d246983fb 100644 --- a/homeassistant/components/velbus/binary_sensor.py +++ b/homeassistant/components/velbus/binary_sensor.py @@ -19,6 +19,6 @@ class VelbusBinarySensor(VelbusEntity, BinarySensorEntity): """Representation of a Velbus Binary Sensor.""" @property - def is_on(self): + def is_on(self) -> bool: """Return true if the sensor is on.""" return self._channel.is_closed() diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index e00d7f084a3bfe..8eabd429aa283c 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.10"], + "requirements": ["velbus-aio==2021.8.11"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push" diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 32f016b8ce333d..968c295382920d 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -90,3 +90,13 @@ def state_class(self): if self._is_counter: return STATE_CLASS_TOTAL_INCREASING return STATE_CLASS_MEASUREMENT + + @property + def extra_state_attributes(self): + """Return the state attributes of the sun.""" + if self._channel.is_temperature(): + return { + "Max": self._channel.get_max(), + "Min": self._channel.get_min(), + } + return {} diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py index e15f046eb3d7ea..a62f6e71971d9b 100644 --- a/homeassistant/components/velbus/switch.py +++ b/homeassistant/components/velbus/switch.py @@ -28,10 +28,19 @@ def is_on(self) -> bool: """Return true if the switch is on.""" return self._channel.is_on() + @property + def extra_state_attributes(self) -> dict: + """Return the state attributes of the sun.""" + return { + "Forced on": self._channel.is_forced_on(), + "Inhibit": self._channel.is_inhibit(), + "Disabled": self._channel.is_disabled(), + } + async def async_turn_on(self, **kwargs: Any) -> None: """Instruct the switch to turn on.""" await self._channel.turn_on() - async def async_turn_off(self, **kwargs: any) -> None: + async def async_turn_off(self, **kwargs: Any) -> None: """Instruct the switch to turn off.""" await self._channel.turn_off() diff --git a/requirements_all.txt b/requirements_all.txt index d3ab4469905678..af31ef9cab13d8 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.10 +velbus-aio==2021.8.11 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index e5cde63798c7bf..70097cc355d638 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.10 +velbus-aio==2021.8.11 # homeassistant.components.venstar venstarcolortouch==0.14 From 9ee0f60659f367c132920478c5b07ad188f1919a Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Fri, 3 Sep 2021 14:56:52 +0200 Subject: [PATCH 31/35] Removed new features --- homeassistant/components/velbus/sensor.py | 10 ---------- homeassistant/components/velbus/switch.py | 9 --------- 2 files changed, 19 deletions(-) diff --git a/homeassistant/components/velbus/sensor.py b/homeassistant/components/velbus/sensor.py index 968c295382920d..32f016b8ce333d 100644 --- a/homeassistant/components/velbus/sensor.py +++ b/homeassistant/components/velbus/sensor.py @@ -90,13 +90,3 @@ def state_class(self): if self._is_counter: return STATE_CLASS_TOTAL_INCREASING return STATE_CLASS_MEASUREMENT - - @property - def extra_state_attributes(self): - """Return the state attributes of the sun.""" - if self._channel.is_temperature(): - return { - "Max": self._channel.get_max(), - "Min": self._channel.get_min(), - } - return {} diff --git a/homeassistant/components/velbus/switch.py b/homeassistant/components/velbus/switch.py index a62f6e71971d9b..6b9609cc85776b 100644 --- a/homeassistant/components/velbus/switch.py +++ b/homeassistant/components/velbus/switch.py @@ -28,15 +28,6 @@ def is_on(self) -> bool: """Return true if the switch is on.""" return self._channel.is_on() - @property - def extra_state_attributes(self) -> dict: - """Return the state attributes of the sun.""" - return { - "Forced on": self._channel.is_forced_on(), - "Inhibit": self._channel.is_inhibit(), - "Disabled": self._channel.is_disabled(), - } - async def async_turn_on(self, **kwargs: Any) -> None: """Instruct the switch to turn on.""" await self._channel.turn_on() From 55f8e84aaf091480f202eef4f591047efd95fe9e Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Mon, 6 Sep 2021 08:40:28 +0200 Subject: [PATCH 32/35] More comments --- homeassistant/components/velbus/__init__.py | 24 ++++----------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 587a0bf4fec02f..9cc8bf0699515c 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -74,26 +74,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: if hass.services.has_service(DOMAIN, SERVICE_SCAN): return True - def get_entry_id(interface: str) -> str | None: - for entry in hass.config_entries.async_entries(DOMAIN): - if "port" in entry.data and entry.data["port"] == interface: - return entry.entry_id - _LOGGER.warning("Can not find the config entry for: %s", interface) - return None - def check_entry_id(interface: str): for entry in hass.config_entries.async_entries(DOMAIN): if "port" in entry.data and entry.data["port"] == interface: - return interface + return entry.entry_id raise vol.Invalid( "The interface provided is not defined as a port in a Velbus integration" ) async def scan(call): - entry_id = get_entry_id(call.data[CONF_INTERFACE]) - if not entry_id: - return - await hass.data[DOMAIN][entry_id]["cntrl"].scan() + await hass.data[DOMAIN][call.data[CONF_INTERFACE]]["cntrl"].scan() hass.services.async_register( DOMAIN, @@ -103,10 +93,7 @@ async def scan(call): ) async def syn_clock(call): - entry_id = get_entry_id(call.data[CONF_INTERFACE]) - if not entry_id: - return - await hass.data[DOMAIN][entry_id]["cntrl"].sync_clock() + await hass.data[DOMAIN][call.data[CONF_INTERFACE]]["cntrl"].sync_clock() hass.services.async_register( DOMAIN, @@ -117,12 +104,9 @@ async def syn_clock(call): async def set_memo_text(call): """Handle Memo Text service call.""" - entry_id = get_entry_id(call.data[CONF_INTERFACE]) - if not entry_id: - return memo_text = call.data[CONF_MEMO_TEXT] memo_text.hass = hass - await hass.data[DOMAIN][entry_id]["cntrl"].get_module( + await hass.data[DOMAIN][call.data[CONF_INTERFACE]]["cntrl"].get_module( call.data[CONF_ADDRESS] ).set_memo_text(memo_text.async_render()) From 56d46bdd3611be016c84d4c5930f7130d94ef961 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Tue, 7 Sep 2021 21:43:17 +0200 Subject: [PATCH 33/35] Bump version --- homeassistant/components/velbus/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 8eabd429aa283c..61a297d401b5b5 100644 --- a/homeassistant/components/velbus/manifest.json +++ b/homeassistant/components/velbus/manifest.json @@ -2,7 +2,7 @@ "domain": "velbus", "name": "Velbus", "documentation": "https://www.home-assistant.io/integrations/velbus", - "requirements": ["velbus-aio==2021.8.11"], + "requirements": ["velbus-aio==2021.9.1"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"], "iot_class": "local_push" diff --git a/requirements_all.txt b/requirements_all.txt index af31ef9cab13d8..61b850bdbf8292 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -2354,7 +2354,7 @@ uvcclient==0.11.0 vallox-websocket-api==2.8.1 # homeassistant.components.velbus -velbus-aio==2021.8.11 +velbus-aio==2021.9.1 # homeassistant.components.venstar venstarcolortouch==0.14 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 70097cc355d638..9ddeb5fc549d5c 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1313,7 +1313,7 @@ url-normalize==1.4.1 uvcclient==0.11.0 # homeassistant.components.velbus -velbus-aio==2021.8.11 +velbus-aio==2021.9.1 # homeassistant.components.venstar venstarcolortouch==0.14 From 38efe56ed593ec67ed07294ad6d937f63b198538 Mon Sep 17 00:00:00 2001 From: Maikel Punie Date: Wed, 8 Sep 2021 08:44:44 +0200 Subject: [PATCH 34/35] Update homeassistant/components/velbus/__init__.py Co-authored-by: brefra --- homeassistant/components/velbus/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/velbus/__init__.py b/homeassistant/components/velbus/__init__.py index 9cc8bf0699515c..48dce9ecf97054 100644 --- a/homeassistant/components/velbus/__init__.py +++ b/homeassistant/components/velbus/__init__.py @@ -151,7 +151,7 @@ def __init__(self, channel): @property def unique_id(self): """Get unique ID.""" - if serial := self._channel.get_module_serial(): + if (serial := self._channel.get_module_serial()) == 0: serial = self._channel.get_module_address() return f"{serial}-{self._channel.get_channel_number()}" From 2824390669845b4e101bfe268ed4389b62933ef2 Mon Sep 17 00:00:00 2001 From: cereal2nd Date: Fri, 10 Sep 2021 15:24:29 +0200 Subject: [PATCH 35/35] Readd the import step --- homeassistant/components/velbus/config_flow.py | 10 ++++++++++ tests/components/velbus/test_config_flow.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 8dfd1e2267dcb4..3ec5af14397439 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -77,3 +77,13 @@ async def async_step_user(self, user_input=None): ), errors=self._errors, ) + + async def async_step_import(self, user_input=None): + """Import a config entry.""" + user_input[CONF_NAME] = "Velbus Import" + prt = user_input[CONF_PORT] + if self._prt_in_configuration_exists(prt): + # if the velbus import is already in the config + # we should not proceed the import + return self.async_abort(reason="already_configured") + return await self.async_step_user(user_input) diff --git a/tests/components/velbus/test_config_flow.py b/tests/components/velbus/test_config_flow.py index e028545a57d030..723b6664fd7d61 100644 --- a/tests/components/velbus/test_config_flow.py +++ b/tests/components/velbus/test_config_flow.py @@ -76,6 +76,15 @@ async def test_user_fail(hass, controller_assert): assert result["errors"] == {CONF_PORT: "cannot_connect"} +async def test_import(hass, controller): + """Test import step.""" + flow = init_config_flow(hass) + + result = await flow.async_step_import({CONF_PORT: PORT_TCP}) + assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + assert result["title"] == "velbus_import" + + async def test_abort_if_already_setup(hass): """Test we abort if Daikin is already setup.""" flow = init_config_flow(hass) @@ -83,6 +92,12 @@ async def test_abort_if_already_setup(hass): domain="velbus", data={CONF_PORT: PORT_TCP, CONF_NAME: "velbus home"} ).add_to_hass(hass) + result = await flow.async_step_import( + {CONF_PORT: PORT_TCP, CONF_NAME: "velbus import test"} + ) + assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT + assert result["reason"] == "already_configured" + result = await flow.async_step_user( {CONF_PORT: PORT_TCP, CONF_NAME: "velbus import test"} )