Z-Wave Device Registry Support (#17291)

* Add device_registry support for sensor and switch domains

* Add device_registry support for light

* Add device registry to binary_sensor, climate, cover

* Add device registry to zwave fan

* Fix test for config entry loading

* lint

* revert erroneous modification

* Revert device_registry.py change
This commit is contained in:
Charles Garwood 2018-10-16 08:58:25 -04:00 committed by Paulus Schoutsen
parent c6d9ceca63
commit 9c52a3ce22
10 changed files with 189 additions and 31 deletions

View File

@ -7,10 +7,11 @@ https://home-assistant.io/components/binary_sensor.zwave/
import logging import logging
import datetime import datetime
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.event import track_point_in_time from homeassistant.helpers.event import track_point_in_time
from homeassistant.components import zwave from homeassistant.components import zwave
from homeassistant.components.zwave import ( # noqa pylint: disable=unused-import from homeassistant.components.zwave import workaround
async_setup_platform, workaround)
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
DOMAIN, DOMAIN,
BinarySensorDevice) BinarySensorDevice)
@ -19,6 +20,23 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = [] DEPENDENCIES = []
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Old method of setting up Z-Wave binary sensors."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Z-Wave binary sensors from Config Entry."""
@callback
def async_add_binary_sensor(binary_sensor):
"""Add Z-Wave binary sensor."""
async_add_entities([binary_sensor])
async_dispatcher_connect(hass, 'zwave_new_binary_sensor',
async_add_binary_sensor)
def get_device(values, **kwargs): def get_device(values, **kwargs):
"""Create Z-Wave entity device.""" """Create Z-Wave entity device."""
device_mapping = workaround.get_device_mapping(values.primary) device_mapping = workaround.get_device_mapping(values.primary)

View File

@ -6,14 +6,15 @@ https://home-assistant.io/components/climate.zwave/
""" """
# Because we do not compile openzwave on CI # Because we do not compile openzwave on CI
import logging import logging
from homeassistant.core import callback
from homeassistant.components.climate import ( from homeassistant.components.climate import (
DOMAIN, ClimateDevice, STATE_AUTO, STATE_COOL, STATE_HEAT, DOMAIN, ClimateDevice, STATE_AUTO, STATE_COOL, STATE_HEAT,
SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE,
SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE) SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE)
from homeassistant.components.zwave import ( # noqa pylint: disable=unused-import from homeassistant.components.zwave import ZWaveDeviceEntity
ZWaveDeviceEntity, async_setup_platform)
from homeassistant.const import ( from homeassistant.const import (
STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE)
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -42,6 +43,22 @@ STATE_MAPPINGS = {
} }
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Old method of setting up Z-Wave climate devices."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Z-Wave Climate device from Config Entry."""
@callback
def async_add_climate(climate):
"""Add Z-Wave Climate Device."""
async_add_entities([climate])
async_dispatcher_connect(hass, 'zwave_new_climate', async_add_climate)
def get_device(hass, values, **kwargs): def get_device(hass, values, **kwargs):
"""Create Z-Wave entity device.""" """Create Z-Wave entity device."""
temp_unit = hass.config.units.temperature_unit temp_unit = hass.config.units.temperature_unit

View File

@ -5,18 +5,36 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/cover.zwave/ https://home-assistant.io/components/cover.zwave/
""" """
import logging import logging
from homeassistant.core import callback
from homeassistant.components.cover import ( from homeassistant.components.cover import (
DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION) DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION)
from homeassistant.components import zwave from homeassistant.components import zwave
from homeassistant.components.zwave import ( # noqa pylint: disable=unused-import from homeassistant.components.zwave import (
ZWaveDeviceEntity, async_setup_platform, workaround) ZWaveDeviceEntity, workaround)
from homeassistant.components.cover import CoverDevice from homeassistant.components.cover import CoverDevice
from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Old method of setting up Z-Wave covers."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Z-Wave Cover from Config Entry."""
@callback
def async_add_cover(cover):
"""Add Z-Wave Cover."""
async_add_entities([cover])
async_dispatcher_connect(hass, 'zwave_new_cover', async_add_cover)
def get_device(hass, values, node_config, **kwargs): def get_device(hass, values, node_config, **kwargs):
"""Create Z-Wave entity device.""" """Create Z-Wave entity device."""
invert_buttons = node_config.get(zwave.CONF_INVERT_OPENCLOSE_BUTTONS) invert_buttons = node_config.get(zwave.CONF_INVERT_OPENCLOSE_BUTTONS)

