Add Dynalite switch platform (#32389)

* added presets for switch devices

* added channel type to __init and const

* ran pylint on library so needed a few changes in names

* removed callback

* bool -> cv.boolean
This commit is contained in:
Paulus Schoutsen 2020-03-04 22:05:39 -08:00 committed by GitHub
parent d216c1f2ac
commit 521cc7247d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 134 additions and 31 deletions

View File

@ -1,4 +1,7 @@
"""Support for the Dynalite networks."""
import asyncio
import voluptuous as vol
from homeassistant import config_entries
@ -10,18 +13,26 @@ from homeassistant.helpers import config_validation as cv
from .bridge import DynaliteBridge
from .const import (
CONF_ACTIVE,
CONF_ACTIVE_INIT,
CONF_ACTIVE_OFF,
CONF_ACTIVE_ON,
CONF_AREA,
CONF_AUTO_DISCOVER,
CONF_BRIDGES,
CONF_CHANNEL,
CONF_CHANNEL_TYPE,
CONF_DEFAULT,
CONF_FADE,
CONF_NAME,
CONF_NO_DEFAULT,
CONF_POLLTIMER,
CONF_PORT,
CONF_PRESET,
DEFAULT_CHANNEL_TYPE,
DEFAULT_NAME,
DEFAULT_PORT,
DOMAIN,
ENTITY_PLATFORMS,
LOGGER,
)
@ -35,16 +46,31 @@ def num_string(value):
CHANNEL_DATA_SCHEMA = vol.Schema(
{vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_FADE): vol.Coerce(float)}
{
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_FADE): vol.Coerce(float),
vol.Optional(CONF_CHANNEL_TYPE, default=DEFAULT_CHANNEL_TYPE): vol.Any(
"light", "switch"
),
}
)
CHANNEL_SCHEMA = vol.Schema({num_string: CHANNEL_DATA_SCHEMA})
PRESET_DATA_SCHEMA = vol.Schema(
{vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_FADE): vol.Coerce(float)}
)
PRESET_SCHEMA = vol.Schema({num_string: vol.Any(PRESET_DATA_SCHEMA, None)})
AREA_DATA_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,
},
)
@ -62,7 +88,10 @@ BRIDGE_SCHEMA = vol.Schema(
vol.Optional(CONF_POLLTIMER, 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.Coerce(bool),
vol.Optional(CONF_ACTIVE, default=False): vol.Any(
CONF_ACTIVE_ON, CONF_ACTIVE_OFF, CONF_ACTIVE_INIT, cv.boolean
),
vol.Optional(CONF_PRESET): PRESET_SCHEMA,
}
)
@ -120,14 +149,17 @@ async def async_setup_entry(hass, entry):
"""Set up a bridge from a config entry."""
LOGGER.debug("Setting up entry %s", entry.data)
bridge = DynaliteBridge(hass, entry.data)
# need to do it before the listener
hass.data[DOMAIN][entry.entry_id] = bridge
entry.add_update_listener(async_entry_changed)
if not await bridge.async_setup():
LOGGER.error("Could not set up bridge for entry %s", entry.data)
hass.data[DOMAIN][entry.entry_id] = None
raise ConfigEntryNotReady
hass.data[DOMAIN][entry.entry_id] = bridge
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "light")
)
for platform in ENTITY_PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform)
)
return True
@ -135,5 +167,9 @@ async def async_unload_entry(hass, entry):
"""Unload a config entry."""
LOGGER.debug("Unloading entry %s", entry.data)
hass.data[DOMAIN].pop(entry.entry_id)
result = await hass.config_entries.async_forward_entry_unload(entry, "light")
return result
tasks = [
hass.config_entries.async_forward_entry_unload(entry, platform)
for platform in ENTITY_PLATFORMS
]
results = await asyncio.gather(*tasks)
return False not in results

View File

