Skip to content

Commit

Permalink
Add Dynalite switch platform (home-assistant#32389)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
balloob authored Mar 5, 2020
1 parent d216c1f commit 521cc72
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 31 deletions.
52 changes: 44 additions & 8 deletions homeassistant/components/dynalite/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
"""Support for the Dynalite networks."""

import asyncio

import voluptuous as vol

from homeassistant import config_entries
Expand All @@ -10,18 +13,26 @@
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,
)

Expand All @@ -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,
},
)

Expand All @@ -62,7 +88,10 @@ def num_string(value):
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,
}
)

Expand Down Expand Up @@ -120,20 +149,27 @@ 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


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
6 changes: 3 additions & 3 deletions homeassistant/components/dynalite/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def __init__(self, hass, config):
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)

Expand All @@ -31,7 +31,7 @@ async def async_setup(self):
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)
Expand Down
4 changes: 1 addition & 3 deletions homeassistant/components/dynalite/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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):
Expand All @@ -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
Expand Down
11 changes: 9 additions & 2 deletions homeassistant/components/dynalite/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,28 @@
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
7 changes: 1 addition & 6 deletions homeassistant/components/dynalite/light.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
"""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


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
)


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/dynalite/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
29 changes: 29 additions & 0 deletions homeassistant/components/dynalite/switch.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/components/dynalite/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
6 changes: 3 additions & 3 deletions tests/components/dynalite/test_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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"
Expand All @@ -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"
Expand Down
8 changes: 6 additions & 2 deletions tests/components/dynalite/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
34 changes: 34 additions & 0 deletions tests/components/dynalite/test_switch.py
Original file line number Diff line number Diff line change
@@ -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"},
],
)

0 comments on commit 521cc72

Please sign in to comment.