View File

@ -7,11 +7,12 @@ https://home-assistant.io/components/fan.zwave/
import logging import logging
import math import math
from homeassistant.core import callback
from homeassistant.components.fan import ( from homeassistant.components.fan import (
DOMAIN, FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH, DOMAIN, FanEntity, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
SUPPORT_SET_SPEED) SUPPORT_SET_SPEED)
from homeassistant.components import zwave from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -35,6 +36,22 @@ SPEED_TO_VALUE = {
} }
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Old method of setting up Z-Wave fans."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Z-Wave Fan from Config Entry."""
@callback
def async_add_fan(fan):
"""Add Z-Wave Fan."""
async_add_entities([fan])
async_dispatcher_connect(hass, 'zwave_new_fan', async_add_fan)
def get_device(values, **kwargs): def get_device(values, **kwargs):
"""Create Z-Wave entity device.""" """Create Z-Wave entity device."""
return ZwaveFan(values) return ZwaveFan(values)

View File

@ -7,13 +7,14 @@ https://home-assistant.io/components/light.zwave/
import logging import logging
from threading import Timer from threading import Timer
from homeassistant.core import callback
from homeassistant.components.light import ( from homeassistant.components.light import (
ATTR_WHITE_VALUE, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, ATTR_WHITE_VALUE, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR,
ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_COLOR, ATTR_TRANSITION, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP, SUPPORT_COLOR,
SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, DOMAIN, Light) SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE, DOMAIN, Light)
from homeassistant.components import zwave from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import
from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.helpers.dispatcher import async_dispatcher_connect
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -43,6 +44,22 @@ TEMP_WARM_HASS = (TEMP_COLOR_MAX - TEMP_COLOR_MIN) / 3 * 2 + TEMP_COLOR_MIN
TEMP_COLD_HASS = (TEMP_COLOR_MAX - TEMP_COLOR_MIN) / 3 + TEMP_COLOR_MIN TEMP_COLD_HASS = (TEMP_COLOR_MAX - TEMP_COLOR_MIN) / 3 + TEMP_COLOR_MIN
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Old method of setting up Z-Wave lights."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Z-Wave Light from Config Entry."""
@callback
def async_add_light(light):
"""Add Z-Wave Light."""
async_add_entities([light])
async_dispatcher_connect(hass, 'zwave_new_light', async_add_light)
def get_device(node, values, node_config, **kwargs): def get_device(node, values, node_config, **kwargs):
"""Create Z-Wave entity device.""" """Create Z-Wave entity device."""
refresh = node_config.get(zwave.CONF_REFRESH_VALUE) refresh = node_config.get(zwave.CONF_REFRESH_VALUE)

View File

@ -5,14 +5,31 @@ For more details about this platform, please refer to the documentation
at https://home-assistant.io/components/sensor.zwave/ at https://home-assistant.io/components/sensor.zwave/
""" """
import logging import logging
from homeassistant.core import callback
from homeassistant.components.sensor import DOMAIN from homeassistant.components.sensor import DOMAIN
from homeassistant.components import zwave from homeassistant.components import zwave
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Old method of setting up Z-Wave sensors."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Z-Wave Sensor from Config Entry."""
@callback
def async_add_sensor(sensor):
"""Add Z-Wave Sensor."""
async_add_entities([sensor])
async_dispatcher_connect(hass, 'zwave_new_sensor', async_add_sensor)
def get_device(node, values, **kwargs): def get_device(node, values, **kwargs):
"""Create Z-Wave entity device.""" """Create Z-Wave entity device."""
# Generic Device mappings # Generic Device mappings