@ -20,8 +20,8 @@ class DynaliteBridge:
self.host = config[CONF_HOST]
# Configure the dynalite devices
self.dynalite_devices = DynaliteDevices(
newDeviceFunc=self.add_devices_when_registered,
updateDeviceFunc=self.update_device,
new_device_func=self.add_devices_when_registered,
update_device_func=self.update_device,
)
self.dynalite_devices.configure(config)
@ -31,7 +31,7 @@ class DynaliteBridge:
LOGGER.debug("Setting up bridge - host %s", self.host)
return await self.dynalite_devices.async_setup()
async def reload_config(self, config):
def reload_config(self, config):
"""Reconfigure a bridge when config changes."""
LOGGER.debug("Reloading bridge - host %s, config %s", self.host, config)
self.dynalite_devices.configure(config)

View File

@ -3,7 +3,7 @@ from homeassistant import config_entries
from homeassistant.const import CONF_HOST
from .bridge import DynaliteBridge
from .const import DOMAIN, LOGGER # pylint: disable=unused-import
from .const import DOMAIN, LOGGER
class DynaliteFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
@ -12,8 +12,6 @@ class DynaliteFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
# pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167
def __init__(self):
"""Initialize the Dynalite flow."""
self.host = None

View File

@ -4,21 +4,28 @@ import logging
LOGGER = logging.getLogger(__package__)
DOMAIN = "dynalite"
ENTITY_PLATFORMS = ["light"]
ENTITY_PLATFORMS = ["light", "switch"]
CONF_ACTIVE = "active"
CONF_ACTIVE_INIT = "init"
CONF_ACTIVE_OFF = "off"
CONF_ACTIVE_ON = "on"
CONF_ALL = "ALL"
CONF_AREA = "area"
CONF_AUTO_DISCOVER = "autodiscover"
CONF_BRIDGES = "bridges"
CONF_CHANNEL = "channel"
CONF_CHANNEL_TYPE = "type"
CONF_DEFAULT = "default"
CONF_FADE = "fade"
CONF_HOST = "host"
CONF_NAME = "name"
CONF_NO_DEFAULT = "nodefault"
CONF_POLLTIMER = "polltimer"
CONF_PORT = "port"
CONF_PRESET = "preset"
DEFAULT_CHANNEL_TYPE = "light"
DEFAULT_NAME = "dynalite"
DEFAULT_PORT = 12345

View File

@ -1,6 +1,5 @@
"""Support for Dynalite channels as lights."""
from homeassistant.components.light import SUPPORT_BRIGHTNESS, Light
from homeassistant.core import callback
from .dynalitebase import DynaliteBase, async_setup_entry_base
@ -8,12 +7,8 @@ from .dynalitebase import DynaliteBase, async_setup_entry_base
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Record the async_add_entities function to add them later when received from Dynalite."""
@callback
def light_from_device(device, bridge):
return DynaliteLight(device, bridge)
async_setup_entry_base(
hass, config_entry, async_add_entities, "light", light_from_device
hass, config_entry, async_add_entities, "light", DynaliteLight
)

View File

@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/dynalite",
"dependencies": [],
"codeowners": ["@ziv1234"],
"requirements": ["dynalite_devices==0.1.30"]
"requirements": ["dynalite_devices==0.1.32"]
}

View File

@ -0,0 +1,29 @@
"""Support for the Dynalite channels and presets as switches."""
from homeassistant.components.switch import SwitchDevice
from .dynalitebase import DynaliteBase, async_setup_entry_base
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Record the async_add_entities function to add them later when received from Dynalite."""
async_setup_entry_base(
hass, config_entry, async_add_entities, "switch", DynaliteSwitch
)
class DynaliteSwitch(DynaliteBase, SwitchDevice):
"""Representation of a Dynalite Channel as a Home Assistant Switch."""
@property
def is_on(self):
"""Return true if switch is on."""
return self._device.is_on
async def async_turn_on(self, **kwargs):
"""Turn the switch on."""
await self._device.async_turn_on()
async def async_turn_off(self, **kwargs):
"""Turn the switch off."""
await self._device.async_turn_off()

