From 89f5ca9ebc7d0ddd5baca4ae27098c9f43085bdc Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Mon, 30 Mar 2020 09:40:07 +0000 Subject: [PATCH 01/20] lib version --- homeassistant/components/dynalite/bridge.py | 2 +- homeassistant/components/dynalite/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/dynalite/bridge.py b/homeassistant/components/dynalite/bridge.py index fa0a91bfab1ca5..6b40658d44fe38 100755 --- a/homeassistant/components/dynalite/bridge.py +++ b/homeassistant/components/dynalite/bridge.py @@ -50,7 +50,7 @@ def update_device(self, device): if device == CONF_ALL: # This is used to signal connection or disconnection, so all devices may become available or not. log_string = ( - "Connected" if self.dynalite_devices.available else "Disconnected" + "Connected" if self.dynalite_devices.connected else "Disconnected" ) LOGGER.info("%s to dynalite host", log_string) async_dispatcher_send(self.hass, self.update_signal()) diff --git a/homeassistant/components/dynalite/manifest.json b/homeassistant/components/dynalite/manifest.json index d6351db17b2439..a6ae0e96c459b6 100755 --- a/homeassistant/components/dynalite/manifest.json +++ b/homeassistant/components/dynalite/manifest.json @@ -5,5 +5,5 @@ "documentation": "https://www.home-assistant.io/integrations/dynalite", "dependencies": [], "codeowners": ["@ziv1234"], - "requirements": ["dynalite_devices==0.1.32"] + "requirements": ["dynalite_devices==0.1.39"] } diff --git a/requirements_all.txt b/requirements_all.txt index 94ce70f6e4e60f..90c5045eab0736 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -474,7 +474,7 @@ dsmr_parser==0.18 dweepy==0.3.0 # homeassistant.components.dynalite -dynalite_devices==0.1.32 +dynalite_devices==0.1.39 # homeassistant.components.rainforest_eagle eagle200_reader==0.2.4 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f8ff29221ace8c..7e2fe8aacd0d3d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -190,7 +190,7 @@ doorbirdpy==2.0.8 dsmr_parser==0.18 # homeassistant.components.dynalite -dynalite_devices==0.1.32 +dynalite_devices==0.1.39 # homeassistant.components.ee_brightbox eebrightbox==0.0.4 From 55a7b4b6bbb6443951cb147c417218f9b0188677 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Mon, 30 Mar 2020 11:11:56 +0000 Subject: [PATCH 02/20] unit-test refactoring --- .../components/dynalite/dynalitebase.py | 3 +- tests/components/dynalite/test_bridge.py | 8 +++ tests/components/dynalite/test_config_flow.py | 62 +++++++++++-------- 3 files changed, 46 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/dynalite/dynalitebase.py b/homeassistant/components/dynalite/dynalitebase.py index 8bb1ab2dc42ab7..a7118636ae9548 100755 --- a/homeassistant/components/dynalite/dynalitebase.py +++ b/homeassistant/components/dynalite/dynalitebase.py @@ -18,8 +18,7 @@ def async_add_entities_platform(devices): # assumes it is called with a single platform added_entities = [] for device in devices: - if device.category == platform: - added_entities.append(entity_from_device(device, bridge)) + added_entities.append(entity_from_device(device, bridge)) if added_entities: async_add_entities(added_entities) diff --git a/tests/components/dynalite/test_bridge.py b/tests/components/dynalite/test_bridge.py index 97759e96b699cf..938bc09f59a528 100755 --- a/tests/components/dynalite/test_bridge.py +++ b/tests/components/dynalite/test_bridge.py @@ -61,8 +61,15 @@ async def test_add_devices_then_register(hass): device2.name = "NAME2" device2.unique_id = "unique2" new_device_func([device1, device2]) + device3 = Mock() + device3.category = "switch" + device3.name = "NAME3" + device3.unique_id = "unique3" + new_device_func([device3]) await hass.async_block_till_done() assert hass.states.get("light.name") + assert hass.states.get("switch.name2") + assert hass.states.get("switch.name3") async def test_register_then_add_devices(hass): @@ -89,3 +96,4 @@ async def test_register_then_add_devices(hass): new_device_func([device1, device2]) await hass.async_block_till_done() assert hass.states.get("light.name") + assert hass.states.get("switch.name2") diff --git a/tests/components/dynalite/test_config_flow.py b/tests/components/dynalite/test_config_flow.py index 96e361e260f8ca..03196f050b890d 100755 --- a/tests/components/dynalite/test_config_flow.py +++ b/tests/components/dynalite/test_config_flow.py @@ -1,6 +1,7 @@ """Test Dynalite config flow.""" from asynctest import CoroutineMock, patch +import pytest from homeassistant import config_entries from homeassistant.components import dynalite @@ -8,12 +9,20 @@ from tests.common import MockConfigEntry -async def run_flow(hass, connection): +@pytest.mark.parametrize( + "first_con, second_con,exp_type, exp_result, exp_reason", + [ + (True, True, "create_entry", "loaded", ""), + (False, False, "abort", "", "no_connection"), + (True, False, "create_entry", "setup_retry", ""), + ], +) +async def test_flow(hass, first_con, second_con, exp_type, exp_result, exp_reason): """Run a flow with or without errors and return result.""" host = "1.2.3.4" with patch( "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", - side_effect=connection, + side_effect=[first_con, second_con], ): result = await hass.config_entries.flow.async_init( dynalite.DOMAIN, @@ -21,35 +30,18 @@ async def run_flow(hass, connection): data={dynalite.CONF_HOST: host}, ) await hass.async_block_till_done() - return result - - -async def test_flow_works(hass): - """Test a successful config flow.""" - result = await run_flow(hass, [True, True]) - assert result["type"] == "create_entry" - assert result["result"].state == "loaded" - - -async def test_flow_setup_fails(hass): - """Test a flow where async_setup fails.""" - result = await run_flow(hass, [False]) - assert result["type"] == "abort" - assert result["reason"] == "no_connection" - - -async def test_flow_setup_fails_in_setup_entry(hass): - """Test a flow where the initial check works but inside setup_entry, the bridge setup fails.""" - result = await run_flow(hass, [True, False]) - assert result["type"] == "create_entry" - assert result["result"].state == "setup_retry" + assert result["type"] == exp_type + if exp_result: + assert result["result"].state == exp_result + if exp_reason: + assert result["reason"] == exp_reason async def test_existing(hass): """Test when the entry exists with the same config.""" host = "1.2.3.4" MockConfigEntry( - domain=dynalite.DOMAIN, unique_id=host, data={dynalite.CONF_HOST: host} + domain=dynalite.DOMAIN, data={dynalite.CONF_HOST: host} ).add_to_hass(hass) with patch( "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", @@ -92,3 +84,23 @@ async def test_existing_update(hass): assert mock_dyn_dev().configure.mock_calls[1][1][0][dynalite.CONF_PORT] == port2 assert result["type"] == "abort" assert result["reason"] == "already_configured" + + +async def test_two_entries(hass): + """Test when two different entries exist with different hosts.""" + host1 = "1.2.3.4" + host2 = "5.6.7.8" + MockConfigEntry( + domain=dynalite.DOMAIN, data={dynalite.CONF_HOST: host1} + ).add_to_hass(hass) + with patch( + "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", + return_value=True, + ): + result = await hass.config_entries.flow.async_init( + dynalite.DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data={dynalite.CONF_HOST: host2}, + ) + assert result["type"] == "create_entry" + assert result["result"].state == "loaded" From e6503dd9e1f32aef75e50a536b934c4ba0a65a2c Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Mon, 30 Mar 2020 11:23:44 +0000 Subject: [PATCH 03/20] added type hints --- homeassistant/components/dynalite/__init__.py | 17 ++++++----- homeassistant/components/dynalite/bridge.py | 21 ++++++++------ .../components/dynalite/config_flow.py | 6 ++-- homeassistant/components/dynalite/const.py | 2 +- .../components/dynalite/dynalitebase.py | 28 ++++++++++++------- homeassistant/components/dynalite/light.py | 18 ++++++++---- homeassistant/components/dynalite/switch.py | 14 +++++++--- 7 files changed, 68 insertions(+), 38 deletions(-) diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index 973d09a384f137..43c1e0fbd448f3 100755 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -1,11 +1,14 @@ """Support for the Dynalite networks.""" import asyncio +from typing import Any, Dict, Union import voluptuous as vol from homeassistant import config_entries +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST +from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv @@ -25,7 +28,7 @@ CONF_FADE, CONF_NAME, CONF_NO_DEFAULT, - CONF_POLLTIMER, + CONF_POLL_TIMER, CONF_PORT, CONF_PRESET, DEFAULT_CHANNEL_TYPE, @@ -37,7 +40,7 @@ ) -def num_string(value): +def num_string(value: Union[int, str]) -> str: """Test if value is a string of digits, aka an integer.""" new_value = str(value) if new_value.isdigit(): @@ -85,7 +88,7 @@ def num_string(value): vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_PORT, default=DEFAULT_PORT): int, vol.Optional(CONF_AUTO_DISCOVER, default=False): vol.Coerce(bool), - vol.Optional(CONF_POLLTIMER, default=1.0): vol.Coerce(float), + vol.Optional(CONF_POLL_TIMER, default=1.0): vol.Coerce(float), vol.Optional(CONF_AREA): AREA_SCHEMA, vol.Optional(CONF_DEFAULT): PLATFORM_DEFAULTS_SCHEMA, vol.Optional(CONF_ACTIVE, default=False): vol.Any( @@ -105,7 +108,7 @@ def num_string(value): ) -async def async_setup(hass, config): +async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool: """Set up the Dynalite platform.""" conf = config.get(DOMAIN) @@ -137,7 +140,7 @@ async def async_setup(hass, config): return True -async def async_entry_changed(hass, entry): +async def async_entry_changed(hass: HomeAssistant, entry: ConfigEntry) -> None: """Reload entry since the data has changed.""" LOGGER.debug("Reconfiguring entry %s", entry.data) bridge = hass.data[DOMAIN][entry.entry_id] @@ -145,7 +148,7 @@ async def async_entry_changed(hass, entry): LOGGER.debug("Reconfiguring entry finished %s", entry.data) -async def async_setup_entry(hass, entry): +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up a bridge from a config entry.""" LOGGER.debug("Setting up entry %s", entry.data) bridge = DynaliteBridge(hass, entry.data) @@ -163,7 +166,7 @@ async def async_setup_entry(hass, entry): return True -async def async_unload_entry(hass, entry): +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" LOGGER.debug("Unloading entry %s", entry.data) hass.data[DOMAIN].pop(entry.entry_id) diff --git a/homeassistant/components/dynalite/bridge.py b/homeassistant/components/dynalite/bridge.py index 6b40658d44fe38..a627805997bbe2 100755 --- a/homeassistant/components/dynalite/bridge.py +++ b/homeassistant/components/dynalite/bridge.py @@ -1,17 +1,22 @@ """Code to handle a Dynalite bridge.""" +from typing import TYPE_CHECKING, Any, Callable, Dict, List + from dynalite_devices_lib.dynalite_devices import DynaliteDevices -from homeassistant.core import callback +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_send from .const import CONF_ALL, CONF_HOST, ENTITY_PLATFORMS, LOGGER +if TYPE_CHECKING: # pragma: no cover + from .dynalitebase import DynaliteBase + class DynaliteBridge: """Manages a single Dynalite bridge.""" - def __init__(self, hass, config): + def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: """Initialize the system based on host parameter.""" self.hass = hass self.area = {} @@ -25,18 +30,18 @@ def __init__(self, hass, config): ) self.dynalite_devices.configure(config) - async def async_setup(self): + async def async_setup(self) -> bool: """Set up a Dynalite bridge.""" # Configure the dynalite devices LOGGER.debug("Setting up bridge - host %s", self.host) return await self.dynalite_devices.async_setup() - def reload_config(self, config): + def reload_config(self, config: Dict[str, Any]) -> None: """Reconfigure a bridge when config changes.""" LOGGER.debug("Reloading bridge - host %s, config %s", self.host, config) self.dynalite_devices.configure(config) - def update_signal(self, device=None): + def update_signal(self, device: "DynaliteBase" = None) -> str: """Create signal to use to trigger entity update.""" if device: signal = f"dynalite-update-{self.host}-{device.unique_id}" @@ -45,7 +50,7 @@ def update_signal(self, device=None): return signal @callback - def update_device(self, device): + def update_device(self, device: "DynaliteBase") -> None: """Call when a device or all devices should be updated.""" if device == CONF_ALL: # This is used to signal connection or disconnection, so all devices may become available or not. @@ -58,13 +63,13 @@ def update_device(self, device): async_dispatcher_send(self.hass, self.update_signal(device)) @callback - def register_add_devices(self, platform, async_add_devices): + def register_add_devices(self, platform: str, async_add_devices: Callable) -> None: """Add an async_add_entities for a category.""" self.async_add_devices[platform] = async_add_devices if platform in self.waiting_devices: self.async_add_devices[platform](self.waiting_devices[platform]) - def add_devices_when_registered(self, devices): + def add_devices_when_registered(self, devices: List["DynaliteBase"]) -> None: """Add the devices to HA if the add devices callback was registered, otherwise queue until it is.""" for platform in ENTITY_PLATFORMS: platform_devices = [ diff --git a/homeassistant/components/dynalite/config_flow.py b/homeassistant/components/dynalite/config_flow.py index ca95c0754a6f70..4c5b2ceb7d88a4 100755 --- a/homeassistant/components/dynalite/config_flow.py +++ b/homeassistant/components/dynalite/config_flow.py @@ -1,4 +1,6 @@ """Config flow to configure Dynalite hub.""" +from typing import Any, Dict + from homeassistant import config_entries from homeassistant.const import CONF_HOST @@ -12,11 +14,11 @@ class DynaliteFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL - def __init__(self): + def __init__(self) -> None: """Initialize the Dynalite flow.""" self.host = None - async def async_step_import(self, import_info): + async def async_step_import(self, import_info: Dict[str, Any]) -> Any: """Import a new bridge as a config entry.""" LOGGER.debug("Starting async_step_import - %s", import_info) host = import_info[CONF_HOST] diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index 267b5727b83dec..32fdf2aa7b6be5 100755 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -22,7 +22,7 @@ CONF_HOST = "host" CONF_NAME = "name" CONF_NO_DEFAULT = "nodefault" -CONF_POLLTIMER = "polltimer" +CONF_POLL_TIMER = "polltimer" CONF_PORT = "port" CONF_PRESET = "preset" diff --git a/homeassistant/components/dynalite/dynalitebase.py b/homeassistant/components/dynalite/dynalitebase.py index a7118636ae9548..31879c5c118f6b 100755 --- a/homeassistant/components/dynalite/dynalitebase.py +++ b/homeassistant/components/dynalite/dynalitebase.py @@ -1,5 +1,9 @@ """Support for the Dynalite devices as entities.""" -from homeassistant.core import callback +from typing import Any, Callable, Dict + +from homeassistant.components.dynalite.bridge import DynaliteBridge +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity @@ -7,8 +11,12 @@ def async_setup_entry_base( - hass, config_entry, async_add_entities, platform, entity_from_device -): + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable, + platform: str, + entity_from_device: Callable, +) -> None: """Record the async_add_entities function to add them later when received from Dynalite.""" LOGGER.debug("Setting up %s entry = %s", platform, config_entry.data) bridge = hass.data[DOMAIN][config_entry.entry_id] @@ -28,29 +36,29 @@ def async_add_entities_platform(devices): class DynaliteBase(Entity): """Base class for the Dynalite entities.""" - def __init__(self, device, bridge): + def __init__(self, device: Any, bridge: DynaliteBridge) -> None: """Initialize the base class.""" self._device = device self._bridge = bridge self._unsub_dispatchers = [] @property - def name(self): + def name(self) -> str: """Return the name of the entity.""" return self._device.name @property - def unique_id(self): + def unique_id(self) -> str: """Return the unique ID of the entity.""" return self._device.unique_id @property - def available(self): + def available(self) -> bool: """Return if entity is available.""" return self._device.available @property - def device_info(self): + def device_info(self) -> Dict[str, Any]: """Device info for this entity.""" return { "identifiers": {(DOMAIN, self._device.unique_id)}, @@ -58,7 +66,7 @@ def device_info(self): "manufacturer": "Dynalite", } - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Added to hass so need to register to dispatch.""" # register for device specific update self._unsub_dispatchers.append( @@ -77,7 +85,7 @@ async def async_added_to_hass(self): ) ) - async def async_will_remove_from_hass(self): + async def async_will_remove_from_hass(self) -> None: """Unregister signal dispatch listeners when being removed.""" for unsub in self._unsub_dispatchers: unsub() diff --git a/homeassistant/components/dynalite/light.py b/homeassistant/components/dynalite/light.py index a5b7139803c174..283b1ee2286191 100755 --- a/homeassistant/components/dynalite/light.py +++ b/homeassistant/components/dynalite/light.py @@ -1,10 +1,16 @@ """Support for Dynalite channels as lights.""" +from typing import Callable + from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from .dynalitebase import DynaliteBase, async_setup_entry_base -async def async_setup_entry(hass, config_entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable +) -> None: """Record the async_add_entities function to add them later when received from Dynalite.""" async_setup_entry_base( @@ -16,24 +22,24 @@ class DynaliteLight(DynaliteBase, Light): """Representation of a Dynalite Channel as a Home Assistant Light.""" @property - def brightness(self): + def brightness(self) -> int: """Return the brightness of this light between 0..255.""" return self._device.brightness @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self._device.is_on - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs) -> None: """Turn the light on.""" await self._device.async_turn_on(**kwargs) - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs) -> None: """Turn the light off.""" await self._device.async_turn_off(**kwargs) @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" return SUPPORT_BRIGHTNESS diff --git a/homeassistant/components/dynalite/switch.py b/homeassistant/components/dynalite/switch.py index 84be74cee36e6c..45e24d8193ab1f 100755 --- a/homeassistant/components/dynalite/switch.py +++ b/homeassistant/components/dynalite/switch.py @@ -1,10 +1,16 @@ """Support for the Dynalite channels and presets as switches.""" +from typing import Callable + from homeassistant.components.switch import SwitchDevice +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant from .dynalitebase import DynaliteBase, async_setup_entry_base -async def async_setup_entry(hass, config_entry, async_add_entities): +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable +) -> None: """Record the async_add_entities function to add them later when received from Dynalite.""" async_setup_entry_base( @@ -16,14 +22,14 @@ class DynaliteSwitch(DynaliteBase, SwitchDevice): """Representation of a Dynalite Channel as a Home Assistant Switch.""" @property - def is_on(self): + def is_on(self) -> bool: """Return true if switch is on.""" return self._device.is_on - async def async_turn_on(self, **kwargs): + async def async_turn_on(self, **kwargs) -> None: """Turn the switch on.""" await self._device.async_turn_on() - async def async_turn_off(self, **kwargs): + async def async_turn_off(self, **kwargs) -> None: """Turn the switch off.""" await self._device.async_turn_off() From 2dcbc131ffaee203f4204cf696441b014235b5ef Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Mon, 30 Mar 2020 11:36:16 +0000 Subject: [PATCH 04/20] added cover --- homeassistant/components/dynalite/__init__.py | 83 ++++++++++++++-- homeassistant/components/dynalite/const.py | 29 +++++- homeassistant/components/dynalite/cover.py | 94 ++++++++++++++++++ tests/components/dynalite/common.py | 1 + tests/components/dynalite/test_cover.py | 98 +++++++++++++++++++ tests/components/dynalite/test_init.py | 31 ++++++ 6 files changed, 328 insertions(+), 8 deletions(-) create mode 100644 homeassistant/components/dynalite/cover.py create mode 100644 tests/components/dynalite/test_cover.py diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index 43c1e0fbd448f3..d1122db05a9b04 100755 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -23,17 +23,29 @@ CONF_AUTO_DISCOVER, CONF_BRIDGES, CONF_CHANNEL, + CONF_CHANNEL_COVER, CONF_CHANNEL_TYPE, + CONF_CLOSE_PRESET, CONF_DEFAULT, + CONF_DEVICE_CLASS, + CONF_DURATION, CONF_FADE, CONF_NAME, CONF_NO_DEFAULT, + CONF_OPEN_PRESET, CONF_POLL_TIMER, CONF_PORT, CONF_PRESET, + CONF_ROOM_OFF, + CONF_ROOM_ON, + CONF_STOP_PRESET, + CONF_TEMPLATE, + CONF_TILT_TIME, + CONF_TRIGGER, DEFAULT_CHANNEL_TYPE, DEFAULT_NAME, DEFAULT_PORT, + DEFAULT_TEMPLATES, DOMAIN, ENTITY_PLATFORMS, LOGGER, @@ -66,15 +78,71 @@ def num_string(value: Union[int, str]) -> str: PRESET_SCHEMA = vol.Schema({num_string: vol.Any(PRESET_DATA_SCHEMA, None)}) +TEMPLATE_ROOM_SCHEMA = vol.Schema( + {vol.Optional(CONF_ROOM_ON): num_string, vol.Optional(CONF_ROOM_OFF): num_string} +) -AREA_DATA_SCHEMA = vol.Schema( +TEMPLATE_TRIGGER_SCHEMA = vol.Schema({vol.Optional(CONF_TRIGGER): num_string}) + +TEMPLATE_TIMECOVER_SCHEMA = vol.Schema( { - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_FADE): vol.Coerce(float), - vol.Optional(CONF_NO_DEFAULT): vol.Coerce(bool), - vol.Optional(CONF_CHANNEL): CHANNEL_SCHEMA, - vol.Optional(CONF_PRESET): PRESET_SCHEMA, - }, + vol.Optional(CONF_CHANNEL_COVER): num_string, + vol.Optional(CONF_DEVICE_CLASS): cv.string, + vol.Optional(CONF_OPEN_PRESET): num_string, + vol.Optional(CONF_CLOSE_PRESET): num_string, + vol.Optional(CONF_STOP_PRESET): num_string, + vol.Optional(CONF_DURATION): vol.Coerce(float), + vol.Optional(CONF_TILT_TIME): vol.Coerce(float), + } +) + +TEMPLATE_DATA_SCHEMA = vol.Any( + TEMPLATE_ROOM_SCHEMA, TEMPLATE_TRIGGER_SCHEMA, TEMPLATE_TIMECOVER_SCHEMA +) + +TEMPLATE_SCHEMA = vol.Schema({str: TEMPLATE_DATA_SCHEMA}) + + +def validate_area(config: Dict[str, Any]) -> Dict[str, Any]: + """Validate that template parameters are only used if area is using the relevant template.""" + conf_set = set() + for template in DEFAULT_TEMPLATES: + for conf in DEFAULT_TEMPLATES[template]: + conf_set.add(conf) + if config.get(CONF_TEMPLATE): + for conf in DEFAULT_TEMPLATES[config[CONF_TEMPLATE]]: + conf_set.remove(conf) + for conf in conf_set: + if config.get(conf): + raise vol.Invalid( + f"{conf} cannot should not be part of area {config[CONF_NAME]} config" + ) + return config + + +AREA_DATA_SCHEMA = vol.Schema( + vol.All( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_TEMPLATE): cv.string, + vol.Optional(CONF_FADE): vol.Coerce(float), + vol.Optional(CONF_NO_DEFAULT): cv.boolean, + vol.Optional(CONF_CHANNEL): CHANNEL_SCHEMA, + vol.Optional(CONF_PRESET): PRESET_SCHEMA, + # the next ones can be part of the templates + vol.Optional(CONF_ROOM_ON): num_string, + vol.Optional(CONF_ROOM_OFF): num_string, + vol.Optional(CONF_TRIGGER): num_string, + vol.Optional(CONF_CHANNEL_COVER): num_string, + vol.Optional(CONF_DEVICE_CLASS): cv.string, + vol.Optional(CONF_OPEN_PRESET): num_string, + vol.Optional(CONF_CLOSE_PRESET): num_string, + vol.Optional(CONF_STOP_PRESET): num_string, + vol.Optional(CONF_DURATION): vol.Coerce(float), + vol.Optional(CONF_TILT_TIME): vol.Coerce(float), + }, + validate_area, + ) ) AREA_SCHEMA = vol.Schema({num_string: vol.Any(AREA_DATA_SCHEMA, None)}) @@ -95,6 +163,7 @@ def num_string(value: Union[int, str]) -> str: CONF_ACTIVE_ON, CONF_ACTIVE_OFF, CONF_ACTIVE_INIT, cv.boolean ), vol.Optional(CONF_PRESET): PRESET_SCHEMA, + vol.Optional(CONF_TEMPLATE): TEMPLATE_SCHEMA, } ) diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index 32fdf2aa7b6be5..654e1649b0f718 100755 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -4,7 +4,7 @@ LOGGER = logging.getLogger(__package__) DOMAIN = "dynalite" -ENTITY_PLATFORMS = ["light", "switch"] +ENTITY_PLATFORMS = ["light", "switch", "cover"] CONF_ACTIVE = "active" @@ -16,16 +16,43 @@ CONF_AUTO_DISCOVER = "autodiscover" CONF_BRIDGES = "bridges" CONF_CHANNEL = "channel" +CONF_CHANNEL_COVER = "channelcover" CONF_CHANNEL_TYPE = "type" +CONF_CLOSE_PRESET = "close" CONF_DEFAULT = "default" +CONF_DEVICE_CLASS = "class" +CONF_DURATION = "duration" CONF_FADE = "fade" CONF_HOST = "host" CONF_NAME = "name" CONF_NO_DEFAULT = "nodefault" +CONF_OPEN_PRESET = "open" CONF_POLL_TIMER = "polltimer" CONF_PORT = "port" CONF_PRESET = "preset" +CONF_ROOM = "room" +CONF_ROOM_OFF = "room_off" +CONF_ROOM_ON = "room_on" +CONF_STOP_PRESET = "stop" +CONF_TEMPLATE = "template" +CONF_TILT_TIME = "tilt" +CONF_TIME_COVER = "timecover" +CONF_TRIGGER = "trigger" DEFAULT_CHANNEL_TYPE = "light" +DEFAULT_COVER_CLASS = "shutter" DEFAULT_NAME = "dynalite" DEFAULT_PORT = 12345 +DEFAULT_TEMPLATES = { + CONF_ROOM: [CONF_ROOM_ON, CONF_ROOM_OFF], + CONF_TRIGGER: [CONF_TRIGGER], + CONF_TIME_COVER: [ + CONF_CHANNEL_COVER, + CONF_DEVICE_CLASS, + CONF_OPEN_PRESET, + CONF_CLOSE_PRESET, + CONF_STOP_PRESET, + CONF_DURATION, + CONF_TILT_TIME, + ], +} diff --git a/homeassistant/components/dynalite/cover.py b/homeassistant/components/dynalite/cover.py new file mode 100644 index 00000000000000..16d6c4c1a5f5bb --- /dev/null +++ b/homeassistant/components/dynalite/cover.py @@ -0,0 +1,94 @@ +"""Support for the Dynalite channels as covers.""" +from typing import Callable + +from homeassistant.components.cover import CoverDevice +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback + +from .dynalitebase import DynaliteBase, async_setup_entry_base + + +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities: Callable +) -> None: + """Record the async_add_entities function to add them later when received from Dynalite.""" + + @callback + def cover_from_device(device, bridge): + if device.has_tilt: + return DynaliteCoverWithTilt(device, bridge) + return DynaliteCover(device, bridge) + + async_setup_entry_base( + hass, config_entry, async_add_entities, "cover", cover_from_device + ) + + +class DynaliteCover(DynaliteBase, CoverDevice): + """Representation of a Dynalite Channel as a Home Assistant Cover.""" + + @property + def device_class(self) -> str: + """Return the class of the device.""" + return self._device.device_class + + @property + def current_cover_position(self) -> int: + """Return the position of the cover from 0 to 100.""" + return self._device.current_cover_position + + @property + def is_opening(self) -> bool: + """Return true if cover is opening.""" + return self._device.is_opening + + @property + def is_closing(self) -> bool: + """Return true if cover is closing.""" + return self._device.is_closing + + @property + def is_closed(self) -> bool: + """Return true if cover is closed.""" + return self._device.is_closed + + async def async_open_cover(self, **kwargs) -> None: + """Open the cover.""" + await self._device.async_open_cover(**kwargs) + + async def async_close_cover(self, **kwargs) -> None: + """Close the cover.""" + await self._device.async_close_cover(**kwargs) + + async def async_set_cover_position(self, **kwargs) -> None: + """Set the cover position.""" + await self._device.async_set_cover_position(**kwargs) + + async def async_stop_cover(self, **kwargs) -> None: + """Stop the cover.""" + await self._device.async_stop_cover(**kwargs) + + +class DynaliteCoverWithTilt(DynaliteCover): + """Representation of a Dynalite Channel as a Home Assistant Cover that uses up and down for tilt.""" + + @property + def current_cover_tilt_position(self) -> int: + """Return the current tilt position.""" + return self._device.current_cover_tilt_position + + async def async_open_cover_tilt(self, **kwargs) -> None: + """Open cover tilt.""" + await self._device.async_open_cover_tilt(**kwargs) + + async def async_close_cover_tilt(self, **kwargs) -> None: + """Close cover tilt.""" + await self._device.async_close_cover_tilt(**kwargs) + + async def async_set_cover_tilt_position(self, **kwargs) -> None: + """Set the cover tilt position.""" + await self._device.async_set_cover_tilt_position(**kwargs) + + async def async_stop_cover_tilt(self, **kwargs) -> None: + """Stop the cover tilt.""" + await self._device.async_stop_cover_tilt(**kwargs) diff --git a/tests/components/dynalite/common.py b/tests/components/dynalite/common.py index 56554efaa071fa..b90e61204441ea 100755 --- a/tests/components/dynalite/common.py +++ b/tests/components/dynalite/common.py @@ -44,6 +44,7 @@ async def create_entity_from_device(hass, device): new_device_func = mock_dyn_dev.mock_calls[1][2]["new_device_func"] new_device_func([device]) await hass.async_block_till_done() + return mock_dyn_dev.mock_calls[1][2]["update_device_func"] async def run_service_tests(hass, device, platform, services): diff --git a/tests/components/dynalite/test_cover.py b/tests/components/dynalite/test_cover.py new file mode 100644 index 00000000000000..b0c3eea3acfc65 --- /dev/null +++ b/tests/components/dynalite/test_cover.py @@ -0,0 +1,98 @@ +"""Test Dynalite cover.""" +from dynalite_devices_lib.cover import DynaliteTimeCoverWithTiltDevice +import pytest + +from .common import ( + ATTR_ARGS, + ATTR_METHOD, + ATTR_SERVICE, + create_entity_from_device, + create_mock_device, + run_service_tests, +) + + +@pytest.fixture +def mock_device(): + """Mock a Dynalite device.""" + return create_mock_device("cover", DynaliteTimeCoverWithTiltDevice) + + +async def test_cover_setup(hass, mock_device): + """Test a successful setup.""" + await create_entity_from_device(hass, mock_device) + entity_state = hass.states.get("cover.name") + assert entity_state.attributes["friendly_name"] == mock_device.name + assert ( + entity_state.attributes["current_position"] + == mock_device.current_cover_position + ) + assert ( + entity_state.attributes["current_tilt_position"] + == mock_device.current_cover_tilt_position + ) + assert entity_state.attributes["device_class"] == mock_device.device_class + await run_service_tests( + hass, + mock_device, + "cover", + [ + {ATTR_SERVICE: "open_cover", ATTR_METHOD: "async_open_cover"}, + {ATTR_SERVICE: "close_cover", ATTR_METHOD: "async_close_cover"}, + {ATTR_SERVICE: "stop_cover", ATTR_METHOD: "async_stop_cover"}, + { + ATTR_SERVICE: "set_cover_position", + ATTR_METHOD: "async_set_cover_position", + ATTR_ARGS: {"position": 50}, + }, + {ATTR_SERVICE: "open_cover_tilt", ATTR_METHOD: "async_open_cover_tilt"}, + {ATTR_SERVICE: "close_cover_tilt", ATTR_METHOD: "async_close_cover_tilt"}, + {ATTR_SERVICE: "stop_cover_tilt", ATTR_METHOD: "async_stop_cover_tilt"}, + { + ATTR_SERVICE: "set_cover_tilt_position", + ATTR_METHOD: "async_set_cover_tilt_position", + ATTR_ARGS: {"tilt_position": 50}, + }, + ], + ) + + +async def test_cover_without_tilt(hass, mock_device): + """Test a cover with no tilt.""" + mock_device.has_tilt = False + await create_entity_from_device(hass, mock_device) + await hass.services.async_call( + "cover", "open_cover_tilt", {"entity_id": "cover.name"}, blocking=True + ) + await hass.async_block_till_done() + mock_device.async_open_cover_tilt.assert_not_called() + + +async def check_cover_position( + hass, update_func, device, closing, opening, closed, expected +): + """Check that a given position behaves correctly.""" + device.is_closing = closing + device.is_opening = opening + device.is_closed = closed + update_func(device) + await hass.async_block_till_done() + entity_state = hass.states.get("cover.name") + assert entity_state.state == expected + + +async def test_cover_positions(hass, mock_device): + """Test that the state updates in the various positions.""" + update_func = await create_entity_from_device(hass, mock_device) + await check_cover_position( + hass, update_func, mock_device, True, False, False, "closing" + ) + await check_cover_position( + hass, update_func, mock_device, False, True, False, "opening" + ) + await check_cover_position( + hass, update_func, mock_device, False, False, True, "closed" + ) + await check_cover_position( + hass, update_func, mock_device, False, False, False, "open" + ) diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index b74fcd64da029a..168506d8a60be7 100755 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -41,6 +41,37 @@ async def test_async_setup(hass): assert len(hass.config_entries.async_entries(dynalite.DOMAIN)) == 1 +async def test_async_setup_bad_config1(hass): + """Test a successful with bad config on templates.""" + host = "1.2.3.4" + with patch( + "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", + return_value=True, + ): + assert not await async_setup_component( + hass, + dynalite.DOMAIN, + { + dynalite.DOMAIN: { + dynalite.CONF_BRIDGES: [ + { + dynalite.CONF_HOST: host, + dynalite.CONF_AREA: { + "1": { + dynalite.CONF_TEMPLATE: dynalite.const.CONF_TIME_COVER, + dynalite.CONF_NAME: "Name", + dynalite.CONF_ROOM_ON: 7, + } + }, + } + ] + } + }, + ) + await hass.async_block_till_done() + assert dynalite.DOMAIN not in hass.data + + async def test_async_setup_bad_config2(hass): """Test a successful with bad config on numbers.""" host = "1.2.3.4" From 18b9d9f31fde83fed4f77aca4accb64c1c7306c1 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Mon, 30 Mar 2020 11:38:44 +0000 Subject: [PATCH 05/20] added test to see that consts have the same value as library consts --- tests/components/dynalite/test_const.py | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/components/dynalite/test_const.py diff --git a/tests/components/dynalite/test_const.py b/tests/components/dynalite/test_const.py new file mode 100644 index 00000000000000..f03bfcbc8973c3 --- /dev/null +++ b/tests/components/dynalite/test_const.py @@ -0,0 +1,44 @@ +"""Check that the consts in the component / HA are the same as in the library.""" +from dynalite_devices_lib import const as dyn_const + +from homeassistant.components import dynalite +import homeassistant.const as ha_const + + +def test_consts(): + """Verify that the consts defined in HA and the component are the same as the ones in the library.""" + assert ha_const.CONF_HOST == dyn_const.CONF_HOST + assert dynalite.CONF_ACTIVE == dyn_const.CONF_ACTIVE + assert dynalite.CONF_ACTIVE_INIT == dyn_const.CONF_ACTIVE_INIT + assert dynalite.CONF_ACTIVE_OFF == dyn_const.CONF_ACTIVE_OFF + assert dynalite.CONF_ACTIVE_ON == dyn_const.CONF_ACTIVE_ON + assert dynalite.CONF_AREA == dyn_const.CONF_AREA + assert dynalite.CONF_AUTO_DISCOVER == dyn_const.CONF_AUTO_DISCOVER + assert dynalite.CONF_CHANNEL == dyn_const.CONF_CHANNEL + assert dynalite.CONF_CHANNEL_COVER == dyn_const.CONF_CHANNEL_COVER + assert dynalite.CONF_CHANNEL_TYPE == dyn_const.CONF_CHANNEL_TYPE + assert dynalite.CONF_CLOSE_PRESET == dyn_const.CONF_CLOSE_PRESET + assert dynalite.CONF_DEFAULT == dyn_const.CONF_DEFAULT + assert dynalite.CONF_DEVICE_CLASS == dyn_const.CONF_DEVICE_CLASS + assert dynalite.CONF_DURATION == dyn_const.CONF_DURATION + assert dynalite.CONF_FADE == dyn_const.CONF_FADE + assert dynalite.CONF_NAME == dyn_const.CONF_NAME + assert dynalite.CONF_NO_DEFAULT == dyn_const.CONF_NO_DEFAULT + assert dynalite.CONF_OPEN_PRESET == dyn_const.CONF_OPEN_PRESET + assert dynalite.CONF_POLL_TIMER == dyn_const.CONF_POLL_TIMER + assert dynalite.CONF_PORT == dyn_const.CONF_PORT + assert dynalite.CONF_PRESET == dyn_const.CONF_PRESET + assert dynalite.CONF_ROOM_OFF == dyn_const.CONF_ROOM_OFF + assert dynalite.CONF_ROOM_ON == dyn_const.CONF_ROOM_ON + assert dynalite.CONF_STOP_PRESET == dyn_const.CONF_STOP_PRESET + assert dynalite.CONF_TEMPLATE == dyn_const.CONF_TEMPLATE + assert dynalite.CONF_TILT_TIME == dyn_const.CONF_TILT_TIME + assert dynalite.CONF_TRIGGER == dyn_const.CONF_TRIGGER + assert dynalite.DEFAULT_CHANNEL_TYPE == dyn_const.DEFAULT_CHANNEL_TYPE + assert dynalite.DEFAULT_NAME == dyn_const.DEFAULT_NAME + assert dynalite.DEFAULT_PORT == dyn_const.DEFAULT_PORT + # check that all the templates have the same parameters + for template in dynalite.DEFAULT_TEMPLATES: + assert dynalite.DEFAULT_TEMPLATES[template] == list( + dyn_const.DEFAULT_TEMPLATES[template] + ) From 5350ce82ac9eee59b2c215e2f171dfb93381d00a Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Wed, 1 Apr 2020 21:30:55 +0300 Subject: [PATCH 06/20] Update tests/components/dynalite/test_init.py Co-Authored-By: Martin Hjelmare --- tests/components/dynalite/test_init.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index 168506d8a60be7..d54719d0f3feee 100755 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -69,7 +69,6 @@ async def test_async_setup_bad_config1(hass): }, ) await hass.async_block_till_done() - assert dynalite.DOMAIN not in hass.data async def test_async_setup_bad_config2(hass): From baaeace83b68df38521cc2509d11a52a7801cf2a Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Wed, 1 Apr 2020 19:44:10 +0000 Subject: [PATCH 07/20] removed trigger template --- homeassistant/components/dynalite/__init__.py | 8 +------- tests/components/dynalite/test_const.py | 1 - 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index d1122db05a9b04..a63d745e66d212 100755 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -41,7 +41,6 @@ CONF_STOP_PRESET, CONF_TEMPLATE, CONF_TILT_TIME, - CONF_TRIGGER, DEFAULT_CHANNEL_TYPE, DEFAULT_NAME, DEFAULT_PORT, @@ -82,8 +81,6 @@ def num_string(value: Union[int, str]) -> str: {vol.Optional(CONF_ROOM_ON): num_string, vol.Optional(CONF_ROOM_OFF): num_string} ) -TEMPLATE_TRIGGER_SCHEMA = vol.Schema({vol.Optional(CONF_TRIGGER): num_string}) - TEMPLATE_TIMECOVER_SCHEMA = vol.Schema( { vol.Optional(CONF_CHANNEL_COVER): num_string, @@ -96,9 +93,7 @@ def num_string(value: Union[int, str]) -> str: } ) -TEMPLATE_DATA_SCHEMA = vol.Any( - TEMPLATE_ROOM_SCHEMA, TEMPLATE_TRIGGER_SCHEMA, TEMPLATE_TIMECOVER_SCHEMA -) +TEMPLATE_DATA_SCHEMA = vol.Any(TEMPLATE_ROOM_SCHEMA, TEMPLATE_TIMECOVER_SCHEMA) TEMPLATE_SCHEMA = vol.Schema({str: TEMPLATE_DATA_SCHEMA}) @@ -132,7 +127,6 @@ def validate_area(config: Dict[str, Any]) -> Dict[str, Any]: # the next ones can be part of the templates vol.Optional(CONF_ROOM_ON): num_string, vol.Optional(CONF_ROOM_OFF): num_string, - vol.Optional(CONF_TRIGGER): num_string, vol.Optional(CONF_CHANNEL_COVER): num_string, vol.Optional(CONF_DEVICE_CLASS): cv.string, vol.Optional(CONF_OPEN_PRESET): num_string, diff --git a/tests/components/dynalite/test_const.py b/tests/components/dynalite/test_const.py index f03bfcbc8973c3..9ac71cf41905e1 100644 --- a/tests/components/dynalite/test_const.py +++ b/tests/components/dynalite/test_const.py @@ -33,7 +33,6 @@ def test_consts(): assert dynalite.CONF_STOP_PRESET == dyn_const.CONF_STOP_PRESET assert dynalite.CONF_TEMPLATE == dyn_const.CONF_TEMPLATE assert dynalite.CONF_TILT_TIME == dyn_const.CONF_TILT_TIME - assert dynalite.CONF_TRIGGER == dyn_const.CONF_TRIGGER assert dynalite.DEFAULT_CHANNEL_TYPE == dyn_const.DEFAULT_CHANNEL_TYPE assert dynalite.DEFAULT_NAME == dyn_const.DEFAULT_NAME assert dynalite.DEFAULT_PORT == dyn_const.DEFAULT_PORT From c3b570afe2888f0bd24464caffaba485ee883c52 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 09:42:28 +0300 Subject: [PATCH 08/20] Update homeassistant/components/dynalite/__init__.py Co-Authored-By: Martin Hjelmare --- homeassistant/components/dynalite/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index a63d745e66d212..db387afc5ca36c 100755 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -110,7 +110,7 @@ def validate_area(config: Dict[str, Any]) -> Dict[str, Any]: for conf in conf_set: if config.get(conf): raise vol.Invalid( - f"{conf} cannot should not be part of area {config[CONF_NAME]} config" + f"{conf} should not be part of area {config[CONF_NAME]} config" ) return config From d6136064dc6c2a8c3d60a979c24f4c8982182147 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 09:53:23 +0300 Subject: [PATCH 09/20] Update homeassistant/components/dynalite/const.py Co-Authored-By: Martin Hjelmare --- homeassistant/components/dynalite/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index 654e1649b0f718..01013a37d9f1a8 100755 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -36,7 +36,7 @@ CONF_STOP_PRESET = "stop" CONF_TEMPLATE = "template" CONF_TILT_TIME = "tilt" -CONF_TIME_COVER = "timecover" +CONF_TIME_COVER = "time_cover" CONF_TRIGGER = "trigger" DEFAULT_CHANNEL_TYPE = "light" From 5430cc7b3f2a2ed4e5bed30ee09d44d9e5f66088 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 06:54:46 +0000 Subject: [PATCH 10/20] removed CONF_TRIGGER from const corrected type hints - not clear why mypy didn't catch it --- homeassistant/components/dynalite/bridge.py | 8 ++++---- homeassistant/components/dynalite/const.py | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/homeassistant/components/dynalite/bridge.py b/homeassistant/components/dynalite/bridge.py index a627805997bbe2..912581f479d0ed 100755 --- a/homeassistant/components/dynalite/bridge.py +++ b/homeassistant/components/dynalite/bridge.py @@ -10,7 +10,7 @@ from .const import CONF_ALL, CONF_HOST, ENTITY_PLATFORMS, LOGGER if TYPE_CHECKING: # pragma: no cover - from .dynalitebase import DynaliteBase + from dynalite_devices_lib.dynalitebase import DynaliteBaseDevice class DynaliteBridge: @@ -41,7 +41,7 @@ def reload_config(self, config: Dict[str, Any]) -> None: LOGGER.debug("Reloading bridge - host %s, config %s", self.host, config) self.dynalite_devices.configure(config) - def update_signal(self, device: "DynaliteBase" = None) -> str: + def update_signal(self, device: "DynaliteBaseDevice" = None) -> str: """Create signal to use to trigger entity update.""" if device: signal = f"dynalite-update-{self.host}-{device.unique_id}" @@ -50,7 +50,7 @@ def update_signal(self, device: "DynaliteBase" = None) -> str: return signal @callback - def update_device(self, device: "DynaliteBase") -> None: + def update_device(self, device: "DynaliteBaseDevice") -> None: """Call when a device or all devices should be updated.""" if device == CONF_ALL: # This is used to signal connection or disconnection, so all devices may become available or not. @@ -69,7 +69,7 @@ def register_add_devices(self, platform: str, async_add_devices: Callable) -> No if platform in self.waiting_devices: self.async_add_devices[platform](self.waiting_devices[platform]) - def add_devices_when_registered(self, devices: List["DynaliteBase"]) -> None: + def add_devices_when_registered(self, devices: List["DynaliteBaseDevice"]) -> None: """Add the devices to HA if the add devices callback was registered, otherwise queue until it is.""" for platform in ENTITY_PLATFORMS: platform_devices = [ diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index 654e1649b0f718..00303bed2f0790 100755 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -37,7 +37,6 @@ CONF_TEMPLATE = "template" CONF_TILT_TIME = "tilt" CONF_TIME_COVER = "timecover" -CONF_TRIGGER = "trigger" DEFAULT_CHANNEL_TYPE = "light" DEFAULT_COVER_CLASS = "shutter" @@ -45,7 +44,6 @@ DEFAULT_PORT = 12345 DEFAULT_TEMPLATES = { CONF_ROOM: [CONF_ROOM_ON, CONF_ROOM_OFF], - CONF_TRIGGER: [CONF_TRIGGER], CONF_TIME_COVER: [ CONF_CHANNEL_COVER, CONF_DEVICE_CLASS, From d158be37ed6a19289f1e5a40e881d0812fb90b19 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 08:50:30 +0000 Subject: [PATCH 11/20] conversion of the config to library CONFs --- .../components/dynalite/convert_config.py | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 homeassistant/components/dynalite/convert_config.py diff --git a/homeassistant/components/dynalite/convert_config.py b/homeassistant/components/dynalite/convert_config.py new file mode 100644 index 00000000000000..ac9dcfe1e13825 --- /dev/null +++ b/homeassistant/components/dynalite/convert_config.py @@ -0,0 +1,91 @@ +"""Convert the HA config to the dynalite config.""" + +from typing import Any, Dict + +from dynalite_devices_lib import const as dyn_const + +from homeassistant.const import CONF_HOST + +from .const import ( + CONF_ACTIVE, + CONF_ACTIVE_INIT, + CONF_ACTIVE_OFF, + CONF_ACTIVE_ON, + CONF_AREA, + CONF_AUTO_DISCOVER, + CONF_CHANNEL, + CONF_CHANNEL_COVER, + CONF_CHANNEL_TYPE, + CONF_CLOSE_PRESET, + CONF_DEFAULT, + CONF_DEVICE_CLASS, + CONF_DURATION, + CONF_FADE, + CONF_NAME, + CONF_NO_DEFAULT, + CONF_OPEN_PRESET, + CONF_POLL_TIMER, + CONF_PORT, + CONF_PRESET, + CONF_ROOM, + CONF_ROOM_OFF, + CONF_ROOM_ON, + CONF_STOP_PRESET, + CONF_TEMPLATE, + CONF_TILT_TIME, + CONF_TIME_COVER, + LOGGER, +) + +CONF_MAP = { + CONF_ACTIVE: dyn_const.CONF_ACTIVE, + CONF_ACTIVE_INIT: dyn_const.CONF_ACTIVE_INIT, + CONF_ACTIVE_OFF: dyn_const.CONF_ACTIVE_OFF, + CONF_ACTIVE_ON: dyn_const.CONF_ACTIVE_ON, + CONF_AREA: dyn_const.CONF_AREA, + CONF_AUTO_DISCOVER: dyn_const.CONF_AUTO_DISCOVER, + CONF_CHANNEL: dyn_const.CONF_CHANNEL, + CONF_CHANNEL_COVER: dyn_const.CONF_CHANNEL_COVER, + CONF_CHANNEL_TYPE: dyn_const.CONF_CHANNEL_TYPE, + CONF_CLOSE_PRESET: dyn_const.CONF_CLOSE_PRESET, + CONF_DEFAULT: dyn_const.CONF_DEFAULT, + CONF_DEVICE_CLASS: dyn_const.CONF_DEVICE_CLASS, + CONF_DURATION: dyn_const.CONF_DURATION, + CONF_FADE: dyn_const.CONF_FADE, + CONF_HOST: dyn_const.CONF_HOST, + CONF_NAME: dyn_const.CONF_NAME, + CONF_NO_DEFAULT: dyn_const.CONF_NO_DEFAULT, + CONF_OPEN_PRESET: dyn_const.CONF_OPEN_PRESET, + CONF_POLL_TIMER: dyn_const.CONF_POLL_TIMER, + CONF_PORT: dyn_const.CONF_PORT, + CONF_PRESET: dyn_const.CONF_PRESET, + CONF_ROOM: dyn_const.CONF_ROOM, + CONF_ROOM_OFF: dyn_const.CONF_ROOM_OFF, + CONF_ROOM_ON: dyn_const.CONF_ROOM_ON, + CONF_STOP_PRESET: dyn_const.CONF_STOP_PRESET, + CONF_TEMPLATE: dyn_const.CONF_TEMPLATE, + CONF_TILT_TIME: dyn_const.CONF_TILT_TIME, + CONF_TIME_COVER: dyn_const.CONF_TIME_COVER, +} + + +def convert_element(value: str) -> str: + """Convert a string if it is in the map.""" + if value in CONF_MAP: + LOGGER.error("XXX replaced %s with %s", value, CONF_MAP[value]) + return CONF_MAP[value] + return value + + +def convert_config(config: Dict[str, Any]) -> Dict[str, Any]: + """Convert a config dict by replacing component consts with library consts.""" + result = {} + for (key, value) in config.items(): + if isinstance(value, dict): + new_value = convert_config(value) + elif isinstance(value, str): + new_value = convert_element(value) + else: + new_value = value + result[convert_element(key)] = new_value + return result From 17d3a89385f6c83bbd1c9e565c23b6c4c70995b8 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 09:21:31 +0000 Subject: [PATCH 12/20] moved to use the value since it should come from the library --- tests/components/dynalite/test_config_flow.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/components/dynalite/test_config_flow.py b/tests/components/dynalite/test_config_flow.py index 03196f050b890d..1a1cdc16f49a18 100755 --- a/tests/components/dynalite/test_config_flow.py +++ b/tests/components/dynalite/test_config_flow.py @@ -73,7 +73,7 @@ async def test_existing_update(hass): assert await hass.config_entries.async_setup(entry.entry_id) await hass.async_block_till_done() mock_dyn_dev().configure.assert_called_once() - assert mock_dyn_dev().configure.mock_calls[0][1][0][dynalite.CONF_PORT] == port1 + assert mock_dyn_dev().configure.mock_calls[0][1][0]["port"] == port1 result = await hass.config_entries.flow.async_init( dynalite.DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, @@ -81,7 +81,7 @@ async def test_existing_update(hass): ) await hass.async_block_till_done() assert mock_dyn_dev().configure.call_count == 2 - assert mock_dyn_dev().configure.mock_calls[1][1][0][dynalite.CONF_PORT] == port2 + assert mock_dyn_dev().configure.mock_calls[1][1][0]["port"] == port2 assert result["type"] == "abort" assert result["reason"] == "already_configured" From 77dbc04f4cc23da9d19d1cac0e9c1f33c3f563f0 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 09:39:11 +0000 Subject: [PATCH 13/20] taking CONF_HOST from homeassistant.const instead of module const --- homeassistant/components/dynalite/bridge.py | 7 +++++-- homeassistant/components/dynalite/const.py | 1 - tests/components/dynalite/test_init.py | 9 +++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/dynalite/bridge.py b/homeassistant/components/dynalite/bridge.py index b40124293a3e93..09cf8e25a10575 100755 --- a/homeassistant/components/dynalite/bridge.py +++ b/homeassistant/components/dynalite/bridge.py @@ -4,14 +4,17 @@ from dynalite_devices_lib.dynalite_devices import DynaliteDevices +from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.dispatcher import async_dispatcher_send -from .const import CONF_ALL, CONF_HOST, ENTITY_PLATFORMS, LOGGER +from .const import CONF_ALL, ENTITY_PLATFORMS, LOGGER from .convert_config import convert_config if TYPE_CHECKING: # pragma: no cover - from dynalite_devices_lib.dynalitebase import DynaliteBaseDevice + from dynalite_devices_lib.dynalite_devices import ( # pylint: disable=ungrouped-imports + DynaliteBaseDevice, + ) class DynaliteBridge: diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index c30016483f7742..8a9429eeb7f030 100755 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -23,7 +23,6 @@ CONF_DEVICE_CLASS = "class" CONF_DURATION = "duration" CONF_FADE = "fade" -CONF_HOST = "host" CONF_NAME = "name" CONF_NO_DEFAULT = "nodefault" CONF_OPEN_PRESET = "open" diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index 44a59a3c1136c0..b4702fc901dbe4 100755 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -4,6 +4,7 @@ from asynctest import call, patch import homeassistant.components.dynalite.const as dynalite +from homeassistant.const import CONF_HOST from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -29,7 +30,7 @@ async def test_async_setup(hass): dynalite.DOMAIN: { dynalite.CONF_BRIDGES: [ { - dynalite.CONF_HOST: "1.2.3.4", + CONF_HOST: "1.2.3.4", dynalite.CONF_PORT: 1234, dynalite.CONF_AUTO_DISCOVER: True, dynalite.CONF_POLL_TIMER: 5.5, @@ -94,7 +95,7 @@ async def test_async_setup_bad_config1(hass): dynalite.DOMAIN: { dynalite.CONF_BRIDGES: [ { - dynalite.CONF_HOST: "1.2.3.4", + CONF_HOST: "1.2.3.4", dynalite.CONF_AREA: { "1": { dynalite.CONF_TEMPLATE: dynalite.CONF_TIME_COVER, @@ -124,7 +125,7 @@ async def test_async_setup_bad_config2(hass): dynalite.DOMAIN: { dynalite.CONF_BRIDGES: [ { - dynalite.CONF_HOST: host, + CONF_HOST: host, dynalite.CONF_AREA: {"WRONG": {dynalite.CONF_NAME: "Name"}}, } ] @@ -138,7 +139,7 @@ async def test_async_setup_bad_config2(hass): async def test_unload_entry(hass): """Test being able to unload an entry.""" host = "1.2.3.4" - entry = MockConfigEntry(domain=dynalite.DOMAIN, data={dynalite.CONF_HOST: host}) + entry = MockConfigEntry(domain=dynalite.DOMAIN, data={CONF_HOST: host}) entry.add_to_hass(hass) with patch( "homeassistant.components.dynalite.bridge.DynaliteDevices.async_setup", From fed451d630052cd77769f2334f2ae996bb5caa61 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 09:43:25 +0000 Subject: [PATCH 14/20] use dict.get removed leftover log --- homeassistant/components/dynalite/convert_config.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/homeassistant/components/dynalite/convert_config.py b/homeassistant/components/dynalite/convert_config.py index ac9dcfe1e13825..b097abb83b4b7f 100644 --- a/homeassistant/components/dynalite/convert_config.py +++ b/homeassistant/components/dynalite/convert_config.py @@ -34,7 +34,6 @@ CONF_TEMPLATE, CONF_TILT_TIME, CONF_TIME_COVER, - LOGGER, ) CONF_MAP = { @@ -71,10 +70,7 @@ def convert_element(value: str) -> str: """Convert a string if it is in the map.""" - if value in CONF_MAP: - LOGGER.error("XXX replaced %s with %s", value, CONF_MAP[value]) - return CONF_MAP[value] - return value + return CONF_MAP.get(value, value) def convert_config(config: Dict[str, Any]) -> Dict[str, Any]: From 989d96b696ea3691cd1ab13be252cefd9c4e7caa Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 10:09:21 +0000 Subject: [PATCH 15/20] force device_class to be from homeassistant consts --- homeassistant/components/dynalite/__init__.py | 5 +++-- tests/components/dynalite/test_init.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index db387afc5ca36c..94c8feec3be7b7 100755 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -6,6 +6,7 @@ import voluptuous as vol from homeassistant import config_entries +from homeassistant.components.cover import DEVICE_CLASSES_SCHEMA from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant @@ -84,7 +85,7 @@ def num_string(value: Union[int, str]) -> str: TEMPLATE_TIMECOVER_SCHEMA = vol.Schema( { vol.Optional(CONF_CHANNEL_COVER): num_string, - vol.Optional(CONF_DEVICE_CLASS): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_OPEN_PRESET): num_string, vol.Optional(CONF_CLOSE_PRESET): num_string, vol.Optional(CONF_STOP_PRESET): num_string, @@ -128,7 +129,7 @@ def validate_area(config: Dict[str, Any]) -> Dict[str, Any]: vol.Optional(CONF_ROOM_ON): num_string, vol.Optional(CONF_ROOM_OFF): num_string, vol.Optional(CONF_CHANNEL_COVER): num_string, - vol.Optional(CONF_DEVICE_CLASS): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_OPEN_PRESET): num_string, vol.Optional(CONF_CLOSE_PRESET): num_string, vol.Optional(CONF_STOP_PRESET): num_string, diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index b4702fc901dbe4..775b32a6dbcb6f 100755 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -70,7 +70,7 @@ async def test_async_setup(hass): dynalite.CONF_CHANNEL_COVER: 3, dynalite.CONF_DURATION: 2.2, dynalite.CONF_TILT_TIME: 3.3, - dynalite.CONF_DEVICE_CLASS: "xxx", + dynalite.CONF_DEVICE_CLASS: "awning", }, }, } From d9b0bac8f9ce6ca32da39cfeee91160e0e0dd3f3 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 10:30:09 +0000 Subject: [PATCH 16/20] move dict.get to inline --- homeassistant/components/dynalite/convert_config.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/dynalite/convert_config.py b/homeassistant/components/dynalite/convert_config.py index b097abb83b4b7f..95d942677ad50f 100644 --- a/homeassistant/components/dynalite/convert_config.py +++ b/homeassistant/components/dynalite/convert_config.py @@ -68,11 +68,6 @@ } -def convert_element(value: str) -> str: - """Convert a string if it is in the map.""" - return CONF_MAP.get(value, value) - - def convert_config(config: Dict[str, Any]) -> Dict[str, Any]: """Convert a config dict by replacing component consts with library consts.""" result = {} @@ -80,8 +75,8 @@ def convert_config(config: Dict[str, Any]) -> Dict[str, Any]: if isinstance(value, dict): new_value = convert_config(value) elif isinstance(value, str): - new_value = convert_element(value) + new_value = CONF_MAP.get(value, value) else: new_value = value - result[convert_element(key)] = new_value + result[CONF_MAP.get(key, key)] = new_value return result From d33df14faa48e074d52f42e00f8604f760edefd5 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 10:38:59 +0000 Subject: [PATCH 17/20] removed CONF from values changed "channelcover" to "channel_cover" --- homeassistant/components/dynalite/__init__.py | 8 ++++---- homeassistant/components/dynalite/const.py | 8 ++++---- homeassistant/components/dynalite/convert_config.py | 12 ++++++------ tests/components/dynalite/test_init.py | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index 94c8feec3be7b7..2f31767c2e57f2 100755 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -16,10 +16,10 @@ # Loading the config flow file will register the flow from .bridge import DynaliteBridge from .const import ( + ACTIVE_INIT, + ACTIVE_OFF, + ACTIVE_ON, CONF_ACTIVE, - CONF_ACTIVE_INIT, - CONF_ACTIVE_OFF, - CONF_ACTIVE_ON, CONF_AREA, CONF_AUTO_DISCOVER, CONF_BRIDGES, @@ -155,7 +155,7 @@ def validate_area(config: Dict[str, Any]) -> Dict[str, Any]: vol.Optional(CONF_AREA): AREA_SCHEMA, vol.Optional(CONF_DEFAULT): PLATFORM_DEFAULTS_SCHEMA, vol.Optional(CONF_ACTIVE, default=False): vol.Any( - CONF_ACTIVE_ON, CONF_ACTIVE_OFF, CONF_ACTIVE_INIT, cv.boolean + ACTIVE_ON, ACTIVE_OFF, ACTIVE_INIT, cv.boolean ), vol.Optional(CONF_PRESET): PRESET_SCHEMA, vol.Optional(CONF_TEMPLATE): TEMPLATE_SCHEMA, diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index 8a9429eeb7f030..96856448b15c1b 100755 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -8,15 +8,15 @@ CONF_ACTIVE = "active" -CONF_ACTIVE_INIT = "init" -CONF_ACTIVE_OFF = "off" -CONF_ACTIVE_ON = "on" +ACTIVE_INIT = "init" +ACTIVE_OFF = "off" +ACTIVE_ON = "on" CONF_ALL = "ALL" CONF_AREA = "area" CONF_AUTO_DISCOVER = "autodiscover" CONF_BRIDGES = "bridges" CONF_CHANNEL = "channel" -CONF_CHANNEL_COVER = "channelcover" +CONF_CHANNEL_COVER = "channel_cover" CONF_CHANNEL_TYPE = "type" CONF_CLOSE_PRESET = "close" CONF_DEFAULT = "default" diff --git a/homeassistant/components/dynalite/convert_config.py b/homeassistant/components/dynalite/convert_config.py index 95d942677ad50f..1fb4c3052c541f 100644 --- a/homeassistant/components/dynalite/convert_config.py +++ b/homeassistant/components/dynalite/convert_config.py @@ -7,10 +7,10 @@ from homeassistant.const import CONF_HOST from .const import ( + ACTIVE_INIT, + ACTIVE_OFF, + ACTIVE_ON, CONF_ACTIVE, - CONF_ACTIVE_INIT, - CONF_ACTIVE_OFF, - CONF_ACTIVE_ON, CONF_AREA, CONF_AUTO_DISCOVER, CONF_CHANNEL, @@ -38,9 +38,9 @@ CONF_MAP = { CONF_ACTIVE: dyn_const.CONF_ACTIVE, - CONF_ACTIVE_INIT: dyn_const.CONF_ACTIVE_INIT, - CONF_ACTIVE_OFF: dyn_const.CONF_ACTIVE_OFF, - CONF_ACTIVE_ON: dyn_const.CONF_ACTIVE_ON, + ACTIVE_INIT: dyn_const.CONF_ACTIVE_INIT, + ACTIVE_OFF: dyn_const.CONF_ACTIVE_OFF, + ACTIVE_ON: dyn_const.CONF_ACTIVE_ON, CONF_AREA: dyn_const.CONF_AREA, CONF_AUTO_DISCOVER: dyn_const.CONF_AUTO_DISCOVER, CONF_CHANNEL: dyn_const.CONF_CHANNEL, diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index 775b32a6dbcb6f..98e2f17d96d75f 100755 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -51,7 +51,7 @@ async def test_async_setup(hass): }, }, dynalite.CONF_DEFAULT: {dynalite.CONF_FADE: 2.3}, - dynalite.CONF_ACTIVE: dynalite.CONF_ACTIVE_INIT, + dynalite.CONF_ACTIVE: dynalite.ACTIVE_INIT, dynalite.CONF_PRESET: { "5": { dynalite.CONF_NAME: "pres6", From 1033851c49a8458bcae1f841207b834ee03e0200 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 11:34:24 +0000 Subject: [PATCH 18/20] moved some CONF values out of const.py and taking them from homeassistant.const --- homeassistant/components/dynalite/__init__.py | 7 ++---- homeassistant/components/dynalite/const.py | 6 ++--- .../components/dynalite/convert_config.py | 8 ++---- tests/components/dynalite/test_init.py | 25 ++++++++----------- 4 files changed, 17 insertions(+), 29 deletions(-) diff --git a/homeassistant/components/dynalite/__init__.py b/homeassistant/components/dynalite/__init__.py index 2f31767c2e57f2..2f2ed8f2fa2293 100755 --- a/homeassistant/components/dynalite/__init__.py +++ b/homeassistant/components/dynalite/__init__.py @@ -8,7 +8,7 @@ from homeassistant import config_entries from homeassistant.components.cover import DEVICE_CLASSES_SCHEMA from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_TYPE from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv @@ -25,17 +25,14 @@ CONF_BRIDGES, CONF_CHANNEL, CONF_CHANNEL_COVER, - CONF_CHANNEL_TYPE, CONF_CLOSE_PRESET, CONF_DEFAULT, CONF_DEVICE_CLASS, CONF_DURATION, CONF_FADE, - CONF_NAME, CONF_NO_DEFAULT, CONF_OPEN_PRESET, CONF_POLL_TIMER, - CONF_PORT, CONF_PRESET, CONF_ROOM_OFF, CONF_ROOM_ON, @@ -64,7 +61,7 @@ def num_string(value: Union[int, str]) -> str: { vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_FADE): vol.Coerce(float), - vol.Optional(CONF_CHANNEL_TYPE, default=DEFAULT_CHANNEL_TYPE): vol.Any( + vol.Optional(CONF_TYPE, default=DEFAULT_CHANNEL_TYPE): vol.Any( "light", "switch" ), } diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index 96856448b15c1b..d18f3eaccb4779 100755 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -1,6 +1,8 @@ """Constants for the Dynalite component.""" import logging +from homeassistant.const import CONF_ROOM + LOGGER = logging.getLogger(__package__) DOMAIN = "dynalite" @@ -17,19 +19,15 @@ CONF_BRIDGES = "bridges" CONF_CHANNEL = "channel" CONF_CHANNEL_COVER = "channel_cover" -CONF_CHANNEL_TYPE = "type" CONF_CLOSE_PRESET = "close" CONF_DEFAULT = "default" CONF_DEVICE_CLASS = "class" CONF_DURATION = "duration" CONF_FADE = "fade" -CONF_NAME = "name" CONF_NO_DEFAULT = "nodefault" CONF_OPEN_PRESET = "open" CONF_POLL_TIMER = "polltimer" -CONF_PORT = "port" CONF_PRESET = "preset" -CONF_ROOM = "room" CONF_ROOM_OFF = "room_off" CONF_ROOM_ON = "room_on" CONF_STOP_PRESET = "stop" diff --git a/homeassistant/components/dynalite/convert_config.py b/homeassistant/components/dynalite/convert_config.py index 1fb4c3052c541f..03ece744d413be 100644 --- a/homeassistant/components/dynalite/convert_config.py +++ b/homeassistant/components/dynalite/convert_config.py @@ -4,7 +4,7 @@ from dynalite_devices_lib import const as dyn_const -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_ROOM, CONF_TYPE from .const import ( ACTIVE_INIT, @@ -15,19 +15,15 @@ CONF_AUTO_DISCOVER, CONF_CHANNEL, CONF_CHANNEL_COVER, - CONF_CHANNEL_TYPE, CONF_CLOSE_PRESET, CONF_DEFAULT, CONF_DEVICE_CLASS, CONF_DURATION, CONF_FADE, - CONF_NAME, CONF_NO_DEFAULT, CONF_OPEN_PRESET, CONF_POLL_TIMER, - CONF_PORT, CONF_PRESET, - CONF_ROOM, CONF_ROOM_OFF, CONF_ROOM_ON, CONF_STOP_PRESET, @@ -45,7 +41,7 @@ CONF_AUTO_DISCOVER: dyn_const.CONF_AUTO_DISCOVER, CONF_CHANNEL: dyn_const.CONF_CHANNEL, CONF_CHANNEL_COVER: dyn_const.CONF_CHANNEL_COVER, - CONF_CHANNEL_TYPE: dyn_const.CONF_CHANNEL_TYPE, + CONF_TYPE: dyn_const.CONF_CHANNEL_TYPE, CONF_CLOSE_PRESET: dyn_const.CONF_CLOSE_PRESET, CONF_DEFAULT: dyn_const.CONF_DEFAULT, CONF_DEVICE_CLASS: dyn_const.CONF_DEVICE_CLASS, diff --git a/tests/components/dynalite/test_init.py b/tests/components/dynalite/test_init.py index 98e2f17d96d75f..8e2290a9c407af 100755 --- a/tests/components/dynalite/test_init.py +++ b/tests/components/dynalite/test_init.py @@ -4,7 +4,7 @@ from asynctest import call, patch import homeassistant.components.dynalite.const as dynalite -from homeassistant.const import CONF_HOST +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_ROOM from homeassistant.setup import async_setup_component from tests.common import MockConfigEntry @@ -31,35 +31,32 @@ async def test_async_setup(hass): dynalite.CONF_BRIDGES: [ { CONF_HOST: "1.2.3.4", - dynalite.CONF_PORT: 1234, + CONF_PORT: 1234, dynalite.CONF_AUTO_DISCOVER: True, dynalite.CONF_POLL_TIMER: 5.5, dynalite.CONF_AREA: { "1": { - dynalite.CONF_NAME: "Name1", + CONF_NAME: "Name1", dynalite.CONF_CHANNEL: {"4": {}}, dynalite.CONF_NO_DEFAULT: True, }, - "2": {dynalite.CONF_NAME: "Name2"}, + "2": {CONF_NAME: "Name2"}, "3": { - dynalite.CONF_NAME: "Name3", - dynalite.CONF_TEMPLATE: dynalite.CONF_ROOM, + CONF_NAME: "Name3", + dynalite.CONF_TEMPLATE: CONF_ROOM, }, "4": { - dynalite.CONF_NAME: "Name4", + CONF_NAME: "Name4", dynalite.CONF_TEMPLATE: dynalite.CONF_TIME_COVER, }, }, dynalite.CONF_DEFAULT: {dynalite.CONF_FADE: 2.3}, dynalite.CONF_ACTIVE: dynalite.ACTIVE_INIT, dynalite.CONF_PRESET: { - "5": { - dynalite.CONF_NAME: "pres6", - dynalite.CONF_FADE: 4.5, - } + "5": {CONF_NAME: "pres5", dynalite.CONF_FADE: 4.5} }, dynalite.CONF_TEMPLATE: { - dynalite.CONF_ROOM: { + CONF_ROOM: { dynalite.CONF_ROOM_ON: 6, dynalite.CONF_ROOM_OFF: 7, }, @@ -99,7 +96,7 @@ async def test_async_setup_bad_config1(hass): dynalite.CONF_AREA: { "1": { dynalite.CONF_TEMPLATE: dynalite.CONF_TIME_COVER, - dynalite.CONF_NAME: "Name", + CONF_NAME: "Name", dynalite.CONF_ROOM_ON: 7, } }, @@ -126,7 +123,7 @@ async def test_async_setup_bad_config2(hass): dynalite.CONF_BRIDGES: [ { CONF_HOST: host, - dynalite.CONF_AREA: {"WRONG": {dynalite.CONF_NAME: "Name"}}, + dynalite.CONF_AREA: {"WRONG": {CONF_NAME: "Name"}}, } ] } From fe361eb9e6b0043d524575a8a34905b61a934e07 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 14:36:21 +0000 Subject: [PATCH 19/20] verifying that device class is a valid HA device class --- homeassistant/components/dynalite/cover.py | 8 ++++++-- tests/components/dynalite/test_cover.py | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/dynalite/cover.py b/homeassistant/components/dynalite/cover.py index 16d6c4c1a5f5bb..dcf16ede58c34a 100644 --- a/homeassistant/components/dynalite/cover.py +++ b/homeassistant/components/dynalite/cover.py @@ -1,10 +1,11 @@ """Support for the Dynalite channels as covers.""" from typing import Callable -from homeassistant.components.cover import CoverDevice +from homeassistant.components.cover import DEVICE_CLASSES, CoverDevice from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback +from .const import DEFAULT_COVER_CLASS from .dynalitebase import DynaliteBase, async_setup_entry_base @@ -30,7 +31,10 @@ class DynaliteCover(DynaliteBase, CoverDevice): @property def device_class(self) -> str: """Return the class of the device.""" - return self._device.device_class + dev_cls = self._device.device_class + if dev_cls in DEVICE_CLASSES: + return dev_cls + return DEFAULT_COVER_CLASS @property def current_cover_position(self) -> int: diff --git a/tests/components/dynalite/test_cover.py b/tests/components/dynalite/test_cover.py index b0c3eea3acfc65..cef4081c60714b 100644 --- a/tests/components/dynalite/test_cover.py +++ b/tests/components/dynalite/test_cover.py @@ -15,7 +15,9 @@ @pytest.fixture def mock_device(): """Mock a Dynalite device.""" - return create_mock_device("cover", DynaliteTimeCoverWithTiltDevice) + mock_dev = create_mock_device("cover", DynaliteTimeCoverWithTiltDevice) + mock_dev.device_class = "blind" + return mock_dev async def test_cover_setup(hass, mock_device): From ad907e72e88540dcaac158e005c1e8a3d34ab105 Mon Sep 17 00:00:00 2001 From: Ziv <16467659+ziv1234@users.noreply.github.com> Date: Thu, 2 Apr 2020 16:25:53 +0000 Subject: [PATCH 20/20] moved shutter to home assistant const --- homeassistant/components/dynalite/const.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/dynalite/const.py b/homeassistant/components/dynalite/const.py index d18f3eaccb4779..ade167e1b3e7a7 100755 --- a/homeassistant/components/dynalite/const.py +++ b/homeassistant/components/dynalite/const.py @@ -1,6 +1,7 @@ """Constants for the Dynalite component.""" import logging +from homeassistant.components.cover import DEVICE_CLASS_SHUTTER from homeassistant.const import CONF_ROOM LOGGER = logging.getLogger(__package__) @@ -36,7 +37,7 @@ CONF_TIME_COVER = "time_cover" DEFAULT_CHANNEL_TYPE = "light" -DEFAULT_COVER_CLASS = "shutter" +DEFAULT_COVER_CLASS = DEVICE_CLASS_SHUTTER DEFAULT_NAME = "dynalite" DEFAULT_PORT = 12345 DEFAULT_TEMPLATES = {