View File

@ -6,13 +6,30 @@ https://home-assistant.io/components/switch.zwave/
""" """
import logging import logging
import time import time
from homeassistant.core import callback
from homeassistant.components.switch import DOMAIN, SwitchDevice from homeassistant.components.switch import DOMAIN, SwitchDevice
from homeassistant.components import zwave from homeassistant.components import zwave
from homeassistant.components.zwave import workaround, async_setup_platform # noqa pylint: disable=unused-import from homeassistant.helpers.dispatcher import async_dispatcher_connect
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Old method of setting up Z-Wave switches."""
pass
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Z-Wave Switch from Config Entry."""
@callback
def async_add_switch(switch):
"""Add Z-Wave Switch."""
async_add_entities([switch])
async_dispatcher_connect(hass, 'zwave_new_switch', async_add_switch)
def get_device(values, **kwargs): def get_device(values, **kwargs):
"""Create zwave entity device.""" """Create zwave entity device."""
return ZwaveSwitch(values) return ZwaveSwitch(values)
@ -25,8 +42,8 @@ class ZwaveSwitch(zwave.ZWaveDeviceEntity, SwitchDevice):
"""Initialize the Z-Wave switch device.""" """Initialize the Z-Wave switch device."""
zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN) zwave.ZWaveDeviceEntity.__init__(self, values, DOMAIN)
self.refresh_on_update = ( self.refresh_on_update = (
workaround.get_device_mapping(values.primary) == zwave.workaround.get_device_mapping(values.primary) ==
workaround.WORKAROUND_REFRESH_NODE_ON_UPDATE) zwave.workaround.WORKAROUND_REFRESH_NODE_ON_UPDATE)
self.last_update = time.perf_counter() self.last_update = time.perf_counter()
self._state = self.values.primary.data self._state = self.values.primary.data

View File