View File

@ -466,7 +466,7 @@ dsmr_parser==0.18
dweepy==0.3.0
# homeassistant.components.dynalite
dynalite_devices==0.1.30
dynalite_devices==0.1.32
# homeassistant.components.rainforest_eagle
eagle200_reader==0.2.1

View File

@ -174,7 +174,7 @@ distro==1.4.0
dsmr_parser==0.18
# homeassistant.components.dynalite
dynalite_devices==0.1.30
dynalite_devices==0.1.32
# homeassistant.components.ee_brightbox
eebrightbox==0.0.4

View File

@ -41,7 +41,7 @@ async def create_entity_from_device(hass, device):
mock_dyn_dev().async_setup = CoroutineMock(return_value=True)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
new_device_func = mock_dyn_dev.mock_calls[1][2]["newDeviceFunc"]
new_device_func = mock_dyn_dev.mock_calls[1][2]["new_device_func"]
new_device_func([device])
await hass.async_block_till_done()

View File

@ -20,7 +20,7 @@ async def test_update_device(hass):
mock_dyn_dev().async_setup = CoroutineMock(return_value=True)
assert await hass.config_entries.async_setup(entry.entry_id)
# Not waiting so it add the devices before registration
update_device_func = mock_dyn_dev.mock_calls[1][2]["updateDeviceFunc"]
update_device_func = mock_dyn_dev.mock_calls[1][2]["update_device_func"]
device = Mock()
device.unique_id = "abcdef"
wide_func = Mock()
@ -50,7 +50,7 @@ async def test_add_devices_then_register(hass):
mock_dyn_dev().async_setup = CoroutineMock(return_value=True)
assert await hass.config_entries.async_setup(entry.entry_id)
# Not waiting so it add the devices before registration
new_device_func = mock_dyn_dev.mock_calls[1][2]["newDeviceFunc"]
new_device_func = mock_dyn_dev.mock_calls[1][2]["new_device_func"]
# Now with devices
device1 = Mock()
device1.category = "light"
@ -73,7 +73,7 @@ async def test_register_then_add_devices(hass):
mock_dyn_dev().async_setup = CoroutineMock(return_value=True)
assert await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
new_device_func = mock_dyn_dev.mock_calls[1][2]["newDeviceFunc"]
new_device_func = mock_dyn_dev.mock_calls[1][2]["new_device_func"]
# Now with devices
device1 = Mock()
device1.category = "light"

View File

@ -83,5 +83,9 @@ async def test_unload_entry(hass):
) as mock_unload:
assert await hass.config_entries.async_unload(entry.entry_id)
await hass.async_block_till_done()
mock_unload.assert_called_once()
assert mock_unload.mock_calls == [call(entry, "light")]
assert mock_unload.call_count == len(dynalite.ENTITY_PLATFORMS)
expected_calls = [
call(entry, platform) for platform in dynalite.ENTITY_PLATFORMS
]
for cur_call in mock_unload.mock_calls:
assert cur_call in expected_calls

View File

@ -0,0 +1,34 @@
"""Test Dynalite switch."""
from dynalite_devices_lib.switch import DynalitePresetSwitchDevice
import pytest
from .common import (
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("switch", DynalitePresetSwitchDevice)
async def test_switch_setup(hass, mock_device):
"""Test a successful setup."""
await create_entity_from_device(hass, mock_device)
entity_state = hass.states.get("switch.name")
assert entity_state.attributes["friendly_name"] == mock_device.name
await run_service_tests(
hass,
mock_device,
"switch",
[
{ATTR_SERVICE: "turn_on", ATTR_METHOD: "async_turn_on"},
{ATTR_SERVICE: "turn_off", ATTR_METHOD: "async_turn_off"},
],
)