@ -66,6 +66,9 @@ DEFAULT_CONF_INVERT_OPENCLOSE_BUTTONS = False
DEFAULT_CONF_REFRESH_VALUE = False DEFAULT_CONF_REFRESH_VALUE = False
DEFAULT_CONF_REFRESH_DELAY = 5 DEFAULT_CONF_REFRESH_DELAY = 5
SUPPORTED_PLATFORMS = ['binary_sensor', 'climate', 'fan',
'light', 'sensor', 'switch']
RENAME_NODE_SCHEMA = vol.Schema({ RENAME_NODE_SCHEMA = vol.Schema({
vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
vol.Required(const.ATTR_NAME): cv.string, vol.Required(const.ATTR_NAME): cv.string,
@ -224,7 +227,6 @@ async def async_setup_platform(hass, config, async_add_entities,
discovery_info[const.DISCOVERY_DEVICE], None) discovery_info[const.DISCOVERY_DEVICE], None)
if device is None: if device is None:
return False return False
async_add_entities([device]) async_add_entities([device])
return True return True
@ -777,6 +779,10 @@ async def async_setup_entry(hass, config_entry):
hass.services.async_register(DOMAIN, const.SERVICE_START_NETWORK, hass.services.async_register(DOMAIN, const.SERVICE_START_NETWORK,
start_zwave) start_zwave)
for entry_component in SUPPORTED_PLATFORMS:
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
config_entry, entry_component))
return True return True
@ -928,9 +934,13 @@ class ZWaveDeviceEntityValues():
async def discover_device(component, device, dict_id): async def discover_device(component, device, dict_id):
"""Put device in a dictionary and call discovery on it.""" """Put device in a dictionary and call discovery on it."""
self._hass.data[DATA_DEVICES][dict_id] = device self._hass.data[DATA_DEVICES][dict_id] = device
await discovery.async_load_platform( if component in SUPPORTED_PLATFORMS:
self._hass, component, DOMAIN, async_dispatcher_send(
{const.DISCOVERY_DEVICE: dict_id}, self._zwave_config) self._hass, 'zwave_new_{}'.format(component), device)
else:
await discovery.async_load_platform(
self._hass, component, DOMAIN,
{const.DISCOVERY_DEVICE: dict_id}, self._zwave_config)
if device.unique_id: if device.unique_id:
self._hass.add_job(discover_device, component, device, dict_id) self._hass.add_job(discover_device, component, device, dict_id)
@ -1010,6 +1020,18 @@ class ZWaveDeviceEntity(ZWaveBaseEntity):
"""Return a unique ID.""" """Return a unique ID."""
return self._unique_id return self._unique_id
@property
def device_info(self):
"""Return device information."""
return {
'identifiers': {
(DOMAIN, self.node_id)
},
'manufacturer': self.node.manufacturer_name,
'model': self.node.product_name,
'name': node_name(self.node),
}
@property @property
def name(self): def name(self):
"""Return the name of the device.""" """Return the name of the device."""

View File

@ -8,7 +8,7 @@ from homeassistant.helpers.entity import Entity
from .const import ( from .const import (
ATTR_NODE_ID, COMMAND_CLASS_WAKE_UP, ATTR_SCENE_ID, ATTR_SCENE_DATA, ATTR_NODE_ID, COMMAND_CLASS_WAKE_UP, ATTR_SCENE_ID, ATTR_SCENE_DATA,
ATTR_BASIC_LEVEL, EVENT_NODE_EVENT, EVENT_SCENE_ACTIVATED, ATTR_BASIC_LEVEL, EVENT_NODE_EVENT, EVENT_SCENE_ACTIVATED,
COMMAND_CLASS_CENTRAL_SCENE) COMMAND_CLASS_CENTRAL_SCENE, DOMAIN)
from .util import node_name, is_node_parsed from .util import node_name, is_node_parsed
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -110,6 +110,18 @@ class ZWaveNodeEntity(ZWaveBaseEntity):
"""Return unique ID of Z-wave node.""" """Return unique ID of Z-wave node."""
return self._unique_id return self._unique_id
@property
def device_info(self):
"""Return device information."""
return {
'identifiers': {
(DOMAIN, self.node_id)
},
'manufacturer': self.node.manufacturer_name,
'model': self.node.product_name,
'name': node_name(self.node)
}
def network_node_changed(self, node=None, value=None, args=None): def network_node_changed(self, node=None, value=None, args=None):
"""Handle a changed node on the network.""" """Handle a changed node on the network."""
if node and node.node_id != self.node_id: if node and node.node_id != self.node_id:

View File

@ -703,21 +703,24 @@ class TestZWaveDeviceEntityValues(unittest.TestCase):
const.COMMAND_CLASS_SWITCH_BINARY], const.COMMAND_CLASS_SWITCH_BINARY],
}}} }}}
values = zwave.ZWaveDeviceEntityValues( with patch.object(zwave, 'async_dispatcher_send') as \
hass=self.hass, mock_dispatch_send:
schema=self.mock_schema,
primary_value=self.primary,
zwave_config=self.zwave_config,
device_config=self.device_config,
registry=self.registry
)
values._check_entity_ready()
self.hass.block_till_done()
assert discovery.async_load_platform.called values = zwave.ZWaveDeviceEntityValues(
assert len(discovery.async_load_platform.mock_calls) == 1 hass=self.hass,
args = discovery.async_load_platform.mock_calls[0][1] schema=self.mock_schema,
assert args[1] == 'binary_sensor' primary_value=self.primary,
zwave_config=self.zwave_config,
device_config=self.device_config,
registry=self.registry
)
values._check_entity_ready()
self.hass.block_till_done()
assert mock_dispatch_send.called
assert len(mock_dispatch_send.mock_calls) == 1
args = mock_dispatch_send.mock_calls[0][1]
assert args[1] == 'zwave_new_binary_sensor'
@patch.object(zwave, 'get_platform') @patch.object(zwave, 'get_platform')
@patch.object(zwave, 'discovery') @patch.object(zwave, 'discovery')